您的位置:首页 > 其它

链表常用技巧:快行指针

2016-03-26 15:46 288 查看
在处理链表的时候,我们经常用到两个指针遍历链表:previous 和 current,也就是current 比 previous 超前一个节点。而这篇博文我要介绍的“快行指针”指针技巧其实也是类似的。

两个指针,一个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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息