# LeetCode 总结 - 搞定 Linked List 面试题

2字数 5681阅读 2877
• 链表删除
• [203] Remove Linked List Elements
• [19] Remove Nth Node From End of List
• [83] Remove Duplicates from Sorted List
• [82] Remove Duplicates from Sorted List II
• 链表反转与旋转
• [92] Reverse Linked List II
• [24] Swap Nodes in Pairs
• [25] Reverse Nodes in k-Group
• [142] Linked List Cycle II
• 链表排序
• [148] Sort List
• [147] Insertion Sort List
• 链表操作
• [143] Reorder List
• [61] Rotate List
• [86] Partition List
• [328] Odd Even Linked List
• [725] Split Linked List in Parts
• 进位加法
• [445] Add Two Numbers II
• 链表合并
• [21] Merge Two Sorted Lists
• [23] Merge k Sorted Lists
• [160] Intersection of Two Linked Lists
• 其他
• [138] Copy List with Random Pointer
• [109] Convert Sorted List to Binary Search Tree

### 链表删除

#### [203] Remove Linked List Elements 移除链表元素

Example:
Given: `1 –> 2 –> 6 –> 3 –> 4 –> 5 –> 6`, `val = 6`
Return: `1 –> 2 –> 3 –> 4 –> 5`

``````public ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode(0);
} else {
}
}
return dummy.next;
}
``````

#### [19] Remove Nth Node From End of List 删除链表中倒数第n个节点

https://leetcode.com/problems/remove-nth-node-from-end-of-list

Example:
Given: `1->2->3->4->5`, and `n = 2`
Return: `1->2->3->5`

``````public ListNode removeNthFromEnd(ListNode head, int n) {
// 设立头结点
ListNode dummy = new ListNode(0);
// 初始化slow,fast
ListNode slow = dummy, fast = dummy;
// fast指针先走n步
for (int i = 0; i < n; i++) {
fast = fast.next;
}
// 两个指针同时移动直到p2到达最后
while (fast.next != null) {
slow = slow.next;
fast = fast.next;
}
// 删除并返回
slow.next = slow.next.next;
return dummy.next;
}
``````

#### [83] Remove Duplicates from Sorted List 删除重复元素

https://leetcode.com/problems/remove-duplicates-from-sorted-list

Given `1->1->2`, return `1->2`.
Given `1->1->2->3->3`, return `1->2->3`.

• 第一种是向后比较结点，发现重复就删掉。因为可能会删掉后面的结点，所以一定要注意cur的判空条件。当正常遍历时，cur可能为空，当删掉了后面结点时cur.next可能为空，都要判断。
``````// Version-1: Compare cur and cur.next without prev
// Note both cur and cur.next could reach null
while (cur != null && cur.next != null) {
if (cur.val == cur.next.val) {
// 直接删除掉后面那个重复的节点，并且cur不变
cur.next = cur.next.next;
} else {
// 只有当比较的两个元素不同，cur才移动到下一个节点
cur = cur.next;
}
}
}

// Version-2: compare cur and prev
// Note update of prev and cur
// Invariant: node prior to prev (inclusive) has no duplicates
while (cur != null) {
if (cur.val == prev.val) {
prev.next = cur.next;
cur = prev.next;
} else {
prev = cur;
cur = cur.next;
}
}
}
``````

#### [82] Remove Duplicates from Sorted List II 删除所有重复元素

``````public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode(0);
}
} else {
}
}
return dummy.next;
}
``````

### 链表反转与旋转

#### [206] Reverse Linked List 反转链表

``````public ListNode reverseList(ListNode head) {
// 前驱指针prev初始化为空
ListNode prev = null;
// 访问某个节点cur.next时，要检验cur是否为null
while (cur != null) {
ListNode next = cur.next;   // next保存着原来cur.next的地址
cur.next = prev;            // 使cur指向prev，这样就与后面的链表断开了
prev = cur;                 // prev指针往后移
cur = next;                 // cur指针往后移
}
// 当cur为null时，prev即为链表尾节点，直接返回作为新反转链表的头
return prev;
}

// 剑指Offer版本
// 逆置后的头结点
ListNode prev = null;
// 当前头结点
while (cur != null) {
// 保存后继
ListNode next = cur.next;

// next为null的节点为尾节点（翻转后的头结点一定是原始链表的尾结点）
if (next == null) reversedHead = cur;

// 逆转的过程，并且能将头结点的 prev 置为 NULL
cur.next = prev;

// 指针往后移
prev = cur; // 前继结点到现任节点，勿忘断链的情形，需要使用 pre 指针保存状态，pre 等价于是后移一个结点
cur = next; // 现任节点到下一结点，cur 后移一个结点
}
}

// dummy node
ListNode dummy = new ListNode(0);
while (cur != null) {
ListNode next = cur.next;
cur.next = dummy.next;
dummy.next = cur;
cur = next;
}
return dummy.next;
}
``````

#### [92] Reverse Linked List II 反转部分链表

``````public ListNode reverseListBetween(ListNode head, int m, int n) {
ListNode dummy = new ListNode(0);
ListNode prev = dummy;
// 找反转区间头节点的前驱，即1->2->3->4->5->NULL中的1，循环过后prev指向1、cur指向2
for (int i = 0; i < m - 1; i++) {
prev = prev.next;
}
// cur此时是反转区间的头节点
ListNode cur = prev.next;
// 在指定区间内不断进行反转
for (int i = 0; i < n - m; i++) {
ListNode temp = cur.next;
cur.next = temp.next;       // 越过cur.next，指向cur.next.next，即本来2->3，现在变成了2->4
temp.next = prev.next;      // temp指向prev.next，即本来3->4，现在变成了3->2
prev.next = temp;           // 即本来1->2，现在变成了1->3
// 经过以上步骤变成了1->3->2->4->5
// 再经过一次最后变成了1->4->3->2->5
}
return dummy.next;
}
``````

#### [24] Swap Nodes in Pairs 成对交换链表节点

https://leetcode.com/problems/swap-nodes-in-pairs

For example,
Given `1->2->3->4`, you should return the list as `2->1->4->3`.

image
``````public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0);
ListNode l1 = dummy;
while (l2 != null && l2.next != null) {
// 保存下一对的首节点
ListNode temp = l2.next.next;
l1.next = l2.next;
l2.next.next = l2;
l2.next = temp;
l1 = l2;
l2 = l2.next;
}
return dummy.next;
}
``````

#### [25] Reverse Nodes in k-Group 每k个节点翻转

https://leetcode.com/problems/reverse-nodes-in-k-group

image
``````public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(0);
ListNode prev = dummy;
while (prev != null) {
// 当return null，说明反转操作已经完成了（不用reverse）
prev = reverse(prev, k);
}
return dummy.next;
}

public ListNode reverse(ListNode prev, int k) {
ListNode last = prev;
// last指针往后移动k+1步，也就是位于反转区间后一个位置
for (int i = 0; i < k + 1; i++) {
last = last.next;
// last为null了，反转区间还如果不够k个元素，就返回null
if (i != k && last == null) return null;
}
// 指向反转区间首元素，也就是逆转后的尾元素
ListNode tail = prev.next;
// 跨过首元素，从第二个开始进行链表头插，也就是把cur提到tail的前面
ListNode cur = prev.next.next;
// 当cur移到last，说明要反转的区间已操作完毕
while (cur != last) {
// 暂存next指针
ListNode next = cur.next;
// 2->3 变成 2->1
cur.next = prev.next;
// dummy->1 变成 dummy->2
prev.next = cur;
// 1->2 变成 1->3
tail.next = next;
// 接着cur后移，以处理下一个节点
cur = next;
}
// tail将会是下一个子序列的prev
return tail;
}
``````

### 环

#### [141] Linked List Cycle 判断链表是否有环

image
``````public boolean hasCycle(ListNode head) {
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
// fast如果和slow相遇了，证明有环
if (slow == fast) return true;
}
// fast如果走到了null，证明没有环
return false;
}
``````

#### [142] Linked List Cycle II 找到环中的第一个节点

``````public ListNode detectCycle(ListNode head) {
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
// 如果相遇了
if (fast == slow) {
// 从头有个指针开始走了
// 两个指针相遇时也就刚好到达了环的第一个节点，参照博客分析
while (slow != slow2) {
slow = slow.next;
slow2 = slow2.next;
}
return slow;
}
}
return null;
}
``````

### 链表排序

#### [148] Sort List 链表排序

https://leetcode.com/problems/sort-list

``````public ListNode sortList(ListNode head) {
ListNode mid = findMiddle(head);        // 找中点
ListNode right = sortList(mid.next);    // 对mid的右边链表先排序
mid.next = null;                        // 这时候才把它断开
ListNode left = sortList(head);         // 再对mid的左边链表排序
return merge(left, right);
}

private ListNode findMiddle(ListNode head) {   // 快慢指针
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}

ListNode dummy = new ListNode(0);
ListNode tail = dummy;     // 尾指针指向dummy node
} else {                        // 如果右边的头小
}
tail = tail.next;  //tail往后移
}
if (head1 != null) {        // 看左边头还是右边头没分完就分过去
} else {
}
return dummy.next;
}
``````

#### [147] Insertion Sort List 链表插入排序

https://leetcode.com/problems/insertion-sort-list

``````public ListNode insertionSortList(ListNode head) {
ListNode dummy = new ListNode(0);
ListNode temp = null, prev = null;
while (cur != null && cur.next != null) {
if (cur.val <= cur.next.val) {
cur = cur.next;
} else {
temp = cur.next;
cur.next = temp.next;
prev = dummy;
while (prev.next.val <= temp.val) {
prev = prev.next;
}
temp.next = prev.next;
prev.next = temp;
}
}
return dummy.next;
}

if (head == null) return null;
ListNode dummy = new ListNode(-1);
while (cur.next != null) {
// Find where to insert cur.next, or stop at cur
ListNode pos = dummy;
while (pos.next.val < cur.next.val) {
pos = pos.next;
}
//  pos(a),pos.next(b),...cur(c),cur.next(d),cur.next.next(e)
//  => a,d,b,...,c,e
if (pos != cur) {
ListNode tmp = pos.next;
pos.next = cur.next;
cur.next = cur.next.next;
pos.next.next = tmp;
} else {
cur = cur.next;
// error1: cur.next is updated already above, but it must update here!
}
}
return dummy.next;
}
``````

### 链表操作

#### [143] Reorder List 重排链表

https://leetcode.com/problems/reorder-list

For example, Given `{1,2,3,4}`, reorder it to `{1,4,2,3}`.

``````public void reorderList(ListNode head) {
ListNode dummy = new ListNode(0);
ListNode temp = null;
while (fast != null && fast.next != null) {
temp = slow;
slow = slow.next;
fast = fast.next.next;
}
// 截断后半部分
temp.next = null;
ListNode l2 = reverse(slow);
merge(l1, l2);
}

ListNode prev = null;
}
return prev;
}

public void merge(ListNode l1, ListNode l2) {
while (l1 != l2) {
ListNode n1 = l1.next;
ListNode n2 = l2.next;
l1.next = l2;
if (n1 == null) break;
l2.next = n1;
l1 = n1;
l2 = n2;
}
}
``````

#### [61] Rotate List 旋转链表

https://leetcode.com/problems/rotate-list

For example:
Given `1->2->3->4->5->NULL` and `k = 2`,
return `4->5->1->2->3->NULL`.

image
``````public ListNode rotateRight(ListNode head, int k) {

int len = 1;
// 得到链表长度
while (index.next != null) {
index = index.next;
len++;
}
// 因为k可能大于链表长度len，所以需要取余处理
k %= len;

// 链表首尾连成一个环
// 得到新的链表头
for (int i = 1; i < len - k; i++) {
}
// 断开环
return res;
}
``````

#### [86] Partition List 划分链表

https://leetcode.com/problems/partition-list

``````public ListNode partition(ListNode head, int x) {
smaller.next = temp;
smaller = smaller.next;
} else {
greater.next = temp;
greater = greater.next;
}
}
}
``````

#### [328] Odd Even Linked List 奇偶链表

image
``````public ListNode oddEvenList(ListNode head) {
// 因为odd肯定在even之前，所以只需要判断even和even.next不为空就可以
while (even != null && even.next != null) {
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
// 偶链表连在奇链表后面
}
``````

#### [725] Split Linked List in Parts 拆分链表成k部分

``````public ListNode[] splitListToParts(ListNode root, int k) {
ListNode[] res = new ListNode[k];

int len = 0;
for (ListNode cur = root; cur != null; cur = cur.next) len++;

int part = len / k;
int surplus = len % k;
ListNode prev = null;
for (int i = 0; i < k; i++, surplus--) {
// 多的部分有k+1个，少的部分有k个
for (int j = 0; j < part + (surplus > 0 ? 1 : 0); j++) {
}
// 与后面部分断开
if (prev != null) prev.next = null;
}
return res;
}
``````

#### [234] Palindrome Linked List 判断链表是否是回文串

``````public boolean isPalindrome(ListNode head) {
// 找中点
// 对中点后的节点进行反转
mid.next = reverse(mid.next);
ListNode q = mid.next;
while (p != null && q != null) {
if (p.val != q.val) return false;
p = p.next;
q = q.next;
}
return true;
}

while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}

ListNode prev = null;
}
return prev;
}
``````

### 进位加法

#### [2] Add Two Numbers 两个链表相加

``````public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
int sum = 0;
while (l1 != null || l2 != null) {
if (l1 != null) {
sum += l1.val;
l1 = l1.next;
}
if (l2 != null) {
sum += l2.val;
l2 = l2.next;
}
cur.next = new ListNode(sum % 10);
sum /= 10;
cur = cur.next;
}
if (sum == 1) cur.next = new ListNode(1);
return dummy.next;
}
``````

#### [445] Add Two Numbers II 两个链表倒序相加

``````public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;

Stack<Integer> stack = new Stack<>();
Stack<Integer> s1 = new Stack<>();
Stack<Integer> s2 = new Stack<>();
while (l1 != null) {
s1.push(l1.val);
l1 = l1.next;
}
while (l2 != null) {
s2.push(l2.val);
l2 = l2.next;
}

int cn = 0;
while (!s1.isEmpty() || !s2.isEmpty()) {
int val = cn;
if (!s1.isEmpty()) {
val += s1.pop();
}
if (!s2.isEmpty()) {
val += s2.pop();
}
// 产生进位cn
cn = val / 10;
val = val % 10;
stack.push(val);
}

// 当l1、l2都到达链表尾且有进位时
if (cn != 0) stack.push(cn);

while (!stack.isEmpty()) {
cur.next = new ListNode(stack.pop());
cur = cur.next;
}

return dummy.next;
}
``````

### 链表合并

#### [21] Merge Two Sorted Lists 合并两个有序链表

``````public ListNode Merge(ListNode list1, ListNode list2) {
if (list1 == null) return list2;
else if (list2 == null) return list1;

// 新建一个头节点，用来存合并的链表
ListNode dummy = new ListNode(0);
dummy.next = null;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
list1 = list1.next;
} else {
list2 = list2.next;
}
}

// 把未结束的链表连接到合并后的链表尾部
if (list1 != null) head.next = list1;
if (list2 != null) head.next = list2;
return dummy.next;
}
``````

#### [23] Merge k Sorted Lists 合并k个有序链表

https://leetcode.com/problems/merge-k-sorted-lists

``````public ListNode mergeKLists(ListNode[] lists) {
ListNode dummy = new ListNode(0);
if (lists == null || lists.length == 0) return dummy.next;

int len = lists.length;
ListNode cur = dummy;
PriorityQueue<ListNode> queue = new PriorityQueue<>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;
}
});
// 把所有List的首节点（如果不为空）都放入优先队列中
for (int i = 0; i < len; i++) {
if (lists[i] != null) {
}
}
while (queue.size() != 0) {
// 从优先队列中取出一个最小的
ListNode node = queue.poll();
cur.next = node;
cur = cur.next;
}
return dummy.next;
}
``````

#### [160] Intersection of Two Linked Lists 两个相交链表的交点

``````public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int lenA = 0, lenB = 0;
// 计算链表A的长度
while (nodeA != null) {
nodeA = nodeA.next;
lenA++;
}
// 计算链表B的长度
while (nodeB != null) {
nodeB = nodeB.next;
lenB++;
}
// 让较长的链表先飞一会
for (int i = 0; i < Math.abs(lenA - lenB); i++) {
}
}
return null;
}
``````

### 其他

#### [138] Copy List with Random Pointer 复制复杂链表（包含一个随机指针）

https://leetcode.com/problems/copy-list-with-random-pointer

``````public RandomListNode copyRandomList(RandomListNode head) {

// First round: make copy of each node,
// and link them together side-by-side in a single list.
while (iter != null) {
next = iter.next;

RandomListNode copy = new RandomListNode(iter.label);
iter.next = copy;
copy.next = next;

iter = next;
}

// Second round: assign random pointers for the copy nodes.
while (iter != null) {
if (iter.random != null) {
iter.next.random = iter.random.next;
}
iter = iter.next.next;
}

// Third round: restore the original list, and extract the copy list.

while (iter != null) {
next = iter.next.next;

// extract the copy
copy = iter.next;
copyIter.next = copy;
copyIter = copy;

// restore the original list
iter.next = next;

iter = next;
}

}
``````

#### [109] Convert Sorted List to Binary Search Tree 有序链表转BST

``````public TreeNode sortedListToBST(ListNode head) {
if (head == null) return null;

}

private TreeNode build(ListNode start, ListNode end) {
if (start == end) return null;

ListNode fast = start;
ListNode slow = start;
// fast走到结尾，那么slow就是中间节点了，即BST的根节点
while (fast != end && fast.next != end) {
slow = slow.next;
fast = fast.next.next;
}

// 递归处理相应的左右部分，链表的左半部分就是左子树，而右半部分则是右子树
TreeNode node = new TreeNode(slow.val);
node.left = build(start, slow);
node.right = build(slow.next, end);

return node;
}
``````

### 剑指Offer

#### [面试题13] 在O(1)时间内删除链表节点

``````public void deleteNode(ListNode head, ListNode toBeDeleted) {
if (head == null || toBeDeleted == null) return;

// 链表有多个节点，要删除的结点不是尾结点: O(1) 时间
if (toBeDeleted.next != null) {
ListNode next = toBeDeleted.next;
toBeDeleted.val = next.val;
toBeDeleted.next = next.next;
next = null;
} else if (head == toBeDeleted) {
// 链表只有一个结点，删除头结点（也是尾结点）:O(1) 时间
toBeDeleted = null;
} else {
// 链表有多个节点，要删除的是尾节点: O(n) 时间
while (temp.next != toBeDeleted) {
temp = temp.next;
}
temp.next = null;
}
}
``````

#### [面试题15] 链表中倒数第k个结点

``````public ListNode findKthToTail(ListNode head, int k) {
if (head == null || k < 1) return null;
for (int i = 0; i < k - 1; i++) {
// 链表节点数可能小于k
if (fast.next != null)
// 快指针先走k-1步
fast = fast.next;
else
return null;
}
// 快指针走到尾节点就退出循环
while (fast.next != null) {
// 从第k步开始，两个指针一起走
fast = fast.next;
slow = slow.next;
}
return slow;
}
``````

#### [面试题5] 从尾到头打印链表

https://www.nowcoder.com/questionTerminal/d0267f7f55b3412ba93bd35cfa8e8035

``````/**
* 用栈保存遍历结果
*/
List<Integer> results = new ArrayList<>();
if (head == null) return results;
Stack<Integer> stack = new Stack<>();
// 只要链表未到达表尾
while (node != null) {
// 就依次遍历链表并添加到Stack中
stack.push(node.val);
node = node.next;
}
// 只要栈不空
while (!stack.isEmpty()) {
// 就不断地将元素添加到List中，并出栈
}
return results;
}

/**
* 头插法
*/
ArrayList<Integer> results = new ArrayList<>();
if (head == null) return results;
while (node != null) {
// 头插法
node = node.next;
}
return results;
}

/**
* 递归
*/
List<Integer> results = new ArrayList<>();
if (head == null) return results;
return results;
}

private void dfs(ListNode head, List<Integer> results) {
// 因为要反过来输出链表，所以先递归输出后面的节点
}
// 再输出自身
}
}
``````

# 解题技巧

• slow，fst双指针，因为链表无法得知长度，所以尝试用这种方法来达到某种效果（长度、检测环等）
• 对于涉及链表长度的问题，往往会通过两个指针进行几何变换来得到想要的差额==要好好画图理解思考
• 使用一些临时变量来存储next指针，以完成插入删除等操作
• 对于插入和删除等操作，往往需要一个额外的指针来记录其前面的节点，再编程之前好好思考其间关系效果会比较好
• 对一些依赖于后面节点才可以完成的操作，使用递归的方式来解决
• 对于有些题目提前使用循环获得其链表的长度也是一种有效的方法
• 对于要考虑最后几个节点的操作，有事可以再遍历之前先将头指针向后移动k个节点