day_04
传送门
-
24 - 两两交换链表中的节点 - 题目链接/文章讲解/视频讲解
-
19 - 删除链表的倒数第 N 个结点 - 题目链接/文章讲解/视频讲解
-
142 - 环形链表 Ⅱ - 题目链接/文章讲解/视频讲解
1. 昨日回顾
- 双指针法适用场景
- 两个指针一前一后不相邻节点间的操作
- 需要一个先行者确定位置,后发者处理情况
- 需要一快一慢遍历时
- 单指针适用场景
- 一前一后相邻节点的操作
- 一个指针遍历时就能处理的问题
- 递归适用场景
- 需要深入到链表尾部,再返回操作的类型
- 从 NULL 开始,处理到 NULL 结束的类型
- 好消息
- 基本递归能处理的,都能用双指针循环的方式处理
2. 题解
24 - 两两交换链表中的节点
- 思路及代码
-
C 语言
- 循环
struct ListNode* swapPairs(struct ListNode* head){ typedef struct ListNode ListNode; ListNode* dummyHead = malloc(sizeof(ListNode)); // 虚拟头节点 dummyHead -> next = head; // 头节点添加到链表里 ListNode* cur = dummyHead; // 每组待交换节点的前一个节点 ListNode* ptrPre; // 每组交换未完成时的前一个节点 ListNode* ptr; // ptrPre要指向的 next 节点 while(cur -> next != NULL && cur -> next -> next != NULL){ // 条件终止情况 ptrPre = cur -> next; // 存前面的节点信息 cur -> next = cur -> next -> next; // ptr = cur -> next -> next; // 存储后面的节点地址 cur -> next -> next = ptrPre; // 修改组内指针位置 ptrPre -> next = ptr; // cur = ptrPre; // 移动 cur 到下一组的前一个指针 } return dummyHead -> next; }
- 递归 (后期另寻时间实现)
-
19 - 删除链表的倒数第 N 个结点
-
思路及代码
- 双重循环暴力法( O(n^2) 不推荐哦 )
- 快慢指针处理
- 递归方式回退处理
-
快慢指针处理
- 过程如下
1. 定义虚拟节点 2. 定义一个快指针,先遍历 n+1 个节点(单链表处理的时候,需要拿到待处理节点的前一个节点) 3. 慢指针在快指针到第 n+1 个后,两个指针开始同步移动 4. 直到快指针 = NULL,就对满指针指向的节点进行处理
-
为什么先遍历 n+1 个节点 ? 而不是 n ?
-
C语言实现代码如下
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){ typedef struct ListNode ListNode; ListNode* dummyHead = malloc(sizeof(ListNode)); // 虚拟头节点 dummyHead -> next = head; // 头节点添加到链表里 ListNode* fast = dummyHead; // 定义快慢指针 ListNode* slow = dummyHead; int i = 0; while(i < n+1 && fast != NULL){ // 移动快指针,快指针要移动到待删除的后一个才行 fast = fast -> next; i++; } while(fast != NULL){ fast = fast -> next; slow = slow -> next; } slow -> next = slow -> next -> next; return dummyHead -> next; }
142 - 环形链表 Ⅱ
法一 : hash表处理思路
- 判断有无环 + 找环起始点的思路
- 判断环是否存在 - hash表处理的过程
1. 添加虚拟头节点,添加到链表里 2. 遍历链表,并维护一个 hash表,表里的 k,v 存储ListNode节点, 这是因为 : 节点里的 val 有可能相同,但是内存地址是不同的,节点是否相同看的是内存地址,而不是数值 判断当前遍历到的节点是否已经存在于 hash表里 T : 说明有环 F : 添加该节点到 hash 表里 3. 如果遍历到 null,说明没环
- 有环的时候,如何寻找环的起始点呢
通过 hash 表可以获取环起点,是第一个已在表里的 ListNode 新建指针,指向头节点,开始遍历,找到 ListNode == 环起点的ListNode ListNode 就是环的起始节点啦,此时直接返回就好
- java 代码实现 - 其实可以直接用 set 集合的。。。
public class Solution { public static ListNode detectCycle(ListNode head) { HashMap<ListNode, ListNode> hashMap = new HashMap<>(); // 创建 hash表来存储数据处理 ListNode dummyHead = new ListNode(); dummyHead.next = head; ListNode cur = dummyHead.next; // 用来遍历的指针 while (cur != null){ if (hashMap.containsKey(cur)){ // 如果这个节点已经存在,说明有环 return hashMap.get(cur); } hashMap.put(cur, cur); cur = cur.next; } // while return null; } }
法二 : 双指针处理思路
- 判断有无环 + 找环起始点的思路
- 判断有无环 - 双指针处理的过程
1. 添加虚拟头节点,添加到链表里 2. 定义两个指针,一快一慢,fast 和 slow 3. 确定 fast、slow 每次行走的步数 4. fast、slow同时遍历,如果fast = null 了,说明没环(fast先遍历完链表) 5. 如果遍历的途中发现 fast = slow,说明有环,且是第一次再 - (不会出现fast跳过slow的情况吗?) 不会! 这是因为 slow 进入环后,假设 slow 的位置是终点 fast 在环里是落后于 slow 的 且每次追赶 slow 的步数为 1,不会出现错过的情况 - (为什么 fast、slow 第一次相遇的时候,slow 一定还没绕完第一圈环呢) 两指针的速度差为一步,但两指针的距离差小于等于一个环的距离 等于的情况 - slow 还没绕环就结束了 小于的情况 - fast 以每次一步的速度追赶 dis / 1 = dis < 环长度 所以才有 slow 一定还没绕完第一圈环的结论
- 有环的时候,如何寻找环的起始点呢
关于这个,推荐看卡哥的文章,文章地址已经贴在上面了 这里关注两点 1. fast 进入环后,可能绕环绕了不止一圈,所以是 n(y+z) 2. 对等式(n-1)(z+y)+z+y-y = x的理解 (n-1)(z+y)是指 fast 指针绕了 n-1 圈环 z 是 fast、slow 第一次相遇时,fast 距离环起点的位置 这个等式翻译一下就是 fast 从 y 位置走 n-1 圈环,后再走z步就能到 x(环起点位置) 现在问题转化为如何求得这些步数,上面的式子中,左右的整体量都是不知道的 好消息是现在不是纯数学维度获取数值,就像物理学家可以使用除了纯数学以外的物理属性一样 虽然都是未知量,但我们可以通过模拟来得到左右两边的整体值 左边是fast指针的模拟,右边是从头开始的新指针,当左右两式相等,就可以找到环的起点了 左右两边走指定的相同步数就能相遇,相同步数是奇?是偶? 无法判断 那就让两边每次都走一步,直到相遇
- C语言代码实现如下
struct ListNode *detectCycle(struct ListNode *head) { typedef struct ListNode ListNode; ListNode* dummyHead = malloc(sizeof(ListNode)); dummyHead -> next = head; ListNode* fast = dummyHead; ListNode* slow = dummyHead; // 如果快指针走到了 null,说明没有环,fast向前走两步,和两两交换的条件一样 while(fast -> next != NULL && fast -> next ->next!=NULL){ fast = fast -> next ->next; // fast 移动两步 slow = slow -> next; // slow 移动一步 if(fast == slow){ // 指针的好处,可以之间判断地址值是否一样,java要用equal才行 // 进入环了,也就是说,到了 y 的位置 ListNode* travel = dummyHead; while(fast != travel){ fast = fast -> next; travel = travel -> next; } return travel; } } return NULL; }
- 判断有无环 - 双指针处理的过程