一起刷LeetCode——一和零(背包问题)

48 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情

一和零

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

分析

  • 这个题目汉化的有点绕,其实就是在一个数组中寻找一个最大子集,返回这个子集的数组长度,这个最大子集中最多有m个0和n个1
  • 所以在遍历数组时,对应的字符串要不要添加到子集中,添加了字符串之后,对应的是两个数量,一个是0的数量,一个是1的数量,0的上限是m,1的上限是n,尽可能的添加更多的字符串到子集,经过这么一分析,这个描述就很像背包问题,背包问题简而言之是背包有一定的容量,给定一组物品,物品有自己的重量和价格,使背包内物品的价格尽可能高。只不过这道题还不是传统的背包问题,是背包问题的变体,这道题有两个容量,但是依然可以使用背包问题的思路。
  • 背包问题是经典的动态规划问题,所以定义一下动态规划相关的状态和转移方程
  • 定义状态:dp[i][j],表示i个0,j个1的情况下,最大的子集长度,所以最后的答案是dp[m][n]
  • 状态转移:
    • 当i个0,j个1的时候,遍历到的字符串是str,0的个数为zero,1的个数是one
    • dp[i][j]=Math.max(dp[i][j], dp[i-zero][j-one]+1),当i个0j个1时,需要比较是当前的子集长度大还是把当前遍历到的字符串加到子集后子集的长度大,维护最大的数字
  • 优化点:整体上是从无数据到遍历数组的字符串,进入dp的时候,需要遍历整个dp,为了更好的更新所有的dp,所以在进入dp的时候是从mn开始遍历

代码

var findMaxForm = function(strs, m, n) {
    let dp = Array.from({length:m+1},() => new Uint8Array(n+1))
    for (let i = 0; i < strs.length; i++) {
        let str = strs[i], zeros = 0, ones = 0
        for (let j = 0; j < str.length; j++){
            str.charAt(j) === "0" ? zeros++ : ones++
        }
        for (let j = m; j >= zeros; j--){
            for (let k = n; k >= ones; k--){
                dp[j][k] = Math.max(dp[j][k], dp[j-zeros][k-ones] + 1)
            }
        }
            
    }
    return dp[m][n]
};

总结

  • 很多问题只要分析好了,思路整理清楚了,代码很快就可以写出来了,这道题需要背包问题的灵敏度,使用背包问题的思路,很快就可以写出解法
  • 今天也是有收获的一天