复盘总结hw

一、喜爱值最大 多重背包

第一行 可使用钱总额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);

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值