回溯、贪心、动态规划的思路是通过大量做题培养的,不是说几天就能掌握,而且题目不会告诉你使用哪个算法。坚持做题。。。
文章目录
动态规划
展示代码的前提,先给出状态转移矩阵的定义
基础题(使用转移方程)
70.爬楼梯(简单)
746.使用最小花费爬楼梯 (简单)
62.不同路径:网格的左上角到右下角共有多少条不同的路径
63.不同路径 II:网格中的障碍物和空位置分别用 1 和 0 来表示。
要处理单行和单列的情况
如果单元格为阻碍物则不变,否则按前面的赋值。
var uniquePathsWithObstacles = function(obstacleGrid) {
var dp = obstacleGrid.map(arr => arr.map(val => val===1? 0 : 1))
// console.log(dp)
var row = obstacleGrid.length, col = obstacleGrid[0].length
for(let i = 1; i<row; i++){
dp[i][0] && (dp[i][0] = dp[i-1][0])
}
for(let j = 1; j<col; j++){
dp[0][j] && (dp[0][j]= dp[0][j-1])
}
for(let i=1; i<row; i++) {
for(let j=1; j<col; j++) {
if(dp[i][j]) {
dp[i][j] = dp[i-1][j] + dp[i][j-1]
}
}
}
// console.log(dp)
return dp[row-1][col-1]
};
343.整数拆分(中等)
给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。
var integerBreak = function(n) {
var dp = new Array(n+1).fill(0)
dp[2] = 1 // 整数2开始才能拆分
for(let i=3; i<=n; i++) {
for(let j=1; j<=i-2; j++) {
dp[i] = Math.max(dp[i], j * (i - j), j * dp[i - j])
}
}
return dp[n]
};
96.不同的二叉搜索树 (中等)
做过的题目
dp[i] : i 个节点时的种类数
思想:
谁做根节点,遍历 J 作为根节点;
J 为根节点的种数谁决定,左子树数量 * 右子树数量;
遍历1—n个节点的种数。
var numTrees = function(n) {
var dp = new Array(n+1).fill(0)
dp[0] = 1
for(let i=1; i<=n; i++) {
for(let j=1; j<=i; j++) {
dp[i] += dp[j-1] * dp[i-j]
}
}
return dp[n]
};
动态规划——子序列问题
300.最长递增子序列(已做、入门)
dp[i]:第 i 个作为尾节点的最长递增序列长度。
var lengthOfLIS = function(nums) {
var dp = new Array(nums.length).fill(1)
for(let i=1; i<nums.length; i++) {
for(let j=0; j<i; j++) {
if(nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1)
}
}
}
return Math.max(...dp)
};
1143.最长公共子序列 (旧题)
dp[i][j]:字符串a前i个与字符串b前j个的最长公共子序列长度、
dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
var longestCommonSubsequence = function(text1, text2) {
var row = text1.length, col = text2.length
var dp = new Array(row+1).fill(0).map(val => new Array(col+1).fill(0))
for(let i=1; i<=row; i++) {
for(let j=1; j<=col; j++) {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1])
if(text2[j-1] == text1[i-1]){
dp[i][j] = Math.max(dp[i][j], dp[i-1][j-1] + 1)
}
}
}
return dp[row][col]
};
1035.不相交的线 (中等)fail
这么分析完之后,大家可以发现:本题说是求绘制的最大连线数,其实就是求两个字符串的最长公共子序列的长度!
那么本题就和我们刚刚讲过的这道题目动态规划:1143.最长公共子序列 (opens new window)就是一样一样的了。
(卧槽)
674.最长连续递增序列 (简单)
与300.最长递增子序列最大的区别在于“连续”
dp[i]:以下标i为结尾的数组的连续递增的子序列长度为dp[i]。
718.最长重复子数组 (中等)
给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。
注意题目中说的子数组,其实就是连续子序列。
dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。
注意这里的dp含义也不同于1143.最长公共子序列,这里的子数组是包括 i-1 的。
var findLength = function(nums1, nums2) {
var [row, col] = [nums1.length, nums2.length]
var dp = new Array(row+1).fill(0).map(val => new Array(col+1).fill(0))
var res = 0
for(let i=1; i<=row; i++) {
for(let j=1; j<=col; j++) {
if(nums1[i-1] === nums2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1
res = Math.max(dp[i][j], res)
}
}
}
return res
};
53.最大子数组和 (已做、简单)
dp[i]:包括下标i之前的最大连续子序列和为dp[i]。
647.回文子串 (中等)
输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
暴力:两层for循环,遍历区间起始位置和终止位置,然后判断这个区间是不是回文。
方法一:双指针法
首先确定回文串,就是找中心然后向两边扩散看是不是对称的就可以了。
在遍历中心点的时候,要注意中心点有两种情况。
一个元素可以作为中心点,两个元素也可以作为中心点。
var countSubstrings = function(s) {
var len = s.length, res = 0
function count(l, r) {
let temp = 0
while(l>=0 && r<len && s[l]==s[r]) {
l--
r++
temp++
}
return temp
}
for(let i = 0; i<len; i++) {
res += count(i, i)
res += count(i, i+1)
}
return res
};
方法二:动态规划 (fail)
布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
因为dp[i][j]的定义,所以j一定是大于等于i的,那么在填充dp[i][j]的时候一定是只填充右上半部分。
最难的是遍历顺序:
根据dp[i + 1][j - 1]是否为true,在对dp[i][j]进行赋值true的。dp[i + 1][j - 1] 在 dp[i][j]的左下角。
所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的。
有的代码实现是优先遍历列,然后遍历行,其实也是一个道理,都是为了保证dp[i + 1][j - 1]都是经过计算的。
var countSubstrings = function(s) {
var len = s.length, res = 0
var dp = new Array(len).fill(0).map(val => new Array(len).fill(false))
for(let i=len-1; i>=0; i--) {
for(let j=i; j<=len-1; j++) {
if(s[i] === s[j]) {
if((j - i) <= 1) {
res++
dp[i][j] = true
} else if(dp[i+1][j-1]) {
res++
dp[i][j] = true
}
}
}
}
return res
};
516.最长回文子序列 (中等)fail
回文子串是要连续的,回文子序列可不是连续的! 回文子串,回文子序列都是动态规划经典题目。
思路其实是差不多的,但本题要比求回文子串简单一点,因为情况少了一点。
动态规划
dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]。
var longestPalindromeSubseq = function(s) {
var len = s.length
var dp = new Array(len).fill(0).map(val => new Array(len).fill(0))
for(let i=len-1; i>=0; i--) {
for(let j=i; j<=len-1; j++) {
if(s[i]===s[j]) {
if((j-i)<=1) {
dp[i][j] = j - i + 1
} else {
dp[i][j] = dp[i+1][j-1] + 2
}
} else {
dp[i][j] = Math.max(dp[i][j-1], dp[i+1][j])
}
}
}
return dp[0][len-1]
};
5.最长回文子串(中等、已做-中心扩展算法)
中心扩展
function palindrome(s, l, r) {
while (l >= 0 && r < s.length && s[l] == s.charAt(r)) {
// 向两边展开
l--; r++;
}
// 左闭右开
return s.slice(l + 1, r);
}
var longestPalindrome = function(s) {
let ans = ""
for (let i=0; i<s.length; i++) {
let str1 = palindrome(s, i, i)
let str2 = palindrome(s, i, i+1)
ans = ans.length < str1.length ? str1 : ans
ans = ans.length < str2.length ? str2 : ans
}
return ans;
};
动态规划
var longestPalindrome = function(s) {
var len = s.length, res = 0, temp = null
var dp = new Array(len).fill(0).map(val => new Array(len).fill(false))
for(let i=len-1; i>=0; i--) {
for(let j=i; j<=len-1; j++) {
if(s[i] === s[j]) {
if((j - i) <= 1 || dp[i+1][j-1]) {
if(res < (j - i + 1)) {
res = j - i + 1
temp = s.slice(i, j+1)
}
dp[i][j] = true
}
}
}
}
return temp
};