【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-> ...
随机推荐
- 加载window事件
$(document).ready()和window.onload的区别 发表于 2012-08-29 由 admin 以浏览器装载文档为例,在页面加载完毕后,浏览器会通过JavaScript为DOM ...
- CentOS6.6部署OpenStack Havana(Nova-Network版)
CentOS6.4部署OpenStack Havana(Nova-Network版) 一 基本设备介绍 测试环境 CentOS6.4 x64 OpenStack 服务 介绍 计算 (Compute) ...
- WebRequestManager对象的使用
WebRequestManager可以喝WebRequest对象一起使用. WebRequestManager对象的add_completedRequest方法,可以用来处理所有WebRequest对 ...
- STM32应用笔记转载
stm32 外部中断嵌套[操作寄存器+库函数] stm32 NVIC中断管理实现[直接操作寄存器] stm32 SPI通信[操作寄存器+库函数] stm32 i2c通信 [操作寄存器+库函数] stm ...
- 关于div宽度和高度的100%设定
设置DIV大小的有两个属性width和height,以前在学习DIV每次给DIV设置100%宽度或高度时都很迷惑,不明白这个100%的宽度(高度)到底有多宽有多高?这个100%是从哪里得到的从哪里继承 ...
- 简单介绍java Enumeration
Enumeration接口 Enumeration接口本身不是一个数据结构.但是,对其他数据结构非常重要. Enumeration接口定义了从一个数据结构得到连续数据的手段.例如,Enumeratio ...
- STM32中的位带(bit-band)操作(转)
源:STM32中的位带(bit-band)操作 支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写.在 CM3 中,有两个区中实现了位带.其中一个是 SRAM 区的最低 1MB 范围 ...
- iOS 之 编外知识点
iOS 使用github iOS 开源库介绍 iOS 优质方案 iOS 开发framework 后端数据库使用 Bomb方案 iOS 错误及解决汇总 后台 之 Bmob 申请苹果企业账号
- Linux FTP 服务器配置简单说明
一. FTP 说明 linux 系统下常用的FTP 是vsftp, 即Very Security File Transfer Protocol. 还有一个是proftp(Profession ftp ...
- robotium从入门到放弃 二 第一个实例
1.导入被测试的源码 我们先下载加你计算器源码,下载地址: https://robotium.googlecode.com/files/AndroidCalculator.zip 如果地址被墙无法现在 ...