代码随想录笔记--贪心算法篇

目录

1--贪心算法

2--分发饼干

3--摆动序列

4--最大子序和

5--买卖股票最佳时机II

6--跳跃游戏

7--跳跃游戏II

8--K次取反后最大化的数组和

9--加油站

10--分发糖果

11--柠檬水找零

12--根据身高重建队列

13--用最少数量的箭引爆气球

14--无重叠区间

15--划分字母区间

16--合并区间

17--单调自增的数字

18--监督二叉树


1--贪心算法

主要思路:

        通过局部最优推导全局最优;

2--分发饼干

主要思路:

        基于贪心算法,每次都尽可能用大的饼干去喂胃口大的孩子,贪心地节省饼干;

#include <iostream>
#include <vector>
#include <algorithm>

class Solution {
public:
    int findContentChildren(std::vector<int>& g, std::vector<int>& s) {
        if(s.size() == 0) return 0;
        // 从小到大排序饼干
        std::sort(g.begin(), g.end());
        std::sort(s.begin(), s.end());
        
        int res = 0; // 结果
        int cookie_idx = s.size() - 1; // 从最后一个饼干开始分发

        // 贪心地每次用大饼干去喂胃口大的孩子
        for(int child_idx = g.size() - 1; child_idx >= 0; child_idx--){
            if(cookie_idx >= 0 && s[cookie_idx] >= g[child_idx]){ // 饼干能满足孩子的胃口
                res++;
                cookie_idx--;
            }
        }
        return res;
    }
};

int main(int argc, char* argv[]){
    // g = [1,2,3], s = [1,1]
    std::vector<int> child = {1, 2, 3};
    std::vector<int> cookie = {1, 1};
    Solution S1;
    int res = S1.findContentChildren(child, cookie);
    std::cout << res << std::endl;
    return 0;
}

3--摆动序列

主要思路:
        基于贪心算法,贪心地计算山谷和山峰的个数;

#include <iostream>
#include <vector>

class Solution {
public:
    int wiggleMaxLength(std::vector<int>& nums) {
        int pre_diff = 0;
        int cur_diff = 0;
        int result = 1; // 默认最右边元素是一个摆动
        for(int i = 0; i < nums.size() - 1; i++){
            cur_diff = nums[i+1] - nums[i];
            if(pre_diff <= 0 && cur_diff > 0 || pre_diff >= 0 && cur_diff < 0){
                result ++;
                pre_diff = cur_diff; // 产生摆动才更新 pre_diff 避免平波递增的情况
            }
        }
        return result;
    }
};

int main(int argc, char* argv[]){
    // nums = [1,7,4,9,2,5]
    std::vector<int> test = {1, 7, 4, 9, 2, 5};
    Solution S1;
    int res = S1.wiggleMaxLength(test);
    std::cout << res << std::endl;
    return 0;
}

4--最大子序和

主要思路:
        贪心地纳入当前数,如果和为负值,就将和置为 0,否则保留;

#include <iostream>
#include <vector>
#include <limits.h>

class Solution {
public:
    int maxSubArray(std::vector<int>& nums) {
        int sum = 0;
        int max_sum = INT_MIN;
        for(int i = 0; i < nums.size(); i++){
            sum = sum + nums[i];
            if(sum > max_sum) max_sum = sum;
            if(sum < 0) sum = 0;
        }
        return max_sum;
    }   
};

int main(int argc, char* argv[]){
    // nums = [-2,1,-3,4,-1,2,1,-5,4]
    std::vector<int> nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    Solution S1;
    int res = S1.maxSubArray(nums);
    std::cout << res << std::endl;
    return 0;
}

5--买卖股票最佳时机II

#include <iostream>
#include <vector>

class Solution {
public:
    int maxProfit(std::vector<int>& prices) {
        int maxProfit = 0;
        for(int i = 1; i < prices.size(); i++){
            int profit = prices[i] - prices[i-1];
            if(profit > 0) maxProfit += profit;
        }
        return maxProfit;
    }
};

int main(int argc, char* argv[]){
    // prices = [7,1,5,3,6,4]
    std::vector<int> prices = {7, 1, 5, 3, 6, 4};
    Solution S1;
    int res = S1.maxProfit(prices);
    std::cout << res;                                     
    return 0;
}

主要思路:

        基于贪心算法,贪心地计算当天利润(当天价格减去前一天价格)(总感觉怪怪的,但又举不出反例),只贪心地考虑正利润;

6--跳跃游戏

主要思路:

        贪心地计算当前的最大覆盖范围,当跳跃覆盖范围包含最后一个下标时,返回True;

#include <iostream>
#include <vector>

class Solution {
public:
    bool canJump(std::vector<int>& nums) {
        int cover = 0;
        for(int cur = 0; cur <= cover; cur++){
            cover = std::max(cover, cur + nums[cur]);
            if(cover >= nums.size() - 1) return true;
        }
        return false;
    }
};

int main(int argc, char* argv[]){
    // nums = [2,3,1,1,4]
    std::vector<int> nums = {2, 3, 1, 1, 4};
    Solution S1;
    int res = S1.canJump(nums);
    if(res) std::cout << "true" << std::endl;
    else std::cout << "false" << std::endl;                                   
    return 0;
}

7--跳跃游戏II

主要思路:

        再不需要额外走多一步的情况下,贪心地计算能够到达最远的地方(在当前覆盖范围里);

        假如目前已经走到上一步能够到达最远的地方了,只能在上一次的覆盖范围内再额外走多一步,这时覆盖范围会更新;判断当前覆盖范围是否包含终点;

#include <iostream>
#include <vector>

class Solution {
public:
    int jump(std::vector<int>& nums) {
        if(nums.size() == 1) return 0;
        int cover_dis = 0;
        int pre_dis = 0;
        int res = 0;
        for(int i = 0; i < nums.size(); i++){
            cover_dis = std::max(cover_dis, i + nums[i]); // 最远可以到达的地方
            if(i == pre_dis){
                res++; // 已经走到上一次记录时,能够走到最远的地方,只能额外向前走多一步了
                pre_dis = cover_dis; // 额外走多一步,可以走到最远的地方
                if(pre_dis >= nums.size() - 1) break; // 额外走多一步,已经可以覆盖终点了,直接返回
            }
        }
        return res;
    }
};

int main(int argc, char* argv[]){
    // nums = [2, 3, 1, 1, 4]
    std::vector<int> nums = {2, 3, 1, 1, 4};
    Solution S1;
    int res = S1.jump(nums);
    std::cout << res << std::endl;                                
    return 0;
}

8--K次取反后最大化的数组和

主要思路:

        对数组按绝对值大小进行排序,贪心地先对绝对值最大的负数进行取反;对所有负数取反后,假如还剩下取反次数,则对绝对值最小的数进行取反;

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>

class Solution {
public:
    int largestSumAfterKNegations(std::vector<int>& nums, int k) {
        // 按绝对值从大到小排序
        std::sort(nums.begin(), nums.end(), [](int a, int b)->bool{
            return std::abs(a) > std::abs(b);
        });

        for(int i = 0; i < nums.size(); i++){
            if(nums[i] < 0 && k > 0){
                nums[i] = nums[i]*(-1);
                k--;
            }
        }
        if(k % 2 == 1){ // k 为奇数,对绝对值最小的进行取反
            nums[nums.size() - 1] *= -1;
        }
        return std::accumulate(nums.begin(), nums.end(), 0);
    }
};

int main(int argc, char* argv[]){
    // nums = [4,2,3], k = 1
    std::vector<int> test = {4, 2, 3};
    int k = 1;
    Solution S1;
    int res = S1.largestSumAfterKNegations(test, k);
    std::cout << res << std::endl;
    return 0;
}

9--加油站

主要思路:

        基于贪心算法,视频讲解:加油站

#include <iostream>
#include <vector>

class Solution {
public:
    int canCompleteCircuit(std::vector<int>& gas, std::vector<int>& cost) {
        int start = 0, cur_sum = 0, tol_sum = 0;
        for(int i = 0; i < gas.size(); i++){
            cur_sum += gas[i] - cost[i]; // 经过当前加油站后剩下的油(假设能跑一圈)
            tol_sum += gas[i] - cost[i]; // 累积剩下的油
            if(cur_sum < 0){ // 正常从加油站0出发,到达不了该加油站
                start = i + 1; // 尝试从第一个不能到达的加油站的下一个加油站出发
                cur_sum = 0; 
            }
        }
        if(tol_sum < 0) return -1; // 跑完全程的累积剩油量为负数,返回-1
        return start; // 剩油量为正数,表明在第一个不能到达的加油站之后,经过剩下的加油站,剩油量为正数
    }
};

int main(int argc, char* argv[]){
    // gas = [1,2,3,4,5], cost = [3,4,5,1,2]
    std::vector<int> gas = {1, 2, 3, 4, 5};
    std::vector<int> cost = {3, 4, 5, 1, 2};
    Solution S1;
    int res = S1.canCompleteCircuit(gas, cost);
    std::cout << res << std::endl;
    return 0;
}

10--分发糖果

主要思路:

        基于贪心算法,从左到右遍历,当一个孩子比左边的孩子高时,贪心地只多分发一个糖果(相对于左边的孩子);

        再从右到左遍历,当一个孩子比右边的孩子高时,贪心地只多分发一个糖果(相对于右边的孩子),一个孩子分发的糖果取决于上述两个遍历过程的最大值;

#include <iostream>
#include <vector>
#include <numeric>

class Solution {
public:
    int candy(std::vector<int>& ratings) {
        // 从左到右遍历
        std::vector<int> candy(ratings.size(), 1); // 初始化全部最小分发一个糖果
        for(int i = 0; i < ratings.size(); i++){
            if(i > 0 && ratings[i] > ratings[i-1]){ // 比左边的孩子高,贪心地只多分一个
                candy[i] = candy[i-1] + 1;
            }
        }

        // 从右到左遍历
        for(int i = ratings.size() - 1; i >= 0; i--){
            if(i < ratings.size() - 1 && ratings[i] > ratings[i+1]){ // 比右边的孩子高,贪心地只多分一个
                candy[i] = std::max(candy[i], candy[i+1] + 1);
            }
        }
        return std::accumulate(candy.begin(), candy.end(), 0);
    }
};

int main(int argc, char* argv[]){
    // ratings = [1,0,2]
    std::vector<int> test = {1, 0, 2};
    Solution S1;
    int res = S1.candy(test);
    std::cout << res << std::endl;
    return 0;
}

11--柠檬水找零

主要思路:

        基于贪心算法,当遇到 20 美元时,贪心地尽可能保留 5 美元,使用一张 10 美元和一张 5 美元来找零;

#include <iostream>
#include <vector>

class Solution {
public:
    bool lemonadeChange(std::vector<int>& bills) {
        int five = 0, ten = 0, twenty = 0;
        for(int i = 0; i < bills.size(); i++){
            if(bills[i] == 5){
                five++;
            }
            else if(bills[i] == 10 && five > 0){
                ten++;
                five--;
            }
            else if(bills[i] == 20 && ten > 0 && five > 0){ // 优先使用 1 张 10 美元和 1 张 5 美元找零
                twenty++;
                ten--;
                five--;
            }
            else if(bills[i] == 20 && five >= 3){
                twenty++;
                five -= 3;
            }
            else{
                return false;
            }
        }
        return true;
    }
};

int main(int argc, char* argv[]){
    // bills = [5,5,5,10,20]
    std::vector<int> nums = {5, 5, 5, 10, 20};
    Solution S1;
    bool res = S1.lemonadeChange(nums);
    if(res) std::cout << "true" << std::endl;
    else std::cout << "false" << std::endl;
    return 0;
}

12--根据身高重建队列

主要思路:

        题目的意思是对于重构后的队列,每个人前面只有 k_i 个人比其高;

        可以先对数组队列按照身高进行从大到小排序(身高相同,k小的排在前面),然后遍历排序后的数组队列,按照 k_i 插入到对应的位置;

        因为已经按身高进行排序了,因此可以确保遍历到的元素,其前面的元素肯定都是更高的,因此插入到前面指定位置 position,也能确保前面有 position 个人相对更高;

#include <iostream>
#include <vector>
#include <algorithm>

class Solution {
public:
    std::vector<std::vector<int>> reconstructQueue(std::vector<std::vector<int>>& people) {
        std::sort(people.begin(), people.end(), [](std::vector<int> a, std::vector<int> b){ 
                                                    if (a[0] == b[0]) return a[1] < b[1]; return a[0] > b[0];});
        std::vector<std::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;
    }
};

int main(int argc, char* argv[]){
    // people = [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
    std::vector<std::vector<int>> test = {{7, 0}, {4, 4}, {7, 1}, {5, 0}, {6, 1}, {5, 2}};
    Solution S1;
    std::vector<std::vector<int>> res = S1.reconstructQueue(test);
    for(auto vec : res){
        for(int val : vec){
            std::cout << val << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

13--用最少数量的箭引爆气球

主要思路:

        基于贪心算法,贪心地将尽可能多的气球重叠在一起,使用一支箭引爆重叠的气球;

#include <iostream>
#include <vector>
#include <algorithm>

class Solution {
private:
    static bool cmp(const std::vector<int>& a, const std::vector<int>& b) {
        return a[0] < b[0];
    }
public:
    int findMinArrowShots(std::vector<std::vector<int>>& points) {
        if (points.size() == 0) return 0;
        // 按左边界进行排序,使重叠的气球紧邻
        std::sort(points.begin(), points.end(), cmp);
        int res = 0;
        for(int i = 0; i < points.size(); i++){
            if(i > 0 && points[i][0] <= points[i-1][1]){ // 左边界小于上一个气球的右边界,表明两者重叠
                points[i][1] = std::min(points[i][1], points[i-1][1]); // 更新重叠区域的右边界
            }
            else{ // 不重叠
                res++;
            }
        }
        return res;
    }
};

int main(int argc, char* argv[]){
    // points = [[10,16],[2,8],[1,6],[7,12]]
    std::vector<std::vector<int>> test = {{10, 16}, {2, 8}, {1, 6}, {7, 12}};
    Solution S1;
    int res = S1.findMinArrowShots(test);
    std::cout << res << std::endl;

    return 0;
}

14--无重叠区间

主要思路:

        对区间进行排序,让尽可能多的区间重叠在一起,判断重叠区间的个数,移除重叠区间,返回结果即可;

#include <iostream>
#include <vector>
#include <algorithm>

class Solution {
public:
    int eraseOverlapIntervals(std::vector<std::vector<int>>& intervals) {
        std::sort(intervals.begin(), intervals.end(), 
                    [](std::vector<int>& a, std::vector<int>& b){
                        return a[0] < b[0]; // 按左边界从小到大排序
                    });
                    
        int res = 0;
        for(int i = 1; i < intervals.size(); i++){
            if(intervals[i][0] < intervals[i-1][1]){ // 重叠
                res++;
                intervals[i][1] = std::min(intervals[i][1], intervals[i-1][1]); // 移除一个区间后,更新右边界
            }
            else{ // 不重叠
                continue;
            }
        }
        return res;
    }
};

int main(int argc, char* argv[]){
    // intervals = [[1,2],[2,3],[3,4],[1,3]]
    std::vector<std::vector<int>> intervals = {{1, 2}, {2, 3}, {3, 4}, {1, 3}};
    Solution S1;
    int res = S1.eraseOverlapIntervals(intervals);
    std::cout << res << std::endl;
    return 0;
}

15--划分字母区间

主要思路:

        用哈希表记录每一个字母最远出现的位置,然后遍历字符串,记录遍历区间中字母的最远位置,当遍历位置到达区间字母的最远位置时,表明包含了区间中所有的字母,记录区间的大小;

#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>

class Solution {
public:
    std::vector<int> partitionLabels(std::string s) {
        std::unordered_map<char, int> hash_map;
        for(int i = 0; i < s.length(); i++){
            hash_map[s[i]] = i; // 覆盖更新字母出现过的最远位置
        }

        int cur_right = 0;
        std::vector<int> res;
        int start = 0;
        for(int i = 0; i < s.length(); i++){
            cur_right = std::max(hash_map[s[i]], cur_right); // 当前遍历的字母里的最远位置
            if(cur_right == i){ // 当前已经遍历到最远位置了
                res.push_back(i - start + 1); // 字母的个数
                start = i+1; // 更新左边界
            }
        }
        return res;
    }
};

int main(int argc, char* argv[]){
    // s = "ababcbacadefegdehijhklij"
    std::string test = "ababcbacadefegdehijhklij";
    Solution S1;
    std::vector<int> res = S1.partitionLabels(test);
    for(int val : res) std::cout << val << " ";
    std::cout << std::endl;
    return 0;
}

16--合并区间

主要思路:

        对区间按左边界进行排序,遍历区间,如果当前区间与上一个区间不重叠,则记录上一个区间;如果当前区间与上一个区间重叠,则合并和更新当前区间的范围;

#include <iostream>
#include <vector>
#include <algorithm>

class Solution {
public:
    std::vector<std::vector<int>> merge(std::vector<std::vector<int>>& intervals) {
        // 对区间按左边界进行排序,让区间尽可能重叠
        std::sort(intervals.begin(), intervals.end(), 
                    [](std::vector<int> &a, std::vector<int> &b){
                        return a[0] < b[0];
                    });
        std::vector<std::vector<int>> res;
        int i;            
        for(i = 1; i < intervals.size(); i++){
            if(intervals[i][0] > intervals[i - 1][1]){ // 不重叠
                res.push_back(intervals[i - 1]);
            }
            else{ // 重叠,合并区间
                intervals[i][0] = std::min(intervals[i-1][0], intervals[i][0]);
                intervals[i][1] = std::max(intervals[i-1][1], intervals[i][1]);
            }
        }
        // 记录最后一个区间
        res.push_back(intervals[i-1]);
        return res;
    }
};

int main(int argc, char* argv[]){
    // intervals = [[1,3],[2,6],[8,10],[15,18]]
    std::vector<std::vector<int>> test = {{1, 3}, {2, 6}, {8, 10}, {15, 18}};
    Solution S1;
    std::vector<std::vector<int>> res = S1.merge(test);
    for(auto vec : res){
        for(int val : vec) std::cout << val << " ";
        std::cout << std::endl;
    }
    return 0;
}

17--单调递增的数字

主要思路:

        从后向前遍历数字,记录不是单调递增的位置,将该位置的数值减1;

        从记录位置向后遍历,将所有数值贪心地更改为 9;

#include <iostream>
#include <string>
 
class Solution {
public:
    int monotoneIncreasingDigits(int n) {
		std::string str = std::to_string(n);
		int flag = str.size();
		for(int i = str.size() - 2; i >= 0; i--){
			if(str[i] > str[i+1]){ // 不符合单调递增
				flag = i; // 记录位置
				str[flag] = str[flag] - 1;
			}
		}
		for(int i = flag + 1; i < str.size(); i++){
			str[i] = '9';
		}
		return atoi(str.c_str());
    }
};
 
//test
int main(int argc, char *argv[]) {
	int n = 332;
	Solution S1;
	int res = S1.monotoneIncreasingDigits(n);
	std::cout << res << std::endl;
	return 0;
}

18--监控二叉树

主要思路:

        对于每一个结点,有三种状态:无覆盖(2)、有覆盖但没安装摄像头(1)、有覆盖且安装摄像头(0);

        为了计算最小摄像头数量,采用后序遍历,从底至上;

        对于 NULL 结点(叶子结点的孩子)应置为 1 状态,对于叶子结点应置为 1 状态,对于叶子节点的父亲,应置为 0 状态;

        对于每一个结点,当有一个孩子处于 2 状态时,将在这结点安装摄像头,即设置为 0 状态;当两个孩子都处于 1 状态时,将这结点设置为 2 状态;当两个孩子一个处于 1 状态,另一个处于 0 状态,则将结点设置为 1 状态;

#include <iostream>
 
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 {
public:
    int minCameraCover(TreeNode* root) {
		int flag = dfs(root);
		if(flag == 2) num++; // 根节点处于无监控状态
		return num;
    }

	int dfs(TreeNode *root){
		if(root == nullptr) return 1; // 有监控状态

		int left = dfs(root->left); // 左
		int right = dfs(root->right); // 右
		// 根
		if(left == 2 || right == 2){ // 有一个孩子处于无监控状态,必须安装摄像头
			num++;
			return 0;
		}
		if(left == 1 && right == 1){ // 两个孩子都处于有监控状态,但都没安装摄像头
			return 2; // 表示此时的节点是处于无监控状态的
		}
		// 一个孩子装有摄像头,另一个孩子处于监控状态,或两个孩子都装有摄像头
		if((left == 1 && right == 0) || (left == 0 && right == 1) || (left == 0 && right == 0)){
			return 1; // 表示当前结点是处于有监控状态的
		}
		return -1; // 出错情况
	}
private:
	int num = 0;
};

int main(int argc, char *argv[]) {
	// [0, 0, null, 0, 0]
	TreeNode* Node1 = new TreeNode(0);
	TreeNode* Node2 = new TreeNode(0);
	TreeNode* Node3 = new TreeNode(0);
	TreeNode* Node4 = new TreeNode(0);
	Node1->left = Node2;
	Node2->left = Node3;
	Node2->right = Node4;

	Solution S1;
	int res = S1.minCameraCover(Node1);
	std::cout << res << std::endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值