链表Linked List

1. 链表

数组是一种顺序表,index与value之间是一种顺序映射,以O(1)O(1)的复杂度访问数据元素。但是,若要在表的中间部分插入(或删除)某一个元素时,需要将后续的数据元素进行移动,复杂度大概为O(n)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
找出链表中环的起始节点ss。解决思路:用两个指针——fast、slow,先判断是否环;两者第一次相遇的节点与ss的距离 == 链表起始节点与ss的距离(有兴趣可以证明一下)。

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
判断链表LL是否中心对称。中心对称的充分必要条件:对于任意的 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
求两个链表相交的第一个结点PP。假定两个链表的长度分别为m、n,相交的第一个结点PP分别距离两个链表的首结点为a、b,则根据链表相交的特性:两个链表的尾节点都是同一个,即m-a = n-b;移项后有m+b = n+a。根据上述性质,在遍历完第一个链表后,再往右b个结点,即到达了结点PP。

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;
}
如需转载,请注明作者及出处.
作者:Treant

链表Linked List的更多相关文章

  1. 算法与数据结构基础 - 链表(Linked List)

    链表基础 链表(Linked List)相比数组(Array),物理存储上非连续.不支持O(1)时间按索引存取:但链表也有其优点,灵活的内存管理.允许在链表任意位置上插入和删除节点.单向链表结构一般如 ...

  2. 数据结构之链表(Linked list)

    说明:如果仔细阅读完全文后,可能感觉有些不统一,这里先说明下原因. 链表尾引用不统一:在介绍单链表时,只有一个链表首部的引用(head) 指向第一个节点.你看到后面关于双链表及循环列表时,除了指向第一 ...

  3. 【LeetCode题解】链表Linked List

    1. 链表 数组是一种顺序表,index与value之间是一种顺序映射,以\(O(1)\)的复杂度访问数据元素.但是,若要在表的中间部分插入(或删除)某一个元素时,需要将后续的数据元素进行移动,复杂度 ...

  4. 数据结构与算法 —— 链表linked list(01)

    链表(维基百科) 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存储, ...

  5. 数据结构与算法 —— 链表linked list(03)

    继续关于linked list的算法题: 删除排序链表中的重复元素 给定一个排序链表,删除所有重复的元素使得每个元素只留下一个. 案例: 给定 1->1->2,返回 1->2 给定  ...

  6. [Java]LeetCode141. 环形链表 | Linked List Cycle

    Given a linked list, determine if it has a cycle in it. Follow up:Can you solve it without using ext ...

  7. 带环链表 linked list cycle

    1 [抄题]: 给定一个链表,判断它是否有环. [思维问题]: 反而不知道没有环怎么写了:快指针fast(奇数个元素)或fast.next(偶数个元素) == null [一句话思路]: 快指针走2步 ...

  8. LeetCode 141:环形链表 Linked List Cycle

    给定一个链表,判断链表中是否有环. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始). 如果 pos 是 -1,则在该链表中没有环. Given a l ...

  9. LeetCode 141. 环形链表(Linked List Cycle) 19

    141. 环形链表 141. Linked List Cycle 题目描述 给定一个链表,判断链表中是否有环. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 ...

随机推荐

  1. [Angular] Reactive Store and AngularFire Observables

    A simple store implemenet: import { Observable } from 'rxjs/Observable'; import { BehaviorSubject } ...

  2. POJ 3169 Layout (HDU 3592) 差分约束

    http://poj.org/problem?id=3169 http://acm.hdu.edu.cn/showproblem.php?pid=3592 题目大意: 一些母牛按序号排成一条直线.有两 ...

  3. POJ 1018 Communication System 贪心+枚举

    看题传送门:http://poj.org/problem?id=1018 题目大意: 某公司要建立一套通信系统,该通信系统需要n种设备,而每种设备分别可以有m个厂家提供生产,而每个厂家生产的同种设备都 ...

  4. shrio 权限管理filterChainDefinitions过滤器配置(转)

    /** * Shiro-1.2.2内置的FilterChain * @see ============================================================= ...

  5. [Yarn] A JavaScript Package Manager

    Yarn is a new JavaScript package manager that aims to be speedy, deterministic, and secure. See how ...

  6. js进阶 12-5 jquery中表单事件如何使用

    js进阶 12-5 jquery中表单事件如何使用 一.总结 一句话总结:表单事件如何使用:可元素添加事件监听,然后监听元素,和javase里面一样. 1.表单获取焦点和失去焦点事件有哪两组? 注意是 ...

  7. MHA 一主两从搭建-脚本VIP-自动切换

    环境介绍:主机名 IP MHA角色 MySQL角色node1 192.168.56.26 Node MySQL Master node2 192.168.56.27 Node MySQL Master ...

  8. [Docker] Download and Remove Docker Images

    Learn the basics of downloading and pulling Docker images from Docker Hub. Learn the difference betw ...

  9. 【Lucene4.8教程之五】Luke 2014-06-24 15:12 1092人阅读 评论(0) 收藏

    一.Luke基本内容 1.Luke简介 Luke可用于查看Lucene创建的索引,并对其进行基本操作. 2.创建Luke (1)从Github上下载源文件 https://github.com/tar ...

  10. matlab 下的集成学习工具箱

    matlab 当前支持的弱学习器(weak learners)类型分别为: 'Discriminant' 'knn' 'tree' 可通过 templateTree 定义: 1. fitcensemb ...