2. Linked List(with example problems)

邹学民
2023-12-01

1. Tools: Dummy Node

When to use:

  1. When it related to the head node, and the head node is uncertain.

Tips:
a. If a node->next is effected, then we need to correct this pointer.
b. If the node to be deleted is dynamically allocated memory, we need to reallocate these memory.

Code:

ListNode* dummy = new ListNode(0);
dummy->next = head;

Example:

  1. Given a linked list and a value x, write a function to reorder this list such that all nodes less than x come before the nodes greater than or equal to x.
    1. Create two dummy nodes, and divide list into two linked list. Finally, append larger one behind the smaller one.

2. Tools: Chaser-Runner

When to use:

  1. When we need to find the specific position of the list, e.g. the middle node.

Code:

ListNode* chaser= head, * runner = head;
while(chaser->next && runner->next->next){
	chaser = chaser->next;
	runner = runner->next->next;
}

Example:

  1. Find the middle point of linked list.
    1. Runner use the speed that twice faster than the chaser. When the runner reach the end, the chaser is at the middle node.
  2. Find the kth to last element of a singly linked list
    1. Runner is k position faster than chacser. When the runner reach the end, the chaser is at the last kth node.
  3. Circular Linked List Problem, see below.

3. Tools: Doubly Linked List in C++

Doubly Linked List in C++ is std::list< T>
Frequent iterator: begin(), end(), rbegin(), rend()
Frequent functions: empty(), size(), push_back(T value), pop_back(T value), erase(iterator pos), insert(iterator pos, T value).

4. Problem Type:

4.1. Operations in Iterating List

When iterating the linked list, each time only process one/pair nodes. The core node is the current node, or else it’s easily cause duplicate-process problem.

4.1.1. Examples:

372. Delete Node in a Linked List

    void deleteNode(ListNode * node) {
        // write your code here
        if (node) {
            node->val = node->next->val;
            node->next = node->next->next;
        }
    }

450. Reverse Nodes in k-Group

class Solution {
public:
    /**
     * @param head: a ListNode
     * @param k: An integer
     * @return: a ListNode
     */
    ListNode * reverseKGroup(ListNode * head, int k) {
        // write your code here
        
        // There are two main parts: repeated reverse Linked list, and the size would be k
        
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        head = dummy;
        
        while(head->next){
            head = reverseNextK(head, k);
        }
        
        return dummy->next;
    }
    
    ListNode* reverseNextK(ListNode* head, int k){
        // check if there is k nodes to be reversed
        ListNode* nk = head;
        for(int i = 0; i < k; i++){
            if(!nk->next){
                return nk;
            }
            nk = nk->next;
        }
        
        //Before: head->n1->n2->..->nk->..
        //After : head->nk->nk-1->..->n1->..
        ListNode* n1 = head->next;
        ListNode* curr = n1, *next = curr->next;
        for(int i = 0; i< k-1; i++){
            if(!next){
                n1->next = nullptr;
                break;
            }
            ListNode* tmp = next->next;
            next->next = curr;
            curr = next;
            next = tmp;
        }
        head->next = curr;
        n1->next = next;
        return n1;
        
        
    }
};

96. Partition List

ListNode * partition(ListNode * head, int x) {
        // write your code here
        ListNode* smaller = new ListNode(0);
        ListNode* larger = new ListNode(0);
        ListNode* n1 = smaller, *n2 = larger;
        
        while(head){
            ListNode* nxt = head ->next;
            head ->next = nullptr;
            
            if(head ->val < x){
                n1 ->next = head;
                n1 = n1 ->next;
            } else {
                n2 ->next = head;
                n2 = n2 ->next;
            }
            head = nxt;
        }
        
        n1 ->next = larger ->next;
        
        return smaller ->next;
    }

4.2. Swap Nodes

If we want to swap two nodes(except deleting), their previous nodes and next nodes will be effected. However, under any circumstance, we can always do this without error:
a. Swap the value of both prvious_node->next
b. Swap the value of both node->next

4.2.1. Examples:

511. Swap Two Nodes in Linked List

As it’snot allowed to swap the value directly, we need to record the prev/post pointer of two value nodes.

One thing need to be mentioned is the corner cases:

  1. What if node not exist
  2. What if two nodes are previous-next relation.
    ListNode * swapNodes(ListNode * head, int v1, int v2) {
        // write your code here
        if(!head)
            return head;
        ListNode* dummy = new ListNode(0);
        dummy ->next = head;

        ListNode *prev_v1 = dummy, *v1_ = dummy ->next, *post_v1;
        while(v1_ && v1_ ->val != v1) {
            post_v1 = v1_ ->next;
            
            prev_v1 = v1_;
            v1_ = post_v1;
        }
        post_v1 = v1_ ? v1_ ->next : nullptr;
        
        ListNode *prev_v2 = dummy, *v2_ = dummy ->next, *post_v2;
        while(v2_ && v2_ ->val != v2) {

            post_v2 = v2_ ->next;
            prev_v2 = v2_;
            v2_ = post_v2;
        }
        post_v2 = v2_ ? v2_ ->next : nullptr;
        
        if(! v1_ || !v2_)
            return dummy ->next;;
            
        if (post_v1 == v2_) {
            prev_v1 ->next  = v2_;
            v2_ ->next = v1_;
            v1_ ->next = post_v2;
            return dummy ->next;
        }
        
        if (prev_v1 == v2_) {
            prev_v2 ->next = v1_;
            v1_ ->next = v2_;
            v2_ ->next = post_v1;
            return dummy ->next;
        }
        
        prev_v1 ->next = v2_;
        prev_v2 ->next = v1_;
        
        v1_ ->next = post_v2;
        v2_ ->next = post_v1;
        
        return dummy ->next;
        
    }

36. Reverse Linked List II
For the special position, such as the previous node of M and post node of N, we need extra pointer to keep its position, in case of mistakes.

ListNode * reverseBetween(ListNode * head, int m, int n) {
        // write your code here
        if(m >= n || !head)
            return head;
            
        ListNode *dummy = new ListNode(0);
        dummy ->next = head;
        head = dummy;
        
        for(int i = 0; i < m -1; i++){
            if( !head)
                return nullptr;
                
            head = head ->next;
        }
        
        ListNode *premNode = head;
        ListNode *mNode = head ->next;
        ListNode *nNode = mNode, *postnNode = mNode ->next;
        for(int i = m; i < n; i++) {
            if(! postnNode )
                return nullptr;
            
            ListNode *tmp = postnNode ->next;
            postnNode ->next = nNode;
            nNode = postnNode;
            postnNode = tmp;
        }
        
        mNode ->next = postnNode;
        premNode ->next = nNode;
        
        return dummy ->next;
    }

4.3. Handling Two Node Simultaneously

When we handle two lists at the same time ,we could user while(l1 && l2), and then handle the non-nullptr list.

4.3.1. Examples:

165. Merge Two Sorted Lists

    ListNode * mergeTwoLists(ListNode * l1, ListNode * l2) {
        // write your code here
        ListNode* dummy = new ListNode(0);
        ListNode *ptr = dummy;
        while(l1 && l2){
            if(l1->val <= l2 ->val){
                ptr ->next = l1;
                l1 = l1 ->next;
            } else {
                ptr ->next = l2;
                l2 = l2 ->next;
            }
            ptr = ptr ->next;
        }
        
        while(l1)
        {
            ptr ->next = l1;
            l1 = l1 ->next;
            ptr = ptr ->next;
        }
        
        while(l2)
        {
            ptr ->next = l2;
            l2 = l2 ->next;
            ptr = ptr ->next;
        }
        
        return dummy ->next;
    }

99. Reorder List

  1. Find the mid node using chaser-runner method
  2. Reverse the second part of the list
  3. Alternatively Merge two Lists.
    void reorderList(ListNode * head) {
        // write your code here
        if(! head)
            return;
        
        ListNode *runner = head, *chaser = head;
        while(runner ->next && runner ->next ->next) {
            runner = runner ->next ->next;
            chaser = chaser ->next;
        }
        
        if(runner == chaser)
            return;
        runner = chaser ->next; 
        chaser ->next = nullptr;
        
        // reverse the latter part of the ListNode
        ListNode * prev, * ptr, * nxt;
        prev = runner;
        ptr = prev ->next;
        prev ->next = nullptr;
        while(ptr) {
            nxt = ptr ->next;
            
            ptr ->next = prev;
            prev = ptr;
            ptr = nxt;
        }
        ptr = prev;
        
        ListNode* dummy = new ListNode(0);
        ListNode *node = dummy;
        
        bool isAlter = false;
        while(head && ptr) {
            if(!isAlter) {
                node ->next = head;
                head = head ->next;
                node = node ->next;
                isAlter = !isAlter;
            } else {
                isAlter = !isAlter;
                node ->next = ptr;
                ptr = ptr ->next;
                node = node ->next;
            }
        }
        if(head)
            node ->next = head;
        
        if(ptr)
            node ->next = ptr;
            
        head = dummy ->next;
    }

170. Rotate List

ListNode * rotateRight(ListNode * head, int k) {
        // write your code here
        
        if( ! head)
            return head;
            
        int len = 0;
        ListNode *ptr = head;
        while(ptr ) {
            len ++;
            ptr = ptr ->next;
        }
        k = k % len;
        
        if(k == 0)
            return head;
        
        ListNode *fast = head;
        for(int i = 0; i < k; i ++) {
            fast = fast ->next;
        }
        
        ListNode *slow = head;
        while(fast ->next) {
            slow = slow ->next;
            fast = fast ->next;
        }
        
        fast ->next = head;
        head = slow ->next;
        slow ->next = nullptr;
        
        return head;
        
    }

4.4. Recursion Problem

If the process of the current node is based on the next node, which is reversely traverse problem, we could use recursion, or stack.

4.4.1. Examples:

  1. Traverse the linked list reversely.
void traverse(ListNode* head)
{
	if(head == nullptr)
		return;
	traverse(head->next);
	visit(head);
}

106. Convert Sorted List to Binary Search Tree

class Solution {
public:
    /**
     * @param head: The first node of linked list.
     * @return: a tree node
     */
    TreeNode *sortedListToBST(ListNode *head) {
        if (head == NULL) {
            return NULL;
        }
        int size = getListSize(head);
        return convertHelper(head, size);
    }
    
    int getListSize(ListNode *head) {
        int size = 0;
        while (head != NULL) {
            head = head->next;
            size++;
        }
        return size;
    }
    
    TreeNode *convertHelper(ListNode *&head, int size) {
        if (size == 0) {
            return NULL;
        }
        
        TreeNode *root = new TreeNode(0);
        root->left = convertHelper(head, size / 2);
        root->val = head->val; head = head->next;
        root->right = convertHelper(head, size - size / 2 - 1);
        return root;
    }
};

4.5. Hash Map

To deep copy a list, we usually use hash map to relate the new node and old node.

4.5.1. Examples:

105. Copy List with Random Pointer

    RandomListNode *copyRandomList(RandomListNode *head) {
        // write your code here
        if(! head)
            return head;
            
        unordered_map<RandomListNode*, RandomListNode*> hash;
        RandomListNode *dummy = new RandomListNode(0);
        RandomListNode *ptr = dummy, *curt = head;
        
        while(curt) {
            RandomListNode *new_node = new RandomListNode(curt ->label);
            hash[curt] = new_node;
            
            ptr ->next = new_node;
            
            curt = curt ->next;
            ptr = ptr ->next;
        }
        
        curt = head;
        while(curt) {
            hash[curt] ->random = curt->random? hash[curt ->random] : nullptr;
            curt = curt ->next;
        }
        
        return dummy ->next;
    }

Problem Discuss:

1. Circular Linked List

Examples:

  1. Given a linked list, determine if it has a cycle in it.

Based on the runner-chaser technique, let the runner use twice speed of the chaser. If there exists circle, the runner and chaser will meet at some position.

102. Linked List Cycle

    bool hasCycle(ListNode * head) {
        // write your code here
        if (!head || !head ->next)
            return false;
            
        ListNode *slow = head, *fast = head ->next;
        while(slow != fast) {
            if (!fast || ! (fast ->next) )
                return false;
                
            slow = slow ->next;
            fast = fast ->next ->next;
        }
        
        return true;
    }
  1. Given a circular linked list, return the node at the beginning of the loop.

Based on previous problem, let the runner use twice speed of the chaser. When they are first time met, move the chaser from the met position to the head, the second time they met is the beginning of the circle.

103. Linked List Cycle II

    ListNode * detectCycle(ListNode * head) {
        // write your code here
        
        if(!head || ! head ->next)
            return nullptr;
            
        ListNode *slow = head, *fast = head ->next;
        while(slow != fast) {
            if( !fast || ! fast ->next)
                return nullptr;
                
            slow = slow ->next;
            fast = fast ->next ->next;
        }
        

        while(head != slow ->next) {
            slow = slow ->next;
            head = head ->next;
        }
        
        return head;
    }

2. Intersection of Two Singly Linked List

To determine whether exists the intersection of two linked list, we need to check whether two lists have circles.
1. If one have circle while the other don’t, they is no intersection.
2. If neither has circle, check if their end nodes are the same.
3. If both has circles, find the Z point(intersection of the circle) of a list, and check if this Z point is in the other list.

How to find the first intersection of two lists?
Get the length of two lists and the gap of two lists, and then, use chaser-runner. Let runner is gap-size faster than the chaser. The first place they met is the first intersection they have.

 类似资料: