1, LeetCode 21. Merge two ordered linked lists
Method 1: iteration
Code and performance
class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(-1); ListNode p = dummy; ListNode p1 = l1; ListNode p2 = l2; while(p1 != null && p2 != null){ if(p1.val < p2.val){ p.next = p1; p1 = p1.next; }else{ p.next = p2; p2 = p2.next; } p = p.next; } p.next = p1 == null ? p2 : p1; return dummy.next; } }
Time complexity: O(m+n)
Space complexity: O(1)
Development: if de duplication is required
In the case of l1.val == l2.val, only one is output, and then two linked lists go down at the same time.
Input: l1 = [1,2,4], l2 = [1,3,4] Output:[1,2,3,4]
code:
class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(-1); ListNode p = dummy; ListNode p1 = l1; ListNode p2 = l2; while(p1 != null && p2 != null){ if(p1.val < p2.val){ p.next = p1; p1 = p1.next; }else if(p1.val == p2.val){ p.next = p1; p1 = p1.next; p2 = p2.next; }else{ p.next = p2; p2 = p2.next; } p = p.next; } p.next = p1 == null ? p2 : p1; return dummy.next; } }
Method 2: recursion
Code and performance
class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { if(list1 == null) return list2; if(list2 == null) return list1; if(list1.val < list2.val){ list1.next = mergeTwoLists(list1.next, list2); return list1; }else{ list2.next = mergeTwoLists(list1, list2.next); return list2; } } }
**Time complexity: * * each recursive operation: remove the head node of l1 or l2 (until at least one linked list is empty), and the function mergeTwoList will call each node recursively at most once. Therefore, the time complexity depends on the length of the combined linked list, i.e. O(n+m).
**Space complexity: * * at the end of recursive call, the mergeTwoLists function can be called at most n+m times. Recursion needs to consume stack space, so the space complexity is O(n+m).
To reprint code
Follow the same idea and change the operation when l1.val == l2.val.
class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { if(list1 == null) return list2; if(list2 == null) return list1; if(list1.val < list2.val){ list1.next = mergeTwoLists(list1.next, list2); return list1; }else if(list1.val == list2.val){ list1.next = mergeTwoLists(list1.next, list2.next); return list1; }else{ list2.next = mergeTwoLists(list1, list2.next); return list2; } } }
2, LeetCode 23. Merge K ascending linked lists
Method 1: Merge
1. Recursive version
The atomic operation of "merging two ordered linked lists" is the same as merging and sorting.
Note the merged base case: if(left == right) return lists[left];, It can not be written as left < = right.
Because different from bisection, the inter partition operations of merging are left = mid + 1 and right = mid, and there is no right = mid - 1; Because mid = left + (right - left) / 2 is rounded down, the case of left > right will not occur.
class Solution { public ListNode mergeKLists(ListNode[] lists) { int n = lists.length; if(n == 0) return null; if(n == 1) return lists[0]; return merge(lists, 0, n-1); } ListNode merge(ListNode[] lists, int left, int right){ if(left == right) return lists[left]; int mid = left + (right - left) / 2; ListNode l1 = merge(lists, left, mid); ListNode l2 = merge(lists, mid+1, right); return mergeTwoLists(l1, l2); } //The mergeTwoLists method code directly copies the previous question ListNode mergeTwoLists(ListNode l1, ListNode l2); }
Time complexity:
Merging and sorting is an analytical idea. Let the maximum length of the linked list be n. The first time K/2 groups are merged, each group takes 2n, and the second time K/4 groups are merged, each group takes 4n... There are logK times in total, and each time takes Kn, so the time complexity is O(nK*logK). Where n is the maximum length of the linked list and K is the number of linked list entries.
Space complexity: mainly recursive stack space, O(logK). (Note: it is better to use the iterative mergeTwoLists method, which will not occupy additional stack space)
If de duplication is required, just change the mergeTwoLists method to de duplication.
2. Iterative version
The implementation idea of iteration is similar to merge sorting, but the specific implementation is very different.
After the first traversal, the combined results of K/2 groups of linked lists are stored in order in the first half of the linked list array.
This is the case for each cycle after that. See the following code for details:
class Solution { public ListNode mergeKLists(ListNode[] lists) { int k = lists.length; if(k == 0) return null; if(k == 1) return lists[0]; while(k > 1){ //For the pointer following i, i walk twice and newRight walk once //Why is it called because it will become a new right boundary (open, not closed) at the end of the for loop int newRight = 0; for(int i=0; i<k; i+=2){ //If K is an odd number, lists[k-1] will be left alone and saved directly without merging if(i == k-1){ lists[newRight++] = lists[i]; }else{ lists[newRight++] = mergeTwoLists(lists[i],lists[i+1]); } } //Assign newRight to K to make k a new right boundary (on) k = newRight; } //Finally, the first element of the linked list array lists is the merged array return lists[0]; } //The mergeTwoLists method code directly copies the previous question ListNode mergeTwoLists(ListNode l1, ListNode l2); }
Time complexity:
It is exactly the same as the recursive version, O(nKlogK).
Space complexity: mainly recursive stack space, O(1). (Note: it is better to use the iterative mergeTwoLists method, which will not occupy additional stack space)
If de duplication is required, just change the mergeTwoLists method to de duplication.
Method 2: priority queue
1. Implement priority queue by yourself
The merging operation is similar to the previous question.
The MinPQ class in the problem solution implements a small top heap.
If it is only used in this problem, the MinPQ class can have no comparator < k > attribute. Just implement the more() method by directly comparing the val attribute of the ListNode. However, in order to make MinPQ more general, I chose to imitate the PriorityQueue of JDK. The commonality of MinPQ is reflected in that its constructor parameters have a comparator < k > comparator, and comparator < T > is a typical functional interface( See this article ). Therefore, when constructing a MinPQ, you can pass in a Lambda expression to replace the comparator < k > comparator in the parameter list. You can customize the Lambda expression at will to change the comparison mechanism of the more() function. For example, in the code of this problem, the Lambda expression is as follows: (node1, node2) - > (node1.val - node2. VAL).
MinPQ does not write the growth () method and cannot be expanded. However, this is not necessary in this problem.
swim, sink, insert and delMin are the four methods. The code is implemented from the official account labuladong, which is "the two fork heap and the priority queue".
In addition, if the heap is stored from queue[0], the left, right and parent methods need to be changed to:
private int parent(int k){ return (k-1) / 2; } private int left(int k){ return 2*k + 1; } private int right(int k){ return 2*k + 2; }
The following is the complete solution:
class Solution { public ListNode mergeKLists(ListNode[] lists) { if(lists.length == 0) return null; ListNode dummy = new ListNode(-1); ListNode p = dummy; MinPQ<ListNode> mp = new MinPQ<>( lists.length, (node1, node2)->(node1.val - node2.val)); //Put the head nodes of k linked lists into the minimum heap for(ListNode head : lists){ if(head != null) mp.insert(head); } while(!mp.isEmpty()){ //Find and connect the next node ListNode node = mp.delMin(); p.next = node; //Move pointer if(node.next != null) mp.insert(node.next); //The p pointer keeps moving forward p = p.next; } return dummy.next; } } //Priority queue. When inserting or deleting elements, it will be sorted automatically, and the head of the queue will become the largest element //The bottom layer is a small top pile //Small top heap: a complete binary tree, and the value of each node is less than or equal to the value of its left and right child nodes class MinPQ<K> { //An array of storage elements private K[] queue; //Number of elements in the current heap private int n; //Maximum heap capacity private int capacity; //Comparator: typical functional interface private Comparator<K> comparator; //constructor public MinPQ(int cap, Comparator<K> comparator){ //Index 0 is not used, so it is cap+1 this.queue = (K[]) new Object[cap + 1]; this.n = 0; this.capacity = cap; this.comparator = comparator; } //Returns the smallest element in the heap public K min(){ return queue[1]; } public boolean isEmpty(){ return n == 0; } public void insert(K e){ //If it is full, it cannot be added if(n >= capacity) return; n++; queue[n] = e; swim(n); } public K delMin(){ //If the heap is empty, it cannot be deleted and null is returned if(n == 0) return null; //Change the smallest element to the last exch(1,n); //Save the value of the original minimum element and delete it K min = queue[n]; queue[n] = null; n--; sink(1); return min; } private void swim(int k){ while(k > 1 && more(parent(k),k)){ exch(k, parent(k)); k = parent(k); } } private void sink(int k){ while(left(k) <= n){ int smaller = left(k); if(right(k) <= n && more(left(k),right(k))) smaller = right(k); if(more(smaller,k)) break; exch(k, smaller); k = smaller; } } //Swap two elements in pq array private void exch(int i, int j){ K temp = queue[i]; queue[i] = queue[j]; queue[j] = temp; } //Judge whether pq[i] is greater than pq[j] private boolean more(int i, int j){ //Cannot use ">" directly return comparator.compare(queue[i],queue[j]) > 0; } //Calculate the index values of the parent node and the left and right child nodes private int parent(int k){ return k / 2; } private int left(int k){ return 2*k; } private int right(int k){ return 2*k + 1; } }
2. Use the PriorityQueue of JDK
class Solution { public ListNode mergeKLists(ListNode[] lists) { if(lists.length == 0) return null; ListNode dummy = new ListNode(-1); ListNode p = dummy; PriorityQueue<ListNode> pq = new PriorityQueue<>( lists.length, (node1, node2)->(node1.val - node2.val)); //Put the head nodes of k linked lists into the minimum heap for(ListNode head : lists){ if(head != null) pq.add(head); } while(!pq.isEmpty()){ //Find and connect the next node ListNode node = pq.poll(); p.next = node; //Move pointer if(node.next != null) pq.add(node.next); //The p pointer keeps moving forward p = p.next; } return dummy.next; } }
3. Performance
Time complexity: the maximum number of elements in the priority queue is K (number of linked list entries), so the time complexity of a delMin or insert is O(logK). All linked list nodes will be added to and popped out of the priority queue. This part takes O(NlogK) in total. K is the number of linked list nodes and N is the total number of linked list nodes. Other operations, such as pointer movement, are not as time-consuming as join and pop-up operations, so the overall time complexity is O(NlogK).
Space: store one more priority queue, O(K).
Of course, this is not very convenient for de duplication. If de duplication, you need to change the heap mechanism.