【leetcode】leetcode 刷題 筆記 (不定期更新)
237.Delete Node in a Linked List
題目要求只給定連結串列中一個節點的前提下,刪除該節點。注意這裡題目並沒有給出對連結串列的引用,因而我們無法遍歷連結串列得到前驅。有一個思路確實很好,它並不是刪除連結串列的節點,而是把該節點的內容改為下一個節點的內容,把該節點的指標給為下一個節點的指標,相當於複製了下一個節點的內容然後刪除下一個節點。
思考:這個思路在只有單鏈表的前提下確實無懈可擊,不過不但這個連結串列被運用到實際應用中會有問題產生,因為應用中的例項可能會持有連結串列節點的引用,這種刪除方式會導致引用發生錯誤,比如原本持有3的引用,正常來看,一旦刪除3,就應該持有null才對,但是上述做法卻讓其持有了4的引用,反而原本持有4的引用的物件持有了null的引用,這是一個弊端。
234.Palindrome Linked List
Palindrome意思是迴文,這個題目給出了判定迴文的思路,即同時從前和從後遍歷,比較兩端的元素,只要有一次不同,就返回false,迴圈結束返回true。
(1) 這裡還涉及如何反轉一個連結串列,要設定next和pre指標,當遍歷到p時,要改變它的next為pre,但是這樣會丟掉原本連結串列的順序,因此要用next先記錄p的next,最後設定pre為p,p為之前儲存的next
(2) 如何找到連結串列的中間元素?設定slow和fast兩個指標,每一次迭代,slow設為下一個,fast設為下兩個。直到fast為null或者fast.next為null為止。
按照上述解法得到的右側鏈表含有較多的元素(奇數情況),然後把右側反轉後,右側含有較少的元素(偶數情況,奇數反而相同,因為中間的元素被兩邊共享),因此,在最後雙向遍歷時,應當使用右側鏈表的頭指標為條件。畫個圖就很清楚了。
public class ListNode { private ListNode next; private int data; public ListNode(int data){ this.data = data; } } public class Solution { public boolean isPalindrome(ListNode head){ ListNode slow = head; ListNode fast = head; while(fast != null && fast.getNext() != null){ slow = slow.getNext(); fast = fast.getNext().getNext(); } //右側更少 ListNode back = reverse(slow); while(back != null){ if (back.getData() != head.getData()) { return false; } back = back.getNext(); head = head.getNext(); } return true; } public ListNode reverse(ListNode head){ ListNode p = head; ListNode pre = null, next = null; while(p != null){ next = p.getNext(); p.setNext(pre); pre = p; p = next; } return pre; } }
203.Remove Linked List Elements
連結串列中的刪除操作,首先很重要一點是如果要刪除頭結點,我們在函式中是無法實現的,因為函式引數中的head是函式棧中對於呼叫處的真正head引用的拷貝,我們在函式中改變對head的賦值,並不能改變呼叫處head的賦值,唯一的解決辦法就是讓函式返回一個新的head,然後呼叫結束之後更新head。
思路:因為刪除操作在單鏈表中必須得到前一個元素,因此遍歷時只能檢查下一個元素的值是否相等。While迴圈的條件是下一個元素是否為空。如果相等,修改next引用,否則,遍歷繼續。
這樣的實現有一點需要注意,就是頭結點無法檢測,因為我們總是檢測下一個元素是否相等,因此在上述迴圈之前,我們必須先對頭節點檢測,只要head相等,那麼head後移。然後再進行上面的while迴圈,這樣上面的while迴圈條件不僅要有itr.next!=null還要有itr!=null,因為對head檢測結束,head可能為null。
public ListNode remove(ListNode head, int value){
while(head != null && head.getData() == value){
head = head.getNext();
}
ListNode p = head;
while(p != null && p.getNext() != null){
if (p.getNext().getData() == value) {
p.setNext(p.getNext().getNext());
}else {
p = p.getNext();
}
}
return head;
}
那麼為什麼同樣是檢測下一個是否等於value,head處於後面的處理必須分開?因為head是對引用賦值,而後面是對引用指向的元素的屬性賦值。因此在while迴圈內部的處理邏輯完全不同。即要修改連結串列元素的next域時,head和後面的操作不同。Head是要後移,而後面的元素是賦值。
這裡還有一種思路,就是新建一個dummy節點,其資料域一定不等於value,比如value+1。然後再進行上面的while迴圈,這樣做的好處就是省去了頭結點的處理,用空間換時間。使得while迴圈 內部的處理邏輯相同。
public ListNode remove1(ListNode head, int value){
ListNode dummy = new ListNode(value + 1);
dummy.setNext(head);
ListNode p = dummy;
while(p != null && p.getNext() != null){
if (p.getNext().getData() == value) {
p.setNext(p.getNext().getNext());
}else {
p = p.getNext();
}
}
return dummy.getNext();
}
160.Intersection of Two Linked Lists
這道題目有一個要求,那就是intersecting的一個或者多個元素之後,兩個連結串列的長度必須相同。否則下述演算法達不到效果。在上述前提下,這個演算法在找相同元素的時候只用了On的複雜度,正常情況下我們需要On2的複雜度。如果存在,那麼返回非空,否則,當a==b時,均為null,會返回null。
public ListNode firstIntersect(ListNode headA, ListNode headB){
ListNode a = headA;
ListNode b = headB;
while(a != b){
a = a == null?headB:a.getNext();
b = b == null?headA:b.getNext();
}
return a;
}
141.Linked List Cycle
這是快慢指標在連結串列中的兩一個應用,除了找中間元素意外,還可判斷是否有環路。Fast指標的條件是非空和下一個非空,那麼結束時,要麼null要麼最後一個。
public boolean hasCycle(ListNode head){
ListNode slow = head;
ListNode fast = head;
while(fast !=null && fast.getNext() != null){
slow = slow.getNext();
fast = fast.getNext().getNext();
if (slow == fast) {
return true;
}
}
return false;
}
83.Remove Duplicates from Sorted List
234已經涉及。比較簡單,主要是一個while迴圈,然後迴圈內部比較當前與下一個,因此,最後一個元素肯定在前驅就會被處理,所以迴圈條件可以是next與當前均不為null。迴圈體內部處理就是一個if,如果不等,就迭代至下一個,反之,進行next拷貝。
不過這個題目給出了一個刪除連結串列中重複元素的方法,就是先排序,然後按照上面方法,複雜度是nlogn。
注意上述方法的前提是排序。
public ListNode deleteDuplicates(ListNode head) {
ListNode p = head;
while(p != null && p.getNext() != null){
if (p.getData() != p.getNext().getData()) {
p = p.getNext();
}else {
p.setNext(p.getNext().getNext());
}
}
return head;
}
24.Swap Nodes in Pairs
首先把head指標調成第二個元素,然後是一個while迴圈,不斷調整相鄰的兩個元素。這裡還必須設定一個pre引用,因為while內的處理邏輯會用到之前一次迭代的元素,這個與reverse操作比較類似,也是一個注意點,就是說只要需要用到之前一次迭代的元素,那麼就用一個pre來記錄。
public ListNode swapPairs(ListNode head) {
ListNode p = head;
if (head != null && head.getNext() != null) {
head = head.getNext();
}
ListNode pre = null;
while(p != null && p.getNext() != null){
ListNode p1 = p.getNext();
p.setNext(p1.getNext());
p1.setNext(p);
if (pre != null) {
pre.setNext(p1);
}
pre = p;
p = p.getNext();
}
return head;
}