Linked List



什么是线性链表(Linked List)?
线性链表是由一组线性集合的数据元素构成的数据结构, 其中每一个节点通过指针指向下一个节点.



1
3
8
4
5
2



| 1 | 4 | 8 | 2 | 5 | 3 |
|---|



| 1 | 4 | 8 | 2 | 5 | 3 |
|---|
1
3
8
4
5
2



| 1 | 4 | 8 | 2 | 5 | 3 |
|---|
1
3
8
4
5
2



线性链表(Linked List)
- 每一个节点都知道下一个节点的地址.
- 链表中的第一个节点可以表示整个链表.
- 如果要访问链表中的某个节点, 需要遍历此节点之前的所有节点. O(n).
- 有些时候节点还知道前一个结点的地址, 这时我们称链表是双向链表.



用代码表示链表 (Linked List)
public class ListNode {
int val;
ListNode next;
ListNode(int val_) {
val = val_;
next = null; // This line is optional
}
}
public class LinkedList {
ListNode head;
ListNode tail; // Optional
int size; // Optional
}



为什么要使用链表(Linked List)
- Array的resize操作比较耗时, 所以我们需要在使用array之前设置容量(capacity)
- 然而事前我们往往不知道需要多少空间(容量)
- 链表(LinkedList)可以有效地使用内存!



基本操作
- Get: <index> ---> value, O(n)
- Set: <index, value> ---> void (new LinkedList), O(n)
- Add: <opt_index, value> ---> void (new LinkedList), O(n)
- Remove: <index/value> ---> void (new LinkedList), O(n)



基本操作
- Get: <index> ---> value, O(n)
- Set: <index, value> ---> void (new LinkedList), O(n)
- Add: <opt_index, value> ---> void (new LinkedList), O(n)
- Remove: <index/value> ---> void (new LinkedList), O(n)
- offer, poll; push, pop; peek;



Example
- LinkedList<Integer> list = new LinkedList<Integer>();
- 构造函数在初始化一个类的实例时被调用
- list.add(2, 3);



如何实现线性链表
public class LinkedList{
// TODO: implement this class.
public int get(int index);
public void set(int index, int value);
public void add(int index, int value);
public void remove(int index);
public void removeByValue(int value);
}public class ListNode {
int val;
ListNode next;
public ListNode(int val_) {
this.val = val_;
}
}


How to implement LinkedList - Fields
public class LinkedList{
private ListNode head;
// the field "head" will always represent the head of the list,
// when the head of the list is changed, we need to assign the new
// memory to the variable "head".
private ListNode tail;
private int size;
public LinkedList() {
head = null;
tail = null;
size = 0;
}
}Java will automatically initialize default values for fields.



How to implement LinkedList - Fields
public class LinkedList{
private ListNode head;
private ListNode tail;
private int size;
public LinkedList() {
}
}Java will automatically generate a non-param constructor if you don't have other constructors.



How to implement LinkedList - Fields
public class LinkedList{
private ListNode head;
private ListNode tail;
private int size;
}Java will automatically generate a non-param constructor if you don't have other constructors.



How to implement LinkedList - Fields
public class LinkedList{
private ListNode head = null;
private ListNode tail = null;
private int size = 0;
}Recommended !!!



How to implement LinkedList - Get
public class LinkedList{
public void checkBoundsExclusive(int index) {
if (index < 0 || index >= size) {
// throw Exception.
}
}
public ListNode getEntry(int index) {
ListNode cur = head;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur;
}
public int get(int index) {
checkBoundsExclusive(index);
return getEntry(index).val;
}
}public ListNode getEntry(int index) {
ListNode cur = head;
while (index-- != 0) {
cur = cur.next;
}
return cur;
}


How to implement LinkedList - Set
public class LinkedList{
public void set(int index, int value) {
checkBoundsExclusive(index);
Listnode node = getEntry(index);
node.val = value;
}
}


How to implement LinkedList - Add
public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode newNode = new ListNode(value);
ListNode pre = getEntry(index-1);
newNode.next = pre.next;
pre.next = newNode;
}
}


How to implement LinkedList - Add
public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode newNode = new ListNode(value);
if (index == 0) {
newNode.next = head;
head = newNode;
return;
}
ListNode pre = getEntry(index-1);
newNode.next = pre.next;
pre.next = newNode;
}
}


How to implement LinkedList - Add
public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode newNode = new ListNode(value);
if (index == 0) {
newNode.next = head;
head = newNode;
return;
}
ListNode pre = getEntry(index-1);
newNode.next = pre.next;
pre.next = newNode;
}
}head



How to implement LinkedList - Add
public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode newNode = new ListNode(value);
if (index == 0) {
newNode.next = head;
head = newNode;
return;
}
ListNode pre = getEntry(index-1);
newNode.next = pre.next;
pre.next = newNode;
}
}head
newHead



How to implement LinkedList - Add
public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode newNode = new ListNode(value);
if (index == 0) {
newNode.next = head;
head = newNode;
return;
}
ListNode pre = getEntry(index-1);
newNode.next = pre.next;
pre.next = newNode;
}
}head
newHead



How to implement LinkedList - Add
public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode newNode = new ListNode(value);
if (index == 0) {
newNode.next = head;
head = newNode;
return;
}
ListNode pre = getEntry(index-1);
newNode.next = pre.next;
pre.next = newNode;
}
}head
newHead



How to implement LinkedList - Remove
public class LinkedList{
public void remove(int index) {
checkBoundsExclusive(index);
size--;
ListNode pre = getEntry(index-1);
pre.next = pre.next.next;
}
}


How to implement LinkedList - Remove
public class LinkedList{
public void remove(int index) {
checkBoundsExclusive(index);
size--;
if (index == 0) {
head = head.next;
return;
}
ListNode pre = getEntry(index-1);
pre.next = pre.next.next;
}
}head



How to implement LinkedList - Remove
public class LinkedList{
public void remove(int index) {
checkBoundsExclusive(index);
size--;
if (index == 0) {
head = head.next;
return;
}
ListNode pre = getEntry(index-1);
pre.next = pre.next.next;
}
}head



How to implement LinkedList - Remove
public class LinkedList{
public void remove(int index) {
checkBoundsExclusive(index);
size--;
if (index == 0) {
head = head.next;
return;
}
ListNode pre = getEntry(index-1);
pre.next = pre.next.next;
}
}head



为什么需要特殊处理
因为头节点有不同之处



null
没有节点指向头节点!!!
head
头节点的不同之处



null
There is no node pointing at head!!!
?
head
Head Node is Different



null
head
fake head
Head Node is Different



null
head
dummy
- dummy.next can represent original linked list.
- head node will have a previous node.
- head node is NOT different any more.
Head Node is Different



如何实现线性链表 - Add
public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while (index-- != 0) {
pre = pre.next;
}
ListNode newNode = new ListNode(value);
newNode.next = pre.next;
pre.next = newNode;
head = dummy.next;
}
}


如何实现线性链表 - Add
public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while (index-- != 0) {
pre = pre.next;
}
ListNode newNode = new ListNode(value);
newNode.next = pre.next;
pre.next = newNode;
head = dummy.next;
}
}ListNode pre = getEntry(index-1);



如何实现线性链表 - Add
public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while (index-- != 0) {
pre = pre.next;
}
ListNode newNode = new ListNode(value);
newNode.next = pre.next;
pre.next = newNode;
head = dummy.next;
}
}ListNode pre = head;
index = index - 1;
while (index-- != 0) {
pre = pre.next;
}



如何实现线性链表 - Add
public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while (index-- != 0) {
pre = pre.next;
}
ListNode newNode = new ListNode(value);
newNode.next = pre.next;
pre.next = newNode;
head = dummy.next;
}
}public class LinkedList{
public void add(int index, int value) {
checkBoundsExclusive(index);
size++;
ListNode newNode = new ListNode(value);
if (index == 0) {
newNode.next = head;
head = newNode;
return;
}
ListNode pre = head;
index = index - 1;
while (index-- != 0) {
pre = pre.next;
}
newNode.next = pre.next;
pre.next = newNode;
}
}


哑元节点(Dummy Node)
- 使得头节点和其他节点一样, 不在特殊.
- 简化边界情况.
- 让代码更简短.
- 越短越好.
- 代码越少出错越少.
- 让代码更简短.
- 链表至少含有一个节点(哑元节点), 链表内容从哑元节点所指向的结点开始.



如何实现线性链表 - Remove
public class LinkedList{
public void remove(int index) {
checkBoundsExclusive(index);
size--;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while (index-- != 0) {
pre = pre.next;
}
pre.next = pre.next.next;
head = dummy.next;
}
}


线性链表(Linked List)总结
- Get



Summary for Linked List
- Get
- Add



Summary for Linked List
- Get
- Add
- Remove



- 如果要修改链表的内容, 需要哑元节点.
- add, remove.
- 每个节点只能被它的前一个结点访问.
- 在当前节点没有赋给其他新的节点之前, 不要修改它的前一个结点的next指针, 除非当前节点不再需要了.
线性链表(Linked List)总结



面试中的链表问题
- 大多情况下, 只会给出头节点.
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
public class Solution {
public ListNode reverseList(ListNode head) {
}
}


面试中的链表问题
- 多数情况下, 只会给出头节点.
- 没有size字段.
- 使用while循环判断是否为空.
- while (cur != null) { // do something }
- while (pre.next != null) { // do something }
- 开始下代码之前需要沟通清楚 !!!
- size, index range, tail, etc.



练习
public class ListNode {
int val;
ListNode next;
public ListNode(int val) {
this.val = val;
}
}



- 链表长度: Linked list length.
- 链表倒数第K的节点: Kth node from the end.
- 链表的中间节点: Mid node of the list.
- 链表是否存在环: Whether circle exists.
计数相关的问题



Linked List Length
public int length(ListNode head) {
ListNode cur = head;
int length = 0;
while (cur != null) {
length++;
cur = cur.next;
}
return length;
}Given a linked list, return the length of it.
Examples:
Input: 1->4->2->3
Output: 4



Kth node from the end
Given a linked list, return the kth node from the end. Linked list will never be empty and k will always be valid.
Examples:
Input: 1->4->2->3, 2
Output: 2
Input: 3->5->9->6->8, 3
Output: 9
public ListNode kthNodeFromEnd(
ListNode head, int k) {
int length = length(head);
int index = length - k;
ListNode cur = head;
int count = 0;
while (count < index) {
cur = cur.next;
count++;
}
return cur;
}


Kth node from the end
public ListNode kthNodeFromEnd(
ListNode head, int k) {
int length = length(head);
int index = length - k;
ListNode cur = head;
while (index-- != 0) {
cur = cur.next;
}
return cur;
}Given a linked list, return the kth node from the end. Linked list will never be empty and k will always be valid.
Examples:
Input: 1->4->2->3, 2
Output: 2
Input: 3->5->9->6->8, 3
Output: 9



Kth node from the end
public ListNode kthNodeFromEnd(
ListNode head, int k) {
ListNode first = head;
while (k-- != 0) {
first = first.next;
}
ListNode second = head;
while (first != null) {
first = first.next;
second = second.next;
}
return second;
}Given a linked list, return the kth node from the end. Linked list will never be empty and k will always be valid.
Examples:
Input: 1->4->2->3, 2
Output: 2
Input: 3->5->9->6->8, 3
Output: 9



Middle Node
public ListNode midNode(ListNode head) {
int length = length(head);
int index = (length - 1) / 2;
ListNode cur = head;
while (index-- != 0) {
cur = cur.next;
}
return cur;
}Given a linked list, return the middle node. Linked list will never be empty.
Examples:
Input: 1->4->2->3
Output: 4
Input: 3->5->9->6->8
Output: 9



Middle Node
public ListNode midNode(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast.next != null &&
fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}Given a linked list, return the middle node. Linked list will never be empty.
Examples:
Input: 1->4->2->3
Output: 4
Input: 3->5->9->6->8
Output: 9



Linked List Cycle
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
ListNode fast = head;
ListNode slow = head;
while (fast != null) {
if (fast.next == null) {
return false;
}
if (fast.next == slow) {
return true;
}
fast = fast.next.next;
slow = slow.next;
}
return false;
}Given a linked list, define if there is a cycle in it.
Examples:
Input: 1->4->2->3
Output: false.
Input: 3->5->9->3(original)
Output: true



Linked List Cycle - Follow up
Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3



Linked List Cycle - Follow up
Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3



Linked List Cycle - Follow up
Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3



Linked List Cycle - Follow up
Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3



Linked List Cycle - Follow up
Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3



Linked List Cycle - Follow up
Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3



Linked List Cycle - Follow up
Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3



Linked List Cycle - Follow up
Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3



Linked List Cycle - Follow up
Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3



Linked List Cycle - Follow up
Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3
l
l1
l2
2 *(l+l1) = l + l1 + ck
=> l1 + l = ck = (c-1)k + k
=> l = (c-1)k + (k - l1)
=> l = (c-1)k + l2



Linked List Cycle - Follow up
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
while (fast != null && slow != null) {
if (fast.next != null) {
fast = fast.next.next;
} else {
return null;
}
slow = slow.next;
if (fast == slow) {
ListNode temp = head;
while (temp != slow) {
temp = temp.next;
slow = slow.next;
}
return slow;
}
}
return null;
}Given a linked list, return the node where the cycle begins or null.
Examples:
Input: 1->4->2->3
Output: null.
Input: 3->5->9->3(original)
Output: 3



Exercise Summary 1.
- head == null?
- k ? compare(k, length) && compare(k, 0) && range(k)
- Null Pointer Exception
- while (node != null)
- node.next
node.next.next
- while (node != null)
-
Test Cases



结构(Structure)相关的问题
- 删除有序链表中重复的节点: Remove Duplicate from Sorted List
- 转置链表: Reverse Linked List
- 成对交换节点: Swap Nodes in Pairs
- 合并链表: Merge Sorted List



Remove Duplicate
public ListNode removeDuplicates(
ListNode head) {
if (head == null) {
return null;
}
ListNode pre = head;
while (pre.next != null) {
if (pre.val == pre.next.val) {
pre.next = pre.next.next;
} else {
pre = pre.next;
}
}
return head;
}Given a sorted linked list, remove all duplicates such that each element appear only once.
Examples:
Input: 1->2->2->2->3->3
Output: 1->2->3



Remove Duplicate - Follow up
public ListNode removeDuplicates(ListNode head) {
if (head == null) {
return null;
}
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while (pre.next != null && pre.next.next != null) {
if (pre.next.val == pre.next.next.val) {
int lastVal = pre.next.val;
while (pre.next != null &&
pre.next.val == lastVal) {
pre.next = pre.next.next;
}
} else {
pre = pre.next;
}
}
return dummy.next;
}Given a sorted linked list, remove all nodes that have duplicate numbers leaving only the distinct numbers from the original list.
Examples:
Input: 1->2->2->2->3->3
Output: 1
Input: 3->4->4->5->6
Output: 3->5->6



Reverse Linked List
Reverse a given linked list.
Examples:
Input: 1->2->3->4
Output: 4->3->2->1
public ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}


Swap Nodes in Pairs
Given a linked list, swap every two adjacent nodes and return its head.
Examples:
Input: 1->2->3->4
Output: 2->1->4->3
Note: Your algorithm should use only constant space. You may not modify the values in the list, only nodes itself can be changed.
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while (pre.next != null && pre.next.next != null) {
ListNode first = pre.next, second = pre.next.next;
first.next = second.next;
second.next = first;
pre.next = second;
pre = first;
}
return dummy.next;
}


Merge Sorted List
Merge two sorted list and return it as a new list.
Examples:
Input: 1->2->2->2, -1->3->4->5
Output: -1->1->2->2->2->3->4->5
public ListNode merge(ListNode head1, ListNode head2) {
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while (head1 != null && head2 != null) {
if (head1.val < head2.val) {
cur.next = new ListNode(head1.val);
head1 = head1.next;
} else {
cur.next = new ListNode(head2.val);
head2 = head2.next;
}
cur = cur.next;
}
while (head2 != null) {
cur.next = new ListNode(head2.val);
head2 = head2.next;
cur = cur.next;
}
while (head1 != null) {
cur.next = new ListNode(head1.val);
head1 = head1.next;
cur = cur.next;
}
return dummy.next;
}


Merge Sorted List
Merge two sorted list and return it as a new list.
Examples:
Input: 1->2->2->2, -1->3->4->5
Output: -1->1->2->2->2->3->4->5
public ListNode merge(ListNode head1, ListNode head2) {
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while (head1 != null && head2 != null) {
if (head1.val < head2.val) {
cur.next = head1;
head1 = head1.next;
} else {
cur.next = head2;
head2 = head2.next;
}
cur = cur.next;
}
if (head1 == null) {
cur.next = head2;
} else if (head2 == null) {
cur.next = head1;
}
return dummy.next;
}


Exercise Summary 2.
- dummy.
- Structure (including head) will be changed.
- Null Pointer Exception
- while (node != null)
- node.next
- while (pre.next != null)
- pre.next, pre.next.next
- while (node != null)
-
Test Cases



s
删除链表中的指定节点. (只知道指定节点的引用).
Examples:
Input: 1->2->3->4, 3
Output: 1->2->4
public void removeNode(ListNode node) {
if (node == null) {
return;
}
if (node.next == null) {
node = null;
return;
}
node.val = node.next.val;
node.next = node.next.next;
return;
}


Delete Node in a Linked List
This is just a workaround, but doesn't work for the last node.
public void removeNode(ListNode node) {
if (node == null) {
return;
}
if (node.next == null) {
node = null;
return;
}
node.val = node.next.val;
node.next = node.next.next;
return;
}node
oriNode
pre
oriNode
instance
oriNode
next
next.next



Delete Node in a Linked List
This is just a workaround, but doesn't work for the last node.
public void removeNode(ListNode node) {
if (node == null) {
return;
}
if (node.next == null) {
node = null;
return;
}
node.val = node.next.val;
node.next = node.next.next;
return;
}node
oriNode
pre
oriNode
instance
oriNode
next
next.next
update value



Delete Node in a Linked List
This is just a workaround, but doesn't work for the last node.
public void removeNode(ListNode node) {
if (node == null) {
return;
}
if (node.next == null) {
node = null;
return;
}
node.val = node.next.val;
node.next = node.next.next;
return;
}node
oriNode
pre
oriNode
instance
oriNode
null



Delete Node in a Linked List
This is just a workaround, but doesn't work for the last node.
public void removeNode(ListNode node) {
if (node == null) {
return;
}
if (node.next == null) {
node = null;
return;
}
node.val = node.next.val;
node.next = node.next.next;
return;
}null
oriNode
pre
oriNode
instance
oriNode
null



总结
- 哑元节点
- 不要丢失节点
- 在修改当前结点之前, 先要它保存后面的内容.
- 练习
- 边界情况



Homework



Homework (Optional)



基础班 05 Linked List CN
By ZhiTongGuiGu
基础班 05 Linked List CN
LinkedList, Charlie
- 347