9、01 背包问题

内容来自刘宇波老师玩转算法面试

1、01 背包问题

image

public class Solution {

    private static class Node {

        public int index;
        public boolean b;  // w[index] 要不要?
        public int w;
        public int v;

        public Node left;  // w[index + 1] 要
        public Node right; // w[index + 1] 不要

        public Node() {
        }

        public Node(int index, boolean b, int w, int v) {
            this.index = index;
            this.b = b;
            this.w = w;
            this.v = v;
        }
    }

    private static Node root;

    /**
     * w[i] 代表重量, v[i] 代表价值, C 代表背包容量
     */
    public static int knapsack01(int[] w, int[] v, int C) {
        if (w == null || w.length == 0 || v == null || v.length == 0) return 0;
        if (w.length != v.length) return 0;
        root = new Node();

        Queue<Node> queue = new LinkedList<>();
        queue.add(root);
        for (int i = 0; i < w.length; i++) {
            List<Node> level = new ArrayList<>(); // 每一层的节点
            int size = queue.size();
            for (int j = 0; j < size; j++) level.add(queue.remove());

            for (Node node : level) {
                node.left = new Node(i, true, node.w + w[i], node.v + v[i]);
                node.right = new Node(i, false, node.w, node.v);
                queue.add(node.left);
                queue.add(node.right);
            }
        }

        List<Node> allNode = new ArrayList<>();
        perOrder(root, allNode);

        int res = -1;
        for (Node node : allNode) {
            if (node.w <= C) res = Math.max(res, node.v);
        }

        return res;
    }

    private static void perOrder(Node node, List<Node> list) {
        if (node == null) return;

        list.add(node);
        perOrder(node.left, list);
        perOrder(node.right, list);
    }
}

2、贪心算法

image

3、递归

3.1、图示

image

3.2、实现

/**
 * w[i] 代表重量, v[i] 代表价值, C 代表背包容量
 */
public static int knapsack01(int[] w, int[] v, int C) {
    if (w == null || w.length == 0 || v == null || v.length == 0) return 0;
    if (w.length != v.length) return 0;

    return dp(w, v, w.length - 1, C);
}

/**
 * 用 w[0 ... index] 的物品, 填充最大容量为 c 的背包的最大价值
 */
private static int dp(int[] w, int[] v, int index, int c) {
    if (index < 0 || c <= 0) return 0;

    // w[index] 的价值为 v[index], w[index] 要不要
    int res = dp(w, v, index - 1, c); // 不要
    if (c >= w[index]) {
        res = Math.max(res, v[index] + dp(w, v, index - 1, c - w[index])); // 要
    }

    return res;
}

4、记忆化搜索

public class Solution {

    private static int[][] memo;

    /**
     * w[i] 代表重量, v[i] 代表价值, C 代表背包容量
     */
    public static int knapsack01(int[] w, int[] v, int C) {
        if (w == null || w.length == 0 || v == null || v.length == 0) return 0;
        if (w.length != v.length) return 0;

        memo = new int[w.length][C + 1];
        for (int[] arr : memo) Arrays.fill(arr, -1);
        return dp(w, v, w.length - 1, C);
    }

    /**
     * 用 w[0 ... index] 的物品, 填充最大容量为 c 的背包的最大价值
     */
    private static int dp(int[] w, int[] v, int index, int c) {
        if (index < 0 || c <= 0) return 0;

        if (memo[index][c] != -1) return memo[index][c];

        // w[index] 的价值为 v[index], w[index] 要不要
        int res = dp(w, v, index - 1, c); // 不要
        if (c >= w[index]) {
            res = Math.max(res, v[index] + dp(w, v, index - 1, c - w[index])); // 要
        }
        memo[index][c] = res;

        return res;
    }
}

5、动态规划

5.1、图示

image
image

5.2、实现

public class Solution {

    /**
     * w[i] 代表重量, v[i] 代表价值, C 代表背包容量
     */
    public static int knapsack01(int[] w, int[] v, int C) {
        if (w == null || w.length == 0 || v == null || v.length == 0) return 0;
        if (w.length != v.length) return 0;

        // memo[i][c] = 用 w[0 ... i] 的物品, 填充最大容量为 c 的背包的最大价值
        int[][] memo = new int[w.length][C + 1];
        for (int[] arr : memo) Arrays.fill(arr, -1);

        for (int c = 0; c <= C; c++) {
            // memo[0][c] = 用 w[0] 的物品, 填充最大容量为 c 的背包的最大价值
            memo[0][c] = (c >= w[0] ? v[0] : 0);
        }

        for (int i = 1; i < memo.length; i++) {
            for (int c = 0; c <= C; c++) {
                // memo[i][c] = 用 w[0 ... i] 的物品, 填充最大容量为 c 的背包的最大价值
                // w[i] 的价值为 v[i], w[i] 要不要
                int res = memo[i - 1][c]; // 不要
                if (c >= w[i]) {
                    res = Math.max(res, v[i] + memo[i - 1][c - w[i]]); // 要
                }
                memo[i][c] = res;
            }
        }

        return memo[w.length - 1][C];
    }
}

6、01 背包问题的优化

6.1、空间优化 1

image

public class Solution {

    /**
     * w[i] 代表重量, v[i] 代表价值, C 代表背包容量
     */
    public static int knapsack01(int[] w, int[] v, int C) {
        if (w == null || w.length == 0 || v == null || v.length == 0) return 0;
        if (w.length != v.length) return 0;

        // 用 w[0 ... i] 的物品, 填充最大容量为 c 的背包的最大价值 = memo[i % 2][c]
        int[][] memo = new int[2][C + 1];
        for (int[] arr : memo) Arrays.fill(arr, -1);

        for (int c = 0; c <= C; c++) {
            // 用 w[0] 的物品, 填充最大容量为 c 的背包的最大价值 = memo[0][c]
            memo[0][c] = (c >= w[0] ? v[0] : 0);
        }

        for (int i = 1; i <= w.length - 1; i++) {
            for (int c = 0; c <= C; c++) {
                // 用 w[0 ... i] 的物品, 填充最大容量为 c 的背包的最大价值 = memo[i % 2][c]
                // w[i] 的价值为 v[i], w[i] 要不要
                int res = memo[(i - 1) % 2][c]; // 不要
                if (c >= w[i]) {
                    res = Math.max(res, v[i] + memo[(i - 1) % 2][c - w[i]]); // 要
                }
                memo[i % 2][c] = res;
            }
        }

        return memo[(w.length - 1) % 2][C];
    }
}

6.2、空间优化 2

image
image

public class Solution {

    /**
     * w[i] 代表重量, v[i] 代表价值, C 代表背包容量
     */
    public static int knapsack01(int[] w, int[] v, int C) {
        if (w == null || w.length == 0 || v == null || v.length == 0) return 0;
        if (w.length != v.length) return 0;

        // memo[c] = 用 w[0 ... i] 的物品, 填充最大容量为 c 的背包的最大价值
        int[] memo = new int[C + 1];
        Arrays.fill(memo, -1);

        for (int c = 0; c <= C; c++) {
            // memo[c] = 用 w[0] 的物品, 填充最大容量为 c 的背包的最大价值
            memo[c] = (c >= w[0] ? v[0] : 0);
        }

        for (int i = 1; i <= w.length - 1; i++) {
            for (int c = C; c >= w[i]; c--) {
                // memo[c] = 用 w[0 ... i] 的物品, 填充最大容量为 c 的背包的最大价值
                // w[i] 的价值为 v[i], w[i] 要不要
                memo[c] = Math.max(memo[c], v[i] + memo[c - w[i]]);
                if (i == w.length - 1 && c == C) return memo[c]; // 在这里或许可以提前返回
            }
        }

        return memo[C];
    }
}

7、01 背包问题的变种

image

8、面试中的 01 背包问题

416 - 分割等和子集

更多问题
322 - 零钱兑换
377 - 组合总和 Ⅳ
474 - 一和零
139 - 单词拆分
494 - 目标和

9.1、图示

image
image

9.2、递归

public static boolean canPartition(int[] nums) {
    int sum = 0;
    for (int num : nums) sum += num;
    if (sum % 2 != 0) return false;
    int c = sum / 2;

    return dp(nums, nums.length - 1, c);
}

/**
 * 使用 nums[0 ... index] 中的数字, 能否完全填满一个容量为 c 的背包
 */
private static boolean dp(int[] nums, int index, int c) {
    if(c == 0) return true;
    if (index < 0 || c < 0) return false;

    // nums[index] 要不要
    boolean res = false;
    res = res || dp(nums, index - 1, c); // 不要
    res = res || dp(nums, index - 1, c - nums[index]); // 要

    return res;
}

9.3、记忆化搜索

public class CanPartition2 {

    private static int[][] memo;

    public static boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) sum += num;
        if (sum % 2 != 0) return false;
        int c = sum / 2;

        // memo[i][c] = 使用 nums[0 ... i] 中的数字, 能否完全填满一个容量为 c 的背包
        memo = new int[nums.length][c + 1]; // 0 -> false, 1 -> true
        for (int[] arr : memo) Arrays.fill(arr, -1);
        return dp(nums, nums.length - 1, c);
    }

    /**
     * 使用 nums[0 ... index] 中的数字, 能否完全填满一个容量为 c 的背包
     */
    private static boolean dp(int[] nums, int index, int c) {
        if (c == 0) return true;
        if (index < 0 || c < 0) return false;

        if (memo[index][c] != -1) return memo[index][c] == 1;

        // nums[index] 要不要
        boolean res = false;
        res = res || dp(nums, index - 1, c); // 不要
        res = res || dp(nums, index - 1, c - nums[index]); // 要
        memo[index][c] = res ? 1 : 0;

        return res;
    }
}

9.4、动态规划

public static boolean canPartition(int[] nums) {
    int sum = 0;
    for (int num : nums) sum += num;
    if (sum % 2 != 0) return false;
    int C = sum / 2;

    // memo[c] = 用 nums[0 ... i] 中的数字, 能否完全填满一个容量为 c 的背包
    boolean[] memo = new boolean[C + 1];

    for (int c = 0; c <= C; c++) {
        // memo[c] = 用 nums[0] 中的数字, 能否完全填满一个容量为 c 的背包
        memo[c] = nums[0] == c;
    }

    for (int i = 1; i <= nums.length - 1; i++) {
        for (int c = C; c >= nums[i]; c--) {
            memo[c] = memo[c] || memo[c - nums[i]];
            if (i == nums.length - 1 && c == C) return memo[c]; // 在这里或许可以提前返回
        }
    }

    return memo[C];
}
posted @ 2023-05-26 20:17  lidongdongdong~  阅读(11)  评论(0编辑  收藏  举报