
🔥 贪心算法(greedy algorithm,又称贪婪算法),在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。
贪心算法采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到问题的一个最优解,虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪婪法不要回溯。
举个例子: 桌面上有 6 张纸币,面额分别为 100,70,50, 20,20,10,现在我们可以拿走 3 张纸币,要求总金额最大,该怎么拿
具体例题如下 📚

思路: 只要当天价格比上一天价格大,就累加,示意图如下,然后算出最大利润即可

#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
int maxProfit(vector<int>& prices) {
// write code here
int ret = 0;
for (int i = 0; i < prices.size(); i++)
{
if (prices[i] > prices[i - 1]) ret += prices[i] - prices[i - 1];
}
return ret;
}
int main()
{
int n;
cin >> n;
vector<int> prices;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
prices.emplace_back(x);
}
cout << maxProfit(prices) << endl;
return 0;
}
解析: 先把整数化成字符串,然后再比较a+b和b+a,如果a+b>b+a,就把a排在b的前面,反之则把a排在b的后面,最后输出排序后的字符串,即可得到最大的整数(如果求最小的整数,则从小到大排序)。 举例说明:a=‘123’,b=‘71’,a+b='12371',b+a=‘71123’,所以a+b<b+a,将b排在前面 注意:正常的字符串存在比较缺陷,如:A=’321’,B=’32’,按照标准的字符串比较规则因为A>B,所以A+B > B+A ,而实际上’32132’ < ’32321’。 具体步骤如下: 1.获取n 2.依次获取n个正整数,将整数转换为字符串:声明字符串数组a[n],将获取到的正整数存入数组a中,即可实现正整数到字符串的转换 3.自定义排序函数:如果a+b>b+a,则把a排在前面,否则将b排在前面(对于字符串a、b,a+b表示连接两个字符串形成一个新串) 4.从大到小输出排序后的字符串即可得到最大的整数
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
using namespace std;
bool cmp(string s1, string s2){
return s1 > s2;
}
int main(){
int n;
cin >> n;
vector<string> s;
for (int i = 0; i < n; i++){
string ch;
cin >> ch;
s.push_back(ch);
}
sort(s.begin(), s.end());
for (auto e : s) cout << e;
return 0;
}
思路: 排序+贪心。每次选择当前剩余的人之中,水平最高的两个人和水平最低的一个人组队,这样就能最大化团队水平中位数的值。
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
// ———— 乒乓球筐
void solve1()
{
string s, t;
while (cin >> s >> t)
{
map<char, int> mp;
for (auto e : s)
{
mp[e]++;
}
for (auto e : t)
{
mp[e]--;
}
int f = 1;
for (auto e : mp)
{
if (e.second < 0) f = 0;
}
if (f == 0) cout << "No" << endl;
else cout << "Yes" << endl;
}
}
const int N = 300010;
int a[N];
void solve2()
{
int n;
cin >> n;
for (int i = 0; i < 3 * n; i++) {
cin >> a[i];
}
sort(a, a + 3 * n);
long long ans = 0;
int r = 3 * n - 1, l = 0;
while (l < n)
{
ans += a[r - 1];
r -= 2;
l++;
}
cout << ans << "\n";
}
int main()
{
solve2();
}
思路: 先把整数化成字符串,然后再比较a+b和b+a,如果a+b>b+a,就把a排在b的前面,反之则把a排在b的后面,最后输出排序后的字符串,即可得到最大的整数(如果求最小的整数,则从小到大排序)。 举例说明:a=‘123’,b=‘71’,a+b='12371',b+a=‘71123’,所以a+b<b+a,将b排在前面 注意:正常的字符串存在比较缺陷,如:A=’321’,B=’32’,按照标准的字符串比较规则因为A>B,所以A+B > B+A ,而实际上’32132’ < ’32321’。 具体步骤如下: 1.获取n 2.依次获取n个正整数,将整数转换为字符串:声明字符串数组a[n],将获取到的正整数存入数组a中,即可实现正整数到字符串的转换 3.自定义排序函数:如果a+b>b+a,则把a排在前面,否则将b排在前面(对于字符串a、b,a+b表示连接两个字符串形成一个新串) 4.从大到小输出排序后的字符串即可得到最大的整数
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
bool cmp(string a, string b) {
return a + b > b + a;
}
int main() {
int n;
cin >> n;
vector<string> ans(n);
for (int i = 0; i < n; i++)
cin >> ans[i];
sort(ans.begin(), ans.end(), cmp);
for (auto e : ans) cout << e;
return 0;
}
思路: 该题的贪心策略就是每次向后寻找能跳到的区间里面右端最远的那一个,如果挑不到或者最后不足N那么就证明无法满足,因此我们可以定义一个cmp函数来进行排序。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct node {
int l, r; //左右区间
};
bool cmp(node a,node b) {
if (a.l != b.l) return a.l < b.l;
return a.r > b.r;
}
int main(){
int n, m;
cin >> n >> m;
vector<node> ans(m);
for (int i = 0; i < m; i++)
cin >> ans[i].l >> ans[i].r;
sort(ans.begin(), ans.end(), cmp);
if (ans[0].l != 1) {//如果最左边节点不是1的话,就做不到
cout << -1 << endl;
return 0;
}
int ret = 1; //记录最少选择区间
int maxr = ans[0].r, tmp = ans[0].r; //记录最大右边
for (int i = 1; i < m && maxr < n;) {
while (i < m && ans[i].l <= maxr + 1) { //记住是 + 1,因为 [1,3] 和[4, 5]可以直接相连
tmp = max(tmp, ans[i].r); //记录在当前最大的右边的时候,可以进来多少个左边
i++;
}
if (tmp == maxr) break;
ret++;
maxr = tmp;
}
if (maxr >= n) //当最大右边大于等于n时即可
cout << ret << endl;
else cout << -1 << endl;
return 0;
}
思路: 这题我们的主要思路是用贪心,
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void solve3() {
string s;
cin >> s;
int n = s.size();
// 1. 判断是否全部相同
bool f = false; //判断是否是回文
for (int i = 1; i < n; i++) {
if (s[i] != s[0]) {
f = true;
break;
}
}
if (f == false){
cout << 0 << endl;
return;
}
// 2. 不是相同字符
f = false;
// 3. 判断本身是否为回文
int l = 0, r = n - 1;
while (l < r) {
if (s[l] == s[r]) l++, r--;
else {
f = true; //本身不是回文
break;
}
}
if (f) cout << n << endl;
else cout << n - 1 << endl;
}
int main()
{
solve3();
return 0;
}
//方法一:差分数组的映射
int minmumNumberOfHost(int n, vector<vector<int> >& startEnd) {
map<int, int> mp;
for (int i = 0; i < startEnd.size(); i++) {
mp[startEnd[i][0]]++; //左边 ++
mp[startEnd[i][1]]--; //右边 --
}
int ans = 0, res = 0;
for (auto ip : mp) {
res += ip.second;
ans = max(ans, res);
}
return ans;
}
//方法二:小根堆,只进入右端点,进入点的左端点和堆顶作比较
int minmumNumberOfHost(int n, vector<vector<int> >& startEnd) {
sort(startEnd.begin(), startEnd.end(), [&](vector<int> a, vector<int>b)->bool
{
return a[0] < b[0];
});
priority_queue<int,vector<int>,greater<int>> heap;
heap.push(startEnd[0][1]);//入右端点
for (int i = 1; i < n; i++) {
int a = startEnd[i][0], b = startEnd[i][1];
if (a >= heap.top()) //无重叠
{
heap.pop();
heap.push(b);
}
else heap.push(b); //有重叠
}
return heap.size();
}
思路:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
struct node{
int a, b;
}arr[N];
bool cmp(node a, node b)
{
return a.b < b.b;
}
int main()
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> arr[i].a >> arr[i].b;
sort(arr, arr + n, cmp);
int ret = 1;
int premin = arr[0].b;
for (int i = 0; i < n; i++)
{
if (arr[i].a >= premin) {
ret++;
premin = arr[i].b;
}
}
cout << ret << endl;
return 0;
}
思路:
int getDis(vector<int>& A, int n) {
int mini = 1e5 + 10, maxi = 0;
for (int i = 0; i < n; i++)
{
mini = min(A[i], mini);
maxi = max(A[i] - mini, maxi);
}
return maxi;
}
思路:栈 + 贪心
class Solution {
public:
bool h[500005];
vector<int> solve(vector<int>& a) {
int n = a.size();
stack<int> st;
vector<int> ret;
int aim = n;
for (auto x :a)
{
st.push(x);
h[x] = true;
// 更新目标值
while (h[aim]) {
aim--;
}
while (st.size() && st.top() >= aim){
ret.push_back(st.top());
st.pop();
}
}
return ret;
}
};题目链接:56. 合并区间
贪心策略:
如何求并集: 由于区间已经按照「左端点」排过序了,因此当两个区间「合并」的时候,合并后的区间:
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals)
{
sort(intervals.begin(), intervals.end());
int l = intervals[0][0], r = intervals[0][1];
vector<vector<int>> ret;
for(int i = 1; i < intervals.size(); i++)
{
int a = intervals[i][0], b = intervals[i][1];
if(r >= a){
r = max(r, b);
}
else{
ret.push_back({l, r});
l = a;
r = b;
}
}
ret.push_back({l, r});
return ret;
}
};题目链接:435. 无重叠区间
贪心策略:
如何移除区间范围较大的区间
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end());
int ret = 0, l = intervals[0][0], r = intervals[0][1];
for(int i = 1; i < intervals.size(); i++)
{
int a = intervals[i][0], b = intervals[i][1];
if(a < r){
ret++;
r = min(r, b);
}
else {
r = b;
}
}
return ret;
}
};题目链接:452. 用最少数量的箭引爆气球
贪心策略:
如何求互相重叠区间?
由于我们是按照「左端点」排序的,因此对于两个区间,我们求的是它们的「交集」:
class Solution {
public:
// 相当于求重合区间数量
int findMinArrowShots(vector<vector<int>>& points) {
// 1. 排序
sort(points.begin(), points.end());
// 2. 求互相重叠数量
int ret = 1, r = points[0][1]; // 初始数量为 1
for(int i = 1; i < points.size(); i++)
{
int a = points[i][0], b = points[i][1];
if(a <= r){
r = min(r, b);
}
else {
ret++;
r = b;
}
}
return ret;
}
};题目链接:354. 俄罗斯套娃信封问题
思路:重写排序 + 贪心 + 二分
当我们把整个信封按照「下面的规则」排序之后:
我们发现,问题就变成了仅考虑信封的「右端点」,完完全全的变成的「最长上升子序列」的模型。那么我们就可以用「贪心+二分」优化我们的算法。
class Solution {
public:
int maxEnvelopes(vector<vector<int>>& envelopes) {
sort(envelopes.begin(), envelopes.end(), [&](const auto& a, const auto& b){
return a[0] != b[0] ? a[0] < b[0] : a[1] > b[1];
});
// 贪心 + 二分
vector<int> ret;
ret.push_back(envelopes[0][1]);
for(int i = 1; i < envelopes.size(); i++)
{
int b = envelopes[i][1];
if(b > ret.back()){
ret.push_back(b);
}
else{
int l = 0, r = ret.size() - 1;
while(l < r)
{
int mid = (l + r) / 2;
if(ret[mid] < b ) l = mid + 1;
else r = mid;
}
ret[l] = b;
}
}
return ret.size();
}
};题目链接:991. 坏了的计算器
贪心策略:【正难则反】
当「反着」来思考的时候,我们发现
这样的话,每次的操作都是「固定唯一」的。
class Solution {
public:
int brokenCalc(int startValue, int target) {
// 正难则反
int ret = 0;
while(target > startValue)
{
if(target % 2 == 1) target += 1;
else {
target /= 2;
}
ret++;
}
return ret + startValue - target;
}
};题目链接:397. 整数替换
贪心策略: 我们的任何选择,应该让这个数尽可能快的变成1
对于偶数:只能执行除 2操作,没有什么分析的;
对于奇数:
class Solution {
public:
int integerReplacement(int n) {
int ret = 0;
while(n > 1)
{
if(n % 2 == 0){
n /= 2;
}
else{
if(n == 3){
n -= 1;
}
else if(n % 4 == 1){
n -= 1;
}
else if(n % 4 == 3){
n = n / 2 + 1;
ret++;
}
}
ret++;
}
return ret;
}
};题目链接:1262. 可被三整除的最大和
思路:正难则反 + 贪心 + 分类讨论
正难则反:
分类讨论:
设累加和为 sum,用 x 标记 %3 == 1 的数,用 y 标记 %3 == 2 的数。
那么根据 sum 的余数,可以分为下面三种情况:
class Solution {
public:
int maxSumDivThree(vector<int>& nums) {
const int INF = 0x3f3f3f3f;
int sum = 0;
// x1 < x2, y1 < y2
int x1 = INF, x2 = INF, y1 = INF, y2 = INF;
for(auto &num: nums)
{
sum += num;
if(num % 3 == 1){
if(num < x1) x2 = x1, x1 = num;
else if(num < x2) x2 = num;
}
else if(num % 3 == 2){
if(num < y1) y2 = y1, y1 = num;
else if(num < y2) y2 = num;
}
}
if(sum % 3 == 0) return sum;
else if(sum % 3 == 1) return sum - min(x1, y1 + y2);
else return sum - min(y1, x1 + x2);
}
};题目链接:738. 单调递增的数字
解法(贪心)
class Solution {
public:
int monotoneIncreasingDigits(int n) {
string s = to_string(n);
int i = 0, m = s.size();
while(i + 1 < m && s[i] <= s[i + 1]) i++; // 找递减位置
if(i + 1 == m) return stoi(s); // 直接返回
// 回推找到不等的位置
while(i - 1 >= 0 && s[i] == s[i - 1]) i--;
s[i]--;
i++;
while(i < m) s[i++] = '9';
return stoi(s);
}
};题目链接:1054. 距离相等的条形码
贪心策略:
class Solution {
public:
vector<int> rearrangeBarcodes(vector<int>& barcodes) {
// 1. 哈希统计, 并且找出出现最多次数的数字
unordered_map<int, int> hash;
int maxVal, maxCnt = 0;
for(auto &x: barcodes) {
if(maxCnt < ++hash[x]){
maxVal = x;
maxCnt = hash[x];
}
}
// 2. 处理出现最多次数的数字
int n = barcodes.size(), ind = 0;
vector<int> ret(n);
while(maxCnt--){
ret[ind] = maxVal;
ind += 2;
}
// 3. 处理剩下的数
hash.erase(maxVal);
for(auto &[x, y]: hash){
while(y--){
if(ind >= n) ind = 1;
ret[ind] = x;
ind += 2;
}
}
return ret;
}
};题目链接:767. 重构字符串
class Solution {
public:
string reorganizeString(string s) {
int hash[26] = {0};
char maxVal;
int maxCnt = 0;
// 1. 找出最多
for(auto &ch: s){
if(maxCnt < ++hash[ch - 'a']){
maxVal = ch;
maxCnt = hash[ch - 'a'];
}
}
// 2. 判断是否存在
int n = s.size();
if(maxCnt > (n + 1) / 2) return "";
// 3. 处理最多
int ind = 0;
// C++中的一种字符串初始化方式,表示创建一个长度为n、所有字符均为空格(' ')的字符串对象。
string ret(n, ' '); // 新方法
while(maxCnt--){
ret[ind] = maxVal;
ind += 2;
}
hash[maxVal - 'a'] = 0; // 记得除掉出现次数最多的字符
// 4. 处理其他
for(int i = 0; i < 26; i++)
{
while(hash[i]--){
if(ind >= n) ind = 1;
ret[ind] = i + 'a';
ind += 2;
}
}
return ret;
}
};题目链接:134. 加油站
暴力解法:
贪心优化:
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int n = gas.size();
for(int i = 0; i < n; i++)
{
int rest = 0, step = 0;
for(; step < n; step++)
{
int pos = (i + step) % n;
rest += gas[pos] - cost[pos];
if(rest < 0) break;
}
if(rest >= 0) return i;
i = i + step;
}
return -1;
}
};题目链接:334. 递增的三元子序列
贪心策略:
class Solution {
public:
bool increasingTriplet(vector<int>& nums) {
int a = nums[0], b = INT_MAX;
for(int i = 1; i < nums.size(); i++)
{
if(nums[i] > b) return true;
else if(nums[i] > a) b = nums[i];
else a = nums[i];
}
return false;
}
};题目链接:300. 最长递增子序列
贪心策略:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> ret;
ret.push_back(nums[0]);
for(int i = 1; i < n; i++)
{
// 看是否能放在最后一个位置
if(nums[i] > ret.back()) ret.push_back(nums[i]);
else{
int l = 0, r= ret.size() - 1;
while(l < r){
int mid = (r + l) >> 1;
if(ret[mid] < nums[i]) l = mid + 1;
else r = mid;
}
ret[l] = nums[i];
}
}
return ret.size();
}
};