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

24.两两交换链表中的节点
题目链接/文章讲解/视频讲解: https://programmercarl.com/0024.%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.html

19.删除链表的倒数第N个节点
题目链接/文章讲解/视频讲解:https://programmercarl.com/0019.%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%ACN%E4%B8%AA%E8%8A%82%E7%82%B9.html

面试题 02.07. 链表相交
题目链接/文章讲解:https://programmercarl.com/%E9%9D%A2%E8%AF%95%E9%A2%9802.07.%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4.html

142.环形链表II
题目链接/文章讲解/视频讲解:https://programmercarl.com/0142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II.html

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

看卡哥视频感悟:

  1. 要想操作节点x,必须有志向x-1,也就是前一个节点的指针
  2. 判断循环中止的条件比我优雅

我的做法
延续上一篇的做法,主要有两点:

  1. 指针不要怕多,不够就加
  2. 只有指针指着的位置是能随便调整箭头指向位置,动左右两侧相邻的节点的时候一定要慎重
  3. 先一条一条写,再找出最小循环单位作为循环体
  4. 边界条件一定要试

下面来找循环体。首先假设有无限多个节点(这样就不用考虑边界情况)

c4f8dffe670f20b915dbe2849fed44f.jpg

结点1、结点2改完之后应该是这样的

289a002e09fb15ed98f1e7613a2071f.jpg

然后开启多指针大法,找循环体

e96b11a7a67bd22070190ac57da0e77.jpg

然后就是验各种边界条件

877bb663bbb594609096220aefbe481.jpg

验证下来发现有这些边界条件

807b057a6947ddf6bd7739898cc22d3.jpg

但这个写的太不优雅了,其实究其本质说的就是卡哥的边界条件,即pre == null || pre.next == null的情况下,循环结束

我的代码如下

public class LinkedList_24_SwapNodesinPairs {
    public ListNode swapPairs(ListNode head) {
  
        // 排除空链表
        if (head == null) return null;
        // 排除只有一个节点的链表
        if (head.next == null) return head;
        // 有>=2的节点的链表
        // 创建头结点
        ListNode dummy = new ListNode();
        dummy.next = head;
        // 创建指针
        ListNode pre = dummy;
        ListNode cur = pre.next;
        ListNode after = cur.next;
        ListNode last = after.next;

        while (true) {
            // 开始交换
            pre.next = after;
            after.next = cur;
            cur.next = last;
            // 开始推进
            pre = pre.next.next;
            cur = pre.next;
            if (cur == null) break;
            after = cur.next;
            if (after == null) break;
            last = after.next;
        }
        return dummy.next;
    }
}

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

说实话,看到这道题目脑子里第一反应就是双指针,我也不知道怎么解释思路,反正就归功多做题的感觉吧

确定了双指针的题目之后就是开始找规律

7281d34d5676cc3f5b653d1a45fa1b2.jpg

注意点:

  1. 要删除节点x,必须有着指向x-1节点的指针
  2. 要验证边界条件
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 创建头结点
        ListNode dummy = new ListNode();
        dummy.next = head;
        // 创建双指针
        ListNode cur = dummy;
        ListNode toDeletePreNode = dummy;
        // 移动cur指针作为探路先锋
        for (int i = 0; i < n; i++) {
            cur = cur.next;
        }
        // 双指针整体开始移动到尾巴,当cur指向最后一个节点,toDeletePreNode就指向了倒数第n+1个节点
        // 因为只有指向前一个节点才能删掉这个节点
        while (cur.next != null) {
            cur = cur.next;
            toDeletePreNode = toDeletePreNode.next;
        }
        // 删除倒数第n个节点
        toDeletePreNode.next = toDeletePreNode.next.next;
        
        return dummy.next;

    }
}

面试题 02.07. 链表相交

又是拿到题脑子里瞬间就知道了做法,我也不知道怎么写思路…

但是这道题目写的时候有几个注意点

这是正确的代码

 public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 排除空链表的情况
        if (headA == null || headB == null) return null;
        // 现在链表至少有一个节点
        ListNode p1 = headA;
        ListNode p2 = headB;
        int lenA = 1;
        int lenB = 1;
        int diff = 0;

        // 计算链表1的长度
        while (p1 != null) {
            p1 = p1.next;
            lenA++;
        }
        // 计算链表2的长度
        while (p2 != null) {
            p2 = p2.next;
            lenB++;
        }
        // p1、p2归位
        p1 = headA;
        p2 = headB;
        // 计算哪个链表长,长的那个先动,保持在同一起跑线
        if (lenA > lenB) {
            diff = lenA - lenB;
            for (int i = 0; i < diff; i++) {
                p1 = p1.next;
            }
        } else {
            diff = lenB - lenA;
            for (int i = 0; i < diff; i++) {
                p2 = p2.next;
            }
        }
        // 此时p1、p2所在的链表后方的节点个数是相等的,且p1 != p2
        // 开始同时遍历链表,指向相同的节点,则位相交的节点,若遍历结束正常退出循环,则说明不相交
        while (p1 != null) {
            if (p1 == p2) {
                return p1;
            }
            p1 = p1.next;
            p2 = p2.next;
        }
        return null;
    }

我一开始写的时候犯了这几个错误:

  1. 计算链表长度的时候我的循环条件写的是 while (p1.next != null)
  2. 循环体中先判断,再改动。也就是先做不改变数据的操作,再做改变数据的操作。我一开始写的是
while (p1 != null) {
    p1 = p1.next;
    p2 = p2.next;
    if (p1 == p2) {
        return p1;
    }
}

这与写的话只要有一条链表是只有一个节点,就会出错

142. 环形链表 II

没啥好说的,卡哥牛

这是我一开始的思路,写到最后就推不下去了

c08d7b0fb13311b7b751c7c662f8e96.jpg

我觉得我的问题在于我钻牛角尖了,我想用数学的方法给它算出来,而不是结合计算机的方法给它试出来

启示就是不要小气,多设变量,找关系

    public ListNode detectCycle(ListNode head) {
        if (head == null) return null;

        ListNode fast = head;
        ListNode slow = head;

        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                ListNode index1 = head;
                ListNode index2 = fast;

                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;

                }
                return index1;
            }
        }

        return null;
    }
  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值