剑指Offer——链表中快行指针用法(链表中倒数第k个结点等)

from:http://blog.youkuaiyun.com/woliuyunyicai/article/details/49687339

问题一、链表中倒数第k个结点

题目描述:

输入一个链表,输出该链表中倒数第k个结点。

问题分析:
典型的链表快行指针法,设置两个指针,使前一个指针p1先走k步,然后两个指针p1,p2同时出发;当p1走到链表末尾的时候,p2所指的元素即是所要求的链表倒数第k个节点;
实现方法要注意区别:先行指针p2先走k步,循环的截止条件是p2 == null;
                                  先行指针p2先走k-1步,循环的截止条件为p2.next == null;

代码:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /* 
  2. public class ListNode { 
  3.     int val; 
  4.     ListNode next = null; 
  5.  
  6.     ListNode(int val) { 
  7.         this.val = val; 
  8.     } 
  9. }*/  
  10. public class Solution {  
  11.     public ListNode FindKthToTail(ListNode head,int k) {  
  12.         // 取出null情况  
  13.         if (k == 0 || head == null)  
  14.             return null;  
  15.         ListNode p1 = head;  
  16.         ListNode p2 = head;  
  17.   
  18.         // 先p2走k-1步(因为p1,p2指向head其实已经走了一步)  
  19.         for (int i = 0; i < k - 1; i ++) {  
  20.             if (p2.next == null)  
  21.                 return null;  
  22.             p2 = p2.next;  
  23.         }  
  24.   
  25.         while (p2.next != null) {  
  26.             p1 = p1.next;  
  27.             p2 = p2.next;  
  28.         }  
  29.   
  30.         return p1;  
  31.     }  
  32. }  


问题二、前后两端链表插入的问题(查找链表的中间节点)

问题描述:

    假定有一个链表a1->a2->.......->an->b1->.....->bn,想要将其重新排列成:a1->b1->a2->b2->.....an->bn,(假设链表长度是偶数个,但不知道链表的确切长度)

问题分析:

    因为不知道链表的具体长度,因此需要遍历链表来确定链表长度;但没必要再遍历第二次去确定an和b1的分解线,即中间节点(因此这个问题的前一部分其实是查找链表的中间节点的问题)

    采用双指针法,p1,p2分别从链表的头前节点出发,p1每步走1步,p2每步走2步;当p2走到尾节点bn时,p1此时走到的节点必然是an;(推广到链表长度为任意数的情况,p1最终仍然指向的中间节点或者中间节点中的一个)。

代码: 

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class B {  
  2.     // 假设链表的长度是偶数的  
  3.     public ListNode insertNode(ListNode head) {  
  4.         if (head == null)  
  5.             return null;  
  6.         // 双指针法,一个每次走一步;一个每次走两步  
  7.         ListNode p1 = head;  
  8.         ListNode p2 = head;  
  9.   
  10.         // 进行快行遍历  
  11.         while ((p2 != null) && (p2.next != null) && (p2.next.next != null)) {  
  12.             p1 = p1.next;  
  13.             p2 = p2.next.next;  
  14.         }  
  15.   
  16.         // 此时p1对应位置为an,p2对应位置为bn  
  17.         p2 = p1.next;  
  18.         p1.next = null// 这一步很重要,注意要将链表从中间断开  
  19.         p1 = head;  
  20.   
  21.         // 进行插入  
  22.         while (p2 != null) {  
  23.             // 注意保存p1,p2的后继值  
  24.             ListNode temp1 = p1.next;  
  25.             ListNode temp2 = p2.next;  
  26.             p1.next = p2;  
  27.             p2.next = temp1;  
  28.   
  29.             p1 = temp1;  
  30.             p2 = temp2;  
  31.         }  
  32.         return head;  
  33.     }  
  34.   
  35.   
  36.     // ============  测试 ==================  
  37.     public static void main(String[] args) {  
  38.         int length = 10;  
  39.         ListNode[] nodes = new ListNode[length];  
  40.         for (int i = length; i > 0; i--) {  
  41.             nodes[i - 1] = new ListNode(i - 1);  
  42.             if (i != length)  
  43.                 nodes[i - 1].next = nodes[i];  
  44.         }  
  45.   
  46.         B a = new B();  
  47.         a.printNodes(a.insertNode(nodes[0]));  
  48.     }  
  49.   
  50.     public void printNodes(ListNode head) {  
  51.         while (head != null) {  
  52.             System.out.print(head.val + "->");  
  53.             head = head.next;  
  54.         }  
  55.     }  
  56.   
  57. }  
返回中间节点是 上述问题的其中一部分:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 假设链表的长度是偶数的  
  2. public ListNode insertNode(ListNode head) {  
  3.     // 双指针法,一个每次走一步;一个每次走两步  
  4.     ListNode p1 = head;  
  5.     ListNode p2 = head;  
  6.   
  7.     // 进行快行遍历  
  8.     while ((p2 != null) && (p2.next != null) && (p2.next.next != null)) {  
  9.         p1 = p1.next;  
  10.         p2 = p2.next.next;  
  11.     }  
  12.   
  13.     return p1;  
  14. }  

问题三、判断链表是否有环:

1、简单版:仅判断链表是否有环

问题分析:判断是否有环,依然采用快行指针法,使用p1,p2;p1走一步,p2走两步;若链表有环,则p1,p2一定会相遇;

证明:假设在行进过程中,p2跳过了p1,走到了位置i + 1上,此时p1所在位置为i(因为假设p2跳过了p1);则在前一步中p2所处的位置为(i + 1 - 2) = i - 1;而p1所处的位置也为i - 1,可见两者必然相遇,不会发生跳过的现象。

代码:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public boolean isCircle(ListNode head) {  
  2.     ListNode p1 = head;  
  3.     ListNode p2 = head;  
  4.   
  5.     while ((p2 != null) && (p2.next != null)) {  
  6.         p2 = p2.next.next;  
  7.         p1 = p1.next;  
  8.   
  9.         if (p1 == p2) return true;  
  10.     }  
  11.     return false;  
  12. }  

2、提高版:找到环的入口节点


问题分析:假设头节点head距离入口节点的距离为k;
因为p2的速度是p1速度的两倍,则p2行进的路程是p1的两倍;当p1走k步走到入口节点,p2则走了2k步,则p2走过了入口节点并往前又走了k步;
现以入口节点root为起点,则p1在0处,p2在k处,p1,p2此时都在环内,由追及问题可知,两者必然相遇在环内某一点上;且k的长度可能很长(即环的长度为size)则此时p2在(k mod size)处,记为K;由追及问题,p2在p1前面K步,又p2的速度快于p1,p2追赶p1,则p2落后于p1共(size - K)步;由p2每次比p1多走一步,则共需要(size-K)步之后,两者便会相遇,记该点为reach;显然该点距离入口节点的距离为(size - (size - K)) = K步(因为p1从0位置走了(size - K)步);

由前面分析知,头节点head距离入口节点root的距离为k;又相遇节点reach距离入口节点的距离也为K(k = K + n*size),则两个指针分别头节点head与相遇节点reach出发往前遍历,最后一定会相遇,并且相遇点一定是入口节点。

代码:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class A {  
  2.     private ListNode isCircle(ListNode head) {  
  3.         ListNode p1 = head;  
  4.         ListNode p2 = head;  
  5.   
  6.         while ((p2 != null) && (p2.next != null)) {  
  7.             p2 = p2.next.next;  
  8.             p1 = p1.next;  
  9.   
  10.             if (p1 == p2)  
  11.                 return p2;  
  12.         }  
  13.         return null;  
  14.     }  
  15.   
  16.     public ListNode findRoot(ListNode head) {  
  17.         ListNode reach = null;  
  18.         if ((reach = isCircle(head)) == null)  
  19.             return null;  
  20.         ListNode p1 = head;  
  21.         ListNode p2 = reach;  
  22.   
  23.         while (p1 != p2) {  
  24.             p1 = p1.next;  
  25.             p2 = p2.next;  
  26.         }  
  27.         return p1;  
  28.     }  
  29.   

  1. }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值