链表+快慢指针:环形链表 (Leetcode141/Leetcode142/剑指24/Leetcode61/剑指35)

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

暴力解法:二次到达

链表实现代码:

class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
/**
* 解法一:二次到达解法
* 1.定义数组记录已访问节点
* new ListNode[10000];
* 2.遍历链表的每个节点,并与容器中已存放的节点依次比较:
* 相同则方法结束,返回true
* 不同则存入最新位置,继续遍历下个节点
* 3.若next指针为null,则方法结束,返回false
*
* @param head
* @return
*/
public boolean hasCycle(ListNode head) {
// 1.定义数组记录已访问节点
ListNode[] array = new ListNode[10000];
// 2.遍历链表的每个节点,
while (head != null) {
// 并与容器钟已存放的节点依次比较
for (int i = 0; i < array.length; i++) {
if (array[i] == head) {
return true;
}
if (array[i] == null) {
array[i] = head; // 将当前节点存放到最新位置
break; // 结束容器的遍历
}
}
head = head.next;
}
// 3.若next指针为null,则方法结束,返回false
return false;
}

 

 

 

 

 

 

 

 

 最优解:追击问题(快慢指针)

/**
* 解法二:快慢指针解法
* 1.定义快慢两个指针:
* slow=head; fast=head.next;
* 2.遍历链表:
* 快指针步长为2:fast=fast.next.next;
* 慢指针步长为1:slow=slow.next;
* 3.当且仅当快慢指针重合,有环,返回true
* 4.快指针为null,或其next指向null,没有环,返回false,操作结束
* @param head
* @return
*/
public boolean hasCycle(ListNode head) {
if (head == null) { // 链表中有节点[0, 10^4]个
return false;
}
// 1.定义快慢两个指针:
ListNode slow = head;
ListNode fast = head.next;
// 2.遍历链表:快指针步长为2,慢指针步长为1
while (fast != null && fast.next != null) {
// 3.当且仅当快慢指针重合:有环,操作结束
if (slow == fast) {
return true;
}
fast = fast.next.next; // 快指针步长为2
slow = slow.next; // 慢指针步长为1
} // 4.快指针为null,或其next指向null,没有环,返回false,操作结束
return false;
}

测试用例

辅助数据结构:链表。代码如下:

class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
输入:head = [1], pos = -1
输出:false
解释:pos 为环开始节点的索引,若pos = -1,则没有环。pos 不作为参数进行传递,仅仅是为了标识链表的实际情况
输入:head = [1,2], pos = 0
输出:true
输入:head = [3,2,0,-4], pos = 1
输出:true

 

 

 

 

 

 

 

 

1/4 环形链表 II - Leetcode 142

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

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

进阶:

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
/*
使用快慢指针找到是否有环(快指针一次走两步,慢指针一次走一步)
找到环以后快指针,慢指针再回到头结点,快慢指针都按照步长1前进,等两个指针再次相遇就是环的入口位置。
大家可以回想一个场景,在学校田径运动中,跑的快的同学会套圈跑的慢的同学,这个其实就是和快慢指针的思路很像的。

如果链表从的头结点就是环的入口,那就是一个标准环形跑道,慢的人跑完一整圈,正好被那个两倍速度的套圈,也就是两个人在起点相遇,那么如果两个人不是在起点相遇,那么相差的距离其实就是头结点到环入口的距离。
*/
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; //相遇代表有环
        }
        if(fast == null || fast.next == null)return null;
        slow = head;//慢指针回到链表头部
        while(fast != slow){
            slow = slow.next;
            fast = fast.next;//快指针也调整为一次走一步
        }
        return slow;
    }
}

 

2/4 反转链表 - 剑指24

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

限制:

0 <= 节点个数 <= 5000

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head, pre = null;
        while(cur != null) {
            ListNode tmp = cur.next; // 暂存后继节点 cur.next
            cur.next = pre;          // 修改 next 引用指向
            pre = cur;               // pre 暂存 cur
            cur = tmp;               // cur 访问下一节点
        }
        return pre;
    }
}

 

3/4 旋转链表 - Leetcode 61

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例 1:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

示例 2:
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

 
/**
 * 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 rotateRight(ListNode head, int k) {
        if(head==null||k==0){
            return head;
        }
        ListNode cursor=head;
        ListNode tail=null;//尾指针
        int length=1;
        while(cursor.next!=null)//循环 得到总长度
        {
            cursor=cursor.next;
            length++;
        }
        int loop=length-(k%length);//得到循环的次数
        tail=cursor;//指向尾结点
        cursor.next=head;//改成循环链表
        cursor=head;//指向头结点
        for(int i=0;i<loop;i++){//开始循环
            cursor=cursor.next;
            tail=tail.next;
        }
        tail.next=null;//改成单链表
        return cursor;//返回当前头
    }
}

 

4/4 复杂链表的复制 - 剑指35

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

示例 1:
e1.png
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
e2.png
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
e3.png
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
/**
 * 考虑构建 原节点 1 -> 新节点 1 -> 原节点 2 -> 新节点 2 -> …… 的拼接链表,
 * 如此便可在访问原节点的 random 指向节点的同时找到新对应新节点的 random 指向节点。
 */

class Solution {
    /**
     * 移花接木
     * 考虑构建 原节点 1 -> 新节点 1 -> 原节点 2 -> 新节点 2 -> …… 的拼接链表,
     * 如此便可在访问原节点的 random 指向节点的同时找到新对应新节点的 random 指向节点。
     * 1. 先插入新节点
     * 2. 更新新节点的random指针
     * 3. 拆分出新节点
     */
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        // 完成链表节点的复制插入
        Node cur = head;
        while (cur != null) {
            //在原节点后插入新复制的节点
            Node copyNode = new Node(cur.val);
            copyNode.next = cur.next;
            cur.next = copyNode;
            cur = copyNode.next;//原链表的下一个节点
        }
        // 完成链表复制节点的随机指针复制
        cur = head;
        while (cur != null) {
            if (cur.random != null) { // 注意判断原来的节点有没有random指针
                cur.next.random = cur.random.next;//复制原节点的random指针(random指向节点的下一个节点就是对应的新节点
            }
            cur = cur.next.next;
        }
        // 将链表一分为二
        Node copyHead = head.next; //新链表的头节点
        cur = head;
        Node curCopy = head.next;
        while (cur != null) {
            cur.next = cur.next.next;//还原原始链表
            cur = cur.next;
            if (curCopy.next != null) {//拆分新链表
                curCopy.next = curCopy.next.next;
                curCopy = curCopy.next;
            }
        }
        return copyHead;
    }
}

 

posted @ 2021-08-09 22:13  Jasper2003  阅读(34)  评论(0编辑  收藏  举报