005贪心——算法备赛

贪心思想是每次选取眼前的最优解,积累成最终的最优解的方法,体现在积小胜为大胜,步步为营。贪心算法的最主要的特点是无后效性。

  • 无后效性:某个状态以后的变化不会影响以前的状态,自与当前状态有关。

找零问题

问题描述

钱币的面额有100元,50元,20元,5元,1元,给定一个正整数n,代表需要找零的钱币总面额,如何找零能使所需钱币数最少?

原题链接

思路分析

在给定的面额中相邻两个数值,较大数最少是较小数的两倍,能用1个100元就不用两个50元,能用1个20元就不用4个5元…这是一个贪心的过程,每次从较大数开始找钱,直到不能完整给出。

当然,并不是所有面额数组都能用贪心的思路解决找零问题,以5,4,3,1为例,要找7元,贪心的思路是一个5元加2个1元,该方案是不如一个4元加1个3元的。能否用贪心解决找零问题的数值数组的特征就是数组从大到小排序相邻较大数至少是较小数的两倍

可以思考一个一般问题,给定一个面值数组和待找零数额n,问所需最小钱币数是多少?这就需要用到动态规划了。在后续的文章中将给出参考解答。

代码

#include <bits/stdc++.h>
using namespace std;
int main()
{
    
    
  // 请在此输入您的代码
  vector<int>d(5);
  vector<int>t={
    
    100,50,20,5,1};
  int n; cin>>n;
  for(int i=0;i<5;i++){
    
    
    d[i]=n/t[i];
    n%=t[i];
  }
  for(int i=0;i<5;i++){
    
    
    cout<<t[i]<<":"<<d[i]<<endl;
  }
  return 0;
}

加油站

题目描述

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gascost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。
原题链接

思路分析

汽车从 某加油站出发到达下一站的的油量增量(gas[i]-cost[i])是固定的。

如果有解,那油量增量的数组和sum一定是大于等于0的,否则无解。

若当前油量增量加剩余油量小于历史低值(sumMin初始为0)说明从上一阶段确定的起点出发行走一段油站的增量为负数。上一阶段起点到该站之间的油站都不能作为出发站,出发站改为下一站。

代码

int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
    
    
        int sum = 0;
        int sumMin = 0;
        int minIndex = -1; // stIndex = minIndex + 1
        for(int i = 0; i < gas.size(); i++) {
    
    
            sum += gas[i] - cost[i];
            if(sum < sumMin) {
    
    
                sumMin = sum;
                minIndex = i;
            }
        }
        if(sum < 0) return -1;
        else return minIndex + 1; // sum > 0 > minSum, 因此 i != size - 1
    }

买股票的最佳时机||

题目描述

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润
原题链接

代码

int maxProfit(vector<int>& prices) {
    
    
        int n = prices.size();
        int ret = 0;
        int tar=0,res=-prices[0];  //tar为最大收益,res为最大剩余(下一阶段的起点,在极小值处更新)
        for (int i = 1; i < n; i++) {
    
    
            tar = max(res + prices[i], tar);  //每次更新最大收益 若卖出prices[i]后的收益比历史最值大则更新。
            res = max(tar - prices[i], res);  //每次更新最大剩余 若买进prices[i]后的剩余比历史最值大则更新。
        }
        return tar;
    }

高度互不相同的最大塔高和

问题描述

给你一个数组 maximumHeight ,其中 maximumHeight[i] 表示第 i 座塔可以达到的 最大 高度。

你的任务是给每一座塔分别设置一个高度,使得:

  1. i 座塔的高度是一个正整数,且不超过 maximumHeight[i]
  2. 所有塔的高度互不相同。

请你返回设置完所有塔的高度后,可以达到的 最大 总高度。如果没有合法的设置,返回 -1

原题链接
思路分析

首先考虑一下最优的情况,当maximumHeight数组中的元素已经互不相同了,此时的最大塔高就是maximumHeight数组和。

maximumHeight数组存在元素相同的情况时:

先把数组从大到小排序,从最大的元素开始思考:

数组中的最大值 m[i] 不变是最优的。
数组中的次大值呢?如果它等于 m[i],那么它必须变成 m[i]−1,如果小于m[i],就取它的最高高度。

再遇到下一个次大值,依此类推。
第i个塔 的实际值为min(maximumHeight[i],Height[i−1]−1) //Height[i-1]为上一个塔实际取的高度。

代码

using ll = long long;
long long maximumTotalSum(vector<int>& m) {
    
    
    sort(m.rbegin(), m.rend());
    ll now = m[0] + 1;  //记录上一个塔取的高度
    ll ans = 0;
    for (int i : m) {
    
    
        now = min(now-1, i * 1LL);
        if (now <= 0) return -1;                                                                         
        ans += now;
    }
    return ans;
}

执行操作后最大的不同元素数

问题描述

给你一个整数数组 nums 和一个整数 k

你可以对数组中的每个元素 最多 执行 一次 以下操作:

  • 将一个在范围 [-k, k] 内的整数加到该元素上。

返回执行这些操作后,nums 中可能拥有的不同元素的 最大 数量。
原题链接
思路分析
想象一群人在场地上,某些同学聚在一起。现在想让这些同学排成一排,那么最靠左的同学,就尽量往左边移动,腾出足够多位置。他旁边的同学,可以尽量紧挨着最靠左的同学。依此类推。

k * 2 + 1 >= n时,说明每个元素的活动空间都大于等于n,能得到的最大不同元素数肯定为最大值n。

代码

 int maxDistinctElements(vector<int>& nums, int k) {
    
    
    int n = nums.size();
    if (k * 2 + 1 >= n) {
    
    
        return n;
    }

    ranges::sort(nums);
    int ans = 0;
    int pre = INT_MIN; // 记录每个元素左边元素的位置
    for (int x : nums) {
    
    
        x = clamp(pre + 1, x - k, x + k); // min(max(x - k, pre + 1), x + k)
        //x为尽可能往左靠的位置
        if (x > pre) {
    
      //在新空位
            ans++;
            pre = x;
        }
    }
    return ans;
}