链表常用技巧:快行指针
2016-03-26 15:46
288 查看
在处理链表的时候,我们经常用到两个指针遍历链表:previous 和 current,也就是current 比 previous 超前一个节点。而这篇博文我要介绍的“快行指针”指针技巧其实也是类似的。
两个指针,一个slowRunner,一个fastRunner;fastRunner 的“快”可以体现在它比 slowRunner 超前 N 个节点,也可以体现在它“跑得更快”,比如slowRunner每次前进一个节点,fastRunner 每次前进2个节点。具体的设计根据实际问题来。下面举几个例子,帮助大家更好地理解。
slowRunner 每次前进 1 个节点;
fastRunner 比 slowRunner 超前 k-1 个节点;每次前进 1 个节点
当fastRunner遍历到最后一个节点时,slowRunner即所求
slowRunner 一次前进1个节点
fastRunner 一次前进2个节点
如果存在环,fastRunner 与 slowRunner 会相遇
假设在环的起始节点loopBegin之前,是k个节点的链表,那么,当slowRunner到达loopBegin时,fastRunner已经走了 2*k 个节点了,它比slowRunner多走了 k 个节点。
设环包含LoopSize个节点,则此时,fastRunner的位置在 k % LoopSize 处。
按照链表方向,此后,没过一个单位时间,fastRunner 就会往 slowRunner靠近一个节点,而两者之间的“距离” 自然就是 LoopSize - k % LoopSize,所以,在(LoopSize - k % LoopSize)个单位时间后,两个节点相遇,节点为collisionNode。
由于是从slowRunner在loopBegin处开始分析的,所以从collisionNode再走(k % LoopSize)个节点,就回到loopBegin了。环就是这么有趣。
collisionNode是很容易就可以得到的,链表的起始节点head是已知的,k 和 LoopSize都只是为了分析的假设值,还不知道。所以,我们得盯着 collisionNode 和 head,想想怎么求 loopBegin。
head 到 loopBegin 距离为k,collisionNode 到 loopBegin 距离为 (k % LoopSize),哇! 可以求解了!
找到collisionNode之后,从一个指针从head 往后遍历,一个指针从collisionNode 往后在环中转圈圈,他们必将在 loopBegin相遇啊!
输出
两个指针,一个slowRunner,一个fastRunner;fastRunner 的“快”可以体现在它比 slowRunner 超前 N 个节点,也可以体现在它“跑得更快”,比如slowRunner每次前进一个节点,fastRunner 每次前进2个节点。具体的设计根据实际问题来。下面举几个例子,帮助大家更好地理解。
1 找出单项链表中倒数第k个节点(k=1指最后一个节点)
链表问题我们应该第一步思考就尝试能不能一次遍历解决问题,所以那些多次遍历的算法我就不介绍了。这里直接介绍“快行指针”的解法。slowRunner 每次前进 1 个节点;
fastRunner 比 slowRunner 超前 k-1 个节点;每次前进 1 个节点
当fastRunner遍历到最后一个节点时,slowRunner即所求
实现
public MyListNode nth2End(MyListNode list,int k) { // slowRunner 和 fastRunner 的初始化 MyListNode slowRunner = list; MyListNode fastRunner = slowRunner; for(int i=0; i<k;i++){ fastRunner = fastRunner.next; if(fastRunner == null){ return null; } } // 遍历开始 while(fastRunner != null){ fastRunner = fastRunner.next; slowRunner = slowRunner.next; } return slowRunner; }
测试
package MyList; import java.util.Random; public class MyListTest { public static void main(String[] args) { MyListTest listTest = new MyListTest(); MyListNode list = listTest.buildList(15); listTest.display(list); int k = 6; MyListNode node = listTest.nth2End(list, k); System.out.printf("The %d-th node to the end is: %d",k,node.val); } public MyListNode nth2End(MyListNode list,int k) { // slowRunner 和 fastRunner 的初始化 MyListNode slowRunner = list; MyListNode fastRunner = slowRunner; for(int i=0; i<k;i++){ fastRunner = fastRunner.next; if(fastRunner == null){ return null; } } // 遍历开始 while(fastRunner != null){ fastRunner = fastRunner.next; slowRunner = slowRunner.next; } return slowRunner; } /** * 创建节点数为num的单项链表,并返回首节点 */ public MyListNode buildList(int num) { if (num == 0) { return null; } MyListNode header = new MyListNode(); Random random = new Random(); int val; MyListNode p = header; for (int i = 0; i < num; i++) { val = random.nextInt(100); p.next = new MyListNode(val); p = p.next; } return header.next; } /** * 打印链表 * */ public void display(MyListNode list) { System.out.println("***** Output the list: *****"); MyListNode p = list; int count = 0; while (p != null) { System.out.printf("%-5d", p.val); p = p.next; count++; if (count == 5) { System.out.println(); count = 0; } } } }
输出
***** Output the list: ***** 89 65 1 1 63 67 11 90 13 31 59 80 49 22 52 The 6-th node to the end is: 31
2 检测链表是否存在环路(不允许额外空间)
算法:slowRunner 一次前进1个节点
fastRunner 一次前进2个节点
如果存在环,fastRunner 与 slowRunner 会相遇
public boolean hasLoop(MyListNode head) { MyListNode slowRunner = head; MyListNode fastRunner = slowRunner; while (fastRunner != null) { // slowRunner每次前进1步 slowRunner = slowRunner.next; // fastRunner每次前进2步 // 每前进一步就进行判空,如果为空则说明到达链表的尾部,返回false fastRunner = fastRunner.next; if(fastRunner != null){ fastRunner = fastRunner.next; } else { return false; } // 判断 slowRunner 与 fastRunner 是否相等 if(slowRunner.equals(fastRunner)){ return true; } } return false; }
***** Output the list: ***** 58 80 78 6 38 94 52 11 94 68 28 86 56 23 87 The 6-th node to the end is: 68 false true
3 找到环路的起始节点(不允许额外空间)
首先,fastRunner比slowRunner快,如果没有环,他们是不可能相遇的,那么他们相遇的节点有什么特性呢?假设在环的起始节点loopBegin之前,是k个节点的链表,那么,当slowRunner到达loopBegin时,fastRunner已经走了 2*k 个节点了,它比slowRunner多走了 k 个节点。
设环包含LoopSize个节点,则此时,fastRunner的位置在 k % LoopSize 处。
按照链表方向,此后,没过一个单位时间,fastRunner 就会往 slowRunner靠近一个节点,而两者之间的“距离” 自然就是 LoopSize - k % LoopSize,所以,在(LoopSize - k % LoopSize)个单位时间后,两个节点相遇,节点为collisionNode。
由于是从slowRunner在loopBegin处开始分析的,所以从collisionNode再走(k % LoopSize)个节点,就回到loopBegin了。环就是这么有趣。
collisionNode是很容易就可以得到的,链表的起始节点head是已知的,k 和 LoopSize都只是为了分析的假设值,还不知道。所以,我们得盯着 collisionNode 和 head,想想怎么求 loopBegin。
head 到 loopBegin 距离为k,collisionNode 到 loopBegin 距离为 (k % LoopSize),哇! 可以求解了!
找到collisionNode之后,从一个指针从head 往后遍历,一个指针从collisionNode 往后在环中转圈圈,他们必将在 loopBegin相遇啊!
public MyListNode getLoopBegin(MyListNode head) { MyListNode slowRunner = head; MyListNode fastRunner = slowRunner; while (fastRunner != null) { // slowRunner每次前进1步 slowRunner = slowRunner.next; // fastRunner每次前进2步 // 每前进一步就进行判空,如果为空则说明到达链表的尾部,返回false fastRunner = fastRunner.next; if (fastRunner != null) { fastRunner = fastRunner.next; } else { System.err.println("The list doesn't have loop!"); return null; } // 判断 slowRunner 与 fastRunner 是否相等 if (slowRunner.equals(fastRunner)) { System.out.println("The collisionNode is: " + slowRunner.val); MyListNode fromHead = head; while (!fromHead.equals(slowRunner)) { fromHead = fromHead.next; slowRunner = slowRunner.next; } return fromHead; } } return null; }
4 完整代码
package MyList; import java.util.HashMap; import java.util.Random; public class MyListTest { public static void main(String[] args) { MyListTest listTest = new MyListTest(); MyListNode head, end; // 随机新建一个链表 HashMap<String, MyListNode> list = listTest.buildList(15); // 获取链表的首位节点 head = list.get("head"); end = list.get("end"); // 打印这个链表看一看 listTest.display(head); int k = 6; // 获得倒数第6个节点 MyListNode node = listTest.nth2End(head, k); System.out.printf("The %d-th node to the end is: %d\n", k, node.val); // 此时链表是不存在环的 boolean ret1 = listTest.hasLoop(head); System.out.println("\n" + ret1); // 创建一个环,再判断含有环吗 end.next = node; boolean ret2 = listTest.hasLoop(head); System.out.println("\n" + ret2); // 求其环的起始节点,看看是不是倒数第6个 MyListNode loopBegin = listTest.getLoopBegin(head); System.out.println("The loopBegin is: " + loopBegin.val); } /** * 获取环的起始节点 */ public MyListNode getLoopBegin(MyListNode head) { MyListNode slowRunner = head; MyListNode fastRunner = slowRunner; while (fastRunner != null) { // slowRunner每次前进1步 slowRunner = slowRunner.next; // fastRunner每次前进2步 // 每前进一步就进行判空,如果为空则说明到达链表的尾部,返回false fastRunner = fastRunner.next; if (fastRunner != null) { fastRunner = fastRunner.next; } else { System.err.println("The list doesn't have loop!"); return null; } // 判断 slowRunner 与 fastRunner 是否相等 if (slowRunner.equals(fastRunner)) { System.out.println("The collisionNode is: " + slowRunner.val); MyListNode fromHead = head; while (!fromHead.equals(slowRunner)) { fromHead = fromHead.next; slowRunner = slowRunner.next; } return fromHead; } } return null; } public boolean hasLoop(MyListNode head) { MyListNode slowRunner = head; MyListNode fastRunner = slowRunner; while (fastRunner != null) { // slowRunner每次前进1步 slowRunner = slowRunner.next; // fastRunner每次前进2步 // 每前进一步就进行判空,如果为空则说明到达链表的尾部,返回false fastRunner = fastRunner.next; if (fastRunner != null) { fastRunner = fastRunner.next; } else { return false; } // 判断 slowRunner 与 fastRunner 是否相等 if (slowRunner.equals(fastRunner)) { return true; } } return false; } public MyListNode nth2End(MyListNode head, int k) { // slowRunner 和 fastRunner 的初始化 MyListNode slowRunner = head; MyListNode fastRunner = slowRunner; for (int i = 0; i < k; i++) { fastRunner = fastRunner.next; if (fastRunner == null) { return null; } } // 遍历开始 while (fastRunner != null) { fastRunner = fastRunner.next; slowRunner = slowRunner.next; } return slowRunner; } /** * 创建节点数为num的单项链表,并保存其 首节点 和 尾部节点 */ public HashMap<String, MyListNode> buildList(int num) { if (num < 1) { return null; } MyListNode header = new MyListNode(); Random random = new Random(); int val; MyListNode p = header; for (int i = 0; i < num; i++) { val = random.nextInt(100); p.next = new MyListNode(val); p = p.next; } HashMap<String, MyListNode> hashMap = new HashMap<>(); hashMap.put("head", header.next); hashMap.put("end", p); return hashMap; } /** * 打印链表 */ public void display(MyListNode list) { System.out.println("***** Output the list: *****"); MyListNode p = list; int count = 0; while (p != null) { System.out.printf("%-5d", p.val); p = p.next; count++; if (count == 5) { System.out.println(); count = 0; } } } }
输出
***** Output the list: ***** 95 12 36 3 10 99 8 88 79 28 6 28 53 45 5 The 6-th node to the end is: 28 false true The collisionNode is: 53 The loopBegin is: 28
相关文章推荐
- [C/C++]反转链表
- C#实现基于链表的内存记事本实例
- C语言实现带头结点的链表的创建、查找、插入、删除操作
- C++实现简单的学生管理系统
- Linux内核链表实现过程
- C++链表倒序实现方法
- C#通过链表实现队列的方法
- C#实现的简单链表类实例
- 找出链表倒数第n个节点元素的二个方法
- Java数据结构之简单链表的定义与实现方法示例
- C语言单循环链表的表示与实现实例详解
- C++实现的链表类实例
- PHP小教程之实现链表
- PHP中模拟链表和链表的基本操作示例
- C语言双向链表的表示与实现实例详解
- js链表操作(实例讲解)
- C语言实现输出链表中倒数第k个节点
- C++语言实现线性表之链表实例
- STL list链表的用法详细解析
- C语言创建链表错误之通过指针参数申请动态内存实例分析