代码随想录(day04)-LeetCode:24、19、面试题02.07、142

1. 【24】两两交换链表中的节点

力扣题目链接:24

【题目描述】:

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

【示例1】:

输入:head = [1,2,3,4]
输出:[2,1,4,3]

【示例2】:

输入:head = []
输出:[]

【示例3】:

输入:head = [1]
输出:[1]

【提示】:

  • 链表中节点的数目在范围 [0, 100]
  • 0 <= Node.val <= 100

虚拟头结点实现

使用虚拟头结点完成,cur指针(用于遍历)要指向要操作的两个相邻节点的前一个节点,这样才方便进行二者之间的相互交换。

具体的两两交换流程可以通过自己画图演示来加强理解,尤其是对与交换顺序边的理解会有很大的帮助。

【Java代码实现】

/**
 * 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 swapPairs(ListNode head) {
        //创建一个虚拟头结点并指向真正的头结点head
        ListNode dummyHead = new ListNode(-1, head); 
        //当前指针cur指向虚拟头结点,用于遍历
        ListNode cur = dummyHead; 
        /*终止条件:
          当节点个数为偶数时,最后cur指向最后一个节点,cur.next = null
          当节点个数为奇数时,最后cur指向倒数第二个节点,cur.next.next = null
         */
         //注意两个条件的位置不能写反,否则可能报空指针异常或进入死循环
        while (cur.next != null && cur.next.next != null) { 
            //以链表:dummyHead--1--2--3--4--5为例
            //如:准备将1--2交换为2--1,cur指向1的前一位dummyHead
            ListNode tmp1 = cur.next; //记录1节点
            ListNode temp2 = cur.next.next.next; //记录3节点

            //开始交换
            cur.next = cur.next.next; // 变为:dummyHead--2
            cur.next.next = tmp1; //变为:2--1
            cur.next.next.next = temp2; //变为:1--3

            //新链表为:dummyHead--2--1--3--4
            //下次循环准备将:3--4换为4--3,此时cur要指向3的前一位1
            cur = cur.next.next;
        }
        return dummyHead.next;
    }
}

递归实现

/**
 * 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 swapPairs(ListNode head) {
        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;
    }
}

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

力扣题目链接:19

【题目描述】:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

【示例1】:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

【示例2】:

输入:head = [1], n = 1
输出:[]

【示例3】:

输入:head = [1,2], n = 1
输出:[1]

【提示】:提示:

  • 链表中结点的数目为 sz
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

双指针算法

在链表中,若要删除一个节点,则一定要找到其前一个节点

使用虚拟头结点可以方便具体操作,不用判断是是头结点还是普通节点。

寻找倒数第n个节点【思路】:

  • 定义一个快指针和一个慢指针,先让快指针向后移动n步,再快慢指针一起向后移动,当快指针指向null时结束。此时慢指针所指的位置即为倒数第n个节点。
  • 只需要保证快慢指针相差n步即可,再一起向后移动。
/**
 * 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 dummyHead = new ListNode(-1, head); //创建虚拟头结点
        ListNode fast = dummyHead; //创建快指针
        ListNode slow = dummyHead; //创建慢指针
        //快指针先移动n步
        while (n-- > 0) { 
            fast = fast.next;
        }
        //快慢指针同时移动
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        //此时slow指向倒数第n个节点的前一个节点
        slow.next = slow.next.next; //删除倒数第n个节点
        return dummyHead.next;
    }
}

3. 面试题【02.07】:链表相交

力扣题目链接:面试题【02.07】

【题目描述】:

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

【示例1】:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'

解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

【示例2】:

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'

解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

【示例3】:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

【提示】:

  • listA 中节点数目为 m
  • listB 中节点数目为 n
  • 0 <= m, n <= 3 * 104
  • 1 <= Node.val <= 105
  • 0 <= skipA <= m
  • 0 <= skipB <= n
  • 如果 listA 和 listB 没有交点,intersectVal 为 0
  • 如果 listA 和 listB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

解题思路:

此题简单来说,就是求两个链表交点节点的指针。 这里大家要注意,交点不是数值相等,而是指针相等

代码实现:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode cur1 = headA;
        ListNode cur2 = headB;
        int len1 = 0, len2 = 0;
        while (cur1 != null) { //求A链表的长度
            len1++;
            cur1 = cur1.next;
        }
        while (cur2 != null) { //求B链表的长度
            len2++;
            cur2 = cur2.next;
        }
        //cur1、cur2重新指向各自链表头结点
        cur1 = headA;
        cur2 = headB;

        //令cur1指向长链表的头结点、len1为其长度
        if(len1 < len2) { 
            //交换长度,len1为大者
            int tmpLen = len1;
            len1 = len2;
            len2 = tmpLen;
            //交换头结点指向,len1指向长者
            ListNode tmpNode = cur1;
            cur1 = cur2;
            cur2 = tmpNode;
        }
        int subLen = len1 - len2; //长度差
        //令A、B链表尾部对齐,即令cur1向后移动subLen步
        while (subLen-- > 0) {
            cur1 = cur1.next;
        }
        //开始遍历,指针相同时则返回
        while (cur1 != null) {
            if (cur1 == cur2) {
                return cur1;
            }
            //指针不相同,向后移动继续比较
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return null;
    }
}

4.【142】环形链表II

力扣题目链接:142

题目描述:

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

【示例1】:

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

【示例2】:

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

【示例3】:

输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

【提示】:

提示:

  • 链表中节点的数目范围在范围 [0, 104]
  • -105 <= Node.val <= 105
  • pos 的值为 -1 或者链表中的一个有效索引

快慢指针算法

做题思路:

可以使用快慢指针进行求解。【题目稍难】

  1. 判断链表是否有环:

    若链表无环为一条直线,则快指针走得更快的情况下(每次移动2步),二者是不会相遇的

    若链表有环,快指针会先到达环里,并且在环里转圈。随后,慢指针**(每次移动1步)**也会到达环里。因为二者移动速度有差别,二者最后终将相遇于环

    相遇原因:当二者均走在环里的时候,快指针相对于慢指针是走1步去追赶慢指针,并且环是有一定长度的。所以二者必将相遇。

  2. 找环的入口:★

    (以下涉及到部分的数学公式推导,推导后能更方便理解,便于具体代码的实现)

    以下内容来自代码随想录网站,有兴趣的同学可以自行查看,卡哥有更详细的讲解,非常详细!

    假设从头结点到环形入口节点的节点数为x,环形入口节点到 fast指针与slow指针相遇节点的节点数为y,从相遇节点再到环形入口节的点节点数为 z。

    则相遇时: slow指针走过的节点数为: x + y; fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈后才遇到slow指针, (y+z)为 一环内节点的个数A。

    因为fast指针是一步走2个节点,slow指针一步走1个节点, 因此:fast指针走过的节点数 = slow指针走过的节点数 * 2,

    即:(x + y) * 2 = x + y + n (y + z)

    两边消掉一个(x+y): x + y = n (y + z)

    因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。

    所以要求x ,将x单独放在左面:x = n (y + z) - y ,

    再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

    这个公式说明什么呢?

    先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

    • 当 n为1的时候,公式就化解为 x = z,这就意味着,==从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。==也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

    • 当n大于1时,就是fast指针在环形转n圈之后才遇到 slow指针。

      其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

代码实现:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        //注意while里的判断顺序,因为fast走2步,要判断两个节点
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) { //链表有环
                ListNode index1 = fast; //相遇的位置
                ListNode index2 = head; //头结点位置
                //等价于:
                //两指针,从头结点走向相遇节点,各走1步,直到相遇,相遇处为环入口
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                 return index1;
            }
        }
        return null;
    }
}

5. 总结

今天对双指针算法,尤其是快慢指针算法有了更深的理解。最后一题【环形链表2】中确实没想到利用数学公式推导节点之间的距离关系,只有把这个过程理解了才能用合适的代码实现。

万丈高楼平地起,加油!day day up !


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值