diff --git a/dynamic-programming/subsequence/1035不相交的线.js b/dynamic-programming/subsequence/1035不相交的线.js new file mode 100644 index 0000000..9377082 --- /dev/null +++ b/dynamic-programming/subsequence/1035不相交的线.js @@ -0,0 +1,31 @@ +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number} + */ +const maxUncrossedLines = function (nums1, nums2) { + +}; + +/* +这个题看似无从下手实际上非常得简单,我们从结果入手,一个不交叉得最大连线是什么样的,经过观察发现就是要我们求 +最大公共序列,直接把1143的题目拿过来改一下即可 +*/ + +function f1(nums1, nums2) { + const m = nums1.length; + const n = nums2.length; + + const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); + + for (let i = m - 1; i >= 0; i--) { + for (let j = n - 1; j >= 0; j--) { + if (nums1[i] === nums2[j]) { + dp[i][j] = dp[i + 1][j + 1] + 1; + } else { + dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]); + } + } + } + return dp[0][0]; +} diff --git a/dynamic-programming/subsequence/1143最长公共子序列.js b/dynamic-programming/subsequence/1143最长公共子序列.js new file mode 100644 index 0000000..e8930ce --- /dev/null +++ b/dynamic-programming/subsequence/1143最长公共子序列.js @@ -0,0 +1,30 @@ +/** + * @param {string} text1 + * @param {string} text2 + * @return {number} + */ +const longestCommonSubsequence = function (text1, text2) { + +}; + +/* +定义dp[i][j]为text1从i位置开始统计,text2从j位置开始统计的最长公共子序列长度,如果text1[i]和text2[j]相等,则dp[i][j]=dp[i+1][j+1] +若不相等则,dp[i][j] = max(dp[i+1][j], dp[i][j+1]) +*/ +function f1(text1, text2) { + const m = text1.length; + const n = text2.length; + + const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); + + for (let i = m - 1; i >= 0; i--) { + for (let j = n - 1; j >= 0; j--) { + if (text1[i] === text2[j]) { + dp[i][j] = dp[i + 1][j + 1] + 1; + } else { + dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]); + } + } + } + return dp[0][0]; +} diff --git a/dynamic-programming/subsequence/392判断子序列.js b/dynamic-programming/subsequence/392判断子序列.js new file mode 100644 index 0000000..9f8c870 --- /dev/null +++ b/dynamic-programming/subsequence/392判断子序列.js @@ -0,0 +1,30 @@ +/** + * @param {string} s + * @param {string} t + * @return {boolean} + */ +const isSubsequence = function (s, t) { + return f1(s, t); +}; + +/* +这个题目起始从头到尾遍历开s[1]在不在t中,然后再看s[2]在不在,最后看s[len - 1]在不在,如果在则返回true, +这个题目也可以是使用动态规划,动态规划的思路就是,如果s[i]在t中存在,如果s[i+1:]这个子序列也在t中存在, +那么s[i:]开始的子序列,在t中也存在,定义dp[i]为,字符串s从i开始的所有子序列s[i:]在t[j]之后的子序列中存在 +s[i] === t[j] +*/ +function f2(s, t) { + if (s.length === 0) return true; + if (t.length === 0) return false; + + const dp = Array(s.length).fill(false); + + for (let j = t.length - 1, i = s.length - 1; j >= 0 && i >= 0; j--) { + if (t[j] === s[i]) { + dp[i] = true; + i--; + } + } + + return dp[0]; +} diff --git a/dynamic-programming/subsequence/53最大子数组和.js b/dynamic-programming/subsequence/53最大子数组和.js new file mode 100644 index 0000000..e2578ce --- /dev/null +++ b/dynamic-programming/subsequence/53最大子数组和.js @@ -0,0 +1,27 @@ +/** + * @param {number[]} nums + * @return {number} + */ +const maxSubArray = function (nums) { + +}; + +/* +定义dp[i]为从nums[i]开始的和最大的子数组,那么动态转移方程为dp[i] = dp[i+1] + nums[i] (dp[i+1] > 0) +*/ + +function f1(nums) { + const dp = Array(nums.length + 1).fill(0); // dp[i]表示从i位置开始的和最大子数组 + let result = -Infinity; + + for (let i = nums.length - 1; i >= 0; i--) { + if (dp[i + 1] > 0) { + dp[i] = nums[i] + dp[i + 1]; + } else { + dp[i] = nums[i]; + } + + result = Math.max(result, dp[i]); + } + return result; +} diff --git a/dynamic-programming/subsequence/583两个字符串的删除操作.js b/dynamic-programming/subsequence/583两个字符串的删除操作.js new file mode 100644 index 0000000..7ea76fa --- /dev/null +++ b/dynamic-programming/subsequence/583两个字符串的删除操作.js @@ -0,0 +1,72 @@ +/** + * @param {string} word1 + * @param {string} word2 + * @return {number} + */ +const minDistance = function (word1, word2) { + +}; + +/* +这个题看似无从下手,其实反过来思考非常容易,直接求出最长的公共子序列,然后步数就是这个两个字符串中多出的那几个字符的和 +*/ + +function f1(word1, word2) { + const commonLen = longestCommonSubsequence(word1, word2); + return word1.length + word2.length - 2 * commonLen; +} + +function longestCommonSubsequence(text1, text2) { + const m = text1.length; + const n = text2.length; + + const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); + + for (let i = m - 1; i >= 0; i--) { + for (let j = n - 1; j >= 0; j--) { + if (text1[i] === text2[j]) { + dp[i][j] = dp[i + 1][j + 1] + 1; + } else { + dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]); + } + } + } + return dp[0][0]; +} + +/* +直接利用动态规划,来求解,定义dp[i][j]为s1中前i个字符和s2中前j个字符,变得相同需要删除的字符数量的最少个数,第i个字符是s1[i-1] +同理第j个字符是s2[j-1]注意这里的dp[i][j]的定义是前i个字符,从1开始数的,之所以要这样定义,是因为s1可以为空字符,需要使用dp[0][0] +来表示空字符 +*/ + +function f2(word1, word2) { + const m = word1.length; + const n = word2.length; + + // 定义dp表m+1行,n+1列 + const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); // 初始化成0没有特殊意义,反正都要填表 + + // 初始第一行,s1为空字符串,那么当s2有多少个字符就取出多少个,即dp[0][j] = j + for (let j = 0; j <= n; j++) { + dp[0][j] = j; + } + + // 列同理 + for (let i = 0; i <= m; i++) { + dp[i][0] = i; + } + + // 填充dp表从上到下,从左到右 + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + if (word1[i - 1] === word2[j - 1]) { // 如果第i个字符和第j个字符相等 + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1; + } + } + } + + return dp[m][n]; +} diff --git a/dynamic-programming/subsequence/718最长重复子数组.js b/dynamic-programming/subsequence/718最长重复子数组.js new file mode 100644 index 0000000..4af9ecf --- /dev/null +++ b/dynamic-programming/subsequence/718最长重复子数组.js @@ -0,0 +1,37 @@ +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number} + */ +const findLength = function (nums1, nums2) { + +}; + +/* +定义:dp[i][j]表示nums1从i开始,nums2从j开始的最长子数组的长度,dp[i][j]可以由两种情况得来,第一种,当nums1[i] !== nums2[j], +那么从这个位置开始的子数组,就不可能是公共子数组,多以dp[i][j] = 0;第二种,当nums1[i] === nums2[j] 那么dp[i][j] = 1 + dp[i+1][j+1] +得来。 +*/ +function f1(nums1, nums2) { + const m = nums1.length; + const n = nums2.length; + let result = 0; + // 定义dp表,并且初始化 + const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); + + // 按照dp[i][j]的定义我们需要从下到上,从右到左填充dp数组 + for (let i = m - 1; i >= 0; i--) { + for (let j = n - 1; j >= 0; j--) { + if (nums1[i] === nums2[j]) { + dp[i][j] = 1 + dp[i + 1][j + 1]; + result = Math.max(result, dp[i][j]); + } else { + dp[i][j] = 0; + } + } + } + console.log(dp); + + return result; +} +f1([0, 0, 0, 0, 1], [0, 0, 0, 0, 1]); diff --git a/dynamic-programming/subsequence/72编辑距离.js b/dynamic-programming/subsequence/72编辑距离.js new file mode 100644 index 0000000..6498ac5 --- /dev/null +++ b/dynamic-programming/subsequence/72编辑距离.js @@ -0,0 +1,48 @@ +/** + * @param {string} word1 + * @param {string} word2 + * @return {number} + */ +const minDistance = function (word1, word2) { + +}; + +/* + 有删除,添加,修改三种操作,word1的删除和word2的添加效果是一样的,所以实际上只有三种操作 + 1. 往word1中删除一个字符 + 2. 往word2中删除一个字符 + 3. 修改word1中的一个字符 + + 定义dp[i][j]表示word1的前i个字符和word2的前j个字符的最少编辑距离,那么在已知dp[i-1][j-1]和dp[i-1][j],dp[i][j-1]的情况下推出来 + 如果第word1的第i个字符和word2的第j个字符相等,那么最小编辑距离就是dp[i-1][j-1]因为第i个数和第j个字符我不需要操作,而剩下的这些字符 + 最少编辑距离就是dp[i-1][j-1],如果这word1的第i个字符和word2的第j个字符不相等,我们可以删除word1的第i个字符,然后将word1的前i-1个字符 + 和word2的前j个字符变得一样所需得编辑距离加上删除word1得第i个字符这一步一共是dp[i-1][j] + 1个编辑距离,同理:dp[i][j-1]得原理一样, + 最后还有一种获得最优得情况是,我不删除,只是将word1中得最后一个字符修改成和word2中卒子后一个字符一样,这样就变成word1[i-1]==word2[j-1] + 相等得情况了。 +*/ +function f1(word1, word2) { + const m = word1.length; + const n = word2.length; + const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); // 初始化成0无意义,只是初始化而已 + + // 初始化,如果word1为空,word2要想变得和word1一样,编辑距离就是它自身长度 + for (let j = 0; j <= n; j++) { + dp[0][j] = j; + } + // word2为空同理 + for (let i = 0; i <= m; i++) { + dp[i][0] = i; + } + + // 填dp表,dp[i][j] 依赖dp[i-1][j-1], dp[i-1][j], dp[i][j-1],所以要从上到下,从左到右遍历 + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + if (word1[i - 1] === word2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[m][n]; +}