如何去除有序數(shù)組的重復(fù)元素

讀完本文,你不僅學(xué)會(huì)了算法套路,還可以順便去 LeetCode 上拿下如下題目:

26.刪除排序數(shù)組中的重復(fù)項(xiàng)

83.刪除排序鏈表中的重復(fù)元素

27.移除元素

283.移動(dòng)零

-----------

我們知道對(duì)于數(shù)組來(lái)說(shuō),在尾部插入、刪除元素是比較高效的,時(shí)間復(fù)雜度是 O(1),但是如果在中間或者開(kāi)頭插入、刪除元素,就會(huì)涉及數(shù)據(jù)的搬移,時(shí)間復(fù)雜度為 O(N),效率較低。

所以上篇文章 O(1)時(shí)間刪除/查找數(shù)組中的任意元素 就講了一種技巧,把待刪除元素交換到最后一個(gè),然后再刪除,就可以避免數(shù)據(jù)搬移。

PS:我認(rèn)真寫(xiě)了 100 多篇原創(chuàng),手把手刷 200 道力扣題目,全部發(fā)布在 labuladong的算法小抄,持續(xù)更新。建議收藏,按照我的文章順序刷題,掌握各種算法套路后投再入題海就如魚(yú)得水了。

有序數(shù)組/鏈表去重

先講講如何對(duì)一個(gè)有序數(shù)組去重,先看下題目:

image

函數(shù)簽名如下:

int removeDuplicates(int[] nums);

顯然,由于數(shù)組已經(jīng)排序,所以重復(fù)的元素一定連在一起,找出它們并不難,但如果毎找到一個(gè)重復(fù)元素就立即刪除它,就是在數(shù)組中間進(jìn)行刪除操作,整個(gè)時(shí)間復(fù)雜度是會(huì)達(dá)到 O(N^2)。

PS:我認(rèn)真寫(xiě)了 100 多篇原創(chuàng),手把手刷 200 道力扣題目,全部發(fā)布在 labuladong的算法小抄,持續(xù)更新。建議收藏,按照我的文章順序刷題,掌握各種算法套路后投再入題海就如魚(yú)得水了。

簡(jiǎn)單解釋一下什么是原地修改:

如果不是原地修改的話,我們直接 new 一個(gè) int[] 數(shù)組,把去重之后的元素放進(jìn)這個(gè)新數(shù)組中,然后返回這個(gè)新數(shù)組即可。

但是原地刪除,不允許我們 new 新數(shù)組,只能在原數(shù)組上操作,然后返回一個(gè)長(zhǎng)度,這樣就可以通過(guò)返回的長(zhǎng)度和原始數(shù)組得到我們?nèi)ブ睾蟮脑赜心男┝恕?/p>

這種需求在數(shù)組相關(guān)的算法題中時(shí)非常常見(jiàn)的,通用解法就是我們前文 雙指針技巧 中的快慢指針技巧。

我們讓慢指針 slow 走在后面,快指針 fast 走在前面探路,找到一個(gè)不重復(fù)的元素就告訴 slow 并讓 slow 前進(jìn)一步。這樣當(dāng) fast 指針遍歷完整個(gè)數(shù)組 nums 后,nums[0..slow] 就是不重復(fù)元素。

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++;
            // 維護(hù) nums[0..slow] 無(wú)重復(fù)
            nums[slow] = nums[fast];
        }
        fast++;
    }
    // 數(shù)組長(zhǎng)度為索引 + 1
    return slow + 1;
}

看下算法執(zhí)行的過(guò)程:

image

再簡(jiǎn)單擴(kuò)展一下,如果給你一個(gè)有序鏈表,如何去重呢?這是力扣第 83 題,其實(shí)和數(shù)組去重是一模一樣的,唯一的區(qū)別是把數(shù)組賦值操作變成操作指針而已:

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;
    }
    // 斷開(kāi)與后面重復(fù)元素的連接
    slow.next = null;
    return head;
}
image

移除元素

這是力扣第 27 題,看下題目:

image

函數(shù)簽名如下:

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

題目要求我們把 nums 中所有值為 val 的元素原地刪除,依然需要使用 雙指針技巧 中的快慢指針:

如果 fast 遇到需要去除的元素,則直接跳過(guò),否則就告訴 slow 指針,并讓 slow 前進(jìn)一步。

這和前面說(shuō)到的數(shù)組去重問(wèn)題解法思路是完全一樣的,就不畫(huà) 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;
}

注意這里和有序數(shù)組去重的解法有一個(gè)重要不同,我們這里是先給 nums[slow] 賦值然后再給 slow++,這樣可以保證 nums[0..slow-1] 是不包含值為 val 的元素的,最后的結(jié)果數(shù)組長(zhǎng)度就是 slow。

PS:我認(rèn)真寫(xiě)了 100 多篇原創(chuàng),手把手刷 200 道力扣題目,全部發(fā)布在 labuladong的算法小抄,持續(xù)更新。建議收藏,按照我的文章順序刷題,掌握各種算法套路后投再入題海就如魚(yú)得水了。

移動(dòng)零

這是力扣第 283 題,我來(lái)描述下題目:

給你輸入一個(gè)數(shù)組 nums,請(qǐng)你原地修改,將數(shù)組中的所有值為 0 的元素移到數(shù)組末尾,函數(shù)簽名如下:

void moveZeroes(int[] nums);

比如說(shuō)給你輸入 nums = [0,1,4,0,2],你的算法沒(méi)有返回值,但是會(huì)把 nums 數(shù)組原地修改成 [1,4,2,0,0]

結(jié)合之前說(shuō)到的幾個(gè)題目,你是否有已經(jīng)有了答案呢?

題目讓我們將所有 0 移到最后,其實(shí)就相當(dāng)于移除 nums 中的所有 0,然后再把后面的元素都賦值為 0 即可。

所以我們可以復(fù)用上一題的 removeElement 函數(shù):

void moveZeroes(int[] nums) {
    // 去除 nums 中的所有 0
    // 返回去除 0 之后的數(shù)組長(zhǎng)度
    int p = removeElement(nums, 0);
    // 將 p 之后的所有元素賦值為 0
    for (; p < nums.length; p++) {
        nums[p] = 0;
    }
}

// 見(jiàn)上文代碼實(shí)現(xiàn)
int removeElement(int[] nums, int val);

至此,四道「原地修改」的算法問(wèn)題就講完了,其實(shí)核心還是快慢指針技巧,你學(xué)會(huì)了嗎?

相關(guān)推薦:

_____________

我的 在線電子書(shū) 有 100 篇原創(chuàng)文章,手把手帶刷 200 道力扣題目,建議收藏!對(duì)應(yīng)的 GitHub 算法倉(cāng)庫(kù) 已經(jīng)獲得了 70k star,歡迎標(biāo)星!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容