【LeetCode】排序 sort(共20题)
链接:https://leetcode.com/tag/sort/
【56】Merge Intervals (2019年1月26日,谷歌tag复习)
合并区间
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].
题解:先按照interval的begin从小到大sort一下,然后顺序遍历,能合并的就合并,不能合并的就加入一个新的interval。
1 /** 2 * Definition for an interval. 3 * struct Interval { 4 * int start; 5 * int end; 6 * Interval() : start(0), end(0) {} 7 * Interval(int s, int e) : start(s), end(e) {} 8 * }; 9 */ 10 //time complexity: O(nlogn) 11 class Solution { 12 public: 13 vector<Interval> merge(vector<Interval>& intervals) { 14 sort(intervals.begin(), intervals.end(), cmp); 15 vector<Interval> ret; 16 for (auto inter : intervals) { 17 if (ret.empty()) { 18 ret.push_back(inter); 19 continue; 20 } 21 if (inter.start > ret.back().end) { 22 ret.push_back(inter); 23 } else { 24 ret.back().end = max(inter.end, ret.back().end); 25 } 26 } 27 return ret; 28 } 29 static bool cmp (const Interval& inter1, const Interval& inter2) { 30 return inter1.start < inter2.start; 31 } 32 };
【57】Insert Interval (2019年1月26日,谷歌tag复习)
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
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].
题解:先按照插入排序把需要插入的interval放在原来intervals中合适的位置,然后合并区间。
1 /** 2 * Definition for an interval. 3 * struct Interval { 4 * int start; 5 * int end; 6 * Interval() : start(0), end(0) {} 7 * Interval(int s, int e) : start(s), end(e) {} 8 * }; 9 */ 10 class Solution { 11 public: 12 vector<Interval> insert(vector<Interval>& intervals, Interval newInterval) { 13 const int n = intervals.size(); 14 int i = 0; 15 for (; i < n; ++i) { 16 if (intervals[i].start >newInterval.start) { 17 auto iter = intervals.begin() + i; 18 intervals.insert(iter, newInterval); 19 break; 20 } 21 } 22 if (i == n) { 23 intervals.push_back(newInterval); 24 } 25 vector<Interval> ret; 26 for (auto interval : intervals) { 27 if (ret.empty() || ret.back().end < interval.start) { 28 ret.push_back(interval); 29 } else { 30 ret.back().end = max(interval.end, ret.back().end); 31 } 32 } 33 return ret; 34 } 35 };
【75】Sort Colors (2018年12月11日,wiggle sort专题复习,荷兰国旗问题)
给了一个数组,包含重复的 0,1,2 三种数字,实现一种操作,能用 one pass,O(1) 的space把这个数组变成0,1,2的顺序数组。
题解:用三根指针,left,right,mid。最后的时候 left 指向 1 的第一个元素,right 指向 1 的最后一个元素。
2019年1月20日补充,left一开始在最左边,right一开始在最右边,我们有一个能够往前移动的指针叫做point或者mid,我们想用mid指针遍历过整段的1,所以如果当前的mid指针指向的是0的话,我们想把这个0放到最前面去,所以把这个0和前面的left做交换,left++,mid++。如果当前的mid指针指向的是 2 的话,我们把它和后面的right做交换,--right,但是这里不能++mid,因为有可能原来的 right位置就是 2,交换完了之后 mid 指向的还是 2.
1 //荷兰国旗问题 2 class Solution { 3 public: 4 void sortColors(vector<int>& nums) { 5 const int n = nums.size(); 6 int left = 0, mid = 0, right = n-1; 7 while (mid <= right) { 8 if (nums[mid] == 0) { 9 swap(nums[left++], nums[mid++]); 10 } else if (nums[mid] == 2) { 11 swap(nums[right--], nums[mid]); 12 } else { 13 mid++; 14 } 15 } 16 return; 17 } 18 };
【147】Insertion Sort List
【148】Sort List
【164】Maximum Gap
给定一个没有排序的数组,返回排序好的数组相邻的两个元素的最大差值。
Input: nums = [3,6,9,1] Output: 3 Explanation: The sorted form of the array is [1,3,6,9], either (3,6) or (6,9) has the maximum difference 3.
题解:可以直接sort,然后1 pass 计算。这样的话时间复杂度是 O(nlogn)
题目要求 O(n) 的时间复杂度,我也不会做。
【179】Largest Number (2019年2月23日,M)
给了一个整数的数组,要求返回一个字符串,这个字符串由整数数组的元素拼接而成。每个元素只能用一次。求这个字符串转换成数字的最大值。
Input:[3,30,34,5,9]
Output: "9534330"
题解:我们想象一下比较两个数,3,和 30,这两个数应该谁在前谁在后,如果3在前面,30在后面,组成的数字是330,如果30在前面,3在后面,组成的数字是303。我们断定330肯定大于303。所以我们算法如下,把整数数组排列成字符串,并且给新的字符串数组排序。排序的规则我们自己定义,s1 + s2 > s2 + s1 返回true。
1 class Solution { 2 public: 3 string largestNumber(vector<int>& nums) { 4 const int n = nums.size(); 5 vector<string> strs(n); 6 for (int i = 0; i < n; ++i) { 7 strs[i] = to_string(nums[i]); 8 } 9 sort(strs.begin(), strs.end(), cmp); 10 string res; 11 for (auto& s : strs) { 12 res += s; 13 } 14 while (res.size() > 1 && res[0] == '0') { 15 res = res.substr(1); 16 } 17 return res; 18 } 19 static bool cmp(const string& s1, const string& s2) { 20 string t1 = s1 + s2, t2 = s2 + s1; 21 return t1 > t2; 22 } 23 };
【242】Valid Anagram
【252】Meeting Rooms (2018年11月22日,为了冲题量)
题意就是给了一个数组,数组里面的每个元素代表一个会议的开始时间和结束时间。问能不能用一个会议室安排下所有的会议。
题解:这题好像以前就做过类似的线段题,先给数组排序,用结束时间从小到大排序,如果结束时间相同,就按照开始时间从小到大排序。然后遍历数组,看前一个元素的结束时间是否小于等于当前元素的开始时间。 都满足条件返回 true,有一个不满足条件的返回false。
1 /** 2 * Definition for an interval. 3 * struct Interval { 4 * int start; 5 * int end; 6 * Interval() : start(0), end(0) {} 7 * Interval(int s, int e) : start(s), end(e) {} 8 * }; 9 */ 10 class Solution { 11 public: 12 bool canAttendMeetings(vector<Interval>& intervals) { 13 if (intervals.empty()) {return true;} 14 sort(intervals.begin(), intervals.end(), cmp); 15 for (auto i = 0; i < intervals.size()-1; ++i) { 16 if (intervals[i].end > intervals[i+1].start) { 17 return false; 18 } 19 } 20 return true; 21 } 22 static bool cmp(const Interval& a, const Interval& b) { 23 if (a.end != b.end) { 24 return a.end < b.end; 25 } 26 return a.start < b.start; 27 } 28 };
【253】Meeting Rooms II (2018年11月22日,为了冲题量)
题意是252的升级版,给了一个数组,数组里面的每个元素代表一个会议的开始时间和结束时间,问想安排下所有的会议,至少需要多少个会议室。
题解:还是先排序。这次可能按照结束时间从大到小排序,结束时间相同的时候就按照开始时间从大到小排序,或者从小到大排序然后从后往前遍历都可以。用一个map记录key是会议室编号,value是这个会议室的下一个会议的开始时间。如果当前会议的结束时间小于等于下一个会议的开始时间就能放在这个会议室里面,不然需要重新开一个会议室。有一种case需要注意,如果我按照会议结束时间从小到大排序然后顺序安排,有可能有一个会议室的时间轴两个会议之间会有一段很大的gap,这个gap可能重新安排就能减少会议室的总体数量。按照结束时间从大到小排序能保证能一个会议室中间的gap不会很大。
1 /** 2 * Definition for an interval. 3 * struct Interval { 4 * int start; 5 * int end; 6 * Interval() : start(0), end(0) {} 7 * Interval(int s, int e) : start(s), end(e) {} 8 * }; 9 */ 10 class Solution { 11 public: 12 int minMeetingRooms(vector<Interval>& intervals) { 13 if (intervals.empty()) {return 0;} 14 const int n = intervals.size(); 15 sort(intervals.begin(), intervals.end(), cmp); 16 int ret = 1; 17 map<int, int> mp; //roomNumber -> starttime 18 mp[ret] = intervals[0].start; 19 for (int i = 1; i < n; ++i) { 20 bool find = false; 21 for (auto& e : mp) { 22 if (e.second >= intervals[i].end) { 23 find = true; 24 e.second = intervals[i].start; 25 break; 26 } 27 } 28 if (!find) { 29 mp[++ret] = intervals[i].start; 30 } 31 } 32 return ret; 33 } 34 static bool cmp(const Interval& a, const Interval& b) { 35 if (a.end != b.end) { 36 return a.end > b.end; 37 } 38 return a.start > b.start; 39 } 40 };
这题还有个 greedy 和 heap 的标签,思想就是 greedy,heap怎么解不懂啊。
【274】H-Index
【280】Wiggle Sort (2018年12月11日,wiggle sort专题)
Given an unsorted array nums
, reorder it in-place such that nums[0] <= nums[1] >= nums[2] <= nums[3]...
.
Example: Input: nums = [3,5,2,1,6,4] Output: One possible answer is [3,5,1,6,2,4]
题解:依次比较相邻的两个数,不符合条件的就做交换。
1 class Solution { 2 public: 3 void wiggleSort(vector<int>& nums) { 4 const int n = nums.size(); 5 for (int i = 1; i < n; ++i) { 6 if (i & 0x1 && nums[i] < nums[i-1]) { 7 swap(nums[i], nums[i-1]); 8 } else if (i % 2 == 0 && nums[i] > nums[i-1]) { 9 swap(nums[i], nums[i-1]); 10 } 11 } 12 return; 13 } 14 };
2019年2月18日补充,时间复杂度是O(N)
【296】Best Meeting Point (2018年11月22日,开始做 hard 题)
给了一个二维的 0/1 矩阵grid, grid[i][j] = 1 代表 1 位置上有个人,现在矩阵上至少有两个人,这几个人想挑一个点 (x, y) 相会,问矩阵上的哪个点到这几个人的曼哈顿距离和最近,返回最近的距离之和。
曼哈顿距离的公式是 distance(p1, p2) = |p2.x - p1.x| + |p2.y - p1.y|
.
题解:我一开始就暴力了,时间复杂度是 O(n*m*k),然后居然卡着时间过了orz,只 beats 了 1.15%,尴尬。下面附上尴尬的强行解。
1 class Solution { 2 public: 3 int minTotalDistance(vector<vector<int>>& grid) { 4 const int n = grid.size(); 5 if (n == 0) {return 0;} 6 const int m = grid[0].size(); 7 if (m == 0) {return 0;} 8 vector<vector<int>> people; 9 for (int i = 0; i < n; ++i) { 10 for (int j = 0; j < m; ++j) { 11 if (grid[i][j]) { 12 people.push_back(vector<int>{i, j}); 13 } 14 } 15 } 16 int ret = INT_MAX; 17 for (int i = 0; i < n; ++i) { 18 for (int j = 0; j < m; ++j) { 19 int summ = 0; 20 for (int k = 0; k < people.size(); ++k) { 21 summ += abs(i - people[k][0]) + abs(j - people[k][1]); 22 if (summ > ret) {break;} 23 } 24 ret = min(summ, ret); 25 } 26 } 27 return ret; 28 } 29 };
我们下面来看一下优秀的解法们:我们可以先分析一下一维数组的情况,我们假设这个数组是 [1,0,0,1,0,0,0,1],会面的位置选在第二个 1 的位置距离和最小。所以这个结论就是一维数组是选在前缀和的中位数上,median,如果是偶数个 1,比如 [1,1,0,1,1], 这个位置选在下标 1, 2, 3上都可以。因为是求曼哈顿距离,所以完全可以行和列分离,分解成两个一维数组的情况。我们首先对矩阵的行和列进行累加,把一个矩阵拍成一维数组,然后结果就是两个一维数组的距离之和。时间复杂度是(m*n*log(nm))
1 class Solution { 2 public: 3 int minTotalDistance(vector<vector<int>>& grid) { 4 const int n = grid.size(); 5 if (n == 0) {return 0;} 6 const int m = grid[0].size(); 7 if (m == 0) {return 0;} 8 vector<int> rows(m, 0), cols(n, 0); 9 for (int j = 0; j < m; ++j) { 10 for (int i = 0; i < n; ++i) { 11 if (grid[i][j]) { 12 rows[j] += 1; 13 cols[i] += 1; 14 } 15 } 16 } 17 int ret = cope(rows) + cope(cols); 18 return ret; 19 } 20 int cope(const vector<int>& nums) { 21 const int n = nums.size(); 22 vector<int> summ = nums; 23 for (int i = 1; i < n; ++i) { 24 summ[i] += summ[i-1]; 25 } 26 int median = (summ[n-1] + 1) / 2; 27 auto iter = lower_bound(summ.begin(), summ.end(), median); 28 int index = distance(summ.begin(), iter); 29 int ret = 0; 30 for (int i = 0; i < nums.size(); ++i) { 31 ret += abs(index - i) * nums[i]; 32 } 33 /* 34 print("nums", nums); 35 print("summ", summ); 36 printf("median = %d, index = %d, ret = %d \n", median, index, ret); 37 */ 38 return ret; 39 } 40 void print(const string name, const vector<int>& nums) { 41 printf("%s: ", name.c_str()); 42 for (auto n : nums) { 43 printf("%d ", n); 44 } 45 printf("\n"); 46 } 47 48 };
还可以更加优化,见solution的最后一个解法,它能把时间复杂度搞成 O(mn)。唉我不想看了,
【324】Wiggle Sort II (2019年2月20日)
摆动排序。Given an unsorted array nums
, reorder it such that nums[0] < nums[1] > nums[2] < nums[3]...
.
Example 1: Input: nums = [1, 5, 1, 1, 6, 4] Output: One possible answer is [1, 4, 1, 5, 1, 6]. Example 2: Input: nums = [1, 3, 2, 2, 3, 1] Output: One possible answer is [2, 3, 1, 3, 1, 2].
Can you do it in O(n) time and/or in-place with O(1) extra space?
题解:我们先把数组排序,然后找到中位数。然后我们用中位数把数组分成两半,如果是奇数长度,前半段长一点。然后我们用两根指针,同时从两段数组的末尾开始扫描。加入一个新的数组。这样就能保证前半段的数字一定小于后半段的数字。
这种解法时间复杂度是O(nlogn),空间复杂度是O(n)
1 class Solution { 2 public: 3 void wiggleSort(vector<int>& nums) { 4 int n = nums.size(); 5 if (n == 0) {return;} 6 sort(nums.begin(), nums.end()); 7 int k = (n-1)/2; 8 int p1 = k, p2 = n-1; 9 vector<int> ret(n); 10 int idx = 0; 11 while (idx < n) { 12 ret[idx++] = nums[p1--]; 13 if (idx >= n) {break;} 14 ret[idx++] = nums[p2--]; 15 } 16 nums = ret; 17 } 18 };
【349】Intersection of Two Arrays (2018年11月6日,算法群相关题)
给了两个数组,返回他们交叠的元素,如果有重复元素的话,只返回一个就行了。
hash-table 里面有这题,我就不重复写了。hash-table:https://www.cnblogs.com/zhangwanying/p/9886262.html
【350】Intersection of Two Arrays II (2018年11月6日,算法群)
给了两个数组,返回他们所有交叠的元素,元素可以任意顺序返回,但是如果一个元素在A,B数组中都出现多次,需要返回公共的多次。
hash-table 里面有这题,我就不重复写了。hash-table:https://www.cnblogs.com/zhangwanying/p/9886262.html
【524】Longest Word in Dictionary through Deleting (2019年2月23日)
给了一个字符串s,和一个 wordlist,找出list里面是 s 的子序列并且最长的一个单词,如果有两个单词相同长度,并且都是s的子序列,那么返回 lexicographcal order 小的那个。
Input: s = "abpcplea", d = ["ale","apple","monkey","plea"] Output: "apple"
题解:我们需要依次判断列表中的单词是不是 s 的子序列。需要写一个 match 函数,然后依次判断可以,或者把列表排序后再判断也可以。
1 class Solution { 2 public: 3 string findLongestWord(string s, vector<string>& d) { 4 const int n = d.size(); 5 //sort(d.begin(), d.end(), cmp); 6 string res = ""; 7 for (auto& t : d) { 8 if (match(s, t)) { 9 if (t.size() > res.size()) {res = t;} 10 if (t.size() == res.size()) {res = min(t, res);} 11 } 12 } 13 return res; 14 } 15 static bool cmp(const string& s1, const string& s2) { 16 if (s1.size() == s2.size()) { 17 return s1 < s2; 18 } 19 return s1.size() > s2.size(); 20 } 21 bool match(const string& s, const string& t) { 22 if (s.size() < t.size()) {return false;} 23 const int ssize = s.size(), tsize = t.size(); 24 int idx = 0; 25 for (int i = 0; i < ssize; ++i) { 26 if (idx < tsize && s[i] == t[idx]) { 27 ++idx; 28 } 29 // if (idx == tsize) {return true;} 30 } 31 return idx == tsize; 32 } 33 };
【527】Word Abbreviation
【710】Random Pick with Blacklist
【767】Reorganize String