贪心算法及相关题目

贪心算法概念

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

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

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.mzph.cn/news/204335.shtml

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

相关文章

el-menu标题过长显示不全问题处理

项目基于vue-element-admin 问题 期望 处理方式 \src\layout\components\Sidebar\index.vue 文件后添加CSS <style scped> /* 侧栏导航菜单经典 文字超长溢出问题 CSS折行 */ .el-submenu__title {display: flex;align-items: center; } .el-submenu__title span {white-…

5个免费实用的AI绘画软件,对新手极其友好!

分享5个实用的AI绘画软件&#xff0c;都可以免费使用&#xff0c;而且操作简单&#xff0c;对新手也比较友好&#xff0c;生成的图片也不错&#xff01; 1、AI-Chat 地址&#xff1a; https://mmm.aiyujiang.com 上传图片并输入AI提示词就能一键生成各类动漫、卡通风格头像&a…

一文让你知道企业真正需要一个怎样的远程协同运维平台?

随着企业业务的快速发展&#xff0c;各类系统也是越来越多&#xff0c;运维工作的作用也就越发突出&#xff0c;运维工作出现一点问题就会牵一发而动全身&#xff0c;所以企业需要一个远程协同运维平台。那企业真正需要一个怎样的远程协同运维平台呢&#xff1f;有空可以看看这…

多元线性回归(一)

基本概念 线性回归时机器学习中监督学习下的一种算法。回归问题主要关注是因变量&#xff08;需要预测的值&#xff0c;可以是一个也可以是多个&#xff09;和一个或多个值型的自变量&#xff08;预测变量&#xff09;之间的关系。 需要预测的值&#xff1a;即目标变量&#x…

Name or service not knownstname

Name or service not knownstname Hadoop 或 Spark 集群启动时 报错 Name or service not knownstname 原因时因为 workers 文件在windows 使用图形化工具打开过 操作系统类型不对引发的 在Linux系统上删除 workers 文件 使用 vim 重新编辑后分发即可

前端笔记(四)Flex 布局

标准流 标准流也叫文档流&#xff0c;指的是标签在页面中默认的派不规则&#xff0c;例如&#xff1a;块元素独占一行&#xff0c;行内元素可以一行显示多个。 但是很多的网页布局都是块元素在一行中显示的&#xff0c;这时候就需要浮动和 Flex 布局&#xff0c;浮动只需要了解…

Java 中的 Collection 容器

Java 中的 “容器” 在 Java 中&#xff0c;java.util.Collection 是一个接口&#xff0c;定义了一组常用的操作和方法&#xff0c;提供了一种方便的方式来管理和操作一组对象。 它是 Java 集合框架的基础之一&#xff0c;提供了统一的方式来处理对象的集合。 Collection 接口继…

OLED材料市场研究:预计2029年将达到1447亿元

由于技术优势突出&#xff0c;近年来OLED 率先在智能手机、可穿戴等中小尺寸领域的渗透率持续提升。OLED就是有机发光显示技术&#xff0c;其最大特点是每个像素独立自发光&#xff0c;具有非常完美的黑色显示能力&#xff0c;在亮度、色彩、响应速度等方面远胜LCD屏幕&#xf…

基于three.js生成动态波浪背景效果

文章目录 前言一、安装three二、新建waves.js文件三、引入waves.js文件比查看效果如有启发&#xff0c;可点赞收藏哟~ 前言 基于three.js生成动态波浪背景效果 一、安装three npm i three -S二、新建waves.js文件 注意geometry.setAttribute和geometry.addAttribute和在不同…

统计centos系统哪一个进程打开文件描述符

一&#xff1a;找出前10进程打开的描述符 # find /proc/ -print|grep -P /proc/\d/fd|awk -F/ {print $3}|uniq -c|sort -rn |awk {print "进程 "$2" 打开 "$1" 个文件描述符"}|head 二&#xff1a;通过进程id找出对应的进程运行的程序。 # ps…

一键AI智能改写,一键AI智能生成原创文章

在数字化时代&#xff0c;创作内容已经成为大家日常生活和工作中不可或缺的一部分。本文将深入探讨一键AI智能改写的概念&#xff0c;剖析其背后的技术原理&#xff0c;同时聚焦于147原创助手这一代表性工具&#xff0c;解读其在改写文案上的独特之处&#xff0c;以及在各大平台…

Java中实用的策略模式【Strategy】

一、简介 我们知道Java中有许多的设计模式&#xff0c;总共32个左右。常见的比如简单工厂、建造者、原型、代理、桥接等&#xff0c;这些设计模式相当于是一个规范&#xff0c;主要是总结出来便于大家理解开发的一种算法思路。 今天主要是给大家介绍一下我们常见的策略模式&a…

【ARM Coresight 系列 2 文章 -- Trace32 对 APBIC 地址的配置 介绍】

文章目录 APBIC RomtableAPBIC Romtable 图 1 APBIC 网络图 如上图所示,如果想通过Trace32/DS-5 去访问 AP, 这个时候需要怎么做呢?可以看到 APBIC 中ROMTABLE 中 APB-AP 的偏移是0x00200000,所以 APB-AP的基地址为 0x2b000000 + 0x00200000,又从 APB-AP 的romtable 中可以…

23款奔驰E350eL升级小柏林音响 13个扬声器 590w

小柏林之声音响是13个喇叭1个功放&#xff0c;功率是590W&#xff0c;对应普通音响来说&#xff0c;已经是上等了。像著名的哈曼卡顿音响&#xff0c;还是丹拿音响&#xff0c;或者是BOSE音响&#xff0c;论地位&#xff0c;论音质柏林之声也是名列前茅。 升级小柏林音响&#…

百元挂耳式蓝牙耳机有哪些?百元挂耳式蓝牙集锦分享

生活节奏匆忙&#xff0c;蓝牙耳机成了解救沉浸音乐迷的利器&#xff0c;而在百元预算内&#xff0c;挂耳式设计更是让你摆脱繁琐&#xff0c;本文将为你盘点几款百元挂耳式蓝牙耳机&#xff0c;让你在音乐和通话中轻松自如&#xff0c;让我们一起发现这些性价比爆棚的挂耳式蓝…

ElasticSearch篇---第五篇

系列文章目录 文章目录 系列文章目录前言一、什么是ElasticSearch?二、ElasticSearch中的集群、节点、索引、文档、类型是什么?三、ElasticSearch中的分片是什么?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,…

宏工科技:电池装备高效交付“唯快不破”

面向TWh时代的锂电设备供应需求&#xff0c;锂电设备向标准化、模块化方向升级的趋势显现。 “近年来&#xff0c;宏工科技聚焦电池匀浆技术创新与规模化降本&#xff0c;通过电池匀浆工艺段的模块化探索与应用&#xff0c;从项目周期、成本、效率等多维度赋能电池前段制造高质…

理解意图,加速迈向L4高度自智网络

PART.1 自智背景 “云网自智”&#xff0c;目标未来&#xff01; 随着自智网络概念的兴起及各类相应概念、规范、指导原则的不断发布&#xff0c;运营商也越发关注系统的自智能力&#xff0c;TMF 的Autonomous Networks 自智网络框架规范设定了自智网络的级别及评价标准&#x…

Pandas教程09:DataFrame数据可视化绘制折线图、柱状图、散点图、直方图等

pandas.plot() 是 pandas 库中的一个非常方便的函数&#xff0c;用于绘制各种图形&#xff0c;例如线图、柱状图、散点图等。以下是一些示例用法&#xff1a; 1.绘制一个简单的线图&#xff1a; # Author : 小红牛 # 微信公众号&#xff1a;wdPython import pandas as pd impo…

2023年淘宝天猫年终惊喜红包玩法

2023年淘宝天猫年终惊喜红包玩法&#xff0c;2023年淘宝年终好价节红包活动 随着2023年的尾声渐近&#xff0c;淘宝再次为广大用户带来了年终的惊喜——一场特别的红包活动。从12月8日零时开始&#xff0c;直至12月12日的午夜&#xff0c;淘宝app将开启一个为期五天的年终好价节…