連結串列
今天主要是把連結串列專題刷完了,連結串列專題的題目不是很難,基本都是考察對連結串列的操作的理解。
在處理連結串列問題的時候,我們通常會引入一個哨兵節點(dummy),dummy節點指向原連結串列的頭結點。這樣,當我們對頭結點進行操作的時候就可以直接使用dummy節點,不用進行特判。
在對連結串列進行操作的時候 while的迴圈條件也是容易犯錯的地方,我們不應該死記這題該是cur != null
還是cur.next != null
又或是其他。而是應該畫個圖,手動模擬一下,便知道結束的條件。
203.移除連結串列元素
題意:刪除連結串列中等於給定值 val 的所有節點。
示例 1:
輸入:head = [1,2,6,3,4,5,6], val = 6
輸出:[1,2,3,4,5]
示例 2:
輸入:head = [], val = 1
輸出:[]
示例 3:
輸入:head = [7,7,7,7], val = 7
輸出:[]
思路
遍歷列表找到要刪除的節點的前一個節點,修改該節點的指標跳過要刪除的節點。
關鍵在於,處理頭結點和如何找到要刪除的前一個節點。我們可以使用一個哨兵節點和pre指標來實現。
pre初始值為哨兵節點,cur初始值是頭結點。這樣每次pre和cur都向後移一位即可,判斷如果cur等於要刪除的節點就讓pre = cur.next
,迴圈的條件為cur != null
。
程式碼
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode();
dummy.next = head;
ListNode pre = dummy;
ListNode cur = head;
while (cur != null) {
if (cur.val == val) {
pre.next = cur.next;
} else {
pre = cur;
}
cur = cur.next;
}
return dummy.next;
}
}
707.設計連結串列
題意:
在連結串列類中實現這些功能:
- get(index):獲取連結串列中第 index 個節點的值。如果索引無效,則返回-1。
- addAtHead(val):在連結串列的第一個元素之前新增一個值為 val 的節點。插入後,新節點將成為連結串列的第一個節點。
- addAtTail(val):將值為 val 的節點追加到連結串列的最後一個元素。
- addAtIndex(index,val):在連結串列中的第 index 個節點之前新增值為 val 的節點。如果 index 等於連結串列的長度,則該節點將附加到連結串列的末尾。如果 index 大於連結串列長度,則不會插入節點。如果index小於0,則在頭部插入節點。
- deleteAtIndex(index):如果索引 index 有效,則刪除連結串列中的第 index 個節點。
思路
本題不是一道演算法題,是一道考察對連結串列的功能的具體實現的設計題。
熟悉連結串列具體操作即可,同樣的我們引入哨兵節點和一個儲存連結串列大小的size變數會方便我們的操作。
程式碼
class ListNode {
int val;
ListNode next;
ListNode(){}
ListNode(int val) {
this.val = val;
}
}
class MyLinkedList {
int size; // 連結串列長度
ListNode head; // 虛擬頭結點
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
public int get(int index) {
if (index < 0 || index >= size) return -1;
ListNode cur = head;
// 包含一個虛擬頭結點,所以要<=
for (int i = 0; i <= index; i ++ ) {
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
if (index > size) return;
if (index < 0) index = 0;
size ++;
ListNode pre = head;
// 包含一個虛擬頭結點,所以要<=
for (int i = 0; i < index; i ++ ) {
pre = pre.next;
}
ListNode toAdd = new ListNode(val);
toAdd.next = pre.next;
pre.next = toAdd;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) return ;
size --;
if (index == 0) {
head = head.next;
return ;
}
ListNode pre = head;
for (int i = 0; i < index; i ++ ) {
pre = pre.next;
}
pre.next = pre.next.next;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
206.反轉連結串列
題意:反轉一個單連結串列。
示例: 輸入: 1->2->3->4->5->NULL 輸出: 5->4->3->2->1->NULL
思路
本題我們可以從題目要求入手,對連結串列進行一個模擬。不難看出我們需要將後一個節點指向前一個節點 ,在此基礎上我們還要解決一個問題:我們以樣例來說明,當我們將節點2指向節點1時,我們應該怎麼移動當前指標到下一個節點。
顯然,我們可以定義一個變數next來預先儲存節點2的next指標即可。另一個問題是如何讓節點1指向null,參考之前說過的操作引入一個哨兵節點,這樣pre指標指向哨兵節點,cur指標指向頭結點。在最開始我們就能讓頭節點指向null。
程式碼
1.雙指標寫法
class Solution {
public ListNode reverseList(ListNode head) {
// 複習,非遞迴寫法
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
2.遞迴寫法
class Solution {
public ListNode reverseList(ListNode head) {
return reverse(head, null);
}
// 關於cur為什麼為空 , 因為我們返回的是pre,如果cur == null 時 pre才指向最後一個連結串列的元素
public ListNode reverse(ListNode cur, ListNode pre) {
if (cur == null) return pre;
ListNode next = cur.next;
cur.next = pre;
return reverse(next, cur);
}
}
24. 兩兩交換連結串列中的節點
題意:給定一個連結串列,兩兩交換其中相鄰的節點,並返回交換後的連結串列。你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。
示例:
思路
拿到一道題目我們看完題乾和資料範圍後,一定先自己動手模擬一下,理清楚題目要求,也能方便我們將模擬操作抽象為具體的程式碼。
下面是根據樣例我們模擬的過程:
在圖片中上面的連結串列是初始時的連結串列,下面的連結串列為我們的目標連結串列。
可以看出如果要得到目標連結串列我們必須得進行三個步驟:
- 將哨兵節點指向節點2
- 將節點2指向節點1
- 將節點1指向節點3
在我們完成上面三個步驟後,我們將當前指標移動兩位,及如果我們要操作兩個節點,指標必須在這兩個節點的前面一個節點。
但是當我們在改變指向以後我們就不能找到節點1和節點3了,所以我們得提前儲存一下節點1和節點3,由此可知迴圈的條件為cur.next != null && cur.next.next != null
根據以上思路不難寫出程式碼。
程式碼
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
while (cur.next != null && cur.next.next != null) {
ListNode tmp = cur.next;
ListNode tmp1 = cur.next.next.next;
cur.next = cur.next.next;
cur.next.next = tmp;
cur.next.next.next = tmp1;
cur = cur.next.next;
}
return dummy.next;
}
}
19.刪除連結串列的倒數第N個節點
題意:給你一個連結串列,刪除連結串列的倒數第 n 個結點,並且返回連結串列的頭結點。
進階:你能嘗試使用一趟掃描實現嗎?
示例:
思路
首先,最暴力的思路就是遍歷一遍統計連結串列長度,再遍歷一遍刪除倒數第N個節點,這種思路的程式碼實現簡單,我們不在此給出。
我們考慮如何使用一趟掃描實現此功能,在這裡我們引入一個快慢指標的思想。
我們設定兩個指標他們的起點都為哨兵節點,其中快指標fast每次先移動n位,然後快慢指標才一起開始移動,當快指標到達連結串列末尾的時候,慢指標剛好指向我們要刪除的倒數第N個節點的前一個節點。
如下圖所示:
所以迴圈的結束條件為fast走到最後時,及cur.next != null
程式碼
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy;
ListNode slow = dummy;
while (n -- > 0) fast = fast.next;
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
160.連結串列相交
題意:給你兩個單連結串列的頭節點 headA 和 headB ,請你找出並返回兩個單連結串列相交的起始節點。如果兩個連結串列沒有交點,返回 null 。
示例:圖示兩個連結串列在節點 c1 開始相交:
題目資料 保證 整個鏈式結構中不存在環。
注意,函式返回結果後,連結串列必須 保持其原始結構 。
示例 1:
示例 2:
示例 3:
思路
首先本題不是比較的val值相同,而是比較的節點相同,及val和next都相同。
明確了這一點之後我們繼續討論如何解決該問題,由於給出的兩個連結串列可能長度並不相同,所以我們應該讓兩個連結串列從相同長度的位置開始進行比較。
所以我們先得到兩個連結串列的長度,再讓長的連結串列先前進兩個連結串列之間的差值的距離。
這樣我們就可以從相同位置開始進行比較,當比較到兩個節點相同的時候,即找到了相交的節點返回即可,當迴圈結束時還沒有找到則沒有相交的節點。
while迴圈的條件不難得出為cur != null
程式碼
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
int len;
while (curA != null) {
lenA ++;
curA = curA.next;
}
while (curB != null) {
lenB ++;
curB = curB.next;
}
// 找出長度大的那個連結串列
curA = headA;
curB = headB;
if (lenB > lenA) {
int tmp = lenA;
lenA = lenB;
lenB = tmp;
curA = headB;
curB = headA;
}
// 讓長的先走
len = lenA - lenB;
while (len -- > 0) curA = curA.next;
while (curA != null) {
if (curA == curB) return curA;
curA = curA.next;
curB = curB.next;
}
return null;
}
}
142.環形連結串列II
題意:給定一個連結串列,返回連結串列開始入環的第一個節點。 如果連結串列無環,則返回 null。
為了表示給定連結串列中的環,使用整數 pos 來表示連結串列尾連線到連結串列中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該連結串列中沒有環。
說明:不允許修改給定的連結串列。
思路
首先要解決題目要求的問題,我們可以將其轉換為解決兩個子問題
子問題1:然後判斷有環
在這裡我們也引入一個快慢指標,快指標每次走兩步,慢指標每次走一步。當連結串列中存在環的時候,快指標和慢指標一定會相遇。
子問題2:如何找到環的起點
根據子問題1得到的相遇的節點,我們用index表示,同時我們定義一個index1在頭結點的位置,然後讓兩個節點一起走,當他們相遇的時候的節點即為環開始的節點。
具體證明請看:程式碼隨想錄
while 迴圈的條件為fast != null && fast.next != null
程式碼
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast) {
ListNode idx = fast;
ListNode idx1 = head;
while (idx != idx1) {
idx = idx.next;
idx1 = idx1.next;
}
return idx;
}
}
return null;
}
}