青銅三人行之刪除鏈表的倒數第N個節點

先說一個消息,爲了方便互相交流學習,青銅三人行建了個微信羣,感興趣的夥伴可以掃碼加下面的小助手抱你入羣哦!
青銅三人行小助手

每週一題,代碼無敵。這周,「青銅三人行」爲你帶來了一道關於“鏈表的題目”。

刪除鏈表的倒數第N個節點

青銅三人行——每週一題@刪除鏈表的倒數第N個節點

力扣題目

給定一個鏈表,刪除鏈表的倒數第 n 個節點,並且返回鏈表的頭結點。

示例:
給定一個鏈表: 1->2->3->4->5, 和 n = 2.
當刪除了倒數第二個節點後,鏈表變爲 1->2->3->5.
說明:
給定的 n 保證是有效的。
進階:
你能嘗試使用一趟掃描實現嗎?

啥是鏈表

要完成這道題,首先就得了解一下啥是鏈表?簡單來說,鏈表是一種數據結構,它又一系列離散的節點組成。其特點是,每個節點上除了自己的數據以外,還會有一個或兩個指針指向下一個或者上一個節點,使得這些節點可以鏈起來。

其中,只有指向下一個節點的鏈表稱爲單向鏈表,它只能從前一個節點到下一個節點一個方向來查找其中的節點數據:

在這裏插入圖片描述
而雙向鏈表則擁有兩個指針,分別指向之前和之後的節點:
在這裏插入圖片描述

而在 JS 中,這道題目裏給我們設定了鏈表的結構,很明顯,是一個單向列表:

  function ListNode(val) {
      this.val = val;
      this.next = null;
  }

解法一,兩次遍歷找到對應的節點:

瞭解了鏈表的數據結構以後,這道題就不難解決了。不過題目裏有個小小的花招,即要求尋找「倒數第 n 個節點」。因爲是單向鏈表,我們沒法倒着尋找節點,因此我們很容易想到先找到整個鏈表的長度,計算出要找的元素的正向位置,然後再從頭遍歷,進行刪除:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let node = head;
    let length = 0;
    while(true) {
        if(node === null) {
            break;
        }       
        node = node.next;
        length++;
    }
    node = head;
    if(n === length) return head.next;
    for(let i = 0; i< length - n -1; i++) {
        node=node.next;
    }
    node.next=node.next.next
    return head;
};

解法二,轉離散節點爲連續節點

這道題數據量較小,因此運行的速度都比較快。於是向着題目中「只掃描一次」這個進階目標前進。書香提出了一種方法,既然題目的難點在於鏈表不容易反向查找,那麼把它映射成一個連續的數據結構不就可以解決了嗎?於是很自然想到了應用數組,完成了題目:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let node = head;
    const arrNodes = [];
    while(true) {
        if(node === null) {
            const length = arrNodes.length;
            if(length === n ) 
                head = head.next;
            else 
                arrNodes[length-n-1].next=arrNodes[length-n+1];
            break;
        }       
        arrNodes.push(node);
        node = node.next;
    }
    return head;
};

值的注意的是,當需要刪除的元素是第一個元素的時候,容易造成數組的越界,需要特殊處理:

 if(length === n ) head = head.next;

解法三,雙指針法

上面書香的解法雖然在一次掃描中完成了任務,卻額外引入了一個數組的外部結構。有沒有更好的辦法呢?Helen 和 曾大師對於這個問題,採用了新的辦法:題目要求刪除倒數第 n 個節點,那麼我只需要在我當前掃描到的節點指針之後相隔 n 的節點再設置一個指針,到後一個指針越界的時候,當前節點就是需要刪除的節點了:

代碼如下:

var removeNthFromEnd = function(head, n) {

  const dummy = new ListNode(0); 
  dummy.next = head;

  let l = dummy;
  let r = dummy;

  let offset = n;

  while (offset--) {
    r = r.next;
  }

  while (r.next) {
    l = l.next;
    r = r.next;
  }

  l.next = l.next.next;

  return dummy.next;
}

ertra

最後,曾大師 go 語言的福利時間又到啦,同樣是雙指針,你能看出有什麼不同嗎?

func removeNthFromEnd(head *ListNode, n int) *ListNode {

    store := head
    i := 0
    p := head
    for p != nil {

        if(i>n) {
            store = store.Next
        }

		p = p.Next
        i++
	}
    // 刪除頭節點
    if(i == n){
        return store.Next
    }
   
    store.Next=store.Next.Next
   
    return head

}

而在時間和空間上,相當驚人 ,嗯嗯…

在這裏插入圖片描述
結尾
這周的題目相對來說比較簡單,主要是說明了鏈表的數據結構。鏈表相對於數組來說,更容易插入、刪除其中的節點,而數組比起來則更容易查找到某個節點(想想爲什麼?)。兩個數據結構相輔相成,在不同的應用場景選擇合適的數據結構,可以讓你的程序運行起來事半功倍哦!這次的題目就這樣了,歡迎通過[email protected]郵箱聯繫我們,下週見!


三人行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章