【算法模板】链表篇—(附牛客习题)
文章目录
前言
好久没有更新这个模块文章了呀。也有许多粉丝跑过来催更嘿嘿,所以在百忙之中来给大家续写 算法模板 这一系列的文章。
本篇文章所用的是 牛客 在线编程的算法习题。在这里博主要感谢 csdn和牛客 两位大大,一个提供笔记平台,一个提供习题练习平台。得所以让我进步!非常感谢!
练习题地址: 牛客算法习题地址
那好话不多说,我们启航!!!
什么是链表???
简介:
由百度百科解释:
链表
是一种物理存储单元上非连续、非顺序的存储结构
,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点
(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域
,另一个是存储下一个结点地址的指针域
。
链表的类型:
常见的链表有:
-
单链表
是最常见的一种链表,也就是上图所示。
-
双链表
每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
-
循环链表
顾名思义,循环链表就是一个死循环的链表。如下图所示:
当然我们也会遇见相对应的算法习题哦!
链表节点的构建(Java和Python版本)
链表节点得构建就像盖房子的打地基一样,就算你上面弄得在天花乱坠。如果你的地基不稳,你可能就没有机会来实现你天花乱坠的想象。
在面试过程中链表有的时候也是需要我们自己来一步步的进行构建,所以今天也就给大家带来相对应的Java
和Python
两种版本的构建。
Java链节点表构建
public class ListNode {
int val;//根据情况来对val定义属性。
ListNode next = null;//一般节点没有赋值就为空。
}
Python节点构建
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
注意: 一般在核心代码模块的时候是不需要我们来构建链表
的节点的,但是如果是在ACM模式则就需要我们会构建相对应的节点
。
所以我们需要牢固掌握节点的构建方法!
链表的基本操作
和数组一样,链表也是有自己的增删改查的方法。那么今天我就给大家带来相对应的链表基本操作嘿嘿嘿。
添加链表节点
添加链表我们可以分为以下两种添加方式:
-
插入式添加
-
链表末尾添加
首先我们来看末尾式添加:
可以直接让尾部节点指向新增节点。
实现代码:
#为了方便讲解我就给大家使用python作为讲解对象啦。
class Solution:
def add(self , A: ListNode, C) -> ListNode:
a = A
#循环到最后一个节点的位置
while a.next:
a = a.next
#让最后一个节点指向添加节点
a.next = C
#返回A节点即可
return A
插入式添加
来看下图所示:
我们能看到,如果插入添加节点的话,是会让B
的指针指向C
,在让C
节点指向D
。则完成一个新节点的插入。
代码实现:
class Solution:
def add(self , A: ListNode, C, k:int) -> ListNode:
#k表示插入位置
a = A
b = a.next
for i in range(k):
a = a.next
b = b.next
#确保不是插入末尾
if b:
a.next = C
C.next = b
else:
a.next = C
return A
以上就是链表的两种增添方式了啦!
删除链表节点
说到删除节点
,其实和上面的插入节点
的方法其实是一样的,只不过两者是反过来进行操作的啦。我们可以从图中来看看哦。
代码实现:
class Solution:
def delete(self , A: ListNode, k:int) -> ListNode:
#k表示删除位置
a = A
for i in range(k):
a = a.next
#确保不是插入末尾
if b:
a.next = C.next
else:
a.next = None
return A
反转链表节点
反转链表可是我们在面试中刷题遇见的最多的一道题嘿嘿嘿。在牛客排行榜中公认的第一名
首先来给大家看看反转链表的具体图:
我们先不在这里讲具体的代码实现嘿嘿,我们放到下面来做经典的反转链表
。我们就能感受到他的魅力了哈哈哈。
习题练习(附练习题)
首先来一道经典的反转链表嘿嘿嘿
。
反转链表
题目:
思路:
(1)定义两个指针: pre 和 cur ;pre 在前 cur 在后。
(2)每次让 pre 的 next 指向 cur ,实现一次局部反转
(3)局部反转完成之后, pre 和 cur 同时往前移动一个位置
(4)循环上述过程,直至 pre 到达链表尾部
图解:
代码的实现:
Java:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
//pre指针:用来指向反转后的节点,初始化为null
ListNode pre = null;
//当前节点指针
ListNode cur = head;
//循环迭代
while(cur!=null){
//Cur_next 节点,永远指向当前节点cur的下一个节点
ListNode Cur_next = cur.next;
//反转的关键:当前的节点指向其前一个节点(注意这不是双向链表,没有前驱指针)
cur.next = pre;
//更新pre
pre = cur;
//更新当前节点指针
cur = Cur_next ;
}
//为什么返回pre?因为pre是反转之后的头节点
return pre;
}
}
Python:
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 返回ListNode
def ReverseList(self, pHead):
# write code here
# 空链表或只有一个节点得链表
if not pHead&nbs***bsp;not pHead.next:
return pHead
cur = pHead
pre = None
# 循环移动curent和pre,移动过程中反转 curent.next=pre
while cur:
next_node = cur.next
cur.next = pre
pre = cur
cur = next_node
return pre
其他思路:
递归:
- 使用递归函数,一直递归到链表的最后一个结点,该结点就是反转后的头结点,记作 ans
- 此后,每次函数在返回的过程中,让当前结点的下一个结点的 next 指针指向当前节点。
- 同时让当前结点的 next 指针指向NULL ,从而实现从链表尾部开始的局部反转
- 当递归函数全部出栈后,链表反转完成。
栈:
可以先把列表中的所有数据放入到栈中,然后在逐一出栈即可成功!
链表内指定区间反转
题目:
思路:
三指针:
判断参数
head
是否为空,若空则直接返回;判断m
是否等于n
,相等则无需进行操作,直接返回;创建一个头结点
head_node
,以便对整个链表的操作实现统一;指针begin初始化为头节点
head_node
,begin将作为待反转链表第一个结点的前驱指针。然后通过变量i(初始化为0)记录当前遍历的结点个数,顺序遍历链表的前m-1
个结点时,begin
不断向前移动,最终指向第m-1
个结点;指针
start
指向待反转链表的第一个结点,初始化为begin->next
;指针finish
指向待反转链表的最后一个结点,初始化为begin
。然后在顺序遍历到第n
个结点的过程中,finish
不断向前移动,直到指向第n个结点。指针
end
指向待反转链表的最后一个结点的下一个位置;采用三指针法反转链表:
- 初始时,指针p指向start,指针q指向start->next;
- 反转过程:
tmp = q->next; q->next = p;p = q;q = tmp
- 结束条件:
q == finish
最后一步:将反转后的链表与其余begin之前的链表和end之后的链表进行连接:
begin->next = finish;
start->next = end;
图解:
代码实现:
Java:
ListNode* reverseBetween(ListNode* head, int m, int n)
{
if(!head || m == n)
return head;
ListNode *head_node = new ListNode(-1);
head_node->next = head; // 创建链表的头结点
ListNode *begin = head_node; // begin指向待反转链表的前一个结点
int i = 0;
while( i <= m -2) {
// 循环结束, begin指向第m-1个结点
begin = begin->next;
++i;
}
ListNode *start = begin->next;
ListNode *finish = begin;
while( i < n) {
// 循环结束, finish指向第n个结点
finish = finish->next;
++i;
}
ListNode *end = finish->next; // end指向第待反转链表的下一个结点
ListNode *p = start;
ListNode *q = start->next;
begin->next = nullptr;
finish->next = nullptr;
while(p != finish) {
ListNode *tmp = q->next;
q->next = p;
p = q;
q = tmp;
}
begin->next = finish;
start->next = end;
head = head_node->next;
delete head_node;
return head;
}
Python:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param head ListNode类
# @param m int整型
# @param n int整型
# @return ListNode类
#
class Solution:
def reverseBetween(self , head: ListNode, m: int, n: int) -> ListNode:
res = ListNode(-1)
res.next = head
pre = res
cur = head
for i in range(1, m):
pre = cur
cur = cur.next
for j in range(m, n):
tmp = cur.next
cur.next = tmp.next
tmp.next = pre.next
pre.next = tmp
return res.next
链表中的节点每k个一组翻转
题目:
思路:
本题虽然是链表习题,但是也结合了其他的数据结构。在本题就可以使用
栈
的方法来进行一个每K
个节点的反转。
- 1、首先遍历链表k个结点入栈,若k大于链表的长度则直接返回链表不翻转
- 2、栈内结点出栈(翻转)
- 3、判断剩下的链表个数够不够k个(少于k个不需要反转,大于k个重复 1、2步骤)
- 4、将已翻转的部分与剩下的链表连接起来
代码实现:
Python:
class Solution:
def reverseKGroup(self , head , k ):
# write code here
# 用于链表头元素
Phead = ListNode(None)
p = Phead
while True:
count = k
stack = []
tmp = head
# 进栈
while count and tmp:
stack.append(tmp)
tmp = tmp.next
count -= 1
# 跳出上面循环,tmp是第k+1的元素
# 如果循环结束,count不为0,则代表不足k个元素
if count:
p.next = head
break
# 对k个元素进行反转
# 出栈
while stack:
p.next = stack.pop()
p = p.next
# 与剩下链表链接起来
p.next = tmp
head = tmp
return Phead.next
由于时间原因本题没有Java
版本的题解,但是后期博主也是会慢慢的补上来的啦。接下来就是最激动的环节啦!!!
习题的系统刷题:
题目 |
---|
合并两个排序的链表 |
合并k个已排序的链表 |
判断链表中是否有环 |
链表中环的入口结点 |
链表中倒数最后k个结点 |
删除链表的倒数第n个节点 |
两个链表的第一个公共结点 |
链表相加(二) |
链表的奇偶重排 |
给他干!!!
其他算法模板文章推荐
【算法模板】BFS秒杀模板—附练习题(开启大海贼时代) |
---|
【算法模板】DFS秒杀模板—附练习题(阳光号启航) |
【算法模板】动态规划(基础DP篇) |
【算法模板】动态规划(基础背包篇)—附习题 |
好了那今天的文章就到这里了,现在是半夜1.30。我得赶紧睡了(狗命要紧),溜了溜了。