算法--贪心
- 人工智能
- 2025-09-17 14:36:01

贪心 原理经典例题[860. 柠檬水找零]( leetcode /problems/lemonade-change/description/)[2208. 将数组和减半的最少操作次数]( leetcode /problems/minimum-operations-to-halve-array-sum/description/)[179. 最大数]( leetcode /problems/largest-number/description/)[376. 摆动序列]( leetcode /problems/wiggle-subsequence/)[300. 最长递增子序列]( leetcode /problems/longest-increasing-subsequence/description/)[334. 递增的三元子序列]( leetcode /problems/increasing-triplet-subsequence/)[334. 递增的三元子序列]( leetcode /problems/increasing-triplet-subsequence/)[122. 买卖股票的最佳时机 II]( leetcode /problems/best-time-to-buy-and-sell-stock-ii/)[1005. K 次取反后最大化的数组和]( leetcode /problems/maximize-sum-of-array-after-k-negations/description/)[870. 优势洗牌]( leetcode /problems/advantage-shuffle/)[409. 最长回文串]( leetcode /problems/longest-palindrome/description/)[942. 增减字符串匹配]( leetcode /problems/di-string-match/)[455. 分发饼干]( leetcode /problems/assign-cookies/)[553. 最优除法]( leetcode /problems/optimal-division/description/)[45. 跳跃游戏 II]( leetcode /problems/jump-game-ii/description/)[55. 跳跃游戏]( leetcode /problems/jump-game/description/)[134. 加油站]( leetcode /problems/gas-station/description/)[738. 单调递增的数字]( leetcode /problems/monotone-increasing-digits/)[991. 坏了的计算器]( leetcode /problems/broken-calculator/description/)[56. 合并区间]( leetcode /problems/merge-intervals/description/)[435. 无重叠区间]( leetcode /problems/non-overlapping-intervals/description/)[452. 用最少数量的箭引爆气球]( leetcode /problems/minimum-number-of-arrows-to-burst-balloons/description/)[354. 俄罗斯套娃信封问题]( leetcode /problems/russian-doll-envelopes/description/)[1262. 可被三整除的最大和]( leetcode /problems/greatest-sum-divisible-by-three/description/)[1054. 距离相等的条形码]( leetcode /problems/distant-barcodes/description/)[767. 重构字符串]( leetcode /problems/reorganize-string/) 原理
贪心策略的思路为希望通过逐步求解局部最优解从而得到全局最优解,由于贪心策略不一定能得到全局最优解,因此如果想使用贪心策略需要严格证明贪心策略可以得到全局最优解。总之使用贪心算法需要解决两个问题:
选择合适的贪心策略证明该贪心策略可以得到全局最优解贪心算法的学习只能通过不断的做题来吸取检验从而学习各种的贪心策略和证明方法。
经典例题 860. 柠檬水找零在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。 每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。 注意,一开始你手头没有任何零钱。 给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
贪心策略:我们发现只有顾客付20美元时找零才会有争议: ①找零10 + 5 ②找零5 + 5 + 5 可以知道5美元在找零的过程中作用比较大,因此我们希望尽可能的保留5美元,所以在顾客付20美元时,优先找零10 + 5,不行再找零5 + 5 + 5
证明:交换论证法我们需要证明最优策略在不破化最优解的前提下可以转化为贪心策略。
当顾客付5美元或者10美元时,贪心策略和最优解法没有争议。
当顾客付20美元时,贪心策略优先找零10 + 5,最优策略可能是优先找零5 + 5 + 5,此时我们可以分为两种情况:
①最优策略后续找零没有用到10美元:如果最优策略后续没有用到10美元,那么现在找零5 + 5 + 5变为找零10 + 5必定不影响最终结果②最优策略后续找零需要用到10美元:如果最优策略后续用到10美元,我现在将找零5 + 5 + 5变为找零10 + 5,这样我就多出5 + 5,在后续用到10美元时,可以用这5 + 5充当10找零因此最优策略最终可以转化为贪心策略,从而得证该贪心策略求得的解就是最优解。
class Solution { public: bool lemonadeChange(vector<int>& bills) { map<int, int> count; count[5] = 0; count[10] = 0; count[20] = 0; for (auto e : bills) { int pay = e - 5; if (5 == pay) { if (0 == count[5]) { return false; } else { count[5]--; } } else if (15 == pay) { if (count[5] && count[10]) { --count[5]; --count[10]; } else if (count[5] > 2) { count[5] -= 3; } else { return false; } } count[e]++; } return true; } }; 2208. 将数组和减半的最少操作次数给你一个正整数数组 nums 。每一次操作中,你可以从 nums 中选择 任意 一个数并将它减小到 恰好 一半。(注意,在后续操作中你可以对减半过的数继续执行操作) 请你返回将 nums 数组和 至少 减少一半的 最少 操作数。
贪心策略:每次选择最大的一个数进行减半操作
证明:交换论证法我们需要证明最优策略在不破化最优解的前提下可以转化为贪心策略。
假设在某次选择时我们贪心策略选择的是最大的数 x,最优策略选择了一个较小的数 y,即 y < x,我们可以分两种情况讨论:
①最优策略后续没有用到x:如果最优策略后续没有用到x,在当前我们选择了更大x代替y反而可以更快使数组减半,因此此时是可以将y替换成x的②最优策略后续需要用到x:如果最优策略后续需要用到x,那我现在先把x用了也是完全没有问题的因此最优策略最终可以转化为贪心策略,从而得证该贪心策略求得的解就是最优解。
class Solution { public: int halveArray(vector<int>& nums) { priority_queue<double, vector<double>, less<double>> pq(nums.begin(), nums.end()); double sum=0; for(auto e:nums){ sum+=e; } double curSum=sum; sum/=2.0; int count=0; while(curSum>sum){ auto tmp=pq.top(); pq.pop(); pq.push(tmp/2); curSum-=tmp/2; ++count; } return count; } }; 179. 最大数给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。 注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
有两个数a、b,如果组合ab >= ba,那么a一定在b的前面,我们按照此规则将所有数字转为字符串之后排序即可。
class Solution { public: class Compare{ public: bool operator()(const string& num1,const string & num2) const{ return num1+num2>num2+num1; } }; string largestNumber(vector<int>& nums) { vector<string> vs; for(auto e:nums){ vs.push_back(to_string(e)); } sort(vs.begin(),vs.end(),Compare()); string ans; for(auto & e:vs){ ans+=e; } return '0'==ans.front()?"0":ans; } }; 376. 摆动序列如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。 相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。 子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。 给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。
贪心策略: 优先选取极值点,即如果nums[ i : j ]这段区间是递增的,那么我们就优先选取nums[j]作为摆动序列的一个波峰,如果nums[ i : j ]这段区间是递减的,那么我们就优先选取nums[j]作为摆动序列的一个波谷
class Solution { public: int wiggleMaxLength(vector<int>& nums) { int count = 0; int i = 0; for (; i + 1 < nums.size() && nums[i] == nums[i + 1]; ++i) ; if (i + 1 < nums.size() && nums[i + 1] < nums[i]) { for (; i + 1 < nums.size() && nums[i + 1] <= nums[i]; ++i) ; ++count; } if (i + 1 == nums.size()) { return count + 1; } while (i + 1 < nums.size()) { for (; i + 1 < nums.size(); ++i) { if (nums[i + 1] < nums[i]) { ++count; break; } } for (; i + 1 < nums.size(); ++i) { if (nums[i + 1] > nums[i]) { ++count; break; } } } return count + 2; } }; 300. 最长递增子序列 class Solution { public: int lengthOfLIS(vector<int>& nums) { vector<int> v(1,INT_MIN); int i=0; for(i=0;i<nums.size();++i){ if(nums[i]>v.back()){ v.push_back(nums[i]); continue; } int left=1; int right=v.size()-1; while(left<right){ int mid=(left+right)/2; if(v[mid]<nums[i]){ left=mid+1; }else{ right=mid; } } v[left]=nums[i]; } return v.size()-1; } };给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
334. 递增的三元子序列给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。 如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。
考虑到如果有两个长度同为n的子序列,它们末尾的元素为a、b(假设a<b),如果现在来了一个元素c,只要c可以插在b后面,那么c就一定可以插在a后面,因此对于长度为n的子序列我们只需要记录a即可。 使用一个数组v,下标表示已知的某个子序列的长度,可能存在多个长度相同的子序列,v里面存放该长度的子序列末尾元素中最小的一个数字,假设现在记录了最小长度v[ 1 ]=a,最大长度v[n]=b,现在遍历到nums到c元素时:如果c>b,则v[n+1]=c,否则使用二分查找在v中找到一个元素v[i](0<=i<n),使v[i]<=c且v[i+1]>c,这意味这c一定能插在长度为i末尾元素为v[i]的子序列的后面,令v[i+1]=c。最后v的最大下标即为最长递增子序列的长度。
class Solution { public: int lengthOfLIS(vector<int>& nums) { vector<int> v(1,INT_MIN); int i=0; for(i=0;i<nums.size();++i){ if(nums[i]>v.back()){ v.push_back(nums[i]); continue; } int left=1; int right=v.size()-1; while(left<right){ int mid=(left+right)/2; if(v[mid]<nums[i]){ left=mid+1; }else{ right=mid; } } v[left]=nums[i]; } return v.size()-1; } }; 334. 递增的三元子序列给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。 如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。
解法与 300. 最长递增子序列类似,由于只需要判断是否存在长度为 3 的递增子序列,因此我们可以直接比较而不需要二分。
class Solution { public: bool increasingTriplet(vector<int>& nums) { int one=nums[0]; int two=INT_MAX; int i=1; for(i=1;i<nums.size();++i){ if(nums[i]>two){ return true; }else if(nums[i]>one){ two=min(two,nums[i]); }else{ one=min(one,nums[i]); } } return false; } }; 122. 买卖股票的最佳时机 II给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。 在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。 返回 你能获得的 最大 利润 。
方法一:贪心策略:只要在低谷期买入,在下一个高峰期卖出,最后一定可以得到最大利润。
//贪心+双指针 class Solution { public: int maxProfit(vector<int>& prices) { int ans = 0; int i = 0; for (i = 0; i < prices.size();) { int j = i; for (; j + 1 < prices.size() && prices[j + 1] > prices[j]; j++); ans += prices[j] - prices[i]; i = j + 1; } return ans; } }; //贪心+正常求解 class Solution { public: int maxProfit(vector<int>& prices) { int ans = 0; int i = 0; for (i = 0; i + 1 < prices.size(); ++i) { if (prices[i + 1] > prices[i]) { ans += prices[i + 1] - prices[i]; } } return ans; } };方法二:动态规划:
class Solution { public: int maxProfit(vector<int>& prices) { vector<vector<int>> dp(prices.size()+1,{0,0}); dp[0][1]=-prices[0]; int i=0; for(;i<prices.size();++i){ dp[i+1][0]=max(dp[i][0],dp[i][1]+prices[i]); dp[i+1][1]=max(dp[i][1],dp[i][0]-prices[i]); } return dp[i][0]; } }; 1005. K 次取反后最大化的数组和给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组: 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后,返回数组 可能的最大和 。
分类讨论即可:
class Solution { public: int largestSumAfterKNegations(vector<int>& nums, int k) { priority_queue<int> pq; int sum = 0; for (auto e : nums) { if (pq.size() < k) { pq.push(e); } else if (e < pq.top()) { pq.pop(); pq.push(e); } sum += e; } int maxNegative = INT_MIN; int minNonNegative = INT_MAX; while (!pq.empty()) { if (pq.top() >= 0) { minNonNegative = pq.top(); } else { if (INT_MIN == maxNegative) { maxNegative = pq.top(); } else { sum += -2 * pq.top(); --k; } } pq.pop(); } if (INT_MIN != maxNegative) { if (0 == minNonNegative || 1 == k % 2) { sum += -2 * maxNegative; } else if (-maxNegative > minNonNegative) { sum += -2 * maxNegative; sum += -2 * minNonNegative; } } else { if (1 == k % 2) { sum += -2 * minNonNegative; } } return sum; } }; 870. 优势洗牌给定两个长度相等的数组 nums1 和 nums2,nums1 相对于 nums2 的优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描述。 返回 nums1 的 任意 排列,使其相对于 nums2 的优势最大化。
利用田忌赛马的思路,对nums2排升序,依次遍历nums1,选择比nums2中略有优势的马进行比赛,如果连nums2中最差的马都比不过,就选择nums2中目前还为比赛的最强的马进行比赛。
class Solution { public: vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) { vector<int> ans(nums1.size(),-1); vector<int> remain; vector<pair<int,int>> vp; vp.push_back({INT_MIN,INT_MIN}); int i=0; for(i=0;i<nums2.size();++i){ vp.push_back({nums2[i],i}); } sort(vp.begin(),vp.end(),[](const pair<int,int>& p1,const pair<int,int>& p2)->bool{ return p1.first<p2.first; }); for(i=0;i<nums1.size();++i){ int left=0; int right=vp.size()-1; while(left<right){ int mid=(left+right+1)/2; if(nums1[i]>vp[mid].first){ left=mid; }else{ right=mid-1; } } for(;left>0&&-1!=ans[vp[left].second];--left); if(0==left){ remain.push_back(nums1[i]); }else{ ans[vp[left].second]=nums1[i]; } } for(i=0;i<ans.size();++i){ if(-1==ans[i]){ ans[i]=remain.back(); remain.pop_back(); } } return ans; } }; 409. 最长回文串给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的 回文串 的长度。 在构造过程中,请注意 区分大小写 。比如 “Aa” 不能当做一个回文字符串。
class Solution { public: int longestPalindrome(string s) { vector<size_t> count(128,0); int i=0; while(i<s.size()) { count[s[i++]]++; } int odd=0; int s_length=0; for(i=0;i<count.size();++i) { s_length+=count[i]/2*2; if(0==odd) { odd=count[i]%2; } } if(odd) { s_length+=1; } return s_length; } }; 942. 增减字符串匹配由范围 [0,n] 内所有整数组成的 n + 1 个整数的排列序列可以表示为长度为 n 的字符串 s ,其中: 如果 perm[i] < perm[i + 1] ,那么 s[i] == ‘I’ 如果 perm[i] > perm[i + 1] ,那么 s[i] == ‘D’ 给定一个字符串 s ,重构排列 perm 并返回它。如果有多个有效排列perm,则返回其中 任何一个 。
遇到’I’:选择当前可选数字中最小的数 遇到’D’:选择当前可选数字中最大的数
class Solution { public: vector<int> diStringMatch(string s) { int left=0; int right=s.size(); vector<int> ans; int i=0; for(i=0;i<s.size();++i){ if('I'==s[i]){ ans.push_back(left++); }else{ ans.push_back(right--); } } ans.push_back(left); return ans; } }; 455. 分发饼干假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是满足尽可能多的孩子,并输出这个最大数值。
对s和g排升序,依次往后遍历g,对每一个胃口值g[i],我们遍历s,找到一个饼干s[j]>=g[i],其中j不需要回退。
class Solution { public: int findContentChildren(vector<int>& g, vector<int>& s) { sort(g.begin(),g.end()); sort(s.begin(),s.end()); int i=0; int j=0; int ans=0; for(i=0;i<g.size()&&j<s.size();++i){ for(;j<s.size()&&s[j]<g[i];++j); if(j<s.size()){ ++ans; ++j; } } return ans; } }; 553. 最优除法给定一正整数数组 nums,nums 中的相邻整数将进行浮点除法。 例如,nums = [2,3,4],我们将求表达式的值 “2/3/4”。 但是,你可以在任意位置添加任意数目的括号,来改变算数的优先级。你需要找出怎么添加括号,以便计算后的表达式的值为最大值。 以字符串格式返回具有最大值的对应表达式。 注意:你的表达式不应该包含多余的括号。
对任意表达式:a/b/c/d/e/f,最大值肯定是(a*c*d*e*f)/b,我们只需要这样加括号即可:a/(b/c/d/e/f)
class Solution { public: string optimalDivision(vector<int>& nums) { if(1==nums.size()){ return to_string(nums[0]); }else if(2==nums.size()){ return to_string(nums[0])+"/"+to_string(nums[1]); } string ans; ans+=to_string(nums[0]); ans+="/("; int i=1; for(;i<nums.size();++i){ ans+=to_string(nums[i]); if(i+1!=nums.size()){ ans+="/"; } } ans+=")"; return ans; } }; 45. 跳跃游戏 II给定一个长度为 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]。
从nums[0]开始,用left、right指针维护下一次跳跃可达且还未达过的区间,以此进行,直至终点。
class Solution { public: int jump(vector<int>& nums) { if(1==nums.size()){ return 0; } int left=0; int right=0; int count=0; while(right<nums.size()){ ++count; int tmp=right+1; while(left<=right){ tmp=max(tmp,left+nums[left]); if(tmp+1>=nums.size()){ return count; } ++left; } right=tmp; } return count; } }; 55. 跳跃游戏给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。
class Solution { public: bool canJump(vector<int>& nums) { if (1 == nums.size()) { return true; } int left = 0; int right = 0; while (right < nums.size()) { int tmp = 0; while (left <= right) { tmp = max(tmp, left + nums[left]); if (tmp + 1 >= nums.size()) { return true; } ++left; } if(tmp<=right){ return false; } right = tmp; } return false; } }; 134. 加油站在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。
使用一个数组diff,diff[i]=gas[i]-cost[i],从left开始,如果 sum(diff[left- -right])小于0,则left=right+1,直至环绕一圈。
class Solution { public: int canCompleteCircuit(vector<int>& gas, vector<int>& cost) { vector<int> diff(gas.size(),0); int i=0; for(;i<gas.size();++i){ diff[i]=gas[i]-cost[i]; } int left=0; int right=0; while(left<diff.size()){ if(diff[left]<0){ ++left; right=left; continue; } int sum=diff[left]; right=left; while(sum>=0){ right=(right+1)%diff.size(); if(left==right){ return left; } sum+=diff[right]; } if(left>right){ return -1; } left=right+1; } return -1; } }; 738. 单调递增的数字当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。 给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增 。
class Solution { public: int monotoneIncreasingDigits(int n) { string s=to_string(n); int i=0; for(i=1;i<s.size();++i){ if(s[i]<s[i-1]){ int j=i-1; s[j]-=1; for(j=j-1;j>=0&&s[j]>s[j+1];--j){ s[j]-=1; } for(j+=2;j<s.size();++j){ s[j]='9'; } } } return stoi(s); } }; 991. 坏了的计算器在显示着数字 startValue 的坏计算器上,我们可以执行以下两种操作: 双倍(Double):将显示屏上的数字乘 2; 递减(Decrement):将显示屏上的数字减 1 。 给定两个整数 startValue 和 target 。返回显示数字 target 所需的最小操作数。
class Solution { public: int brokenCalc(int startValue, int target) { if(startValue<1){ return INT_MAX; } if(startValue>=target){ return startValue-target; }else if(target<2*startValue){ int tmp1= 2*startValue-target+1; int tmp2=INT_MAX; int tmp3=INT_MAX; if(0==target%2){ tmp2=startValue-target/2+1; }else{ tmp2=startValue-(target+1)/2+2; tmp3=startValue-(target-1)/2+2; } return min(tmp1,min(tmp2,tmp3)); } if(0==target%2){ return brokenCalc(startValue,target/2)+1; } return brokenCalc(startValue,target+1)+1; } }; 56. 合并区间以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
class Solution { public: class Compare { public: bool operator()(vector<int> v1, vector<int> v2) { if (v1[0] < v2[0]) { return true; } return false; } }; vector<vector<int>> merge(vector<vector<int>>& intervals) { sort(intervals.begin(), intervals.end(), Compare()); vector<vector<int>> ans; vector<int> tmp(intervals[0]); for (auto& e : intervals) { if (e[0] > tmp[1]) { ans.push_back(tmp); tmp = e; } else if (e[1] > tmp[1]) { tmp[1] = e[1]; } } ans.push_back(tmp); return ans; } }; 435. 无重叠区间给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。 注意 只在一点上接触的区间是 不重叠的。例如 [1, 2] 和 [2, 3] 是不重叠的。
class Solution { public: int eraseOverlapIntervals(vector<vector<int>>& intervals) { sort(intervals.begin(), intervals.end(), [](vector<int>& v1, vector<int>& v2) -> bool { if (v1[0] != v2[0]) { return v1[0] < v2[0]; } return v1[1] < v2[1]; }); int i = 0; int j = 1; int res = 0; for (; j < intervals.size(); ++j) { if (intervals[j][0] >= intervals[i][1]) { i = j; continue; } if (intervals[i][1] > intervals[j][1]) { i = j; } ++res; } return res; } }; 452. 用最少数量的箭引爆气球有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。 给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。
class Solution { public: int findMinArrowShots(vector<vector<int>>& points) { sort(points.begin(), points.end(), [](vector<int>& v1, vector<int>& v2) -> bool { return v1[0] < v2[0]; }); int res = 0; int i = 0; int j = 1; for (; j < points.size(); i = j, ++j, ++res) { if (points[j][0] > points[i][1]) { continue; } int start = points[j][0]; int end = min(points[i][1], points[j][1]); j++; while (j < points.size() && points[j][0] <= end) { start = points[j][0]; end = min(end, points[j][1]); ++j; } } if (i + 1 == points.size()) { ++res; } return res; } }; 354. 俄罗斯套娃信封问题给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。 当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。 请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。 注意:不允许旋转信封。
按信封宽度由小到大排序,对于宽度相同的信封由大到小排序,我们这时只需要考虑排完序后的信封的高度,这就转为求解“最长递增子序列”问题。
class Solution { public: int maxEnvelopes(vector<vector<int>>& envelopes) { sort(envelopes.begin(), envelopes.end(), [](vector<int>& v1, vector<int>& v2) -> bool { if (v1[0] == v2[0]) { return v1[1] > v2[1]; } return v1[0] < v2[0]; }); vector<int> v; v.push_back(envelopes[0][1]); int i=1; for(;i<envelopes.size();++i){ if(envelopes[i][1]>v.back()){ v.push_back(envelopes[i][1]); continue; } int left=0; int right=v.size()-1; while(left<right){ int mid=(left+right)/2; if(envelopes[i][1]>v[mid]){ left=mid+1; }else{ right=mid; } } v[left]=min(v[left],envelopes[i][1]); } return v.size(); } }; 1262. 可被三整除的最大和给你一个整数数组 nums,请你找出并返回能被三整除的元素 最大和。
利用正难则反原则,先对数组求和得到sum, 如果sum的余数为1:sum要么减去最小的余数为1的整数,要么减去最小和次小的余数为2的整数 如果sum的余数为2:sum要么减去最小的余数为2的整数,要么减去最小和次小的余数为1的整数
class Solution { public: int maxSumDivThree(vector<int>& nums) { map<int, priority_queue<int,vector<int>,greater<int>>> mp; int sum = 0; int i = 0; for (; i < nums.size(); ++i) { sum+=nums[i]; if (0 != nums[i] % 3) { mp[nums[i] % 3].push(nums[i]); } } if(1==sum%3){ int tmp1=INT_MAX; if(mp[2].size()>1){ tmp1=mp[2].top(); mp[2].pop(); tmp1+=mp[2].top(); } int tmp2=mp[1].empty()?INT_MAX:mp[1].top(); sum-=min(tmp1,tmp2); }else if(2==sum%3){ int tmp1=INT_MAX; if(mp[1].size()>1){ tmp1=mp[1].top(); mp[1].pop(); tmp1+=mp[1].top(); } int tmp2=mp[2].empty()?INT_MAX:mp[2].top(); sum-=min(tmp1,tmp2); } return sum; } }; 1054. 距离相等的条形码在一个仓库里,有一排条形码,其中第 i 个条形码为 barcodes[i]。 请你重新排列这些条形码,使其中任意两个相邻的条形码不能相等。 你可以返回任何满足该要求的答案,此题保证存在答案。
将相同的条形码放入到同一个集合里面,一个集合一个集合地遍历,先将结果填入奇数下标中,最后在填入偶数下标中,需要注意第一次填时必须先填最大的集合,之后的集合顺序无所谓。
class Solution { public: vector<int> rearrangeBarcodes(vector<int>& barcodes) { map<int,int> mp; pair<int,int> p={0,0}; for(auto e:barcodes){ mp[e]++; if(p.second<mp[e]){ p={e,mp[e]}; } } mp.erase(p.first); int i=0; vector<int> ans(barcodes.size(),0); for(i=0;i<barcodes.size();i+=2){ ans[i]=p.first; if(0==--p.second&&!mp.empty()){ p=*(mp.begin()); mp.erase(mp.begin()); } } for(i=1;i<barcodes.size();i+=2){ ans[i]=p.first; if(0==--p.second&&!mp.empty()){ p=*(mp.begin()); mp.erase(mp.begin()); } } return ans; } }; 767. 重构字符串给定一个字符串 s ,检查是否能重新排布其中的字母,使得两相邻的字符不同。 返回 s 的任意可能的重新排列。若不可行,返回空字符串 “” 。
只要最大相同的字母的个数不超过(n+1)/2,就一定可以重新排列。
class Solution { public: string reorganizeString(string s) { map<char, int> mp; pair<char, int> p = {0, 0}; for (auto e : s) { mp[e]++; if (p.second < mp[e]) { p = {e, mp[e]}; } } if(p.second>(s.size()+1)/2){ return ""; } mp.erase(p.first); int i = 0; string ans(s.size(), 0); for (i = 0; i < s.size(); i += 2) { ans[i] = p.first; if (0 == --p.second&&!mp.empty()) { p = *(mp.begin()); mp.erase(mp.begin()); } } for (i = 1; i < s.size(); i += 2) { ans[i] = p.first; if (0 == --p.second && !mp.empty()) { p = *(mp.begin()); mp.erase(mp.begin()); } } return ans; } };