【算法优选】 动态规划之子数组、子串系列——贰

简介: 【算法优选】 动态规划之子数组、子串系列——贰

🎋前言

动态规划相关题目都可以参考以下五个步骤进行解答:

  1. 状态表示
  2. 状态转移⽅程
  3. 初始化
  4. 填表顺序
  5. 返回值

后面题的解答思路也将按照这五个步骤进行讲解。

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

🚩题目描述

给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。

一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。

请你返回乘积为正数的最长子数组长度。

  • 示例 1:
    输入:nums = [1,-2,-3,4]
    输出:4
    解释:数组本身乘积就是正数,值为 24 。
  • 示例 2:
    输入:nums = [0,1,-2,-3,-4]
    输出:3
    解释:最长乘积为正数的子数组为 [1,-2,-3] ,乘积为 6 。
    注意,我们不能把 0 也包括到子数组中,因为这样乘积为 0 ,不是正数。
  • 示例 3:
    输入:nums = [-1,-2,-3,0,1]
    输出:2
    解释:乘积为正数的最长子数组是 [-1,-2] 或者 [-2,-3] 。
class Solution {
    public int getMaxLen(int[] nums) {
    }
}

🚩算法思路

继续效仿「最⼤⼦数组和」中的状态表⽰,尝试解决这个问题。

状态表⽰: dp[i] 表⽰「所有以 i 结尾的⼦数组,乘积为正数的最⻓⼦数组的⻓度」。

思考状态转移:对于 i 位置上的 nums[i] ,我们可以分三种情况讨论:

  1. 如果 nums[i] = 0 ,那么所有以 i 为结尾的⼦数组的乘积都不可能是正数,此时dp[i] = 0 ;
  2. 如果 nums[i] > 0 ,那么直接找到 dp[i - 1] 的值(这⾥请再读⼀遍 dp[i -1] 代表的意义,并且考虑如果 dp[i - 1] 的结值是 0 的话,影不影响结果),然后加⼀即可,此时 dp[i] = dp[i - 1] + 1 ;
  3. 如果 nums[i] < 0 ,这时候你该蛋疼了,因为在现有的条件下,你根本没办法得到此时的最⻓⻓度。因为乘法是存在「负负得正」的,单单靠⼀个 dp[i - 1] ,我们⽆法推导出 dp[i] 的值。但是,如果我们知道「以 i - 1 为结尾的所有⼦数组,乘积为负数的最长⼦数组的⻓度」 neg[i - 1] ,那么此时的 dp[i] 是不是就等于 neg[i - 1] + 1呢?

通过上⾯的芬析,我们可以得出,需要两个 dp 表,才能推导出最终的结果。不仅需要⼀个「乘积为正数的最长子数组」,还需要⼀个「乘积为负数的最长子数组」。

  1. 状态表⽰:

f[i] 表⽰:以 i 结尾的所有⼦数组中,乘积为「正数」的最⻓⼦数组的⻓度;

g[i] 表⽰:以 i 结尾的所有⼦数组中,乘积为「负数」的最⻓⼦数组的⻓度。

  1. 状态转移⽅程:

遍历每⼀个位置的时候,我们要同步更新两个 dp 数组的值。

对于 f[i] ,也就是以 i 为结尾的乘积为「正数」的最⻓⼦数组,根据nums[i] 的值,可以分为三种情况:

  • nums[i] = 0 时,所有以 i 为结尾的⼦数组的乘积都不可能是正数,此时 f[i] =0 ;
  • nums[i] > 0 时,那么直接找到 f[i - 1] 的值(这⾥请再读⼀遍 f[i - 1] 代表
    的意义,并且考虑如果 f[i - 1] 的结值是 0 的话,影不影响结果),然后加⼀即可,此时 f[i] = f[i - 1] + 1 ;
  • nums[i] < 0 时,此时我们要看 g[i - 1] 的值(这⾥请再读⼀遍 g[i - 1] 代表的意义。因为负负得正,如果我们知道以 i - 1 为结尾的乘积为负数的最⻓⼦数组的⻓度,加上 1 即可),根据 g[i - 1] 的值,⼜要分两种情况:
  • g[i - 1] = 0 ,说明以 i - 1 为结尾的乘积为负数的最⻓⼦数组是不存在的,⼜因为 nums[i] < 0 ,所以以 i 结尾的乘积为正数的最⻓⼦数组也是不存在的,此时 f[i] = 0 ;
  • g[i - 1] != 0 ,说明以 i - 1 为结尾的乘积为负数的最⻓⼦数组是存在的,⼜因为 nums[i] < 0 ,所以以 i 结尾的乘积为正数的最⻓⼦数组就等于 g[i -1] + 1 ;

综上所述, nums[i] < 0 时, f[i] = g[i - 1] == 0 ? 0 : g[i - 1] +1;

对于 g[i] ,也就是以 i 为结尾的乘积为「负数」的最⻓⼦数组,根据nums[i] 的值,可以分为三种情况:

  • nums[i] = 0 时,所有以 i 为结尾的⼦数组的乘积都不可能是负数,此时g[i] =0 ;
  • nums[i] < 0 时,那么直接找到 f[i - 1] 的值(这⾥请再读⼀遍 f[i - 1] 代表
    的意义,并且考虑如果 f[i - 1] 的结值是 0 的话,影不影响结果),然后加⼀即可(因为正数 * 负数 = 负数),此时 g[i] = f[i - 1] + 1 ;
  • nums[i] > 0 时,此时我们要看 g[i - 1] 的值(这⾥请再读⼀遍 g[i - 1] 代表的意义。因为正数 * 负数 = 负数),根据 g[i - 1] 的值,⼜要分两种情况:
  • g[i - 1] = 0 ,说明以 i - 1 为结尾的乘积为负数的最⻓⼦数组是不存在的,⼜因为 nums[i] > 0 ,所以以 i 结尾的乘积为负数的最⻓⼦数组也是不存在的,此时 f[i] = 0 ;
  • g[i - 1] != 0 ,说明以 i - 1 为结尾的乘积为负数的最⻓⼦数组是存在的,⼜因为 nums[i] > 0 ,所以以 i 结尾的乘积为正数的最⻓⼦数组就等于 g[i -1] + 1 ;

综上所述, nums[i] > 0 时, g[i] = g[i - 1] == 0 ? 0 : g[i - 1] +1 ;

这⾥的推导⽐较绕,因为不断的出现「正数和负数」的分情况讨论,我们只需根据下⾯的规则,严格找到此状态下需要的 dp 数组即可:

  • 正数 * 正数 = 正数
  • 负数 * 负数 = 正数
  • 负数 * 正数 = 正数 * 负数 = 负数
  1. 初始化:

可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使⽤这种技巧要注意两个点:

  • 辅助结点⾥⾯的值要「保证后续填表是正确的」;
  • 「下标的映射关系」。

在本题中,最前⾯加上⼀个格⼦,并且让 f[0] = g[0] = 0 即可。

  1. 填表顺序:

根据「状态转移⽅程」易得,填表顺序为「从左往右,两个表⼀起填」。

  1. 返回值:

根据「状态表⽰」,我们要返回 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(ret, f[i]);
        }
        return ret;
    }
}

🌳等差数列划分

🚩题目描述

如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。

例如,[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。

给你一个整数数组 nums ,返回数组 nums 中所有为等差数组的 子数组 个数。

子数组 是数组中的一个连续序列。

  • 示例 1:
    输入:nums = [1,2,3,4]
    输出:3
    解释:nums 中有三个子等差数组:[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] 自身。
  • 示例 2:
    输入:nums = [1]
    输出:0
class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
    }
}

🚩算法思路:

  1. 状态表⽰:

由于我们的研究对象是「⼀段连续的区间」,如果我们状态表⽰定义成 [0, i] 区间内⼀共有多少等差数列,那么我们在分析 dp[i] 的状态转移时,会⽆从下⼿,因为我们不清楚前⾯那么多的「等差数列都在什么位置」。

所以说,我们定义的状态表⽰必须让等差数列「有迹可循」,让状态转移的时候能找到「⼤部队」。因此,我们可以「固定死等差数列的结尾」,定义下⾯的状态表⽰:

dp[i] 表⽰必须「以 i 位置的元素为结尾」的等差数列有多少种。

  1. 状态转移⽅程:

我们需要了解⼀下等差数列的性质:如果 a b c 三个数成等差数列,这时候来了⼀个 d ,其中 b c d 也能构成⼀个等差数列,那么 a b c d 四个数能够成等差序列吗?答案是:显然的。

因为他们之间相邻两个元素之间的差值都是⼀样的。有了这个理解,我们就可以转⽽分析我们的状态转移⽅程了。

对于 dp[i] 位置的元素 nums[i] ,会与前⾯的两个元素有下⾯两种情况:

  • nums[i - 2], nums[i - 1], nums[i] 三个元素不能构成等差数列:那么以nums[i] 为结尾的等差数列就不存在,此时 dp[i] = 0 ;
  • nums[i - 2], nums[i - 1], nums[i] 三个元素可以构成等差数列:那么以nums[i - 1] 为结尾的所有等差数列后⾯填上⼀个 nums[i] 也是⼀个等差数列,此时dp[i] = dp[i - 1] 。

但是,因为 nums[i - 2], nums[i - 1], nums[i] 三者⼜能构成⼀个新的等差数列,因此要在之前的基础上再添上⼀个等差数列,于是 dp[i] = dp[i - 1] + 1 。

综上所述:状态转移⽅程为:

当: nums[i - 2] + nums[i] != 2 * nums[i - 1] 时, dp[i] = 0

当: nums[i - 2] + nums[i] == 2 * nums[i - 1] 时, dp[i] = 1 + dp[i -1]

  1. 初始化:

由于需要⽤到前两个位置的元素,但是前两个位置的元素⼜⽆法构成等差数列,因此 dp[0] =

dp[1] = 0

  1. 填表顺序:

毫⽆疑问是「从左往右」。

  1. 返回值:

因为我们要的是所有的等差数列的个数,因此需要返回整个 dp 表⾥⾯的元素之和

🚩代码实现

class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        int n = nums.length;
        int result = 0;
        int[] dp = new int[n ];
        for (int i = 2; i < n; i++) {
            if((nums[i] - nums[i - 1]) == (nums[i - 1] - nums[i - 2])) {
                dp[i] = dp[i - 1] + 1;
            } else {
                dp[i] = 0;
            }
            result += dp[i];
        }
        return result;
    }
}

🍀最长湍流子数组

🚩题目描述

给定一个整数数组 arr ,返回 arr 的 最大湍流子数组的长度 。

如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是 湍流子数组 。

更正式地来说,当 arr 的子数组 A[i], A[i+1], …, A[j] 满足仅满足下列条件时,我们称其为湍流子数组:

若 i <= k < j :

当 k 为奇数时, A[k] > A[k+1],且当 k 为偶数时,A[k] < A[k+1];

或 若 i <= k < j :

当 k 为偶数时,A[k] > A[k+1] ,且当 k 为奇数时, A[k] < A[k+1]。

  • 示例 1:
    输入:arr = [9,4,2,10,7,8,8,1,9]
    输出:5
    解释:arr[1] > arr[2] < arr[3] > arr[4] < arr[5]
  • 示例 2:
    输入:arr = [4,8,12,16]
    输出:2
  • 示例 3:
    输入:arr = [100]
    输出:1
class Solution {
    public int maxTurbulenceSize(int[] arr) {
    }
}

🚩思路解析

  1. 状态表⽰:

我们先尝试定义状态表⽰为:

dp[i] 表⽰「以 i 位置为结尾的最⻓湍流数组的⻓度」。

但是,问题来了,如果状态表⽰这样定义的话,以 i 位置为结尾的最⻓湍流数组的⻓度我们没法从之前的状态推导出来。因为我们不知道前⼀个最⻓湍流数组的结尾处是递增的,还是递减的。因此,我们需要状态表⽰能表⽰多⼀点的信息:要能让我们知道这⼀个最⻓湍流数组的结尾是「递增」的还是「递减」的。因此需要两个 dp 表:

f[i] 表⽰:以 i 位置元素为结尾的所有⼦数组中,最后呈现「上升状态」下的最⻓湍流数组的⻓度;

g[i] 表⽰:以 i 位置元素为结尾的所有⼦数组中,最后呈现「下降状态」下的最⻓湍流数组的⻓度。

  1. 状态转移⽅程:

对于 i 位置的元素 arr[i] ,有下⾯三种情况:

  • arr[i] > arr[i - 1] :如果 i 位置的元素⽐ i - 1 位置的元素⼤,说明接下来应该去找 i -1 位置结尾,并且 i - 1 位置元素⽐前⼀个元素⼩的序列,那就是 g[i- 1] 。更新 f[i] 位置的值: f[i] = g[i - 1] + 1 ;
  • arr[i] < arr[i - 1] :如果 i 位置的元素⽐ i - 1 位置的元素⼩,说明接下来应该去找 i - 1 位置结尾,并且 i - 1 位置元素⽐前⼀个元素⼤的序列,那就是 f[i - 1] 。更新 g[i] 位置的值: g[i] = f[i - 1] + 1 ;
  • arr[i] == arr[i - 1] :不构成湍流数组。
  1. 初始化:

所有的元素「单独」都能构成⼀个湍流数组,因此可以将 dp 表内所有元素初始化为1 。由于⽤到前⾯的状态,因此我们循环的时候从第⼆个位置开始即可。

  1. 填表顺序:

毫⽆疑问是「从左往右,两个表⼀起填」。

  1. 返回值:

应该返回「两个 dp 表⾥⾯的最⼤值」,我们可以在填表的时候,顺便更新⼀个最⼤值

🚩代码实现

class Solution {
    public int maxTurbulenceSize(int[] arr) {
        // 1. 创建 dp 表
        // 2. 初始化
        // 3. 填表
        // 4. 返回值
        int n = arr.length;
        int[] f = new int[n];
        int[] g = new int[n];
        for(int i = 0; i < n; i++) {
            f[i] = g[i] = 1;
        }
        int ret = 1;
        for(int i = 1; i < n; i++) {
            if(arr[i - 1] < arr[i]) {
                f[i] = g[i - 1] + 1;
            } else if(arr[i - 1] > arr[i]) {
                g[i] = f[i - 1] + 1;
            }
            ret = Math.max(ret, Math.max(f[i], g[i]));
        }
        return ret;
    }
}

🌲单词拆分

🚩题目描述

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

  • 示例 1:
    输入: s = “leetcode”, wordDict = [“leet”, “code”]
    输出: true
    解释: 返回 true 因为 “leetcode” 可以由 “leet” 和 “code” 拼接成。
  • 示例 2:
    输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
    输出: true
    解释: 返回 true 因为 “applepenapple” 可以由 “apple” “pen” “apple” 拼接成。注意,你可以重复使用字典中的单词。
  • 示例 3:
    输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
    输出: false
class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
    }
}

🚩算法思路:

  1. 状态表⽰:

对于线性 dp ,我们可以⽤「经验+题⽬要求」来定义状态表⽰:

  • 以某个位置为结尾,进行一系列操作;
  • 以某个位置为起点,进行一系列操作。

这⾥我们选择⽐较常⽤的⽅式,以某个位置为结尾,结合题⽬要求,定义⼀个状态表⽰:

dp[i] 表⽰: [0, i] 区间内的字符串,能否被字典中的单词拼接⽽成。

  1. 状态转移⽅程:

对于 dp[i] ,为了确定当前的字符串能否由字典⾥⾯的单词构成,根据最后⼀个单词的起始位置 j ,我们可以将其分解为前后两部分:

  • 前⾯⼀部分 [0, j - 1] 区间的字符串;
  • 后⾯⼀部分 [j, i] 区间的字符串。

其中前⾯部分我们可以在 dp[j - 1] 中找到答案,后⾯部分的⼦串可以在字典⾥⾯找到。

因此,我们得出⼀个结论:当我们在从 0 ~ i 枚举 j 的时候,只要 dp[j - 1] = true 并且后⾯部分的⼦串 s.substr(j, i - j + 1) 能够在字典中找到,那么dp[i] =true 。

  1. 初始化:

可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使⽤这种技巧要注意两个点:

  • 辅助结点⾥⾯的值要「保证后续填表是正确的」;
  • 「下标的映射关系」。

在本题中,最前⾯加上⼀个格⼦,并且让 dp[0] = true ,可以理解为空串能够拼接⽽成。

其中为了⽅便处理下标的映射关系,我们可以将字符串前⾯加上⼀个占位符 s = ’ ’ + s ,这样就没有下标的映射关系的问题了,同时还能处理「空串」的情况。

  1. 填表顺序:

显⽽易⻅,填表顺序「从左往右」。

  1. 返回值:

由「状态表⽰」可得:返回 dp[n] 位置的布尔值。

这里我们再引入哈希表优化的⼩细节:

在状态转移中,我们需要判断后⾯部分的⼦串「是否在字典」之中,因此会「频繁的⽤到查询操作」。为了节省效率,我们可以提前把「字典中的单词」存⼊到「哈希表」中。

🚩代码实现:

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        // 1. 创建 dp 表
        // 2. 初始化
        // 3. 填表
        // 4. 返回值
        // 优化⼀:将字典⾥⾯的单词存在哈希表⾥⾯
        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++) {
            // 填 dp[i]
            for(int j = i; j >= 1; j--) {
                //最后⼀个单词的起始位置
                if(dp[j - 1] && hash.contains(s.substring(j, i + 1))) {
                    dp[i] = true;
                    break; // 优化⼆
                }
            }
        }
        return dp[n];
    }
}

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

🚩题目描述

定义字符串 base 为一个 “abcdefghijklmnopqrstuvwxyz” 无限环绕的字符串,所以 base 看起来是这样的:

  • “…zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd…”.

给你一个字符串 s ,请你统计并返回 s 中有多少 不同非空子串 也在 base 中出现。

  • 示例 1:
    输入:s = “a”
    输出:1
    解释:字符串 s 的子字符串 “a” 在 base 中出现。
  • 示例 2:
    输入:s = “cac”
    输出:2
    解释:字符串 s 有两个子字符串 (“a”, “c”) 在 base 中出现。
  • 示例 3:
    输入:s = “zab”
    输出:6
    解释:字符串 s 有六个子字符串 (“z”, “a”, “b”, “za”, “ab”, and “zab”) 在 base 中出现。
class Solution {
    public int findSubstringInWraproundString(String s) {
    }
}

🚩算法思路:

  1. 状态表⽰:

对于线性 dp ,我们可以⽤「经验+题⽬要求」来定义状态表⽰:

  • 以某个位置为结尾,进行一系列操作;
  • 以某个位置为起点,进行一系列操作。

这⾥我们选择⽐较常⽤的⽅式,以某个位置为结尾,结合题⽬要求,定义⼀个状态表⽰:

dp[i] 表⽰:以 i 位置的元素为结尾的所有⼦串⾥⾯,有多少个在 base 中出现过。

  1. 状态转移⽅程:

对于 dp[i] ,我们可以根据⼦串的「⻓度」划分为两类:

  • ⼦串的⻓度等于 1 :此时这⼀个字符会出现在 base 中;
  • ⼦串的⻓度⼤于 1 :如果 i 位置的字符和 i - 1 位置上的字符组合后,出现在 base中的话,那么 dp[i - 1] ⾥⾯的所有⼦串后⾯填上⼀个 s[i] 依旧在 base 中出现。因此 dp[i] = dp[i - 1] 。

综上, dp[i] = 1 + dp[i - 1] ,其中 dp[i - 1] 是否加上需要先做⼀下判断。

  1. 初始化:

可以根据「实际情况」,将表⾥⾯的值都初始化为 1 。

  1. 填表顺序:

显⽽易⻅,填表顺序「从左往右」。

  1. 返回值:

这⾥不能直接返回 dp 表⾥⾯的和,因为会有重复的结果。在返回之前,我们需要先「去重」:

  • 相同字符结尾的 dp 值,我们仅需保留「最⼤」的即可,其余 dp 值对应的⼦串都可以在最⼤的⾥⾯找到;
  • 可以创建⼀个⼤⼩为 26 的数组,统计所有字符结尾的最⼤ dp 值。最后返回「数组中所有元素的和」即可。

🚩代码实现

class Solution {
    public int findSubstringInWraproundString(String ss) {
        int n = ss.length();
        char[] s = ss.toCharArray();
        // 1. 利⽤ dp 得到每⼀个位置为结尾的最⻓连续数组的⻓度
        int[] dp = new int[n];
        for(int i = 0; i < n; i++) {
            dp[i] = 1;
        }
        for(int i = 1; i < n; i++) {
            if (s[i - 1] + 1 == s[i] || (s[i - 1] == 'z' && s[i] == 'a')) {
                dp[i] += dp[i - 1];
            }
        }
        // 2. 去重
        int[] hash = new int[26];
        for (int i = 0; i < n; i++) {
            hash[s[i] - 'a'] = Math.max(hash[s[i] - 'a'], dp[i]);
        }
        // 3. 返回结果
        int sum = 0;
        for(int x : hash) {
            sum += x;
        }
        return sum;
    }
}

⭕总结

关于《【算法优选】 动态规划之子数组、子串系列——贰》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

相关文章
|
1月前
|
算法 测试技术 C++
【动态规划算法】蓝桥杯填充问题(C/C++)
【动态规划算法】蓝桥杯填充问题(C/C++)
|
8天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
25 2
|
1月前
|
算法
动态规划算法学习三:0-1背包问题
这篇文章是关于0-1背包问题的动态规划算法详解,包括问题描述、解决步骤、最优子结构性质、状态表示和递推方程、算法设计与分析、计算最优值、算法实现以及对算法缺点的思考。
62 2
动态规划算法学习三:0-1背包问题
|
1月前
|
算法
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
65 0
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
|
1月前
|
算法
动态规划算法学习二:最长公共子序列
这篇文章介绍了如何使用动态规划算法解决最长公共子序列(LCS)问题,包括问题描述、最优子结构性质、状态表示、状态递归方程、计算最优值的方法,以及具体的代码实现。
118 0
动态规划算法学习二:最长公共子序列
|
1月前
|
存储 算法
动态规划算法学习一:DP的重要知识点、矩阵连乘算法
这篇文章是关于动态规划算法中矩阵连乘问题的详解,包括问题描述、最优子结构、重叠子问题、递归方法、备忘录方法和动态规划算法设计的步骤。
97 0
|
1月前
|
算法 C++
【算法解题思想】动态规划+深度优先搜索(C/C++)
【算法解题思想】动态规划+深度优先搜索(C/C++)
|
24天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
8天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。
|
10天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。