LeetCode 19. Remove Nth Node From End of List

題目描述(中等難度)

給定一個(gè)鏈表,將倒數(shù)第 n 個(gè)結(jié)點(diǎn)刪除。

解法一

刪除一個(gè)結(jié)點(diǎn),無非是遍歷鏈表找到那個(gè)結(jié)點(diǎn)前邊的結(jié)點(diǎn),然后改變下指向就好了。但由于它是鏈表,它的長(zhǎng)度我們并不知道,我們得先遍歷一遍得到它的長(zhǎng)度,之后用長(zhǎng)度減去 n 就是要?jiǎng)h除的結(jié)點(diǎn)的位置,然后遍歷到結(jié)點(diǎn)的前一個(gè)位置就好了。

public ListNode removeNthFromEnd(ListNode head, int n) {
    int len = 0;
    ListNode h = head;
    while (h != null) {
        h = h.next;
        len++;
    }
    //長(zhǎng)度等于 1 ,再刪除一個(gè)結(jié)點(diǎn)就為 null 了
    if (len == 1) {
        return null;
    }

    int rm_node_index = len - n;

    //如果刪除的是頭結(jié)點(diǎn)
    if (rm_node_index == 0) {
        return head.next;
    }

    //找到被刪除結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn)
    h = head;
    for (int i = 0; i < rm_node_index - 1; i++) {
        h = h.next;
    }

    //改變指向
    h.next = h.next.next;
    return head;
}

時(shí)間復(fù)雜度:假設(shè)鏈表長(zhǎng)度是 L ,那么就第一個(gè)循環(huán)是 L 次,第二個(gè)循環(huán)是 L - n 次,總共 2L - n 次,所以時(shí)間復(fù)雜度就是 O(L)。

空間復(fù)雜度:O(1)。

我們看到如果長(zhǎng)度等于 1 和刪除頭結(jié)點(diǎn)的時(shí)候需要單獨(dú)判斷,其實(shí)我們只需要在 head 前邊加一個(gè)空節(jié)點(diǎn),就可以避免單獨(dú)判斷。

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    int length  = 0;
    ListNode first = head;
    while (first != null) {
        length++;
        first = first.next;
    }
    length -= n;
    first = dummy;
    while (length > 0) {
        length--;
        first = first.next;
    }
    first.next = first.next.next;
    return dummy.next;
}

解法二 遍歷一次鏈表

上邊我們遍歷鏈表進(jìn)行了兩次,我們?nèi)绾沃槐闅v一次呢。

看了 leetcode 的講解。

想象一下,兩個(gè)人進(jìn)行 100m 賽跑,假設(shè)他們的速度相同。開始的時(shí)候,第一個(gè)人就在第二個(gè)人前邊 10m ,這樣當(dāng)?shù)谝粋€(gè)人跑到終點(diǎn)的時(shí)候,第二個(gè)人相距第一個(gè)人依舊是 10m ,也就是離終點(diǎn) 10m。

對(duì)比于鏈表,我們?cè)O(shè)定兩個(gè)指針,先讓第一個(gè)指針遍歷 n 步,然后再讓它倆同時(shí)開始遍歷,這樣的話,當(dāng)?shù)谝粋€(gè)指針到頭的時(shí)候,第二個(gè)指針就離第一個(gè)指針有 n 的距離,所以第二個(gè)指針的位置就剛好是倒數(shù)第 n 個(gè)結(jié)點(diǎn)。

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode first = dummy;
    ListNode second = dummy;
    //第一個(gè)指針先移動(dòng) n 步
    for (int i = 1; i <= n + 1; i++) {
        first = first.next;
    } 
    //第一個(gè)指針到達(dá)終點(diǎn)停止遍歷
    while (first != null) {
        first = first.next;
        second = second.next;
    }
    second.next = second.next.next;
    return dummy.next;
} 

時(shí)間復(fù)雜度:

第一個(gè)指針從 0 到 n ,然后「第一個(gè)指針再從 n 到結(jié)束」和「第二個(gè)指針從 0 到倒數(shù)第 n 個(gè)結(jié)點(diǎn)的位置」同時(shí)進(jìn)行。

而解法一無非是先從 0 到 結(jié)束,然后從 0 到倒數(shù)第 n 個(gè)結(jié)點(diǎn)的位置。

所以其實(shí)它們語句執(zhí)行的次數(shù)其實(shí)是一樣的,從 0 到倒數(shù)第 n 個(gè)結(jié)點(diǎn)的位置都被遍歷了 2 次,所以總共也是 2L - n 次。只不過這個(gè)解法把解法一的兩次循環(huán)合并了一下,使得第二個(gè)指針看起來是順便遍歷,想法很 nice。

所以本質(zhì)上,它們其實(shí)是一樣的,時(shí)間復(fù)雜度依舊是 O(n)。

空間復(fù)雜度:O(1)。

解法三

沒看講解前,和室友討論下,如何只遍歷一次鏈表。室友給出了一個(gè)我竟然無法反駁的觀點(diǎn),哈哈哈哈。

第一次遍歷鏈表確定長(zhǎng)度的時(shí)候,順便把每個(gè)結(jié)點(diǎn)存到數(shù)組里,這樣找結(jié)點(diǎn)的時(shí)候就不需要再遍歷一次了,空間換時(shí)間???哈哈哈哈哈哈哈哈哈。

public ListNode removeNthFromEnd(ListNode head, int n) {
    List<ListNode> l = new ArrayList<ListNode>();
    ListNode h = head;
    int len = 0;
    while (h != null) {
        l.add(h);
        h = h.next;
        len++;
    }
    if (len == 1) {
        return null;
    }
    int remove = len - n;
    if (remove == 0) {
        return head.next;
    }
    //直接得到,不需要再遍歷了
    ListNode r = l.get(remove - 1);
    r.next = r.next.next;
    return head;
}

時(shí)間復(fù)雜度:O(L)。

空間復(fù)雜度:O(L)。

利用兩個(gè)指針先固定間隔,然后同時(shí)遍歷,真的是很妙!另外室友的想法也很棒,哈哈哈哈哈。

更多詳細(xì)通俗題解詳見 leetcode.wang 。

?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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