算法【贪心经典题目专题3】

发布于:2025-02-19 ⋅ 阅读:(44) ⋅ 点赞:(0)
题目一

测试链接:581. 最短无序连续子数组 - 力扣(LeetCode)

分析:这道题只需要找到左边界和右边界即可,对于左边界代表左边界右边的最小值都比左边界大,右边界代表右边界左边的最大值都比右边界小。代码如下。

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        if(nums.size() == 1){
            return 0;
        }
        int length = nums.size();
        int left_max = nums[0];
        int left = 0;
        for(int i = 1;i < length;++i){
            if(left_max > nums[i]){
                left = i;
            }
            left_max = max(left_max, nums[i]);
        }
        int right_min = nums[length-1];
        int right = length - 1;
        for(int i = length-2;i >= 0;--i){
            if(right_min < nums[i]){
                right = i;
            }
            right_min = min(right_min, nums[i]);
        }
        return max(left - right + 1, 0);
    }
};

题目二

测试链接:https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists/

分析:将每一个列表的第一个数放入有序表中,即可得到这些数组成的区间,将最小的数弹出,然后将弹出的数字所属列表中的第二个数加入游戏表,得到一个新的区间,更新区间,直到其中一个列表遍历完即可得到答案。代码如下。

class Solution {
public:
    int nums_index[3500] = {0};
    vector<int> smallestRange(vector<vector<int>>& nums) {
        multimap<int, int> table;
        vector<int> ans;
        ans.push_back(-100000);
        ans.push_back(100000);
        int len = 200001;
        int length = nums.size();
        for(int i = 0;i < length;++i){
            table.insert(pair<int, int>(nums[i][0], i));
        }
        map<int, int>::iterator minValue, maxValue;
        int from;
        while (true)
        {
            minValue = table.begin();
            maxValue = table.end();
            --maxValue;
            if((*maxValue).first - (*minValue).first + 1 < len){
                len = (*maxValue).first - (*minValue).first + 1;
                ans[0] = (*minValue).first;
                ans[1] = (*maxValue).first;
            }
            from = (*minValue).second;
            if(++nums_index[from] == nums[from].size()){
                break;
            }
            table.erase(minValue);
            table.insert(pair<int, int>(nums[from][nums_index[from]], from));
        }
        return ans;
    }
};

题目三

组团买票

景区里一共有m个项目,景区的第i个项目有如下两个参数:game[i] = { Ki, Bi },Ki、Bi一定是正数。Ki代表折扣系数,Bi代表票价,举个例子 : Ki = 2, Bi = 10,如果只有1个人买票,单张门票的价格为 : Bi - Ki * 1 = 8,所以这1个人游玩该项目要花8元,如果有2个人买票,单张门票的价格为 : Bi - Ki * 2 = 6,所以这2个人游玩该项目要花6 * 2 = 12元,如果有5个人买票,单张门票的价格为 : Bi - Ki * 5 = 0,所以这5个人游玩该项目要花5 * 0 = 0元,如果有更多人买票,都认为花0元。于是可以认为,如果有x个人买票,单张门票的价格为 : Bi - Ki * x,x个人游玩这个项目的总花费是 : max { x * (Bi - Ki * x), 0 }。单位一共有n个人,每个人最多可以选1个项目来游玩,也可以不选任何项目,由你去按照上面的规则,统一花钱购票。你想知道自己需要准备多少钱,就可以应付所有可能的情况,返回这个最保险的钱数。

1 <= M、N、Ki、Bi <= 10^5

来自真实大厂笔试,没有在线测试。

分析:最保险的钱数就是让景区赚的钱最大,可以考虑对每一个来的人进行判断,如果这个人还可以让景区赚到钱,就将这个人分配到赚钱最多的项目;如果不能赚到钱,直接停止。代码如下。

class Solution {
public:
    class Game{
        public:
            int ki;
            int bi;
            int people;

            int earn(){
                return bi - 2 * people * ki - ki;
            }

        Game(int a, int b, int c){
            ki = a;
            bi = b;
            people = c;
        }
    };
    int maxMoney(vector<vector<int>>& game, int n) {
        int length = game.size();
        auto cmp = [](Game& g1, Game& g2){
            return g1.earn() < g2.earn();
        };
        priority_queue<Game, vector<Game>, decltype(cmp)> p(cmp);
        for(int i = 0;i < length;++i){
            Game temp(game[i][0], game[i][1], 0);
            p.push(temp);
        }
        int ans = 0;
        for(int i = 1;i <= n;++i){
            Game temp = p.top();
            p.pop();
            if(temp.earn() <= 0){
                break;
            }
            ans += temp.earn();
            ++temp.people;
            p.push(temp);
        }
        return ans;
    }
};

题目四

平均值最小累加和

给定一个数组arr,长度为n,再给定一个数字k,表示一定要将arr划分成k个集合,每个数字只能进一个集合。返回每个集合的平均值都累加起来的最小值,平均值向下取整

1 <= n <= 10^5  0 <= arr[i] <= 10^5   1 <= k <= n

来自真实大厂笔试,没有在线测试。

分析:因为一个大的数和一个小的数进行求平均值会将小的数拉高,所以将前k-1个小的数单独成k-1个集合,剩下的数成一个集合,那样求出来就是每个集合的平均值累加最小。代码如下。

class Solution {
public:
    int minAverage(vector<int>& arr, int k) {
        int length = arr.size();
        int sum = 0;
        for(int i = 0;i < k-1;++i){
            sum += arr[i];
        }
        int temp = 0;
        for(int i = k-1;i < length;++i){
            temp += arr[i];
        }
        temp /= (length - k + 1);
        sum += temp;
        return sum;
    }
};

题目五

执行所有任务的最少初始电量

每一个任务有两个参数,需要耗费的电量、至少多少电量才能开始这个任务。返回手机至少需要多少的初始电量,才能执行完所有的任务。

来自真实大厂笔试,没有在线测试。

分析:这道题的基本思路是从电量为0开始往回推且可以看出将任务耗费电量大的放前面、至少多少电量小的放前面,但是如果单一考虑这两个方向,总会找到反例,所以需要将这两个方向统一起来考虑。因为需要耗费电量越大越好,至少电量越小越好,所以构造变量耗费电量减至少电量,这个变量越大越好。进行排序后遍历回推即可得到答案。代码如下。

class Solution {
public:
    int minPower(vector<vector<int>>& tasks) {
        int length = tasks.size();
        sort(tasks.begin(), tasks.end(), [](vector<int>& v1, vector<int>& v2){
            return (v1[0] - v1[1]) > (v2[0] - v2[1]);
        });
        int power = 0;
        for(int i = 0;i < length;++i){
            power += tasks[i][0];
            if(power < tasks[i][1]){
                power = tasks[i][1];
            }
        }
        return power;
    }
};

题目六

两个0和1数量相等区间的最大长度

给出一个长度为n的01串,现在请你找到两个区间使得这两个区间中,1的个数相等,0的个数也相等。这两个区间可以相交,但是不可以完全重叠,即两个区间的左右端点不可以完全一样。现在请你找到两个最长的区间,满足以上要求,返回区间最大长度

来自真实大厂笔试,没有在线测试。

分析:这道题只需找到从左开始第一个0的位置和第一个1的位置,从右边开始第一个0的位置和第一个1的位置,对同是0和同是1进行长度的计算取最大值即可。代码如下。

class Solution {
public:
    int maxLen(vector<int>& arr) {
        int left_zero = -1, left_one = -1;
        int right_zero = -1, right_one = -1;
        int length = arr.size();
        for(int i = 0;i < length;++i){
            if(left_zero >= 0 && left_one >= 0){
                break;
            }
            if(arr[i] == 0 && left_zero < 0){
                left_zero = i;
            }
            if(arr[i] == 1 && left_one < 0){
                left_one = i;
            }
        }
        for(int i = length-1;i >= 0;--i){
            if(right_zero >= 0 && right_one >= 0){
                break;
            }
            if(arr[i] == 0 && right_zero < 0){
                right_zero = i;
            }
            if(arr[i] == 1 && right_one < 0){
                right_one = i;
            }
        }
        int ans = 0;
        if(left_zero >= 0 && right_zero >= 0){
            ans = max(ans, right_zero - left_zero);
        }
        if(left_one >= 0 && right_one >= 0){
            ans = max(ans, right_one - left_one);
        }
        return ans;
    }
};