「代码随想录算法训练营」第二十五天 | 贪心算法 part3
134. 加油站
题目链接:https://leetcode.cn/problems/gas-station/
题目难度:中等
文章讲解:https://programmercarl.com/0134.加油站.html
视频讲解:https://www.bilibili.com/video/BV1jA411r7WX
题目状态:没有思路,学习题解
思路一:全局最优解
首先将所有路径的加油量和耗油量加一起,看是否大于0,若小于0,表示整体的加油量小于耗油量,不可能跑完全程,直接返回-1;若大于0,看其到每一站存储的油量最小值是否大于等于0,若是,则表示其在每一站都是可以跑完的,直接返回0(表示从0开始出发可以完成全程);若并不是这样的,则反向加出发点,看其什么时候的最小值不会为负数,此时就是出发点的位置。
代码一:
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int curSum = 0;
int min = INT_MAX;
for(int i = 0; i < gas.size(); ++i) {
int rest = gas[i] - cost[i];
curSum += rest;
min = std::min(min, curSum);
}
if(curSum < 0) return -1;
if(min >= 0) return 0;
for(int i = gas.size() - 1; i >= 0; --i) {
int rest = gas[i] - cost[i];
min += rest;
if(min >= 0) return i;
}
return -1;
}
};
思路二:贪心算法
局部最优,首先当所有路径的油差之和小于0,直接返回-1,其不可能跑完全程;再次,若从A点到B点的油差和小于0,要从B点的下一点重新记录,因为A到B之间无论从哪一点开始,都跑不完,最后返回合适的位置。
代码:
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int curSum = 0;
int totalSum = 0;
int start = 0;
for(int i = 0; i < cost.size(); ++i) {
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if(curSum < 0) {
start = i + 1;
curSum = 0;
}
}
if(totalSum < 0) return -1;
return start;
}
};
135. 分发糖果
题目链接:https://leetcode.cn/problems/candy/
题目难度:困难
文章讲解:https://programmercarl.com/0135.分发糖果.html
视频讲解:https://www.bilibili.com/video/BV1ev4y1r7wN
题目状态:好难,要长脑子了
思路:
首先创建一个数组,存储每个孩子手里的糖。
- 首先,每个孩子手里都要有一颗糖。
- 从左向右,判断右边孩子分数是否要大于左边孩子,若大于,就把右边孩子手里加一颗糖。
- 从右向左,判断左边孩子的分数是否大于右边孩子,若大于,则判断左边孩子手里的糖是否已经大于右边孩子了,若不大于,则将左边孩子手里的糖加到比右边孩子大。
代码:
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int> candyVal(ratings.size(), 1);
for(int i = 1; i < ratings.size(); ++i) {
if(ratings[i] > ratings[i - 1]) candyVal[i] = candyVal[i - 1] + 1;
}
for(int i = ratings.size() - 2; i >= 0; --i) {
if(ratings[i] > ratings[i + 1]) candyVal[i] = max(candyVal[i], candyVal[i + 1] + 1);
}
int res = 0;
for(auto &val : candyVal) res += val;
return res;
}
};
860. 柠檬水找零
题目链接:https://leetcode.cn/problems/lemonade-change/
题目难度:简单
文章讲解:https://programmercarl.com/0860.柠檬水找零.html
视频讲解:https://www.bilibili.com/video/BV12x4y1j7DD
题目状态:没绕过来,看完题解豁然开朗
思路:
维护五块钱和十块钱的个数five和ten
- 当给5元时,five++;
- 当给10元时,five--;
- 当给20元时,要么ten--和five--,要么five-3。
代码:
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
int five = 0, ten = 0;
for(int i = 0; i < bills.size(); ++i) {
if(bills[i] == 5) five++;
if(bills[i] == 10) {
if(five <= 0) return false;
five--;
ten++;
}
if(bills[i] == 20) {
if(five > 0 && ten > 0) {
five--;
ten--;
} else if(five >= 3) {
five -= 3;
} else {
return false;
}
}
}
return true;
}
};
406. 根据身高重建队列
题目链接:https://leetcode.cn/problems/queue-reconstruction-by-height/
题目难度:中等
文章讲解:https://programmercarl.com/0406.根据身高重建队列.html
视频讲解:https://www.bilibili.com/video/BV1EA411675Y
题目状态:继续学习题解...
思路:
首先对整个数组进行排序,排序规则是比较身高,最高的排在前面,若身高相同,比较前方人数,人数低的排在前面。
之后开始插入返回数组,按照排序后的数组依次插入,插入的位置是自己的前方人数,下图更容易理解插入的过程。
代码实现:
class Solution {
public:
static bool cmp(const vector<int> &a, const 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>> que;
for(int i = 0; i < people.size(); ++i) {
int position = people[i][1];
que.insert(que.begin() + position, people[i]);
}
return que;
}
};
链表代码:(性能更好)
class Solution {
public:
// 身高从大到小排(身高相同k小的站前面)
static bool cmp(const vector<int>& a, const 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);
list<vector<int>> que; // list底层是链表实现,插入效率比vector高的多
for (int i = 0; i < people.size(); i++) {
int position = people[i][1]; // 插入到下标为position的位置
std::list<vector<int>>::iterator it = que.begin();
while (position--) { // 寻找在插入位置
it++;
}
que.insert(it, people[i]);
}
return vector<vector<int>>(que.begin(), que.end());
}
};