算法珠玑——双指针(4)

双指针(4)

https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list

栈(递归),双指针都涉及,是道好题。

我的原做法,性能还可以,也达到过100%
主要是用了双倍的指针进度。


这是原做法的性能,在两方面都可圈可点。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {        
        ListNode* p = head;

        int len = 1;
        while(p && p->next)
        {
            p = p->next->next;
            len+=2;
        }
        if (!p) len--;

        p = head;
        // 目标结点n, bound为n-1
        int bound = len - n;
        
        if (bound == 0)
            return head->next;
            
        for (int i = 1; i < bound; i+=2)
        {
            if (p && p->next && (i+1) != bound)
                p = p->next->next;
            else
                p = p->next;
        }

        if (p->next)
            p->next = p->next->next;
        else
            p->next = nullptr;
        return head;
    }
};

另一种做法,我觉得比我做的想法更妙一点(且更简单一点),使用了相差n的双指针。但是速度和我差不多。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {        
        if(!head -> next) return nullptr;
        ListNode *fast = head;
        ListNode *slow = head;

        for(int i = 0; i < n; i++){
            fast = fast->next;
        }

        if(!fast){
            return head->next;    
        }
        
        while(fast -> next){
            fast = fast -> next;
            slow = slow -> next;
        }
        slow -> next = slow -> next -> next;
        return head;
    }
};

最亮眼的是这个做法。是我打算讲的。
这是递归的做法,我觉得性能可能有一点问题。但是偶尔也能过100%。

class Solution {
public:
    int cur = 0;
    ListNode* removeNthFromEnd(ListNode* head, int n) {        
        if(!head) return nullptr;
        head->next = removeNthFromEnd(head->next,n);
        cur++;
        if(n==cur) return head->next;
        return head;
    }
};

这道题应该有可以继续优化的空间。
是第一种还是其他性能更好呢?很明显后两种比较通俗,我发现后两种的性能问题主要在于没有使用第一个fast = fast->next->next技巧。

我把第二种改成了这样:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {        
        if(!head -> next) return nullptr;
        ListNode *fast = head;
        ListNode *slow = head;

        for(int i = 0; i < n;){
            if (fast && fast->next && (i+1) != n)
            {
                fast = fast->next->next;
                i+=2;
            }
            else
            {
                fast = fast->next;
                i++;
            }
        }

        if(!fast){
            return head->next;    
        }
        
        while(fast->next){
            fast = fast->next;
            slow = slow->next;
        }
        slow -> next = slow -> next -> next;
        return head;
    }
};

结果

性能优化是一辈子的事情。

最后讲一下这个递归方法。
int变量定义在外是为了减少递归中的变量声明调用内存浪费。

class Solution {
public:
    int cur = 0;
    // 整个归纳步骤:
    // 因为递归是一整个归纳,故基本情况和终止情况也要放在外面
    ListNode* removeNthFromEnd(ListNode* head, int n) {    
        // 基本情况下的变化部分(非基本情况): a(1) == nullptr
        if(!head) return nullptr;
        // 归纳步骤:核心部分1
        head->next = removeNthFromEnd(head->next,n);
        cur++;
        // 终止情况下的变化部分(非终止情况): a(x) == head->next 
        if(n==cur) return head->next;
        // 归纳步骤:核心部分2
        return head;
    }
};
posted @ 2021-12-04 21:48  千心  阅读(28)  评论(0)    收藏  举报