今日任务
● 24. 两两交换链表中的节点
● 19.删除链表的倒数第N个节点
● 面试题 02.07. 链表相交
● 142.环形链表II
● 总结
详细布置
24. 两两交换链表中的节点
链表的操作进来使用头结点,来辅助迅速找到循环体
题目链接:. - 力扣(LeetCode)
题目链接/文章讲解/视频讲解: 代码随想录
//不改val只改next指针
public ListNode swapPairs(ListNode head) {
//最终返回的头结点
ListNode res = null;
//定义虚拟头结点
ListNode prev = new ListNode();
prev.next = head;
//定义指针,因为是两两交换,所以指针的滑动最小距离是2
ListNode cur = head;
ListNode tmpNext = null;
ListNode tmpNextNext = null;
while(cur != null && cur.next != null){
tmpNext = cur.next;
//第一次时记录翻转后的头结点
if(res == null){
res = tmpNext;
}
tmpNextNext = tmpNext.next;
//开始翻转
prev.next = tmpNext;
tmpNext.next = cur;
cur.next = tmpNextNext;
//指针后移
prev = cur;
cur = tmpNextNext;
}
return res == null ? head:res;
}
核心点:
1.链表的头结点是链表的起点与其他的节点有定义上的差异,所以定义虚拟头结点的只是为了把原来的头结点与其他的节点的差异找平,可以更快的找到循环体
2.链表的问题与数组问题是一样的,如果理解的数组与链表的差异点,链表问题与数组问题的解法也就一致了
核心差异:
数组可以通过arr[i] 的方式O(1)找到元素,所以知道索引i也就知道了值,但是链表只能通过next 的方式O(n)找元素,且每次cur指针变化后,原指针对应的指向就变了,所以需要不停地用临时变量来记录指针位置节点的 prev 与 next ,特别涉及到改变next指针指向的时候
19.删除链表的倒数第N个节点
- 双指针解法
- 双指针的操作,要注意,删除第N个节点,那么我们当前遍历的指针一定要指向 第N个节点的前一个节点,建议先看视频。
题目链接:. - 力扣(LeetCode)
题目链接/文章讲解/视频讲解:代码随想录
//删除倒数第n个节点
//双指针方法,实际就是,快指针走N步之后,慢指针再跟着一起走,快指针到头时,则慢指针的位置就是要删除的前一个位置
public ListNode removeNthFromEnd(ListNode head, int n) {
//新建一个虚拟头节点指向head
ListNode dummyNode = new ListNode(0);
dummyNode.next = head;
//快慢指针指向虚拟头节点
ListNode fastIndex = dummyNode;
ListNode slowIndex = dummyNode;
// 只要快慢指针相差 n 个结点即可
for (int i = 0; i <= n; i++) {
fastIndex = fastIndex.next;
}
while (fastIndex != null) {
fastIndex = fastIndex.next;
slowIndex = slowIndex.next;
}
// 此时 slowIndex 的位置就是待删除元素的前一个位置。
// 检查 slowIndex.next 是否为 null,以避免空指针异常
if (slowIndex.next != null) {
slowIndex.next = slowIndex.next.next;
}
return dummyNode.next;
}
无脑暴力解:
算总长->确定N的位置->删除
//删除倒数第n个节点
//链表的个数未知,则第一反应就是需要先获取链表的总个数,这道题实际本质上只是为了求链表长度
public ListNode removeNthFromEnd(ListNode head, int n) {
//算总长
int total = 0;
ListNode cur = head;
while(cur != null && cur.next != null){
cur = cur.next.next;
total+=2;
}
if(cur !=null && cur.next == null){
total++;
}
//删节点
ListNode prev = new ListNode();
prev.next = head;
ListNode res = null;
ListNode tmpCur = head;
int count = 1;
int target = total-n+1;
while(tmpCur!=null){
//删除节点
if(count == target){
prev.next = tmpCur.next;
}
if(res == null){
res = prev.next;
}
prev = tmpCur;
tmpCur = tmpCur.next;
count ++;
}
return res;
}
面试题 02.07. 链表相交
题目链接:. - 力扣(LeetCode)
题目链接/文章讲解:代码随想录
核心思路:
A走到底后,再接着走B; B走到底后,接着走A,此时相遇点就是交叉点,判断是否相遇直接用 == 内存地址判断即可
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
//直接判断内存地址即可
while(curA != curB){
curA = curA == null ? headB:curA.next;
curB = curB == null ? headA:curB.next;
}
return curA;
}
142.环形链表II
算是链表比较有难度的题目,需要多花点时间理解 确定环和找环入口,建议先看视频。
题目链接:
题目链接/文章讲解/视频讲解:代码随想录
核心思路:
快慢指针相遇则存在环,相遇后,快指针降低为1,慢指针从head起始处一起跑再相遇时就是环入口(具体证明过程见代码随想录)
public ListNode detectCycle(ListNode head) {
if(head == null){
return null;
}
//是否存在环
boolean hasCycle = false;
ListNode slow = head,fast = head;
while (fast.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;
//快慢指针相遇,即存在环
if(slow == fast){
hasCycle = true;
break;
}
}
if(hasCycle){
//存在环
slow = head;
while(slow != fast){
fast = fast.next;
slow = slow.next;
}
return slow;
}
return null;
}
总结
- 对于链表的指针的逻辑可以参考数组去理解,需要注意差异点
- 虚拟头结点可以拉平head与其他节点的差异
- 链表相交,突破点在于路径和相同
- 链表是否有环,需要用快慢指针,判断环入口,需要在相遇处用新指针一起走直到在此次相遇
- 注意心境!不要过于功利,也不需要过于追求最优解,本来就是普通人,忘记也正常,但是很明显的是,做的多了debug多了,对于问题边界的判断力也在不断的增强