当前位置: 首页 > news >正文

递归入手三维动态规划

一和零

https://leetcode.cn/problems/ones-and-zeroes/description/

题目解析

由递归入手, 就是一颗选和不选的树, 我们很好通过 dfs 来写出递归

题目解析
public class 一和零 {
    public static int zeros;
    public static int ones;
    
    public int findMaxForm(String[] strs, int m, int n) {
        return dfs(strs,m,n,0);
    }

    private int dfs(String[] strs, int m, int n, int pos) {
        if (pos == strs.length) {
            return 0;
        }
        int ans = Integer.MIN_VALUE;
        // 不选这个
        ans = dfs(strs,m,n,pos + 1);
        // 选这个
        zerosAndOnes(strs[pos],pos);
        if (m - zeros >= 0 && n - ones >= 0) {
            ans = Math.max(ans,dfs(strs,m - zeros,n - ones,pos + 1) + 1);
        }
        return ans;
    }
    
    private void zerosAndOnes(String str, int pos) {
        ones = 0;
        zeros = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == '1') {
                ones++;
            }
        }
        zeros = str.length() - ones;
    }
}

在这里插入图片描述

不出所料地绝对超时了, 我们进行记忆化搜索进行优化, 挂上缓存, 避免重复的递归

记忆化搜索

我们根据变化的三个变量, 构造出一张表, 开始都是 -1, 每次递归如果不是 -1, 说明之前已经进行过一次递归了, 直接拿缓存的值即可
注意 : 数组的下标 m 和 n 需要增加 1, 因为我们是以数组的下标来进行映射的, 所以数组要扩大 + 1

class Solution {
    public static int zeros;
    public static int ones;
    public int findMaxForm(String[] strs, int m, int n) {
        int[][][] dp = new int[m + 1][n + 1][strs.length];
        for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                for (int k = 0; k < strs.length; k++) {
                    dp[i][j][k] = -1;
                }
            }
        }
        return dfs(strs,m,n,0,dp);
    }

    private int dfs(String[] strs, int m, int n, int pos,int[][][] dp) {
        if (pos == strs.length) {
            return 0;
        }
        if (dp[m][n][pos] != -1) {
            return dp[m][n][pos];
        }
        int ans = Integer.MIN_VALUE;
        // 不选这个
        ans = dfs(strs,m,n,pos + 1,dp);
        // 选这个
        zerosAndOnes(strs[pos],pos);
        if (m - zeros >= 0 && n - ones >= 0) {
            ans = Math.max(ans,dfs(strs,m - zeros,n - ones,pos + 1,dp) + 1);
        }
        dp[m][n][pos] = ans;
        return ans;
    }

    private void zerosAndOnes(String str, int pos) {
        ones = 0;
        zeros = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == '1') {
                ones++;
            }
        }
        zeros = str.length() - ones;
    }
}

在这里插入图片描述

当我们挂上缓存后已经可以通过了, 这时候我们由自底向上, 考虑如何实现 dp

动态规划

在这里插入图片描述

根据之前的记忆化搜索, 我们可以发现, i 层是严格依赖于 i + 1 层的值, i + 1 层是没有字符串的, 所以全部都置为 0
逻辑直接抄记忆化搜索中的一些关系, 我们只需要处理好边界条件即可

初始化

最上面一层都是 0, 无需进行初始化

填表顺序

层数从上到小还是从下到上都可以, j, k, 由于当前层是依赖于上一层的位置关系, 所以这个 从前往后还是从后往前进行填表都可以

返回值

返回第 0 层, 因为我们是自底向上的, 前 m 个和前 n 个的最大值
所以返回 return dp[0][m][n]

class Solution {
    public int zeros;
    public int ones;
    public int findMaxForm(String[] strs, int m, int n) {
        int[][][] dp = new int[strs.length + 1][m + 1][n + 1];
        for (int i = strs.length - 1; i >= 0; i--) {
            zerosAndOnes(strs[i]);
            for (int j = 0; j <= m; j++) {
                for (int k = 0; k <= n; k++) {
                    dp[i][j][k] = dp[i + 1][j][k];
                    if (j - zeros >= 0 && k - ones >= 0) {
                        dp[i][j][k] = Math.max(dp[i][j][k],dp[i + 1][j - zeros][k - ones] + 1);
                    }
                }
            }
        }
        return dp[0][m][n];
    }

    
    private void zerosAndOnes(String str) {
        ones = 0;
        zeros = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == '1') {
                ones++;
            }
        }
        zeros = str.length() - ones;
    }
}
空间压缩

在这里插入图片描述

  1. 因为每一层是相互依赖于每一层的, 所以我们可以使用一个二维表格来进行填写表格, 先把前面的 i 那一层全部删除掉, 我们考虑下一层的依赖关系即可
  2. 根据图片, 我们知道如果在同一层进行填写的话, 依赖的是左上位置的格子, 所以要晚一点覆盖掉这里的格子, 所以我们要从大到小开始填写, 之前的 if 条件来判断防止越界可以直接加到 for 循环中, 防止不必要的遍历, 至于 i 的遍历顺序 从前到后还是从后到前都可以
class Solution {
    public int zeros;
    public int ones;
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        for (int i = strs.length - 1; i >= 0; i--) {
            zerosAndOnes(strs[i]);
            for (int j = m; j >= zeros; j--) {
                for (int k = n; k >= ones; k--) {
                    dp[j][k] = Math.max(dp[j][k],dp[j - zeros][k - ones] + 1);
                }
            }
        }
        return dp[m][n];
    }

    
    private void zerosAndOnes(String str) {
        ones = 0;
        zeros = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == '1') {
                ones++;
            }
        }
        zeros = str.length() - ones;
    }
}

盈利计划

题目解析

也是先从递归入手, 我们写出决策树, 每一个 i 都判断选和不选, 直接先通过暴力递归来找到答案

递归
class Solution {
    public static int mod = 1000000007;

    public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
        return dfs1(n,minProfit,group,profit,0);
    }

    private int dfs1(int n, int minProfit, int[] group, int[] profit, int pos) {
        // 没人了
        if (n <= 0) {
            return minProfit <= 0 ? 1 : 0;
        }
        // 没有工作了
        if (pos == group.length) {
            return minProfit <= 0 ? 1 : 0;
        }

        // 不干这个工作
        int ans = 0;
        ans = dfs1(n,minProfit,group,profit,pos + 1) % mod;

        // 干这个工作
        if (n >= group[pos]) {
            ans = (ans +  dfs1(n - group[pos], minProfit - profit[pos], group,profit, pos + 1) % mod) % mod;
        }
        return ans;
    }
}

在这里插入图片描述

不出意料还是超时了, 但是也就差 10 个用例, 说明我们的做法是正确的

记忆化搜索

我们挂上缓存, 需要注意一个细节, 利润会被我们减到负数有可能, 所以我们需要一个比较, 如果变成负数说明利润已经达到了, 我们把它变成 0 , 我们来修改一下代码, 和上一题的整体步骤是差不多的

class Solution {
    public static int mod = 1000000007;

    public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
        int[][][] dp = new int[group.length + 1][n + 1][minProfit + 1];
        for (int i = 0; i <= group.length; i++) {
            for (int j = 0; j <= n; j++) {
                for (int k = 0; k <= minProfit; k++) {
                    dp[i][j][k] = -1;
                }
            }
        }
        return dfs1(n,minProfit,group,profit,0,dp);
    }

    private int dfs1(int n, int minProfit, int[] group, int[] profit, int pos,int[][][] dp ) {
        // 没人了
        if (n <= 0) {
            return minProfit <= 0 ? 1 : 0;
        }
        // 没有工作了
        if (pos == group.length) {
            return minProfit <= 0 ? 1 : 0;
        }

        if (dp[pos][n][minProfit] != -1) {
            return dp[pos][n][minProfit];
        }
        // 不干这个工作
        int ans = 0;
        ans = dfs1(n,minProfit,group,profit,pos + 1,dp) % mod;

        // 干这个工作
        if (n >= group[pos]) {
            ans = (ans +  dfs1(n - group[pos],Math.max(minProfit - profit[pos],0),group,profit,pos + 1,dp) % mod) % mod;
        }
        dp[pos][n][minProfit] = ans;
        return ans;
    }
}
动态规划
初始化

最上面一层都是 0, 无需进行初始化
根据图片, 并且每一层都依赖上一层, 所以我们只需要初始化最上面一层即可, 这个和上一题是一样的, 当利润为 0 的时候发现是填入 1, 我们根据记忆化搜索的返回条件可以知道, 所以我们就知道, 最高层的利润为 0 的点填入 1
填表顺序

填表顺序

根据图上画的, 依赖上一层, 从左向右, 从右向左填 j, k都行

返回值

自底向上填写, 最下层满足 dp[0][n][minProfit] 这个可以根据记忆化搜索的返回值得知

class Solution {
    public static int mod = 1000000007;

     public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
        int[][][] dp = new int[group.length + 1][n + 1][minProfit + 1];
        for (int j = 0; j <= n; j++) {
            dp[group.length][j][0] = 1;
        }

        for (int i = group.length - 1; i >= 0; i--) {
            for (int j = n; j >= 0; j--) {
                for (int k = minProfit; k >= 0; k--) {
                    dp[i][j][k] = dp[i + 1][j][k] % mod;
                    if (j >= group[i]) {
                        dp[i][j][k] = (dp[i][j][k] + dp[i + 1][j - group[i]][Math.max(k - profit[i],0)] % mod) % mod;
                    }
                }
            }
        }
        return dp[0][n][minProfit];
    }
}
空间压缩

在这里插入图片描述

根据上面那张图, 我们用一个二维表来填写, 由于依赖左上方的位置, 所以我们从右下角从右向左, 从上到下进行填写

  1. 先把所有的前缀 i 都删掉
  2. 改一下遍历顺序
  3. 把 if 里的提到 for 循环中, 不提也可以
class Solution {
    public static int mod = 1000000007;

      public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
        int[][] dp = new int[n + 1][minProfit + 1];
        for (int j = 0; j <= n; j++) {
                dp[j][0] = 1;
        }
        for (int i = group.length - 1; i >= 0; i--) {
            for (int j = n; j >= group[i]; j--) {
                for (int k = minProfit; k >= 0; k--) {
                    dp[j][k] = (dp[j][k] + dp[j - group[i]][Math.max(k - profit[i],0)] % mod) % mod;
                }
            }
        }
        return dp[n][minProfit];
    }
}

此处取模可以去掉一个, 两层是怕溢出, 此题不会溢出, 去掉会快 8 秒左右

相关文章:

  • RabbitMQ 2025/3/5
  • 海南自贸港的数字先锋:树莓集团的战略布局解析
  • 开发环境搭建-02.后端环境搭建-熟悉项目结构
  • Rust语言入门与应用:未来发展趋势解析
  • Pytorch系列教程:模型训练的基本要点
  • DeepSeek、Grok 和 ChatGPT 对比分析:从技术与应用场景的角度深入探讨
  • 【ROS2机器人入门到实战】
  • Linux环境变量
  • 四、Redis 事务与 Lua 脚本:深入解析与实战
  • 计算机网络基础:服务器远程连接管理(Telnet命令)
  • 【大模型(LLMs)微调面经 】
  • 计算机毕业设计SpringBoot+Vue.js球队训练信息管理系统(源码+文档+PPT+讲解)
  • Linux中VirtualBox的虚拟机开机自启
  • 打印三角形及Debug
  • Pipeline模式详解:提升程序处理效率的设计模式
  • AI编程工具-(五)
  • vue+neo4j 四大名著知识图谱问答系统
  • AI浏览器BrowserUse:安装与配置(四)
  • 容器 /dev/shm 泄漏学习
  • 第五章 STM32 环形缓冲区
  • 微软将裁员3%,减少管理层
  • 微软宣布将裁员3%
  • 俄副外长:俄美两国将举行双边谈判
  • 国际博物馆日中国主会场确定,北京将展“看·见殷商”等展览
  • 浙江首个核酸药谷落子杭州,欢迎订阅《浪尖周报》第23期
  • ​中国超大规模市场是信心所在——海南自贸港建设一线观察