赞
踩
刷动态规划的题,感觉简单、中等的区分度没有这么高,都是能想到状态表示和状态转移方程就做的出来,想不出来就做不出来,所以重点还是在多做多想
跳过了两道别的方法的题
// 上面的,最长公共子串方法(字符串反转后和原字符串的最长公共子串就是最长回文子串 。)是错的
// abdeba
// abedba
// 最长公共子串为 2,但最长回文子串为 1
/* leetcode 5. 最长回文子串 给你一个字符串 s,找到 s 中最长的回文子串。 示例 1: 输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。 示例 2: 输入:s = "cbbd" 输出:"bb" 示例 3: 输入:s = "a" 输出:"a" 示例 4: 输入:s = "ac" 输出:"a" 提示: 1 <= s.length <= 1000 s 仅由数字和英文字母(大写和 / 或小写)组成 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/longest-palindromic-substring 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 */ /** * @param {string} s * @return {string} */ // 动态规划 var longestPalindrome = function(s) { // f[i] - s[0~i] 中,以第 i 个字符为结尾的回文子串的最大长度 // 状态转移 - // f[i - 1] + 2 (s[i] === s[i - f[i-1] - 1]) // f[i - 1] + 1 (f[i - 1] === 1 && s[i] === s[i - 1]]) // ... 无法转移 // f[i][j] - s[i~j] 是不是一个回文串(i<=j) // 状态转移 // f[i][j] = f[i+1][j-1] && s[i] == s[j] // 初始化 // 回文串长 1:f[i][i] = true // 回文串长 2:f[i][j] = (s[i] === s[j]) j=i+1 const n = s.length // 定义 f 并初始化为 false const f = new Array(n + 5).fill(0).map(() => new Array(n + 5).fill(false)) // 在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。 // let res = '' let res = [0, 0, 0] for (let i = 0; i < n; i++) { // i - 字符串长度-1 for (let j = 0; j + i < n; j++) { // j - 字符串的起始位置 const k = i + j // k - 字符串的终止位置 if (i === 0) { // 字符串长 1 f[j][k] = true } else if (i === 1) { // 字符串长 2 f[j][k] = (s[j] === s[k]) } else { // 字符串的长大于 2 f[j][k] = f[j + 1][k - 1] && (s[j] === s[k]) } // 记录最长的回文子串 // if (f[j][k] && i + 1 > res.length) { // res = s.slice(j, k + 1) // slice 不会影响性能,性能反而要好点 // } // 怕 slice 影响了性能,试着只是记录最长回文子串的起止位置和长度 if (f[j][k] && i + 1 > res[2]) { res[0] = j res[1] = k res[2] = k - j + 1 } } } return s.slice(res[0], res[1] + 1) } // 中心扩展法 // 枚举所有的「回文中心」并尝试「扩展」,直到无法扩展为止,此时的回文串长度即为此「回文中心」下的最长回文串长度。 var longestPalindrome1 = function(s) { const n = s.length // 扩展函数 function expandAroundCenter(s, l, r) { while (l >= 0 && r < n && s[l] === s[r]) { l-- r++ } return [l + 1, r - 1] // 返回回文串的左右位置 } let start = end = 0 for (let i = 0; i < n; i++) { // 对长度为 1 的回文串进行扩展 const [l1, r1] = expandAroundCenter(s, i, i) // 对长度为 2 的回文串进行扩展 const [l2, r2] = expandAroundCenter(s, i, i + 1) // 更新最长回文子串 if (r1 - l1 > end - start) { start = l1 end = r1 } if (r2 - l2 > end - start) { start = l2 end = r2 } } // 返回最长回文子串 return s.slice(start, end + 1) } // Manacher 算法 // 使用臂长的概念,优化中心扩展法,跳过部分不需要比较的字符 var s = "babad" const res = longestPalindrome(s) console.log(res)
/* leetcode 62. 不同路径 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 问总共有多少条不同的路径? 示例 1: ![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png) 输入:m = 3, n = 7 输出:28 示例 2: 输入:m = 3, n = 2 输出:3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 1. 向右 -> 向下 -> 向下 2. 向下 -> 向下 -> 向右 3. 向下 -> 向右 -> 向下 示例 3: 输入:m = 7, n = 3 输出:28 示例 4: 输入:m = 3, n = 3 输出:6 提示: 1 <= m, n <= 100 题目数据保证答案小于等于 2 * 109 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/unique-paths 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 */ /** * @param {number} m * @param {number} n * @return {number} */ // 动态规划 var uniquePaths = function(m, n) { // f(i, j) - 走到 (i, j) 共有的路径数 // 状态转移 - 最后一步是向下/右 // f(i, j) = f(i - 1, j) + f(i, j - 1) // 将最上/左方初始化为 1(偷懒全部初始化为 1) const f = new Array(m + 5).fill(0).map(() => new Array(n + 5).fill(1)) // 只计算第 2 行/列之后的 for (let i = 1; i < m; i++) { for (let j = 1; j < n; j++) { f[i][j] = f[i - 1][j] + f[i][j - 1] } } return f[m-1][n-1] }; // 动态规划 - 空间优化 // f(i, j) 仅与第 i 行和第 i-1 行的状态有关,可使用滚动数组思想降低空间复杂度 var uniquePaths1 = function(m, n) { // m n 具有交换性,让 m 一直是较小的那个 [m, n] = [Math.min(m, n), Math.max(m, n)] // console.log(m, n) let pre = new Array(m + 5).fill(1) let cur = new Array(m + 5).fill(1) // 只计算第 2 行/列之后的 for (let i = 1; i < n; i++) { for (let j = 1; j < m; j++) { // j 就是遍历 m 了 cur[j] = pre[j] + cur[j - 1] } pre = cur } return pre[m-1] }; // 动态规划 - 空间优化(进一步优化) var uniquePaths1 = function(m, n) { // m n 具有交换性,让 m 一直是较小的那个 [m, n] = [Math.min(m, n), Math.max(m, n)] let cur = new Array(m + 5).fill(1) // 只计算第 2 行/列之后的 for (let i = 1; i < n; i++) { for (let j = 1; j < m; j++) { // j 就是遍历 m 了 cur[j] += cur[j - 1] } } return cur[m-1] }; var m = 3, n = 7 // var m = 7, n = 3 // var m = 3, n = 2 // var m = 3, n = 3 const res = uniquePaths(m, n) console.log(res)
/* leetcode 63. 不同路径 II 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? 网格中的障碍物和空位置分别用 1 和 0 来表示。 示例 1: ![](https://assets.leetcode.com/uploads/2020/11/04/robot1.jpg) 输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]] 输出:2 解释: 3x3 网格的正中间有一个障碍物。 从左上角到右下角一共有 2 条不同的路径: 1. 向右 -> 向右 -> 向下 -> 向下 2. 向下 -> 向下 -> 向右 -> 向右 示例 2: ![](https://assets.leetcode.com/uploads/2020/11/04/robot2.jpg) 输入:obstacleGrid = [[0,1],[0,0]] 输出:1 提示: m == obstacleGrid.length n == obstacleGrid[i].length 1 <= m, n <= 100 obstacleGrid[i][j] 为 0 或 1 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/unique-paths-ii 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 */ /** * @param {number[][]} obstacleGrid * @return {number} */ // 动态规划 var uniquePathsWithObstacles = function(obstacleGrid) { // f(i, j) - 走到 (i, j) 共有的路径数 // 状态转移 - 最后一步是向下/右 // f(i, j) = f(i - 1, j) + f(i, j - 1) // 将最上/左方初始化为 1(不能这样初始化了,因为有障碍物后面的点不可达) // 有障碍物处的 f(i, j) 始终为 0 const m = obstacleGrid.length const n = obstacleGrid[0].length // 出发点或终点有障碍物,直接返回 0——不特判也能得到正确结果 if (obstacleGrid[0][0] === 1 || obstacleGrid[m-1][n-1] === 1) { return 0 } // 不同的初始化方式 // const f = new Array(m).fill(0).map(() => new Array(n).fill(1)) // // 所有有障碍的地方初始化为 0 // obstacleGrid.forEach((item, i) => { // item.forEach((x, j) => { // if (item[j] === 1) { // f[i][j] = 0 // } // }) // }) // // 第 0 行中,若出现障碍物,则此后的点均初始化为 0 // for (let i = 0; i < n; i++) { // if (obstacleGrid[0][i] === 1) { // for (let j = i; j < n; j++) { // f[0][j] = 0 // } // break // } // } // // 第 0 列中,若出现障碍物,则此后的点均初始化为 0 // for (let i = 0; i < m; i++) { // if (obstacleGrid[i][0] === 1) { // for (let j = i; j < m; j++) { // f[j][0] = 0 // } // break // } // } // 也可以先全部初始化为 0,然后将初始可达的点置为 1 const f = new Array(m).fill(0).map(() => new Array(n).fill(0)) // 第 0 行中,若未出现障碍物,则点均初始化为 1 for (let i = 0; i < n; i++) { if (obstacleGrid[0][i] === 1) { break } f[0][i] = 1 } // 第 0 列中,若出现障碍物,则此后的点均初始化为 0 for (let i = 0; i < m; i++) { if (obstacleGrid[i][0] === 1) { break } f[i][0] = 1 } // 只计算第 1 行/列之后的 for (let i = 1; i < m; i++) { for (let j = 1; j < n; j++) { if (obstacleGrid[i][j] !== 1) { f[i][j] = f[i - 1][j] + f[i][j - 1] } } } return f[m-1][n-1] }; // 动态规划 - 空间优化(滚动数组思想) // 懒得敲了 var obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]] var obstacleGrid = [[0,1],[0,0]] var obstacleGrid = [[1, 0]] var obstacleGrid = [[0,0],[1,1],[0,0]] const res = uniquePathsWithObstacles(obstacleGrid) console.log(res)
/* leetcode 64. 最小路径和 给定一个包含非负整数的 m * n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 说明:每次只能向下或者向右移动一步。 示例 1: ![](https://assets.leetcode.com/uploads/2020/11/05/minpath.jpg) 输入:grid = [[1,3,1],[1,5,1],[4,2,1]] 输出:7 解释:因为路径 1→3→1→1→1 的总和最小。 示例 2: 输入:grid = [[1,2,3],[4,5,6]] 输出:12 提示: m == grid.length n == grid[i].length 1 <= m, n <= 200 0 <= grid[i][j] <= 100 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/minimum-path-sum 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 */ /** * @param {number[][]} grid * @return {number} */ // 动态规划 var minPathSum = function(grid) { // f(i, j) - 到达点 (i, j) 的路径上数字总和的最小值 // 状态转移 - 最后一步向下/右 // f(i, j) = min(f(i - 1, j), f(i, j - 1)) + grid(i, j) const m = grid.length // [][0] const n = grid[0].length // [0][] const f = new Array(m).fill(0).map(() => new Array(n).fill(0)) let sum = 0 for (let i = 0; i < m; i++) { sum += grid[i][0] f[i][0] = sum } sum = 0 for (let i = 0; i < n; i++) { sum += grid[0][i] f[0][i] = sum } for (let i = 1; i < m; i++) { for (let j = 1; j < n; j++) { f[i][j] = Math.min(f[i - 1][j], f[i][j - 1]) + grid[i][j] } } return f[m - 1][n - 1] } // 动态规划 - 空间优化(滚动数组) // 空间小了 0.3MB 时间短了 4 ms var minPathSum = function(grid) { // f(i, j) - 到达点 (i, j) 的路径上数字总和的最小值 // 状态转移 - 最后一步向下/右 // f(i, j) = min(f(i - 1, j), f(i, j - 1)) + grid(i, j) const m = grid.length // [][0] const n = grid[0].length // [0][] // [m, n] = [Math.min(m, n), Math.max(m, n)] let cur = new Array(n).fill(0) cur[0] = grid[0][0] // 第一行的每列路径,全部记录下来 for (let i = 1; i < n; i++) { cur[i] = cur[i - 1] + grid[0][i] } for (let i = 1; i < m; i++) { // 第一列的每行累加 cur[0] += grid[i][0] for (let j = 1; j < n; j++) { cur[j] = Math.min(cur[j], cur[j - 1]) + grid[i][j] } } return cur[n - 1] } var grid = [[1,3,1],[1,5,1],[4,2,1]] // 7 grid = [[1,2,3],[4,5,6]] // 12 const res = minPathSum(grid) console.log(res)
/* leetcode 91. 解码方法 一条包含字母 A-Z 的消息通过以下映射进行了 编码 : 'A' -> 1 'B' -> 2 ... 'Z' -> 26 要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"111" 可以将 "1" 中的每个 "1" 映射为 "A" ,从而得到 "AAA" ,或者可以将 "11" 和 "1"(分别为 "K" 和 "A" )映射为 "KA" 。注意,"06" 不能映射为 "F" ,因为 "6" 和 "06" 不同。 给你一个只含数字的 非空 字符串 num ,请计算并返回 解码 方法的 总数 。 题目数据保证答案肯定是一个 32 位 的整数。 示例 1: 输入:s = "12" 输出:2 解释:它可以解码为 "AB"(1 2)或者 "L"(12)。 示例 2: 输入:s = "226" 输出:3 解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 示例 3: 输入:s = "0" 输出:0 解释:没有字符映射到以 0 开头的数字。含有 0 的有效映射是 'J' -> "10" 和 'T'-> "20" 。由于没有字符,因此没有有效的方法对此进行解码,因为所有数字都需要映射。 示例 4: 输入:s = "06" 输出:0 解释:"06" 不能映射到 "F" ,因为字符串开头的 0 无法指向一个有效的字符。 提示: 1 <= s.length <= 100 s 只包含数字,并且可能包含前导零。 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/decode-ways 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 */ /** * @param {string} s * @return {number} */ // 动态规划 var numDecodings = function(s) { // f(i) - 以 s[i] 结尾的字符串,的解码总数 // 状态转移 - 最后一步,解码一/两个字符 if (s[0] == 0) { return 0 } const len = s.length const f = new Array(len + 1).fill(0) f[0] = 1 f[1] = 1 for (let i = 2; i <= len; i++) { if (s[i - 1] != 0) { f[i] = f[i - 1] } let num = s.slice(i - 2, i) num = parseInt(num) // console.log(num) if (num >= 10 && num <= 26) { f[i] += f[i - 2] } } return f[len] } // 动态规划 - 空间优化(滚动数组) // 空间减小了 0.1MB 时间增加了 20 ms var numDecodings = function(s) { // f(i) - 以 s[i] 结尾的字符串,的解码总数 // 状态转移 - 最后一步,解码一/两个字符 // f(i) = f(i - 1) + f(i - 2) // 条件:s[i] != 0, s[i-1, i] 在 [10, 26] 之间 if (s[0] == 0) { return 0 } const len = s.length let pre = 1 let cur = 1 for (let i = 2; i <= len; i++) { let tmp = 0 if (s[i - 1] != 0) { tmp = cur } let num = s.slice(i - 2, i) num = parseInt(num) // console.log(num) if (num >= 10 && num <= 26) { tmp += pre } pre = cur cur = tmp } return cur } var s = "06" // 0 var s = "12" // 2 var s = "226" // 3 const res = numDecodings(s) console.log(res)
/* leetcode 96. 不同的二叉搜索树 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 示例: 输入: 3 输出: 5 解释: 给定 n = 3, 一共有 5 种不同结构的二叉搜索树: 1 3 3 2 1 \ / / / \ \ 3 2 1 1 3 2 / / \ \ 2 1 2 3 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/unique-binary-search-trees 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 */ /** * @param {number} n * @return {number} */ // 动态规划 var numTrees = function(n) { // G(n) - 序列长度为 n 的二叉搜索树的种数 // f(i, n) - 以 i 为根结点,序列长度为 n 的二叉搜索树的种数 // 状态转移 - 不同根结点构成所有二叉搜索树 // G(n) = f(1, n) + f(2, n) + ... + f(n, n) // f(i, n) = G(i - 1)G(n - i) => // G(n) = G(0)G(n-1) + G(1)G(n-2) + ... + G(n-1)G(0) // let g = [] // 必须初始化为 0,这样声明,里面的值是 undefined,无法计算 // console.log(g[0]) // undefined let g = new Array(n + 1).fill(0) g[0] = 1 g[1] = 1 for (let i = 2; i <= n; i++) { for (let j = 1; j <= i; j++) { g[i] += g[j - 1] * g[i - j] } } return g[n] } // G(n) = G(0)G(n-1) + G(1)G(n-2) + ... + G(n-1)G(0) —— 卡塔兰数 C_n // C0 = 1,C_(n+1)=2(2n+1)/(n+2) C_n var n = 10 const res = numTrees(n) console.log(res)
推导出来的 G(n) 的公式 G ( n ) = ∑ i = 1 n F ( i , n ) , G ( 0 ) = 1 , G ( 1 ) = 1 G(n) = \sum_{i=1}^{n}F(i, n),G(0)=1, G(1)=1 G(n)=∑i=1nF(i,n),G(0)=1,G(1)=1
上述即为 卡塔兰数 G n + 1 = 2 ( 2 n + 1 ) n + 2 C n , C 0 = 1 G_{n+1} = \frac{2(2n+1)}{n+2} C_n,C_0=1 Gn+1=n+22(2n+1)Cn,C0=1
这题也不是动态规划呀,先搁着,以后写递归的时候再回头来看(搁置??)
/* leetcode 95. 不同的二叉搜索树 II 给定一个整数 n,生成所有由 1 ... n 为节点所组成的 二叉搜索树 。 示例: 输入:3 输出: [ [1,null,3,2], [3,2,null,1], [3,1,null,null,2], [2,1,3], [1,null,2,null,3] ] 解释: 以上的输出对应以下 5 种不同结构的二叉搜索树: 1 3 3 2 1 \ / / / \ \ 3 2 1 1 3 2 / / \ \ 2 1 2 3 提示: 0 <= n <= 8 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/unique-binary-search-trees-ii 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 */ /** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {number} n * @return {TreeNode[]} */ var generateTrees = function(n) { };
这个 DFS 回溯讲的很清晰,到时候做 DFS 回溯板块的时候,要回来看
/* leetcode 97. 交错字符串 给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串: s = s1 + s2 + ... + sn t = t1 + t2 + ... + tm |n - m| <= 1 交错 是 s1 + t1 + s2 + t2 + s3 + t3 + ... 或者 t1 + s1 + t2 + s2 + t3 + s3 + ... 提示:a + b 意味着字符串 a 和 b 连接。 示例 1: ![](https://assets.leetcode.com/uploads/2020/09/02/interleave.jpg) 输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" 输出:true 示例 2: 输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc" 输出:false 示例 3: 输入:s1 = "", s2 = "", s3 = "" 输出:true 提示: 0 <= s1.length, s2.length <= 100 0 <= s3.length <= 200 s1、s2、和 s3 都由小写英文字母组成 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/interleaving-string 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 */ /** * @param {string} s1 * @param {string} s2 * @param {string} s3 * @return {boolean} */ // 动态规划 var isInterleave = function(s1, s2, s3) { // 能决定下一步的是?现在选择的是哪个字符串,另一个字符串从哪里选择 // f(i, j) - s3[i~] 是从 j(0/1) 字符串选择的 // 放弃 // 上面的思路错了——不用管交错的,直接一个字符一个字符接着匹配就行 // 主要是看 s3 的字符能不能和 s1/s2 的字符匹配 // f(i, j) - s1[1~i] 和 s2[1~j] 能否交错组成 s3[1~i+j] // 状态转移 - s3[i+j] 最后是和谁匹配的 s1[i] 还是 s2[j] // (f(i - 1, j) && s1[i] === s3[i + j]) || (f(i, j - 1) && s2[j] === s3[i + j]) const len1 = s1.length const len2 = s2.length if (len1 + len2 !== s3.length) { return false } const f = new Array(len1 + 5).fill(false).map(() => new Array(len2 + 5).fill(false)) // f[0, x] f[x, 0] 都要初始化出来 f[0, 0]-用不到,可以不管 f[0][0] = true // 如果传入的都是空串,就要初始化 f[0, 0] // 初始化复杂了 - 动态转移不从 1 开始,从 0 开始 // for (let i = 1; i <= len1; i++) { // f[i][0] = (s1[i - 1] === s3[i - 1]) // if (f[i][0] === false) { // 有字符不匹配了,后面的就不可能匹配上 // break // } // } // for (let i = 1; i <= len2; i++) { // f[0][i] = (s2[i - 1] === s3[i - 1]) // if (f[0][i] === false) { // break // } // } // for (let i = 1; i <= len1; i++) { // for (let j = 1; j <= len2; j++) { // const x = i - 1, y = j - 1 // f[i][j] = (f[i - 1][j] && (s1[x] === s3[x + y + 1])) || (f[i][j - 1] && (s2[y] === s3[x + y + 1])) // } // } // 动态转移不从 1 开始,从 0 开始 for (let i = 0; i <= len1; i++) { for (let j = 0; j <= len2; j++) { const x = i - 1, y = j - 1 if (i > 0) { // 这里就要加上自己去或了 f[i][j] = f[i][j] || (f[i - 1][j] && (s1[x] === s3[x + y + 1])) } if (j > 0) { f[i][j] = f[i][j] || (f[i][j - 1] && (s2[y] === s3[x + y + 1])) } } } return f[len1][len2] } // 动态规划 - 空间优化(滚动数组的思想) // 空间小了 3MB 时间多了 4ms // 老实说,懂了滚动数组的思想,但单独写代码写不出来,只能按照未优化的代码去改写 var isInterleave = function(s1, s2, s3) { // 不用管交错的,直接一个字符一个字符接着匹配就行 // 主要是看 s3 的字符能不能和 s1/s2 的字符匹配 // f(i, j) - s1[1~i] 和 s2[1~j] 能否交错组成 s3[1~i+j] // 状态转移 - s3[i+j] 最后是和谁匹配的 s1[i] 还是 s2[j] // (f(i - 1, j) && s1[i] === s3[i + j]) || (f(i, j - 1) && s2[j] === s3[i + j]) const len1 = s1.length const len2 = s2.length if (len1 + len2 !== s3.length) { return false } const cur = new Array(len2 + 5).fill(false) cur[0] = true // 动态转移不从 1 开始,从 0 开始 for (let i = 0; i <= len1; i++) { for (let j = 0; j <= len2; j++) { const x = i - 1, y = j - 1 if (i > 0) { cur[j] = cur[j] && (s1[x] === s3[x + y + 1]) } if (j > 0) { cur[j] = cur[j] || (cur[j - 1] && (s2[y] === s3[x + y + 1])) } } } return cur[len2] } var s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" // true var s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc" // false var s1 = "", s2 = "", s3 = "" // true var s1 = "db", s2 = "b", s3 = "cbb" // false const res = isInterleave(s1, s2, s3) console.log(res)
递归 + 记忆化 + DP,
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。