一、前言
今天是算法的第四天,对链表阶段进行一个结尾,今日任务:
二、 24. 两两交换链表中的节点
题目描述
思路分析
这道题我是使用了虚拟头节点方法来做的
内部实际就是循环遍历节点,然后提取不变的节点,两两交换节点,在赋值回去不变的节点就可以了
代码展示
public ListNode swapPairs(ListNode head) { // 设置虚拟头节点 ListNode dummyNode = new ListNode(0); dummyNode.next = head; ListNode prev = dummyNode; // 先进行下一个节点的判断, 在进行下下个节点的判断, 避免空指针异常 while (prev.next != null && prev.next.next != null) { // 缓存不变的链表信息(只动两个节点进行位置交换, 将两个节点之后的节点缓存下来) ListNode temp = prev.next.next.next; // 缓存第一个节点信息(先将第二个节点的位置放到第一个节点的位置上, 第一个节点就没有了节点信息) ListNode temp1 = prev.next; // 将第二个节点提取到第一个节点的位置 prev.next = prev.next.next; // 将不变的链表放置到 原第一个节点后面 temp1.next = temp; // 将原第一个节点放置到现第二个节点的位置 prev.next.next = temp1; // 链表移动两位 prev = prev.next.next; } return dummyNode.next; } 复制代码
提交结果
总结
这道题一个月之前做过,所以印象还是很深刻的,本题还可以通过递归来做,感兴趣的可以去看代码随想录网站
三、 19. 删除链表的倒数第 N 个结点
题目描述
思路分析
这道题还是通过虚拟头结点来做
难点在于我们不知道当前节点的长度,所以需要使用两个节点来做
先设置虚拟头结点 dummy,再用两个节点 = dummy,循环使 fast = fast.next, 循环结束之后 head长度 = n + fast长度,依此条件来对 slow进行循环获取到需要删除的节点
代码展示部分为上一次提交代码,注释更详细,本次提交代码可看【提交结果】
代码展示
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { // 为了方便对头节点操作, 还是使用虚拟头节点 `dummy` 来做 ListNode dummy = new ListNode(-1); dummy.next = head; // 创建一个链表 `slow` 表示当前节点 ListNode slow = dummy; // 使用一个链表 `fast` 去获取当前节点后第n个节点 ListNode fast = dummy; // 根据提示可知 n < 链表长度的, 所以使用fast去判断slow节点是不是倒数第n个节点 while (n-- > 0) { fast = fast.next; } // 记住 待删除节点slow 的上一节点 ListNode prev = null; // 循环终止条件是 `fast` 链表的 next == null while (fast != null) { // prev 存储当前节点 prev = slow; // slow和fast节点向下进一步 slow = slow.next; fast = fast.next; } // 循环结束之后, 上一节点是 prev, 下一节点是 slow.next, 删除show节点 prev.next = slow.next; // 下面是看大佬代码的收获 // 释放 待删除节点slow 的next指针, 这句删掉也能AC slow.next = null; return dummy.next; } } 复制代码
提交结果
总结
这道题第一眼就被倒数节点蒙住了,还在想怎么解决,看了眼LeetCode上次提交的代码(咱就说不是故意的,进入页面之后编辑器上就有上次提交的代码展示),直接就会了,还是要做过啊,做过最少就有一些思路了
四、 面试题 02.07. 链表相交
题目描述
思路分析
咱就说, 以前做 LeetCode注释记录的真全面
这道题印象还是很深刻的,需要注意的一点是判断的是节点地址是否相同,而不是节点值是否相同
根据示例可以知道,判断的关键在于两条链表的交点,那这个交点之后的链表长度肯定是相等的
- 首选去获取两条链表的长度
- 根据长度去对链表进行操作,使得两条链表长度保持一致
- 在去遍历链表,去判断两个链表的节点是否相同,节点相同的话就直接返回,不同的话返回null
代码展示
public ListNode getIntersectionNode(ListNode headA, ListNode headB) { // 两个链表, 接受 headA和 headB ListNode nodeA = headA; ListNode nodeB = headB; // 记录两个链表的长度 Integer skipA = 0, skipB = 0; // 计算出两个链表的长度 while(nodeA != null){ skipA++; nodeA = nodeA.next; } while(nodeB != null){ skipB++; nodeB = nodeB.next; } // 循环结束之后要重新为两个链表赋值 nodeA = headA; nodeB = headB; // 两个链表长度差 Integer n = skipA - skipB; // 让链表A为最长链表, 方便我们后续移动链表操作 if (skipA < skipB){ // 交换链表节点 ListNode node = nodeA; nodeA = nodeB; nodeB = node; // 如果B链表长. 那么长度差是负值, 所以这里要重新进行计算 n = skipB - skipA; } // n是长度差, 缩短链表A至长度与B相等 while(n-- > 0 ){ nodeA = nodeA.next; } // 记录返回值 ListNode node = null; // 循环判断链表值是否相等 while(nodeA != null){ // 如果两个值相等, 则跳出循环 if (nodeA == nodeB){ return nodeA; } nodeA = nodeA.next; nodeB = nodeB.next; } return null; } 复制代码
提交结果
总结
只能说,游刃有余,哈哈哈哈
五、 142. 环形链表 II
题目描述
解题思路
- 首先,我们要去判断当前链表 head是不是环形链表
- 新建两个链表 nodeA和 nodeB,去接收原链表 head
- 循环遍历,nodeA每次前进一步,nodeB每次前进两步
- 当链表相遇时,证明链表 head是环形链表
- 如果不是环形链表,则直接返回 null
- 如果是环形链表,那么 nodeA肯定会与 nodeB相遇
- 因为 nodeA每次前进一步,nodeB每次前进两步,所以 nodeA绕环形链表部分走一圈时,nodeB会绕环形链表走两圈,这期间不论环形链表部分是单数链表还是双数链表,肯定会相遇最少一次
- 现在我们确定了链表 head是环形链表之后,该去判断环形链表部分的初始节点位置,下面简称为入环点
- 假设链表 head的头结点到入环点的距离为 x,入环点到相遇点为 y,相遇点到入环点为 z
- 那么 nodeA走到相遇点的距离为 x + y
- nodeB走到相遇点的距离为 n(y + z) + x + y
- 又因为 nodeA走一步,nodeB走两步,所以: (x + y)* 2 = n(y + z) + x + y
- 解得: x = n(y + z) -y
- 整理可得: x = (n - 1)(y + z) + z
- 因为 nodeA跑一步 nodeB跑两步,所以 n是肯定大于 1的
- 又因为环形链表部分的长度等于 y + z
- 所以 x = z + nodeA绕环形链表圈数
- 那么我们现在要做的,就是让 nodeA跑 x距离,让 nodeB跑 (z + n圈)
- 所以我们让 nodeA = head,让 nodeA从头开始跑
- nodeB不变,因为当前 nodeB距离入环点就是 z的距离
- 同时让 nodeA和 nodeB每次循环前进一位,保证两个节点肯定会在入环点相遇
- 具体代码如下所示
代码展示
public static ListNode detectCycle(ListNode head) { // 搞两个链表,接收 head // A是慢链表,B是快链表 ListNode nodeA = head; ListNode nodeB = head; // 判断该链表是否为环形链表 boolean flag = false; while(nodeB != null && nodeB.next != null){ nodeA = nodeA.next; nodeB = nodeB.next.next; if (nodeA == nodeB){ flag = true; break; } } // 如果为环形链表,判断入口位置 if (flag){ nodeA = head; while(nodeA != nodeB){ nodeA = nodeA.next; nodeB = nodeB.next; } return nodeA; } return null; } 复制代码
提交结果
总结
这次解题思路和代码不是扒的上一次的了,是直接扒的之前的文章,本次完成代码在【提交结果】中
还记得我是从本题开始看的卡哥视频讲解,之后直接一发入魂
六、 1652. 拆炸弹
题目描述
思路分析
这道题我有三种思路去做
拼接数组
搞一个长度为 code.length * 2 的数组, 然后根据 k > 0或者小于 0的情况从后或者从前去遍历想加就可以了,很简单,但是很明显内存消耗会比较大
双重 for循环
for循环遍历 code数组,在根据 k > 0为判断条件写一个 for循环进行双重 for循环进行遍历,但是这个时间消耗也会很大,具体可能是 n * k倍?我不太会算这个,有大佬的话可以评论区指出来我会进行改正的
滑动窗口
先求出 code[0]位置上计算后的 sum,同时求出计算 code[0]处值范围的头 head下标和尾 tail下标,在遍历过程中,sum += code[head] - code[tail] 就可以了
我也是使用的这种方法做的
代码展示
public static int[] decrypt(int[] code, int k) { // 取出当前数组的长度 int length = code.length; // 设置返回值 int[] ans = new int[length]; // 如果 k==0 则直接返回, int数组默认元素为 0 if (k == 0){ return ans; } // 初始化总和为 sum = 0, count代表首次计算总和时计算元素的个数,Math.abs()方法是求取绝对值的 // 以 k>0 为条件,使用三元运算符动态获取头尾下标 int sum = 0, count = Math.abs(k), tail = k > 0? 1: 0, head = k > 0? 0 : length-1; // 计算 ans[0] 的值 while(count-- > 0){ sum += k > 0? code[head = (head + 1) % length] : code[tail = (tail - 1 + length) % length]; } // 赋值 ans[0] = sum; // 循环为 ans数组进行赋值 for (int i = 1; i < length; i++) { // 计算当前下标的 sum值, 同时 head++ sum += code[++head % length] - code[tail]; // 赋值 ans[i] = sum; // 计算尾下标 tail = ++tail % length; } return ans; } 复制代码
提交结果
总结
这道题一定要注意判断下标时对 length取余的操作,看我的提交结果就知道很容易出错了,【苦涩】