【路飞】两两交换链表中的节点&删除链表倒数第n个

190 阅读3分钟

分享两道算法题

两两交换链表中的节点

leetcode-cn.com/problems/sw…


首先进行需求分析。

  1. 两两交换其实是 2个一组的反转链表,还是指针的使用。
  2. 简单的反转需要3步,例如a -> b -> c,第一步 用一个临时变量temp保存 c,第二步 a -> c, 第三步 b -> a,第四步 返回temp作为下一次反转的开始节点。
  3. 需要注意的是,当第二次两两交换的时候,是会出现需要将开始节点的上一个节点重新指向反转后的新的开始节点的情况。简单来说就是 a -> b -> c, b 和 c 反转后需要让 a 重新指向 c ,即 a -> c -> b.
  4. 最后如果链表的长度是单数,意味着最后一个节点是单独的,没有下一个节点和它进行反转,即需要留意 a.next === null.

然后一行行写代码把上面的要求实现吧。

    function swapPairs(head) {
        if (head?.next) return head
        
        // 首先准备返回的节点,反转后的第一个节点,整个链表的起始节点
        // 既然是两两交换,自然是 head.next
        const ret = head.next
        
        let n1 = head // 只是改个名字方便理解,不是必要的
        
        // 当每一次反转都会将下一次的开头保存出来
        // 如果是单数节点,将不会存在n2
        let lastEnd // 上一个节点
        while(n1) {
            let n2 = n1.next
            if (!n2) {
                break
            }
            
            let n3 = n2.next
            
            if (lastEnd) {
                // 只有第二次反转才有上一个
                lastEnd.next = n3
            }
            lastEnd = n1
            
            // 反转
            n1.next = n3
            n2.next = n1
            
            n1 = n3
        }
        
        return ret
    }

删除链表倒数第 n 个节点

leetcode-cn.com/problems/re…


还是先分析一个需求吧

  1. 看到其中有个要求写道 你能用一躺扫描实现吗,这个挑战接下了。首先关键词是扫描。
  2. 提到扫描不可避免的就是指针,又是一道考指针的思想的题目。

问题是怎么知道要删除的节点是哪一个,在扫描的过程中能保存什么。

如果能得到要删除的节点的前一个节点就好了。

a -> b -> c -> d -> e 假如要删除倒数第二个的话 可以数出来是 d, 扫描一遍就是指针从 a 依次指到 e 的过程,一个指针保存不了什么那就用两个。如果两个指针之间有条件联系起来的话。

假设一前一后的话
a -> b -> c -> d -> e
1    2

如果中间有距离的话
a -> b -> c -> d -> e
1         2

那么当扫描一遍之后,可以获得跟随着的指针指着的节点,
你走一步,我走一步,直到走不动
a -> b -> c -> d -> e
          1         2

那么问题就是这种间距怎么和倒数第n个建立起联系。

假如
a -> b -> c -> d -> e 倒数第二个 n = 2 人为数应该是 d

我们想要指针停在 c , 那和结尾就相隔 1 个节点
那起始状态应是
a -> b -> c -> d -> e
1         2
2所指的位置刚好是 从 a 开始走了 2 步

所以简单的demo应该是
let p = head
for(let i = 0; i < n; i++) {
    p = p.next // p 将会作为先锋队
}

while (p.next) {
    p = p.next
    head = head.next
}

联系建立起来了,还要考虑一下各种边界情况

前提条件: 1 <= n <= 链表长度

  1. 有个特殊情况,当链表的长度是 1 的时候,只会返回 null
  2. 还有一个特殊情况,当链表长度是 2 的时候,n = 2 时,这个在前面的指针指的会是 nulla -> b 如果走两步,就是 b.next
  3. 当 n = 链表长度的时候,即删除第一个节点,这时候在前面的指针 p 指的是 null
  4. 其实第二点和第三点是一样的。

上代码

    function removeNthFromEnd(head, n) {
        let p = head
        if (!(p = p.next)) {
            // 链表长度为 1 时
            return p
        } else {
            // 第二个节点已经读过了,由于不想重复读值所以用了一个 p 作为指针
            // 这是 p 是指着第二个节点的, 已经走过一步了
            // 所以是 n - 1
            for(let i = 0; i < n - 1; i++) {
                p = p.next
            }
            
            // 上面提到的注意事项里 当删除第一个的时候, p 将会是 null
            if (!p) {
                return head.next
            }
            
            let follow = head
            while (p.next) {
                p = p.next
                follow = follow.next
            }
            
            // 删除节点
            follow.next = follow.next.next
            return head
        }
    }

结束