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
  • ​​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
  • ​​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)