链表+快慢指针:环形链表 (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; } }
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 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; } }
给定一个链表,旋转链表,将链表每个节点向右移动 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;//返回当前头 } }
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
输入: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; } }