2018年力扣高頻算法面試題6鏈表

之前在CSDN寫過一篇 鏈表與快慢指針 的筆記(判斷鏈表是否有環、找到環的入口、反轉鏈表):

刪除鏈表中的節點

請編寫一個函數,使其可以刪除某個鏈表中給定的(非末尾)節點,你將只被給定要求被刪除的節點。
分析:之前有同學去哈深面試也問過類似的問題。沒有給我們鏈表的起點,只給我們了一個要刪的節點,跟我們以前遇到的情況不太一樣,我們之前要刪除一個節點的方法是要有其前一個節點的位置,然後將其前一個節點的next連向要刪節點的下一個,然後delete掉要刪的節點即可。這道題的處理方法是先把當前節點的值用下一個節點的值覆蓋了,然後我們刪除下一個節點即可
吐槽:誰能想到還可以用替身攻擊,還可以把下一個的複製到當前節點,然後把下一個節點的刪掉,神奇!!!

class Solution {
public:
    void deleteNode(ListNode* node) {
           ListNode* old_node_next = node->next;
            node->val = old_node_next->val;
            node->next = old_node_next->next;
            delete old_node_next;
    }
};

複製帶隨機指針的鏈表

給定一個鏈表,每個節點包含一個額外增加的隨機指針,該指針可以指向鏈表中的任何節點或空節點。
要求:返回這個鏈表的深拷貝,必須返回給定頭的拷貝作爲對克隆列表的引用。
其實,,,完全沒看懂這個題。。。
解答:使用遞歸的解法,寫起來相當的簡潔,還是需要一個 HashMap 來建立原鏈表結點和拷貝鏈表結點之間的映射。在遞歸函數中,首先判空,若爲空,則返回空指針。然後就是去 HashMap 中查找是否已經在拷貝鏈表中存在了該結點,是的話直接返回。否則新建一個拷貝結點 res,然後建立原結點和該拷貝結點之間的映射,然後就是要給拷貝結點的 next 和 random 指針賦值了,直接分別調用遞歸函數即可,參見代碼如下:

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;

    Node() {}

    Node(int _val, Node* _next, Node* _random) {
        val = _val;
        next = _next;
        random = _random;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        unordered_map<Node*, Node*> m;
        return helper(head, m);
    }
    Node* helper(Node* node, unordered_map<Node*, Node*>& m) {
        if (!node) return nullptr;
        if (m.count(node)) return m[node];
        Node *res = new Node(node->val, nullptr, nullptr);
        m[node] = res;
        res->next = helper(node->next, m);
        res->random = helper(node->random, m);
        return res;
    }
};

相交鏈表

編寫一個程序,找到兩個單鏈表相交的起始節點。
要求:如果兩個鏈表沒有交點,返回 null.在返回結果後,兩個鏈表仍須保持原有的結構。可假定整個鏈表結構中沒有循環。程序儘量滿足 O(n) 時間複雜度,且僅用 O(1) 內存。
這個題真的是easy等級!!!然而我真的沒寫出來,哭。一開始想的是反轉鏈表,這樣就可以從頭開始比較,結果題目要求鏈表必須保持原有的結構。
解析:方法一:分別遍歷兩個鏈表,得到分別對應的長度。然後求長度的差值,把較長的那個鏈表向後移動這個差值的個數,然後一一比較即可

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (!headA || !headB) return NULL;
        int lenA = getLength(headA), lenB = getLength(headB);
        if (lenA < lenB) {
            for (int i = 0; i < lenB - lenA; ++i) headB = headB->next;
        } else {
            for (int i = 0; i < lenA - lenB; ++i) headA = headA->next;
        }
        while (headA && headB && headA != headB) {
            headA = headA->next;
            headB = headB->next;
        }
        return (headA && headB) ? headA : NULL;
    }
    int getLength(ListNode* head) {
        int cnt = 0;
        while (head) {
            ++cnt;
            head = head->next;
        }
        return cnt;
    }
};

方法二:雖然題目中強調了鏈表中不存在環,但是我們可以用環的思想來做,我們讓兩條鏈表分別從各自的開頭開始往後遍歷,當其中一條遍歷到末尾時,我們跳到另一個條鏈表的開頭繼續遍歷。兩個指針最終會相等,而且只有兩種情況,一種情況是在交點處相遇,另一種情況是在各自的末尾的空節點處相等。爲什麼一定會相等呢,因爲兩個指針走過的路程相同,是兩個鏈表的長度之和,所以一定會相等。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (!headA || !headB) return NULL;
        ListNode *a = headA, *b = headB;
        while (a != b) {
            a = a ? a->next : headB;
            b = b ? b->next : headA;
        }
        return a;
    }
};

排序鏈表

在 O(n log n) 時間複雜度和常數級空間複雜度下,對鏈表進行排序。
分析:看到時間複雜度O(nlgn)最先想到的就是把鏈表的數據存到vector,用sort進行排序,然後把排好序的數據重新建成一個鏈表…很明顯這個方法效率太低.
歸併排序:歸併排序的核心其實是分治法 Divide and Conquer,就是將鏈表從中間斷開,分成兩部分,左右兩邊再分別調用排序的遞歸函數 sortList(),得到各自有序的鏈表後,再進行 merge(),這樣整體就是有序的了。

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if (!head || !head->next) return head;
        ListNode *slow = head, *fast = head, *pre = head;
        while (fast && fast->next) {
            pre = slow;
            slow = slow->next;
            fast = fast->next->next;
        }
        pre->next = NULL;
        return merge(sortList(head), sortList(slow));
    }
    ListNode* merge(ListNode* l1, ListNode* l2) {
        if (!l1) return l2;
        if (!l2) return l1;
        if (l1->val < l2->val) {
            l1->next = merge(l1->next, l2);
            return l1;
        } else {
            l2->next = merge(l1, l2->next);
            return l2;
        }
    }
};

補充幾個其他類型算法題:

刪除排序數組中的重複項

給定一個排序數組,你需要在原地刪除重複出現的元素,使得每個元素只出現一次,返回移除後數組的新長度。
不要使用額外的數組空間,你必須在原地修改輸入數組並在使用 O(1) 額外空間的條件下完成。
鏈接:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array
方法一:理解無礙,但是比較慢

nums.erase(unique(nums.begin(),nums.end()),nums.end());
return nums.size();

方法二:leetcode官網給的答案是用雙指針做:數組完成排序後,我們可以放置兩個指針i和j,其中i是慢指針,而j是快指針。只要nums[i]==nums[j],我們就增加j以跳過重複項。當我們遇到 nums[j]!=nums[i]時,跳過重複項的運行已經結束,因此我們必須把它nums[j]的值複製到nums[i+1]。然後遞增i,接着我們將再次重複相同的過程,直到j到達數組的末尾爲止。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int n=nums.size();
        if(n<=1)return n;
        
        int i=0,j=1;
        while(j<n)
        {
            if(nums[i]!=nums[j])
            {
                nums[i+1]=nums[j];
                i++;
            }
             j++;
        }
        return i+1;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章