目录
力扣27—— 移除元素
力扣26——删除有序数组中的重复项
力扣283——移动零的位置
力扣203号题——移除链表元素
力扣83——删除链表中的重复元素
力扣82——删除排序链表中的重复元素 II
力扣206——反转链表
力扣92——反转链表 II
力扣876——链表的中间结点
力扣剑指offer22——链表中倒数第k个节点
力扣234——回文链表
力扣21——合并两个有序链表
力扣面试题02.04—— 分割链表
力扣160——相交链表
力扣141——环形链表
力扣142——环形链表 II
class Solution {
public int removeElement(int[] nums, int val){
int p1 = 0;//只指向对的元素
int p2 = 0;//遇到待删除元素就跳过
for (int i = 0; i < nums.length; i++) {
if(nums[i] != val){
nums[p1] = nums[p2];
p1 ++;
}
p2 ++;
}
return p1;
}
}
解析:当遇到不是待删除元素时,p1和p2同时向后走,但遇到待删除数时,p1按兵不动,让p2一直走,直到走到不为待删除数时。
p1收集的是正确的元素,p2遇到待删除的数就跳过。
走完正个数组后,p1刚好指向正确元素的下一个位置,数值刚好等于长度值,所以直接返回 p1就可以了
class Solution {
public int removeDuplicates(int[] nums) {
//判断边界条件
if(nums == null || nums.length == 0)return 0;
int slow = 0;//只用来接收正确的
int fast = 0;//遇到重复的就跳过
while(fast < nums.length){
if(nums[slow] != nums[fast]){
slow ++;
nums[slow] = nums[fast];
}
fast ++;
}
return slow + 1;
}
}
解析:先进行判空处理,可能数组为空。
用双指针来解决:注意题目的数组是有序的,说明重复的数必然相邻。
slow 只用来接收正确的 fast 遇到重复的就跳过
如果相等,f 后移 1 位
如果不相等,将 s 位置的元素复制到 s+1 位置上,s 后移一位,f 后移 1 位
重复上述过程,直到 q 等于数组长度。
返回 s + 1,即为新数组长度。
public class Num_283_moveZeroes {
public void moveZeroes(int[] nums){
int p1 = 0;
int p2 = 0;
int conut = 0;//统计0的个数
while(p2 < nums.length){
if(nums[p2] != 0){
nums[p1] = nums[p2];
p1++;
}else{
conut++;
}
p2++;
}
while(conut-- > 0){
nums[p1++] = 0;
}
}
}
解析:当遇到不是0时,p1和p2同时向后走,但遇到0时,p1按兵不动,让p2一直走,直到走到不为0时。
p1收集的是正确的元素,p2遇到0就跳过。
当遇到0时 conut++统计0的个数
走完正个数组后,p1刚好指向正确元素的下一个位置,再次while循环添加conut个 0
/** * 思路一:最普通的方法,找前驱,和 特殊处理头节点 */
public ListNode removeElements(ListNode head, int val) {
//保证头节点不是待删除的数
//1.必须先保证heal不是空指针,才能继续访问里面的val值
while(head != null && head.val == val){
//1.0
// ListNode node = head;
// head = node.next;
// node.next = null;
//其实题目里给的链表,不需要考虑回收、释放的问题,可以直接支这样写
//2.0
head = head.next;
}
//走到这就保证了头节点不再是待删除的数
//先判空
if(head == null){
return null;
}else{
//到这里说明头节点不是待删除的节点
//开始找前驱
ListNode prve = head;
while(prve.next != null ){
if( prve.next.val == val ) {
prve.next = prve.next.next;
}else{
//只有不等于val值,才能移动前驱
prve = prve.next;
}
}
}
return head;
}
/** * 思路二:递归写法。题目值给了我们头节点,那我们就只判断头节点是否是需要删除的节点, * 剩下的节点交给子方法去处理并把结果返回。 *
public ListNode removeElements(ListNode head, int val) {
//1.什么情况下不需要拆分了?:终止条件
if( head == null ){
return null;
}else{
//要用head.next来接收子函数的返回值
head.next = removeElements(head.next, val);
if(head.val == val){//跳过
head = head.next;
}
}
return head;
}
/** * 思路三: 创建一个虚拟头节点来删除。 * 之前删除老是需要考虑特殊的头节点问题,之前就给头节点创出来一个虚拟节点 */
public ListNode removeElements(ListNode head, int val) {
//判断是否为空
if(head == null){
return head;
}
//创建出一个虚拟头节点
ListNode dummyHead = new ListNode();
dummyHead.next = head;//连接
ListNode prve = dummyHead;//前驱
while(prve.next != null){
if(prve.next.val == val){
prve.next = prve.next.next;
}else{
prve = prve.next;
}
}
return dummyHead.next;
}
/** * 思路一:用双指针 prve 和 node。这是个排序的链表。如果当prve和node相等时, * prve所指向的节点肯定是第一次出现,而node指向的指针就是所谓的重复数了。 * 让node一直向后走,当node不等于prve时,才后prve向后走一步。 * 注释:node肯定是每次都要向后走的,node == null结束 */
class Solution {
public ListNode deleteDuplicates(ListNode head) {
//特殊情况:当head为空,或者只有一个元素的时候,肯定没有重复元素存在
if(head == null || head.next == null){
return head;
}
ListNode prve = head;
ListNode node = head.next;
while(node != null){
if(prve.val == node.val){
//前后相等的时候,出现重复元素
node = node.next;
prve.next = node;//更新prve所指向的地址
}else{
//两个数不相等,依次向后移动
prve = node;
node = node.next;
}
}
return head;
}
}
/** * 思路二:递归方法。他们只给我们头节点,所以我们给只处理头节点是否是重复数 * 剩下的交给子方法去解决。 看返回节点的头节点。如果这是重复数,那么就已经被子方法 * 删的只剩下一个了,再与原来的头节点相比较是否需要删除,如果返回的不是重复数就正常连接就可以了 */
public ListNode deleteDuplicates(ListNode head) {
//终止条件:当head为空,或者只有一个元素的时候,肯定没有重复元素存在
if(head == null || head.next == null){
return head;
}else{
//到这里说明至少存在两个节点,可能会出现重复元素
head.next = deleteDuplicates(head.next);
if(head.val == head.next.val){
return head.next;//正在删除还得看这步
}
return head;
}
}
/** * 思路一:虚拟头节点 + 三指针。之前我们删除一个链表都是找前驱,但现在头结点也有可能会是重复元素改怎么办? * 可以创建一个虚拟头节点,使得每个节点都具备头节点。那边怎么知道一个数是否是重复元素? * 可以用两种指针来比较node1和node2,当他们相等时,肯定就是了。但要求的是一个重复元素都 * 不留,两个指针都用来判断的,改怎么删除节点呢?可以再创建 一个指针prve(前驱)来串联所有 * 正确的节点 */ //上下两个思路是一样的,看看留个印象 /** * 思路:要求是重复节点一个都不留,我们之前单链表的删除都是通过 * 前驱节点删除 =》 现在第一个节点就有可能会是重复节点了, * 要保证 prve 坚决不能能带删除的节点,我们可以引用 * 虚拟头节点的概念,如果不增加一个空节点来做,那么题目会变的非常的复制难办。 * 但现在我们还需要 两个指针来判断是否为 重复数,那就再引入 node1,node2 */ //注释:一定要先判断node2 != null 用while循环到不是重复数为止
public ListNode deleteDuplicates(ListNode head){
// //当链表为空或者 只有一个是时候,肯定没有重复元素
if(head == null || head.next == null){
return head;
}
//此时说明至少有两个节点,可能存在重复数
ListNode dummyHead = new ListNode();//虚拟头节点
dummyHead.next = head;//连接一下
ListNode prve = dummyHead;//前驱
ListNode node1 = prve.next;//遇到重复数保持不动
ListNode node2 = node1.next;//每次向后移动
while(node2 != null){
if(node1.val == node2.val){
//说明此时两个节点都是要被删除的
while(node2 != null && node1.val == node2.val){
node2 = node2.next;
}//走到不是刚遇到的重复数的时候停下来
prve.next = node2;//更新前驱节点保存的地址
//判断一下node2是否走到头了
if(node2 == null)
return dummyHead.next;
}else{
//如果他们都不是重复的数
//移动prve的位置
prve = prve.next;
}
node1 = node2;
node2 = node2.next;
}
return dummyHead.next;
}
/** * 递归写法:功能是删除链表里面所有重复的元素,返回新链表的头 */ /** * 思路:我们要做的是 必须保证头节点不是待删除元素,然后把头节点不是待删除链表 * 交给子方法去处理,让它完成里面的重复删除。 */
public ListNode deleteDuplicates(ListNode head) {
//什么情况下不用删除了?
if(head == null || head.next == null){
//为空,或者只有一个节点,那肯定没有重复数啊
return head;
}
//判断头节点是否是待删除节点
if(head.val != head.next.val){
head.next = deleteDuplicates(head.next);
return head;//因为第一个不是待删除节点,所以可以直接连接
}else{
//重点!如果是带删除节点的话 ,先处理掉
//注释:我们只需要做到删除头节点带删数,至于后面的数里面有没有头节点交给方法去执行
ListNode node = head.next;
while(node != null && head.val == node.val){
//重复的话就一直让node往后走
node = node.next;
}//循环完后node跟第一个头节点val相比,肯定不相同。
//node是新的头节点,至于这个头节点是否需要删除,就交给子方法
return deleteDuplicates(node);
}
}
/** * 思路一:头插法。因为题目没有要求原地翻转,最简单的方法就可以创建一个新链表 * 用一个虚拟头节点,再用一个指针去遍历原链表,遇到一个节点就new一个一模一样值 * 的节点用来头插入虚拟头节点。这样虚拟头节点得到的链表就是反过来了的 */
public class Num206_reverseList {
public ListNode reverseList(ListNode head) {
//当链表为空或者 只有一个是时候,就不需要翻转链表了
if(head == null || head.next == null){
return head;
}
ListNode dummyHead = new ListNode();
while(head != null){
ListNode node = new ListNode(head.val);
node.next = dummyHead.next;
dummyHead.next = node;
head = head.next;
}
return dummyHead.next;
}
}
/** * 思路二:这个就是迭代法。 原地移动链表。 所谓的链表反转就是相当于 原来的 1 -》 2 变成了 2 -》 1 * 把每个节点的 原来的 所指向的方法反一下就可以了。 * 用 两个指针 prve 和 cur 来实现相反,再加上一个指针 next记录下 * 一个节点的地址,保持三指针的移动。 * 注释:当 cur走到null的时候, 你会发现 此时的prve正好在 链表末尾上, * 到时候直接返回 prve就可以了 */ /** * 思路二:迭代法。可以遇到一个节点就让那个节点进行头插步骤。但需要一个不动的前驱。 * 所以创建一个虚拟头节点。再用prve和node两个节点,每遇到一个节点,prve就让他保存 * 的地址指向下一个的下一个,遇到null为截止。node就是prve.next地址节点进行头插。 * 然后每次都更新dummyHead链接的地址 */ /*
public class Num206_reverseList {
public ListNode reverseList(ListNode head) {
//当链表为空或者 只有一个是时候,就不需要翻转链表了
if(head == null || head.next == null){
return head;
}
//此时链表至少有两个节点
ListNode dummyHead = new ListNode();//虚拟前驱
ListNode prve = head;//因为要让第一个节点指向null
dummyHead.next = prve;//连接一下
while(prve.next != null){
ListNode node = prve.next;
prve.next = node.next;//跳过头插节点
node.next = dummyHead.next;//进行头插
dummyHead.next = node;//更新新的头节点地址
}
return dummyHead.next;
}
}
/** //这个就是迭代法 * 思路2:原地移动链表。 所谓的链表反转就是相当于 原来的 1 -》 2 变成了 2 -》 1 * 把每个节点的 原来的 所指向的方法反一下就可以了。 * 用 两个指针 prve 和 cur 来实现相反,再加上一个指针 next记录下 * 一个节点的地址,保持三指针的移动。 * 注释:当 cur走到null的时候, 你会发现 此时的prve正好在 链表末尾上, * 到时候直接返回 prve就可以了 */
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prve = null;//因为要让第一个节点指向null
ListNode cur = head;
while(cur != null){
ListNode next = cur.next;//记录下一个节点的地址,不然等会反转就找不到了
cur.next = prve;
prve = cur;//移动一位
cur = next;//移位移位
}
return prve;
}
}
/** * 思路三:运用递归法来解决。 可以这么拆分:我们每次都知道头节点,所以我们也只 * 处理头节点的情况, 把 head.next交给子方法去解决后用 prve 接受一下返回地址, * 因为它就是接下来新链表的头节点了 * 然后我们手动把头节点移动到链表末尾 * 注释:记得先保存链表的第二个节点地址,因为等会交给子方法翻转后它就是末尾了。 */
public class Num206_reverseList {
public ListNode reverseList(ListNode head) {
//什么情况下可以不用拆分,或者可以直接知道答案?
if(head == null || head.next == null){
return head;
}
//到这说明至少有两个节点了
ListNode node = head.next;//保存地址,方便等会头节点插它后面
ListNode prve = reverseList(head.next);//这就是新链表的头节点,保存下来
node.next = head;
head.next = null;//不置空 会 回环
return prve;
}
}
/** *思路一:迭代法。找到left节点的前驱,所以需要一个虚拟头节点,循环left - 1次就可以找到 * 第一个翻转节点到最后肯定是在末尾的,所以用指针node执行它并保持不动,每次更新它执行的地址, * node后面的节点都需要进行头插,用cur执行。prve就是前驱的地址。每次都要更新prve.next * 和 node.next的地址 */
public class Num_92_reverseBetween {
public ListNode reverseBetween(ListNode head, int left, int right) {
//当链表为空或者 只有一个是时候,就不需要翻转链表了
if(head == null || head.next == null){
return head;
}
ListNode dummyHead = new ListNode();
ListNode prve = dummyHead;
dummyHead.next = head;
//寻找left - 1次找到前驱位置
for (int i = 0; i < left - 1; i++) {
prve = prve.next;
}//出来后此时的prve就是待翻转节点的前驱了
ListNode node = prve.next;
//需要翻转的次数是 right - left
for (int i = 0; i < right - left; i++) {
ListNode cur = node.next;//待翻转的节点
node.next = cur.next;//跳过待翻转的节点连接下一个节点地址
cur.next = prve.next;//头插
prve.next = cur;//更新头插节点地址
}
return dummyHead.next;
}
}
/** * 思路三: 定义一个静态变量,让它执行翻转后末尾节点指向的地址,保留。 * 末尾的节点分为两种情况,第一种是 后面不存在不需要翻转的节点,那么此刻的 next就应该是null * 第二种是 后面存在 不需要翻转的节点,如:1~5 我只翻转 2到4,那么 5 就是不需要翻转的节点, * 那么此刻的 next 保存的就是 5 的地址,这种情况发生在 left == right 时刻发生。 */
public class Num_92_reverseBetween {
//定义一个静态变量,让它执行翻转后末尾节点指向的地址,保留。
public static ListNode next = null;
public ListNode reverseBetween(ListNode head, int left, int right) {
//终止条件
if( head.next == null || left == right ){
next = head.next;
return head;
}
//如果此时头节点就需要翻转
if(left == 1){
ListNode prve = head;//需要我们手动尾插的头节点
ListNode cur = prve.next;//等会翻转链表后的尾节点
ListNode node = reverseBetween(head.next, left, right - -1);
cur.next = prve;
prve.next = next;
return node;
}
head.next = reverseBetween(head.next, left - 1, right - 1);
return head;
}
}
/** * 思路一:简单的办法就是统计长度。如果我们知道链表长度为 n,那么只需要 走 n / 2 步就 * 会刚好走到中间节点位置 */
public class Num876_middleNode {
public ListNode middleNode(ListNode head) {
int conut = 0;
//统计链表长度
for (ListNode x = head; x != null ; x = x.next) {
conut ++;
}
//找到中间节点
for (int i = 0; i < conut / 2; i++) {
head = head.next;
}
return head;
}
}
/** * 思路二:快慢指针。我们引入两个引用 slow ,fast * slow走一步,fast每次走两步,当 fast走到头的时候, * slow刚好停留在 中间节点上 */
public class Num876_middleNode {
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
//只有还有下一个节点存在,指针就可以走两步不报错,
//一步走到最后节点上,二步走到 null 不影响。
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
/** * 思路一:总长度 - K。所谓的倒数第k个节点,就是正的数 n - k个节点。所以先统计出总长度 * 再从头走 n - k次,就是倒数第k个的节点.就是从 head 走 n - k 步的节点 * 这里还需要考虑k的合法性 */
public class 剑指offer_Num22_getKthFromEnd {
public ListNode getKthFromEnd(ListNode head, int k) {
//边界
if(head == null || k < 0){
return null;
}
int n = 0;
//统计链表长度
for (ListNode x = head; x != null ; x = x.next) {
n++;
}
//如果 n - k小于 0就直接不进入循环
for (int i = 0; i < n - k; i++) {
head = head.next;
}
return head;
}
}
/** * 思路二:相对距离法。 引入两个引用,slow和 fast。 * 先让 fast 走 k 步,然后两个引用再同时开始依次向后移动 * 因为他们相距的距离是不变的,当 fast走到 null头的时候, * 此时的 slow正好落在 倒数第 k 个节点的位置上。 * 不懂的可以逆着想,如果我们让一个指针落在 k 上,一个指针落在 null末尾 * 它们同时向 左走,当那个落在 k上的指针到头节点的时候停止。一个道理。 */ //注释:任需要考虑边界问题,k的合法性。
public class 剑指offer_Num22_getKthFromEnd {
public ListNode getKthFromEnd(ListNode head, int k) {
//边界
if(head == null || k < 0){
return null;
}
ListNode slow = head;
ListNode fast = head;
for (int i = 0; i < k; i++) {
if(fast == null){
//k的合理性 如果还没移动完就走到头了,k肯定是超标了
return head;
}
fast = fast.next;
}
while(fast != null){
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
/** * 思路一: 开辟一个新链表,链表的内容就是元链表的反转。 * 然后两个链表依次比较到 null 如果全部都相等,就说明是回味链表。 */
public class Num234_isPalindrome {
public boolean isPalindrome(ListNode head) {
ListNode p1 = reverseList(head);
ListNode p2 = head;
while(p1 != null || p2 != null){
if(p1.val != p2.val){
return false;
}
p1 = p1.next;
p2 = p2.next;
}
return true;
}
//头插法
public ListNode reverseList(ListNode head) {
ListNode dummyHead = new ListNode();
while(head != null){
ListNode node = new ListNode(head.val);//每次的新节点
node.next = dummyHead.next;
dummyHead.next = node;
head = head.next;
}
return dummyHead.next;
}
}
/** * 思路二:反转链表的中间部分。可以先用之前的快慢指针找到链表 * 的中间节点位置,然后在这个节点往后的链表进行反转,然后依次比较 */
public class Num234_isPalindrome {
public boolean isPalindrome(ListNode head) {
//先找到中间节点的地址
ListNode prve = middleNode(head);
//反转链表中间位置及以后的链表
ListNode node = reverseList(prve);
//这里的条件也可以设置成 node != null
//比如是 1~5,虽然说它中间节点反转的节点多一个,
//但 头链表的2后面还是连接在那个中间节点 3 上面的
while(head != prve){
if(head.val != node.val){
return false;
}
head = head.next;
node = node.next;
}
return true;
}
//翻转链表
public ListNode reverseList(ListNode head) {
//当链表为空或者 只有一个是时候,就不需要翻转链表了
if(head == null || head.next == null){
return head;
}
//此时链表至少有两个节点
ListNode dummyHead = new ListNode();//虚拟前驱
ListNode prve = head;//因为要让第一个节点指向null
dummyHead.next = prve;//连接一下
while(prve.next != null){
ListNode node = prve.next;
prve.next = node.next;//跳过头插节点
node.next = dummyHead.next;//进行头插
dummyHead.next = node;//更新新的头节点地址
}
return dummyHead.next;
}
//返回链表的中间节点地址,用的是快慢指针的方法
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
//只有还有下一个节点存在,指针就可以走两步不报错,
//一步走到最后节点上,二步走到 null 不影响。
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
/** 迭代法 *思路一: 要求新链表是原链表拼接而成,说明这里不允许创建新节点。 * 搞一个虚拟节点dummyHead,然后用prve引用连接正确的一次然后, * 向后移。 如果有一个走到空要记得连接另一个 */ //注释要判空,不然会空引用
public class Num_21_mergeTwoLists {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//判空
if(list1 == null){
return list2;
}
//判空
if(list2 == null){
return list1;
}
ListNode dummyHead = new ListNode();//虚拟头节点
ListNode prve = dummyHead;
while (list1 != null || list2 != null){
if(list1.val <= list2.val){
prve.next = list1;
list1 = list1.next;//后移
}else{
prve.next = list2;
list2 = list2.next;//后移
}
prve = prve.next;//后移
}
//判空
if(list1 == null){
prve.next = list2;
}
//判空
if(list2 == null){
prve.next = list1;
}
return dummyHead.next;
}
}
/** * 思路二:递归法。 每次只判断头节点的大小,剩下的交给子方法去处理,并返回地址 */
public class Num_21_mergeTwoLists {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//终止条件
if(list1 == null){
return list2;
}
if(list2 == null){
return list1;
}
if(list1.val <= list2.val){
list1.next = mergeTwoLists(list1.next, list2);
return list1;
}
list2.next = mergeTwoLists(list1, list2.next);
return list2;
}
}
/** * 思路一:创建两个新链表。这两个链表就是由原链表组成的。 * 一个链表存放小于 x 的节点。 * 一个链表存放大于等于 x 的节点 * 然后小链表连接大链表就成功了。 */
public class 面试题_Num02_04_partition {
public ListNode partition(ListNode head, int x) {
//边界--判空
if(head == null || head.next == null){
return head;
}
//存放小于x的链表
ListNode dummyHead1 = new ListNode();
ListNode prve1 = dummyHead1;//尾节点,用于连接
//存放大于等于x的链表
ListNode dummyHead2 = new ListNode();
ListNode prve2 = dummyHead2;//尾节点,用于连接
//方法1 new新的链表
// while(head != null){
// if(head.val < x){
// prve1.next = new ListNode(head.val);
// prve1 = prve1.next;//后移
// }else{
// prve2.next = new ListNode(head.val);
// prve2 = prve2.next;//后移
// }
// head = head.next;
// }
//方法2 原地修改
while(head != null){
if(head.val < x){
prve1.next = head;
prve1 = prve1.next;
head = head.next;
prve1.next = null;//切断后面的连接
}else{
prve2.next = head;
prve2 = prve2.next;
head = head.next;
prve2.next = null;//切断后面的连接
}
}
//小链表连接大链表
prve1.next = dummyHead2.next;
return dummyHead1.next;
}
}
/**
* 思路一:路径相等。
* 我们假设A链表不相等的为 a ,相等路径为 c
* 我们假设B链表不相等的为 b ,相等路径为 c
* 整个A链表路径 = a + c, 整个B链表路径 = b + c
* 如果此时引入两个指针,指针A走完A链表路径再走B链表不相等路程
* 指针B走完B链表路径再走A链表不相等路程
* 两个指针的总路径为: a + c + b = b + c + a; 如果它们相交,此刻肯定在
* 第一个相交的节点上,因为路径相等。
* 如果是不想交的话此刻两个链表值都为 null
*/
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode l1 = headA;
ListNode l2 = headB;
//地址相等的话就结束循环
while(l1 != l2){
l1 = (l1 == null) ? headB : l1.next;
l2 = (l2 == null) ? headA : l2.next;
}
//走到null什么不相交
if(l1 == null ){
return null;
}
return l1;
}
/**
* 思路:用快慢指针。创建两个引用slow和fast。
* slow一次走一步,fast一次走两步。当slow进入环形节点时,
* 当slow走当一半路径时,不管当时的fast在哪肯定会超过slow。
* 因为fast比slow快一倍,slow走一半,fast肯定走完了一圈,
* 如果是环形必然会路过。
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head;//慢指针
ListNode fast = head;//快指针
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
//某一刻路过时
if(slow == fast){
return true;
}
}
return false;
}
}
/** * 思路:假设环形节点前的路径为 a; * 环形入口的节点 到 快慢指针在环内相交时的节点距离为 b * 环形内剩下的距离为 c。 * 那么快指针的路径为 a + N(b + c) + b;(N表示走了N圈) * 那么慢指针的路径为 a + b;(因为慢指针在进入环的时候走一半就肯定会被快指针追上) * 快指针的路径是慢的两倍得出等式: a + N(b + c) + b = 2(a + b); * 化简一下: N(b + c) = a + b,再化简一下,从N里面提出一个 b + c; * * 等于 (N - 1)(b + c) + c = a; * 重点来了!:(N - 1)(b + c)其中(b + c)是一圈的路径 * (N - 1)(b + c)这个只是表示循环了的圈数,走了也等于白走,会回归原地,路程相当于是 0 * 当一个指针从头节点开始走,慢指针从b点上开始走,当两个指针相遇时,必然是入口节点。 * 因为 (N - 1)(b + c) + c = a; 路径是相等的。不懂的话你可以先 * 假设 如果 N == 1的话 a = c */
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;//慢指针
ListNode fast = head;//快指针
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
//某一刻路过时
if(slow == fast){
//此时创建一个新指针让它从头走
ListNode prve = head;
while(prve != slow){
prve = prve.next;
slow = slow.next;
}//循环完后就相遇了
return prve;
}
}
return null;
}
}