代码随想录算法训练营第4天| 24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II

24. 两两交换链表中的节点

想法

自己尝试去写,结果一直报错。总结原因是 对于节点更新的过程不是很明确,可能还没有将需要保存的节点信息保存下来就去更新。

看完视频

看完视频后尝试利用视频的思路自己写出代码。在写代码前首先应该明确3件事情:

  1. 需要设置几个节点
    ListNode cur; //指向要交换的两个节点的前一个节点
    ListNode temp1; // 保存要交换节点的第一个节点
    ListNode temp2; // 保存向要交换的两个节点的下一个节点
  2. 如何交换和更新
    cur.next = cur.next.next; // 会导致第一个节点丢失,所以要先用temp1保存下来。
    cur.next.next = temp1; // 会导致要交换节点的后面节点丢失,所以要先用temp保存后面节点的信息。
    temp1.next = temp2;
  3. 终止条件
    cur一直指向的都是要交换节点的前一个节点
    cur.next != null && cur.next.next != null

注意点

虚拟节点的使用
及时保存节点更新过程中可能导致丢失的节点信息
递归版本不是特别理解,可以参考一下视频b站递归方法讲解,复习的时候再理解一下。

// 迭代
class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode cur = dummyHead; //指向要交换的两个节点的前一个节点
        ListNode temp1 = head; // 保存要交换节点的第一个节点
        ListNode temp2 = null;  // 保存向要交换的两个节点的下一个节点
        while(cur.next != null && cur.next.next != null){
            temp1 = cur.next;
            temp2 = cur.next.next.next;

            cur.next = cur.next.next;
            cur.next.next = temp1;
            temp1.next = temp2;

            cur = cur.next.next;          
        }
        return dummyHead.next;
    }
}
// 递归版本
class Solution {
    public ListNode swapPairs(ListNode head) {
        // base case 退出提交
        if(head == null || head.next == null) return head;
        // 获取当前节点的下一个节点
        ListNode next = head.next;
        // 进行递归
        ListNode newNode = swapPairs(next.next);
        // 这里进行交换
        next.next = head;
        head.next = newNode;

        return next;
    }
}

19.删除链表的倒数第N个节点

双指针的操作,要注意,删除第N个节点,那么我们当前遍历的指针一定要指向 第N个节点的前一个节点,建议先看视频。
题目链接:leecode删除链表的倒数第N个节点
文章讲解/视频讲解:删除链表的倒数第N个节点

看到题目的想法

要删除倒数第k个节点,那我们首先要找到要删除节点的前一个节点,要找到前一个节点,我们肯定是要循环pre = pre.next得到。所以我们要确定循环次数,所以最开始我们要知道链表中的节点个数。所以初步考虑分3步进行

  1. 确定链表节点的个数nums
  2. 根据nums和n的值确定要删除节点的前一个节点
  3. 删除操作 pre.next = pre.next.next。
    emmm…感觉这样写有点暴力但是好歹能写出来正常运行

看完视频

双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。

  1. 定义fast指针和slow指针,初始值为虚拟头结点
  2. fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作)
  3. fast和slow同时移动,直到fast== null,此时slow就指向要删除节点的前一个节点

收获

这道题的关键就是找到要删除节点的前一个节点,用双指针来找
虚拟头节点的使用

 // 快慢指针
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyNode = new ListNode(0);
        dummyNode.next = head;

        ListNode fast = dummyNode;
        ListNode slow = dummyNode;

        // 快指针先走n+1步
        // 因为这里我们需要找到要删除节点的前一个节点即第n-1个节点
        for(int i = 0; i <= n; i++){
            fast = fast.next;
        }

        // 当fast == null时slow就指向了要删除节点的前一个节点
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;

        return dummyNode.next;       
    }
}
 // 自己写的比较暴力的解法
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {

        ListNode cur = head;
        int nums = 0;
        while(cur != null) {
            cur = cur.next;
           nums++; 
        }

        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode pre = dummyHead;
        
        if(nums != n){
            for(int i = 0; i <= nums - n - 1; i++) pre = pre.next;
        }

        pre.next = pre.next.next;

        return dummyHead.next;
        
    }
}

面试题 02.07. 链表相交

注意:数值相同,不代表指针相同。
题目链接:leecode链表相交
题目链接/文章讲解:链表相交

想法

在牛客网剑指offer做过 JZ52 两个链表的第一个公共结点
感觉是典型的双指针的题目

解题思路来自牛客网的精华题解,讲的很清楚就直接粘贴过来了

使用两个指针N1,N2,一个从链表1的头节点开始遍历,我们记为N1,一个从链表2的头节点开始遍历,我们记为N2。

让N1和N2一起遍历,当N1先走完链表1的尽头(为null)的时候,则从链表2的头节点继续遍历,同样,如果N2先走完了链表2的尽头,则从链表1的头节点继续遍历,也就是说,N1和N2都会遍历链表1和链表2。

因为两个指针,同样的速度,走完同样长度(链表1+链表2),不管两条链表有无相同节点,都能够到达同时到达终点。

(N1最后肯定能到达链表2的终点,N2肯定能到达链表1的终点)。

所以,如何得到公共节点:

有公共节点的时候,N1和N2必会相遇,因为长度一样嘛,速度也一定,必会走到相同的地方的,所以当两者相等的时候,则会第一个公共的节点
无公共节点的时候,此时N1和N2则都会走到终点,那么他们此时都是null,所以也算是相等了。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode a = headA;
        ListNode b = headB;

        while(a != b){
            a = (a == null) ? headB: a.next;
            b = (b == null) ? headA: b.next;
        }
        return a;
    }
}

收获

双指针的应用

142.环形链表II

算是链表比较有难度的题目,需要多花点时间理解 确定环和找环入口,建议先看视频。
题目链接:leecode环形链表II
文章讲解/视频讲解:环形链表II讲解

想法

在剑指offer做过,JZ23 链表中环的入口结点

用快慢指针遍历可以找到环的入口,但具体如何遍历忘记了,去看讲解了

看完视频

需要2次遍历
第1次遍历,fast = head, slow = head ,fast一次走2步,slow一次走一步,直到两者相遇,记录第一次相遇节点。
第2次遍历,fast = head,slow = 第1次相遇的节点,二者都是每次各走1步;当fast = slow时,两者相遇,此时相遇节点即为环的入口。

注意点

  • 判断链表是否有环
    可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

  • 如果有环,如何找到这个环的入口
    2次快慢指针遍历链表

  • while(fast != null || fast.next != null) {};
    情况一:有环,一直在环里遍历
    情况二:没有环,遍历到链表结尾退出while;此时应满足没有环的条件:if(fast == null || fast.next == null) return null;

// 代码和老师的有一点不一样,主要是多了一个if(fast == null || fast.next == null) return null;,再寻找第二次相遇点
public class Solution {
    public ListNode detectCycle(ListNode head) {
        // 定义快慢指针
        ListNode fast = head;
        ListNode slow = head;
        // 记录快慢指针第一次相遇的结点
        while(fast != null && fast.next != null){
            //快指针是慢指针的两倍速度
            fast = fast.next.next;
            slow = slow .next;
            if(fast == slow)
                break;
        }
        //若是快指针指向null,则不存在环
        // 这个边界条件可能考虑不全
        if(fast == null || fast.next == null) return null;

        //其中一个指针重新指向链表头部
        slow = head;
        // 与第一次相遇的结点相同速度出发,相遇结点为入口结点
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;        
    }
}

你可能感兴趣的:(算法,链表,java)