今天的题也是第1道相对简单,后两道虽然自己也做出来了,但都没有完全做对,有不同的坑。这种坑就导致有时自以为做对了没有问题,但实际不正确。在真正笔试或面试时只有正确率而没有结果错误的测试用例,就很难找出错误,所以需要多积累经验。
第1题(LeetCode 860. 柠檬水找零)比较简单,自己实现的思路是当付的钱是5时,只能收着;付的钱是10时,只能找5元;只有收的钱是20时才有选择空间,可以找10 + 5元,或者找3个5元。而当付的钱是10元和20元时都需要找5元,只有收到20元时才需要找10元,所以贪心策略是遇到20元时优先找10 + 5元,不满足时才找3个5元。然后当10元或5元钱数小于0时即返回false。
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
int n5 = 0, n10 = 0;
for (int bill : bills) {
if (bill == 5) {
n5++;
}
else if (bill == 10) {
n5--;
n10++;
}
else {
if (n10 > 0 && n5 > 0) {
n10--;
n5--;
}
else {
n5 -= 3;
}
}
if (n5 < 0 || n10 < 0) {
return false;
}
}
return true;
}
};
题解的思路、实现方式与自己一致。
第2题(LeetCode 406. 根据身高重建队列)自己实现的思路(错误的)是首先对people按照自己的位次(第2个属性)由小到大排序。此时,people中的所有元素一定是要么需要往前(左)移动,要么不需要移动的,没有需要往后(右)移动的。再循环遍历people。对于people中的每一个当前元素(对应下标i),都在内层循环从第0个元素开始遍历到当前元素为止,记录与当前元素身高相比更高或相等的人个数,一旦个数达到people[i][1],就将people[i]从原来位置取出,放入到达到个数的位置之后,以满足people[i][1]的要求。之后再对people[i + 1]进行同样的内部循环和处理,直到遍历完每一个元素。
class Solution {
public:
static bool cmp(vector<int> a, vector<int> b) {
return a[1] < b[1];
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(), people.end(), cmp);
for (int i = 0; i < people.size(); ++i) {
int cnt = 0;
for (int j = 0; j < i; ++j) {
if (people[j][0] >= people[i][0]) {
++cnt;
if (cnt == people[i][1] + 1) { // 是+1
people.insert(people.begin() + j, people[i]);
people.erase(people.begin() + i + 1);
break;
}
}
}
}
return people;
}
};
需要注意第13行处的判断用了people[i][1] + 1而不是people[i][1],这是为了避免如果令cnt + 1的元素就是people[i]本身,people进行无意义的插入与删除。实现过程中用到了vector的insert()和erase()函数,需要再次熟悉下。也正因为用了这两个操作,其时间复杂度为O(n),导致总的时间复杂度达到O(n³)。
但看了题解并思考后,发现自己上面的思路和实现虽然AC,但却是错误的。因为在每个元素左移的过程中,都可能会破坏之前已经左移过的元素的合法性。比如输入为[[7,0],[5,0],[7,2],[6,1],[5,2],[7,1]]时,上面错误代码的输出就会是[[7,0],[5,0],[7,1],[6,1],[7,2],[5,2]],而正确答案是[[5,0],[7,0],[5,2],[6,1],[7,1],[7,2]]。
题解的正确思路是首先对people按照身高由大到小排序。这样一来,从左往右处理(插入到前面某位置)元素时,因为该元素身高是小于其左边所有元素的,所以不会影响到已经处理过元素的合法性。此外,可以用一个新的vector<vector<int>>来保存结果,当要插入某个元素(对应下标i)时,因为结果中保存的元素身高都不小于该元素,其目标插入位置就是people[i][1],不需再用一个循环来确定。
class Solution {
public:
static bool cmp(vector<int> a, vector<int> b) {
if (a[0] == b[0]) {
return a[1] < b[1];
}
return a[0] > b[0];
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(), people.end(), cmp);
vector<vector<int>> res;
for (int i = 0; i < people.size(); ++i) {
int pos = people[i][1];
res.insert(res.begin() + pos, people[i]);
}
return res;
}
};
需要注意,排序规则中,如果两元素身高相等,应该把k小的元素排在前面。否则最后插入时,可能出现插入目标位置靠后的元素先插入,导致插入的目标位置越界而报错。此外,这一题与day 34中的第3题(LeetCode 135. 分发糖果)很相似,都是先按照一个维度做好相应处理,再处理另一个维度,如果同时考虑两者就很难做到。
二刷:忘记题解方法和insert()用法(vector.insert(vector.begin() + idx, x))。
第3题(LeetCode 452. 用最少数量的箭引爆气球)自己的思路正确,但实现上因为一个问题而导致超时。首先对气球按照start坐标(按照end排序也可以)排序,贪心策略是对尽可能多气球的重叠区间射出一只箭。所以需要不断更新重叠区间。如果当前气球与重叠区间有重叠,就更新重叠区间的末尾为原本末尾和当前气球end中的最小值;如果当前气球与重叠区间没有重叠,就射出一只箭,并将重叠区间末尾更新为当前气球的end。由于已经排过序,所以重叠区间只需要用末尾来表示即可。
class Solution {
public:
static bool cmp(vector<int>& a, vector<int>& b) { // 要按引用类型传参
return a[0] < b[0]; // 按照end排序也可以
}
int findMinArrowShots(vector<vector<int>>& points) {
sort(points.begin(), points.end(), cmp);
int end = points[0][1];
int ans = 1;
for (int i = 1; i < points.size(); ++i) {
if (points[i][0] <= end) {
end = min(end, points[i][1]);
}
else {
ans++;
end = points[i][1];
}
}
return ans;
}
};
而在实现时,因为cmp函数的参数没有用引用类型而导致超时。所以这里获得一个很重要的经验,就是以后题目中的cmp函数也要按照引用类型传参。
题解与自己方法一样,也是看了题解才直到了自己代码超时的原因。
二刷:有点忘记方法,后来写出来。