860e. 柠檬水找零
方法一:贪心
用时:12m1s
思路
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
int num5 = 0, num10 = 0;
for (int& bill : bills) {
if (bill == 5) ++num5; // 5美元
else if (bill == 10) { // 10美元
if (num5 == 0) return false;
else {
--num5;
++num10;
}
} else { // 20美元
if (num10 > 0) { // 如果有10美元零钱
if (num5 == 0) return false;
else {
--num5;
--num10;
}
} else { // 如果没有10美元零钱
if (num5 < 3) return false;
else num5 -= 3;
}
}
}
return true;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
406m. 根据身高重建队列
方法一:贪心(从高到低考虑)
用时:40m17s
思路
将所有人按照身高从高到低排序,若身高相同,则按照 k i k_i ki从小到大排序,然后遍历每一个人,将他们插入到结果数组中的第 k i k_i ki个位置中,由于后遍历到的人身高低于先遍历到的人,即使将他们插入到已经位于数组中的人前面也不会导致错误的排序(局部最优)。
- 时间复杂度: O ( n 2 ) O(n^2) O(n2),insert操作的时间复杂度是 O ( n ) O(n) O(n)。
- 空间复杂度: O ( log n ) O(\log n) O(logn),不算存放结果的数组,所需空间是排序需要的空间。
C++代码
class Solution {
private:
static bool cmp(vector<int>& a, vector<int>& b) {
if (a[0] != b[0]) return a[0] > b[0];
else return a[1] < b[1];
}
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
vector<vector<int>> res;
sort(people.begin(), people.end(), cmp); // 排序
for (int i = 0; i < people.size(); ++i) res.insert(res.begin() + people[i][1], people[i]); // 将每个人插入到第k_i个位置中
return res;
}
};
方法二:贪心(从低到高考虑)
用时:7m59s
思路
将数组按照身高从低到高排序,若身高相同按照
k
i
k_i
ki从高到低排序,排序后从前往后取出人放到结果数组中,对于第
i
i
i个人
(
h
i
,
k
i
)
(h_i,k_i)
(hi,ki),将其放置在第
k
i
+
1
k_i+1
ki+1个空位中。
贪心逻辑:这样放置后,第
i
i
i个人前面会有
k
i
k_i
ki个空位,因为数组事先进行了排序,所以第
i
i
i个人前面非空位上的人肯定是矮于第
i
i
i个人的,不会对其造成影响,且对于留下来的
k
i
k_i
ki个空位,接下来放置的人的身高肯定大于等于第
i
i
i个人,这样对于当前的人肯定满足要求(局部最优)。
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)。
- 空间复杂度: O ( log n ) O(\log n) O(logn)。
C++代码
class Solution {
private:
static bool cmp(vector<int>& a, vector<int>& b) {
if (a[0] != b[0]) return a[0] < b[0];
else return a[1] > b[1];
}
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
int size = people.size();
vector<vector<int>> res(size);
sort(people.begin(), people.end(), cmp);
for (int i = 0; i < size; ++i) { // 遍历每个人
int space = people[i][1] + 1; // 当前的人前面需要的空白位置
for (int j = 0; j < size; ++j) { // 遍历结果数组的每一个位置
if (res[j].empty()) {
--space;
if (space == 0) {
res[j] = people[i];
break;
}
}
}
}
return res;
}
};
看完讲解的思考
方法二的时间复杂度实际是要优于方法一,因为方法一中使用了insert操作,c++的vector底层是用静态数组实现的,在insert操作中,当vector的元素个数超过数组的容量,就会重新创建一个数组,然后将原先数组的元素赋值到新的数组中,扩容的操作会导致额外的时间复杂度。而方法二预先生成了确定大小的数组,后续只是将元素赋值给对应位置。
代码实现遇到的问题
无。
452m. 用最少数量的箭引爆气球
方法一:贪心
用时:16m2s
思路
按照左边界对points进行排序,用上一个气球的右边界来记录当前重叠区间的最小右边界,然后依次遍历points,如果当前气球的左边界大于最小右边界,则说明当前气球与上一个重叠区间不重叠,则需要的箭的数量加一。
- 时间复杂度: O ( n log n ) O(n \log n) O(nlogn)。
- 空间复杂度: O ( log n ) O(\log n) O(logn)。
C++代码
class Solution {
private:
static bool cmp(const vector<int>& a, const vector<int>& b) {
return a[0] < b[0];
}
public:
int findMinArrowShots(vector<vector<int>>& points) {
int res = 1;
sort(points.begin(), points.end(), cmp);
for (int i = 1; i < points.size(); ++i) {
if (points[i][0] > points[i - 1][1]) ++res;
else points[i][1] = min(points[i - 1][1], points[i][1]);
}
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
435m. 无重叠区间
方法一:贪心
用时:9m41s
思路
先根据区间的左边界进行排序,然后依次遍历各个区间,若当前区间的左边界小于上一个区间的右边界,则表示有重复,需要在这两个区间中丢弃一个,选择右边界比较大的区间丢弃。
- 时间复杂度: O ( n log n ) O(n \log n) O(nlogn)。
- 空间复杂度: O ( log n ) O(\log n) O(logn)。
C++代码
class Solution {
private:
static bool cmp(const vector<int>& a, const vector<int>& b) {
return a[0] < b[0];
}
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
int res = 0;
sort(intervals.begin(), intervals.end(), cmp);
for (int i = 1; i < intervals.size(); ++i) {
if (intervals[i][0] < intervals[i - 1][1]) { // 如果当前区间的左边界小于上一个区间的右边界,则表示有重叠
++res;
intervals[i][1] = min(intervals[i - 1][1], intervals[i][1]); // 相当于舍弃掉右边界的值比较大的区间
}
}
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
763m. 划分字母区间
方法一:贪心
用时:24m24s
思路
贪心逻辑:首先记录每个字符最后出现的位置,然后遍历每个字符,不断更新区间的右边界(局部最优),当当前位置就是区间的右边界,则表示可以分割字符串。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
vector<int> partitionLabels(string s) {
// 记录每个字符最后出现的位置
unordered_map<char, int> hashMap;
for (int i = 0; i < s.length(); ++i) hashMap[s[i]] = i;
// 分割字符串
int begin = 0;
int end = hashMap[s[0]];
vector<int> res;
for (int i = 0; i < s.length(); ++i) {
end = max(end, hashMap[s[i]]); // 不断更新重合区间的右边界
if (i == end || end == s.length()) { // 如果当前位置就是重叠区间的右边界,则可以分割字符串;或者重叠区间的右边界已包括剩下全部字符,则剩余字符串无法再分割
res.push_back(end - begin + 1);
if (end == s.length()) break; // 剩余字符串无法再分割,提前终止
begin = i + 1; // 新的子串的起始位置
}
}
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
最后的碎碎念
最近突然好多事,又要刷题,又要写论文,又要跑实验,又参加了个比赛,还有一堆局。真希望一天能有48个小时。