方法一:兩次遍歷算法
思路
我們注意到這個問題可以容易地簡化成另一個問題:刪除從列表開頭數起的第 (L - n + 1)個結點,其中 L是列表的長度。只要我們找到列表的長度 L,這個問題就很容易解決。
算法
首先我們將添加一個啞結點作爲輔助,該結點位於列表頭部。啞結點用來簡化某些極端情況,例如列表中只含有一個結點,或需要刪除列表的頭部。在第一次遍歷中,我們找出列表的長度 L。然後設置一個指向啞結點的指針,並移動它遍歷列表,直至它到達第 (L - n)個結點那裏。我們把第 (L - n)個結點的 next 指針重新鏈接至第 (L - n + 2)個結點,完成這個算法。
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;
}
複雜度分析
時間複雜度:O(L),該算法對列表進行了兩次遍歷,首先計算了列表的長度 LL 其次找到第 (L - n)個結點。 操作執行了 2L−n 步,時間複雜度爲O(L)。
空間複雜度:O(1)O(1),我們只用了常量級的額外空間。
方法二:一次遍歷算法
算法
上述算法可以優化爲只使用一次遍歷。我們可以使用兩個指針而不是一個指針。第一個指針從列表的開頭向前移動 n+1 步,而第二個指針將從列表的開頭出發。現在,這兩個指針被 n 個結點分開。我們通過同時移動兩個指針向前來保持這個恆定的間隔,直到第一個指針到達最後一個結點。此時第二個指針將指向從最後一個結點數起的第 n個結點。我們重新鏈接第二個指針所引用的結點的 next 指針指向該結點的下下個結點。
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode first = dummy;
ListNode second = dummy;
// Advances first pointer so that the gap between first and second is n nodes apart
for (int i = 1; i <= n + 1; i++) {
first = first.next;
}
// Move first to the end, maintaining the gap
while (first != null) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
return dummy.next;
}
複雜度分析
時間複雜度:O(L),該算法對含有 L 個結點的列表進行了一次遍歷。因此時間複雜度爲 O(L)。
空間複雜度:O(1),我們只用了常量級的額外空間。
刷題記錄的代碼:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode p =head;
List<Integer> list = new ArrayList<>();
while(p!=null){
list.add(p.val);
p=p.next;
}
list.remove(list.size()-n);
if(list.isEmpty()){
return null;
}
ListNode m = null;
ListNode Nhead = new ListNode(list.get(0));
m=Nhead;
for(int i = 1 ;i<list.size();i++){
ListNode listNode = new ListNode(list.get(i));
m.next=listNode;
m=listNode;
}
return Nhead;
}
}