如何去除有序數組的重複元素

咱們知道對於數組來講,在尾部插入、刪除元素是比較高效的,時間複雜度是 O(1),可是若是在中間或者開頭插入、刪除元素,就會涉及數據的搬移,時間複雜度爲 O(N),效率較低。git

因此上篇文章 O(1)時間刪除/查找數組中的任意元素 就講了一種技巧,把待刪除元素交換到最後一個,而後再刪除,就能夠避免數據搬移。算法

PS:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,所有發佈在 labuladong的算法小抄,持續更新。建議收藏,按照個人文章順序刷題,掌握各類算法套路後投再入題海就如魚得水了。數組

有序數組/鏈表去重

先講講如何對一個有序數組去重,先看下題目:app

1cbe3f0948d6824aa7dafcd1f1380b7a.jpg

函數簽名以下:ide

int removeDuplicates(int[] nums);

顯然,因爲數組已經排序,因此重複的元素必定連在一塊兒,找出它們並不難,但若是毎找到一個重複元素就當即刪除它,就是在數組中間進行刪除操做,整個時間複雜度是會達到 O(N^2)。函數

PS:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,所有發佈在 labuladong的算法小抄,持續更新。建議收藏,按照個人文章順序刷題,掌握各類算法套路後投再入題海就如魚得水了。spa

簡單解釋一下什麼是原地修改:3d

若是不是原地修改的話,咱們直接 new 一個 int[] 數組,把去重以後的元素放進這個新數組中,而後返回這個新數組便可。指針

可是原地刪除,不容許咱們 new 新數組,只能在原數組上操做,而後返回一個長度,這樣就能夠經過返回的長度和原始數組獲得咱們去重後的元素有哪些了。code

這種需求在數組相關的算法題中時很是常見的,通用解法就是咱們前文 雙指針技巧 中的快慢指針技巧

咱們讓慢指針 slow 走在後面,快指針 fast 走在前面探路,找到一個不重複的元素就告訴 slow 並讓 slow 前進一步。這樣當 fast 指針遍歷完整個數組 nums 後,nums[0..slow] 就是不重複元素

int removeDuplicates(int[] nums) {
    if (nums.length == 0) {
        return 0;
    }
    int slow = 0, fast = 0;
    while (fast < nums.length) {
        if (nums[fast] != nums[slow]) {
            slow++;
            // 維護 nums[0..slow] 無重複
            nums[slow] = nums[fast];
        }
        fast++;
    }
    // 數組長度爲索引 + 1
    return slow + 1;
}

看下算法執行的過程:

04f34a959ab6f013c90028c8d11241dd.gif

再簡單擴展一下,若是給你一個有序鏈表,如何去重呢?這是力扣第 83 題,其實和數組去重是如出一轍的,惟一的區別是把數組賦值操做變成操做指針而已:

ListNode deleteDuplicates(ListNode head) {
    if (head == null) return null;
    ListNode slow = head, fast = head;
    while (fast != null) {
        if (fast.val != slow.val) {
            // nums[slow] = nums[fast];
            slow.next = fast;
            // slow++;
            slow = slow.next;
        }
        // fast++
        fast = fast.next;
    }
    // 斷開與後面重複元素的鏈接
    slow.next = null;
    return head;
}

3bcd836dd59a37b1fe4dfb97325b5820.gif

8306e7ebbef26302521befe437ec97a2.jpg

函數簽名以下:

int removeElement(int[] nums, int val);

題目要求咱們把 nums 中全部值爲 val 的元素原地刪除,依然須要使用 雙指針技巧 中的快慢指針:

若是 fast 遇到須要去除的元素,則直接跳過,不然就告訴 slow 指針,並讓 slow 前進一步。

這和前面說到的數組去重問題解法思路是徹底同樣的,就不畫 GIF 了,直接看代碼:

int removeElement(int[] nums, int val) {
    int fast = 0, slow = 0;
    while (fast < nums.length) {
        if (nums[fast] != val) {
            nums[slow] = nums[fast];
            slow++;
        }
        fast++;
    }
    return slow;
}

注意這裏和有序數組去重的解法有一個重要不一樣,咱們這裏是先給 nums[slow] 賦值而後再給 slow++,這樣能夠保證 nums[0..slow-1] 是不包含值爲 val 的元素的,最後的結果數組長度就是 slow

PS:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,所有發佈在 labuladong的算法小抄,持續更新。建議收藏,按照個人文章順序刷題,掌握各類算法套路後投再入題海就如魚得水了。

移動零

這是力扣第 283 題,我來描述下題目:

給你輸入一個數組 nums,請你原地修改,將數組中的全部值爲 0 的元素移到數組末尾,函數簽名以下:

void moveZeroes(int[] nums);

好比說給你輸入 nums = [0,1,4,0,2],你的算法沒有返回值,可是會把 nums 數組原地修改爲 [1,4,2,0,0]

結合以前說到的幾個題目,你是否有已經有了答案呢?

題目讓咱們將全部 0 移到最後,其實就至關於移除 nums 中的全部 0,而後再把後面的元素都賦值爲 0 便可。

因此咱們能夠複用上一題的 removeElement 函數:

void moveZeroes(int[] nums) {
    // 去除 nums 中的全部 0
    // 返回去除 0 以後的數組長度
    int p = removeElement(nums, 0);
    // 將 p 以後的全部元素賦值爲 0
    for (; p < nums.length; p++) {
        nums[p] = 0;
    }
}

// 見上文代碼實現
int removeElement(int[] nums, int val);

至此,四道「原地修改」的算法問題就講完了,其實核心仍是快慢指針技巧,你學會了嗎?

相關文章
相關標籤/搜索