【路飞】重排链表&&环路检测

144 阅读3分钟

记录两道算法题

重排链表

leetcode-cn.com/problems/re…


先看一个例子

1 -> 2 -> 3 -> 4

从后面的节点开始,倒着插入到前面的节点。

1 -> 4 -> 2 -> 3

可以看作是将 4 插入 1.next, 将 3 插入 2.next

如果是奇数长度的链表

1 -> 2 -> 3 -> 4 -> 5

1 -> 5 -> 2 -> 4 -> 3

(1)可以看出链表的终点是链表的中间节点

(2)倒数第n个节点成为第n个节点的next

如果用数组来做,下面就可以不用看了,把节点遍历一遍push到数组,然后通过数组下标进行操作,毫无难度。本来数组就是比单向链表方便的。

如果用链表怎么做呢?链表只能看看怎么遍历保留想要的东西。

那么如果我们用while循环来做的话,终止的条件可以是当节点到了中间节点就停下来。

把链表通过中间节点折成两半的话,对折起来刚刚好是对称的,再相加一挤就嵌进去了,就是我们想要的结果。所以我们还需要得到后半部分链表的反转。

需要两个算法的知识点

1. 找到链表的中间节点
2. 反转链表
123456789
  1. 用快慢指针

    链表长度是存在奇数和偶数的,一个在前面的指针走到 null 为止,另一个指针随着前面的指针停下来而停下来,会得到一个相对位置差。这是快慢指针的设计想法。

    一个指针走一步,一个指针走两步,他们的差距就会是 2 的倍数。而链表的中间就是链表的一半。两者之间是可以有这么一个联想的。大家可以自己指一下走一遍。

    大家可以用5秒钟思考一下,如果一个指针走一步,一个指针走三步。那走一步的指针会停在链表的哪一个位置。

    5,2,1。好,如果链表足够长的话,是基本上停在了3分之一的前后。一个没什么用的联想。

    let n1 = node, n2 = node
    while(n2) {
        n1 = n1.next
        n2 = n2.next.next
    }
  1. 反转链表-用3个指针 我之前的文章
    主要的思路是这样的,用两个变量改变指向。用一个变量保存被斩断的节点。
    a -> b -> c -> d -> e -> f
    1    2    3
    
    a <- b -- c -> d -> e -> f
    1    2    3
    
    a <- b -- c -> d -> e -> f
         1    2    3
         
    a <- b <- c -- d -> e -> f
         1    2    3
         
    ...
    
    重复这样的步骤直到 3 指向 null

代码如下:

    let n1 = head, n2 = head.next, n3 = n2
    while(n3) {
        n3 = n2.next
        n2.next = n1
        n1 = n2
        n2 = n3
    }
    head.next = null
    
    // n1 将是链表的开头,head将是链表的结尾

那么组合一下,最终的代码是:

    function Node(val, next) {
        this.val = val
        this.next = next
    }
        
        // 测试
      const a = new Node(1, new Node(2, new Node(3, new Node(4, null))))
      console.log(reorderList(a))

      function reorderList(head) {
        let n1 = head,
          n2 = head
        while (n2 && n2.next) {
          n1 = n1.next
          n2 = n2.next.next
        }
        
        // 中间节点
        const mid = n1
        
        // 复用了变量
        n2 = mid.next
        let n3 = n2
        // 反转链表
        // 这里是从中间节点开始。反转后半部分的链表
        while (n3) {
          n3 = n2.next
          n2.next = n1
          n1 = n2
          n2 = n3
        }
        
        // 解决 相互指向
        mid.next = null

        n2 = head
        // 从头遍历到中间节点停下来
        while (n2 !== mid) {
          n3 = n2.next
          // 当链表长度是偶数的时候,会少插入一次
          // 比如 中间节点是 3,
          // 1 -> 2 -> 3 -> 4
          // 1 -> 4 -> 2 -> 3
          if (n1 === n3) {
            break
          }
          // 将后半部分节点加入到对应节点的next
          // 1 -> 2
          // 4 -> 3
          // 1 -> 4 -> 2 -> 3
          n2.next = n1
          const temp = n1.next
          n2 = n1.next = n3
          n1 = temp
        }

        return head
     }

环路检测

leetcode-cn.com/problems/li…


这道题用一个 map 进行标记已经出现过的节点,当第二次出现的时候,这个节点就是答案,没有就是null。

    function detectCycle(head) {
        const map = new Map()
        let node = head
        while (node) {
          if (map.has(node)) {
            return node
          } else {
            map.set(node, true)
          }
          node = node.next
        }

        return null
    }

还有一直不生成额外空间的方法,需要列等式,还没理清楚。快慢指针有闭环是一定会相遇的,但和环路的开头节点有什么样的联系还有待解决。到时候有机会更新这篇文章。