鏈表的題目都比較死,幾種類型反覆做反覆記憶就好了
環形鏈表
解法一:記錄查找法
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<ListNode>();
ListNode node = head;
while (node != null) {
if (set.contains(node)) {
return true;
}
set.add(node);
node = node.next;
}
return false;
}
}
解法二:快慢指針法(背下來就好了,真的想不到)
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode fast = head.next;
ListNode slow = head;
while (fast != slow) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
環形鏈表II
解法一:hash
public class Solution {
public ListNode detectCycle(ListNode head) {
Set<ListNode> set = new HashSet<ListNode>();
ListNode cur = head;
while (cur != null) {
if (set.contains(cur)) {
return cur;
}
set.add(cur);
cur = cur.next;
}
return null;
}
}
解法二:快慢指針法
思路:
- 先和環形鏈表I一樣判斷有沒有環,有才進行第二步
- 從同一起點開始,fast一次走兩步,slow一次走一步,當他們相遇之後,fast回到原點一次走一步,第二次相遇節點就是環連接點
- 我的理解:
- 就假設最簡單的第二圈發生第一次相遇,第二圈走完了就第二次相遇了
- 第一次相遇時fast走了F+(a+b)+a
- slow走了F+a
- 因爲fast是slow的兩倍速度,所以第二次相遇時fast=2*slow
- 第二次相遇slow走了F+a+b
- 那麼fast要走2F+2a+2b,還差F+b
- 所以就假設fast以一倍的速度陪slow走完b,另一倍速從F出發,當fast與slow相遇時,相當於fast走過了b+F
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (true) {
if (fast == null || fast.next == null) return null;
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
fast = head;
//第二階段,找環接入點
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
合併兩個有序鏈表
要點:保留一個pre標記,讓一個新的指針去遍歷
public class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(0);
ListNode cur = pre;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
cur = cur.next;
l1 = l1.next;
} else {
cur.next = l2;
cur = cur.next;
l2 = l2.next;
}
}
//如果l1空了,就把l2接上去
if (l1 == null) {
cur.next = l2;
} else {
cur.next = l1;
}
return pre.next;
}
}
反轉鏈表
迭代法:原地反轉
public class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
遞歸:
public class Solution {
public ListNode reverseList(ListNode head) {
//遞歸終止條件
if (head == null || head.next == null) {
return head;
}
//下探
ListNode cur = reverseList(head.next);
head.next.next = head;
head.next = null;
//每一次都返回最後一個節點
return cur;
}
}
圖片引自leetcode題解
兩兩交換鏈表中的節點
解法一:遞歸
public class Solution {
//每次看人家寫遞歸都覺得自己像個傻子,看得懂想不到
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode next = head.next;
head.next = swapPairs(next.next);
next.next = head;
return next;
}
}
解法二:迭代
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode node = new ListNode(-1);
node.next = head;
ListNode pre = node;
while (pre.next != null && pre.next.next != null) {
ListNode l1 = pre.next, l2 = pre.next.next;
ListNode next = l2.next;
l1.next = next;
l2.next = l1;
pre.next = l2;
pre = l1;
}
return node.next;
}
}
相交鏈表
本題的要點就是交替遍歷兩條路
已上圖爲例,一條路徑遍歷a-c-b,另一條遍歷b-c-a
當兩條路徑遍歷相遇時,就是交點
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode node1 = headA;
ListNode node2 = headB;
while (node1 != node2) {
node1 = node1 == null ? headB : node1.next;
node2 = node2 == null ? headA : node2.next;
}
return node1;
}
}
刪除排序鏈表中的重複元素
迭代實現:
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) return head;
ListNode node = head;
while (node.next != null && node != null) {
if (node.val == node.next.val){
node.next = node.next.next;
}else {
node = node.next;
}
}
return head;
}
遞歸實現:
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) return head;
head.next = deleteDuplicates(head.next);
return head.val == head.next.val ? head = head.next : head;
}
刪除鏈表的倒數第N個節點
一趟掃描:快慢指針法
public class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null || head.next == null) return null;
ListNode fast = head;
ListNode slow = head;
//將fast和slow的步調差調成n,當fast到達末尾了,slow到達指定節點
while (n > 0) {
fast = fast.next;
n--;
}
//倒數的數就是頭結點的情況,此時fast是null
if (fast == null){
return head.next;
}
//倒數的數不是頭結點,按順序移動fast和slow節點,直到倒數第二個,測試slow節點到達刪除節點的上一個
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
//slow是待刪除節點上一個節點,刪除slow.next節點
slow.next = slow.next.next;
return head;
}
}
兩數相加II
- 遍歷鏈表,將值分別壓入棧中
- 獲取的棧頂元素就是同一位的元素了,如果有就直接出棧獲取,如果沒有就爲0
- 計算兩個值與進位數的和爲sum
- 讓sum對10求模得本位的數
- 讓sum對10整除得進位數
- 用本位數創建一個節點插入到鏈表頭(pre.next)
public class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//初始化stack
Stack<Integer> stack1 = buildStack(l1);
Stack<Integer> stack2 = buildStack(l2);
//新建個前驅結點方便返回
ListNode pre = new ListNode(-1);
int carry = 0;//進位數
while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
int x = stack1.isEmpty() ? 0 : stack1.pop();
int y = stack2.isEmpty() ? 0 : stack2.pop();
int sum = x + y + carry;
ListNode node = new ListNode(sum % 10);//計算出本位的值
//將node插入到頭結點
node.next = pre.next;
pre.next = node;
//計算是否有進位數
carry = sum / 10;
}
return pre.next;
}
private Stack<Integer> buildStack(ListNode head) {
ListNode node = head;
Stack<Integer> stack = new Stack<>();
while (node != null) {
stack.push(node.val);
node = node.next;
}
return stack;
}
}
迴文鏈表
O(n) 時間複雜度和 O(1) 空間複雜度解法:
- 快慢指針找到中點
- 選擇一半進行翻轉
- 翻轉後再次比較
public class Solution {
/**
* 快慢指針找到中點
* 選擇一半進行翻轉
* 翻轉後再次比較
*/
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;
}
if (fast != null) slow = slow.next;//偶數個結點,讓slow指向下一個,作爲後半段的開頭
//如果爲奇數剛好過了中點,就是後半段的開頭
//將鏈表分爲以head和slow開頭的等長的兩段
cut(head, slow);
return isEqual(head, reverse(slow));
}
//翻轉鏈表
private ListNode reverse(ListNode head) {
ListNode newHead = null;
while (head != null) {
ListNode tmp = head.next;
head.next = newHead;
newHead = head;
head = tmp;
}
return newHead;
}
//判定兩個鏈表是否相等
private boolean isEqual(ListNode head1, ListNode head2) {
while (head1 != null && head2 != null) {
if (head1.val != head2.val) return false;
head1 = head1.next;
head2 = head2.next;
}
return true;
}
//分割鏈表
private void cut(ListNode head, ListNode slow) {
while (head.next != slow) {
head = head.next;
}
head.next = null;
}
}
奇偶鏈表
思路:奇數放一個鏈表,偶數放一個鏈表,最後兩個鏈表連接起來
public class Solution {
public ListNode oddEvenList(ListNode head) {
if (head == null || head.next == null) return 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;
}
}
分割鏈表
思路:
- 統計鏈表長度
- 長度小於等於k,每個子鏈表放一個節點
- 長度大於k,計算每個子鏈表長度,分割鏈表
public class Solution {
public ListNode[] splitListToParts(ListNode root, int k) {
if (root == null) return new ListNode[k];
ListNode cur = root;
int count = 0;
//統計節點數
while (cur != null) {
count++;
cur = cur.next;
}
ListNode[] nodes = new ListNode[k];
cur = root;
//每個子鏈表不超過1個節點
if (count <= k) {
for (int i = 0; i < count; i++) {
nodes[i] = new ListNode(cur.val);
cur = cur.next;
}
} else {
//計算每個子鏈表的節點數
int remain = count % k;
int preCount = count / k;
//記錄每部分的節點個數
int[] counts = new int[k];
for (int i = 0; i < k; i++) {
//前remain個鏈表長度爲precount+1
counts[i] = remain-- > 0 ? preCount + 1 : preCount;
}
//遍歷鏈表,存儲元素
for (int i = 0; i < k; i++) {
//初始化子鏈表的頭結點和個數
int num = counts[i];
nodes[i] = cur;
while (--num > 0) {
cur = cur.next;
}
//截斷鏈表
ListNode tmp = cur.next;
cur.next = null;
cur = tmp;
}
}
return nodes;
}
}