leetcode贪心算法学习

根据carl的代码随想录刷题顺序,自己的学习总结,用于回顾知识点

局部最优,从而整体最优

分发饼干

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

思路:1. 先将两个数组进行排序

	   2. 从胃口最大的小孩开始遍历小孩数组
	   3. 一个循环就可以,通过index控制饼干的位置,每满足一个条件,index--

代码

class Solution {
public:
    int nums;
    int findContentChildren(vector& g, vector& s) {
        if (s.size() == 0) return 0;
        sort(g.begin(),g.end(),less());
        sort(s.begin(),s.end(),less());
        int imax = min(g.size(), s.size());
        int index = s.size() - 1;
        for (int i = imax-1; i >= 0; i--) {
            if (g[i] <= s[index]) {
                nums++;
                index--;
            } 
        }
        return nums;
    }
};

摆动序列

思路:统计局部坡度,即 prediff 和 curdiff 的关系

注意要考虑边界问题 和 平坡 的情况

先排除数组长度为 1 的情况,然后设置最小输出的坡度数量为 1

如果 前一个坡度 prediff <= 0 && curdiff > 0 证明出现了一个最小拐点, 同理 prediff >= 0 && curdiff < 0

记得更新坡度 prediff = curdiff , i = 1开始

代码:

class Solution {
public:
    int wiggleMaxLength(vector& nums) {
        if (nums.size() == 1) return 1;
        int prediff = 0;
        int curdiff = 0;
        int resul = 1;
        for (int i = 1; i < nums.size(); i++) {
            curdiff = nums[i] - nums[i - 1];
            if ((prediff >= 0 && curdiff < 0) || (prediff <= 0 && curdiff > 0)) {
                resul++;
            }
            prediff = curdiff;
        }
        return resul;
    }
};

最大子数组和(数组中最大的连续的子数组的和)

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

思路:设置一个最小的值,然后从数组第一个元素开始遍历,当累加的值 > max时,max = count

当累加的值 < 0 时,重置为 0 (因为一个也没有的子序列的和就是 0 )

代码:

class Solution {
public:
    int maxSubArray(vector& nums) {
        int resul = INT32_MIN;
        int count = 0;
        for (int i = 0; i < nums.size(); i++) {
            count += nums[i];
            if (count >= resul) resul = count;
            if (count <= 0) count = 0;
        }
        return resul;
    }
};

买卖股票的最佳时机Ⅱ〽️

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。 返回 你能获得的 最大 利润 。

思路

计算当天和前一天的利润,如果 >0 则取, <= 0 则不取

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

跳跃游戏(能否到达)

数组中的每个元素代表你在该位置可以跳跃的最大长度。判断能否到达最后一个下标。

思路:每遍历一个元素,更新总体的覆盖范围,其覆盖范围 cover = i + nums[i]

当 cover >= nums.size() - 1 即代表可以到达最后一个下标

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

跳跃游戏Ⅱ(所需最小步数)

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。

思路

1.先排除数组的数目 <= 1 的情况

2.最小步数是由 n 个子覆盖所决定, subcover每更新一次,resul++, 第一个subCover = nums[0]

3.循环的条件是 i <= cover,

循环内部: i < cover 时, 更新 subCover = max(subCover, num[i] + i)

		    i == cover 时要更新 cover , cover = subCover, 满足条件要及时 break

代码:

class Solution {
public:
    int jump(vector& nums) {
        if (nums.size() == 1) return 0;
        int resul = 1;
        int cover = nums[0];
        int subCover = cover;
        if (cover >= nums.size() - 1) return resul;
        for (int i = 0; i <= cover; i++) {
            if (i < cover) {
                subCover = max(subCover, i + nums[i]);
            }
            else {
                subCover = max(subCover, i + nums[i]);
                cover = subCover;
                resul++;
                if (cover >= nums.size() - 1) break;
            }
        }
        return resul;
    }
};

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

给你一个整数数组 nums 和一个整数 k ,选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。
重复这个过程恰好 k 次。可以多次选择同一个下标 i 。

以这种方式修改数组后,返回数组可能的最大和 。

思路:要点:用绝对值进行从大到小进行排序,然后遍历数组,k > 0 && nums[i] < 0 就取反

代码:

class Solution {
public:
    static bool cmp(const int& a, const int& b) {
        return abs(a) > abs(b);
    }
    int largestSumAfterKNegations(vector& nums, int k) {
        sort(nums.begin(), nums.end(), cmp);
        int resul = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] < 0 && k > 0) {
                nums[i] *= -1;
                k--;
            }
        }
        if (k % 2 != 0) nums[nums.size() - 1] *= -1;
        for (auto a : nums) resul += a;
        return resul;
    }
};

加油站

思路

当前的油站的油gas+目前车里的油不够到达cost,则该点不是起点

有一个 当前用油 curSum 和 totalSum, curSum < 0时 需要更新

start = i + 1;

代码:

class Solution {
public:
    int canCompleteCircuit(vector& gas, vector& 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) {
                curSum = 0;
                start = i + 1;
            }
        }
        if (totalSum < 0) return -1;
        return start;
    }
};

分发糖果(两次排序)

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

每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目

注意

1.从左向右,如果右大于左,则右 = 左 + 1

2.从右向左,如果左大于右,则左 = max(左,右+ 1)

代码:

class Solution {
public:
    int candy(vector& ratings) {
        vector candy(ratings.size(), 1);
        // 先从左向右遍历,左小于右,右就比左大 1
        for (int i = 0; i < ratings.size() - 1; i++) {
            if (ratings[i + 1] > ratings[i]) {
                candy[i + 1] = candy[i] + 1;
            }
        }
        // 从左向右遍历
        for (int i = ratings.size() - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1]) candy[i] = max(candy[i + 1] + 1,candy[i]);
        }
        int resul = 0;
        for (auto a : candy) resul += a;
        return resul;
    }
};

柠檬水找零

class Solution {
public:
    bool lemonadeChange(vector& bills) {
//        vector wall(21,0);
        int wall5 = 0, wall10 = 0;
        for (int i = 0; i < bills.size(); i++) {
            if (bills[i] == 5) {
                wall5++;
            }
            else if (bills[i] == 10) {
                if (wall5 > 0){
                    wall5--;
                    wall10++;
                } 
                else return false;
            }
            else {
                if (wall5 > 0 && wall10 > 0) {
                    wall5--;
                    wall10--;
                }
                else if (wall5 >= 3 && wall10 == 0) {
                    wall5 -= 3;
                }
                else return false;
            }
        }
        return true;
    }
};

先整体取优,再局部优化

根据身高重建队列

先按身高从高到低进行排序,身高相等,前面人少的排前面(也就是people[1])

然后用 insert 去插入,先插入的是升高高的,后插入的人如果 [1] 和前面身高高的人相同,一定会插在他前面

代码:

class Solution {
public:
    static bool cmp(const vector& a, const vector& b) {
        if (a[0] == b[0]) return a[1] < b[1];
        return a[0] > b[0];
    }
    vector> reconstructQueue(vector>& people) {
        sort(people.begin(), people.end(), cmp);
        vector> que;
        for (int i = 0; i < people.size(); i++) {
            int pos = people[i][1];
            que.insert(que.begin() + pos, people[i]);
        }
        return que;
    }
};

用最少数量的箭引爆气球

思路:先从 start 从小到大排序,然后进行判定:

若左气球的 end >= 右气球的 start 说明有一部分重合 右气球的 end 需要重新选择最小的边界

否则,没有重合,箭 + 1

代码:

class Solution {
public:
    static bool cmp(const vector& a, const vector& b) {
        return a[0] < b[0];
    }
    int findMinArrowShots(vector>& points) {
        if (points.size() == 0) return 0;
        sort(points.begin(), points.end(), cmp);
        int resul = 1;
        for (int i = 1; i < points.size(); i++) {
            if (points[i][0] > points[i - 1][1]) resul++;
            else points[i][1] = min(points[i - 1][1], points[i][1]);
        }
        return resul;
        
    }
};

无重叠区间

返回 需要移除区间的最小数量,使剩余区间互不重叠

思路:返回需要移除的区间,则可以记录 非交叉空间的个数(count)

上一个区间的 end 小于等于 下一个区间的 begin 即 end <= intervals[i][0];

然后更新 end count++

class Solution {
public:
    static bool cmp(const vector& a, const vector& b) {
        return a[1] < b[1];
    }
    int eraseOverlapIntervals(vector>& intervals) {
        if (intervals.size() <= 1) return 0;
        sort(intervals.begin(), intervals.end(), cmp);
        int end = intervals[0][1];
        int count = 1; // count 代表非重叠的个数
        for (int i = 1; i < intervals.size(); i++) {
            if (intervals[i][0] >= end) {
                end = intervals[i][1];
                count++;
            }
        }
        return intervals.size() - count; // 总数 - 非重叠的个数 = 重叠的数
    }
};

划分字母区间

重点: 哈希表: hash[str[i] - 'a'] = i; 用来确定某一个字母所抵达的最大位置

然后遍历字符串,设定 left 和 right 当 i == right时,也就是当遍历到 i == hash[str[i] - 'a'] 条件满足,push_back 到结果中

中间要进行起始位置的记录,Push 完之后起始位置要 +1

代码

class Solution {
public:
    vector partitionLabels(string s) {
        int hash[26];
        for (int i = 0; i < s.size(); i++) {
            hash[s[i] - 'a'] = i;
        }
        vector resul;
        int lef = 0;
        int rig = hash[s[0] - 'a'];
        for (int i = 0; i < s.size(); i++) {
            rig = max(rig, hash[s[i] - 'a']);
            if (rig == i) {
                resul.push_back(rig - lef + 1);
                lef = rig + 1;
            }
        }
        return resul;
    }
};

合并区间

其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

思路

1.先按左边界从小到大排序

2.建立 resul 容器,如果 interval[i - 1][1] < interval[i][0] 说明不重叠,就把 iterval[i] pushresul

​ 否则就进行覆盖,用 resul.back()[1] = 新的右边界就可以

代码:

class Solution {
public:
    static bool cmp(const vector& a, const vector& b) {
        return a[0] < b[0];
    }
    vector> merge(vector>& intervals) {
        if (intervals.size() <= 1) return intervals;
        sort(intervals.begin(), intervals.end(), cmp);
        vector> resul;
        resul.push_back(intervals[0]);
        for (int i = 1; i < intervals.size(); i++) {
            if (resul.back()[1] >= intervals[i][0]) {
                resul.back()[1] = max(resul.back()[1],intervals[i][1]);
            }
            else {
                resul.push_back(intervals[i]);
            }
        }
        return resul;
    }
};

单调递增的数字

当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。

给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增 。

思路:用到 to_stringstoi 进行转换

从后往前遍历,如果 i - 1 的 > i 标记 i 到最后统一由 i 之后的位置全部转换成 ‘9’

代码:

class Solution {
public:
    int monotoneIncreasingDigits(int n) {
        std::string str = to_string(n);
        int flag = 0;
        for (int i = str.size() - 1; i > 0; i--) {
            if (str[i - 1] > str[i]) {
                str[i - 1]--;
                flag = i;
            }
        }
        if (flag != 0) {
            for (int i = flag; i < str.size(); i++) {
                str[i] = '9';
            }
        }
        return stoi(str);
    }
};

监控二叉树

代码:

class Solution {
public:
    int resul;
    int traversal(TreeNode* cur) {
        if (cur == nullptr) return 2;
        int lef = traversal(cur->left);
        int rig = traversal(cur->right);
        // 先判断 2 (有覆盖的情况)
        if (lef == 2 && rig ==2) return 0;
        // 再判断 0 的情况,先判断是否有无覆盖的情况,再判断有覆盖的情况
        if (lef == 0 || rig == 0) {
            resul++; //此时摄像头要 + 1
            return 1;
        }
        // 判断 1 的情况
        if (lef ==1 || rig == 1) return 2;
        return -1; 
    }
    int minCameraCover(TreeNode* root) {
        resul = 0;
        if (traversal(root) == 0) resul++;
        return resul;
    }
};

你可能感兴趣的:(leetcode,贪心算法,学习)