一、 动态规划专题

①.记忆化递归“搜索”(自顶向下):用一个数组存放(递归子树的)中间结果,并且在下次递归前判断该结果是否计算过,若计算过直接返回,否则就递归,从而实现剪枝的效果,即空间换时间。

②动态规划法(自底向上):找规律,求子问题的解从而得到最终解,写动态转移方程。(找不到动态转移方程就先尝试记忆化弄清问题结构,再反推自底向上)。

     动态规划 “三板斧”

  1. 分治,找到最优子结构 opt[n]=best_of(opt[n-1], opt[n-2], ...)

  2. 状态定义,i 条件时的状态 f[i]

  3. DP方程,也就是递推公式,例如一维的斐波那契递推公式 dp[i] = dp[i-1] + dp[i-2] ; 二维递推公式例如 dp[i][j] = max(dp[i-1][j], dp[i][j-1]),高级的DP公式可能会达到三维甚至三维以上。

     推理方法:

对于一维情况,求 dp[i] 可假设 dp[i-1] 已经求出,需要如何判断才能求出下一项;

对于二维情况,利用递推关系用“填表格”的方式顺序计算,每个 dp 项的值其实等于一个递归子调用的结果,即递归子问题的解。

1.爬楼梯

思路: 同斐波拉契数列

练习:120、64

// 递归树里面有重叠子结构  用记忆数组剪枝
var climbStairs = function(n) {const memo = Array(n+1).fill(-1)if(n==1) return 1if(n==2) return 2if(memo[n]==-1)memo[n] = climbStairs(n-1) + climbStairs(n-2)return memo[n]
};// 有递归子问题 便可动态规划
// 时间、空间复杂度均为O(n)
var climbStairs = function(n) {const dp = []dp[1]=1dp[2]=2for(let i=3; i<=n; i++)dp[i]=dp[i-1]+dp[i-2]return dp[n]
};// 结果只与前俩次有关可继续优化空间 滑动数组
// 时间复杂度为O(n),时间复杂度为 O(1)
var climbStairs = function(n) {   if (n <= 1) return n;   const dp =[]dp[1] = 1dp[2] = 2for (let i = 3; i <= n; i++) {let sum = dp[1] + dp[2]dp[1] = dp[2]dp[2] = sum}return dp[2]
}

2.整数拆分

思路 :遍历分数,分为俩数还是多个数。

练习:279、91、63

// 递归树里面有重叠子结构  存在重复 用记忆数组
var integerBreak = function(n) {const memo = Array(n+1).fill(-1)if(n == 2) return 1if(memo[n]!=-1)return memo[n]let res = 1// 将数字从1开始分 1*(n-1)...i*(n-i)for(let i=1;i<=n-1;i++)//分割为俩个数i*(n-i) 或分割为多个数res = Math.max(res,i*(n-i),i*integerBreak(n-i))memo[n]=resreturn res
};//转为动态规划
// 时间复杂度:O(n^2) 空间复杂度:O(n)
var integerBreak = function(n) {const memo =Array(n+1).fill(-1)memo[2]=1//memo[i]:数字i分割(至少俩部分)后的最大成绩for(let i=3;i<=n;i++)//分割为 俩个数相乘 j*(i-j) for(let j=1;j<i;j++)//或分割为多个数:则将i-j继续分割即memo[i-j](显然memo[i-j]已经算过)memo[i]=Math.max(memo[i], j*(i-j), j*memo[i-j])return memo[n]
};

3.打家劫舍

思路:不偷当前第i家,则结果为后面 i+1 家的结果;否则结果为nums[i] + 后 i+2家的价值

练习:213、337、309

// 记忆化搜索
var rob = function (nums) {if (nums == null || !nums.length) return 0const memo =Array(nums.length).fill(-1)// 考虑抢劫nums[i...length)范围的所有房子const tryRob = (nums, i) => {if (i >= nums.length)  return 0;if(memo[i]!=-1) return memo[i]let res = Math.max(//不偷第i间,结果为从i+1间开始//偷第i间,结果为第i间价值+从i+2间开始tryRob(nums, i + 1),tryRob(nums, i + 2) + nums[i])memo[i] = resreturn res};return  tryRob(nums, 0)
};// 优化dp,空间复杂度O(n)
var rob = function(nums) {const len = nums.length;if(len == 0) return 0const dp = new Array(len)dp[0] = 0dp[1] = nums[0]
// dp[i]:考虑前i间房子的价值 注意下标nums:[0...len-1] dp:[1...len]for(let i = 2; i <= len; i++)        //+nums[i-1]即偷第i间dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i-1] ,)return dp[len]
};//时间复杂度:O(n)、空间复杂度O(1)
var rob = function(nums) {if(!nums.length) { return 0; }let dp0 = 0let dp1 = nums[0]for(let i = 2; i <= nums.length; i++) {const dp2 = Math.max(dp1, dp0 + nums[i-1] )dp0 = dp1dp1 = dp2}return dp1
};

4.  01背包问题

思路:memo[i][j] 表示容量为 j 时,0-i 号物品能获得最大价值;对于第 i 号物品,当前容量若放不下则结果为之前的价值,若放得下则考虑放与不放的价值谁大(放该物品时的价值=不放时容量的价值+该物品的价值)

练习:完全背包问题

     // 时间空间复杂度:n*capacityconst knapsack01 = (weight, value, capacity) => {const n = weight.lengthif (n == 0) return 0const memo = new Array(n).fill(Array(capacity + 1).fill(-1))// 初始化第一行(放第一个物品)for (let k = 0; k <= capacity; k++)memo[0][k] = (k >= weight[0] ? weight[0] : 0)for (let i = 1; i < n; i++)for (let j = 0; j <= capacity; j++) {if (j < weight[i])memo[i][j] = memo[i - 1][j]elsememo[i][j] = Math.max(memo[i-1][j], value[i] + memo[i - 1][j - weight[i]])}return memo[n - 1][capacity]}console.log(knapsack01([1, 2, 3], [6, 10, 12], 5)) //22// 实际上下一行的数据 只依靠上一行,优化后 空间复杂度为O(n)const knapsack01 = (weight, value, capacity) => {const n = weight.lengthif (n == 0) return 0const memo = new Array(2).fill(Array(capacity + 1).fill(-1))// 初始化第一行(放第一个物品)for (let k = 0; k <= capacity; k++)memo[0][k] = (k >= weight[0] ? weight[0] : 0)for (let i = 1; i < n; i++)for (let j = 0; j <= capacity; j++) {if (j < weight[i])memo[i % 2][j] = memo[(i - 1) % 2][j]elsememo[i % 2][j] = Math.max(memo[(i - 1) % 2][j], value[i] + memo[(i - 1) % 2][j - weight[i]])}return memo[(n - 1) % 2][capacity]}console.log(knapsack01([1, 2, 3], [6, 10, 12], 5)) //22//继续优化const knapsack01 = (weight, value, capacity) => {const n = weight.lengthif (n == 0) return 0const memo = new Array(capacity + 1).fill(-1)for (let k = 0; k <= capacity; k++)memo[k] = (k >= weight[0] ? weight[0] : 0)for (let i = 1; i < n; i++)for (let j = capacity; j >= weight[i]; j--) {memo[j] = Math.max(memo[j - 1], value[i] + memo[j - weight[i]])}return memo[capacity]}

5.分割等和子集

思路:类似背包问题,若能分割,则说明数组里的部分和等于sum的一半,即在数组里找到n个数填满容量为sum/2的背包。对于第 i 个数,放或不放只要能填满背包即可(若放,则需要能放得下)

练习:377、474、139、494

        var canPartition = function(nums) {let sum = nums.reduce((pre, cur) => pre + cur);// 和为奇数时,不可能划分成两个和相等的集合if (sum % 2 != 0) return falselet n = nums.length,c = sum / 2let dp = new Array(n + 1).fill(false).map(() => new Array(c + 1).fill(false));// dp[i][j] 表示前i个物品 能不能放满容量为j的背包// base case 背包空间c=0时候,就相当于装满了for (let i = 0; i <= n; i++) dp[i][0] = true;for (let i = 1; i <= n; i++) {for (let j = 1; j <= c; j++) {if (j < nums[i - 1]) {// 背包容量不足,不能装入第 i 个物品dp[i][j] = dp[i - 1][j];} else {// 不装入或装入背包dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];}}}return dp[n][c];};
// 优化var canPartition = function(nums) {let sum = 0for (let n of nums)sum += nif (sum % 2) return falselet n = nums.length,c = sum / 2const memo = Array(c + 1).fill(false)for(let i =0;i<=c;i++) // 看第一个数能不能放入被入背包memo[i] = (nums[0]==i)for (let i = 1; i <= n; i++) // 看后面物品中 能不能放满容量为c的背包for (let j = c; j >= nums[i]; j--)// 第i个物品不放 或放memo[j] = memo[j] || memo[j - nums[i]] // memo[j] |= memo[j-nums[i]]return memo[c]};

6. 最长递增子序列

思路:dp[i] = max(dp[i], dp[j] + 1) for j in [0, i) ,num[ j ]<num[ i ] ,dp[ i ] 表示[ 0...i ]内选择nums[ i ] 可以获得的最长上升子序列的长度。

练习: 376

var lengthOfLIS = function(nums) {const dp = new Array(nums.length).fill(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);}return Math.max(...dp);};

7.最长公共子序列

思路:

a. 分治 LCS[i] = LCS(最后一个字母相同)+ LCS(最后一个字母不相同)

b. 状态定义 f[ i ][ j ] 表示第一个字符串索引 0-i 构成的子串与第二个字符串索引 0-j 子串的最长公共序列

c. DP方程:

if text1[i-1] == text2[i-1]:
        dp[i][j] = dp[i-1][j-1] + 1
    else:
        dp[i][j] = max(dp[i-1][j], dp[i][j-1])

时间、空间复杂度:O(mn)

var longestCommonSubsequence = function(text1, text2) {let dp = Array.from(Array(text1.length+1),() => Array(text2.length+1).fill(0));for(let i = 1; i <= text1.length; i++) {for(let j = 1; j <= text2.length; 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[text1.length][text2.length];
};

8. 零钱兑换

思路: dp[j] = min(dp[j - coins[i]] + 1, dp[j]) , dp[j]:凑足总额为 j 所需最少硬币个数为dp[j],对于第 i 个硬币拿与不拿,看最后总数谁少。

        var coinChange = function(coins, amount) {let dp = new Array(amount + 1).fill(Infinity);dp[0] = 0;for (let i = 1; i <= amount; i++)for (let coin of coins)if (i >= coin )dp[i] = Math.min(dp[i], dp[i - coin] + 1);return dp[amount] === Infinity ? -1 : dp[amount];}

9.不同路径

思路:要到达终点[m-1][n-1],有俩种方式从左边来即[i-1][j]或从上边来即[i][j-1]。由此可得动态转移方程:dp[i][j] = dp[i-1][j] + dp[i][j-1] (dp[i][j] 表示从起点到任意位置的路径数,d[i][0]为1理由是从[0, 0]的位置到[i, 0]的路径只有一条,同理d[0][j])

时间、空间复杂度:O(m * n)

a. 分治(子问题) path = path(top) + path(left)

b. 状态定义 f[i, j] 表示第i行第j列的不同路径数

c. DP方程 dp[i][j] = dp[i-1][j] + dp[i][j-1]

// dp[i][j]为每行每列任意点的不同路径数
var uniquePaths = function(m, n) {const dp=new Array(m).fill(1).map(()=>Array(n).fill(1))for(i=1;i<m;i++)for(j=1;j<n;j++)dp[i][j]=dp[i-1][j]+dp[i][j-1]return dp[m-1][n-1]
};//优化空间(O(n)):只需要保存每一行任意点的路径数即可,DP方程可以更新为 dp[j] = dp[j] + dp[j-1]`
var uniquePaths = function(m, n) {const dp=new Array(n).fill(1) for(i=1;i<m;i++)for(j=1;j<n;j++)dp[j] = dp[j] + dp[j-1]return dp[n-1]
};

二、22年各厂面试题整理

tx:  移掉 K 位数字

思路:贪心,遍历字符串维护一个栈,如果当前数字小于前一个,则移除前一个元素同时k--,并加入当前元素;若当前元素为0且栈为空则不入栈。

时间、空间复杂度:O(n)

var removeKdigits = function(num, k) {const stk = []for (const digit of num) {while (k && stk.length && stk[stk.length-1] > digit) {stk.pop()k --}if (digit == '0' && stk.length == 0) //如果当前元素为0,且栈为空,则跳过continuestk.push(digit)}while (k-- > 0) stk.pop()return stk.length == 0 ? "0" : stk.join('')
}

leetcode专题训练笔记相关推荐

  1. leetcode专题训练 77. Combinations

    方法1 这道题用递归的方法即可.我的想法是,k=n时的所有排列组合result,一定是由k=n-1时的排列组合tmp和一个新数组成. class Solution:def solve(self, be ...

  2. C++——素数(质数)专题训练4

    作者有话说:时隔一年的质数专题训练更新啦~  近期会多多更新笔记!!! 1255:求质数 时间限制: 1.000 Sec  内存限制: 128 MB 题目描述 输入正整数n,输出不大于n的最大质数 输 ...

  3. 数据结构专题-学习笔记:李超线段树

    数据结构专题 - 学习笔记:李超线段树 1. 前言 2. 详解 3. 应用 4. 总结 5. 参考资料 1. 前言 本篇博文是博主学习李超线段树的学习笔记. 2020/12/21 的时候我在 线段树算 ...

  4. 莫比乌斯反演专题学习笔记

    莫比乌斯反演专题学习笔记 本文记录一些和莫反有关的内容的笔记 可能存在诸多谬误,阅读时请谨慎分析 若发现文中有谬误,如您愿意,恳请您向我指出,不胜感激! 为什么要学莫比乌斯反演? 解决一类与狄利克雷卷 ...

  5. 卷进大厂系列之LeetCode刷题笔记:二分查找(简单)

    LeetCode刷题笔记:二分查找(简单) 学算法,刷力扣,加油卷,进大厂! 题目描述 涉及算法 题目解答 学算法,刷力扣,加油卷,进大厂! 题目描述 力扣题目链接 给定一个 n 个元素有序的(升序) ...

  6. 可由一个尾指针唯一确定的链表有_极客算法训练笔记(三),链表详细图解,别再逃避了朋友...

    目录 缓存引爆链表 链表单链表双向链表循环链表双向循环链表 LinkedHashMap实现LRU缓存,源码解析(JDK1.8) 算法 爬楼梯 算法 反转链表 算法 链表环检测 缓存引爆链表 存储结构 ...

  7. 严蔚敏算法约瑟夫环_极客算法训练笔记(三),链表详细图解,别再逃避了朋友...

    目录 缓存引爆链表 链表 单链表 双向链表 循环链表 双向循环链表 LinkedHashMap实现LRU缓存,源码解析(JDK1.8) 算法 爬楼梯 算法 反转链表 算法 链表环检测 缓存引爆链表 存 ...

  8. 数学/数论专题-学习笔记:狄利克雷卷积

    数学/数论专题-学习笔记:狄利克雷卷积 1. 前言 2. 一些基础函数 3. 积性函数 4. 狄利克雷卷积 5. 总结 6. 参考资料 1. 前言 狄利克雷卷积,是学习与继续探究 μ\muμ 函数和 ...

  9. QLU_ACM 2021 专题训练(一)题解 [暴力、排序、贪心、二分]

    欢迎大家来做QLU_ACM寒假组织的专题训练

最新文章

  1. Ubuntu配置伪分布式hadoop时报错:localhost: mkdir: 无法创建目录/usr/local/hadoop/logs: 权限不够...
  2. 华为eNsp模拟器安装常见报错汇总
  3. Confluence 6 升级完成后的检查
  4. 深度学习tensorflow框架的会话
  5. 使用Redis创建分布式锁
  6. discuz设置用户每天回帖数_[建站教程]Discuz3.4设置QQ互联登陆教程
  7. linux的基础知识——信号的四要素和kill
  8. m5310模组数据上传至onenet_彻底火了的NB-IoT突破1亿大关(附最新芯片和模组厂)!...
  9. Ridge regression
  10. 读“我为什么不要应届毕业生”
  11. Linux部署启动服务脚本
  12. QT之隐藏任务栏图标
  13. 7.20-7.26 字节推荐算法(DATA-EDU)5道面试题分享
  14. 利用k8s集群部署第一个容器化应用
  15. iOS开发-沙盒(sandbox)机制
  16. Distributed System 基础(四)隐私性(Privacy)
  17. 电视剧中的程序员,是真的敲代码吗?
  18. 中学计算机教材,人教版初中信息技术教材梳理
  19. 通过升级cmake版本解决NDK编译报错:no member named ‘signbit‘ in the global namespace;
  20. matlab使用yalmip工具箱

热门文章

  1. python 字典默认会引用 除非深拷贝
  2. Fedora ssh服务,防火墙服务设置
  3. 对$()与``区别的理解
  4. xml字符串转xml对象,xml对象转json对象
  5. redis主从配置转
  6. Ext JS 6开发实例(三) :主界面设计
  7. 这两天说到的苹果软件中毒是个什么情况?
  8. 苹果原生NSURLSession的上传和下载
  9. [Android实例] 天天动听 悬浮歌词(迷你歌词)效果解读
  10. 【调试手段】linux下valgrind内存泄露检查