【数据结构与算法 | 灵神题单 | 快慢指针(链表)篇】力扣141,142, 143

1. 力扣141:环形链表

1.1 题目:

给你一个链表的头节点 head ,判断链表中是否有环。

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

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

进阶:你能用 O(1)(即,常量)内存解决此问题吗?

1.2 思考

采用快慢指针法,快指针一次走2步,慢指针一次走一步,如果快慢指针能够相遇,就说明链表环,否则,在快指针遍历完了,二者是不可能相遇的。

拿scala刷题感觉有点唐。

1.3:题解

/**
 * Definition for singly-linked list.
 * class ListNode(var _x: Int = 0) {
 *   var next: ListNode = null
 *   var x: Int = _x
 * }
 */

object Solution {
    def hasCycle(head: ListNode): Boolean = {
        if(head == null){
            false
        } else{
            var (fast, slow) = (head, head)
            while(fast.next != null && fast.next.next != null){
                fast = fast.next.next
                slow = slow.next
                if(slow == fast){
                    return true
                }
            }
            false
        }
    }
}

2. 力扣142:环形链表2

2.1 题目:

给定一个链表的头节点  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 或者链表中的一个有效索引

进阶:你是否可以使用 O(1) 空间解决此题?

2.2 思路:

传说中的龟兔赛跑法。

2.3 题解:

/**
 * 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) {
        if(head == null){
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        boolean flag = false;
        while(fast.next != null && fast.next.next != null){
            fast = fast.next.next;
            slow = slow.next;
            // 如果快慢指针相遇,说明是有环的,更新flag
            if(fast == slow){
                flag = true;
                break;
            }
        }
        // 如果有环的情况,进入到下面的if语句
        if(flag){
            slow = head;
            // 慢指针回到头节点
            // 再次相遇时,指向的节点就是入环的第一个节点
            while(slow != fast){
                slow = slow.next;
                fast = fast.next;
            }
            return slow;
        }
        return null;
    }
}

3. 力扣143:重排链表

3.1 题目:

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

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

示例 2:

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

提示:

  • 链表的长度范围为 [1, 5 * 104]
  • 1 <= node.val <= 1000

3.2 思路:

看注释。

3.3 题解:

/**
 * 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 void reorderList(ListNode head) {
        // 快慢指针,慢指针定位到中间节点,然后将一个链表以分为二
        // 记录一下第二个链表的头节点,然后遍历到最后一个节点,然后开始将最后一个节点插入到第一个链表中
        ListNode fast = head;
        ListNode slow = head;
        // 因为链表的长度至少是1,所以不用额外判断head节点为空的情况
        while(fast.next != null && fast.next.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        // 将一个链表一分为2,并将slow的下一个节点作为第二个链表的首元节点
        // 因为第二个链表的头节点也有可能被移除
        // 所以需要哨兵节点
        ListNode next = slow.next;
        // 将两个链表断开
        slow.next = null;

        ListNode dummy = new ListNode(10086, next);

        // 规律:在p指针的next处插入第二个链表的尾节点
        // 直到dummy.next==null
        ListNode p = head;

        while(dummy.next != null){
            // 循环条件限制了node不可能为第二个链表的哨兵节点
            ListNode node = tailNode(dummy);
            
            // 从p的后面插入
            node.next = p.next;
            p.next = node;
            // 这里为什么可以直接大胆的将后挪一个位置呢?不怕下一轮循环p为null,p.next报错吗
            // 因为第一个链表的长度大于等于第二个链表的长度
            // 所以如果dummy.next != null,说明第二个链表还没被遍历完
            // 所以p肯定还有next节点的,然后又向p节点后插入了一个节点,所以只要
            //dummy.next != null,则p后面肯定有不少于两个节点
            p = p.next.next;
        }
    }
    private ListNode tailNode(ListNode dummy) {
        ListNode p = dummy;
        ListNode parent = null;
        while(p.next != null){
            parent = p;
            p = p.next;
        }
        // 删掉尾巴节点
        if(parent != null){
            parent.next = null;
        }
        // 不可能返回哨兵节点,因为返回链表的最后一个节点的时候,压根不会进入循环
        return p;
    }
}

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值