目录
[5. 用最少数量的箭引爆气球](#5. 用最少数量的箭引爆气球)
1.单调递增的数字

但是如果只这么处理会出现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.坏了的计算器
看到这题我们首先就想到正向推导。
我们贪心地想到,如果想让初始数字以尽可能少的次数变成目标数字,可以多用*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.合并区间
区间问题解法
我们为了思路统一,统一采用左端点排序

假设我们有一个区间,从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.无重叠区间

仍然是先按照左端点排序。
我们要移除最少的区间,那也就是说要保留更多的区间。
因此我们可以采用以下策略来保留更多区间。
当存在区间重叠时,我们保留较小的右端点,这样它和后面的 区间有重叠的可能性就会变小。
当区间无重叠,我们更新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会超时,但是在堆箱子这题中是可以通过的
排序后的思路和最长递增子序列相同
有两个需要注意的问题
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.距离相等的条形码

如果出现最多的数超过(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.重构字符串
这道题和上一道题是一模一样的的,唯一的不同就是这道题可能无解,因此我们需要判断一些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;
}
};