面試常見鏈表題目總結
- 2019 年 10 月 3 日
- 筆記
160. 相交鏈表
編寫一個程序,找到兩個單鏈表相交的起始節點。
如下面的兩個鏈表:
在節點 c1 開始相交。
示例 1:
輸入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 輸出:Reference of the node with value = 8 輸入解釋:相交節點的值為 8 (注意,如果兩個列表相交則不能為 0)。從各自的表頭開始算起,鏈表 A 為 [4,1,8,4,5],鏈表 B 為 [5,0,1,8,4,5]。在 A 中,相交節點前有 2 個節點;在 B 中,相交節點前有 3 個節點。
示例 2:
輸入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 輸出:Reference of the node with value = 2 輸入解釋:相交節點的值為 2 (注意,如果兩個列表相交則不能為 0)。從各自的表頭開始算起,鏈表 A 為 [0,9,1,2,4],鏈表 B 為 [3,2,4]。在 A 中,相交節點前有 3 個節點;在 B 中,相交節點前有 1 個節點。
示例 3:
輸入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 輸出:null 輸入解釋:從各自的表頭開始算起,鏈表 A 為 [2,6,4],鏈表 B 為 [1,5]。由於這兩個鏈表不相交,所以 intersectVal 必須為 0,而 skipA 和 skipB 可以是任意值。 解釋:這兩個鏈表不相交,因此返回 null。
注意:
- 如果兩個鏈表沒有交點,返回
null
. - 在返回結果後,兩個鏈表仍須保持原有的結構。
- 可假定整個鏈表結構中沒有循環。
- 程序盡量滿足 O(n) 時間複雜度,且僅用 O(1) 內存。
設置快慢指針
public ListNode getIntersectionNode (ListNode headA, ListNode headB) { if (headA == null || headB == null) return null; ListNode p1 = headA; ListNode p2 = headB; while (p1 != p2) { if (p1 == null) p1 = headB; else p1 = p1.next; if (p2 == null) p2 = headA; else p2 = p2.next; } return p1; }
206. 反轉鏈表
反轉一個單鏈表。
示例:
輸入: 1->2->3->4->5->NULL 輸出: 5->4->3->2->1->NULL
進階:
你可以迭代或遞歸地反轉鏈表。你能否用兩種方法解決這道題?
遞歸法:
public ListNode reverseList(ListNode head) { if(head == null || head.next == null){ return head; } ListNode rest = head.next; ListNode newHead = reverseList(rest); rest.next = head; head.next = null; return newHead; }
迭代法:
public ListNode reverseList(ListNode head) { ListNode prev = null; ListNode cur = head; while(cur != null){ ListNode next = cur.next; cur.next = prev; prev = cur; cur = next; } return prev; }
21. 合併兩個有序鏈表
將兩個有序鏈表合併為一個新的有序鏈表並返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。
示例:
輸入:1->2->4, 1->3->4 輸出:1->1->2->3->4->4
雙指針思想
public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if(l1 == null) return l2; if(l2 == null) return l1; if(l1.val < l2.val){ l1.next = mergeTwoLists(l1.next,l2); return l1; }else{ l2.next = mergeTwoLists(l1,l2.next); return l2; } }
83. 刪除排序鏈表中的重複元素
給定一個排序鏈表,刪除所有重複的元素,使得每個元素只出現一次。
示例 1:
輸入: 1->1->2 輸出: 1->2
示例 2:
輸入: 1->1->2->3->3 輸出: 1->2->3
一次遍歷,注意邊界條件。
public ListNode deleteDuplicates(ListNode head) { ListNode cur = head; while(cur != null && cur.next != null){ if(cur.val == cur.next.val) cur.next = cur.next.next; else cur = cur.next; } return head; }
19. 刪除鏈表的倒數第N個節點
給定一個鏈表,刪除鏈表的倒數第 n 個節點,並且返回鏈表的頭結點。
示例:
給定一個鏈表: 1->2->3->4->5, 和 n = 2. 當刪除了倒數第二個節點後,鏈表變為 1->2->3->5.
說明:
給定的 n 保證是有效的。
進階:
你能嘗試使用一趟掃描實現嗎?
設置啞節點1,讓它走 n+1 步,再設置啞節點2,然後啞節點1和啞節點2一起移動,直到啞節點1走完鏈表,此時啞節點1和啞節點2之間正好隔着 n 個節點,再通過啞節點2刪除倒數第 n 個節點。
public ListNode removeNthFromEnd(ListNode head, int n) { ListNode dummy = new ListNode(0); dummy.next = head; ListNode first = dummy; for (int i = 1; i <= n + 1;i++){ first = first.next; } ListNode second = dummy; while(first != null){ first = first.next; second = second.next; } second.next = second.next.next; return dummy.next; }
234. 迴文鏈表
請判斷一個鏈表是否為迴文鏈表。
示例 1:
輸入: 1->2 輸出: false
示例 2:
輸入: 1->2->2->1 輸出: true
進階:
你能否用 O(n) 時間複雜度和 O(1) 空間複雜度解決此題?
設置快慢指針,移動速度分別是2和1。當快指針到達鏈表尾部的時候,慢指針就在正中間(奇數個節點的情況下)或者正中間的左邊(偶數個節點的情況下),再將慢指針向後移動一位,反轉以慢指針為頭的鏈表,再逐個節點對比是否相等。
public boolean isPalindrome (ListNode head) { if (head == null || head.next == null) return true; ListNode slow = head; ListNode fast = head.next; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } slow = slow.next; slow = reverse(slow); while (slow != null) { if (head.val == slow.val) { head = head.next; slow = slow.next; } else return false; } return true; } private ListNode reverse (ListNode head) { ListNode newHead = null; while (head != null) { ListNode nextNode = head.next; head.next = newHead; newHead = head; head = nextNode; } return newHead; }
24. 兩兩交換鏈表中的節點
給定一個鏈表,兩兩交換其中相鄰的節點,並返回交換後的鏈表。
你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。
示例:
給定 1->2->3->4, 你應該返回 2->1->4->3.
設置啞節點,注意循環條件,指針移動的速度是2(因為需要兩兩交換節點)。
public ListNode swapPairs (ListNode head) { ListNode dummy = new ListNode(-1); dummy.next = head; ListNode pre = dummy; while (pre.next != null && pre.next.next != null) { ListNode l1 = pre.next; ListNode l2 = pre.next.next; l1.next = l2.next; l2.next = l1; pre.next = l2; pre = l1; } return dummy.next; }
445. 兩數相加 II
給定兩個非空鏈表來代表兩個非負整數。數字最高位位於鏈表開始位置。它們的每個節點只存儲單個數字。將這兩數相加會返回一個新的鏈表。
你可以假設除了數字 0 之外,這兩個數字都不會以零開頭。
進階:
如果輸入鏈表不能修改該如何處理?換句話說,你不能對列表中的節點進行翻轉。
示例:
輸入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) 輸出: 7 -> 8 -> 0 -> 7
既然不能改變鏈表的結構(翻轉鏈表),那就用一個棧來保存鏈表中的值,可以做到逆向輸出。
相加部分的代碼邏輯就按常規思路寫。
public ListNode addTwoNumbers (ListNode l1, ListNode l2) { Stack<Integer> l1Stack = listNodetoStack(l1); Stack<Integer> l2Stack = listNodetoStack(l2); int carry = 0; ListNode head = new ListNode(-1); while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) { int x = l1Stack.isEmpty() ? 0 : l1Stack.pop(); int y = l2Stack.isEmpty() ? 0 : l2Stack.pop(); int sum = x + y + carry; ListNode node = new ListNode(sum % 10); carry = sum / 10; node.next = head.next; head.next = node; } return head.next; } private Stack<Integer> listNodetoStack (ListNode head) { Stack<Integer> stack = new Stack<>(); while (head != null) { stack.push(head.val); head = head.next; } return stack; }
725. 分隔鏈表
給定一個頭結點為 root
的鏈表, 編寫一個函數以將鏈表分隔為 k
個連續的部分。
每部分的長度應該儘可能的相等: 任意兩部分的長度差距不能超過 1,也就是說可能有些部分為 null。
這k個部分應該按照在鏈表中出現的順序進行輸出,並且排在前面的部分的長度應該大於或等於後面的長度。
返回一個符合上述規則的鏈表的列表。
舉例: 1->2->3->4, k = 5 // 5 結果 [ [1], [2], [3], [4], null ]
示例 1:
輸入: root = [1, 2, 3], k = 5 輸出: [[1],[2],[3],[],[]] 解釋: 輸入輸出各部分都應該是鏈表,而不是數組。 例如, 輸入的結點 root 的 val= 1, root.next.val = 2, root.next.next.val = 3, 且 root.next.next.next = null。 第一個輸出 output[0] 是 output[0].val = 1, output[0].next = null。 最後一個元素 output[4] 為 null, 它代表了最後一個部分為空鏈表。
示例 2:
輸入: root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3 輸出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] 解釋: 輸入被分成了幾個連續的部分,並且每部分的長度相差不超過1.前面部分的長度大於等於後面部分的長度。
提示:
root
的長度範圍:[0, 1000]
.- 輸入的每個節點的大小範圍:
[0, 999]
. k
的取值範圍:[1, 50]
.
先統計出鏈表長度,除以 k, 求商和餘數,其中:
- 餘數代表最後結果中有多少個長鏈表
- 商代表每個短鏈表的長度(結果集中後部的鏈表)
- 長鏈表比短鏈表多一個節點
public ListNode[] splitListToParts (ListNode root, int k) { ListNode cur = root; int len = 0; while (cur != null) { cur = cur.next; len++; } int mod = len % k; int size = len / k; cur = root; ListNode[] ans = new ListNode[k]; for (int i = 0; cur != null && i < k; i++) { ans[i] = cur; int curSize = size + (mod-- > 0 ? 1 : 0); for (int j = 0; j < curSize - 1; j++) { cur = cur.next; } ListNode next = cur.next; cur.next = null; cur = next; } return ans; }
328. 奇偶鏈表
給定一個單鏈表,把所有的奇數節點和偶數節點分別排在一起。請注意,這裡的奇數節點和偶數節點指的是節點編號的奇偶性,而不是節點的值的奇偶性。
請嘗試使用原地算法完成。你的算法的空間複雜度應為 O(1),時間複雜度應為 O(nodes),nodes 為節點總數。
示例 1:
輸入: 1->2->3->4->5->NULL 輸出: 1->3->5->2->4->NULL
示例 2:
輸入: 2->1->3->5->6->4->7->NULL 輸出: 2->3->6->7->1->5->4->NULL
說明:
- 應當保持奇數節點和偶數節點的相對順序。
- 鏈表的第一個節點視為奇數節點,第二個節點視為偶數節點,以此類推。
設置三個指針,其中奇指針和偶指針是很自然能想到的,evenHead
起輔助作用,用於將奇鏈表和偶鏈表結合起來。
public ListNode oddEvenList (ListNode head) { ListNode odd = head; ListNode even = head.next; ListNode evenHead = even; while (even != null && even.next != null) { odd.next = even.next; odd = odd.next; even.next = odd.next; even = even.next; } odd.next = evenHead; return head; }