贪心算法合集一

1005.K次取反后最大化的数组和

给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。) 

以这种方式修改数组后,返回数组可能的最大和(返回的是数组可能的最大和,可以排序)

答案用了两次贪心

我感觉只要在翻转K次,每次翻转最小的就行

1.是正数:翻转最小的整数

2.是负数:翻转最大的负数

134. 加油站

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

局部最优

局部最优在这里指的是:当我们从某个加油站i出发,沿着环路行驶,如果在到达某个加油站jj > i)之前,油箱中的油量(即curSum)变为负数,那么我们就知道从加油站i到加油站j(不包括j)之间的任何加油站都不能作为起始点。因为从任何这些加油站出发,我们都会在到达加油站j之前耗尽汽油。

推导全局最优

全局最优是找到一个起始加油站,使得从该加油站出发可以绕环路行驶一周而不耗尽汽油。我们如何通过局部最优来推导全局最优呢?

  1. 初始假设:我们从第一个加油站(编号为0)开始行驶。
  2. 计算curSum:在行驶过程中,我们不断累加每个加油站的剩余油量rest[i]curSum中。
  3. 检查curSum:如果curSum在某个点i变为负数,我们知道从0到i之间的任何加油站都不能作为起始点。
  4. 更新起始点:我们将起始点更新为i+1,并重置curSum为0。
  5. 继续行驶:从新的起始点开始,我们继续行驶并重复步骤2-4,直到我们遍历完所有的加油站。

在这个过程中,我们始终在寻找一个起始点,使得从这个点出发行驶到任何一个加油站时,curSum都保持非负。由于我们总是从curSum变为负数的下一个加油站开始重新计算,我们可以确保找到的是这样一个起始点:从这个点出发,curSum在整个环路中都是非负的,从而实现了全局最优。

贪心的体现

  1. 选择当前最优:在每一步中,我们都选择了一个局部最优解——即当前剩余油量最多的加油站作为下一步的起点。
  2. 无后效性:一旦我们选择了某个加油站作为起点,并决定行驶到下一个加油站,这个决定就不会影响我们未来在环路上的行驶。换句话说,我们的决策只取决于当前的状态,而不取决于之前的状态或未来的决策。
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 < gas.size(); i++) {
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            if (curSum < 0) {   // 当前累加rest[i]和 curSum一旦小于0
                start = i + 1;  // 起始位置更新为i+1
                curSum = 0;     // curSum从0开始
            }
        }
        if (totalSum < 0) return -1; // 说明怎么走都不可能跑一圈了
        return start;
    }
};

135. 分发糖果

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

你需要按照以下要求,帮助老师给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

那么这样下来,老师至少需要准备多少颗糖果呢?

对于这道题目,我们需要保证每个孩子获得的糖果数至少比其相邻的孩子(如果存在的话)多一个,如果其评分更高。这可以通过两次遍历实现:

  1. 从左到右遍历:对于每个孩子,如果其评分高于前一个孩子,则给予其比前一个孩子多一个的糖果数。
  2. 从右到左遍历:对于每个孩子,如果其评分高于后一个孩子,并且按从左到右遍历的规则给的糖果数小于其后一个孩子加一个的糖果数,则更新其糖果数为后一个孩子加一个的糖果数。
class Solution {
public:
    int candy(vector<int>& ratings) {
        int n = ratings.size();
        vector<int> left(n,1);       // 初始化分配空间
        for (int i = 0; i < n; i++){
            if(i > 0 && ratings[i] > ratings[i - 1]){
                left[i] = left[i - 1] + 1;
            } else {
                left[i] = 1;
            }
        }
        // 打印左边遍历的结果  
        for (int i = 0; i < n; i++) {  
            std::cout << "Left[" << i << "]: " << left[i] << std::endl;  
        }
        int totalnum = 0;
        vector<int> right(n, 1);
        for(int i = n - 1; i >= 0; i--){
            if(i < n-1 && ratings[i] > ratings[i+1]){
                right[i]=right[i+1]+1;
            } else{
                right[i] = 1;
            }
            totalnum += max(left[i], right[i]);
        }
        // 打印右边遍历的结果  
        for (int i = 0; i < n; i++) {  
            std::cout << "Right[" << i << "]: " << right[i] << std::endl;  
        } 

        return totalnum;
    }
};

406.根据身高重建队列

局部最优

在这个问题中,局部最优可以理解为:在当前考虑的范围内(即按照身高排序后的队列中),我们总是优先处理身高较高的个体,并根据其k值(即前面有k个身高不低于其的人)来将其放置在正确的位置上。这样做的目的是为了保持身高的有序性(从高到低),并且尽量满足每个人的k值要求。

全局最优

全局最优则是指:整个队列最终满足所有人的身高和位置要求,即对于队列中的任意一个人[h, k],其前面恰好有k个身高不低于h的人

  1. 身高排序:首先按照身高从高到低排序,确保了后面插入的人(身高较矮)不会影响到前面已经插入的人(身高较高)的相对位置,从而满足了身高的全局有序性。

  2. k值的利用:在身高排序的基础上,根据每个人的k值将其插入到队列中的正确位置。后插入的人不会影响到前面已经插入的人的位置,从而保证了k值的正确性。

    class Solution {
    private:  
        // Helper function to print the queue in a readable format  
        void printQueue(const vector<vector<int>>& que) {  
            for (const auto& person : que) {  
                cout << "[" << person[0] << ", " << person[1] << "] ";  
            }  
            cout << endl;  
        }  
    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];  
                cout << "Inserting person [" << people[i][0] << ", " << people[i][1] << "] at position " << position << ":" << endl;  
                cout << "Before insertion: ";  
                printQueue(que);  
                que.insert(que.begin() + position, people[i]);  
                cout << "After insertion: ";  
                printQueue(que);  
                cout << endl;  
            }
            return que;  
        }
    };  
    标准输出
    Inserting person [6, 0] at position 0:
    Before insertion: 
    After insertion: [6, 0] 
    
    Inserting person [5, 0] at position 0:
    Before insertion: [6, 0] 
    After insertion: [5, 0] [6, 0] 
    
    Inserting person [4, 0] at position 0:
    Before insertion: [5, 0] [6, 0] 
    After insertion: [4, 0] [5, 0] [6, 0] 
    
    Inserting person [3, 2] at position 2:
    Before insertion: [4, 0] [5, 0] [6, 0] 
    After insertion: [4, 0] [5, 0] [3, 2] [6, 0] 
    
    Inserting person [2, 2] at position 2:
    Before insertion: [4, 0] [5, 0] [3, 2] [6, 0] 
    After insertion: [4, 0] [5, 0] [2, 2] [3, 2] [6, 0] 
    
    Inserting person [1, 4] at position 4:
    Before insertion: [4, 0] [5, 0] [2, 2] [3, 2] [6, 0] 
    After insertion: [4, 0] [5, 0] [2, 2] [3, 2] [1, 4] [6, 0] 
    
    
    

  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值