Java LeetCode 474.一和零

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

题目描述

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];
    }

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

### 回答1: 可以使用Java中的排序功能来实现。可以使用Arrays.sort()函数,将列表中的元素按照字母顺序排序,或者使用Collections.sort()函数,将列表中的元素按用户指定的排序规则排序。 ### 回答2: 为了实现LeetCode 2561题(Rearranging Fruits)的要求,需要使用Java编程语言。主要思路是遍历给定的水果数组,同时用一个哈希表来记录每个水果出现的次数。然后根据题目要求,重新排列水果使得相同类型的水果尽可能接近,并且按照出现次数的非递增顺序排序。 具体实现步骤如下: 1. 创建一个HashMap来存储每个水果的出现次数。遍历给定的水果数组,如果该水果已经存在于HashMap中,则将其出现次数加1;否则,将该水果添加到HashMap,并将其出现次数初始化为1。 2. 创建一个ArrayList来存储已经排列好的水果。通过HashMap的entrySet方法获取到每种水果和它的出现次数,然后将这些entry按照出现次数的非递增顺序进行排序。 3. 遍历排序好的entry集合,根据每个水果的出现次数,在ArrayList中连续添加相应数量的水果。 4. 返回排列好的水果数组。 以下是Java代码的示例实现: ```java import java.util.*; class Solution { public String[] rearrange(String[] fruits) { HashMap<String, Integer> fruitCountMap = new HashMap<>(); // 统计每个水果的出现次数 for (String fruit : fruits) { if (fruitCountMap.containsKey(fruit)) { fruitCountMap.put(fruit, fruitCountMap.get(fruit) + 1); } else { fruitCountMap.put(fruit, 1); } } ArrayList<Map.Entry<String, Integer>> sortedEntries = new ArrayList<>(fruitCountMap.entrySet()); // 根据出现次数进行非递增排序 Collections.sort(sortedEntries, new Comparator<Map.Entry<String, Integer>>() { public int compare(Map.Entry<String, Integer> entry1, Map.Entry<String, Integer> entry2) { return entry2.getValue().compareTo(entry1.getValue()); } }); ArrayList<String> rearrangedFruits = new ArrayList<>(); // 根据出现次数连续添加水果 for (Map.Entry<String, Integer> entry : sortedEntries) { String fruit = entry.getKey(); int count = entry.getValue(); for (int i = 0; i < count; i++) { rearrangedFruits.add(fruit); } } return rearrangedFruits.toArray(new String[0]); } } ``` 使用以上代码,可以对给定的水果数组进行重新排列,使得相同类型的水果尽可能接近,并且按照出现次数的非递增顺序进行排序。返回的结果就是排列好的水果数组。 ### 回答3: 题目要求将一个字符串中的水果按照特定规则重新排列。我们可以使用Java来实现这个问题。 首先,我们需要定义一个函数来解决这个问题。 ```java public static String rearrangeFruits(String fruits) { // 将字符串转换为字符数组方便处理 char[] fruitArray = fruits.toCharArray(); // 统计每种水果的数量 int[] fruitCount = new int[26]; for (char fruit : fruitArray) { fruitCount[fruit - 'a']++; } // 创建一个新的字符数组来存储重新排列后的结果 char[] rearrangedFruitArray = new char[fruitArray.length]; // 逐个将水果按照规则放入新数组中 int index = 0; for (int i = 0; i < 26; i++) { while (fruitCount[i] > 0) { rearrangedFruitArray[index++] = (char) ('a' + i); fruitCount[i]--; } } // 将字符数组转换为字符串并返回 return new String(rearrangedFruitArray); } ``` 上述代码中,我们首先将字符串转换为字符数组,并使用一个长度为26的数组来统计每一种水果的数量。然后,我们创建一个新的字符数组来存储重新排列后的结果。 接下来,我们利用双重循环将每一种水果按照规则放入新数组中。最后,我们将字符数组转换为字符串并返回。 例如,如果输入字符串为`"acbba"`,则经过重新排列后,输出结果为`"aabbc"`。 这样,我们就用Java实现了题目要求的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值