[Leetcode] 21 & 23 & 56 & 57: Intervals
Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.
Example:
Input: 1->2->4, 1->3->4 Output: 1->1->2->3->4->4
题目理解:把两个排好序的linkedlist merge起来,然后返回merge好的linkedlist的头节点。
关于linkedlist的题目一般都要准备好双指针,然后通过双指针的“速度”“方向”“遍历”来完成.这道题也是需要使用双指针:
题目给了一个很关键的提示:两个linkedlist都是排好序的,那么我们可以得到以下的结论:
1. let s1 = head of list 1, s2 = head of list 2
2. if s1 < s2 and s1 -> next < s2, then there does not exist any element e such that s1 < e < s2.
因为我们只有两个linkedlist,所以这个结论我们可以很容易通过contradiction来证明。
在实施起来也非常容易. 这里需要使用一个dummy 和curr节点分别在每次遍历的时候更新和用作返回值。
解法1:
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) 12 { 13 ListNode *dummy = new ListNode(-1), *curr = dummy; 14 while (l1 && l2) { 15 if (l1->val < l2->val) { 16 dummy->next = l1; 17 l1 = l1->next; 18 } else { // if l1 and l2 are equal, then choose l2 (you can also choose l1) 19 dummy->next = l2; 20 l2 = l2->next; 21 } 22 dummy = dummy->next; 23 } 24 if (l1) 25 dummy->next = l1; 26 else if (l2) 27 dummy->next = l2; 28 return curr->next; 29 } 30 };
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
Example:
Input: [ 1->4->5, 1->3->4, 2->6 ] Output: 1->1->2->3->4->4->5->6
题目理解:与上一道题不同的是这次是k个linkedlist,所以最简单能想到的方法便是继续套用两个的做法:
1. merge list i with j, where j != i and j is in the range of k - i
2. when finish list i, merge i + 1 until i == k
这个做法其实就是把每两个都merge一遍,这样的复杂度便是:
1. assume the average length of each list is m
2. to merge i = 1 with all the valid j: it will require (k + k) + (2k + k) + (3k + k) ... + ((m - 1)k + k) = 2k + 3k + 4k + ... + mk = O(m2 * k)
这样做的复杂度会比较高,我们可以通过一些别的方法来降低复杂度.这个算法实际上并没有充分利用排好序的特征,只是在merge的时候保证是可以通过线性扫描来完成。
一般对于排好序的数组一般还是应该先想到二分法和快速/归并排序。首先最直观的方法便是使用一个priority queue来实现:假如不是一个linkedlist,那么我们实际上
只需要每次都把所有的数字push进我们的heap其实就可以了,这样时间复杂度便是:
1. first list will require m insertions, for each insertion the complexity will be log m (worst case), so total will be m log m
2. second list require 2m log 2m
3. total will be m log m + 2m log 2m + 3m log 3m + ... + km log km = O(m * k2)
这里其实可以看到这个算法并没有提高太多,如果我们的k远大于averge length m,那么实际上这个算法并没有提升多少。假如我们不用每次都把所有之前的数字都遍历一遍,或者通过某种方式降低每次
push进去的node的数量其实就可以有效的降低我们的复杂度。那么就可以每次都放k个节点进到我们的priority queue中,我们每次保证这个queue只有k个元素,这样时间复杂度就会大量的降低:
1. push all the first nodes into priority queue (pq).
2. pop the first node by from pq, so that we get the smallest node among the k head nodes
3. append the pop node to our return list, and push the next node from the node we just pop
4. going back to 2 until there is no more nodes left from pq
这个方法可以保证每次push进去的复杂度永远是log k,一共有m * k 个节点,所以总的时间复杂度是O(m * k * log k),这也是这道题的最终结果:
解法1:
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* mergeKLists(vector<ListNode*>& lists) { 12 auto cmp = [](const ListNode* left, const ListNode* right) { 13 return (left -> val) > (right -> val); 14 }; 15 priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq(cmp); 16 ListNode* dummy = new ListNode(-1); 17 ListNode* curr = dummy; 18 for (auto node : lists) 19 if (node) 20 pq.push(node); 21 22 while (!pq.empty()) { 23 auto t = pq.top(); 24 pq.pop(); 25 curr -> next = t; 26 curr = curr -> next; 27 if (t && t -> next) 28 pq.push(t -> next); 29 } 30 return dummy -> next; 31 } 32 };
Given a collection of intervals, merge all overlapping intervals.
Example 1:
Input: [[1,3],[2,6],[8,10],[15,18]] Output: [[1,6],[8,10],[15,18]] Explanation: Since intervals [1,3] and [2,6] overlaps, merge them into [1,6].
Example 2:
Input: [[1,4],[4,5]] Output: [[1,5]] Explanation: Intervals [1,4] and [4,5] are considered overlapping.
题目理解:给一个没有顺序的interval数组,现在要把所有有交错的interval都merge一起,返回最终的结果。
第一步是先按照每一个interval的起始位置排序,然后遍历一遍把所有 current end <= next start 的两个interal都merge一次,这样最后的list就是已经merge好的了
解法1:
1 #define vvi vector<vector<int>> 2 #define vi vector<int> 3 #define pb push_back 4 class Solution { 5 public: 6 vi mergeTwo(vi& l, vi& r) { 7 return {min(l[0], r[0]), max(l[1], r[1])}; 8 } 9 vvi merge(vvi& in) { 10 vvi res; 11 sort(in.begin(), in.end()); 12 for (int i = 0; i < in.size(); ++i) { 13 if (res.empty() || res.back()[1] < in[i][0]) 14 res.pb(in[i]); 15 else 16 { 17 vi merged = mergeTwo(res.back(), in[i]); 18 res.pop_back(); 19 res.pb(merged); 20 } 21 22 } 23 return res; 24 } 25 };
Insert Interval
Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessary).
You may assume that the intervals were initially sorted according to their start times.
Example 1:
Input: intervals = [[1,3],[6,9]], newInterval = [2,5] Output: [[1,5],[6,9]]
Example 2:
Input: intervals =[[1,2],[3,5],[6,7],[8,10],[12,16]]
, newInterval =[4,8]
Output: [[1,2],[3,10],[12,16]] Explanation: Because the new interval[4,8]
overlaps with[3,5],[6,7],[8,10]
.
题目理解:给一个数组,每个element都是[start, end],代表一个interval。现在要插入一个新的interval:[new start, new end],返回最终数组。如果新的interval与之前的任意的interval有交错的话:new start < start or new end > end,则需要把这种的interval merge一起。我们可以先看几个test:1. [[1, 3], [10, 13]] - insert [5, 7] -> [[1, 3], [5, 7], [10, 13]] (no intersection)2. [[1, 3], [5, 10]] - insert [3, 5] -> [[1, 3], [3, 5], [5, 10]] -> [[1, 5], [5, 10]] -> [[1, 10]] (multiple intersections)如果只有两个interval的话,这个问题便很好解决:1. old interval list = [[start, end]]2. insert new interval [new start, new end]3. new interal list = [[min(start, new start), max(new end, end)]]实际上我们需要merge的只有交错的interval,所以如果能把所有会交错的数组通过某种方式都拿出来,那么我们只要把所有的这些交错的数组种取最小的start和最大的end便可以组成我们新的数组:1. create a list that holds all the intervals that have end < new start; create another list that holds all the intervals that have new end < start2. so the rest of the intervals are all going to intersect with new interval, we keep updating our new start and new end once we get a new one that will intersect with new interval.3. concatenate the two list, and insert the new interval at the middle. (O(n))
这样的算法时间复杂度是O( n) 因为我们在遍历前首先要排序,之后要再次遍历整个数组。
解法1:
1 class Solution { 2 public: 3 vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) { 4 vector<vector<int>> left, right; 5 int s = newInterval[0], e = newInterval[1]; 6 for (auto& i : intervals) { 7 if (i[1] < s) { // all less than newInterval 8 left.push_back(i); 9 } else if (i[0] > e) { // all greater than newInterval 10 right.push_back(i); 11 } else { 12 s = min(s, i[0]); // the one needs to be merged 13 e = max(e, i[1]); 14 } 15 } 16 left.push_back({s, e}); 17 // O(n) 18 left.insert(left.end(), right.begin(), right.end()); 19 return left; 20 } 21 };