贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
这一类的题目也没什么固定套路,理论上的求解过程如下:
贪心算法一般分为如下四步:
做题的时候,一般就是想想看本题的局部最优是怎么求解的,解出来看看是否满足全局最优,举几个例子如果都不是反例,那就可以用贪心算法做出来了。
注意要点:
文字的叙述不是很好说,简单而言就是小饼干先去喂饱小胃口!
下面贴出代码:
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int index = 0;
for (int i = 0; i < s.size(); i++)
{
if (index < g.size() && g[index] <= s[i]) {index++;}
}
return index;
}
};
int cmp(const int* a, const int* b)
{
return *a - *b;
}
int findContentChildren(int* g, int gSize, int* s, int sSize){
//先排序
qsort(g, gSize, sizeof(int), cmp);
qsort(s, sSize, sizeof(int), cmp);
int index = 0;
for (int i = 0; i < sSize; i++)
{
if (index < gSize && s[i] >= g[index]) {index++;}
}
return index;
}
注意要点:
纯文字叙述可能不是一下子能模拟出来,我把代码随想录上面这道题的精华图拉到这里,可以看一下,再配合文字和代码进行理解:
下面贴出代码:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if (nums.size() == 1) {return nums.size();}
int curdiff = 0;
int prediff = 0;
int ans = 1;
for (int i = 1; i < nums.size(); i++)
{
curdiff = nums[i] - nums[i - 1];
if ((prediff <= 0 && curdiff > 0) || (prediff >= 0 && curdiff < 0))
{
ans++;
prediff = curdiff;
}
}
return ans;
}
};
int wiggleMaxLength(int* nums, int numsSize){
if (numsSize < 2) {return numsSize;}
int prediff = 0, curdiff = 0;
int result = 1;
for (int i = 0; i < numsSize - 1; i++)
{
curdiff = nums[i + 1] - nums[i];
if ((prediff <= 0 && curdiff > 0) || (prediff >= 0 && curdiff < 0))
{
result++;
prediff = curdiff;
}
}
return result;
}
注意要点:
这道题其实不是很好想,记一下这个代码的逻辑会用就可以了,一下子没想出来很正常的。
下面贴出代码:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int ans = INT32_MIN;
int count = 0;
for (const int& num : nums)
{
count += num;
if (count > ans) {ans = count;}
if (count < 0) {count = 0;}
}
return ans;
}
};
int maxSubArray(int* nums, int numsSize){
int count = 0;
int result = INT_MIN;
for (int i = 0; i < numsSize; i++)
{
count += nums[i];
result = count > result ? count : result;
if (count < 0) {count = 0;}
}
return result;
}
注意要点:
下面贴出代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ans = 0;
for (int i = 0; i < prices.size() - 1; i++)
{
int now = prices[i + 1] - prices[i];
ans += now > 0 ? now : 0;
}
return ans;
}
};
int maxProfit(int* prices, int pricesSize){
int result = 0;
for (int i = 1; i < pricesSize; i++)
{
if (prices[i] > prices[i - 1])
{
result += prices[i] - prices[i - 1];
}
}
return result;
}
注意要点:
下面贴出代码:
class Solution {
public:
bool canJump(vector<int>& nums) {
int n = nums.size();
int cover = 0;
if (n == 1) {return 1;}
for (int i = 0; i <= cover; i++)
{
cover = max(nums[i] + i, cover);
if (cover >= n - 1) {return 1;}
}
return 0;
}
};
bool canJump(int* nums, int numsSize){
int cover = 0;
if (numsSize < 2) {return true;}
for (int i = 0; i <= cover; i++)
{
int now = nums[i] + i;
cover = now > cover ? now : cover;
if (cover >= numsSize - 1) {return true;}
}
return false;
}
注意要点:
下面贴出代码:
class Solution {
public:
int jump(vector<int>& nums) {
int n = nums.size();
if (n == 1) {return 0;}
int ans = 0;
int cur = 0, next = 0;
for (int i = 0; i < n; i++)
{
next = max(nums[i] + i, next);
if (i == cur)
{
ans++;
if (next >= n - 1) {break;}
cur = next;
}
}
return ans;
}
};
int jump(int* nums, int numsSize){
if (numsSize == 1) {return 0;}
int cur = 0;
int ans = 0;
int next = 0;
for (int i = 0; i < numsSize; i++)
{
next = (nums[i] + i) > next ? (nums[i] + i) : next;
if (i == cur)
{
cur = next;
ans++;
if (next >= numsSize - 1) {break;}
}
}
return ans;
}
注意要点:
下面贴出代码:
class Solution {
private:
static int cmp(const int a, const int b)
{
return abs(a) > abs(b);
}
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
sort(nums.begin(), nums.end(), cmp);
int n = nums.size();
for (int i = 0; i < n; i++)
{
if (nums[i] < 0 && k)
{
nums[i] = -nums[i];
k--;
}
}
if (k % 2) {nums[n - 1] = -nums[n - 1];}
int ans = 0;
for (const int& num : nums) {ans += num;}
return ans;
}
};
int cmp(const int* a, const int* b)
{
return abs(*b) - abs(*a);
}
int largestSumAfterKNegations(int* nums, int numsSize, int k){
qsort(nums, numsSize, sizeof(int), cmp);
int ans = 0;
for (int i = 0; i < numsSize; i++)
{
if (nums[i] < 0 && k)
{
nums[i] = -nums[i];
k--;
}
}
if (k % 2) {nums[numsSize - 1] = -nums[numsSize - 1];}
for (int i = 0; i < numsSize; i++) {ans += nums[i];}
return ans;
}
注意要点:
纯文字叙述不够直观,我把代码随想录的图片拉下来,可以更清晰的理解这个局部求最优的算法:
下面贴出代码:
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int cur = 0;
int total = 0;
int start = 0;
for (int i = 0; i < gas.size(); i++)
{
cur += gas[i] - cost[i];
total += gas[i] - cost[i];
if (cur < 0)
{
start = i + 1;
cur = 0;
}
}
if (total < 0) {return -1;}
return start;
}
};
int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){
int cur = 0, total = 0;
int start = 0;
for (int i = 0; i < gasSize; i++)
{
cur += gas[i] - cost[i];
total += gas[i] - cost[i];
if (cur < 0)
{
start = i + 1;
cur = 0;
}
}
if (total < 0) {return -1;}
return start;
}
注意要点:
这道题很难靠直接头脑中的模拟直接做出来,我这儿再次白嫖代码随想录的示意图,可以更清晰直观的看出解题的步骤:
下面贴出代码:
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int> candy(ratings.size(), 1);
for (int i = 1; i < candy.size(); i++)
{
if (ratings[i] > ratings[i - 1]) {candy[i] = candy[i - 1] + 1;}
}
for (int i = candy.size() - 2; i >= 0; i--)
{
if (ratings[i] > ratings[i+1]) {candy[i] = max(candy[i+1]+1, candy[i]);}
}
int sum = 0;
for (const int& num : candy) {sum += num;}
return sum;
}
};
int candy(int* ratings, int ratingsSize){
//贪心两次
int* candy = (int* )malloc(sizeof(int) * ratingsSize);
candy[0] = 1;
for (int i = 1; i < ratingsSize; i++)
{
if (ratings[i] > ratings[i - 1]) {candy[i] = 1 + candy[i - 1];}
else {candy[i] = 1;}
}
int sum = 0;
for (int i = ratingsSize - 1; i > 0; i--)
{
sum += candy[i];
if (ratings[i] < ratings[i - 1])
{
candy[i - 1] = candy[i - 1] > (candy[i] + 1) ? candy[i - 1] : candy[i] + 1;
}
}
sum += candy[0];
return sum;
}
注意要点:
下面贴出代码:
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
int five = 0, ten = 0, twenty = 0;
for (int bill : bills)
{
if (bill == 5) {five++;}
else if (bill == 10)
{
if (five <= 0) {return 0;}
ten++;
five--;
}
else
{
if (five && ten)
{
five--;
ten--;
}
else if (five >= 3) {five -= 3;}
else return 0;
}
}
return 1;
}
};
bool lemonadeChange(int* bills, int billsSize){
int* types = (int* )malloc(sizeof(int) * 2);
types[0] = types[1] = 0;
for (int i = 0; i < billsSize; i++)
{
if (bills[i] == 5)
{
types[0]++;
}
else if (bills[i] == 10)
{
types[1]++;
if (types[0] <= 0) {return false;}
types[0]--;
}
else
{
if (types[1])
{
types[1]--;
if (types[0] <= 0) {return false;}
types[0]--;
}
else if (types[0] < 3) {return false;}
else types[0] -= 3;
}
}
return true;
}
注意要点:
下面贴出代码:
class Solution {
private:
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];
}
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(), people.end(), cmp);
list<vector<int>> que;
for (int i = 0; i < people.size(); i++)
{
int pos = people[i][1];
list<vector<int>>:: iterator it = que.begin();
while (pos--) {it++;}
que.insert(it, people[i]);
}
return vector<vector<int>> (que.begin(), que.end());
}
};
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
int cmp(const void* a, const void* b)
{
int* tmp1 = *(int** )a;
int* tmp2 = *(int** )b;
//身高从高到低,如果一样,k从小到大
return tmp1[0] == tmp2[0] ? tmp1[1] - tmp2[1] : tmp2[0] - tmp1[0];
}
void move(int** people, int peopleSize, int start, int end)
{
for (int i = end; i > start; i--)
{
people[i] = people[i - 1];
}
}
int** reconstructQueue(int** people, int peopleSize, int* peopleColSize, int* returnSize, int** returnColumnSizes){
*returnSize = peopleSize;
*returnColumnSizes = (int* )malloc(sizeof(int) * peopleSize);
for (int i = 0; i < peopleSize; i++) {(*returnColumnSizes)[i] = 2;}
qsort(people, peopleSize, sizeof(int*), cmp);
//排序完以后遍历入队
for (int i = 0; i < peopleSize; i++)
{
//由k判断应该放在哪里
int pos = people[i][1];
int* tmp = people[i];
//需要插入则之后的位置直到他原先的所在前一个位置都要向后移位
move(people, peopleSize, pos, i);
people[pos] = tmp;
}
return people;
}
注意要点:
为了更简单的理解,我白嫖了代码随想录的示意图,后面的很多重叠区间的题目都可以套用这张图来思考:
下面贴出代码:
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) {
sort(points.begin(), points.end(), cmp);
int result = 1;
for (int i = 1; i < points.size(); i++)
{
if (points[i][0] > points[i - 1][1])
{
result++;
}
else
{
points[i][1] = min(points[i - 1][1], points[i][1]);
}
}
return result;
}
};
int cmp(const void* a, const void* b)
{
int* tmp1 = *(int** )a;
int* tmp2 = *(int** )b;
return tmp1[0] == tmp2[0] ? tmp1[1] > tmp2[1] : tmp1[0] > tmp2[0];
}
int findMinArrowShots(int** points, int pointsSize, int* pointsColSize){
if (!pointsSize) {return 0;}
//先按照第一个元素的大小从小到大排列
qsort(points, pointsSize, sizeof(int*), cmp);
int num = 1;
for (int i = 1; i < pointsSize; i++)
{
if (points[i][0] > points[i - 1][1])
{
num++; // 需要一支箭
}
else
{
// 更新重叠气球最小右边界
points[i][1] = points[i-1][1] < points[i][1] ? points[i-1][1] : points[i][1];
}
}
return num;
}
注意要点:
下面贴出代码:
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) {
sort(intervals.begin(), intervals.end(), cmp);
int count = 0;
int end = intervals[0][1];
for (int i = 1; i < intervals.size(); i++)
{
if (intervals[i][0] >= end) {end = intervals[i][1];}
else
{
count++;
end = min(end, intervals[i][1]);
}
}
return count;
}
};
int cmp(const void* a, const void* b)
{
int* tmp1 = *(int** )a;
int* tmp2 = *(int** )b;
return tmp1[0] == tmp2[0] ? tmp1[1] > tmp2[1] : tmp1[0] > tmp2[0];
}
int eraseOverlapIntervals(int** intervals, int intervalsSize, int* intervalsColSize){
if (!intervalsSize) {return 0;}
qsort(intervals, intervalsSize, sizeof(int*), cmp);
int num = 0;
for (int i = 1; i < intervalsSize; i++)
{
if (intervals[i][0] < intervals[i - 1][1])
{
num++;
if (intervals[i][1] > intervals[i-1][1])
{
intervals[i][1] = intervals[i-1][1];
}
}
}
return num;
}
注意要点:
合并的逻辑如下图所示,这里再次把代码随想录的图片粘贴上来:
下面贴出代码:
class Solution {
private:
static bool cmp(const vector<int>& a, const vector<int>& b)
{
return a[0] < b[0];
}
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), cmp);
vector<vector<int>> result;
result.push_back(intervals[0]);
for (int i = 1; i < intervals.size(); i++)
{
if (result.back()[1] >= intervals[i][0])
{
result.back()[1] = max(result.back()[1], intervals[i][1]);
}
else
{
result.push_back(intervals[i]);
}
}
return result;
}
};
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
int cmp(const void* a, const void* b)
{
//左边元素从小到大,如果相等,则右边元素从大到小
int* tmp1 = *(int** )a;
int* tmp2 = *(int** )b;
return tmp1[0] == tmp2[0] ? tmp1[1] < tmp2[1] : tmp1[0] > tmp2[0];
}
int** merge(int** intervals, int intervalsSize, int* intervalsColSize, int* returnSize, int** returnColumnSizes){
qsort(intervals, intervalsSize, sizeof(int*), cmp);
//开辟答案的二维数组,每列两个元素
int** ans = (int** )malloc(sizeof(int* ) * intervalsSize);
for (int i = 0; i < intervalsSize; i++)
{
ans[i] = (int* )malloc(sizeof(int) * 2);
}
*returnSize = 0;
ans[(*returnSize)++] = intervals[0];
for (int i = 1; i < intervalsSize; i++)
{
//发生重叠
if (intervals[i][0] <= ans[(*returnSize) - 1][1])
{
ans[(*returnSize) - 1][1] = fmax(ans[(*returnSize) - 1][1], intervals[i][1]);
}
else
{
ans[(*returnSize)++] = intervals[i];
}
}
*returnColumnSizes = (int* )malloc(sizeof(int) * (*returnSize));
for (int i = 0; i < *returnSize; i++)
{
(*returnColumnSizes)[i] = 2;
}
return ans;
}
注意要点:
这一题我做过了一遍,第二遍还是忘了怎么做,把图贴在下面,更好理解和记忆这题的解题思路和流程:
下面贴出代码:
class Solution {
public:
vector<int> partitionLabels(string s) {
int hash[26] = {0};
for (int i = 0; i < s.size(); i++) {hash[s[i] - 'a'] = i;}
vector<int> result;
int left = 0, right = 0;
for (int i = 0; i < s.size(); i++)
{
right = max(right, hash[s[i] - 'a']);
if (i == right)
{
result.push_back(right - left + 1);
left = right + 1;
}
}
return result;
}
};
/**
1. Note: The returned array must be malloced, assume caller calls free().
*/
int* partitionLabels(char * s, int* returnSize){
int* hash = (int* )malloc(sizeof(int) * 26);
//遍历字符串,把当前位置放在对应的hash表中,最后就能得到这个字母最后一次出现的位置
for (int i = 0; i < strlen(s); i++)
{
hash[s[i] - 'a'] = i;
}
int* ans = (int* )malloc(sizeof(int) * strlen(s));
*returnSize = 0;
int left = 0, right = 0;
for (int i = 0; i < strlen(s); i++)
{
right = right > hash[s[i] - 'a'] ? right : hash[s[i] - 'a'];
if (i == right)
{
ans[(*returnSize)++] = right - left + 1;
left = right + 1;
}
}
return ans;
}
注意要点:
下面贴出代码:
class Solution {
public:
int monotoneIncreasingDigits(int n) {
string strNum = to_string(n);
int flag = strNum.size();
for (int i = strNum.size() - 1; i > 0; i--)
{
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);
}
};
int my_itoa(int n, char* s)
{
int count = 0;
while (n)
{
s[count++] = n % 10 + '0';
n /= 10;
}
s[count] = '\0';
for (int i = 0; i < count / 2; i++)
{
int tmp = s[i];
s[i] = s[count - i - 1];
s[count - i - 1] = tmp;
}
return count;
}
int monotoneIncreasingDigits(int n){
char* s = (char* )malloc(sizeof(char) * 11);
//字符串操作每一位的数字更简单
int len = my_itoa(n, s);
//printf("s[0] = %c\n", s[0]);
int flag = len;
for (int i = len - 1; i > 0; i--)
{
if (s[i - 1] > s[i])
{
flag = i;
s[i - 1]--;
}
}
for (int i = flag; i < len; i++)
{
s[i] = '9';
}
return atoi(s);
}
注意要点:
这道题比较难,主要是要对每个节点进行状态的分类加粗样式,同时逻辑推导两个孩子的状态应该对应的父亲的状态。
下面贴出代码:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
private:
int result;
// 一共有三种状态
// 0代表无覆盖,1代表有摄像头,2代表有覆盖
int traversal(TreeNode* cur)
{
if (!cur) {return 2;}
int left = traversal(cur->left);
int right = traversal(cur->right);
if (left == 2 && right == 2) {return 0;}
if (!left || !right)
{
result++;
return 1;
}
if (left == 1 || right == 1) {return 2;}
return -1;
}
public:
int minCameraCover(TreeNode* root) {
result = 0;
if (!traversal(root)) {result++;}
return result;
}
};
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
int traversal(struct TreeNode* root, int* result)
{
//后续遍历
//0是无覆盖,1是有摄像头,2是有覆盖
if(!root) {return 2;} //空节点说明是叶子,不要摄像头且应该有覆盖到
int left = traversal(root->left, result);
int right = traversal(root->right, result);
//左右均覆盖,那此时这个节点肯定是无覆盖
if (left == 2 && right == 2) {return 0;}
//有一个无覆盖,另一个是有摄像头或有覆盖,这个节点应该有摄像头
if (!left || !right)
{
(*result)++;
return 1;
}
//左右节点有一个摄像头,那这个节点肯定有覆盖
if (left == 1 || right == 1) {return 2;}
return -1; //只是为了函数结构,实际不会进入这里
}
int minCameraCover(struct TreeNode* root){
int result = 0;
//还要考虑头结点,如果未覆盖需要+1
if (!traversal(root, &result)) {result++;}
return result;
}
贪心法,比较找到解题套路,都是看着局部最优能推导出全局最优,且找不出反例,那就是贪心法解决问题了。
贪心法可以解决比如之前说的发饼干、找零、取反的问题;同时也能解决序列的一些单调性相关问题;
贪心法还能解决股票投资问题(之后动态规划还会再讲);还有经典的区间重叠的各种问题,都是先对一个维度排序之后进行贪心;
困难的比如有两个相互影响的维度的贪心,这时一定要选择一边先贪心,然后再去考虑另一个维度的贪心情况!
最后我白嫖了代码随想录总结的贪心法问题以及一些注意要点,如下图所示:
406题中:vector的插入比较慢,就可以用list(底层是链表实现)
list<vector<int>> que;
for (int i = 0; i < people.size(); i++)
{
int pos = people[i][1];
list<vector<int>>:: iterator it = que.begin();
while (pos--) {it++;}
que.insert(it, people[i]);
}
同时,在C中的数组插入可以通过move然后赋值:
//需要插入则之后的位置直到他原先的所在前一个位置都要向后移位
move(people, peopleSize, pos, i);
people[pos] = tmp;
56题中,可以直接对vector的尾部元素进行提取进而比较是否需要重叠:
vector<vector<int>> result;
result.back()[1] >= intervals[i][0]
738题中,涉及字符串和整型数的相互转换:
string strNum = to_string(n);
stoi(strNum)