贪心算法
1. 简单贪心算法(常识问题)
到底什么是贪心算法:利用局部最优来推出全局最优就是核心
1.1 LeetCode第455题—分发饼干
局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩
class Solution {
public:
//我们要的结果是尽可能的将我们的饼干分发给更多的小孩,如果我们这么思考应该怎么想呢?
//尽可能让大饼干去满足大胃口的人
//局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩。
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int index = s.size()-1;
int num = 0;
for(int i = g.size()-1;i>=0;--i)
{
if(index >= 0 && s[index] >= g[i])
{
num++;
index--;
}
}
return num;
}
};
1.2 LeetCode第1005题—K此取反后最大化的数组和
class Solution {
static bool cmp(int a,int b)
{
return abs(a) > abs(b);
}
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
//如果数组中所有的元素都是正数,那么我们应该尽可能的让最小的哪一个进行转换,
//如果数组中有负数,那么我们应该尽可能的让较小的负数来做这一次的转换
//这道题并没有让你返回下标,而是返回结果
sort(nums.begin(),nums.end(),cmp);
for(int i = 0;i<nums.size();++i)
{
if(nums[i] < 0 && k > 0)
{
nums[i] *= -1;
k--;
}
}
while(k--)
{
nums[nums.size()-1] *= -1;
}
int ret = 0;
for(int& e : nums)
{
ret += e;
}
return ret;
}
};
1.2 LeetCode第860题—柠檬水找零
因为5元既可以给10元的找钱,也可以给20元的找钱,但是10元的只能够给20元的找钱,所以我们尽可能的,在找20元的时候,优先把10元的找出去,这样才能够尽可能的给每一个人都把钱找了
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
//5元是不需要找零钱的,因为单价本身就是5元
//如果一上来就是超5元的,那么不需要继续下去了直接返回false
//5美元是万能的,所以我们在收到20美元的时候,应该尽可能的先把10元的用出去
int five = 0,ten = 0;
for(auto& bill : bills)
{
if(bill == 5)
{
five++;
}
else if(bill == 10)
{
if(five < 0)
return false;
ten++;
five--;
}
else
{
if(five > 0 && ten > 0)
{
five--;
ten--;
}
else if(five >= 3)
{
five -= 3;
}
else
{
return false;
}
}
}
return true;
}
};
2. 中等贪心算法
2.1 LeetCode第376题—摆动序列
有可能是会出现[3,3,3,5]的情况的,所以这里就相乘波峰和波谷交替来增加最长的摆动序列。
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n = nums.size();
if (n < 2)
return n;
int curdiff = 0;
int prevdiff = 0;
int result = 1;
for(int i = 0;i<n-1;++i)
{
curdiff = nums[i+1] - nums[i];
if((curdiff >0 && prevdiff<= 0) || (curdiff < 0 && prevdiff>= 0))
{
result++;
prevdiff = curdiff;
}
}
return result;
}
};
2.2 LeetCode第122题—买卖股票的最佳时机II
- 局部最优:收集每天的正利润,全局最优:求得最大利润.
class Solution {
public:
//必须在购买前先将手里的股票卖掉
//局部最优:收集每天的正利润,全局最优:求得最大利润
//当然这道题也可以使用动态规划来做
int maxProfit(vector<int>& prices) {
int ret = 0;
//第一天买入第五天卖出,其实这个是可以把他进行拆分的
for(int i = 1;i<prices.size();++i)
{
ret += std::max(prices[i] - prices[i-1],0);
}
return ret;
}
};
3. LeetCode第435题—无重叠区间
我们依旧按照右边界进行排序,来记录要删除的区间个数
class Solution {
public:
//我们来记录要删除的区间
//我们根据右边界来进行排序
static bool cmp(vector<int>& a, vector<int>& b)
{
return a[1] < b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(),intervals.end(),cmp);
int del = 0;
int t = intervals[0][1];
for(int i = 1;i<intervals.size();++i)
{
if(intervals[i][0] < t)
del++;
else
t = intervals[i][1];
}
return del;
}
};
4. LeetCode第406题—根据身高建队列
LeetCode题目链接:https://leetcode-cn.com/problems/queue-reconstruction-by-height/
其实就是把这个调整到合适的位置,其实这道题还是挺有意思的。
排序,根据身高,然后对于身高相同的,就根据第二个参数进行排序,对于第二个参数也就是每个需要插入的位置,所以我们就拿到这个位置,然后将对应的vector< int >插入其中,但是使用vector这里就会效率很慢,所以最好的方式在插入的部分,我们使用list来解决
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];
}
//使用list的确要快很多
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort (people.begin(), people.end(), cmp);
//list可以在任意位置进行插入删除,时间复杂度位O(1)效率更高
vector<vector<int>> res;
list<vector<int>> que;
for (int i = 0; i < people.size(); i++) {
int position = people[i][1];
std::list<vector<int>>::iterator it = que.begin();
while(position--)
it++;
que.insert(it,people[i]);
}
return vector<vector<int>>(que.begin(),que.end());
}
};
使用vector来解决
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>> res;
for (int i = 0; i < people.size(); i++) {
int position = people[i][1];
res.insert(res.begin() + position, people[i]);
}
return res;
}
};
5. LeetCode第763题—划分字母区间
LeetCode题目链接:https://leetcode-cn.com/problems/partition-labels/
解题思路:
class Solution {
public:
//必须要让每个片段中的字母就出现在这个片段中,别的片段中不能在出现该字母,来进行划分
//当你所不断跟新迭代下标,当你的下标值和你当前的下标值一样的时候,这一段的后面就不会在出现字符
vector<int> partitionLabels(string s) {
vector<int> hash(26,0);
for(int i = 0;i<s.size();++i)
hash[s[i]-'a'] = i;
vector<int> v;
int left = 0;
int right = 0;
for(int i = 0;i<s.size();++i)
{
right = max(right,hash[s[i]-'a']);
//此时这里就是分割线
if(i == right)
{
v.push_back(right-left+1);
left = right+1;
}
}
return v;
}
};
6. LeetCode第452题—用最少数量的箭引爆气球
LeetCode题目链接:https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/
这道题就是沿着y轴射出去一支箭,对于只要穿过X的begin到end的任意一个位置都一样将该气球射穿,那么我们这里维护一个右边界,简单点说就是:在x等于6的地方沿着y轴射出一支箭,那么这一支箭,将会使气球1和气球2击穿,当下一个气球的左边界还要大于所维护的最小右边界的时候,说明此时这个气球无法做到和前面的气球同时被击穿,所以需要新增加一支箭
class Solution {
public:
//我发现这类题目就好难读懂呀
//从y的方向角度射出去一只箭,对于这个箭来说,只要进行区间那么就可以射破
static bool cmp(vector<int>& a , vector<int>& b)
{
return a[0] < b[0];
}
int findMinArrowShots(vector<vector<int>>& points) {
if(points.size() == 0)
return 0;
sort(points.begin(),points.end(),cmp);
int num = 1;
int minRight = points[0][1];
for(int i = 1;i<points.size();++i)
{
if(points[i][0] > minRight)
{
num++;
minRight = points[i][1];
}
else
{
//这里的思想应该怎么思考,才是这道题最复杂的地方
minRight = std::min(minRight,points[i][1]);
}
}
return num;
}
};
7. LeetCode第134题—加油站
LeetCode题目链接:https://leetcode-cn.com/problems/gas-station/
解题思路:
从局部最优,去推导全局最有的情况
- 你要确保从i位置出发,能够开到i+1的位置(如果从[0,i],发现在第i处的时候,为负了,那么起始位置就应该设置为i+1,为什么呢?起始可以抽象为A,B两个点的,把[0,i]这一段都抽象为A,那么A->B是过不去了,那么下一个起始位置就应该设置为下一个点)
- 所有站里的油总量要>=车子的总耗油量
class Solution {
public:
//对于这道题其实就是局部最优得到全局最优
//是否是跑完整个过程,我们可以这样来思考
//首先你得从i 跑到 i+1的位置,最起码这个过程是要保证油量的足够 >= 0
//第二点就是你要保证,到达i位置的时候,总的剩余油量>= 0
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int start = 0;
int curSum = 0,tatalSum = 0;
for(int i = 0;i<gas.size();++i)
{
//计算[0,i]这一段
curSum += gas[i] - cost[i];
tatalSum += gas[i] - cost[i];
if(curSum < 0)
{
//此时说明这一段从0 -> i这段路是走不通的
//那么从i+1这段开始,重新计算
start = i+1;
curSum = 0;
}
}
return tatalSum >= 0 ? start:-1;
}
};
8. LeetCode第135题—分发糖果
LeetCode题目链接:https://leetcode-cn.com/problems/candy/
- 从左边往右边遍历,来对糖果的数量进行求解
- 从右边往左边遍历,这里是会出现一点小问题的,此时对于糖果的数组来说,每一个位置都是满足右边的大于左边的糖果数,但是此时如果对于左边的大于右边的分数时,我们简单的让v[i] = v[i+1] + 1;那么这一步是不对的,因为他会破坏第一个条件,所以我们需要做的就是,两个中取较大值。
class Solution {
public:
//两个局部最优,求全局最优,就可以得到我们想要的结果
int candy(vector<int>& ratings) {
int n = ratings.size();
vector<int> v(n,1);
//如果右边的比左边的大,那么就要给右边多加一个
for(int i = 1;i<n;++i)
{
if(ratings[i] > ratings[i-1])
v[i] = v[i-1] +1;
}
//从右往左进行遍历的过程需要好好的思考和调整
for(int i = n-2;i>=0;--i)
{
//这里为啥要去最大值呢?
//因为此时数组里面的糖果数量已经满足了大于左边,那么如果我们只是单纯的考虑右边进行修改
//那么左边就不再满足了,所以我们取既满足左边,又满足右边的一个数
if(ratings[i] > ratings[i+1])
v[i] = max(v[i],v[i+1] + 1);
}
int res = 0;
for(int& num : v)
res += num;
return res;
}
};
9. LeetCode第55题—跳跃游戏I
LeetCode题目链接:https://leetcode-cn.com/problems/jump-game/
就看能否覆盖的面积达到数组的最后一个位置。
class Solution {
public:
bool canJump(vector<int>& nums) {
//每一个下标表示你可以跳跃的最大长度,但是并不表示你必须要跳跃这么大才可以
//在所能移动的数组中不断的更新我想要的最优结果
//起始对于这道题我们并不需要关心他一次跳跃几步,能跳到哪里,我们更多所需要去关心的就是,他所能覆盖的面积,是否能够达到数组的最后一个位置。
int cover = nums[0];
for(int i = 0;i<=cover;++i)
{
cover = std::max(i+nums[i],cover);
if(cover >= nums.size()-1)
return true;
}
return false;
}
};
9.1 LeetCode第55题—跳跃游戏II
LeetCode题目链接:https://leetcode-cn.com/problems/jump-game-ii/
那么这道题和第一道跳跃游戏究竟有什么不同呢?
其实这是一道贪心算法+动态规划的题目
- 如果当前覆盖最远距离下标不是是集合终点,步数就加一,还需要继续走。
- 如果当前覆盖最远距离下标就是是集合终点,步数不用加一,因为不能再往后走了。(所以条件判断里面才会写nums.size()-1)
class Solution {
public:
int jump(vector<int>& nums) {
int maxfar = 0;//下一步所能覆盖的最远位置下标
int curfar = 0;//当然所能覆盖的最远位置下标
int step = 0;
//这里为什么是nums.size()-1需要好好的思考一下
//因为即使在做一次,很有可能依旧会在这个位置卡住
//如果这里让i等于最后一个位置下标,那么毫无争议的可以说,次数就会多增加一次
for(int i = 0;i<nums.size()-1;++i)
{
maxfar = max(maxfar,i+nums[i]);
if(i == curfar)
{
curfar = maxfar;
step++;
}
}
return step;
}
};
10. LeetCode第738题—单调递增的数字
LeetCode题目链接:https://leetcode-cn.com/problems/monotone-increasing-digits/
例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]–,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。
这一点如果想清楚了,这道题就好办了。
局部最优:遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]–,然后strNum[i]给为9,可以保证这两位变成最大单调递增整数。
class Solution {
public:
//局部最优求解全局最有的一种方式,从后向前进行遍历,
int monotoneIncreasingDigits(int N) {
string strNum = to_string(N);
// flag用来标记赋值9从哪里开始
// 设置为这个默认值,为了防止第二个for循环在flag没有被赋值的情况下执行
int flag = 0;
for (int i = strNum.size() - 1; i > 0; i--) {
//如果此时不如"98"
if (strNum[i - 1] > strNum[i] ) {
flag = i;
strNum[i - 1]--;
}
}
for (int i = flag; i < strNum.size(); i++) {
strNum[i] = '9';
}
return stoi(strNum);
}
};