贪心算法
贪心法:遵循某种规律,不断贪心的选取当前最优策略的算法设计方法。
钞票支付问题
有1元、5元、10元、20元、100元、200元的钞票无穷多张。现使用这些钞票支付x元,最少需要多少张?
如:x=628
最佳支付方法:
3张200块的,1张20块的,1张5块的,3张1块的;
共需要3+1+1+3=8张。
直觉告诉我们:尽可能多的使用面值较大的钞票!
为何这么做一定 是对的?
面额为1元、5元、10元、20元、100元、200元,任意面额是比自己小的面额的
倍数关系。
所以当使用一张较大面额钞票时,若用较小面额钞票替换,一定需要更多的其他面额的钞票!
如果增加面额7元,则贪心不成立.
#include <stdio.h>
/*有1元、5元、10元、20元、100元、200元的钞票无穷多张。现使用这些钞票支付x元,最少需要多少张?*/
int main(){
const int RMB[]={200,100,20,10,5,1};
const int num=6;
int x=628;
int count=0,i,use=0;
for(i=0;i<6;i++){
use=x/RMB[i];
count+=use;
x=x-RMB[i]*use;
printf("需面额为%d的%d张,剩余%d元\n",RMB[i],use,x);
}
printf("共需%d张\n",count);
getchar();
}
分糖果(455)
LeetCode455题
已知一-些孩子和一-些糖果,每个孩子有需求因子g,每个糖果有大小s,当某个糖果的大小s>=某个孩子的需求因子g时,代表该糖果可以满足该孩子;求使用这些糖果,最多能满足多少孩子? (注意,某个孩子最多只能用1个糖果满足)
例如,需求因子数组g= [5, 10,2, 9, 15,9];糖果大小数组s=[6, 1, 20, 3, 8];最多
可以满足3个孩子。
例如,需求因子数组g=[5, 10,2,9, 15, 9];糖果大小数组s=[6, 1,20,3, 8]。.
为了更明显的判断某个孩子可以被某个糖果满足,对g, s进行排序后观察:
g=[2,5,9,9, 10, 15]; s=[1,3, 6, 8, 20]。
1.当某个孩子可以被多个糖果满足时,是否需要优先用某个糖果满足这个孩子?
2.当某个糖果可以满足多个孩子时,是否需要优先满足某个孩子?
分析
需求因子
数组g=[2,5,9,9, 10, 15];
糖果大小数组s=[1,3, 6,8, 20]。
核心目标:
让更多孩子得到满足,有如下规律:
1.某个糖果如果不能满足某个孩子,则该糖果也一定 不能满足需求因子更大的孩子。
如,
糖果1(s= 1)不能满足孩子1(g=2),则不能满足孩子2、孩子3、…孩子7;
糖果2(s= 3)不能满足孩子2(g= 5),则不能满足孩子3、孩子4、…孩子7;
2.某个孩子可以用更小的糖果满足,则没必要用更大糖果满足,因为可以保笛更大的糖果
满足需求因子更大的孩子。(贪心!)
如,
孩子1(g=2),可以被糖果2(s= 3)满足,则没必要用糖果3、糖果4、糖果5满足;
孩子2(g=5),可以被糖果3(s = 6)满足,则没必要用糖果4、糖果5满足;
3.孩子的需求因子更小则其更容易被满足,故优先从需求因子小的孩子尝试,可以得到正
确的结果。
算法思路
1.对需求因子数组g与糖果大小数组s进行从小到大
的排序。
2.按照从小到大的顺序使用各糖果尝试是否可满足某个孩子,每个糖果只尝试1
次;若尝试成功,则换下一个孩子尝试;直到发现没更多的孩子或者没更多的糖果,循环结束。
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
std::sort(g.begin(),g.end());
std::sort(s.begin(),s.end());
int child=0,cookie=0;
while(child<g.size()&&cookie<s.size()){
if(g[child]<=s[cookie]){
child++;
}
cookie++;
}
return child;
}
};
摇摆序列(力扣376)
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
示例 1:
输入: [1,7,4,9,2,5]
输出: 6
解释: 整个序列均为摆动序列。
示例 2:
输入: [1,17,5,10,13,15,10,5,16,8]
输出: 7
解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
示例 3:
输入: [1,2,3,4,5,6,7,8,9]
输出: 2
思路
使用状态机记录上升,下降或开始状态.
如果出现联系上升或下降,需要删除一下元素以保持摆动,关键问题在于删除哪些,很明显,如果上升保留最后那个,因为最后的最大更容易与后面元素形成摆动,同理如果下降也保留最后的.
class Solution {
public int wiggleMaxLength(int[] nums) {
if(nums.length<2) return nums.length;
State state=State.start;
int len=1;
for(int i=1;i<nums.length;i++){
switch(state){
case start:
if(nums[i-1]>nums[i]){
len++;
state=State.down;
} else if(nums[i-1]<nums[i]){
len++;
state=State.up;
}
break;
case up:
if(nums[i-1]>nums[i]){
len++;
state=State.down;
}
break;
case down:
if(nums[i-1]<nums[i]){
len++;
state=State.up;
}
break;
}
}
return len;
}
}
enum State{
up,down,start,
}
移除k个数字(402)
给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。
注意:
num 的长度小于 10002 且 ≥ k。
num 不会包含任何前导零。
示例 1 :
输入: num = “1432219”, k = 3
输出: “1219”
解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。
示例 2 :
输入: num = “10200”, k = 1
输出: “200”
解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。
示例 3 :
输入: num = “10”, k = 2
输出: “0”
解释: 从原数字移除所有的数字,剩余为空就是0。
分析
若去掉某- -位数字,为了使得到的新数字最小,需要尽可能让得到的新数字优先
最高位最小,其次次高位最小,再其次第3位最小…
例如:
一个4位数“1***”,一定比任何9***”.“
8***.、…“2****
小!
一个4位数若最高位确定,如“51**>
一定比任何“ 59**”、“58**. … 、“52**小!
一个4位数若最高、次高位确定,如“531*-定比任何“539*”、“538*"、 …
“532*小!
从高位向低位遍历,如果对应的数字大十下一位.数字,则把该位数字去掉,得到的数字最小!
但是如果暴力遍历复杂度较高,可以使用栈.
使用栈存储最终结果或删除工作,从高位向低位遍历num,如果遍历的数字大于栈顶元素,则将该数字push入栈,如果小于栈顶元素则进行pop弹栈,直到栈为空或不能再删除数
字(k==0)或栈顶小于当前元素为止。
还需考虑遍历完后k不为0,如num=12834,k=2,遍历完后num=1234,k=1,此时应从栈顶删除,结果为123;还有一种情况是串中含有0.
class Solution {
public:
string removeKdigits(string num, int k) {
std::vector<int> S; //使用vector当作占(因为vector可以遍历)
std::string result =""; //存储最终结果的字符串
for (int i = 0; i < num.length(); i++) { //从最高位循环扫描数字num
int number = num[i] -'0'; //将字符型的num转化为整数使用
while(S.size() != 0 &&S[S.size()-1] > number&&k>0){
S.pop_back();
k--;
}
if(number!=0||S.size()!=0) {
S.push_back(number) ; //将数字number压入栈中
}
}
while(S.size() != 0 && k > 0){ //如果栈不空,且仍然可以删除数字
S.pop_back();
k--; //删除数字后更新K
}
//将栈中的元素从头遍历,存储至result
for(int i=0;i<S.size();i++){
result.append(1, '0' + S[i]);
}
if (result =="") {
//如果result为空, result即为0
result = "0";
}
return result ;
}
};
跳跃游戏(55)
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
用一个值来记录能跳到的最远位置,从第一个开始遍历寻找能跳到的最远位置,途中如果i>reach表示没法达到i,则返回false.
时间复杂度为O(n)
class Solution {
public boolean canJump(int[] nums) {
int reach=0;
for(int i=0;i<nums.length&&i<=reach;i++){
reach=Math.max(i+nums[i], reach);
}
return reach>=nums.length-1;
}
}
跳跃游戏2(45)
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
分析:
在到达某点前若一直不跳跃,发现从该点不能跳到更远的地方了,在这
之前肯定有次必要的跳跃!
结论:在无法到达更远的地方时,在这之前应该跳到- -个可以到达更远位置的位置!
算法思路:
1.设置Current. max. index为 当前可达到的最远位置;
2.设置pre. max max index为在遍历各个位置的过程中,各个位置可达到的最远位置;
3.设置jump_ min为 最少跳跃的次数。
4.利用i遍历nums数组,若i超过current_ max_ index, jump_ min加1,current max index =pre_ max_ max_ index
5.遍历过程中,若nums[i]+i (index[i])更大,则更新pre_ max_ max index = nums[i] + i.
public int jump(int[] nums) {
if(nums.length<2)
return 0;
int cur_max=nums[0],pre_max=nums[0],jump=1;
for (int i = 1; i < nums.length; i++) {
if(i>cur_max) {
jump++;
cur_max=pre_max;
}
if(pre_max<i+nums[i])
pre_max=i+nums[i];
}
return jump;
}
射击气球(452)
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。
一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
Example:
输入:
[[10,16], [2,8], [1,6], [7,12]]
输出:
2
解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。
分析:
1.对于某个气球,至少需要使用1只亏箭将它击穿。
2.在这只气球将其击穿的同时, 1
尽可能击穿其他更多的气球! (贪心!)
思路:
1.对各个气球进行排序,按照气球的左端点从小到大排序。
2.遍历气球数组,同时维护- -个射击区间,在满足可以将当前气球射穿的
情况下,尽可能击穿更多的气球,每击穿- -个新的气球,更新- -次射
击区间(保证射击区间可以将新气球也击穿)。
3.如果新的气球没办法被击穿了,则需要增加-名弓箭手,即维护- -个新的射击区间(将该气球击穿),随后继续遍历气球数组。
public int findMinArrowShots(int[][] points) {
if (points.length == 0) return 0;
// sort by x_end
Arrays.sort(points, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1] - o2[1];
}
});
int arrows = 1;
int xStart, xEnd, firstEnd = points[0][1];
for (int[] p : points) {
xStart = p[0];
xEnd = p[1];
if (firstEnd < xStart) {
arrows++;
firstEnd = xEnd;
}
}
return arrows;
}
最优加油方法
已知一条公路上,有一个起点与-个终点,这之间有n个加油站;已知从这n个加
油站到终点的距离d与各个加油站可以加油的量l,起点位置至终点的距离L与起
始时刻油箱中汽油量P;假设使用1个单位的汽油即走1个单位的距离,油箱没有
上限,最少加几次油,可以从起点开至终点?(如果无法到达终点,返回-1)
思考:
汽车经过n个加油站,对于这n个加油站,应该在哪个加油站停1、来加油,
最终既能到达终点,又使得加油次数最少?
若按顺序遍历加油站,则面临:
如果在某个加油站停下来加油,可能是没必要的,有可能不进行这次加
油也能到达终点;
如果在某个加油站不停下来加油,可能由于汽油不够而无法到达终点
或者后面要更多次的加油才能到达终点。
何时加油最合适?
油用光的时候加油最合适!
在哪个加油站加油最合适? .
在油量最多的加油站加油最合适!
思路:
1.设置一个最大堆,用来存储经过的加油站的汽油量。
2.按照从起点至终点的方向,遍历各个加油站之间的距离。
3.每次需要走两个加油站之间的距离d,如果发现汽油不够走距离d时,从最大堆中取出- -个油量添加,直到可以足够走距离d。
4.如果把最大堆的汽油都添加仍然不够行进距离d,则无法达到终点。
5.当前油量P减少d。
6.将当前加油站油量添加至最大堆。
#include <vector>
#include <algorithm>
#include <queue>
bool cmp(const std::pair<int, int> &a,const std::pair<int ,int> &b) {
return a.first > b.first;
}
int get_minimum_stop(int L,int p, //L为起点到终点的距离, P为起点初始的汽油量
std::vector<std::pair<int, int> > &stop) {
// pair<加油站至终点的距离,加油站汽油量
std::priority queue<int> Q; //存储油量的最大堆
int result = 0; //记录加过几次油的变量
stop.push_back(std::make_ pair(0,0));
//将重点作为-一个停靠点,添加至stop数组
std::sort(stop.begin(), stop.end(), cmp); //以停靠点至终点距离从大到小进行排序
for (int i = 0; i < stop.size(); i++){ //遍历各个停靠点
int dis = L - stop[i] . first;
//当前要走的距离即为当前距终点距离
while(!Q.empty() &&P < dis ) {
P += Q.top();
Q.pop() ;
result++;
}
if (Q.empty() &&P < dis){
return -1;
}
P= P-dis;
L = stop[i].first;
Q.push(stop[i].second); //更新L为当前停靠点至终点距离
}
return result;
}
活动安排问题
设有n个活动的集合E={1,2.,…n} ,其中,每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。
每个活动都有一个要求使用该资源的起始时间s和一个结束时间f, 且s<f。
如果选择了活动i ,则它在半开区间[S, f)内占用资源。
若区间[s, f)与区间[s, f)不相交,则称活动i与活动j是相容的。
活动安排问题:在所给的活动集合中选出最大的相容活动子集。
设计思路
该问题要求高效地安排一系列争用某一 公共资源的活动
如果一个活动能尽早结束那就能留更多的时间给别的活动,所以尽可能地选择结束早的.
那我们就可以先对活动结束时间升序排序,再检测是否相容.
步骤:
按结束时间对活动进行升序排列
选择一个结束时间最 早的活动
依次检查后续活动是否与当前已选择的所有活动相容,若相容则将该活
动加入已选择活动集合中,再继续检查下一活动, 否则直接检查下一活
动
直到所有活动全部检查完毕
例如:
i为活动,s[i]为开始时间,f[i]为结束时间.
若被检查的活动的开始时间s;小于最近选择的活动j的结束时间f,则不选择
活动i,否则选择活动i加入已选活动集合中
首先选择最早结束的活动1,s[2]<f[1],所以不选2,s[3]<f[1],也不选,s[4]>=f[1],所以选4,以此类推得到最大相容活动子集{1,4,8,11}.
public class Greedy_Activaty {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
Activaty[] activaties=new Activaty[n];
for (int i = 0; i < n; i++) {
activaties[i]=new Activaty(sc.nextInt(), sc.nextInt(), sc.nextInt());
}
Deque<Activaty> reult=getResult(activaties);
for(Activaty t:reult) {
System.out.println(t.id);
}
}
public static Deque<Activaty> getResult(Activaty[] activaties) {
Arrays.sort(activaties);
Deque<Activaty> result=new LinkedList<Activaty>();
result.add(activaties[0]);
for(int i=1;i<activaties.length;i++) {
Activaty t=activaties[i],pre=result.getLast();
if (t.start>=pre.end) {
result.add(t);
}
}
return result;
}
}
class Activaty implements Comparable<Activaty>{
int id;//活动编号
int start;
int end;//开始和结束时间
@Override
public int compareTo(Activaty o) {//对结束时间升序排序
return this.end-o.end;
}
public Activaty(int id,int start,int end) {
this.id=id;
this.start=start;
this.end=end;
}
}
部分背包问题
有n个物品,他们的重量分别为w[],价值为v[],现有容量为c的背包,这些物品允许部分装入,求背包能装入的最大价值.
部分背包问题是背包问题中最简单的了,直接使用贪心策略,尽量选择性价比最大的物品装入背包.对于性价比大的如果能整个装入那就整个装入,不能就装入一部分.
public class SomePackage {
public static float some_package(int[] w,int[] v,int n,int c){
Thing[] things=new Thing[n];
float result=0;
for (int i = 0; i < n; i++) {
things[i]=new Thing(w[i], v[i]);
}
Arrays.sort(things);
for(Thing t:things) {
if(t.w<=c) {
result+=t.v;
c-=t.w;
}else {
result+=(float)c/t.w*t.v;
c=0;
break;
}
}
return result;
}
}
class Thing implements Comparable<Thing>{
int w;
int v;
float v_w;
public Thing(int w,int v){
this.w=w;
this.v=v;
v_w=(float)v/w;
}
@Override
public int compareTo(Thing o) {//从大到小排序的比较
// TODO Auto-generated method stub
if (o.v_w-this.v_w>0) {
return 1;
}else if (o.v_w-this.v_w<0) {
return -1;
}else{
return 0;
}
}
}