[路飞]_前端算法第七弹-快慢指针

51 阅读4分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

快慢指针,顾名思义,就是有快的和慢的两个指针分别指向链表,根据其移动的速度差,来完成一系列问题的实现。下面这两道题,就是利用了快慢指针的方法。

返回倒数第 k 个节点

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

输入: 1->2->3->4->5 和 k = 2
输出: 4

这是一道快慢指针的题,我们需要两个指针,一个快指针,一个慢指针, 快指针比慢指针多走k步,当快指针走完全程的时候,慢指针刚好走到指定位置.

当快指针fast刚开始移动的时候,慢指针slow不移动,直到快指针和慢指针之间的间距
fast-slow = k的时候,慢指针才和快指针一起以相同的速度移动,当快指针走完全程的时候
慢指针slow刚好距离终点k个节点。


var kthToLast = function (head, k) {
  // 快指针
  let fast = 0;
  // 慢指针
  let slow = 0;
  快指针指向的链表
  let link = head;
  // 如果快指针指向的链表循环完成,则慢指针指向的链表为所求节点
  while (link) {
    // 如果快慢指针间距达到所需距离,慢指针移动
    if (fast - slow == k) {
      head = head.next;
      slow++
    }
    // 快指针移动
    link = link.next;
    fast++;
  }
  // 返回慢指针指向节点值
  return head.val

判断环形链表

给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

如果链表中存在环,则返回 true 。 否则,返回 false 。

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

这道题有很多种做法,我们先来分析第一种,也就是今天讨论的双指针法,因为我们只需要判断它是不是一个环,并不需要知道环的起始点是哪里,所以只要快慢指针重合,就能证明这是一个闭合链表。

var hasCycle = function (head) {
    // 特殊值判断,如果链表不存在或者只有一个值,那么快慢指针则无法用,所以需要先排除掉
    if(!head||!head.next) return false
    // 定义快慢指针,快指针比慢指针快一步,并且快指针一次走两步。
    let fast = head.next.next;
    let slow = head.next;
    // 终止条件,如果链表不是循环链表,则必有终点
    while(fast&&fast.next){
        // 如果快慢指针重合,说明是循环链表
        if(fast==slow) return true;
	// 慢指针走一步,快指针走两步
        slow = slow.next;
        fast = fast.next.next;
    }
    // while结束说明没有环
    return false

};

上面就是这道题快慢指针的做法,一个一次走一步,一个一次走两步,如果循环一定会相遇,因为比如当快指针开始循环,此时比慢指针落后两步,那下一次一定只落后一步,再下一次就会相逢,因为快指针每次只多走一步,所以必会重逢。

说完了快慢指针,刚刚还说过这道题有多种解法,我们来看一看其他解法。

这里最值得一提的就是hash算法,我们设定一个hash地址,将没有存入的结点存入,判断下一个结点,如果遇到了已经存入的结点,那就说明遇到了循环。

var hasCycle = function (head) {
// 创建一个空地址
let set = new Set();
    // 循环链表
    while (head) {
        // 判断该节点有没有存入过
        if (set.has(head)) {
            return true
        } else {
	    // 没有存入过的结点存入
            set.add(head)
        }
        head = head.next
    }
    return false
};

其余方法

var hasCycle = function (head) {
    // 方法1 JavaScript属性
    // 这是根据JavaScript的属性,如果遇到循环链表则会报错
     try {
        JSON.stringify(head)
    } catch{
        return true
    }
    return false
    // 方法2 污染链表
    while(head){
	// 将每一个路过的结点都进行标记,下次再遇到则证明已经来过
        if(head.sign) return true;
        head.sign = true;
        head = head.next
    }
    return false
};