链表常用技巧:快行指针

在处理链表的时候,我们经常用到两个指针遍历链表: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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值