贪心算法例题总结
<font size="6px">
一、硬币支付问题</font>
****题目描述:有1元,5元,10元,50元,100元,500元的硬币各c1,c5,c10,c50,c100,c500枚。现在要用这些硬币来支付A元,最少需要多少枚硬币?
假定本题至少存在一种支付方案
0<=ci<=10^9
0<=A<=10^9
输入:
第一行有六个数字,分别代表从小到大6种面值的硬币的个数
第二行为A,代表需要支付A元
样例:
输入:
3 2 1 3 0 2
620
输出:
6
思路分析:首先按照题目要求进行输入。然后调用递归函数。函数的参数总共有5个参数,第一个参数是总额,num和coins是两个数组,然后current是当前位置。因为要最少的硬币,所以从数组最末开始选取,current开始时是等于5的。然后先获取本轮递归中硬币的币值,然后用总额除以币值获得需要硬币的个数。然后获取硬币实际有多少个。调用函数选取两者中的最小值。继续递归。最后确定递归出口,并输出结果。
//硬币支付问题
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
int[] num=new int[6];
int[] coins= {1,5,10,50,100,500};
for(int i=0;i<num.length;i++) {
num[i]=reader.nextInt(); //输入每种硬币的个数
}
int A=reader.nextInt(); //输入支付的总额
int answer=Count(A,num,coins,coins.length-1); //调用函数
System.out.println(answer); //输出结果
}
private static int Count(int A, int[] num,int[] coins,int current) {
if(A<=0) { //出口
return 0;
}
int coinsValue=coins[current]; //获取本次循环的硬币值
int temp=A/coinsValue; //看总额要用多少个硬币才能凑出来
int cnt=num[current]; //硬币有多少枚
int t=Math.min(temp, cnt); //运用函数选取两者较小值
return t+Count(A-t*coinsValue,num,coins,current-1); //递归调用
}
<font size="6px">
二、快速渡河问题</font>
****题目描述:有N个人期望去跨越一条河,但是只有一只船,这只船一次最多只能携带两个人。因此一些排列是可以把这艘船运送所有人的往返时间尽可能短的。每个人都有不同的划船速度,两个人一组时的整体速度是由慢的那个人决定的。你的工作就是确定一种策略,用最短的时间将所有人运送过去。
输入输出:
输入的总人数不超过1000,划船速度不超过100
第一行输入总人数,第二行输入每个人的划船速度。
输出最短时间。
样例:
输入:
4
1 2 5 10
输出:
17
思路分析:首先按照题目要求输入每个人的速度,并将速度进行从小到大的排序。然后调用自定义的函数。这道题比较快的两种过河方法分别是:1、永远用速度最快的人带其他人过河。2、先将速度最快的和速度第二快的人一起过河,然后速度最快的返回。然后速度最慢的两个人过河,速度第二快的人返回。通过这两种方式过河,直到最后所有人都通过。
//快速渡河问题
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
int sum=reader.nextInt();
int[] person=new int[sum];
for(int i=0;i<person.length;i++) {
person[i]=reader.nextInt(); //按题目要求输出每个人的速度
}
Arrays.sort(person); //对速度进行排序
f(sum,person); //调用函数
}
private static void f(int sum,int[] person) {
int left=sum; //尚未过河的总人数
int count=0; //总时间
while(left>0) {
if(left==1) { //如果只有一人未过河
count+=person[0];
break;
}else if(left==2) { //如果有两人未过河
count+=person[1];
break;
}else if(left==3) { //如果有三人未过河
count+=person[1]+person[0]+person[2];
break;
}else { //三人以上未过河
//1和速度最慢的先过河,1返回,1和速度第二慢的 过河,1返回
int temp1=2*person[0]+person[left-1]+person[left-2];
//1、2先过河,1返回,两个最慢的过河,2返回
int temp2=2*person[1]+person[0]+person[left-1];
int judge=Math.min(temp1,temp2); //选取最小值
count+=judge;
left-=2; //人数-2
}
}
System.out.println(count); //输出结果
}
<font size="6px">
三、区间调度问题</font>
****题目描述:有n项工作,每项工作分别在si时间开始,早ti时间结束。对于每项工作,你都可以选择参与与否,如果选择了参与,那么自始至终都必须全程参与。此外,参与工作的时间段不能重复(即使是开始瞬间和结束瞬间的重叠也是不允许的),你的目标是参与尽可能多的工作,那么最多能参与多少工作呢?
1<=n<=10000
1<=si<=ti<=10^9
输入输出:
第一行:n
第二行:n个整数空格隔开,代表n个工作的开始时间
第三行:n个整数空格隔开,代表n个工作的结束时间
样例:
输入:
5
1 2 4 6 8
3 5 7 9 10
输出:
3
思路分析:这道题需要用到面向对象的思想,将一个工作的开始时间和结束时间打包成一个对象。本题将一个工作打包成了一个对象Job,变量包括开始时间begin和结束时间end,实现Comparable方法,按照按照结束时间先后排序。因为本题使用贪心算法,本题的贪心之处在于想要参加更多的工作,那就让每一项工作的结束时间尽可能早,这样后面就能选择更多工作。所以对工作排序时,结束时间早的放在前面。创建完Job对象之后,来到main方法。首先按照题目输入开始和结束时间,并把时间打包成Job对象,然后进行排序。调用自定义的f方法。Job数组中的第一个工作时肯定要接的,所以初始化count=1,current=jobs[0]。然后对数组进行遍历,如果有一个工作的开始时间比current的结束时间晚的,就count++;并且将current=jobs[i];。全部遍历结束之后输出结果。
//区间调度问题
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
int num=reader.nextInt();
int[] begin=new int[num];
int[] end=new int[num];
Job[] jobs=new Job[num];
for(int i=0;i<num;i++) {
begin[i]=reader.nextInt(); //获取每段开始时间
}
for(int i=0;i<num;i++) {
end[i]=reader.nextInt(); //获取每段结束时间
}
for(int i=0;i<num;i++) {
jobs[i]=new Job(begin[i], end[i]); //封装成一个job对象
}
Arrays.sort(jobs); //按照结束时间的先后进行排序
f(jobs); //调用函数
}
private static void f(Job[] jobs) {
int count=1;
Job current=jobs[0]; //第一段时间是肯定要取的
for(int i=1;i<jobs.length;i++) {
if(jobs[i].begin>current.end) { //选择下一段开始时间大于本次的结束时间的
count++;
current=jobs[i];
}
}
System.out.println(count); //输出结果
}
private static class Job implements Comparable<Job>{ //Job对象,要实现Comparable接口
int begin;
int end;
public Job(int begin,int end) {
this.begin=begin;
this.end=end;
}
@Override
public int compareTo(Job other) { //按照结束先后进行排序,如果结束时间相同,则按开始先后排序
if(this.end!=other.end) {
return this.end-other.end;
}else {
return this.begin-other.begin;
}
}
}
<font size="6px">
四、字典最小序问题</font>
****题目描述:给一个定长为N的字符串S,构造一个字符串T,长度也为N。起初,T是一个空串,随后反复进行下列任意操作
1、从S的头部删除一个字符,加到T的尾部
2、从S的尾部删除一个字符,加到T的尾部
目标是最后生成的字符串T的字典序尽可能小
1<=N<=2000
字符串S只包含大写英文字母
输入:字符串S
输出:字符串T
要求每80个字符换行输出
样例输入:
6
ACDBCB
样例输出:
ABCBCD
思路分析:这道题首先建立输入,然后调用自定义的方法f。String和StringBuilder的一个区别就是StringBuilder可以修改字符串,本题也采用了StringBuilder的append方法。首先创建一个StringBuilder对象,调用reverse方法对字符串s进行反转。然后创建结果字符串answer。建立循环,比较字符串s和s1的字典序,字典序小的,就将0号位上的字符添加到结果字符串中。最后输出结果。
//字典序最小问题
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
int N = reader.nextInt();
String s = reader.next(); //字符串输入
f(s, N); //调用函数
}
private static void f(String s, int N) {
StringBuilder stringBuilder = new StringBuilder(s); //创建StringBuilder对象
String s1 = stringBuilder.reverse().toString(); //将字符串反转,定义一个新的字符串
StringBuilder answer = new StringBuilder(""); //结果字符串
while (answer.length() < N) { //出口
if (s.compareTo(s1) >= 0) { //如果s的字典序比s1大
answer.append(s1.charAt(0)); //将字符加到结果字符串中
s1 = s1.substring(1); //删除该字符在旧字符串中的位置
} else { //如果s的字典序比s1小
answer.append(s.charAt(0));
s = s.substring(1);
}
if(rs.length()%80==0) {
System.out.println(rs.substring(cnt*80,(cnt+1)*80)); //每80个字符换行输出
cnt++;
}
}
if(rs.length()>cnt*80) {
System.out.println(rs.substring(cnt*80)); //剩余字符输出
}
}
<font size="6px">
五、最优装载问题</font>
题目描述:给出n个物体,第i个物体重量为wi。选择尽量多的物体,使得总重量不超过C
思路分析:这道题较为简单。思路就是每次选择物体最小的。
//最优装载问题
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
int C = reader.nextInt(); //输入总重量
int n = reader.nextInt(); //输入物体总数
int[] object = new int[n];
for (int i = 0; i < object.length; i++) {
object[i] = reader.nextInt(); //输入每个物体的质量
}
f(object, C); //调用函数
}
private static void f(int[] object, int C) {
Arrays.sort(object); //排序
int count = 0; //计数器
for (int i = 0; i < object.length; i++) {
if (C - object[i] >= 0) { //确定是否越界
count++;
C -= object[i];
} else {
break;
}
}
System.out.println(count);
}
<font size="6px">
六、部分背包问题</font>
题目描述:有n个物体,第i个物体的重量为wi,价值为vi。在总重量不超过C的情况下让总价值尽量高。每一个物体都可以只取走一部分,价值和重量按比例算。
求最大总价值
注意:每个物体可以只拿一部分,因此一定可以让总重量恰好为C
思路分析:这道题其实和前面的区间调度问题的思想是一样的,需要创建一个对象来封装重量w和价值v,本题就创建了一个Obj对象,并实现Comparable接口,进行比价,按照每种物品的单价进行排序。创建完对象之后,将重量数组和价值数组封装在对象中。然后遍历这个数组,如果剩余可装的重量大于当前重量,就总重量count加上对应的价值,最大重量逐步减小。如果剩余可装的重量小于于当前重量,就用(C / objs[i].w)计算还能装入多少个物体,然后count加上价值。最后输出结果
// 部分背包问题
public static void main(String[] args) {
int[] w = { 1, 2, 3, 4, 5 }; //重量数组
int[] v = { 3, 4, 3, 1, 4 }; //价值数组
double C = 10; //最大重量
int n = w.length;
Obj[] objs = new Obj[n];
for (int i = 0; i < n; i++) {
objs[i] = new Obj(w[i], v[i]); //创建Obj对象数组
}
Arrays.sort(objs); //按照价值大小排序s
double count = 0; //总价值
for (int i = objs.length - 1; i >= 0; i--) {
if (objs[i].w <= C) { //如果剩余可装的重量大于当前重量
count += objs[i].v;
C -= objs[i].w;
} else { //如果剩余可装的重量小于于当前重量
count += objs[i].v * (C / objs[i].w);
break;
}
}
System.out.println(count); //输出结果
}
private static class Obj implements Comparable<Obj> { //创建一个价值类
int w, v;
double price;
public Obj(int w, int v) {
this.w = w;
this.v = v;
}
public double getPrice() {
this.price = v / w;
return price;
}
@Override
public int compareTo(Obj other) {
if (this.getPrice() > other.getPrice()) {
return 1;
} else if (this.getPrice() == other.getPrice()) {
return 0;
} else {
return -1;
}
}
}
<font size="6px">
七、乘船问题</font>
**题目描述:有n个人,第i个人重量为wi,每艘船的最大载重量均为C,且最多只能乘两人。用最少的船装下所有人。
贪心策略:考虑最轻的人i,如果每个人都无法和他一起坐船(重量和超过C),则唯一的方案就是每个人都坐一条船。否则,他应该选择能和他一起坐船的人中最重的一个人一起坐船
求需要船的数量**
思路分析:这道题首先要对每个人的重量进行排序,因为输入时不一定有序。然后初始化变量,并确定左指针和右指针。开始循环,如果如果最轻的人和最重的人的总重超过最大值,,就最重的人自己一条船,右指针左移一位。否则,最重的和最轻的一条船,左指针右移,右指针左移,两个人都上船。直到最后所有人都上船。输出结果。
public static void main(String[] args) {
int[] w = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //每个人的重量
int n = w.length;
int c = 10; //每艘船的最大载重量
Arrays.sort(w); //对每个人的重量进行排序
int cntOfPerson = n; //剩余人数
int cntOfBoat = 0; //所需船数
int p1 = 0; //左指针
int p2 = n - 1; //右指针
while (cntOfPerson > 0) { //出口条件,所有人上船
if (w[p1] + w[p2] > c) { //如果最轻的人和最重的人的总重超过最大值
p2--;
cntOfPerson--; //最重的人自己一条船,右指针左移一位
cntOfBoat++;
} else { //否则
p1++;
p2--; //最重的和最轻的一条船,左指针右移,右指针左移
cntOfPerson -= 2;
cntOfBoat++;
}
}
System.out.println(cntOfBoat); //输出总船数
}