简化问题,提升代码效率——从链表节点删除说起

189 阅读3分钟
  • 今天在LeetCode碰到一道链表的算法题,Remove Nth Node From End of List。题目难度不大。首先还是看一下题目:

  • 题目描述:对于一个给定的链表,请删除从链表尾部倒数第n个节点,并返回链表头指针。

  • 题目分析:假设链表长度为k,删除从链表尾部倒数第n个节点,也就是顺序的第k-n+1个节点,将第k-n个节点的next指向第k-n+1个节点指向的下一个节点。那么剩下需要做的就只有找到第k-n个节点。由此就产生了两种不同的思路。

  • 解法一:首先遍历链表获得长度k,然后在从头遍历查找到第k-n个节点,修改k-n的next指向,然后返回链表有指针。

    链表遍历获取长度,代码如下:

    const getListLength = (head) => {
        let count = 0;
        while (head) {
            ++count;
            head = head.next;
        }
        return count
    }
    

    然后查找整个节点,当删除的节点为链表的头部和尾部时需要特殊处理,代码实现如下:

    const removeNthFromEnd = function(head, n) {
        let headp = head;
        let len = getListLength(head);
    
        //当n等于链表长度,也就是删除链表头部
        if (n === len) {
            headp = head.next;
            return headp;
        }
        //当n为1时,删除链表尾部
        if (n === 1 && len === 1) {
            return null;
        }
        len -= n;
        while (len > 1) {
            --len;
            head = head.next;
        }
        //修改节点next指向
        const temp = head.next.next;
        head.next = temp;
        return headp;
    };
    

    上面的代码虽然能够解决问题,但是看上去总是显得拙劣,解题思路不够整齐和巧妙。那么是否可以找到一个解法将所有情况统一处理呢,不再去处理上述两种页数情况,答案是yes。通过给链表添加一个头指针,是特殊情况也纳入统一解法中。代码实现如下:

    const removeNthFromEnd = function(head,n){
        const dummy = ListNode(0);
        dummy.next = head;
        let first = dummy; 
        let len = getListLength(head);
        len -= n;
        while(len > 0){
            first = first.next;
            --len;
        }
        first.next = first.next.next;
        return dummy.next;
    }
    

    通过给链表添加一个头指针,避免掉单独处理边界情况,这种方法值得学习!

  • 解法二: 采用双指针,第两个指针相差n个节点,这样在一次遍历后后面的指针就指向了第k-n个节点。

    const removeNthFromEnd = function(head, n) {
        const dummy = ListNode(0, null);
        dummy.next = head;
        let first = dummy;
        let second = dummy;
        for (let i = 0; i <= n; i++) {
            first = first.next;
        }
    
        while (first !== null) {
            first = first.next;
            second = second.next;
        }
    
        second.next = second.next.next;
        return dummy.next;
    }
    
  • 总结:第一种解法对链表遍历了两次,遍历k+k-n次节点,时间复杂度o(k),第二种解法采用双指针,需要遍历k次。第一次确定first指向的节点,进行了n+1次遍历,然后在后续再次进行了k-n-1次遍历,因此总的遍历次数只有k次,相对于第一种减少了k-n次,如果k是一个大数,那么在指向效率上则会出现明显差距。另外通过给链表添加头指针将问题简化,特殊情况一般化,使得问题的解决思路更统一。