先說一個消息,爲了方便互相交流學習,青銅三人行建了個微信羣,感興趣的夥伴可以掃碼加下面的小助手抱你入羣哦!
每週一題,代碼無敵。這周,「青銅三人行」爲你帶來了一道關於“鏈表的題目”。
刪除鏈表的倒數第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]郵箱聯繫我們,下週見!
三人行