bu目录
一张图概括链表![](https://img-blog.csdnimg.cn/direct/6b3e2dde2abf4125b798395c7a2d32df.png)
总结
- 循环条件,什么情况应该判断指针本身为空呢?
- 答:看操作的不是虚拟头结点,是的话就不用判断本身,不是的话需要判断;另外看这个这个遍历的指针最后需要走到哪里 需不需要对最后一个节点做操作
- 学链表的时候注意画图设计简单case边界case来调试,现在写链表的题都是可以脑子有链表图debug了
- 虚拟头结点作用?
- 每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。
- 链表多往双指针上想!
- 不要把值和节点的概念混淆起来,节点是一个实例,占用一块空间,值只是它的成员变量。值怎样和节点本身没有任何关系,一个实例只由它的地址唯一确定!
- 三道题套路解决递归问题 | lyl's blog 里面有递归的题解 很好的博客!
1、两两交换链表的节点 206
思路
- 保存2个交换后会断的结点,画图更新即可
- 外层while要有两个next的非空检测
- 如果在循环之外定义了 temp 和 temp1,这导致它们在循环的每次迭代中都不会更新。这意味着 temp 和 temp1 在第一次迭代之后就不再指向正确的节点,这会破坏链表的结构。
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(-1, head);
ListNode cur = dummyHead;//虚拟节点常用2步曲
while(cur.next != null && cur.next.next != null) {//用&& 不然无限循环!一个next放前面
ListNode temp = cur.next; //这两个临时变量放循环里面,不然你就更新它们,你这不是附空值,你得放循环里
ListNode temp1 = temp.next.next;//这两个节点得提前储存,不然改链表指向后就定位不到了
//三步改写
cur.next = cur.next.next;
cur.next.next = temp;
temp.next = temp1;
cur = cur.next.next;//别忘了移动
}
return dummyHead.next;
}
}
2、删除倒数第n个节点 19
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
核心就是,保持两个指针距离为n,这样就能准确找到倒数第n个节点的位置了。
思路
- 一眼快慢指针
- 要记得指向要删除的元素的前一个,这样才好删,所以fast到n+1步,慢的正好指到要删除节点的前一个点
注意点
- 边界值带入一个特例算一下
- 当节点只要不是指向虚拟结点时,都要判断自身是否为null,不能直接调用next!!!
//思路:双指针,快的先动n+1步再走,慢的正好指到要删除节点的前一个点
ListNode dummy = new ListNode(-1, head);
//ListNode cur = dummy;
ListNode fast = dummy, slow = dummy;
int i = 1;//从1开始,带入一个数字算一下
while (i <= n + 1){//快的先到指定位置
fast = fast.next;
i++;
}
while (fast != null) {
//当节点只要不是指向虚拟结点时,都要判断自身是否为null,不能直接调用next
fast = fast.next;
slow = slow.next;//结束时slow正好在要删除节点的前一个
}
slow.next = slow.next.next;
return dummy.next;
}
3、链表相交
面试题 02.07. 链表相交 - 力扣(LeetCode)
两个链表的交点不是数值相同,而是指针相同。因此我们可以让 AB 两个链表末尾对齐,比较两个指针所指的位置是否相同判断是否存在交点。
思路1---长短链对齐
- 算出2链长度
- for 循环将长链先走lenB-lenA步
- 两链同时向前走,相等就返回
注意点
- 算完长度后移动长链时,要重新将指针摆回头结点
- 两种向后移动代码,参看代码块:一种while(curA != null),一种 while(curA != curB),但是条件判断放的位置不同
代码1---长短链对齐
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//先行移动长链来实现同步移动
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
//算长度
for (; curA != null; curA = curA.next){
lenA++;
}
for (; curB != null; curB = curB.next){
lenB++;
}
curA = headA;//重新刷新赋值,不用再写ListNode了
curB = headB;
//对齐
if (lenA >= lenB) {
for (int i = 0; i < lenA - lenB; i++) {
curA = curA.next;
}
} else {
for (int i = 0; i < lenB - lenA; i++) {
curB = curB.next;
}
}
//开始移动,相同就返回;注意不是判断值,是判断指针是否相同
// while (curA != curB) {//这种while中带限制写法,等while运行完再进行比较
// curA = curA.next;
// curB = curB.next;
// }
// if (curA == curB) {
// return curA;
// }
// return null;//一直没有返回空
while (curA != null) {//上面那种写法是全部比一遍,也是停在相同节点处
//注意:得先进行比较,不然就漏掉第一个点了
if (curA == curB) {//这种写法就是遇到指向相同的就返回了
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
思路2-- 双指针 两链合并
使用双指针的方法,可以将空间复杂度降至 O(1)
当链表 headA 和 headB都不为空时,创建两个指针 pA 和 pB,初始时分别指向两个链表的头节点 headA和 headB,然后将两个指针依次遍历两个链表的每个节点。具体做法如下:
- 每步操作需要同时更新指针 pA和 pB
- 如果指针 pA不为空,则将指针 pA 移到下一个节点;如果指针 pB不为空,则将指针 pB移到下一个节点。
- 如果指针 pA 为空,则将指针 pA移到链表 headB的头节点;如果指针 pB为空,则将指针 pB移到链表 headA 的头节点。
- 当指针 pA和 pB 指向同一个节点或者都为空时,返回它们指向的节点或者 null
核心:curA == null ? headB : curA.next;
代码2-- 双指针 两链合并
两种--写不写复杂表达式
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//两链合并遍历找交点
ListNode curA = headA;
ListNode curB = headB;
//如果你想提升点速度,就加一个空判断
if (headA == null || headB == null) {
return null;
}
while (curA != curB) {
//?后面不用写两个curA=,已经有了
curA = curA == null ? headB : curA.next;//双等号
curB = curB == null ? headA : curB.next;
}
return curA;//这个不相交也能直接输出null
}
//方法二:双指针:长短指针都遍历一遍,会在一个值相遇,进行返回
ListNode temp = headA, temp1 = headB;
while (temp != temp1) {
if (temp == null) {
temp = headB;
} else {
temp = temp.next;
}
if (temp1 == null) {
temp1 = headA;
} else {
temp1 = temp1.next;
}
}
return temp1;
}
思路3 -- 哈希集合
哈希做法比较好理解
思路
1、将A链存入set
2、用个指针指向b,找B链在集合中有没有一致的即可
空间复杂度O(m+n)
代码3 -- 哈希集合
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//哈希集合做法,将a存入,再查询b中有没有一致的
Set<ListNode> visited = new HashSet<ListNode>();
ListNode cur = headA;
//将A链存入set
while (cur != null) {
visited.add(cur);
cur = cur.next;
}
//找B链有没有一致的
cur = headB;//已经定义过的变量不用再加变量名
while (cur != null) {
if (visited.contains(cur)){
return cur;
}
cur = cur.next;
}
return null;
}
4、环形链表 142
理解
- 是否有环:快慢指针相交
- 入环节点:index1和index2相遇
- 首先判断是否有环,其次寻找交点
- 这种链表的寻找入口、寻找尾部节共同节点啥的基本都是双指针;
图示
推导
思路
- 快慢链表相遇等于有环
- 快慢指针2步、1步,如果相遇,进行入口判断
- 因为慢x+y,快x+y+n(y+z),这两个相等,推导一下x = (n - 1) (y + z) + z,所以x=z,那就2个指针,一个放头结点、一个放相遇点,相等就可以了
- 以fast为条件先进去判断while循环
- 判断是否有环:if (fast == slow),记录相遇点,推导之后相遇点和head的中间就是环入口!
- 开始找入环节点,这个要放在if的判断条件下面,while (index1 != index2) 就能找到
注意点
- 找入环节点,这个要放在if的判断条件下面
- 最外层的while循环,没有定虚拟节点,你就得考虑fast自身是否为空;并且因为fast是走2步,所以要判断next
public ListNode detectCycle(ListNode head) {
//快慢链表相遇等于有环
ListNode fast = head, slow = head;
//以fast为条件先进去判断
//这个while循环,没有定虚拟节点,你就得考虑fast自身是否为空
while(fast != null && fast.next != null){//因为fast是走2步,所以要判断next
fast = fast.next.next;
slow = slow.next;
//开始判断是否有环
if (fast == slow) {
ListNode index1 = head;
ListNode index2 = fast;//记录相遇点,推导之后相遇点就是环入口
//开始找入环节点,这个要放在if的判断条件下面
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;//没有就返回空
}
拜拜~~