动态规划和贪心算法都是一种递推算法,均用局部最优解来推导全局最优解;是对遍历解空间的一种优化,当问题具有最优子结构时,可用动规,而贪心是动规的的特例。
贪心:遵循某种规则,不断(贪心地)选取当前最优策略,最终找到最优解。
题目一: 硬币问题
问题描述:
有1元,5元,10元,50元,100元,500元的硬币各 c 1 c_1 c1, c 5 c_5 c5, c 10 c_{10} c10, c 50 c_{50} c50, c 100 c_{100} c100, c 500 c_{500} c500枚;现在要使用这些硬币来支付A元,最少需要多少枚硬币?假定本题至少存在一种支付方案。
输入:
第一行有6个数字,分别代表从小到大6种面值的硬币的个数
第二行为A,代表需支付的A元
0
=
<
c
i
=
<
1
0
9
0=< c_i =< 10^9
0=<ci=<109
0
=
<
A
=
<
1
0
9
0=< A =<10^9
0=<A=<109
输出:
最后的结果,一个整数
样例1:
输入:
3 2 1 3 0 2
620
输出:
6
完整java代码
public class Main {
static int[] cnts = new int[6]; // 存各种面值硬币对应的数量
static int[] coins = {1, 5, 10, 50, 100, 500};
public static void main(String[] args) {
input();
}
private static void input() {
Scanner scanner = new Scanner(System.in);
for (int i = 0; i < cnts.length; i++) {
cnts[i] = scanner.nextInt();
}
int A = scanner.nextInt();
int result = f(A, 5);
System.out.println(result);
scanner.close();
}
private static int f(int A, int cur) {
// A本身不合理了。
if (A <= 0){
return 0;
}
// 用的是1块的。
if (cur == 0){
return A;
}
int coinValue = coins[cur]; // 获取当前的面值大小
int x = A / coinValue; // 金额里有多少个coinValue,理想值
int cnt = cnts[cur]; // 当前面值的硬币有cnt个,实际值
int temp = Math.min(x, cnt);
return temp+f(A-temp*coinValue, cur-1);
}
}
题目二:快速渡河
问题描述:
N位旅行者乘一条船穿过一条河,但该船最多只能载两个人。因此,必须安排某种策略(需要人来回),以便所有人都能过河。每个人都有不同的划船速度;船中两个人的划船速度取决于较慢者的速度。问,最短时内完成所有人过河,最短时间是多少。
输入:
输入的第一行包含一个整数T(1 <= T <= 20),即测试用例的数量。
然后是T例。每个案例的第一行包含N,第二行包含N个整数,给出每个人过河的时间。
每个案例前面都有一个空白行。
人数不会超过1000,而且没有人花费超过100秒的时间穿越。
输出:
对于每个测试用例,打印一行,其中包含所有N个人穿过河流所需的总秒数。
完整java代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
input();
}
private static void input() {
Scanner scanner = new Scanner(System.in);
int T = scanner.nextInt(); // T 个测试用例
for (int i = 0; i < T; i++) {
int n = scanner.nextInt(); // 每个测试用例对应多少个数据
int[] speed = new int[n];
for (int j = 0; j < speed.length; j++) {
speed[j] = scanner.nextInt();
}
// 排序
Arrays.sort(speed);
f(n, speed);
}
scanner.close();
}
private static void f(int n, int[] speed) {
int left = n;
int ans = 0;
while (left > 0){
// 如果仅有一个人,则直接相加
if (left == 1){
ans += speed[0];
break;
}else if (left == 2){
// 如果仅有两个人,由于排过序,取大值即可
ans += speed[1];
break;
}else if (left == 3){
// 如果有三个人:1,2,3, 12->; 1<-; 13->;
ans += speed[2] + speed[0] + speed[1];
}else {
// 12出发,1返回, 34出发,2返回, 12再出发
int s1 = 2*speed[1] + speed[0] + speed[left-1]; // 两个最慢的人凑一组
// 13出发,1返回,14出发,1返回,12再出发
int s2 = speed[left-1] + speed[left-2] + 2*speed[0]; // 速度快的人带速度最慢的两个人。
ans += Math.min(s1, s2);
left -= 2; // 左侧的人数
}
}
System.out.println(ans);
}
}
题目三:区间调度
问题描述:
有 n n n 项工作,每项工作分别在 s i s_i si 时间开始,在 t i t_i ti 时间结束。对于每项工作,你都可以选择参与与否,如果选择了参与,那么自始至终都必须全程参与。此外,参与工作的时间段不能重复(即使是开始的瞬间和结束的瞬间是重叠也是不允许的)。你的目标是参与尽可能多的工作,那么最多可以参与多少项工作呢?
1
=
<
n
=
<
100000
1 =< n =< 100000
1=<n=<100000
1
=
<
s
i
=
<
t
i
<
=
1
0
9
1 =< s_i =< t_i <= 10^9
1=<si=<ti<=109
输入:
第一行:n
第二行:n个整数,空格隔开,代表n个工作的开始时间
第三行:n个整数,空格隔开,代表n个工作的结束时间
输出:
最后的结果,一个整数
样例:
输入:
5
1 2 4 6 8
3 5 7 9 10
输出:
3
完整java代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
input();
}
private static void input() {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] start = new int[n];
int[] end = new int[n];
Jobs[] jobs = new Jobs[n];
for (int i = 0; i < start.length; i++) {
start[i] = scanner.nextInt();
}
for (int i = 0; i < end.length; i++) {
end[i] = scanner.nextInt();
}
// 实现打包
for (int i = 0; i < jobs.length; i++) {
jobs[i] = new Jobs(start[i], end[i]);
}
Arrays.sort(jobs);
int result = f(n, jobs);
System.out.println(result);
scanner.close();
}
private static int f(int n, Jobs[] jobs) {
int cnt = 1;
int y = jobs[0].end; // 第一个结束的任务,选
for (int i = 0; i < n; i++) {
if (jobs[i].start > y){
cnt++; // 开始时间大于结束时间,选
y = jobs[i].end;
}
}
return cnt;
}
// 必须实现排序规则
private static class Jobs implements Comparable<Jobs>{
int start;
int end;
public Jobs(int start, int end){
this.start = start;
this.end = end;
}。
@Override
public int compareTo(Jobs o) {
int x = this.end - o.end;
if (x == 0){
return this.start - o.start;
}else {
return x;
}
}
}
}
题目四:区间选点
问题描述:
给定
n
n
n 个闭合的整数区间
[
a
i
,
b
i
]
[a_i,b_i]
[ai,bi] 和
n
n
n 个整数
c
1
,
.
.
.
,
c
n
c _1,...,c_n
c1,...,cn 。
编写一个程序:从标准输入读取间隔数,它们的终点和整数 c 1 , . . . , c n c _1,...,c_n c1,...,cn, 计算整数集合 Z Z Z 的最小尺寸,它至少具有 c i c_i ci 公共元素的间隔 [ a i , b i ] [a_i,b_i] [ai,bi] ,对于每个 i = 1 , 2 , 3 , . . . , n i = 1, 2, 3, ... , n i=1,2,3,...,n ;将答案写入标准输出。
输入:
输入的第一行包含整数
n
(
1
<
=
n
<
=
50000
)
n(1 <= n <= 50000)
n(1<=n<=50000) ,表示 间隔数。以下
n
n
n 行描述了间隔。输入的第
(
i
+
1
)
(i + 1)
(i+1)行包含由单个空格分隔的三个整数
a
i
a_i
ai,
b
i
b_i
bi和
c
i
c_i
ci,并且使得
0
<
=
a
i
<
=
b
i
<
=
50000
0 <= a_i <= b_i <= 50000
0<=ai<=bi<=50000 并且
1
<
=
c
i
<
=
b
i
−
a
i
+
1
1 <= c_i <= b_i-a_i + 1
1<=ci<=bi−ai+1 产量。
输出:
输出恰好包含一个等于集合 Z Z Z 的最小尺寸的整数,该集合 Z Z Z 至少与区间 [ a i , b i ] [a_i,b_i] [ai,bi] 共享 c i c_i ci 元素。
样例:
输入:
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
输出:
6
思路:根据区间的终点排序,从左到右遍历,尽量选择靠右的点。注意不能重复选点。
完整java代码
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
Interval[] intervals = new Interval[n];
for (int j = 0; j < n; j++) {
intervals[j] = new Interval(scanner.nextInt(), scanner.nextInt(), scanner.nextInt());
}
Arrays.sort(intervals);
int max = intervals[n-1].t; // 右端点最大值
int[] axis = new int[max+1];
for (int i = 0; i < n; i++) {
int s = intervals[i].s;
int t = intervals[i].t;
int count = sum(axis, s, t);
intervals[i].c -= count;
while (intervals[i].c > 0){
if (axis[t]==0){
axis[t] = 1;
intervals[i].c--;
t--;
}else {
t--;
}
}
}
System.out.println(sum(axis,0, max));
}
private static int sum(int[] axis, int s, int t) {
int sum = 0;
for (int i = s; i <= t; i++) {
sum += axis[i];
}
return sum;
}
private static class Interval implements Comparable<Interval>{
int s;
int t;
int c;
public Interval(int s, int t, int c) {
this.s = s;
this.t = t;
this.c = c;
}
public int compareTo(Interval other) {
int x = this.t - other.t;
if (x==0) {
return this.s-other.s;
}else {
return x;
}
}
}
}