Java LeetCode 474.一和零

本博客探讨了如何使用动态规划解决LeetCode中的一道题目,即找到一个二进制字符串数组的最大子集,使得子集中最多有m个0和n个1。通过01背包的动态规划模板,我们定义了状态转移方程,并逐步解析了解题过程。最终,通过计算每个字符串中0和1的数量,实现了状态转移,找到了最大子集的长度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

https://leetcode-cn.com/problems/ones-and-zeroeshttps://leetcode-cn.com/problems/ones-and-zeroes给你一个二进制字符串数组  strs  和两个整数  和 

请你找出并返回  strs  的最大子集的长度,该子集中 最多 有  个  和  n  个 

如果  x  的所有元素也是  y  的元素,集合  x  是集合  y  的 子集

难度  中等  来源:力扣(LeetCode)

示例 1

输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例 2

输入:strs = ["10", "0", "1"], m = 1, n = 1

输出:2

解释:最大的子集是 {"0", "1"} ,所以答案是 2 。

方法名称

public int findMaxForm(String[] strs, int m, int n) {
    //在此写出你的代码
}

理解:

这个题中,题目解释得到子集,说明每一个字符串只能取一次。根据条件最多有m个0和n个1。而每一个字符串只包含‘0’和‘1’,说明对于每一个字符串中的1和零,要么取,要么不取。对于取还是不取,这就是动态规划要解决的问题。动态规划的思路是:物品一个一个尝试,容量一点一点尝试,每个物品分类讨论的标准是:选与不选。因为条件限制了0和1的数量,每一个字符串的0和1要么取,要么不取,是01背包的问题。

本题为了理解,不使用优化。下面介绍没有经过优化的01背包模板。

01背包模板

状态转移方程定义

dp[i][j],表示从0-i个物品中选择一个物品,把它放在承重为j的背包中的最大价值

状态转移方程实现

假设需要用一个背包去背不同重量的砖块,不同重量的砖块挣的钱也不一样。背包中已经放了一些砖块。现在要新放入一个砖块:

(1)如果放入的砖块重量加上原来的重量大于了背包的承受重量,背包就会被弄破,所以不能放入这个砖块。背包里面的砖块还是那些砖块。

(2)如果放入的砖块重量加上原来的重量小于等于了背包的承受重量,这个时候,如果放入这个砖块,那么背包里面多了一个砖块,但是背包里还能放进去的砖块的重量就少了。同时,放进去了一个砖块,意味了能挣到这个砖块重量的钱了;如果嫌弃钱太少,就不放入这个砖块,那么背包中的砖块还是那些,重量还是那么重。

(1)不能取,背包的状态不更新

dp[i][j] = dp[i - 1][j];背包中还是那些物品,背包还能承受的重量为j,价值没有变。

(2)能取

1)产生的价值太少,不想取,背包的状态不更新

dp[i][j] = dp[i - 1][j];背包中还是那些物品,背包还能承受的重量为j,价值没有变。

2)取,背包的状态更新

dp[i][j] = value[i] + dp[i - 1][j - weight[i]];新加入的物品产生了价值(间接相当于加入了这个物品),背包中还有原来的物品。新加入的物品减少了背包还能承受的重量。价值是新加入的物品的价值加旧的包的价值

这种情况中需要选择一个产生最大价值的情况。

状态转移方程初始化

dp[i][0] = 0;当背包不能承受重量的时候,什么物品都不能加,没有价值。

本题步骤

套入模板,要格外注重状态方程的定义,它是对于整个题目的理解。虽然有模板,但是不熟练的时候,仍然是套不上的,所以要多多的练习,同时,具体问题具体分析,没必要严格套模板。这个模板是没有优化的。

状态转移方程的定义

题目对于0和1都有限制,那么0和1都是我们的背包承受重量。这种情况也不需要害怕,在二维数组的基础上再加一个维度就可以了。定义的状态转移方程如下:

dp[k][i][j]:从0-k个字符串中选择一个字符串,把它放在i(限制0的容量)和j(限制1的容量)容量下的最大子集数量。

这样定义之后,我们肯定是要知道每一个字符串中1和0的数量。所以要先进行一个预备工作,创建一个二维数组,计算每一个字符串中0的数量,放在0下标,1的数量放在1下标。

        int len = strs.length;
        //获得1和0的二维数组
        int[][] cnt = new int[len][2];
        for (int i = 0; i < len; i++) {
            int zero = 0;
            int one = 0;
            for (char ch : strs[i].toCharArray()) {
                if (ch == '0') {
                    ++zero;
                } else {
                    ++one;
                }
            }
            cnt[i] = new int[] {zero, one};
        }

状态转移方程实现

dp[k][i][j]

(1)当取不了这个字符串中的1和0,也就是说,这个字符串中的1和0数量大于了题目限制的1和0数量或者总的1和0数量大于了题目的限制,这个时候,不更新包的状态。

if (i < zero || j < one), 有dp[k][i][j] = dp[k - 1][i][j];

(2)能取,又分成两种情况

1)要取这个字符串,得到它的1和0的数量后,背包中还能放1和0要减少,同时,多了一个子集(注意对这个三维数组的定义)。

dp[k][i][j] = dp[k - 1][i - zero][j - one] + 1;

2)不取这个字符串,那么就不需要更新包的状态。

dp[k][i][j] = dp[k - 1][i][j];

(2)中取一个最大的值。

状态转移方程初始化

dp[k][0][0] = 0;当1被限制在0数量,0被限制在0数量的时候,啥都放不了,自然是0。不过Java在创建数组的时候,默认是0,所以可以不用写。

特殊的,当这个字符串数组是空的或它的长度是0,就直接返回0,不用走下面的步骤了。

public int findMaxForm(String[] strs, int m, int n) {
        if (strs == null || strs.length == 0) {
            return 0;
        }
        int len = strs.length;
        int[][] cnt = new int[len][2];
        for (int i = 0; i < len; i++) {
            int zero = 0;
            int one = 0;
            for (char ch : strs[i].toCharArray()) {
                if (ch == '0') {
                    ++zero;
                } else {
                    ++one;
                }
            }
            cnt[i] = new int[] {zero, one};
        }

        int[][][] dp = new int[len + 1][m + 1][n + 1];
        for (int k = 1; k <= len; k++) {
            int zero = cnt[k - 1][0];
            int one = cnt[k - 1][1];
            for (int i = 0; i <= m; i++) {
                for (int j = 0; j <= n; j++) {
                    if (i < zero || j < one) {
                        dp[k][i][j] = dp[k - 1][i][j];
                    } else {
                        dp[k][i][j] = Math.max(dp[k - 1][i][j], dp[k - 1][i - zero][j - one] + 1);

                }
            }
        }
        return dp[len][m][n];
    }

可以看出,这样写出来的速度比较慢,但是胜在好理解。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值