算法学习——LeetCode力扣贪心篇4
763. 划分字母区间
763. 划分字母区间 - 力扣(LeetCode)
描述
给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。
返回一个表示每个字符串片段的长度的列表。
示例
示例 1:
输入:s = “ababcbacadefegdehijhklij”
输出:[9,7,8]
解释:
划分结果为 “ababcbaca”、“defegde”、“hijhklij” 。
每个字母最多出现在一个片段中。
像 “ababcbacadefegde”, “hijhklij” 这样的划分是错误的,因为划分的片段数较少。
示例 2:
输入:s = “eccbbbbdec”
输出:[10]
提示
- 1 <= s.length <= 500
- s 仅由小写英文字母组成
代码解析
题意简述为
即最多能切多少段?使得同一字母都出现在一个片段,且片段最多。
思路
先统计整个字符串的map字典。
然后再次遍历字符串,当遍历字符串某一点时,当前字符串区间出现的字母个数就是整个字符串的字母个数(即当前字符串区间出现的字母,全部都出现在这里,区间外没有这个字母再次出现了),纪录当前区间是一段。
class Solution {
public:vector<int> partitionLabels(string s) {map<char,int> my_map; //整个字符串字典map<char,int> my_map_tmp;// 一段的字典vector<int> result;int start = 0; //段开始标志for(int i=0 ; i< s.size() ; i++) my_map[s[i]]++;// for(auto it:my_map) cout<<it.first<<' '<<it.second<<endl;for(int i=0 ; i<s.size() ;i++){my_map_tmp[s[i]]++; //更新一段的字典//检查当前的字典和全局字典,字母出现的次数for(auto it = my_map_tmp.begin() ; it != my_map_tmp.end() ;it++){//当前段中字母出现个数和全局不同(即还有字母没发现),跳出if( it->second != my_map[it->first]) break;//当前段出现的字母,字母数量和全局数量都相同(即全发现)if(it == -- my_map_tmp.end() ) //遍历到最后一个字母都没跳出{result.push_back(i-start+1); //记录段长度start = i+1; //下一段开始点my_map_tmp.clear();//清空段字典}}}return result;}
};
56. 合并区间
56. 合并区间 - 力扣(LeetCode)
描述
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
提示
- 1 <= intervals.length <= 104
- intervals[i].length == 2
- 0 <= starti <= endi <= 104
代码解析
按照左边界排序,排序之后局部最优:每次合并都取最大的右边界,这样就可以合并更多的区间了,整体最优:合并所有重叠的区间。
其实就是用合并区间后左边界和右边界,作为一个新的区间,加入到result数组里就可以了。如果没有合并就把原区间加入到result数组。
class Solution {
public:static bool compare(vector<int>&a , vector<int>&b){return a[0] < b[0];}vector<vector<int>> merge(vector<vector<int>>& intervals) {if(intervals.size() <= 1) return intervals;vector<vector<int>> result;//按照区间左边界排序sort(intervals.begin() , intervals.end(),compare);//第一个区间作为临时区间vector<int> tmp = intervals[0];for(int i = 1 ; i< intervals.size() ; i++){//如果当前区间和临时区间有重叠//取临时区间和当前区间的公共集合作为新的临时区间if(tmp[1] >= intervals[i][0]){tmp[0] = min(tmp[0] , intervals[i][0]);tmp[1] = max(tmp[1] , intervals[i][1]);}else//当临时区间和当前区间不重合{result.push_back(tmp);//临时区间存入tmp = intervals[i];//当前区间为新的临时区间}if(i==intervals.size()-1)//当前区间是最后一个区间的时候{result.push_back(tmp);//存入未存入的临时区间}}return result;}
};
738. 单调递增的数字
738. 单调递增的数字 - 力扣(LeetCode)
描述
当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。
给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增 。
示例
示例 1:
输入: n = 10
输出: 9
示例 2:
输入: n = 1234
输出: 1234
示例 3:
输入: n = 332
输出: 299
提示
- 0 <= n <= 109
代码解析
找到一个比目标值小的最大递增数列
递归法
从左到右遍历,找符合递增的部分,
当发现不符合的部分,不符合部分都置9
找到符合部分-1的最大递增数(后面都置9要借位)
例:输入668841
发现6688部分符合递增,但是从4开始不符合,因此41变成99
之后找到6688-1的最大递增(后面99要借1)为6679
在和最后两个99合并,得到667999
class Solution {
public:int monotoneIncreasingDigits(int n) {if(n<10) return n;string num = to_string(n);string result ;result += num[0];int flag = 0; //是否符合标志位,当不符合递增的时候,后面都置9for(int i=1 ;i<num.size();i++) //遍历{if(num[i] >= num[i-1] && flag==0) //找到符合递增的部分{result += num[i];}else if(flag==0) //找到第一个不符合递增的数 ,并求之前符合部分-1最大递增{result = to_string(monotoneIncreasingDigits(stoi(result)-1) );flag = 1; //设置为不符合}if(flag == 1) //不符合后面全都置9{result += '9';}}return stoi(result);}
};
贪心法 反向遍历
局部最优:遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]–,然后strNum[i]给为9,可以保证这两位变成最大单调递增整数。
全局最优:得到小于等于N的最大单调递增的整数。
但这里局部最优推出全局最优,还需要其他条件,即遍历顺序,和标记从哪一位开始统一改成9。
那么从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299
class Solution {
public:int monotoneIncreasingDigits(int n) {if(n<10) return n;string num = to_string(n);int flag;for(int i=num.size()-1 ; i >=1 ;i--){if(num[i] < num[i-1]){flag = i;num[i-1] -= 1;}}for(int i = flag ; i<num.size() ;i++){num[i] = '9';}return stoi(num);}
};
968. 监控二叉树
968. 监控二叉树 - 力扣(LeetCode)
描述
给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
示例
示例 1:
输入:[0,0,null,0,0]
输出:1
解释:如图所示,一台摄像头足以监控所有节点。
示例 2:
输入:[0,0,null,0,null,0,null,null,0]
输出:2
解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。
提示
- 给定树的节点数的范围是 [1, 1000]。
- 每个节点的值都是 0。
代码解析
从下往上看,局部最优:让叶子节点的父节点安摄像头,所用摄像头最少,整体最优:全部摄像头数量所用最少!
此时,大体思路就是从低到上,先给叶子节点父节点放个摄像头,然后隔两个节点放一个摄像头,直至到二叉树头结点。
我们分别有三个数字来表示:
0:该节点无覆盖
1:本节点有摄像头
2:本节点有覆盖
-
情况1:左右节点都有覆盖
左孩子有覆盖,右孩子有覆盖,那么此时中间节点应该就是无覆盖的状态了。
-
情况2:左右节点至少有一个无覆盖的情况
如果是以下情况,则中间节点(父节点)应该放摄像头:
- 情况3:左右节点至少有一个有摄像头
如果是以下情况,其实就是 左右孩子节点有一个有摄像头了,那么其父节点就应该是2(覆盖的状态) - 情况4:头结点没有覆盖
以上都处理完了,递归结束之后,可能头结点 还有一个无覆盖的情况,如图:
class Solution {
private:int result;int traversal(TreeNode* cur) {// 空节点,该节点有覆盖if (cur == NULL) return 2;int left = traversal(cur->left); // 左int right = traversal(cur->right); // 右// 情况1// 左右节点都有覆盖if (left == 2 && right == 2) return 0;// 情况2// left == 0 && right == 0 左右节点无覆盖// left == 1 && right == 0 左节点有摄像头,右节点无覆盖// left == 0 && right == 1 左节点有无覆盖,右节点摄像头// left == 0 && right == 2 左节点无覆盖,右节点覆盖// left == 2 && right == 0 左节点覆盖,右节点无覆盖if (left == 0 || right == 0) {result++;return 1;}// 情况3// left == 1 && right == 2 左节点有摄像头,右节点有覆盖// left == 2 && right == 1 左节点有覆盖,右节点有摄像头// left == 1 && right == 1 左右节点都有摄像头// 其他情况前段代码均已覆盖if (left == 1 || right == 1) return 2;// 以上代码我没有使用else,主要是为了把各个分支条件展现出来,这样代码有助于读者理解// 这个 return -1 逻辑不会走到这里。return -1;}public:int minCameraCover(TreeNode* root) {result = 0;// 情况4if (traversal(root) == 0) { // root 无覆盖result++;}return result;}
};