「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战」
300.最长递增子序列
dp[i]的定义:dp[i]表示 i 之前包括i的最长上升子序列的长度。
状态转移方程:位置 i 的最长升序子序列等于 j 从 0 到 i-1 各个位置的最长升序子序列 + 1 的最大值。
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
const lengthOfLIS = (nums) => {
let dp = new Array(nums.length).fill(1);
let result = 1;
for (let i = 0; i < nums.length; i++) {
for (let j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1)
}
}
result = Math.max(result, dp[i])
}
return result
}
334. 递增的三元子序列
dp[i]的定义:dp[i]表示 i 之前包括i的最长上升子序列的长度。
状态转移方程:位置 i 的最长升序子序列等于 j 从 0 到 i-1 各个位置的最长升序子序列 + 1 的最大值。
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
const increasingTriplet = (nums) => {
let dp = new Array(nums.length).fill(1);
let result = 1;
if (Array.from(new Set(nums)).length === 2) {
return false
}
for (let i = 0; i < nums.length; i++) {
for (let j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1)
}
}
result = Math.max(result, dp[i])
if (result >= 3) {
return true
}
}
return false
}
354. 俄罗斯套娃信封问题
dp[i]的定义:dp[i]表示 i 之前包括 i 的最长上升子序列的长度。
状态转移方程:位置i的最长升序子序列等于 j 从 0 到 i-1 各个位置的最长升序子序列 + 1 的最大值。
if (envelopes[i][0] > envelopes[j][0] && envelopes[i][1] > envelopes[j][1]) {
dp[i] = Math.max(dp[i], dp[j] + 1)
}
const maxEnvelopes = (envelopes) => {
let result = 1;
let dp = new Array(envelopes.length).fill(1);
envelopes.sort((a, b) => a[0] - b[0])
for (let i = 1; i < envelopes.length; i++) {
for (let j = 0; j < i; j++) {
if (envelopes[i][0] > envelopes[j][0] && envelopes[i][1] > envelopes[j][1]) {
dp[i] = Math.max(dp[i], dp[j] + 1)
}
}
result = Math.max(result, dp[i]);
}
return result;
};
646. 最长数对链
dp[i]的定义:dp[i]表示 i 之前包括 i 的最长上升子序列的长度。
状态转移方程:位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。
if (pairs[i][0] > pairs[j][1]) {
dp[i] = max(dp[i], dp[j] + 1);
}
const findLongestChain = (pairs) => {
let dp = new Array(pairs.length).fill(1);
let result = 1;
pairs.sort((a, b) => a[0] - b[0])
for (let i = 1; i < pairs.length; i++) {
for (let j = 0; j < i; j++) {
if (pairs[i][0] > pairs[j][1]) {
dp[i] = Math.max(dp[i], dp[j] + 1)
}
}
result = Math.max(result, dp[i])
}
return result
}
674. 最长连续递增序列
dp[i]的定义:dp[i]表示以下标i为结尾的数组的连续递增的子序列长度。
状态转移方程:如果 nums[i + 1] > nums[i],那么以 i+1 为结尾的数组的连续递增的子序列长度 一定等于 以i为结尾的数组的连续递增的子序列长度 + 1 。
if (nums[i + 1] > nums[i]) {
dp[i + 1] = dp[i] + 1;
}
const findLengthOfLCIS = (nums) => {
let dp = Array(nums.length).fill(1);
for (let i = 0; i < nums.length - 1; i++) {
if (nums[i + 1] > nums[i]) {
dp[i + 1] = dp[i] + 1;
}
}
return Math.max(...dp);
};
718. 最长重复子数组
dp[i][j]的定义:以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]
状态转移方程:根据dp[i][j]的定义,dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来。即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;
if (A[i - 1] === B[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1
}
const findLength = (A, B) => {
const [m, n] = [A.length, B.length];
const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0));
let res = 0;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (A[i - 1] === B[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1
}
res = Math.max(dp[i][j], res)
}
}
return res;
}
673. 最长递增子序列的个数
const findNumberOfLIS = function (nums) {
let n = nums.length, maxLen = 0, ans = 0;
const dp = new Array(n).fill(0);
const cnt = new Array(n).fill(0);
for (let i = 0; i < n; ++i) {
dp[i] = 1;
cnt[i] = 1;
for (let j = 0; j < i; ++j) {
if (nums[i] > nums[j]) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
cnt[i] = cnt[j]; // 重置计数
} else if (dp[j] + 1 === dp[i]) {
cnt[i] += cnt[j];
}
}
}
if (dp[i] > maxLen) {
maxLen = dp[i];
ans = cnt[i]; // 重置计数
} else if (dp[i] === maxLen) {
ans += cnt[i];
}
}
return ans;
};
1143.最长公共子序列
dp[i][j]的定义:长度为 [0, i - 1] 的字符串 text1 与长度为 [0, j - 1] 的字符串 text2 的最长公共子序列为dp[i][j]
状态转移方程:主要就是两大情况: text1[i - 1] 与 text2[j - 1] 相同,text1[i - 1] 与 text2[j - 1] 不相同
-
如果
text1[i - 1]与text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1 -
如果
text1[i - 1]与text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列和text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的。
if (text1[i - 1] === text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
}
const longestCommonSubsequence = (text1, text2) => {
const [m, n] = [text1.length, text2.length];
const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (text1[i - 1] === text2[j - 1]) {
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[m][n];
}
1035. 不相交的线
const maxUncrossedLines = (nums1, nums2) => {
const [m, n] = [nums1.length, nums2.length];
const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (nums1[i - 1] === nums2[j - 1]) {
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[m][n];
}
392. 判断子序列
dp[i][j]的定义:dp[i][j] 表示以下标 i-1 为结尾的字符串s,和以下标 j-1 为结尾的字符串t,相同子序列的长度为dp[i][j]。
状态转移方程:在确定递推公式的时候,首先要考虑如下两种操作:
s[i - 1] == t[j - 1]- t中找到了一个字符在s中也出现了,那么
dp[i][j] = dp[i - 1][j - 1] + 1,因为找到了一个相同的字符,相同子序列长度自然要在dp[i-1][j-1]的基础上加1
- t中找到了一个字符在s中也出现了,那么
s[i - 1] != t[j - 1]- 相当于t要删除元素,继续匹配。此时相当于t要删除元素,t如果把当前元素
t[j - 1]删除,那么dp[i][j]的数值就是 看s[i - 1]与t[j - 2]的比较结果了,即:dp[i][j] = dp[i][j - 1]
- 相当于t要删除元素,继续匹配。此时相当于t要删除元素,t如果把当前元素
if (s[i - 1] === t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1
} else {
dp[i][j] = dp[i][j - 1]
}
const isSubsequence = (s, t) => {
const [m, n] = [s.length, t.length];
const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s[i - 1] === t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1
} else {
dp[i][j] = dp[i][j - 1]
}
}
}
return dp[m][n] === m ? true : false;
}
115. 不同的子序列
dp[i][j]的定义:以 i-1 为结尾的s子序列中出现以 j-1 为结尾的t的个数为dp[i][j]。
状态转移方程:
-
s[i - 1]与t[j - 1]相等:dp[i][j]可以有两部分组成。-
一部分是用
s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。 -
一部分是不用
s[i - 1]来匹配,个数为dp[i - 1][j]。 -
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
-
-
s[i - 1]与t[j - 1]不相等,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i - 1][j]。
if (s[i - 1] === t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
} else {
dp[i][j] = dp[i - 1][j]
}
const numDistinct = (s, t) => {
const [m, n] = [s.length, t.length];
const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0));
for (let i = 0; i <= m; i++) {
dp[i][0] = 1;
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s[i - 1] === t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
} else {
dp[i][j] = dp[i - 1][j]
}
}
}
return dp[m][n]
}
583. 两个字符串的删除操作
dp[i][j]的定义:以 i-1 为结尾的字符串 word1,和以 j-1 位结尾的字符串 word2,想要达到相等,所需要删除元素的最少次数。
状态转移方程:
-
当
word1[i - 1]与word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1] -
当
word1[i - 1]与word2[j - 1]不相同的时候,有三种情况:-
删
word1[i - 1],最少操作次数为dp[i - 1][j] + 1 -
删
word2[j - 1],最少操作次数为dp[i][j - 1] + 1 -
同时删
word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
-
最后当然是取最小值,所以当 word1[i - 1]与 word2[j - 1] 不相同的时候
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] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 2)
}
const minDistance = (word1, word2) => {
const [m, n] = [word1.length, word2.length];
const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 1; j <= n; j++) {
dp[0][j] = j;
}
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] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 2)
}
}
}
return dp[m][n]
}
72. 编辑距离
dp[i][j]的定义:dp[i][j] 表示以下标 i-1为结尾的字符串 word1,和以下标 j-1 为结尾的字符串 word2,最近编辑距离为dp[i][j]。
状态转移方程:
-
word1[i - 1] == word2[j - 1],不操作 -
word1[i - 1] != word2[j - 1]-
word1删除一个元素,那么就是以下标i-2为结尾的word1与j-1为结尾的word2的最近编辑距离 再加上一个操作。dp[i][j] = dp[i - 1][j] + 1 -
word2删除一个元素,那么就是以下标i-1为结尾的word1与j-2为结尾的word2的最近编辑距离 再加上一个操作。dp[i][j] = dp[i][j - 1] + 1 -
替换元素,
word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增加元素,那么以下标i-2为结尾的word1与j-2为结尾的word2的最近编辑距离 加上一个替换元素的操作。即dp[i][j] = dp[i - 1][j - 1] + 1
-
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] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1)
}
const minDistance = (word1, word2) => {
const [m, n] = [word1.length, word2.length];
const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 1; j <= n; j++) {
dp[0][j] = j;
}
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] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1)
}
}
}
return dp[m][n]
}
712. 两个字符串的最小ASCII删除和
const minimumDeleteSum = (word1, word2) => {
const [m, n] = [word1.length, word2.length];
const dp = new Array(m + 1).fill(0).map(x => new Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
dp[i][0] = dp[i - 1][0] + word1[i - 1].charCodeAt();
}
for (let j = 1; j <= n; j++) {
dp[0][j] = dp[0][j - 1] + word2[j - 1].charCodeAt();
}
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] + word1[i - 1].charCodeAt(), dp[i][j - 1] + word2[j - 1].charCodeAt(), dp[i - 1][j - 1] + (word1[i - 1].charCodeAt() + word2[j - 1].charCodeAt()))
}
}
}
return dp[m][n]
}
647. 回文子串
dp[i][j]的定义:表示区间范围[i,j] 的子串是否是回文子串,如果是 dp[i][j]为 true,否则为 false。
状态转移方程:整体上是两种,就是 s[i] 与 s[j] 相等,s[i] 与 s[j] 不相等这两种。
-
当
s[i]与s[j]不相等,dp[i][j]是false。 -
当
s[i]与s[j]相等时,有如下三种情况- 下标 i 与 j 相同,同一个字符例如a,当然是回文子串
- 下标 i 与 j 相差为1,例如aa,也是文子串
- 下标 i 与 j 相差大于1的时候,例如
cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是i+1与j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。
if (s[i] === s[j]) {
if ((j - i) < 2) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
result += dp[i][j] ? 1 : 0;
}
const countSubstrings = (s) => {
const m = s.length;
let result = 0;
const dp = new Array(m).fill(0).map(x => new Array(m).fill(0));
for (let j = 0; j < m; j++) {
for (let i = 0; i <= j; i++) {
if (s[i] === s[j]) {
if ((j - i) < 2) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
result += dp[i][j] ? 1 : 0;
}
}
}
return result;
}
516. 最长回文子序列
dp[i][j]的定义:字符串 s 在 [i, j] 范围内最长的回文子序列的长度为 dp[i][j]。
状态转移方程:
-
s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2 -
s[i]与s[j]不相同,说明s[i]和s[j]的同时加入 并不能增加[i,j]区间回文子串的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。-
加入
s[j]的回文子序列长度为dp[i + 1][j] -
加入
s[i]的回文子序列长度为dp[i][j - 1]
-
那么 dp[i][j] 一定是取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
if (s[i] === s[j]) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1])
}
const longestPalindromeSubseq = (s) => {
const m = s.length;
const dp = new Array(m).fill(0).map(x => new Array(m).fill(0));
for (let i = 0; i < m; i++) {
dp[i][i] = 1;
}
for (let i = m - 1; i >= 0; i++) {
for (let j = i + 1; j < m; j++) {
if (s[i] === s[j]) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1])
}
}
}
return dp[0][m - 1];
}
5. 最长回文子串
const longestPalindrome = (s) => {
let m = s.length;
let result = '';
const dp = new Array(m).fill(0).map(x => new Array(m).fill(0));
for (let i = m - 1; i >= 0; i--) {
for (let j = i; j < m; j++) {
if (s[i] === s[j]) {
if (j - i < 2 || dp[i + 1][j - 1]) {
dp[i][j] = true
}
}
if (dp[i][j] && j - i + 1 > result.length) {
result = s.substring(i, j + 1);
}
}
}
return result;
};
53.最大子数组和
dp[i][j]的定义:包括下标i之前的最大连续子序列和为 dp[i]。
状态转移方程:dp[i] 只有两个方向可以推出来:
dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和nums[i],即:从头开始计算当前连续子序列和
一定是取最大的,所以dp[i] = max(dp[i - 1] + nums[i], nums[i])
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i])
const maxSubArray = (nums) => {
let dp = new Array(nums.length).fill(0);
dp[0] = nums[0];
let result = nums[0];
for (let i = 1; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i])
result = Math.max(result, dp[i])
}
return result;
}
152. 乘积最大子数组
function maxProduct(nums) {
if (nums.length == 0) return 0;
let dpMax = [...nums], dpMin = [...nums];
for (let i = 1; i < nums.length; i++) {
// 比较i*最大值,i*最小值
dpMax[i] = Math.max(dpMax[i - 1] * nums[i], Math.max(nums[i], dpMin[i - 1] * nums[i]));
// 求最小值
dpMin[i] = Math.min(dpMin[i - 1] * nums[i], Math.min(nums[i], dpMax[i - 1] * nums[i]));
}
let ans = dpMax[0];
for (let i = 1; i < dpMax.length; i++) {
ans = Math.max(ans, dpMax[i]);
}
return ans;
};
978. 最长湍流子数组
dp[i][j]的定义:以 i 位置结尾的最长连续子数组的长度
状态转移方程:
- 定义
up[i]表示以位置 i 结尾的,并且arr[i - 1] < arr[i]的最长湍流子数组长度。 - 定义
down[i]表示以位置 i 结尾的,并且arr[i - 1] > arr[i]的最长湍流子数组长度。
up[i] 和 down[i] 初始化都是 1,因为每个数字本身都是一个最小的湍流子数组。
up[i] = down[i - 1] + 1,当 arr[i - 1] < arr[i];
down[i] = up[i - 1] + 1,当 arr[i - 1] > arr[i];
const maxTurbulenceSize = (nums) => {
let up = new Array(nums.length).fill(1);
let down = new Array(nums.length).fill(1);
let result = 1;
for (let i = 1; i < nums.length; i++) {
if (nums[i - 1] < nums[i]) {
up[i] = down[i - 1] + 1
result = Math.max(result, up[i])
} else if (nums[i - 1] > nums[i]) {
down[i] = up[i - 1] + 1
result = Math.max(result, down[i])
}
}
return result;
};
跟大家推荐代码随想录,真的非常好
参考文章: