目录
1.单调递增的数字
2.坏了的计算器
3.合并区间
4.无重叠区间
5. 用最少数量的箭引爆气球
6.整数替换
解法1:模拟+记忆化搜索
解法2位运算+贪心
7.俄罗斯套娃信封问题
补充.堆箱子
8.可被3整除的最大和
9.距离相等的条形码
10.重构字符串
1.单调递增的数字
738. 单调递增的数字 - 力扣(LeetCode)
但是如果只这么处理会出现bug
例如如下这种情况,我们需要找到最前面的5.
因此
class Solution {
public:int monotoneIncreasingDigits(int n) {string num = to_string(n);int sz = num.size();int i = 0;while(i+1 < sz && num[i] <= num[i+1])i++;if(i+1 == sz)return n;//如果全递增直接返回,或者处理nums[i]的时候判断一下while(i - 1 >= 0 && num[i-1] == num[i])i--;num[i]--;for(int k = i + 1; k < sz; k++){num[k] = '9';}return stoi(num);//std::stoi 会忽略前导零,只返回字符串表示的整数值}
};
2.坏了的计算器
991. 坏了的计算器 - 力扣(LeetCode)
看到这题我们首先就想到正向推导。
我们贪心地想到,如果想让初始数字以尽可能少的次数变成目标数字,可以多用*2操作。
但是我们发现这种贪心是不对的,对于*2和-1的操作,判断标准不是很明确。 用正向的方法比较难解决这个问题
于是我们想到正难则反,而*2 和-1的操作正好有相反的/2和+1操作。
这一题有个非常重要的点就是它里面的数据是没有小数的,从起始数据到目标数据,不断*2,-1操作下是不能产生小数的。
所以我们在面对奇数的时候,就不能进行/2操作了,只能进行+1操作。
整理思路,我们得出以下结论:
1.当end <= begin
当 end <= begin时,end要想达到begin,肯定不可能进行/2操作,只能进行+1操作,这时候无论奇数偶数,都只有+1的选择。我们需要进行begin-end次操作
2.当end > begin 的时
当end > begin 的时候
我们如果想要先+1再/2,那么我们必须要加偶数个1,我们才能/2.最后,我们通过k+1次操作得到了(x+k)/2.
而如果我们先/2,那么我们只需要1+k/2 次就能得到(x+k)/2。
我们很容易得到先进行除法是更优的。
因此在end > begin的时候 ,我们奇数仍然是只能+1,偶数则是只进行/2操作
class Solution {
public:int brokenCalc(int startValue, int target) {int ret = 0;while(target > startValue){if(target % 2 == 0){target /= 2;ret++;}else{target++;ret++;}}ret += startValue - target;return ret;}
};
3.合并区间
56. 合并区间 - 力扣(LeetCode)
区间问题解法
我们为了思路统一,统一采用左端点排序
假设我们有一个区间,从left 到right
那么我们下一个区间有以下这些情况。
整体分为有重叠部分和没有重叠部分。
当有重叠部分时,我们保存左端点,取较大的右端点。(因为进行排序后,左端点是升序排序的)
当没有重叠部分时,我们把{left,right}加入ret数组,然后更新left和right。
class Solution {
public:static bool cmp(vector<int> a, vector<int> b){return a[0] < b[0];}vector<vector<int>> merge(vector<vector<int>>& intervals) {//排序sort(intervals.begin(), intervals.end(), cmp);//合并区间vector<vector<int>> ret;int left = intervals[0][0];int right = intervals[0][1];for(int i = 1; i < intervals.size(); i++){if(intervals[i][0] <= right)//有重叠部分{right = max(right,intervals[i][1]);//合并}else//无重叠{ret.push_back({left, right});//加入到结果中left = intervals[i][0];right = intervals[i][1];}}ret.push_back({left, right});return ret;}
};
4.无重叠区间
435. 无重叠区间 - 力扣(LeetCode)
仍然是先按照左端点排序。
我们要移除最少的区间,那也就是说要保留更多的区间。
因此我们可以采用以下策略来保留更多区间。
当存在区间重叠时,我们保留较小的右端点,这样它和后面的 区间有重叠的可能性就会变小。
当区间无重叠,我们更新left right并把count++即可。
遍历数组结束后要记得加上最后存在left right里面的那段区间。
最后,因为我们求的是移除的区间,因此把数组的长度减去count就是我们的返回值
class Solution {
public:int eraseOverlapIntervals(vector<vector<int>>& intervals) {int count = 0;sort(intervals.begin(), intervals.end());int left = intervals[0][0];int right = intervals[0][1];for(int i = 1; i < intervals.size(); i++){if(intervals[i][0] < right){right = min(right,intervals[i][1]);}else{count++;left = intervals[i][0];right = intervals[i][1];}}count ++;return intervals.size() - count;}
};
5. 用最少数量的箭引爆气球
452. 用最少数量的箭引爆气球 - 力扣(LeetCode)
理解题意,我们发现这也是一个区间问题。
我们发现这题找的实际上是交集。
class Solution {
public:int findMinArrowShots(vector<vector<int>>& points) {sort(points.begin(), points.end());int cover = 0;int right = points[0][1];for(int i = 1; i < points.size(); i++){if(points[i][0] <= right)//和上一个区间有交集{cover++;//重叠部分++right = min(right, points[i][1]);//右端点较小的同时也是是重叠区间的右端点}else{right = points[i][1];//无交集,以新的区间为基准再求重叠部分}}return points.size() - cover;}
};
6.整数替换
解法1:模拟+记忆化搜索
就是暴力枚举每种情况,对于偶数直接除以2,对于奇数则是取 min(hash[n-1], hash[n+1])
需要注意的地方是,为了防止INT_MAX + 1 溢出,我们dfs函数的类型使用long long,哈希表也是一样。
class Solution {
public:unordered_map<long long, int> hash;int integerReplacement(int n) {return dfs(n);}int dfs(long long n){if(n == 1)return 0;if(n % 2 == 0){if(!hash.count(n/2)){hash[n/2] = dfs(n/2);}return 1 + hash[n/2];}else{if(!hash.count(n+1) ){hash[n+1] = dfs( n+1 );}if(!hash.count( n-1 ) ){hash[ n-1 ] = dfs( n-1 );}return 1 + min(hash[n-1], hash[n+1]);}}
};
解法2位运算+贪心
对于偶数我们进行/2操作即可。
对于奇数操作, 当其为01结尾的时候,我们采用-1操作操作数会更少。
当其为11结尾的时候,-1操作会使得一位1变为0,而+1操作可能会使得多于一位的1变为0,因此采用+1的操作会更快捷地得到最终值。
最后有一个例外,即3,我们应该采用-1操作。
要判断最后两位是01还是11,我们只需要把这些数对4取余即可,看余数是1还是3即可判断。
这道题的贪心就贪在不同的奇数是+1还是-1,已经在过程中证明好了
同样需要加一个longlong
class Solution {
public:int integerReplacement(int num) {int ret = 0;long long n = num;while(n > 3){if(n % 2 == 0){ret++;n = n >> 1;}else{if(n % 4 == 3){n = n + 1;ret++;}else if(n % 4 == 1){n = n - 1;ret++;}}}if(n == 3)return ret + 2;else if(n == 2)return ret + 1;else return ret;}
};
7.俄罗斯套娃信封问题
解法1:区间排序+动态规划(超时)
解法2:区间排序 + 贪心 + 二分
我们严格按照左端点排序后,实际上已经不用看左端点了,因为总是递增的。
那么这道题经过这样处理,就变成了一个最长递增子序列问题。
但是当左端点有重复的时候需要注意,这时候如果我们的右端点按照升序排,我们就会把一些不该加的元素加入。
这时候就需要我们在左端点相同的时候把右端点降序排序
这样的好处是,我们就不会有可能把这些数字全部接到原数组后面了。因为如果有更小的数,那么我们会将其与原来的数替换。例如上一个数是3,按照升序排序的话,4679都会排到它后面。
可是如果我们降序,则我们在将9加入数组后,后面的764都只是更新数组末尾的9而已,让9不断被替换为764.
class Solution {
public:static bool cmp(const vector<int> & a, const vector<int> & b){if(a[0] != b[0])return a[0] < b[0];else return a[1] > b[1];}int maxEnvelopes(vector<vector<int>>& env) {sort(env.begin(), env.end(), cmp);vector<int> ret;ret.push_back(env[0][1]);for(int i = 1; i < env.size(); i++){if(env[i][1] > ret.back()){ret.push_back(env[i][1]);}else{int left = 0; int right = ret.size() - 1;while(left < right){int mid = left + (right-left) / 2;if( env[i][1] <= ret[mid]){right = mid;}else{left = mid + 1;}}ret[left] = env[i][1];}}return ret.size();}
};
补充.堆箱子
虽然俄罗斯套娃信封中的解法1会超时,但是在堆箱子这题中是可以通过的
面试题 08.13. 堆箱子 - 力扣(LeetCode)
排序后的思路和最长递增子序列相同
有两个需要注意的问题
class Solution {
public:static bool cmp(vector<int> a, vector<int> b){return a[0] < b[0];}int pileBox(vector<vector<int>>& box) {sort(box.begin(), box.end(),cmp);//1.需要自己手动写一个排序函数,按照某一维度来排。 //默认排序会出问题int n = box.size();if(n == 0)return 0;vector<int> dp(n , 0);for(int i = 0; i < n; i++)//2.初始化dp表的时候,每个箱子最小的堆叠高度是自己的高度{dp[i] = box[i][2];}int ret = dp[0];for(int i = 1; i < n; i++){for(int j = i - 1; j >= 0; j--){if(box[j][0] < box[i][0] && box[j][1] < box[i][1] && box[j][2] < box[i][2]){dp[i] = max(dp[i], dp[j] + box[i][2]);}}ret = max(ret, dp[i]);}return ret;}
};
8.可被3整除的最大和
1262. 可被三整除的最大和 - 力扣(LeetCode)
我们正难则反,根据累加和的不同情况,删去一些数即可,而我们只需要贪心地删除最小值即可。
分类情况中, 我们把第二种情况拿来举例子,可能我们有1个或者4个或者7个除以3余1的数,但是我们只需要取出最小的那个。整体来看是把这一组数分成两块,一块是一个除以3余1的数,另一块是可以被3整除的。
下面的另外三种也是一样的分组方式。
但是我们要注意,这些情况有可能不会全都有,因此我们可以先将x1 x2 y1 y2等赋值为
10010(题目中数组元素最大为10000),这样我们在最后取max的时候就必然不会取到无效情况。
更新最小值以及次小值只需要分类讨论即可
class Solution {
public:int maxSumDivThree(vector<int>& nums) {int sum = 0;int n = nums.size();for(int i = 0; i < n; i++){sum += nums[i];}if(sum % 3 == 0){return sum;}else if(sum % 3 == 1){int x1,y1,y2;x1 = y1 = y2 = 1e4 + 10;for(int i = 0; i < n; i++){int tmp = nums[i];if(tmp % 3 == 1){x1 = min(x1, tmp);}else if(tmp % 3 == 2){if(tmp <= y1){y2 = y1;y1 = tmp;}else if(tmp > y1 && tmp <= y2){y2 = tmp;}}}return max(sum - x1, sum - y1 - y2);}else//取余等于2{int x1,x2,y1;x1 = x2 = y1 = 1e4 + 10;for(int i = 0; i < n; i++){int tmp = nums[i];if(tmp % 3 == 2){y1 = min(y1, tmp);}else if(tmp % 3 == 1){if(tmp <= x1){x2 = x1;x1 = tmp;}else if(tmp > x1 && tmp <= x2){x2 = tmp;}}}return max(sum - x1 - x2, sum - y1);}}
};
class Solution {
public:int maxSumDivThree(vector<int>& nums) {int sum = 0;int n = nums.size();for(int i = 0; i < n; i++){sum += nums[i];}if(sum % 3 == 0){return sum;}else if(sum % 3 == 1){int x1,y1,y2;x1 = y1 = y2 = 1e4 + 10;for(int i = 0; i < n; i++){int tmp = nums[i];if(tmp % 3 == 1){x1 = min(x1, tmp);}else if(tmp % 3 == 2){if(tmp <= y1){y2 = y1;y1 = tmp;}else if(tmp > y1 && tmp <= y2){y2 = tmp;}}}return max(sum - x1, sum - y1 - y2);}else//取余等于2{int x1,x2,y1;x1 = x2 = y1 = 1e4 + 10;for(int i = 0; i < n; i++){int tmp = nums[i];if(tmp % 3 == 2){y1 = min(y1, tmp);}else if(tmp % 3 == 1){if(tmp <= x1){x2 = x1;x1 = tmp;}else if(tmp > x1 && tmp <= x2){x2 = tmp;}}}return max(sum - x1 - x2, sum - y1);}}
};
9.距离相等的条形码
1054. 距离相等的条形码 - 力扣(LeetCode)
如果出现最多的数超过(n+1)/2,那么必定有两个相同元素会相邻
我们只需要把最大的那个元素先排好,然后去排其它的即可,我们的i如果越界,那么将其置为1即可
class Solution {
public:vector<int> rearrangeBarcodes(vector<int>& barcodes) {int maxval = -1; int maxcount = 0;unordered_map<int, int> hash;for(auto ch : barcodes ){if( maxcount < ++hash[ch] ){maxval = ch;maxcount = hash[ch];}}int n = barcodes.size();int i;vector<int> ret(n);for(i = 0; i < n && maxcount > 0; i += 2){ret[i] = maxval;maxcount --;}hash.erase(maxval);for(auto& [x, y] : hash){for(int j = 0; j < y; j++){if(i >= n)i = 1;ret[i] = x;i += 2;}}return ret;}
};
10.重构字符串
767. 重构字符串 - 力扣(LeetCode)
这道题和上一道题是一模一样的的,唯一的不同就是这道题可能无解,因此我们需要判断一些maxcount 会不会大于(s.size() + 1)/2.
class Solution {
public:string reorganizeString(string s) {int n = s.size();char maxval;int maxcount = 0;string ret = s;unordered_map<char, int> hash;for(auto ch: s){if(maxcount < ++hash[ch]){maxval = ch;maxcount = hash[ch];}}if(maxcount > (n + 1) / 2)return "";int i;for(i = 0; i < n && maxcount > 0; i += 2){ret[i] = maxval;maxcount--;}hash.erase(maxval);for(auto &[x, y] : hash){for(int j = 0; j < y; j++){if(i >= n)i = 1;ret[i] = x;i += 2;}}return ret;}
};