链表的相关习题(持续更新)
无头单向非循环链表实现(无头指的是无单独的节点头)
链表是由一个个节点组成的,所以需要创建节点类
对于每个链表,都有一个头结点,所以创建一个成员变量head,用于指向头结点,用size记录链表的长度
这里需要用private修饰size是因为防止被外界随意修改,所以需要提供对应的公开方法
头插法
一般来说,一定要考虑链表为空的情况,尤其是不带头的链表
如果链表为空,需要让头结点直接指向该节点
如果链表不为空,需要让node 的next指向头结点,然后head指向新的头结点node
但其实如果head等于null,走不等于null的语句也可以正确执行,所以可以优化成这样
最后不要忘记了需要维护的 size
总结
- 头插法不用考虑链表为空的情况(不为空的代码包含两种情况)
尾插法
由于尾插法需要找到最后一个节点的位置,然后将最后一个节点的next指向新插入节点,所以需要考虑链表为空的情况
总结
- 尾插法需找到链表最后一个节点
- 尾插法需额外考虑链表为空的情况
指定下标插入
当下标为0或者是size时(size为链表长度),分别调用 头插法和尾插法即可
当index不是0下标和size下标时,思路如下:
- 找到index下标节点的前一个节点
- 先将新节点指向 index下标的节点
- 将cur的next指向新节点
代码中需要再考虑一些细节
- 需判断下标是否合法
- 调用头插法和尾插法后 记得直接return结束函数
总结
- 对于index下标,考虑合法性,考虑头插尾插情况
- 定义初识值为head的指针走index-1步
- 不需要额外考虑链表为空的情况
查看单链表中是否包含值为key的节点
思路:遍历整个链表,判断每个节点的值即可
总结
-
遍历一遍链表判断即可
-
不需额外考虑链表为空
删除第一次出现值为key的节点
思路:
- 遍历链表,找到值为key的节点的前一个节点(不考虑头结点值为key的情况)
- 若找到了,进行删除操作;没找到,则无事发生
- 若头结点值为key,则头结点移一下即可
假设删除33
语句为 cur.next = cur.next.next;
最后考虑头结点的好处:
- 若在开头就考虑,则需循环重复判断头结点(因为中间的代码无法考虑头结点),并需要额外判断head是否为空,有可能链表里只有一个节点并且是目标节点,不判断会报空指针异常(总之就是需要多考虑)
总结
- 需额外考虑链表为空
- 遍历链表找到需要删除的节点的上一个节点
- 最后考虑头结点
- 若维护的size变量,记得加上size–.
删除所有值为key的节点
思路:
- 与删除一个值为key的节点的思路差不多,不会在遍历里
- 只是需要格外注意一些细节
删除元素需要将cur的next指向删除节点的next,此时cur不能往后一步走,因为有可能删除节点的下一个节点也有可能值为key
所以只有不删除元素时,cur才能往后走一步
总结
- 需考虑链表为空的情况
- 遍历链表,判断cur.next不是cur,保证 cur是每次删除节点的前一个节点(不会删除头结点)
- 注意只有当 没删除节点时才移动
- 最后需要考虑头结点的情况
得到单链表的长度
只需要返回维护的成员变量size即可
总结
- 链表长度既可以维护一个size变量,也可以通过遍历链表获取
- 维护变量size会降低运行时间,但代码容易犯错,需时刻提醒自己还有个size变量
打印链表
遍历链表即可,可以特判当链表为空时打印null
总结
根据需求进行打印
清空链表
清空链表虽可以直接令head等于null,但过于暴力
将每个节点的next都置为null则可以提高内存回收效率
思路如下:
- 定义两个引用cur和curN
- 临时存储cur的next节点
- 将cur的next置为null
4.令cur = curN
- 重复第2、3、4步
最后不能忘记了维护的size
总结
- 直接head = null,太过暴力,虽系统也会自动回收内存
- 遍历整个链表,将每个节点的next置为null,可以提高内存回收效率
- 若维护了变量size,记得将size置为0
//无头单向非循环链表
public class SingleLinkedList {
//静态内部类 节点
public static class ListNode {
public int val;
public ListNode next;
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
}
//成员变量头结点
public ListNode head;
//链表的长度
private int size;
//头插法
public void addFirst(int data) {
//链表为空情况不用特殊考虑
ListNode node = new ListNode(data);
node.next = head;
head = node;
this.size++;
}
//尾插法
public void addLast(int data) {
//核心思路:找到最后一个节点
ListNode node = new ListNode(data);
if (head == null) {//链表为空的情况
head = node;
this.size++;
return;
}
ListNode cur = head;//找到最后一个节点的位置
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;//最后一个节点的next等于新节点
this.size++;
}
//任意位置插入,第一个数据节点为0号下标
public boolean addIndex(int index, int data) {
if (index < 0 || index > this.size) {
return false;//下标不合法
}
if (index == 0) {//下标为0相当于头插
addFirst(data);
return true;
}
if (index == this.size) {//下标为size相当于尾插法
addLast(data);
return true;
}
ListNode node = new ListNode(data);
//让一个节点指针走 index-1 步
ListNode cur = head;
int count = index - 1;
while (count > 0) {
cur = cur.next;
count--;
}
node.next = cur.next;
cur.next = node;
this.size++;
return true;
}
//查找在单链表当中是否包含值为key的节点
public boolean contains(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//删除第一次出现值为key的节点
public void remove(int key) {
if (head == null) {//空链表删除不了
return;
}
ListNode cur = head;//
while (cur.next != null) {
if (cur.next.val == key) {
cur.next = cur.next.next;
this.size--;
return;//删除一个直接走人
}
cur = cur.next;
}
//考虑头结点是key的情况
if (head.val == key) {
head = head.next;
this.size--;
}
}
public void display() {
if (head == null) {
System.out.println("null");
return;
}
ListNode cur = head;
while (cur != null) {
if (cur != head) {
System.out.print("-->");
}
System.out.print(cur.val);
cur = cur.next;
}
System.out.println();
}
//删除所有值为key的节点
public void removeAllKey(int key) {
if (head == null) {//空链表删不了
return;
}
ListNode cur = head;
while (cur.next != null) {
if (cur.next.val == key) {
cur.next = cur.next.next;
this.size--;
} else {//关键点易错点
cur = cur.next;
}
}
if (head.val == key) {
head = head.next;
this.size--;
}
}
//清空链表
public void clear() {
ListNode cur = head;
ListNode curN = null;
while (cur != null) {
curN = cur.next;
cur.next = null;
cur = curN;
}
head = null;
this.size = 0;
}
}
无头双向非循环链表模拟实现
链表由节点组成,需要写一个节点类
双向链表每个节点都多存储了前一个节点的引用
这里比刚才的单链表多维护一个last,记录的是链表中的最尾节点的引用
头插法
思路:
-
链表为空时,将head和last 都等于新创建的节点
-
不为空时,进行如下操作
-
node.next = head;
-
head.prev = node
-
head = node;
总结
- 需考虑链表为空的情况,并注意容易忽略的last变量
- 注意别忘了prev和size
尾插法
思路:
- 考虑链表为空的情况,为空的时候将node直接赋值给head和last
- 不为空时,利用last进行尾插
- last.next = node;
- node.prev = last;
- last = node;
总结
- 需考虑链表为空的情况,跟头插法一样,也可以直接调用头插法
- 利用last引用插入节点
任意位置插入,第一个数据节点为0号下标
思路:
-
先判断下标的合法性
-
当下标为0时,相当于头插法
-
当下标为链表的长度size时,相当于尾插法
-
除了头插和尾插,接下来找到index下标的节点
此时cur 指向的节点的下标就是index -
进行插入操作
第一步node.next = cur;
第二步node.prev = cur.prev;
第三步cur.prev.next = node;
第四步cur.prev = node;
总结
- 考虑插入下标 index 的合法性
- 考虑是头插法和尾插法的情况
- 找到index下标的节点,进行插入操作
- 不需要额外考虑链表为空的情况
查找值为key的节点是否在链表中
思路:与单链表代码相同,遍历链表即可
总结
- 与单链表代码相同
- 无需额外考虑链表为空的情况
删除第一次出现关键字为key的节点
思路:
- 与单链表思路相同,但需要额外判断删除的节点是否为last节点
- 如果为last节点,则last节点 向前一步走
- 如果不是last节点,则正常按照单链表时的删除方式删除
写到这里时,就需要判断删除的节点是否为整个链表的最后一个节点,也就是last节点
在最后考虑头节点时,也同样需要考虑删除的节点是否为last节点
总结
- 需额外考虑链表为空
- 定义引用cur遍历链表,判断cur的下一个节点是否为目标删除节点
- 需要每次判断删除的节点是否为 last 节点
- 最后考虑头结点,同样删除的时候也需考虑头节点
- 如果删除的节点是last节点,记得改变完last节点后,将last的节点的next置为null
- 当删除的节点是头节点时,记得将新head 的prev置为null
删除所有值为key的节点
思路:
- 只需将 只删除一个key的节点 的代码稍作修改即可
总结
- 只有当没有删除节点时,cur才需要往后移动一位
- 需额外考虑链表为空
- 定义引用cur遍历链表,判断cur的下一个节点是否为目标删除节点
- 需要每次判断删除的节点是否为 last 节点
- 最后考虑头结点,同样删除的时候也需考虑头节点
- 如果删除的节点是last节点,记得改变完last节点后,将last的节点的next置为null
- 当删除的节点是头节点时,记得将新head 的prev置为null
打印链表
根据自己想法来即可
清空链表
思路:
- 定义两个引用 cur 和 curN
- curN 指向cur的下一个节点
- 将cur节点的 next和prev置为null
- 将cur指向curN,curN指向cur的下一个节点
总结
- 需要考虑链表为空
- 通过两个引用,一个引用用于置为null,一个引用用于记录位置
- 注意最后不要忘记将last也置为空
选择题1
所以选A,在进行链表指向的改动时,一定要最后修改已知节点的指向,比如这个p节点,一定是先修改p.prev的next,然后再修改p的prev,如果先修改p的prev,那么就丢失了原先的那个点。
选择题2
解:当head.next 等于 head或 head.prev 等于 head 时,此时说明链表为空,所以选C
给定 x, 把一个链表整理成前半部分小于 x, 后半部分大于等于 x 的形式
牛客网:给定 x, 把一个链表整理成前半部分小于 x, 后半部分大于等于 x 的形式
思路:
- 创建两个链表less和big,一个用于存储小于x的节点,一个用于存储大于等于x的节点
- 将链表less的尾和big的头拼接起来则为最终结果链表
创建两个节点存储两个链表的头节点
由于需要每次尾插,所以需要一个引用用于记录两个链表当中的尾节点
接着开始遍历链表
当节点的值小于x 时,需要考虑less的链表是否为空,只有不为空时,才可以用lessLast尾节点进行插入
当链表为空时,让头结点和尾节点都指向新节点
当链表不为空时,进行尾插法
接下来同理考虑 节点值大于等于x的情况
最后一定不能忘了让cur向前一步走
在创建好less和big链表后,接下来,需要将两个链表拼接起来
在拼接的时候,我们需要令lessLast.next = bigHead;
但是当less链表为空时,就不能这么调用
所以我们需要特判一下两个链表为空的情况
考虑特殊情况后即可直接头尾拼接即可
这里需要注意的是:一定要将big链表的尾节点的next置为空,它的next是链表当中的某一个节点或null
那为什么刚才特判的时候不置为空?是因为less和big当中的某一个链表为空,那么最后一个节点的next一定为null
public class Partition {
public ListNode partition(ListNode pHead, int x) {
if (pHead == null) {//链表为空
return null;
}
//记录两个链表的头节点
ListNode lessHead = null;
ListNode bigHead = null;
//记录两个链表的尾节点
ListNode lessLast = null;
ListNode bigLast = null;
//开始遍历原链表
ListNode cur = pHead;
while (cur != null) {
if (cur.val < x) {//节点值小于x
if (lessHead == null) {
lessHead = lessLast = cur;
} else {
lessLast.next = cur;
lessLast = cur;
}
} else {
if (bigHead == null) {
bigHead = bigLast = cur;
} else {
bigLast.next = cur;
bigLast = cur;
}
}
cur = cur.next;
}
//开始拼接
if (lessHead == null) {
return bigHead;
}
if (bigHead == null) {
return lessHead;
}
lessLast.next = bigHead;
bigLast.next = null;//*
return lessHead;
}
}
不带头链表做法(更简便)
可以用带头的链表,这样则不需要考虑下列几种情况
- 尾插时不需要考虑链表是否为空
- 拼接时不需要考虑 less 链表和 big 链表为空的情况
总结
流程:
- 创建 less 链表 和 big 链表,分别存储小于和大于等于x值的节点
- 遍历链表根据节点的值 进行尾插
- 将两个链表拼接起来
易错点和注意事项:
- 无论带不带头,都需在拼接时,将big链表尾节点的next置为null(非常容易错)
- 若带头,则需要在尾插时考虑链表为空的情况,在拼接时考虑less和big链表为空的情况
判断链表是否回文
思路:
方法1:
- 遍历链表将每个节点的值存到一个新数组中
- 利用数组判断是否回文
方法2:
- 利用快慢指针找到链表中间的节点(偶数个向尾偏)
- 翻转从中间节点向后的链表
- 判断是否回文
翻转前:
翻转后:
方法1在牛客网不能用,需要额外开辟空间
按照思路,我们首先先找到中间节点
快慢指针思路:
- 定义两个引用初始都指向头节点
- 一个每次走两步,一个每次走一步
- 当快的指针没法往后走时,两个指针停止移动
- 此时slow指向的节点就为目标节点
定义两个指针:
开始移动
注意事项:
- fast != null 一定要写在 fast.next != null 的前面
- fast != null 保证了走一步的时候不会空指针异常,fast.next != null保证了走的第二步不会空指针异常
- 为什么不用判断 slow 为空呢? 因为fast一定比slow快,fast一定比slow先为空或同时为空
得到中间节点后,接下来将以中间节点开头的链表翻转
翻转时,我们需要三个指针相互配合
大致步骤如下:
- 提前记录cur的下一个节点
- 将cur的next指向prev
- prev指向cur
- cur指向curN
需要注意的是,在翻转链表的时候,一定要将原链表的头节点的next置为null
可以将prev的初始值设置为null,这样当cur为头节点时,cur.next = prev 则可以满足要求
也可以特判一下,加一个if语句,不过前者更简单
翻转函数返回的是翻转后新链表的头节点
这里的curN可以是head也可以null,因为会在遍历时被改变
值得注意的是,不可以将 curN = cur.next;放在末尾,会导致空指针异常
当翻转结束时,prev会指向新链表的头节点,最后返回 prev 即可
接下来是判断
值得注意的是,循环条件不能是 cur1 != cur2,这样写会导致空指针异常。
当节点数为偶数个时,有一边会率先是null,然后next不了发生空指针异常。
如果想要在不破坏链表的情况下判断回文,那么就需要再把链表翻转回来
那么在判断回文时,就不能直接return,我们需要一个标记来判断是否为回文链表
完整代码:
总结
流程: 找中间节点 --> 翻转链表 --> 判断回文 (–> 给人家再翻转回来)
- 找到中间节点(向尾偏),利用快慢指针
- 翻转链表,从中间节点开始
- 判断回文,两指针面对面走
注意事项与易错点:
(1) 找中间节点时
- 注意fast != null 要写在 fast.next != null 的前面
(2) 翻转链表时
- 一定要将原头节点的 next置为null,将prev初始化为null为最佳解决方案
- curN = cur.next一定要在循环的最开头,否则会空指针异常
- 翻转后链表的新头节点为prev节点
(3)判断回文时
- while循环条件里不能是 cur1 != cur2,会导致空指针异常
- 若不想破坏原链表,则需要一个标记,在翻转回原链表后,返回标记即可
判定链表相交并求出交点
思路:
方法1:(哈希表法)
- 选择任意一个链表,将该链表的所有节点存储到哈希表中
- 遍历另一个链表,判断每个节点是否已经在哈希表中了
- 若在哈希表中存在,则该节点为链表相交节点
- 若遍历完链表,没有节点在哈希表中存在,则两链表不想交
方法2:
- 获取两个链表的长度
- 定义两个指针,长度较长的链表的指针 走长度差步
- 接着两指针同步开始移动,如果两节点相等,则该节点为相交节点
- 若任意指针为null,则说明没有交点
方法1:
所有链表题,都先特判null
接着选择一条链表,将该链表的所有节点存储到哈希表中,这里我选择了headA链表
然后遍历另一个链表,判断是否存在相交节点
完整代码如下
方法2:
链表题都需要特判 链表是否为空
判断完后接下来先获取链表的长度
获取长度后,谁长谁的指针先走长度差步
接着同步移动,若为同一节点,则返回该节点,若遍历完,则返回null代表无相交节点
完整代码:
总结
方法1:哈希表法
流程:
- 将一链表的所有节点存储到哈希表
- 遍历另一链表,若节点在哈希表中存在,该节点为目标节点
- 遍历完链表若未返回节点,则代表两链表为相交
易错点:
- 注意需特判链表为空的情况
- set中添加元素的方法是 add 不是put
方法2:
流程:
- 获取两链表的长度
- 派两个指针,长度长的链表的指针先走 长度差步
- 随后两指针同步移动
- 若同时指向相同节点,则该节点为相交节点
- 若遍历完未找到节点,则返回null
易错点:
- 需要判断链表为空
- 注意同步移动时,循环条件是 curA != null && curB != null
判定链表带环
思路:
方法1:(利用快慢指针)
- 定义fast和slow两个引用分别指向链表的头节点
- 在不超出链表长度的前提下,fast每次走两步,slow每次走一步
- 如果链表有环,那么两个指针一定会相遇,否则则无环
方法2:(利用哈希表)
- 遍历链表,将每个节点存储到哈希表中
- 若节点在哈希表中已经存在,则证明链表有环
方法1:
值得注意的是,fast != null 保证了fast走的第一步不会空指针异常 , fast.next != null 同理保证了走的第二步
而且fast != null 必须在 fast.next != null 的前面
if (fast == slow)该判断必须在slow和fast移动后
如果先判断,由于fast和slow的初始值都是head,那么只要进循环就都会返回true
方法2:
总结
方法1:(快慢指针法)
流程:
- 定义两个指针指向链表的头节点
- 一个每次走两步,一次每次走一步
- 若相遇则有环,若fast遍历完链表,则无环
易错点:
- 循环条件中的fast != null 一定在 fast.next != null 的前面
- 先移动,后判断,先判断fast == slow 由于初始值都是head,所以所有情况都是true了
- 需要判断链表是否为空
方法2:(哈希表法)
流程:
- 遍历链表,将每个节点存储到哈希表中
- 若节点已经在哈希表中,则有环
- 若遍历完链表,则无环
易错点:
- 需要判断链表是否为空
求环的入口点
方法1:(哈希表法)
思路:
- 遍历链表,将每个节点存储到哈希表中
- 若节点已在哈希表中,则该节点为环的入口点
该方法虽然简单,但是需要额外开一份空间
方法2:
思路:
- 利用快慢指针判定是否有环
- 若有环,则快慢两个指针的相遇点到入环节点的距离 等于 头节点到入环节点的距离(证明在后面)
- 再定义一个 指针指向头节点
- 指向相遇点的节点 和 指向头节点的指针以每次一步的速度 同步移动
- 当两个节点相同时,则为入环节点
方法1代码:
方法2:
距离相同的证明如下:
- 假设一个链表,快慢指针相遇点如下图所示 (有环就有相遇点)
记头节点到 入环点 的距离为 a
入环点到相遇点的距离为 b
相遇点到入环点的距离为 c
由图可得:
- 慢指针移动距离为 a + b
- 快指针移动距离为 a + n(b + c) + b,其中 n 为可能移动的圈数
- 由于快指针移动的距离是 慢指针的二倍
- 可得 2*(a + b) = a + n(b + c) + b
- 化简后可得 a = (n - 1) * (b + c) + c
- 该式子则可说明 假设 x 从头节点开始往入环点走,y从相遇点往入环点走,那么 x 走了a距离,y走了n-1圈 和 c距离后,两人就会在入环点相遇
问:如果慢指针也转圈了,那么公式是不是推错了?
答:如果转圈则距离为 a + b + n(a + b),在后面化简时 n1(a+b) 会与右边快指针的n2(a+b)消掉(两个圈数不一样所以n1n2),所以不用写
还有一种思路也可以证明距离相等:
- 在快慢指针到达相遇点时
- 此时在定义一个指针指向头结点记为point,该指针与slow的速度一样
- 接着slow和point指针同步移动
- 那么当point 走到相遇点时,slow指针相当于走了原 fast指针走过的距离
- 则point 指针和 slow 指针也会在 之前的相遇点相遇
- 在环中移动并且速度一样还能相遇
- 只能说明当point指针 走到入环点时与 slow已经相遇
代码:
1.特判链表为空
2.在进行完特判后,接着需要获取快慢指针的相遇点
获取相遇点可以封装成一个函数会比较简单考虑的少,这里先写一个不封装成函数的写法
除了slow指针和fast指针外,这里定义了一个标记flag,用于判断链表是否有环
注意这里的fast != null 和 fast.next != null,一定是fast != null 在前
注意这里的 if (fast == slow)判断一定是fast和slow移动后才判断
只有当移动后的两指针相遇了这一种情况,才说明有环
3.创建一个新指针,开始与 slow同步同速移动
由于无环的情况已经被排除了,所以走到这里一定有环,所以while循环条件判断里无需判断cur是否为空
相遇后返回cur或slow即可
//方法1:哈希表法
public ListNode detectCycle(ListNode head) {
//特判链表为空
if (head == null) {
return null;
}
//遍历链表
Set<ListNode> hash = new HashSet<>();
ListNode cur = head;
while (cur != null) {
//如果已存在,则该节点为入环节点
if (hash.contains(cur)) {
return cur;
} else {
hash.add(cur);
}
cur = cur.next;
}
return null;
}
//方法2:快慢指针法
public ListNode detectCycle(ListNode head) {
//特判链表为空
if (head == null) {
return null;
}
//获取快慢指针的相遇点
ListNode slow = head;
ListNode fast = head;
boolean flag = false;//标记链表是否有环
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {//只有当移动后的两指针相遇这一种情况
flag = true; //才能说明有环
break;
}
}
if (!flag) {//无环则返回null
return null;
}
//新节点与slow节点同步移动
ListNode cur = head;
while (cur != slow) {//一定有环,cur不会为空
cur = cur.next;
slow = slow.next;
}
return cur;
}
在求相遇点时,可以封装成一个函数,这样既简单又不会出错
总结
方法1:(哈希表法)
流程:
- 遍历链表,将所有的节点存储到哈希表中
- 若存储的节点已经在哈希表中存在,则该节点为环的入口点
- 若遍历完链表,则代表无环,则返回null
易错点:
- 需要判断链表为空
- set添加元素的方法是add,而不是put
方法2:(快慢指针法)
- 利用快慢指针判环,求得两指针相遇点
- 利用 头节点到入环点的距离 和 相遇点到入环点的距离相等这一现象
- 创建新指针与原slow节点同步同速开始移动
- 相遇时即为入环点
易错点:
- 需特判链表为空
- 求相遇点时,只有当slow和fast 移动后 的相遇才相当于是有环(有可能都停在head),所以while循环里需要先移动,后判断(特别易错)
- 求相遇点时,while循环的判断条件里的 fast != null 一定在 fast.next != null 的后面
- 求相遇点,封装成一个函数最佳(函数的功能为 有环返回相遇点,无环返回null),不用函数时,用一个布尔类型标记最佳
合并两个有序链表
记所需合并链表为 list1 和 list2,记结果链表为res
思路:
-
与合并两个有序数组思路大致相同
-
需要定义三个指针分别指向list1、list2和res的头节点。(记为cur1, cur2, last)
-
每次比较指向list1和list2的指针的值,值小的节点就尾插到res链表上
-
当cur1或指向 cur2等于null时,此时不等于null的一方直接接到 res 链表即可
模拟的过程如下:
其中res结果链表为 一个带头的节点,节点的值对结果毫无影响,可以使任意值
每次比较cur1 和 cur2 节点的值,1 < 2 所以将cur1 节点直接尾插到 res链表上,并且cur1和last要往后一步走
接下来再次比较两个指针所指向的节点的值,2 < 3,所以尾插cur2节点,然后记得last和cur2往后一步走
接着重复比较cur1 和 cur2的值,直到有一方的链表遍历完
在该种情况中,则是上面的链表先遍历完
此时只需要将cur2节点及其后面的节点,直接拼接到last后面即可
代码:
链表题都需要考虑链表为空的情况
接着 创建一个带头新链表 和 三个指针
接着开始擂台比较,只有小的一方可以走,大的需要留在擂台上继续比较
接着没遍历完的链表往后一拼接就行
最后返回头节点即可,注意,由于我们是带头的节点,所以返回的是newHead.next;
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//特判链表为空
//如果一方为空,合并完就是另一个链表
if (list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
//带头链表
ListNode newHead = new ListNode(0);
//三个指针
ListNode cur1 = list1;
ListNode cur2 = list2;
ListNode last = newHead;
//重复比较
while (cur1 != null && cur2 != null) {
if (cur1.val <= cur2.val) {
//尾插cur1节点
last.next = cur1;
last = cur1;
cur1 = cur1.next;
} else {
//尾插cur2节点
last.next = cur2;
last = cur2;
cur2 = cur2.next;
}
}
//拼接剩余节点,不用执着于last拼接完就有可能
//不是最后一个节点了
if (cur1 == null) {
last.next = cur2;
} else {
last.next = cur1;
}
return newHead.next;
}
总结
合并两个有序链表与合并两个有序数组思路差不多
流程:
- 创建一个带头的新链表
- 创建三个指针分别指向所给链表和新链表
- 擂台比较两链表指针的值,小的一方,尾插到新链表中,大的一方继续比较
- 当出现遍历完链表的情况,则谁没遍历完拼接谁
易错点:
- 需判断链表为空的情况,有一方为空,则直接返回另一链表的头节点
- 如果新链表不带头,需额外单独比较一次,给新链表的头节点初始化
- 如果新链表带头,则最后返回的是 头节点的下一个节点