- 最小删除距离(只允许删除)
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
//两字符串最小编辑距离
//二维dp数组,行和列大小为length + 1
var minDistance = function(word1, word2) {
let dp = [];
for (let i = 0; i <= word1.length; i++) {
dp.push(new Array(word2.length + 1));
}
for (let i = 0; i <= word1.length; i++) {
for (let j = 0; j <= word2.length; j++) {
//i为0,编辑距离为j
if (i == 0) {
dp[i][j] = j;
//j为0编辑距离为i
} else if (j == 0) {
dp[i][j] = i;
} else {
//两字母相同
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);
}
}
}
}
return dp[word1.length][word2.length];
};
- 序列s中有多少子序列与t相同
/**
* @param {string} s
* @param {string} t
* @return {number}
*/
//dp[i][j]代表序列s(0, i)中有多少个序列t(0, j)的子序列
var numDistinct = function(s, t) {
let dp = [];
for (let i = 0; i <= s.length; i++) {
dp.push(new Array(t.length + 1).fill(0));
}
//相当于不断加入s序列中的字符,看子序列个数是否会增加(尾部元素相同才会增加)
dp[0][0] = 1;
for (let i = 1; i <= s.length; i++) {
dp[i][0] = 1;
for (let j = 1; j <= Math.min(i, t.length); j++) {
//尾字符相同,以新字符结尾的子序列
if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i-1][j-1];
}
//不以新字符结尾的子序列
if (i > j) {
dp[i][j] += dp[i-1][j];
}
}
}
return dp[s.length][t.length];
};
- 最小编辑距离
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
var minDistance = function(word1, word2) {
if ((!word1 && !word2) || word1 == word2) return 0;
let dis = [];
for (let i = 0; i <= word1.length; i++) {
dis.push(new Array(word2.length + 1));
}
for (let i = 0; i <= word1.length; i++) {
for (let j = 0; j <= word2.length; j++) {
if (i == 0) {
dis[i][j] = j;
} else if (j == 0) {
dis[i][j] = i;
} else {
dis[i][j]
= Math.min(dis[i][j-1] + 1, dis[i-1][j] + 1, dis[i-1][j-1] + (word1[i-1] == word2[j-1] ? 0 : 1));
}
}
}
return dis[word1.length][word2.length];
};
最优子结构: dp不是number就是boolean.
-
最长回文子串:更短的区间字符串是否回文
-
分割等和数组:更少括号组合数量
-
字符串字典匹配:前置更短字符串能否匹配
-
是否可以找出和为m的子序列:前置是否可以找出和为m或者前置是否可以找出和为m-nums[i]的子序列。
-
组成和为amount的最小数目:组成和为amount-nums[i]的最小数目
-
最长回文子串 dp[i][j] = true/false. 长度为i,以j开头的字符串是否是回文子串
dp数组有3行,每次新增加一个长度,shift第一行,push([])到第三行
dp[i][j] == s[j] == s[j + i - 1] && dp[i-1][j+1]
- 分割等和数组 dp[i][j] = true/false. 从0~i是否可以选出和为j的子序列(不一定连续).
规划的tip是,是否选择nums[i]
dp[i][j] = dp[i-1][j] || dp[i-1][j - nums[i]]
dp数组其实可以缩短为1行,因为dp[i][j]只依赖于dp[i-1][j]或者dp[i-1][j - nums[i]]。为避免提前被重写,第二层从大到小。
dp[j] = dp[j] || (j > nums[i] && dp[j-nums[i]]);
/**
* @param {number[]} nums
* @return {boolean}
*/
var canPartition = function(nums) {
if (nums.length == 0) return true;
if (nums.length == 1) return false;
let max = 0, sum = 0;
for (let i = 0; i < nums.length; i++) {
sum += nums[i];
max = Math.max(nums[i], max);
}
if (sum % 2 == 1 || max > sum / 2) return false;
sum = sum / 2;
let dp = new Array(sum + 1).fill(false);
dp[0] = true;
//0~i中选数
for (let i = 1; i < nums.length; i++) {
for (let j = sum; j >= 0; j--) {
dp[j] = dp[j] || (j >= nums[i] && dp[j - nums[i]]);
if (j == sum && dp[j]) return true;
}
}
return false;
};
- 给定数字n,生成n对有效括号种类的组合方式
tip: 以最左边左括号及其对应的右括号为标志,之哟啊该对括号内包含的括号数目不同,则在括号对数相同的情况下,一定会生成不同的串。
解法:考虑新加的第n对括号, arr[n] = "(" + arr[p] + ")" + arr[q] p + q = n - 1。
/**
* @param {number} n
* @return {string[]}
*/
var generateParenthesis = function(n) {
let arr = [[""]];
//迭代n次
for (let i = 1; i <= n; i++) {
let arri = [];
for (let p = 0; p < i; p++) {
for (let m = 0; m < arr[p].length; m++) {
for (let k = 0; k < arr[i-p-1].length; k++) {
arri.push('(' + arr[p][m] + ')' + arr[i-p-1][k]);
}
}
}
arr.push(arri);
}
return arr[n];
};
- word break. 题目:给定字符串,判断是否能够用字典中的单词拼出来。
tip: 感觉上只能用暴力的题,都可以换成逆思路用dp
/**
* @param {string} s
* @param {string[]} wordDict
* @return {boolean}
*/
var wordBreak = function(s, wordDict) {
let maxLen = 0;
for (let i = 0; i < wordDict.length; i++) {
if (wordDict[i].length > maxLen) {
maxLen = wordDict[i].length;
}
}
let dp = new Array(s.length);
for (let i = 0; i < s.length; i++) {
for (k = 1; k <= maxLen; k++) {
if ((i == k - 1 && wordDict.indexOf(s.substr(0, k)) > -1 ) ||
(i >= k && dp[i - k] && wordDict.indexOf(s.substr(i - k + 1, k)) > -1)) {
dp[i] = true;
}
}
dp[i] = !!dp[i];
}
return dp[s.length - 1];
};
- 最少硬币
题目:给定硬币coins,求组成amount的最小硬币数量。
解法:最优子结构为更小面值的最少硬币数量。
dp[amount] = Math.min(dp[amount - coins[i]]) + 1;
- 最大连续乘积
tip:由于正负特性,维护两个数组
/**
* @param {number[]} nums
* @return {number}
*/
var maxProduct = function(nums) {
if (nums.length == 1) return nums[0];
let max = [], min = [], result;
max[0] = min[0] = nums[0];
result = max[0];
for (let i = 1; i < nums.length; i++) {
if (nums[i] == 0) {
max[i] = min[0] = 0;
} else if (nums[i] > 0) {
if (max[i - 1] > 0) {
max[i] = nums[i] * max[i-1];
} else {
max[i] = nums[i];
}
if (min[i - 1] <= 0) {
min[i] = min[i - 1] * nums[i];
} else {
min[i] = nums[i];
}
} else {
if (min[i - 1] <= 0) {
max[i] = min[i - 1] * nums[i];
} else {
max[i] = nums[i];
}
if (max[i - 1] > 0) {
min[i] = max[i-1] * nums[i];
} else {
min[i] = nums[i];
}
}
if (max[i] > result) {
result = max[i];
}
}
return result;
};
- 字符串编解码
最优子结构:dp[i]代表以i结尾的字符串的解码方式数目
题目:11106有几种编解码方式?(比如AAJF)
/**
* @param {string} s
* @return {number}
*/
var numDecodings = function(s) {
if (s[0] == '0') return 0;
let dp = new Array(s.length);
dp[0] = 1;
dp[1] = 1;
for (let i = 1; i < s.length; i++) {
if (s[i] == '0') {
if (s[i-1] == '1' || s[i-1] == '2') {
dp[i + 1] = dp[i - 1];
} else {
return 0;
}
} else {
if (s[i - 1] == '1' || (s[i-1] == '2' && s[i] <= '6')) {
dp[i + 1] = dp[i - 1] + dp[i];
} else {
dp[i + 1] = dp[i];
}
}
}
return dp[s.length];
};
- 最长回文子序列
最优子结构:
dp[x][y] = Math.max(dp[x][y], dp[x][y - 1], dp[x + 1][y]);
max函数中dp[x][y]的计算是如果两端相等,直接中间+2
- 是否是interleaving String
题目:判断s3是否能够为s1和s2分别分割为子串后交错构成。
解法:最优子结构: dp[i][j] = (s1[i] == s3[i+j-1] && dp[i-1][j] || (s2[j] == s3[i+j-1] && dp[i][j-1]);
/**
* @param {string} s1
* @param {string} s2
* @param {string} s3
* @return {boolean}
*/
var isInterleave = function(s1, s2, s3) {
if (s1.length + s2.length != s3.length) return false;
let dp = new Array(s2.length + 1);
dp[0] = true;
for (let i = 0; i <= s1.length; i++) {
for (j = 0; j <= s2.length; j++) {
if (i == 0 && j == 0) continue;
dp[j] = dp[j] && i > 0 && s1[i - 1] == s3[i + j - 1];
if (!dp[j]) {
dp[j] = j > 0 && s2[j - 1] == s3[i + j - 1] && dp[j-1];
}
}
}
return !!dp[s2.length]
};
- 金币凑齐金额amount的方式
最优子结构:dp[i][j] = dp[i-1][j] + dp[i][j-amount];
/**
* @param {number} amount
* @param {number[]} coins
* @return {number}
*/
var change = function(amount, coins) {
let dp = new Array(amount + 1).fill(0);
dp[0] = 1;
//dp[j]凑齐到j金币的方式
for (let i = 0; i < coins.length; i++) {
for (let j = 0; j <= amount; j++) {
if (coins[i] <= j){
dp[j] += dp[j - coins[i]];
}
}
}
return dp[amount];
};
- 障碍矩阵路径个数
题目:给定m * n矩阵,中有障碍,求从top-left到bottom-right的方法
解法:二维dp. 上一步只可能来自左边或者上面。如果有障碍则作废。
/**
* @param {number[][]} obstacleGrid
* @return {number}
*/
var uniquePathsWithObstacles = function(obstacleGrid) {
let m = obstacleGrid.length, n = obstacleGrid[0].length;
if (m == 1 && n == 1) return (obstacleGrid[0][0] + 1) % 2;
if (obstacleGrid[m-1][n-1] == 1) return 0;
let paths = [];
for (let i = 0; i < m; i++) {
paths.push(new Array(n));
}
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
paths[i][j] = 0;
if(i == 0 && j == 0) {
paths[i][j] = 1;
} else {
if (i > 0 && obstacleGrid[i-1][j] == 0){
paths[i][j] += paths[i-1][j];
}
if (j > 0 && obstacleGrid[i][j-1] == 0) {
paths[i][j] += paths[i][j-1];
}
}
}
}
return paths[m-1][n-1];
};
- 回文串个数
解法:dp[i][j]代表substring(i, j)是否回文。
dp[i][j] = i == 1 || (s[j] == s[j + i - 1] && (i <= 3 || dp[i-2][j+1]))
/**
* @param {string} s
* @return {number}
*/
var countSubstrings = function(s) {
let ans = 0, dp = [];
for (let i = 0; i <= s.length; i++) {
dp.push(new Array(s.length));
}
for (let i = 1; i <= s.length; i++) {
for (let j = 0; j < s.length - i + 1; j++) {
if (i == 1 || (s[j] == s[j + i - 1] && (i <= 3 || dp[i-2][j+1]))) {
dp[i][j] = true;
ans++;
}
}
}
return ans;
};
- 求n个结点的BST组成方式
tip: 左右子树的排列组合,可以用dp来做
/**
* @param {number} n
* @return {number}
*/
var numTrees = function(n) {
if (n <= 2) return n;
let dp = new Array(n + 1).fill(0);
dp[1] = 1;
dp[2] = 2;
for (let i = 3; i <= n; i++) {
dp[i] = 2 * dp[i-1];
let j = 1, k = i - 2;
while (j < k) {
dp[i] += 2 * dp[j] * dp[k];
j++;
k--;
}
if (j == k) {
dp[i] += dp[j] * dp[j];
}
}
return dp[n];
};
12、判断s3是否为s1和s2交织而成
tip: 首先证明只要判断最后一个字符相同以及子串满足条件,则整体最优子结构满足条件 dp[i]只依赖dp[i-1]时,可以只用一维数组
/**
* @param {string} s1
* @param {string} s2
* @param {string} s3
* @return {boolean}
*/
var isInterleave = function(s1, s2, s3) {
if (s1.length + s2.length != s3.length) return false;
let dp = new Array(s2.length + 1);
dp[0] = true;
for (let i = 0; i <= s1.length; i++) {
for (j = 0; j <= s2.length; j++) {
if (i == 0 && j == 0) continue;
dp[j] = dp[j] && i > 0 && s1[i - 1] == s3[i + j - 1];
if (!dp[j]) {
dp[j] = j > 0 && s2[j - 1] == s3[i + j - 1] && dp[j-1];
}
}
}
return !!dp[s2.length]
};
- 0-1背包问题
题目:有限背包容量能够拿到的最大价值
function KnapsackProblem01(vs, ws, w) {
let dp = [];
for (let i = 0; i <= vs.length; i++) {
dp.push(new Array(w + 1));
}
dp[0][0] = 0;
//可选物品个数
for(let i=0; i<=vs.length; i++) {
//可承受重量
for(let j=w; j>=0;j--) {
if (i==0 || j == 0) {
dp[j] = 0;
} else if (j > ws[i-1]) {
dp[j] = Math.max(dp[j-ws[i-1]] + vs[i-1], dp[j]);
}
}
}
}
- 0-X背包问题
0i的商品总价值相当于,0i选商品w-weight[i], 或者0~i-1选总价值w
function KnapsackProblem(vs, ws, w) {
let dp = new Array(vs.length + 1);
for (let i = 0; i <= vs.length; i++) {
for (let j = 0; j <= w; j++) {
if (i == 0 || j == 0) {
dp[j] = 0;
} else if (ws[i-1] <= w){
dp[j] = Math.max(dp[j-ws[i-1]] + vs[i-1], dp[j]);
}
}
}
}
- 整数拆分
数n能拆分出的乘积最大的序列
/**
* @param {number} n
* @return {number}
*/
var integerBreak = function(n) {
let dp = new Array(n + 1);
dp[0] = 0;
dp[1] = 1;
dp[2] = 1;
for (let i = 3; i <= n; i++) {
dp[i] = i - 1;
for (let j = 1; j <= Math.floor(i / 2); j++) {
//可选2个数或者多个数
dp[i] = Math.max(dp[i], j * dp[i - j], j * (i - j));
}
}
return dp[n];
};
4.打家劫舍2
首尾房子不能一起劫持。
结果是Math.max(subrob(arr[0, ...n-2]), subrob(arr[1, ...n-1]));
- 打家劫舍3
方法1: 考虑2个数组的dp 包含结点i和不包含结点i
var rob = function(root) {
const f = new Map();
const g = new Map();
const dfs = (node) => {
if (node === null) {
return;
}
dfs(node.left);
dfs(node.right);
f.set(node, node.val + (g.get(node.left) || 0) + (g.get(node.right) || 0));
g.set(node, Math.max(f.get(node.left) || 0, g.get(node.left) || 0) + Math.max(f.get(node.right) || 0, g.get(node.right) || 0));
}
dfs(root);
return Math.max(f.get(root) || 0, g.get(root) || 0);
};
方法2: 中规中矩dp
/**
* 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 {TreeNode} root
* @return {number}
*/
var rob = function(root) {
let map = new Array(10 ** 4);
var postorder = function(node, num) {
if (!node.left && !node.right) {
map[num] = node.val;
return;
}
let a = 0, b = 0;
if (node.left) {
postorder(node.left, num * 2 + 1);
a += map[num * 2 + 1];
if (node.left.left) {
b += map[2 * (2 * num + 1) + 1];
}
if (node.left.right) {
b += map[2 * (2 * num + 1) + 2];
}
}
if (node.right) {
postorder(node.right, num * 2 + 2);
a += map[num * 2 + 2];
if (node.right.left) {
b += map[2 * (2 * num + 2) + 1];
}
if (node.right.right) {
b += map[2 * (2 * num + 2) + 2];
}
}
map[num] = Math.max(node.val + b, a);
}
postorder(root, 0);
return map[0];
};
- 分割等和子集(是否存在和为target的子序列)
/**
* @param {number[]} nums
* @return {boolean}
*/
var canPartition = function(nums) {
if (nums.length == 0) return true;
if (nums.length == 1) return false;
let max = 0, sum = 0;
for (let i = 0; i < nums.length; i++) {
sum += nums[i];
max = Math.max(nums[i], max);
}
if (sum % 2 == 1 || max > sum / 2) return false;
sum = sum / 2;
let dp = new Array(sum + 1).fill(false);
dp[0] = true;
//0~i中选数
for (let i = 1; i < nums.length; i++) {
for (let j = sum; j >= 0; j--) {
dp[j] = dp[j] || (j >= nums[i] && dp[j - nums[i]]);
if (j == sum && dp[j]) return true;
}
}
return false;
};
- 求排列和组合的不同
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
- 硬币问题(求序列中和为target的排列数)
var combinationSum4 = function(nums, target) {
let dp = new Array(target + 1).fill(0);
dp[0] = 1;
for (let i = 1; i <= target; i++) {
for (let j = 0; j < nums.length; j++) {
if (i >= nums[j]) {
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
};
- 买卖股票
双dp状态转移
- 最长递增子序列
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > result) result = dp[i];
}