贪心算法及相关题目

贪心算法概念

        贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解 。

贪心算法性质(判断是否可以使用贪心算法)

1、贪心选择性质

        一个问题的整体最优解可通过一系列局部的最优解的选择达到,并且每次的选择可以依赖以前作出的选择,但不依赖于后面要作出的选择。这就是贪心选择性质。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。

2、最优子结构性质

        当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用贪心法求解的关键所在。

例题:

1. 双核计算机处理任务的最短时间(面试题)

题目描述:

某台计算机为双核,可以同时处理两个任务,现在给出一组数据,每个数据表示一个任务处理完需要的时间,求这台双核计算机处理完这些任务需要的最短时间

示例:

        输入:[12, 20, 30]

        输出:32

思路:

我们看到题后先自己分析一波,怎样选择处理才能得到最短时间?

先尝试一下不同的组合方式:

先选12和20分别在两个内核处理,再将30交给处理12的内核(先处理小的):最少42秒

先选30和20分别在两个内核处理,再将12交给处理20的内核(先处理大的):最少32秒

我们发现先较大的一起处理,再将下一个数据交给较小的内核处理,最后两内核中较大的为最优解

局部最优解:每次将 耗时最多的 交给 用时较小的内核 处理

代码:

#include <iostream>
#include <algorithm>
using namespace std;int getmintime(int* ar, const int n)
{int time = 0; //记录耗时int core[2] = {0}; //两个内核,元素为各个内核耗时sort(ar, ar + n, greater<int>()); //排序从大到小for(int i = 0; i < n; i++){int j = core[0] <= core[1] ? 0 : 1; //选择用时少的内核core[j] += ar[i]; //将最大耗时的交给用时少的内核处理time = max(time,core[j]);}return time;}int main()
{int ar[] = {12,20,30};cout << getmintime(ar,3) << endl;return 0;
}

2. 分发饼干(力扣455)

题目描述:

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是每个孩子最多只能给一块饼干。

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

 

示例1:

        输入:g = [1,2,3],s = [1,1]

        输出:1

示例2:

        输入:g = [1,2],s = [1,2,3]

        输出:2

思路:排序+双指针

局部最优解:小胃口吃小饼干,大胃口吃大饼干

代码:

#include <algorithm>
class Solution {
public:int findContentChildren(vector<int>& g, vector<int>& s) {int num = 0;sort(g.begin(), g.end(), less<int>());sort(s.begin(), s.end(), less<int>());vector<int>::iterator it1 = g.begin();vector<int>::iterator it2 = s.begin();while (it1 != g.end() && it2 != s.end()){if (*it1 <= *it2){num++;it1++;it2++;}else{it2++;}}return num;}
};

3. 摆动序列(力扣376)

题目描述:

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

  • 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。

  • 相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为 它的最后一个差值为零。

子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。

示例1:

        输入:nums = {1,7,4,9,2,5};

        输出:6

示例2:

        输入:nums = {1,17,5,10,13,15,10,5,16,8};

        输出:7

思路:我们先对示例画图这样就很清晰了,我们去掉图中红圈元素,剩下元素就可以构成摆动序列,且长度最长

局部最优解:去掉所有单调路径两端之间所有其他元素

代码:

class Solution {
public:int wiggleMaxLength(vector<int>& nums) {vector<int>::iterator it = nums.begin();while (it != nums.end() - 1){if (*it > *(it + 1)){vector<int>::iterator it2 = it + 1;while (it2 != nums.end() - 1 && *it2 >= *(it2 + 1)){it2 = nums.erase(it2);}it = it2;}else if (*it < *(it + 1)){vector<int>::iterator it2 = it + 1;while (it2 != nums.end() - 1 && *it2 <= *(it2 + 1)){it2 = nums.erase(it2);}it = it2;}else{it = nums.erase(it);}}return nums.size();}
};

4. 最大子数组和(力扣53)

题目描述:

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

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

示例1:

        输入:nums = {2,1,-3,4,-1,2,1,-5,4}

        输出:6

示例2:

        输入:nums = {5,4,-1,7,8}

        输出:23

思路:

如果之前连续和小于0,则舍弃之前连续和当前连续和当前值

如果之前连续和大于0,则当前连续和当前值之前连续和

最后最大连续和为结果

局部最优解:之前连续和小于0则舍弃之前和,从当前位置重新开始算

代码:

1.贪心解法

class Solution {
public:int maxSubArray(vector<int>& nums) {if (nums.size() == 1){return nums[0];}int nowtotal = nums[0]; //当前和int pretotal = nums[0]; //之前和int maxtotal = nums[0]; //最大和for (int i = 1; i < nums.size(); i++){if (pretotal < 0){nowtotal = nums[i]; //舍弃与之前和相加,保留当前值作为目前和pretotal = nums[i];maxtotal = maxtotal > nowtotal ? maxtotal : nowtotal;}else{nowtotal = nums[i] + pretotal;pretotal = nowtotal;maxtotal = nowtotal > maxtotal ? nowtotal : maxtotal;}}return maxtotal;}
};

2.动态规划解法

#include <algorithm>
class Solution {
public:int maxSubArray(vector<int>& nums) {vector<int> dp = nums; //dp数组,每个元素为当前连续和for (int i = 1; i < nums.size(); i++){dp[i] = dp[i - 1] > 0 ? dp[i - 1] + dp[i] : dp[i];}sort(dp.begin(), dp.end(), greater<int>());return dp[0];}
};

5. 买股票的最佳时机II(力扣122)

题目描述:

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

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

返回 你能获得的 最大 利润 。

示例1:

        输入:price = {7,1,5,3,6,4}

        输出:7

示例2:

        输入:price = {1,2,3,4,5}

        输出:4

思路:

我们对于示例进行画图:

由于题目描述:在任何时候 最多 只能持有 一股 股票,因此我们可以使用双指针遍历

局部最优解为:上升段的值

代码:

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

6. 跳跃游戏(力扣55)

题目描述:

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。

示例1:

        输入:nums = {2,3,1,1,4}

        输出:true

示例2:

        输入:nums = {3,2,1,0,4}

        输出:false                                                                                                                       

思路:

第一种解法:从起始向后走 最大可以移动步数(maxcanwalk)

maxcanwalk <= 下一位可走的步数 =》maxcanwalk = 下一位可走的步数,并向后走1位

maxcanwalk > 下一位可走的步数 =》maxcanwalk -= 1,并向后走1位

走到最后位置返回true;maxcanwalk==0&&没到最后位置返回false。

第二种解法:最大覆盖范围

从0下标开始,每次向后走1步,总共走nums[0]步,保存最大覆盖范围,如果最大覆盖范围小于最后一位则false

局部最优解:每次走都是范围最大的

代码:

第一种方法:

class Solution {
public:bool canJump(vector<int>& nums){if (nums.size() == 1) //只有一个元素{return true;}int index = 0;int maxcanwalk = nums[index];for (index; index < nums.size() - 1; index++){if (maxcanwalk == 0 && index != nums.size() - 1){return false;}if (maxcanwalk <= nums[index + 1]){maxcanwalk = nums[index + 1];}else{maxcanwalk -= 1;}}return true;}
};

第二种方法:

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

7. 跳跃游戏II(力扣45)

题目描述:

给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i] 
  • i + j < n

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

示例1:

        输入:nums = {2,3,1,1,4}

        输出:2

示例2:

        输入:nums = {2,3,0,1,4}

        输出:2

思路:

看到这道题最开始能想到的就是 每次我都能跳最远 这样我就能最少次数到终点。

我们需要记录 起点,终点,最大覆盖范围 。从起点到终点一一遍历,保存最大覆盖范围(注意边界不能超过size-1),当我们走到终点时则记为跳跃了一步,将终点设置为最大覆盖范围,这样循环直到走到size-1结束(特殊情况为只有1个元素)

局部最优解:每次跳跃到我当前位置能达到的最远位置

代码:

class Solution {
public:int jump(vector<int>& nums){if (nums.size() == 1) //特殊情况,只有一个元素{return 0;}int start = 0; //起点int end = nums[0] < nums.size() - 1 ? nums[0] : nums.size() - 1; //第一次的终点int max_boundary = end; //每次的最大覆盖范围int steps = 0; //跳跃次数for (start; start <= end; start++){//这一位置的覆盖范围(不能超过size - 1)int nowcanwalk = start + nums[start] < nums.size() - 1 ? start + nums[start] : nums.size() - 1;max_boundary = max(max_boundary, nowcanwalk);if (start == end) //走到当前的终点记为一次跳跃{end = max_boundary; //更新终点steps++;}}return steps;}
};

8. K次取反后最大化的数组和(力扣1005)

给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

  • 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。

重复这个过程恰好 k 次。可以多次选择同一个下标 i 。

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

示例1:

        输入:nums = {4,2,3}, k = 1

        输出:5

示例2:

        输入:nums = {3,-1,0,2}, k = 3

        输出:6

思路:

按照绝对值大小,从大到小排序,然后按次数将负数取反,最后累加即可得到最大和

局部最优解:每次让绝对值大的负数变成正数

代码:

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

9. 加油站(力扣134)

题目描述:

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

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

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例1:

        输入:gas = { 1,2,3,4,5 }; cost = { 3,4,5,1,2 }

        输出:3

示例2:

        输入:gas = { 2,3,4 }; cost = { 3,4,3 }

        输出:-1

思路:

从0下标开始,当你剩余油量和负数且为最小负数时,你的下一位才可能是能让你跑完的起点。

局部最优解:从0下标开始,剩余油量和<0时,记录下一个位置为起始位置

代码:

class Solution {
public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {int costsum = 0; //剩余油量累积和int totalsum = 0; //总共剩余油量int start = 0;for (int i = 0; i < gas.size(); i++){costsum += gas[i] - cost[i];totalsum += gas[i] - cost[i];if (costsum < 0){start = i + 1;costsum = 0;}}if (totalsum < 0) //总共剩余油量<0代表无论从哪跑都无法跑一圈{return -1;}return start;}
};

10. 分发糖果(力扣135)

题目描述:

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

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

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子评分更高的孩子会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目

 

示例1:

        输入:ratings = {1,0,2}

        输出:5

示例2:

        输入:ratings = {1,2,87,87,87,2,1}

        输出:13

思路:

这道题乍一看,觉得只需要一次遍历过去,两两比较大的比小的多1即可,但是这样是不对的!

这道题的关键点在于一个数要同时兼顾两边,而要做到同时兼顾两边的数,那么就需要先确定一边,才能确定另一边。

这道题我们可以分为这两种情况来兼顾两边:

(开始所有人糖果都为1)

①从左向右走,找右边比左边数大的,将右边的糖果数增加

②从右向左走,找左边比右边数大的,将左边的糖果数增加(此时需要注意保留①得到的结果和②结果中的最大值)

代码:

class Solution {
public:int candy(vector<int>& ratings) {int size = ratings.size();vector<int> v(size, 1);for (int i = 0; i < size - 1; i++) //从左向右走,判断右边比左边大的情况{if (ratings[i + 1] > ratings[i]){v[i + 1] = v[i] + 1;}}int total = 0;for (int i = size - 1; i > 0; i--) //从右向左走,判断左边比右边大的情况{if (ratings[i - 1] > ratings[i]){v[i - 1] = max(v[i - 1], v[i] + 1); //保留 从左向右 和 从右向左 结果中的最大值}total += v[i];}total += v[0];return total;}
};

11. 柠檬水找零(力扣860)

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

        

示例1:

        输入:bills = {5,5,5,10,20}

        输出:true

示例2:

        输入:bills = {5,5,10,10,20}

        输出:false

思路:

这道题很简单,只需要根据2种情况判断手头是否有足够数量的此种支票即可。

给10要找5:判断手头是否右1张5

给20要找15:判断手头是否右1张10和1张5,没有再判断是否有3张5(1张10,1张5优先,5用的情况比较多)

代码:

class Solution {
public:bool lemonadeChange(vector<int>& bills) {int five = 0;int ten = 0;for (int x : bills){if (x == 5){five++;}else if (x == 10){ten++;if (five < 1){return false;}five--;}else if (x == 20){if (ten > 0 && five > 0){ten--;five--;}else if (five >= 3){five -= 3;}else{return false;}}}return true;}
};

12. 根据身高重建队列(力扣406)

题目描述:

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

示例1:

        输入:people = { {7,0}, {4,4}, {7,1}, {5,0}, {6,1}, {5,2} }

        输出:{ {5,0}, {7,0}, {5,2}, {6,1}, {4,4}, {7,1} }

思路:

与第10题分发糖果一样,遇到这种有两种维度影响的题,需要先确定一方,才能确定另一方

我们来先确定h身高这个维度,我们按照身高从大到小顺序先排列一下(其中身高一样的,根据k从小到大排),排完后按照k来进行插入,画图表示:

代码:

class Solution {
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>> queue;for (int i = 0; i < people.size(); i++){int index = people[i][1];queue.insert(queue.begin() + index, people[i]);}return queue;}
};

13. 用最少数的箭引爆气球(力扣452)

题目描述:

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstartxend, 且满足  xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 

示例1:

        输入:points = { {10,16}, {2,8}, {1,6}, {7,12} }

        输出:2

思路:排序 + 贪心

看到这道题时可以发现,也是有两个维度影响是否重叠的判断(左边界和右边界),因此我们需要确定一边再去看另一边。我们先根据左边界从小到大排序,然后根据右边界进行判断:

局部最优解:每次让重复区域尽可能包括多个气球

代码:

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

14. 无重叠区间(力扣435)

题目描述:

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 

示例1:

        输入:intervals = { {1,2}, {2,3}, {3,4}, {1,3} }

        输出:1

思路:排序 + 贪心

这道题和上一道题其实思路差不多,有两个维度影响判断是否重叠,因此需要先确定一个方向再确定另一个。我们根据左边界从小到大排序,判断当前左边界和上一个的右边界大小关系:

①当前左边界 < 上一个右边界:说明有重叠,我们左边界已经按照从小到大排序,因此只需要剔除掉右边界更大的(左边界以确定,右边界越大可能覆盖的越多)

②否则:说明没有重叠,只需要更新右边界即可

局部最优解:有重叠时剔除覆盖范围大的那个。

代码:

class Solution {
public:int eraseOverlapIntervals(vector<vector<int>>& intervals) {if (intervals.size() <= 1){return 0;}sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b) {return a[0] < b[0]; });int delnum = 0;int right = intervals[0][1];for (int i = 1; i < intervals.size(); i++){if (right > intervals[i][0]){delnum++;right = min(right, intervals[i][1]);}else{right = intervals[i][1];}}return delnum;}
};

15. 划分字母区间(力扣763)

题目描述:

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。

返回一个表示每个字符串片段的长度的列表。

示例1:

        输入:s = {"ababcbacadefegdehighklij"}

        输出:{9, 7, 8}

示例2:

        输入:s = {"eaaaabaaccd"}

        输出:{1, 7, 2, 1}

思路:

我们看到这道题的正常思路是 从头开始走 判断当前字母的最远位置之前是否包括其他字母的最远位置,如果是则这个区间为一个单独的区间。那么我们该如何判断当前是否包括其他字母的最远位置呢?我来画个图帮大家理解一下:

局部最优解:从头开始走,走到 当前走过区域中元素最远距离的最大值 时,即为一个区间

代码:

class Solution {
public:vector<int> partitionLabels(string s) {int hash[27] = { -1 };for (int i = 0; i < s.size(); i++){hash[s[i] - 'a'] = i;  //a->0  b->1 ...}vector<int> outcome;int left = 0;int farthest_point = 0; //保存当前走过区域中元素最远距离的最大值for (int right = 0; right < s.size(); right++){farthest_point = max(farthest_point, hash[s[right] - 'a']);if (right == farthest_point){outcome.push_back(right - left + 1);left = right + 1;}}return outcome;}
};

16. 合并区间(力扣56)

题目描述:

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

 示例1:

        输入:intervals = {{1,3}, {2,6}, {8,10}, {15,18}}

        输出:{{1,6}, {8,10}, {15,18}}

示例2:

        输入:intervals = {{1,4}, {4,5}}

        输出:{{1,5}}

思路:

这道题和之前做过的区间有关的题差不多,按照我这个顺序做过的同学应该能很清楚的想到,这道题只是比之前的多一个合并的操作

代码:

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

17. 单调自增的数字(力扣738)

题目描述:

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

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

示例1:

        输入:n = 10

        输出:9

示例2:

        输入:n = 332

        输出:299

思路:

题目要求数字中每一位都是按照递增,且最后这个数需要是最大的,因此我们可以想到找到第一个导致不递增的数的位置,将此位的前一位的数减1,此位以及之后位全部变为9就是最大且递增的。

局部最优解:前一位 > 这一位时:前一位-1,后面全部置为9

代码:

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

18.监控二叉树(力扣968)

题目描述:

给定一个二叉树,我们在树的节点上安装摄像头。

节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。

计算监控树的所有节点所需的最小摄像头数量。

思路:

我们看到这道题时为了让监控的个数最少,因此每个监控最好能够监视3个节点,因此我们能够想到让叶子节点的父节点为监控,并且隔一个为一个监控,此时可以确定需要从下往上遍历二叉树,也就需要后序遍历,这就是这道题的大体思路,如何判断这个节点是否需要放监控呢?我们就需要根据它两个孩子的状态来决定是否需要放监控。

我们画图说明:

局部最优解:叶子节点的父节点一定为摄像头,并从叶子的父节点向上每隔一个节点为一个摄像头

代码:

/*** 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 {
public:int outcome = 0;//状态 0:无覆盖  1:有覆盖  2:有摄像头int getstate(TreeNode* root){if (root == nullptr) //空节点{return 1;}//左int left = getstate(root->left);//右int right = getstate(root->right);//根if (left == 0 || right == 0) //左右孩子有一个无覆盖{outcome++;return 2; //父节点需要有摄像头}if (left == 1 && right == 1) //左右孩子都被覆盖{return 0; //父节点一定没有被覆盖}if (left == 2 || right == 2) //左右孩子有一个摄像头{return 1; //父节点一定被覆盖}return -1; //运行不到此处,只是为了编译通过}int minCameraCover(TreeNode* root){if (getstate(root) == 0) //由于遍历到根节点就退出了,如果此时根节点没有被覆盖,我们需要在根节点安装摄像头{outcome++;}return outcome;}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/211993.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序中生命周期钩子函数

微信小程序 App 的生命周期钩子函数有以下 7 个&#xff1a; onLaunch(options)&#xff1a;当小程序初始化完成时&#xff0c;会触发 onLaunch&#xff08;全局只触发一次&#xff09;。onShow(options)&#xff1a;当小程序启动或从后台进入前台显示时&#xff0c;会触发 on…

JenKins快速安装与使用,Gitlab自动触发Jenkins

一、JenKins 0.准备&#xff0c;配置好环境 1&#xff09;Git&#xff08;yum安装&#xff09; 2&#xff09;JDK&#xff08;自行下载&#xff09; 3&#xff09;Jenkins&#xff08;自行下载&#xff09; 1.下载安装包 进官网&#xff0c;点Download下方即可下载。要下…

工业机器视觉megauging(向光有光)使用说明书(三,轻量级的visionpro)

下来我们说说第二个相机的添加&#xff1a; 第一步&#xff0c;点击相机二&#xff0c;如下&#xff1a; 第二步&#xff0c;点击&#xff1a;加载工具组.xml&#xff0c;加载toolgroupxml2目录下的&#xff1a;工具组.xml 注意&#xff0c;一个相机只能用一个toolgroupxml,第…

mockito加junit实现单元测试笔记

目录 一、简介1.1 单元测试的特点1.2 mock类框架使用场景1.3 常用mock类框架1.3.1 mockito1.3.2 easymock1.3.3 powermock1.3.4 JMockit 二、mockito的单独使用2.1 mock对象与spy对象2.2 初始化mock/spy对象的方式初始化mock/spy对象第1种方式初始化mock/spy对象第2种方式初始化…

2023下半年软考架构师-心态崩了

2023.11.06 考试心得&#xff1a;上午综合知识&#xff0c;面广&#xff0c;但考的不深&#xff0c;主要都是理论性的&#xff0c;计算题不多&#xff0c;只要平时准备一下&#xff0c;有30题可以不会&#xff0c;过的概率还是很大的。下午的案例&#xff0c;这个就会有深度了…

C++新经典模板与泛型编程:SFINAE替换失败并不是一个错误

替换失败并不是一个错误&#xff08;SFINAE&#xff09; SFINAE是一个英文简称&#xff0c;全称为Substitution Failure is not an Error&#xff0c;翻译成中文就是“替换失败并不是一个错误”。 SFINAE可以看作C语言的一种特性或模板设计中要遵循的一个重要原则&#xff0c;…

变电站工程师软件工具:Omicron IEDScout Crack

变电站工程师打软件 IEDScout 5.2.0 帮助您确保发电、输电和配电作业的安全可靠&#xff0c;IEDScout 是专为操作 IEC 61850 装置的保护和变电站自动化工程师打造的一款理想软件工具。它可以控制 IED&#xff08;智能电子设备&#xff09;&#xff0c;并于使用过程中执行多种实…

从零开始,利用ChatGPT学会写作的完整指南

文章目录 前言了解ChatGPT访问OpenAI平台使用ChatGPT进行简单的对话定义写作主题逐步生成文章段落添加个性化和细节编辑和润色反复修改直至满意 图书推荐内容简介作者简介获取方式 前言 在数字时代&#xff0c;人工智能技术日益成熟&#xff0c;为我们提供了全新的学习和创作机…

软件测试外包干了2个月,技术进步2年。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;18年通过校招进入北京某软件公司&#xff0c;干了接近2年的功能测试&#xff0c;今年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了2年的功能测试&…

Spatial Data Analysis(四):空间自相关示例

Spatial Data Analysis&#xff08;四&#xff09;&#xff1a;空间自相关示例 空间自相关是地理信息科学&#xff08;GIS&#xff09;和空间统计学中的重要概念之一&#xff0c;用于研究地理空间上的数据变异性和相关性。空间自相关分析的目标是探讨地理空间中的现象是否呈现…

C //例10.4 从键盘输入10个学生的有关数据,然后把它们转存到磁盘文件上去。

C程序设计 &#xff08;第四版&#xff09; 谭浩强 例10.4 例10.4 从键盘输入10个学生的有关数据&#xff0c;然后把它们转存到磁盘文件上去。 IDE工具&#xff1a;VS2010 Note: 使用不同的IDE工具可能有部分差异。 代码块 方法&#xff1a;使用指针&#xff0c;函数的模块…

普通方法和构造方法的创建调用和注意事项

普通方法 创建&#xff1a;只能基于类&#xff1b;调用&#xff1a;只能基于方法&#xff1b;语法&#xff1a;访问修饰符public 返回值类型||void 方法名&#xff08;[参数列表]&#xff09;{方法体}根据返回值的选择和参数列表的选择可以为普通方法归类出四种类型&#xff1…

文心一言大模型应用开发入门

本文重点介绍百度智能云平台、文心一言、千帆大模型平台的基本使用与接入流程及其详细步骤。 注册文心一言 请登录文心一言官方网站 https://yiyan.baidu.com/welcome 点击登录&#xff1b;图示如下&#xff1a; 请注册文心一言账号并点击登录&#xff0c;图示如下&#xff1…

Android12之MediaCodec硬编解码调试手段(四十九)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只…

UVM:UVM的树形结构

UVM采用树形的组织结构来管理验证平台的各个部分。sequencer、driver、monitor、agent、model、 scoreboard、env等都是树的一个结点。为什么要用树的形式来组织呢&#xff1f;因为作为一个验证平台&#xff0c;它必须能够掌握自己治下的所 有“人口”&#xff0c;只有这样做了…

安防监控系统镜头选型分析,低噪声,低振动,多通道

安防镜头步进驱动选用型号 GC6107 C6109 GC6209 GC6119 GC6129 GC6139 GC6208 GC6150 GC6151 GC6152 GC6125 GC6236采用5V的镜头驱动 。其中GC6107 C6109 GC6209 GC6119 GC6129 GC6139 GC6208关键特性两通道&#xff0c;256细分&#xff0c;低噪&#xff0c;内部和外部时钟…

解决CentOS下PHP system命令unoconv转PDF提示“Unable to connect or start own listener“

centos系统下&#xff0c;用php的system命令unoconv把word转pdf时提示Unable to connect or start own listene的解决办法 unoconv -o /foo/bar/public_html/upload/ -f pdf /foo/bar/public_html/upload/test.docx 2>&1 上面这个命令在shell 终端能执行成功&#xff0c…

solidity案例详解(六)服务评价合约

有服务提供商和用户两类实体&#xff0c;其中服务提供商部署合约&#xff0c;默认诚信为true&#xff0c;用户负责使用智能合约接受服务及评价&#xff0c;服务提供商的评价信息存储在一个映射中&#xff0c;可以根据服务提 供商的地址来查找评价信息。用户评价信息&#xff0c…

HarmonyOS4.0从零开始的开发教程03初识ArkTS开发语言(中)

HarmonyOS&#xff08;二&#xff09;初识ArkTS开发语言&#xff08;中&#xff09;之TypeScript入门 浅析ArkTS的起源和演进 1 引言 Mozilla创造了JS&#xff0c;Microsoft创建了TS&#xff0c;Huawei进一步推出了ArkTS。 从最初的基础的逻辑交互能力&#xff0c;到具备类…

13年测试老鸟总结,性能测试常遇问题+解决方案+分析...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、内存溢出 1&a…