【LeetCode题解】链表Linked List
1. 链表
数组是一种顺序表,index与value之间是一种顺序映射,以\(O(1)\)的复杂度访问数据元素。但是,若要在表的中间部分插入(或删除)某一个元素时,需要将后续的数据元素进行移动,复杂度大概为\(O(n)\)。链表(Linked List)是一种链式表,克服了上述的缺点,插入和删除操作均不会引起元素的移动;数据结构定义如下:
public class ListNode {
String val;
ListNode next;
// ...
}
常见的链表有单向链表(也称之为chain),只有next指针指向后继结点,而没有previous指针指向前驱结点。
链表的插入与删除操作只涉及到next指针的更新,而不会移动数据元素。比如,要在FAT与HAT插入结点GAT,如图所示:

Java实现:
ListNode fat, gat;
gat.next = fat.next;
fat.next = gat;
又比如,要删除结点GAT,如图所示:

Java实现:
fat.next = fat.next.next;
从上述代码中,可以看出:因为没有前驱指针,一般在做插入和删除操作时,我们需要通过操作前驱结点的next指针开始。
2. 题解
| LeetCode题目 | 归类 |
|---|---|
| 237. Delete Node in a Linked List | 删除 |
| 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 | |
| 24. Swap Nodes in Pairs | 移动 |
| 206. Reverse Linked List | |
| 92. Reverse Linked List II | |
| 61. Rotate List | |
| 86. Partition List | |
| 328. Odd Even Linked List | |
| 21. Merge Two Sorted Lists | 合并 |
| 23. Merge k Sorted Lists | |
| 141. Linked List Cycle | 有环 |
| 142. Linked List Cycle II | 有环 |
| 234. Palindrome Linked List | |
| 143. Reorder List | |
| 160. Intersection of Two Linked Lists | |
| 2. Add Two Numbers | |
| 445. Add Two Numbers II |
237. Delete Node in a Linked List
删除指定结点。由于是单向链表,因此只需更新待删除节点即可。
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
203. Remove Linked List Elements
删除指定值的结点。用两个指针实现,curr用于遍历,prev用于暂存前驱结点。
public ListNode removeElements(ListNode head, int val) {
ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
fakeHead.next = head;
for (ListNode curr = head, prev = fakeHead; curr != null; curr = curr.next) {
if (curr.val == val) { // remove
prev.next = curr.next;
} else { // traverse
prev = prev.next;
}
}
return fakeHead.next;
}
19. Remove Nth Node From End of List
删除链表的倒数第n个结点。思路:因为单向链表是没有前驱指针的,所以应找到倒数第n+1个结点;n有可能等于链表的长度,故先new一个head的前驱结点fakeHead。用两个指针slow、fast从fakeHead开始,先移动fast n+1步,使得其距离slow为n+1;然后,两个指针同步移动,当fast走到null时,slow即处于倒数第n+1个结点,删除slow的next结点即可。
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
fakeHead.next = head;
ListNode slow = fakeHead, fast = fakeHead;
for (int i = 1; i <= n + 1; i++) {
fast = fast.next;
}
while(fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next; // the n-th node from end is `slow.next`
return fakeHead.next;
}
83. Remove Duplicates from Sorted List
删除有序链表中的重复元素。处理思路有上一问题类似,不同的是判断删除的条件。
public ListNode deleteDuplicates(ListNode head) {
ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
fakeHead.next = head;
for (ListNode curr = head, prev = fakeHead; curr != null && curr.next != null; curr = curr.next) {
if (curr.val == curr.next.val) { // remove
prev.next = curr.next;
} else {
prev = prev.next;
}
}
return fakeHead.next;
}
82. Remove Duplicates from Sorted List II
上一问题的升级版,删除所有重复元素结点。在里层增加一个while循环,跳过重复元素结点。
public ListNode deleteDuplicates(ListNode head) {
ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
fakeHead.next = head;
for (ListNode curr = head, prev = fakeHead; curr != null; curr = curr.next) {
while (curr.next != null && curr.val == curr.next.val) { // find the last duplicate
curr = curr.next;
}
if (prev.next == curr) prev = prev.next;
else prev.next = curr.next;
}
return fakeHead.next;
}
24. Swap Nodes in Pairs
链表中两两交换。按step = 2 遍历链表并交换;值得注意的是在更新next指针是有次序的。
public ListNode swapPairs(ListNode head) {
ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
fakeHead.next = head;
for (ListNode prev = fakeHead, p = head; p != null && p.next != null; ) {
ListNode temp = p.next.next;
prev.next = p.next; // update next pointer
p.next.next = p;
p.next = temp;
prev = p;
p = temp;
}
return fakeHead.next;
}
206. Reverse Linked List
逆序整个链表。逆序操作可以看作:依次遍历链表,将当前结点插入到链表头。
public ListNode reverseList(ListNode head) {
ListNode newHead = null;
for (ListNode curr = head; curr != null; ) {
ListNode temp = curr.next;
curr.next = newHead; // insert to the head of list
newHead = curr;
curr = temp;
}
return newHead;
}
92. Reverse Linked List II
上一问题的升级,指定区间[m, n]内做逆序;相当于把该区间的链表逆序后,再拼接到原链表中。
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode newHead = null, curr = head, firstHead = null, firstHeadPrev = null;
for (int i = 1; curr != null && i <= n; i++) {
if (i < m - 1) {
curr = curr.next;
continue;
}
if (i == m - 1) {
firstHeadPrev = curr; // mark first head previous node
curr = curr.next;
} else {
if (i == m) firstHead = curr; // mark first head node
ListNode temp = curr.next;
curr.next = newHead;
newHead = curr;
curr = temp;
}
}
firstHead.next = curr;
if (firstHeadPrev != null) firstHeadPrev.next = newHead;
if (m == 1) return newHead;
return head;
}
61. Rotate List
指定分隔位置,将链表的左右部分互换。只需修改左右部分的最后节点的next指针即可,有一些special case需要注意,诸如:链表为空,k为链表长度的倍数等。为了得到链表的长度,需要做一次pass。故总共需要遍历链表两次。
public ListNode rotateRight(ListNode head, int k) {
if (head == null || k == 0) return head;
int n = 0, i;
ListNode curr, leftLast = head, rightFist, rightLast = head;
for (curr = head; curr != null; curr = curr.next) { // get the length of list
n++;
}
k %= n; // k maybe larger than n
if (k == 0) return head;
for (i = 1, curr = head; i <= n; i++, curr = curr.next) { // mark the split node
if (i == n - k) leftLast = curr;
if (i == n) rightLast = curr;
}
rightFist = leftLast.next;
leftLast.next = null;
rightLast.next = head;
return rightFist;
}
86. Partition List
类似于quick sort的partition,不同的是要保持链表的原顺序。思路:用两个链表,一个保留小于指定数x,一个保留不大于指定数x;最后拼接到一起即可。
public ListNode partition(ListNode head, int x) {
if (head == null) return null;
ListNode lt = new ListNode(-1), gte = new ListNode(-2); // less than, greater than and equal
ListNode p, p1, p2;
for (p = head, p1 = lt, p2 = gte; p != null; p = p.next) {
if (p.val < x) {
p1.next = p;
p1 = p1.next;
} else {
p2.next = p;
p2 = p2.next;
}
}
p2.next = null;
p1.next = gte.next;
return lt.next;
}
328. Odd Even Linked List
链表分成两部分:偶数编号与奇数编号,将偶数链表拼接到奇数链表的后面。
public ListNode oddEvenList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode odd = head, even = head.next, evenHead = head.next;
while (odd.next != null && odd.next.next != null) {
odd.next = odd.next.next;
odd = odd.next;
if (even != null && even.next != null) {
even.next = even.next.next;
even = even.next;
}
}
odd.next = evenHead; // splice even next to odd
return head;
}
21. Merge Two Sorted Lists
合并两个有序链表。比较简单,分情况比较。
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head = new ListNode(-1), p, p1, p2;
for (p = head, p1 = l1, p2 = l2; p1 != null || p2 != null; p = p.next) {
if (p1 != null) {
if (p2 != null && p1.val > p2.val) {
p.next = p2;
p2 = p2.next;
} else {
p.next = p1;
p1 = p1.next;
}
} else {
p.next = p2;
p2 = p2.next;
}
}
return head.next;
}
23. Merge k Sorted Lists
合并k个有序链表。思路:借助于堆,堆的大小为k,先将每个链表的首结点入堆,堆顶元素即为最小值,堆顶出堆后将next入堆;依此往复,即可得到整个有序链表。
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) return null;
PriorityQueue<ListNode> minHeap = new PriorityQueue<>(lists.length, new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;
}
});
// initialization
for (ListNode node : lists) {
if (node != null)
minHeap.offer(node);
}
ListNode head = new ListNode(-1);
for (ListNode p = head; !minHeap.isEmpty(); ) {
ListNode top = minHeap.poll();
p.next = top;
p = p.next;
if (top.next != null)
minHeap.offer(top.next);
}
return head.next;
}
141. Linked List Cycle
判断链表是否有环。用两个指针,一个快指针一个慢指针,一个每次移动两步,一个每次移动一步;最后两者相遇,即说明有环。
public boolean hasCycle(ListNode head) {
if (head == null) return false;
for (ListNode slow = head, fast = head; fast.next != null && fast.next.next != null; ) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
142. Linked List Cycle II
找出链表中环的起始节点\(s\)。解决思路:用两个指针——fast、slow,先判断是否环;两者第一次相遇的节点与\(s\)的距离 == 链表起始节点与\(s\)的距离(有兴趣可以证明一下)。
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) return null;
ListNode slow = head, fast = head;
boolean isCycled = false;
while (slow != null && fast != null && fast.next != null) { // first meeting
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
isCycled = true;
break;
}
}
if (!isCycled) return null;
for (fast = head; slow != fast; ) { // find the cycle start node
slow = slow.next;
fast = fast.next;
}
return slow;
}
234. Palindrome Linked List
判断链表\(L\)是否中心对称。中心对称的充分必要条件:对于任意的 i <= n/2 其中n为链表长度,有L[i] = L[n+1-i]成立。因此先找出middle结点(在距离首结点n/2处),然后逆序右半部分链表,与左半部分链表的结点一一比较,即可得到结果。在找出middle结点时也用到了小技巧——快慢两个指针遍历链表,当fast遍历完成时,slow即为middle结点(证明分n为奇偶情况);当n为偶数时,middle结点有两个,此时slow为左middle结点。换句话说,无论n为奇数或偶数,此时的slow为右半部分子链表的第一个结点的前驱结点。
public boolean isPalindrome(ListNode head) {
if (head == null) return true;
ListNode slow = head, fast = head, p;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode q = reverseList(slow.next); // the first node of the right half is `slow.next`
for (p = head; q != null; p = p.next, q = q.next) {
if (p.val != q.val) return false;
}
return true;
}
143. Reorder List
对于除去首结点外的链表,将右半部分子链表从后往前依次插入进左半部分链表。解决思路与上类似,找出middle结点,然后依次插入。值得注意:Java的对象传参是引用类型,需要更新左半部份子链表的最后一个结点的next指针,不然则链表的结点的无限循环导致OOM。
public void reorderList(ListNode head) {
if (head == null || head.next == null) return;
ListNode slow = head.next, fast = head.next;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode p, q = reverseList(slow.next);
slow.next = null; // update the next pointer of the left half's last node
for (p = head; q != null; ) {
ListNode pNext = p.next, qNext = q.next;
p.next = q; // insert qNode into the next of p node
q.next = pNext;
p = pNext;
q = qNext;
}
}
160. Intersection of Two Linked Lists
求两个链表相交的第一个结点\(P\)。假定两个链表的长度分别为m、n,相交的第一个结点\(P\)分别距离两个链表的首结点为a、b,则根据链表相交的特性:两个链表的尾节点都是同一个,即m-a = n-b;移项后有m+b = n+a。根据上述性质,在遍历完第一个链表后,再往右b个结点,即到达了结点\(P\)。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode ptrA = headA, ptrB = headB;
while (ptrA != ptrB) { // in case ptrA == ptrB == null
ptrA = (ptrA != null) ? ptrA.next : headB;
ptrB = (ptrB != null) ? ptrB.next : headA;
}
return ptrA;
}
2. Add Two Numbers
模拟两个链表的加法。开始的时候没理解清楚题意,被坑了多次WA。链表的head表示整数的个位,则应从首端对齐开始做加法。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = new ListNode(-1);
boolean carry = false; // mark whether has carry
for (ListNode p = l1, q = l2, r = head; p != null || q != null || carry; r = r.next) {
int pVal = (p == null) ? 0 : p.val;
int qVal = (q == null) ? 0 : q.val;
int sum = carry ? pVal + qVal + 1 : pVal + qVal;
carry = sum >= 10;
r.next = new ListNode(sum % 10);
if (p != null) p = p.next;
if (q != null) q = q.next;
}
return head.next;
}
445. Add Two Numbers II
与上一题不同的是,链表的head表示整数的最高位,则应是尾端对齐相加。为了尾端对齐,将采用stack来逆序链表,之后相加步骤与上类似;但创建新链表应使用头插法。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode p;
Stack<Integer> s1 = new Stack<>();
Stack<Integer> s2 = new Stack<>();
for (p = l1; p != null; p = p.next) {
s1.push(p.val);
}
for (p = l2; p != null; p = p.next) {
s2.push(p.val);
}
ListNode head = new ListNode(-1);
boolean carry = false; // mark whether has carry
for (ListNode r = null; !s1.isEmpty() || !s2.isEmpty() || carry; ) {
int pVal = (s1.isEmpty()) ? 0 : s1.pop();
int qVal = (s2.isEmpty()) ? 0 : s2.pop();
int sum = carry ? pVal + qVal + 1 : pVal + qVal;
carry = sum >= 10;
ListNode node = new ListNode(sum % 10);
node.next = r;
head.next = node;
r = node;
}
return head.next;
}
【LeetCode题解】链表Linked List的更多相关文章
- 【LeetCode】链表 linked list(共34题)
[2]Add Two Numbers (2018年11月30日,第一次review,ko) 两个链表,代表两个整数的逆序,返回一个链表,代表两个整数相加和的逆序. Example: Input: ( ...
- LeetCode 题解之Linked List Cycle II
1.题目描述 2.问题分析 使用快慢指针方法判断链表是否有环,然后寻找环开始的节点. 3.代码 ListNode *detectCycle(ListNode *head) { if( head == ...
- LeetCode题解之Linked List Cycle
1.题目描述 2.问题分析 使用快慢指针方法,一个快指针,一个慢指针,如果到某个时候,快指针追上了慢指针,则说明有环存在. 3.代码 bool hasCycle(ListNode *head) { i ...
- Leetcode题解 - 链表简单部分题目代码+思路(21、83、203、206、24、19、876)
- 【LeetCode题解】25_k个一组翻转链表(Reverse-Nodes-in-k-Group)
目录 描述 解法一:迭代 思路 Java 实现 Python 实现 复杂度分析 解法二:递归(不满足空间复杂度) 思路 Java 实现 Python 实现 复杂度分析 更多 LeetCode 题解笔记 ...
- 【LeetCode题解】24_两两交换链表中的节点(Swap-Nodes-in-Pairs)
目录 描述 解法一:迭代 思路 Java 实现 Python 实现 复杂度分析 解法二:递归(不满足空间复杂度要求) 思路 Java 实现 Python 实现 复杂度分析 更多 LeetCode 题解 ...
- 【LeetCode题解】19_删除链表的倒数第N个节点(Remove-Nth-Node-From-End-of-List)
目录 描述 解法:双指针 思路 Java 实现 Python 实现 复杂度分析 更多 LeetCode 题解笔记可以访问我的 github. 描述 给定一个链表,删除链表的倒数第 n 个节点,并且返回 ...
- 【LeetCode题解】206_反转链表(Reverse-Linked-List)
目录 描述 解法一:迭代 思路 Java 实现 Python 实现 复杂度分析 解法二:递归 思路 Java 实现 Python 实现 复杂度分析 更多 LeetCode 题解笔记可以访问我的 git ...
- [LeetCode 题解]:Intersection of Two Linked Lists
前言 [LeetCode 题解]系列传送门: http://www.cnblogs.com/double-win/category/573499.html 1.题目描述 Suppose an ...
- [LeetCode] 92. Reverse Linked List II 反向链表II
Reverse a linked list from position m to n. Do it in-place and in one-pass. For example:Given 1-> ...
随机推荐
- Linux磁盘配额实验
1.实现磁盘限额的条件 *需要Linux内核支持 *安装quota软件包2.Linux磁盘限额的特点 作用范围:针对指定 文件系统(分区) 限制对象:普通用户帐号.组帐号 限制类型:磁盘容量(默认单位 ...
- K-means算法简介
K-means 算法是无监督的 聚类算法,算法简单,有效. K-means算法: 输入参数: 指定聚类数目 k,训练集 X 输出 : k 个聚类 算法描述: K-means 算法 是一个 迭代算法,每 ...
- Ubuntu下搭建C++开发环境
Ubuntu使用eclipse搭建c/c++编译环境----CDT插件 Ubuntu(Linux)使用Eclipse搭建C/C++编译环境 这两天,给自己电脑弄了双系统,除了原来的W ...
- PHP文件夹操作2
mkdir("路径文件名"); 创建文件夹 imdir("路径文件名"); 删除文件夹(只能删除空的文件夹) rename("路径",&qu ...
- iOS开发——pch文件创建
新换的公司,接手的项目里面连pch文件都没有,每次需要用到屏幕的宽高时,都是现写.今天既然碰到了,就把PCH这个玩意说一下. 1.Command+N,打开新建文件窗口:iOS->Other-&g ...
- springMVC+Hibernate配置
本文描述下 sypro 项目中使用 springMVC+Hibernate配置,初学SpringMVC做下简单整理解. 1.web项目首先我们要使用 web.xml文件将 spring配置引入进来 & ...
- innodb系统表空间维护
环境说明: 有一个在运行中的mysql环境,由于之前的配置文件设置的过于简单(没有配置innodb_data_file_path变更):造成现在系统表空间已经满了 如果innodb_data_file ...
- cordova环境搭建
首先,需要了解一下cordova 和 phonegap 的关系.phoneGap是原先的名字,Cordova是phoneGap被捐给apache之后 用的项目名,phoneGap的名字也被保留了. 1 ...
- 读书笔记--用Python写网络爬虫02--数据抓取
抓取(scraping)---爬虫从网页中抽取一些数据用以实现某些用途. 三种抽取网页数据的方法:正则表达式.Beautiful Soup和lxml. 2.1 分析网页 通过浏览器自带选项,查看网页源 ...
- 怎样看paper 最有效率
thinking more after reading. Don't just read the papers.in addition, at begining, you'd better focus ...