一、喜爱值最大 多重背包
第一行 可使用钱总额X 零食种类N
第2~N+1行 零食价格 数量 喜爱程度
问:选取X元零食可达到最大喜爱度
6 7
3 1 8
4 1 2
3 1 1
9 1 7
4 1 1
5 1 18
4 1 4
输出 18
6 7
3 1 8
4 1 2
3 1 1
9 1 7
4 1 1
4 1 8
4 1 4
输出 9
解析:多重背包问题
状态方程:
# k为装入第i种物品的件数, k <= min(n[i], j/w[i])
dp[i][j] = max{(dp[i-1][j − k*w[i]] + k*v[i]) for every k}
同理也可以进行空间优化,而且 j 也必须逆向枚举,优化后伪代码为
// 完全背包问题思路二伪代码(空间优化版)
dp[0,...,W] = 0
for i = 1,...,N
for j = W,...,w[i] // 必须逆向枚举!!!
for k = [0, 1,..., min(n[i], j/w[i])]
dp[j] = max(dp[j], dp[j−k*w[i]]+k*v[i])
总的时间复杂度约为O(NWn-) = O(Wsigma(ni)) 级别.n-表示n平均
package zsh;
import java.util.Scanner;
public class BoundedKnapsack { //多重背包
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int amount = scanner.nextInt(); //总金额 //背包重量
int snackTypes = scanner.nextInt(); //零食种类 //物品种类
int[] price = new int[snackTypes]; //零食价格 //物品重量
int[] num = new int[snackTypes]; //零食数量 //物品数量
int[] degree = new int[snackTypes]; //喜爱程度 //物品价格
for (int i = 0; i < snackTypes; i++) {
price[i] = scanner.nextInt();
num[i] = scanner.nextInt();
degree[i] = scanner.nextInt();
}
int[] dp = new int[amount+1];
for (int i = 0; i < snackTypes; i++) {
// for (int j = amount; j > 0; j -= price[i]) { //注意此处不能为j -= price[i],会略去很多中间态,使结果错误
for (int j = amount; j >= price[i]; j--) {
int min = Math.min(j/price[i], num[i]);
for (int k = 1; k <= min; k++) {
dp[j] = Math.max(dp[j], dp[j - k * price[i]] + k * degree[i]);
}
}
}
System.out.println(dp[amount]);
}
}
//反例
//5 2
//2 3 4
//3 4 5
二、夜宵发放 恰好装满01背包,求方案总数
满足总价等于X且商品不允许重复,进行夜宵组合,计算对应的组合个数
输入:
第一行:总价X和商品件数M
第二行:M件商品的价格
输出:
返回不同夜宵的种类组合个数
如果输入无法达成目标,返回-1
输入
//25 5 5 4
//3 5 10 7 5 1 2 3 2
输出 2
输入
//2 5
//3 5 10 7 5
输出 -1
方法一:恰好装满01背包,求方案总数问题 dp[0] = 1 初始化巧妙,时间复杂度O(NW)
装满背包或将背包装至某一指定容量的方案总数。对于这类问题,需要将状态转移方程中的 max 改成 sum ,大体思路是不变的。初始化dp[0] = 1
例如若每件物品均是完全背包中的物品,转移方程即为
dp[i][j] = sum(dp[i−1][j], dp[i-1][j−w[i]]) // j >= w[i]
状态方程:
if(dp[i - price] != 0){
dp[i] += dp[i - price]; //等同上面sum()
}
举例
0 1 2 3 4 5
1 1 1 0 0 0 0
2 1 1 0 0
3 2 1 1
2 2 3 2 3
package zsh;
import java.util.Scanner;
public class CombinationSum {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int target = scanner.nextInt();
int num = scanner.nextInt();
int[] arr = new int[num];
for (int i = 0; i < num; i++) {
arr[i] = scanner.nextInt();
}
int[] dp = new int[target+1];
dp[0] = 1;
for(int price : arr){
for(int i = target; i >= price; i--){
if(dp[i - price] != 0){
dp[i] += dp[i - price];
}
}
}
if(dp[target] > 0) {
System.out.println(dp[target]);
}
else{
System.out.println(-1);
}
}
}
//25 5 5 4
//3 5 10 7 5 1 2 3 2
方法二:排序 dfs 剪枝 回溯
package zsh;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
public class Solution {
static int count = 0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
int len = candidates.length;
List<List<Integer>> res = new ArrayList<>();
if (len == 0) {
return res;
}
// 关键步骤
Arrays.sort(candidates);
Deque<Integer> path = new ArrayDeque<>(len);
dfs(candidates, len, 0, target, path, res);
return res;
}
/**
* @param candidates 候选数组
* @param len 冗余变量
* @param begin 从候选数组的 begin 位置开始搜索
* @param target 表示剩余,这个值一开始等于 target,基于题目中说明的"所有数字(包括目标数)都是正整数"这个条件
* @param path 从根结点到叶子结点的路径
* @param res
*/
private void dfs(int[] candidates, int len, int begin, int target, Deque<Integer> path, List<List<Integer>> res) {
if (target == 0) {
res.add(new ArrayList<>(path));
count++;
return;
}
for (int i = begin; i < len; i++) {
// 大剪枝:减去 candidates[i] 小于 0,减去后面的 candidates[i + 1]、candidates[i + 2] 肯定也小于 0,因此用 break
if (target - candidates[i] < 0) {
break;
}
// 小剪枝:同一层相同数值的结点,从第 2 个开始,候选数更少,结果一定发生重复,因此跳过,用 continue
// if (i > begin && candidates[i] == candidates[i - 1]) {
// continue;
// }
path.addLast(candidates[i]);
// 调试语句 ①
// System.out.println("递归之前 => " + path + ",剩余 = " + (target - candidates[i]));
// 因为元素不可以重复使用,这里递归传递下去的是 i + 1 而不是 i
dfs(candidates, len, i + 1, target - candidates[i], path, res);
path.removeLast();
// 调试语句 ②
// System.out.println("递归之后 => " + path + ",剩余 = " + (target - candidates[i]));
}
}
public static void main(String[] args) {
// int[] candidates = new int[]{10, 1, 2, 7, 6, 1, 5};
// int target = 8;
int[] candidates = new int[]{1, 1, 1};
int target = 2;
Solution solution = new Solution();
List<List<Integer>> res = solution.combinationSum2(candidates, target);
System.out.println("输出 => " + res);
System.out.println(count);
}
}
原代码,没有排序剪枝,时间复杂度高,O(2^N)
package zsh;
import java.util.Arrays;
import java.util.Scanner;
public class Main1 {
static int res = 0;
static int totalPrice;
public static void dfs( int index, int[] priceArr, int sum){
sum += priceArr[index];
if(totalPrice < sum){
return;
}
if(totalPrice == sum){
res++;
return;
}
for(int i = index+1; i < priceArr.length; i++){
dfs(i, priceArr, sum);
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
totalPrice = scanner.nextInt();
int num = scanner.nextInt();
int[] priceArr = new int[num];
for (int i = 0; i < num; i++) {
priceArr[i] = scanner.nextInt();
}
int min = Arrays.stream(priceArr).min().getAsInt();
if(totalPrice < min){
res = -1;
}
else {
for (int i = 0; i < num; i++) {
dfs( i, priceArr, 0);
}
if (res == 0) {
res = -1;
}
}
System.out.println(res);
}
}
//2 5
//3 5 10 7 5
三、连连看 bfs
存在M行N列矩阵,判断两个同样类型的方块之间是否能用k条相连的直线连接,0代表空地,其他数字代表不同类型的方块,直线只能经过空地,并且在矩阵内部,要求找出两点之间连接起来最少需要的直线数。
输入:第一行 M N 0<M,N<51
第二行到M+1行表示矩阵
M+2行表示需要判断的两个点
3 5
1 0 2 0 3
0 2 0 1 1
0 3 0 0 0
0 2 1 1
输出 最少直线数 2
解析:bfs遍历当前点可以直线走到的所有点存到队列,从队列取首再遍历该节点直线可到点,在第i层直线数就是i。注意不同于普通bfs,只遍历当前可到下一层,这个是遍历直线可到节点。
package zsh;
import java.util.LinkedList;
import java.util.Queue;
public class ShortestPath2 {
public static void main(String[] args) {
int row = 3;
int col = 5;
int[][] arr = {{1, 0, 2, 0, 3},
{0, 2, 0, 1, 1},
{0, 3, 0, 0, 0}};
int[][] visit = new int[row][col];
int[] start = new int[]{0, 2};
int[] end = new int[]{1, 1};
visit[start[0]][start[1]] = 1;
int count = 0;
Queue<int[]> queue = new LinkedList<>();
queue.add(start);
while(!queue.isEmpty() && visit[end[0]][end[1]] == 0) {
Queue<int[]> tmpq = new LinkedList<>();
while (!queue.isEmpty()) {
int[] p = queue.poll();
int x = p[0];
int y = p[1];
for (int i = x + 1; i < row && (arr[i][y] == 0 || (i == end[0] && y == end[1])); i++) {
if (visit[i][y] == 0) {
tmpq.add(new int[]{i, y});
visit[i][y] = 1;
}
}
for (int i = x - 1; i >= 0 && (arr[i][y] == 0 || (i == end[0] && y == end[1])); i--) {
if (visit[i][y] == 0) {
tmpq.add(new int[]{i, y});
visit[i][y] = 1;
}
}
for (int i = y + 1; i < col && (arr[x][i] == 0 || (x == end[0] && i == end[1])); i++) {
if (visit[x][i] == 0) {
tmpq.add(new int[]{x, i});
visit[x][i] = 1;
}
}
for (int i = y - 1; i >= 0 && (arr[x][i] == 0 || (x == end[0] && i == end[1])); i--) {
if (visit[x][i] == 0) {
tmpq.add(new int[]{x, i});
visit[x][i] = 1;
}
}
}
count++;
queue = tmpq;
}
System.out.println(count);
}
}