代码随想录算法训练营第四天|24.两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II
链表结点
public class ListNode {
// 值
int val;
// 指针
// 淡化指针
// 指的下一个结点
public ListNode next;
ListNode() {
}
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
24.两两交换链表中的节点
解题思路(交换不明白,看了卡哥)
确实有难度
- 需要考虑链表中结点的个数
- 需要考虑从哪个位置操作结点
- 链表断了的位置,需要记录断掉的结点
- 如何链接链表
- 考虑从哪个位置操作下一个结点
- 如何从该循环结束
代码
public class SwapPairs {
public ListNode solute(ListNode head) {
// 虚拟结点
ListNode dummy = new ListNode(-1, head);
ListNode cur = dummy;
// 偶数交换cur后两个不为空 奇数交换cur后一个不为空
while (cur.next != null && cur.next.next != null) {
// 提前存 链接会断的结点
ListNode temp1 = cur.next;
ListNode temp2 = cur.next.next.next;
// 开始链接链表操作
// 头换成2
cur.next = cur.next.next;
// 2链上1
cur.next.next = temp1;
// 1链上3
temp1.next = temp2;
// 将cur 移动到下次操作的位置
cur = cur.next.next;
}
// 返回虚拟结点的next 则为新链表的head
return dummy.next;
}
}
19.删除链表的倒数第N个节点
解题思路一(自己想的笨办法)
- 首先获得链表的结点个数sz
- 遍历完后,需要将cur重新设置到head
- 判断n和sz的大小,如果等于sz=n就是首结点,如果n=1就是尾结点
- 排除以上可能性,开始考虑 该结点的上一个 也就是sz-n+1位 cur.next = cur.next.next
- 解决后,返回头节点
- 忘了还要考虑链表为空的情况
代码
public class RemoveNthFromEnd {
public static void main(String[] args) {
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
// ListNode node3 = new ListNode(3);
// ListNode node4 = new ListNode(4);
// ListNode node5 = new ListNode(5);
// ListNode node6 = new ListNode(6);
// ListNode node7 = new ListNode(7);
node1.next = node2;
// node2.next = node3;
// node3.next = node4;
// node4.next = node5;
// node5.next = node6;
// node6.next = node7;
System.out.println(removeNthFromEnd(node1, 2));
}
static ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(-1, head);
ListNode cur = head;
int sz = 0;
ListNode temp1 = null;
ListNode temp2 = null;
if (head == null) return null;
while (cur != null) {
sz++;
cur = cur.next;
}
cur = dummy.next;
if (sz == n) {
cur = cur.next;
return cur;
}
for (int i = 1; i < sz - n + 1; i++) {
if (i == sz - n) {
temp1 = cur;
temp2 = cur.next.next;
break;
}
cur = cur.next;
}
if (temp1 == null) return null;
temp1.next = temp2;
return dummy.next;
}
}
解法思路二 - 运用虚拟结点(录友提供)
- 计算链表结点的数量
- 使用头节点,可以有效处理掉,不知道头尾结点的后面和前面都是谁
总结 链表题目一定要多使用虚拟结点,结果题目的效果杠杠滴
代码
public class RemoveNthFromEnd1 {
static ListNode removeNthFromEnd(ListNode head, int n) {
ListNode prehead = new ListNode(-1, head);
ListNode cur = head;
//计算节点的数量
int size = 0;
while (cur != null) {
size++;
cur = cur.next;
}
//找到要删除节点的前一个节点
cur = prehead;
for (int i = 0; i < size - n; i++) {
cur = cur.next;
}
cur.next = cur.next.next;//删除目标节点
return prehead.next;
}
}
解题思路四(后续补充卡哥)
面试题 02.07. 链表相交
解题思路(卡哥)
双指针 永远的神 要明白什么是链表相交,是你两个同时指向同一个结点了,而不是结点的val相同 理解了之后可以开始写题啦
- 求两个链表交点的指针
- 设置curA curB 求出 各自的size
- 然后让链表尾对齐(或者可以通过反转链表 正对齐)
- 然后开始逐个比较节点,当节点不同,同时下一个节点,相同返回
- 没有最后return null
解题代码
让gpt帮我看了看,犯了很多小失误,都是复制粘贴的错
gpt也有出错的时候
public class GetIntersectionNode {
static ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 处理cur 本身,不改变链表的结构
ListNode curA = headA;
int szA = 0;
//ListNode curB = headA;
ListNode curB = headB; // 错误修正1
int szB = 0;
while (curA != null) {
szA++;
curA = curA.next;
}
while (curB != null) {
//szA++;
szB++; // 错误修正2
curB = curB.next;
}
curA = new ListNode(-1, headA);
curB = new ListNode(-1, headB);
// curA = headA; // 错误修正3
// curB = headB; // 错误修正4
// 只能说gpt也有犯错的时候 错误修正3/4 糊涂呀 gpt
// 边界条件的解决最好用虚拟头节点 好处多多
// 不用再考虑头结点的处理问题了
if (szA == szB) {
while (curA != curB) {
curA = curA.next;
curB = curB.next;
if (curA == curB) {
return curA;
}
}
} else if (szA > szB) {
for (int i = 0; i < szA - szB; i++) {
curA = curA.next;
}
while (curA != curB) {
curA = curA.next;
curB = curB.next;
if (curA == curB) {
return curA;
}
}
} else {
for (int i = 0; i < szB - szA; i++) {
curB = curB.next;
}
while (curA != curB) {
curA = curA.next;
curB = curB.next;
if (curA == curB) {
return curA;
}
}
}
return null;
}
}
142.环形链表II
解题思路(直接学习卡哥)
理解下题目,应该是 有一个结点被指了两次
怎么找到被指了两次的结点
快慢指针 永远的神 这不就是追击问题
数学真的太美妙了 算法真的太美妙了
代码
public class DetectCycle {
static ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {// 有环
ListNode index1 = fast;
ListNode index2 = head;
// 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}
gpt总结
这个代码的逻辑是基于“快慢指针”或“双指针”技术的。存在两个指针,一个快指针(fast)一次移动两个节点,一个慢指针(slow)一次移动一个节点。
首先,快指针和慢指针从链表的头部开始移动。如果链表有环,那么快指针和慢指针最终会在环内的某个位置相遇。如果链表没有环,那么快指针会先到达链表的尾部,此时返回 null。
如果快慢指针在环内相遇了,我们现在需要找到环的起点。其中一个思想是,从头部和相遇点分别设定两个指针,每次各走一步,那么当这两个指针再次相遇时,相遇点就是环的起点。
这个想法的依据是数学定理:在快慢指针相遇后,头部节点到环的起点的距离等于当前相遇点到环的起点的距离。
因此,当 index1 和 index2 指针相遇时,该相遇点即为环的起点,返回 index1 或者 index2 即可。 如果 index1 和 index2 指针未能相遇,那么表示链表无环,返回 null。 这种算法的时间复杂度是 O(N),空间复杂度是 O(1),其中 N 是链表的长度。因此,此算法效率极高。