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

动态规划 之 子数组 算法专题

一, 最大子数组和

  1. 状态表示
    dp[i] 表示以i位置结尾的最大的子数组和
  2. 状态转移方程
    分成两种情况:
    1.i位置本身就是最大的数组
    dp[i] = nums[i]
    2.以i位置结尾的最大的子数组和 为 i - 1位置结尾的最大的子数组和 + nnums[i]
    dp[i] = dp[i - 1] + nums[i]
  • dp[i] = max(dp[i - 1] + nums[i], nums[i])
  1. 初始化
    dp[0] = nums[0]
  2. 填表顺序
    从左往右
  3. 返回值
    返回所有数的最大值
class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        dp[0] = nums[0];
        int ret = dp[0];
        for(int i = 1; i < n; i++){
            dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
            ret = Math.max(ret, dp[i]);
        }
        return ret;
    }
}

二. 环形子数组的最大和

先将环形的问题转换成简单的问题:
如果不存在环形, 和上一题是类似的
存在环形, 我们要找收尾相接的最大值, 相当于是找中间的最小值, 最后再用sum - 最小值就是最大值

  1. 状态表示
    f[i] 表示以i位置结尾的最大的子数组和
    g[i] 表示以i位置结尾的最小的子数组和
  2. 状态转移方程
    分成两种情况:
    1.i位置本身就是最大/小的数组
    f[i] = nums[i] g[i] = nums[i]
    2.以i位置结尾的最大的子数组和 为 i - 1位置结尾的最大/小的子数组和 + nnums[i]
    f[i] = f[i - 1] + nums[i] g[i] = g[i - 1] + nums[i]
  • f[i] = max(f[i - 1] + nums[i], nums[i])
  • g[i] = min(g[i - 1] + nums[i], nums[i])
  1. 初始化
    f[0] = nums[0] g[0] = nums[0]
  2. 填表顺序
    从左往右
  3. 返回值
    返回f表最大值和sum - g表最小值 的最大值
    细节: 如果sum 和g表最小值 相等时, 结果是不正确的, 返回f表最大值即可
    环形子数组的最大和
class Solution {
    public int maxSubarraySumCircular(int[] nums) {
        int n = nums.length;
        int[] f = new int[n];
        int[] g = new int[n];
        f[0] = nums[0];
        g[0] = nums[0];
        int max = f[0];
        int min = g[0];
        int sum = nums[0];
        for(int i = 1; i < n;i++){
            f[i] = Math.max(f[i - 1] + nums[i], nums[i]);
            max = Math.max(max, f[i]);
            g[i] = Math.min(g[i - 1] + nums[i], nums[i]);
            min = Math.min(min, g[i]);
            sum += nums[i];
        }
        System.out.println(max);
        System.out.println(min);
        System.out.println(sum);

        return sum == min ? max : Math.max(max, sum - min);

    }
}

三. 乘积最大子数组

乘积最大子数组

  1. 状态表示
    dp[i] 表示以i位置结尾的最大的子数组的成绩
  2. 状态转移方程
    分成两种情况:
    1.i位置本身就是最大的数组
    dp[i] = nums[i]
    2.以i位置结尾的最大的子数组和 为 i - 1位置结尾的最大的子数组的乘积 * nums[i]
    但是此时就会出现一个问题, 如果nums[i]为负数, 那么乘上前面的最大值, 会成为一个更小的负数, 并不是我们想要的, 所以当nums[i]为负数时, 我们要乘的是前面的最小值
    最大值 f[i] = f[i - 1] * nums[i] 或者 f[i] = g[i - 1] * nums[i]
    最小值 g[i] = g[i - 1] * nums[i] 或者 g[i] = f[i - 1] * nums[i]
  • f[i] = max(f[i - 1] * nums[i], nums[i], g[i - 1] * nums[i])
  • g[i] = min(g[i - 1] * nums[i], nums[i], f[i - 1] * nums[i])
  1. 初始化
    可以使用增加虚拟节点的方式, f[0] = 1 g[0] = 1即可, 不会影响结果
  2. 填表顺序
    从左往右
  3. 返回值
    返回f表的最大值
class Solution {
    public int maxProduct(int[] nums) {
        int n = nums.length;
        int[] f = new int[n + 1];
        int[] g = new int[n + 1];
        f[0] = 1;
        g[0] = 1;
        int ret = Integer.MIN_VALUE;
        for(int i = 1; i <= n; i++){
            f[i] = Math.max(Math.max(f[i - 1] * nums[i - 1], nums[i - 1]), g[i - 1] * nums[i - 1]);//注意下标映射
            g[i] = Math.min(Math.min(g[i - 1] * nums[i - 1], nums[i - 1]), f[i - 1] * nums[i - 1]);
            ret = Math.max(ret, f[i]);
        }

        return ret;
    }
}

四. 乘积为正数的最长子数组长度

乘积为正数的最长子数组长度

  1. 状态表示
    f[i] 表示以i位置结尾的乘积为正数的子数组的最长长度
    g[i] 表示以i位置结尾的乘积为负数的子数组的最长长度
  2. 状态转移方程
    分成两种情况:
    1.nums[i]为正数, 那么就需要找前面为正数的最长数组长度f[i - 1]
    f[i] = f[i - 1] + 1
    2.nums[i]为负数, 那么就需要找前面为负数的最长数组长度g[i - 1]
    但是如果g[i - 1] == 0, 那么说明前面的数乘积不是负数, 此时长度应该为0
    f[i] = g[i - 1] == 0?0 : g[i - 1] + 1

g[i]与f[i]类似:
1.nums[i]为负数, 那么就需要找前面为正数的最长数组长度f[i - 1]
g[i] = f[i - 1] + 1
2.nums[i]为正数, 那么就需要找前面为负数的最长数组长度g[i - 1]
但是如果g[i - 1] == 0, 那么说明前面的数乘积不是负数, 此时长度应该为0
g[i] = g[i - 1] == 0?0 : g[i - 1] + 1

  • nums[i] > 0, f[i] = f[i - 1] + 1 g[i] = g[i - 1] == 0?0 : g[i - 1] + 1
  • nums[i] < 0, f[i] = g[i - 1] == 0?0 : g[i - 1] + 1 g[i] = f[i - 1] + 1
  1. 初始化
    可以使用增加虚拟节点的方式, f[0] =0 g[0] = 0即可, 不会影响结果
  2. 填表顺序
    从左往右
  3. 返回值
    返回f表的最大值
class Solution {
    public int getMaxLen(int[] nums) {
        int n = nums.length;
        int[] f = new int[n + 1];
        int[] g = new int[n + 1];

        int ret = Integer.MIN_VALUE;
        for(int i = 1; i <= n; i++){
            if(nums[i - 1] > 0){//注意映射关系
                f[i] = f[i - 1] + 1;
                g[i] = g[i - 1] == 0 ? 0 :  g[i - 1] + 1;
            }else if(nums[i - 1] < 0){
                f[i] = g[i - 1] == 0 ? 0 :  g[i - 1] + 1;
                g[i] = f[i - 1] + 1;
            }

            ret = Math.max(f[i], ret);
        }

        return ret;
    }
}

五. 等差数列划分

等差数列划分

  1. 状态表示
    dp[i] 表示以i位置结尾的所有的子数组中等差数列的个数
  2. 状态转移方程
    等差数列最少需要三个数构成
    分成两种情况:
    1.nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2]
    说明此时这个三个数能构成等差数列, 那么前面的所有等差数列加上i位置, 还是等差数列, 所以
    dp[i] = dp[i - 1] + 1
    2.nums[i] - nums[i - 1] != nums[i - 1] - nums[i - 2]
    说明此时这个三个数不能构成等差数列, 那么以i位置结尾的所有的子数组中等差数列的个数为0
    dp[i] = 0
  • dp[i] = nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2] ? dp[i - 1] + 1 : 0;
  1. 初始化
    dp[0] = 0 dp[1] = 0
  2. 填表顺序
    从左往右
  3. 返回值
    返回所有等差数列个数 即sum
class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        int sum = 0;
        for(int i = 2; i < n; i++){
            dp[i] = nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2] ? dp[i - 1] + 1 : 0;
            sum += dp[i];
        }
        return sum;
    }
}

六. 最长湍流子数组

最长湍流子数组

  1. 状态表示
    f[i] 表示以i位置结尾, 最后呈现"上升"状态下的最大湍流子数组的长度
    g[i] 表示以i位置结尾, 最后呈现"下降"状态下的最大湍流子数组的长度

  2. 状态转移方程
    分成两种情况:
    1.呈上升趋势
    f[i] = g[i - 1] + 1
    g[i] = 1(无法呈上升趋势, 所以只能作为开头)
    2.呈下降趋势
    g[i] = f[i - 1] + 1
    f[i] = 1
    3.如果和前一个相等
    g[i] = 1
    f[i] = 1

  3. 初始化
    可以全部初始化为1, 可以少考虑很多情况

  4. 填表顺序
    从左往右

  5. 返回值
    返回f表和g表的最大值

class Solution {
    public int maxTurbulenceSize(int[] nums) {
        int n = nums.length;
        
        int[] f = new int[n];
        int[] g = new int[n];

        Arrays.fill(f, 1);
        Arrays.fill(g, 1);
        int ret = 1;
        for(int i = 1; i < n; i++){
            if(nums[i] < nums[i - 1]){
                g[i] = f[i - 1] + 1;
            }else if(nums[i] > nums[i - 1]){
                f[i] = g[i - 1] + 1;
            }
            ret = Math.max(Math.max(ret, f[i]), g[i]);
        }
        return ret;
    }
}

七. 单词拆分

单词拆分

  1. 状态表示
    dp[i] 表示[0, i]区间内的字符串, 能否被字典中的单词拼接而成, 返回true, false
  2. 状态转移方程
    用j来标记(j, i)区间内的单词, dp[i] 只需判断(j, i)区间内的单词在字段中是否存在, 并且[0, j - 1]区间内的字符串, 能否被字典中的单词拼接而成, 即dp[j - 1]
  • dp[i] 为true: dp[i - 1]为true && (j, i)区间内的单词在字段中存在
  1. 初始化
    添加一个辅助节点, 但是因为操作字符, 我们可以在字符前添加一个元素占位, 这样就不用考虑后续下标映射的问题
  2. 填表顺序
    从左往右
  3. 返回值
    返回最后一个位置dp[n]时候为true
class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        //优化, 将字典里面的单词存到哈希表中
        Set<String> hash = new HashSet(wordDict);

        int n = s.length();
        boolean[] dp = new boolean[n + 1];
        dp[0] = true;
        s = " " + s;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= i; j++){
                if(dp[j - 1] && hash.contains(s.substring(j, i + 1))){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[n];
    }
}

八. 环绕字符串中唯一的子字符串

环绕字符串中唯一的子字符串

  1. 状态表示
    dp[i] 表示以i位置为结尾的所有子串中在base中出现的个数
  2. 状态转移方程
    1.长度为1, 那么字符本身一定在base中
    2.长度 > 1, 如果i位置和i - 1位置拼接后在base中, 即s[i - 1] + 1 = s[i] || s[i - 1] = ‘z’ && s[i] = ‘a’, 那么dp[i] = 以i - 1位置为结尾的所有子串中在base中出现的个数, 即dp[i - 1]
  • dp[i] = dp[i - 1] + 1
  1. 初始化
    可以先全部初始化为1, 就不用考虑长度为1的情况
  2. 填表顺序
    从左往右
  3. 返回值
    因为可能有重复的情况, 所以要去重
    以某一个字符位置为结尾的所有子串中在base中出现的个数, 如果有多个, 那么一定取最大的那个, 因为小的在大的中一定包含了
    所以返回所有字符取完最大后的和
class Solution {
    public int findSubstringInWraproundString(String s) {
        int n = s.length();
        char[] ss = s.toCharArray();
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        for(int i = 1; i < n; i++){
            if(ss[i - 1] + 1 == ss[i] ||( ss[i - 1] == 'z' && ss[i] == 'a')){
                dp[i] += dp[i - 1];
            }
        }
        int[] ret = new int[26];
        for(int i = 0; i < n; i++){
            ret[ss[i] - 'a'] = Math.max(ret[ss[i] - 'a'], dp[i]);
        }
        int sum = 0;
        for(int i = 0; i < 26; i++){
            sum += ret[i];
        }
        return sum;
    }
}

http://www.kler.cn/a/395855.html

相关文章:

  • 基于汇编语言的贪吃蛇程序
  • 【快捷入门笔记】mysql基本操作大全-SQL表
  • 企业如何提高招聘能力?
  • sealos部署K8s,安装docker时master节点突然NotReady
  • 如何在CentOS 7上搭建SMB服务
  • Shell 脚本中的大小写陷阱:为什么 ${PWD} 而不是 ${pwd}?
  • Ceph 中PG与PGP的概述
  • Algen的跨链互操作性:增强区块链连接性
  • CSS Module:告别类名冲突,拥抱模块化样式(5)
  • 如何使用 WebAssembly 扩展后端应用
  • 0 -vscode搭建python环境教程参考(windows)
  • 【论文分享】三维景观格局如何影响城市居民的情绪
  • Vue3 虚拟列表组件库 virtual-list-vue3 的使用
  • JavaScript 单选框设置选中
  • 第5章-总体设计 5.2 需求转化为规格
  • java中设计模式的使用(持续更新中)
  • Mac解压包安装MongoDB8并设置launchd自启动
  • ‘视’不可挡:OAK相机助力无人机智控飞行!
  • pycharm分支提交操作
  • el-tree 父节点隐藏
  • 电子电气架构 --- 车载48V系统
  • JVM的组成、字节码文件的组成
  • java 随机生成验证码
  • 构建客服知识库:企业效率提升的关键步骤
  • k-近邻算法(K-Nearest Neighbors, KNN)详解:机器学习中的经典算法
  • 丹摩征文 | 图像生成,FLUX.1+ComfyUI部署教程