leetcode--动态规划


良心推荐一篇好文章,博主也是看了这篇文章弄懂了动态规划。
告别动态规划,连刷 40 道题,我总结了这些套路,看不懂你打我(万字长文)
以下结题未写最优解,读者可自行思考。

面试题08.01 三步问题

三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。
实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。
示例1:
输入:n = 3 输出:4 说明: 有四种走法
示例2:
输入:n = 5 输出:13

/**
 * @param {number} n
 * @return {number}
 */
var waysToStep = function(n) {
    //初始化数组
    let dp = new Array(n+1).fill(0);//表示第n个台阶的方式
    //定义数组各元素之间的关系dp[i] = dp[i-1]+dp[i-2]+dp[i-3];
    //初始化
    dp[1] = 1;
    dp[2] = 2;
    dp[3] = 4;
    for (let i = 4; i <= n; i++) {
        dp[i] = dp[i-1]+dp[i-2]+dp[i-3];  
    }
    return dp[n] % 1000000007;
};

53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    /*
    解法一:
    let cursum = nums[0],max = nums[0];
    for (let i=1;i<nums.length;i++){
        if(cursum > 0){
            cursum += nums[i];
        }else{
            cursum = nums[i];
        }
        if(cursum > max){
            max = cursum;
        }
    }
    return max;
    */
    // 解法二:动态规划
    //定义数组 dp[i] 表示长度为i的数组的最大子序列和
    let dp = new Array(nums.length+1);
    //各元素之间的关系 dp[i] = Math.max(nums[i-1],nums[i-1]+dp[i-1])
    //初始化元素
    dp[0] = nums[0];
    dp[1] = nums[0];
    for (let i=2;i<=nums.length;i++){
        dp[i] = Math.max(nums[i-1],nums[i-1]+dp[i-1]);
    }
    return Math.max(...dp);
};

62.不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

问总共有多少条不同的路径?

在这里插入图片描述
例如,上图是一个7 x 3 的网格。有多少可能的路径?

示例 1:

输入: m = 3, n = 2 输出: 3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向右 -> 向下
  2. 向右 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向右 示例 2:

输入: m = 7, n = 3 输出: 28

/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) {
    //定义数组元素的含义 表示从起点走到[i,j]有dp[i][j]种路径
    var dp = new Array(m);
    for (let i = 0; i < m; i++) {
        dp[i] = new Array(n);
        for (let j = 0; j < n; j++) {
            dp[i][j] = 0;      
        } 
    }
    //找到元素之间的关系 dp[i][j] = dp[i-1][j]+dp[i][j-1]
    //找到初始值
    for (let i = 0; i < m; i++) {
        dp[i][0] = 1;    
    }
    for (let j = 0; j < n; j++) {
        dp[0][j] = 1;    
    }
    //计算dp[i][j] = dp[i-1][j]+dp[i][j-1]
    for (let i = 1; i < m; i++) {
        for (let j = 1; j < n; j++) {
            dp[i][j] = dp[i-1][j]+dp[i][j-1];       
        }   
    }
    // console.log(dp[m-1][n-1])
    return dp[m-1][n-1];
};

63. 不同路径 II

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? 网格中的障碍物和空位置分别用 1 和 0 来表示。

说明:m 和 n 的值均不超过 100。

示例 1:

输入: [ [0,0,0], [0,1,0], [0,0,0] ] 输出: 2 解释: 3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:

  1. 向右 -> 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右 -> 向右
/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
var uniquePathsWithObstacles = function(obstacleGrid) {
    var m = obstacleGrid.length;
    var n = obstacleGrid[0].length;
    //定义数组含义 dp[i][j]表示从起点到[i][j]坐标的路径总数
    var dp = new Array(m);
    for (let i = 0; i < m; i++) {
        dp[i] = new Array(n);
        for (let j = 0; j < n; j++) {
            dp[i][j] = 0;      
        } 
    }
    // 表示数组之间的元素关系 dp[i][j] = dp[i-1][j]+dp[i][j-1];
    //初始化
    for (let i = 0; i < m; i++) {//最左边
        if(obstacleGrid[i][0] != 1){
            dp[i][0] = 1;
        } else {
            dp[i][0] = 0;
        }  
    }
      for (let j = 0; j < n; j++) {//最上边
        if(obstacleGrid[0][j] != 1){
            dp[0][j] = 1;
        }  else{
            dp[0][j] = 0;
        }  
    }
    //计算最终值
    for (let i = 1; i < m; i++) {
        for (let j = 1; j < n; j++) {
            if(obstacleGrid[i][j] != 1){
                dp[i][j] = dp[i-1][j]+dp[i][j-1];
            } else {
                dp[i][j] = 0;
            }
        } 
    }
    console.log(dp[m-1][n-1]);
    return dp[m-1][n-1];
};

64.最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入: [ [1,3,1], [1,5,1], [4,2,1] ] 输出: 7 解释: 因为路径 1→3→1→1→1
的总和最小。

/**
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
    var m = grid.length;
    var n = grid[0].length;
    //定义数组含义 dp[i][j]表示起点到[i][j]的最小路径和
    var dp = new Array(m);
    for (let i = 0; i < m; i++) {
        dp[i] = new Array(n);
        for (let j = 0; j < n; j++) {
            dp[i][j] = 0;      
        } 
    }
    dp[0][0] = grid[0][0];
    //寻找数组元素之间的关系dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + arr[i][j];
    //寻找初始值
    for (let i = 1; i < m; i++) { //最左边那一列
        dp[i][0] = dp[i-1][0]+grid[i][0];     
    }
    for (let j = 1; j < n; j++) { //最上面那一行
        dp[0][j] = dp[0][j-1]+grid[0][j];    
    }
    //计算得出结果
    for (let i = 1; i < m; i++) {
       for (let j = 1; j < n; j++) {
            dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1]) + grid[i][j];   
       }   
    }
    // console.log(dp[m-1][n-1]);
    return dp[m-1][n-1];
};

70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶 示例 2:

输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶
/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    //定义数组dp[i]表示爬i个台阶有的方法
    let dp = new Array(n+1);
    //找出数组元素之间的关系 dp[i] = dp[i-1]+dp[i-2];
    //初始化
    dp[0] = 0;
    dp[1] = 1;
    dp[2] = 2;
    for (let i = 3;i <= n; i++){
        dp[i] = dp[i-1]+dp[i-2];
    }
    return dp[n];
};

96.不同的二叉搜索树

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

示例: 输入: 3 输出: 5 解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

1         3     3      2      1
\       /     /      / \      \
 3     2     1      1   3      2
/     /       \                 \   
2    1       2                   3
/**
 * @param {number} n
 * @return {number}
 */
var numTrees = function(n) {
    // 定义数组 dp[n]表示以 1 ... n 为节点组成的二叉搜索树有dp[n]种
    var dp = new Array(n+1).fill(0);
    //写出数组各元素之间的关系 dp[i] += dp[i] + dp[i-j-1];
    //初始化
    dp[0] = 0;dp[1] = 1;
    //求出dp[n]
    for(let i=2;i<=n;i++){
        for(let j=0;j<i;j++){//j表示左子树的节点
            dp[i] += dp[j] * dp[i-j-1];
        }
    }
    return dp[n];

};

121. 买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

注意:你不能在买入股票前卖出股票。

示例 1:

输入: [7,1,5,3,6,4] 输出: 5 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 =
6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 示例 2:

输入: [7,6,4,3,1] 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

/**
 * 思路:找到最小的,每次比较和最小值的差值,保存这个最大的差值
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    let min = prices[0],max = 0;
    prices.forEach(item => {
        if(item < min) {
            min = item;
        }
        if(item - min > max) {
            max = item - min;
        }
    });
    return max;
};

198.打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,
如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。 示例 2:

输入:[2,7,9,3,1] 输出:12 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5
号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function(nums) {
    let len = nums.length;
    if(len === 0) return 0;
    else if(len === 1) return nums[0];
    else{
        //定义数组 dp[i]表示偷盗i家所获得的最多的财产
        let dp = new Array(len + 1);
        // 数组各元素之间的关系dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
        //初始化数组
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0],nums[1]);
        //计算dp[i]
        for (let i = 2;i< len;i++){
            dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[len-1];
    }
};

303. 区域和检索 - 数组不可变

给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。

示例:

给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()

sumRange(0, 2) -> 1 sumRange(2, 5) -> -1 sumRange(0, 5) -> -3

/**
 * @param {number[]} nums
 */
var NumArray = function(nums) {
    this.nums = nums;
};

/**
 * @param {number} i
 * @param {number} j
 * @return {number}
 */
NumArray.prototype.sumRange = function(i, j) {
    let sum = 0;
    for (let k = i ; k <= j ;k++){
        sum += this.nums[k];
    }
    return sum;
};

392. 判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度
<=100)。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

示例 1: s = “abc”, t = “ahbgdc”

返回 true.

示例 2: s = "axc", t = "ahbgdc"

返回 false.

后续挑战 :

如果有大量输入的 S,称作S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T
的子序列。在这种情况下,你会怎样改变代码?

/**
 * 思路:利用栈,将每一个字符压入栈,然后一一比对,一样的就拿出来,直到最后栈为空,则返回true,否则返回false
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isSubsequence = function(s, t) {
    //解法一:
    /*

    if(s.length > t.length) return false;
    if(s.length === 0 && t.length === 0) return true;
    var sstack = s.split('');
    var tstack = t.split('');
    for (let i=0;i<tstack.length;i++){
        if(tstack[i] === sstack[0]){
            sstack.shift();
        }
        if(sstack.length === 0){
            return true;
        }
    }
    if(sstack.length === 0){
        return true;
    }
    return false;
    */
     //解法二:贪心算法
    if(s.length > t.length) return false;
    if(s.length === 0 && t.length === 0) return true;
    let spos = 0,tpos = 0,slen = s.length,tlen = t.length;
    while (tpos<tlen){
        if(t[tpos] === s[spos]) spos++;
        if(spos === slen) return true;
        tpos++;
    }
    return false;

};

746.使用最小花费爬楼梯

数组的每个索引作为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i](索引从0开始)

每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。

您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。

示例 1:

输入: cost = [10, 15, 20] 输出: 15 解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。
示例 2:

输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] 输出: 6 解释:
最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。 注意:

cost 的长度将会在 [2, 1000]。 每一个 cost[i] 将会是一个Integer类型,范围为 [0, 999]。

/**
 * @param {number[]} cost
 * @return {number}
 */
var minCostClimbingStairs = function(cost) {
    //定义数组  表示走到第i个台阶所花费的最小精力
    let n = cost.length;
    let dp = new Array(n+1).fill(0);
    //确定数组元素之间的关系 dp[i] = Math.min(dp[i-2],dp[i-1])+cost[i];
    //初始化
    dp[0] = cost[0];
    dp[1] = cost[1];
    for (let i = 2; i <= n; i++) {
        if(i === n) { //如果最后一步落在了最后一个台阶上,最后一个台阶花费值不计算在内
            dp[i] = Math.min(dp[i-2],dp[i-1]);
        }else{
            dp[i] = Math.min(dp[i-2],dp[i-1])+cost[i];
        }  
    }
    return dp[n];
};

1205.除数博弈

爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。

最初,黑板上有一个数字 N 。在每个玩家的回合,玩家需要执行以下操作:

选出任一 x,满足 0 < x < N 且 N % x == 0 。 用 N - x 替换黑板上的数字 N 。
如果玩家无法执行这些操作,就会输掉游戏。

只有在爱丽丝在游戏中取得胜利时才返回 True,否则返回 false。假设两个玩家都以最佳状态参与游戏。

/**
 * @param {number} N
 * @return {boolean}
 */
var divisorGame = function(N) {
    if(N ===1 ) {
        return false;
    }
    if(N===2){
        return true;
    }
    //定义数组 数组代表为N时的boolean值
    var dp = new Array(N+1);
    dp[1] = false;
    dp[2] = true;
    for (let i = 3; i <= N; i++) {
        dp[i] = false;
        for (let j = 1; j < i; j++) {
            if(i % j === 0 && !dp[i-j]){
                dp[i] = true;
                break;
            }  
        } 
    }
    return dp[N];
    //确定数组各元素之间的关系
    //初始化数组
    //计算并返回结果
};

718.最长重复子数组

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:

输入: A: [1,2,3,2,1] B: [3,2,1,4,7] 输出:3 解释: 长度最长的公共子数组是 [3, 2, 1] 。

/**
 * @param {number[]} A
 * @param {number[]} B
 * @return {number}
 */
var length = 0;
var findLength = function(a, b) {
   var lena = a.length,lenb = b.length ,max = 0;;
   var def = new Array(lena + 1);
   for (let i = 0; i < def.length; i++) {
      def[i] = new Array(lenb+1);
      for (let j = 0; j < lenb + 1; j++) {
          def[i][j] = 0;
          
      }   
   }
   for (let i = 1; i <= lena; i++) {
       for (let j = 1; j <= lenb; j++) {
           if(a[i-1] === b[j-1]){
               def[i][j] = def[i-1][j-1]+1;
           } 
            max = Math.max(def[i][j],max);
       }   
    }
    return max;
};

面试题 17.16. 按摩师

一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,
因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。

示例 1:

输入: [1,2,3,1] 输出: 4 解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。 示例 2:

输入: [2,7,9,3,1] 输出: 12 解释: 选择 1 号预约、 3 号预约和 5 号预约,总时长 = 2 + 9 + 1 =
12。 示例 3:

输入: [2,1,4,5,3,1,1,3] 输出: 12 解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2

  • 4 + 3 + 3 = 12。
/**
 * @param {number[]} nums
 * @return {number}
 */
var massage = function(nums) {
    // 定义数组 dp[i]表示长度为i的数组总的预约时间
    let len = nums.length;
    if(len === 0) return 0;
    if(len === 1) return nums[0];
    let dp = new Array(len+1).fill(0);
    // 找到各元素之间的关系 dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
    //初始化
    dp[0] = nums[0];
    dp[1] = Math.max(nums[0],nums[1]);
    for (let i = 2; i < len;i++){
        dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
    }
    return dp[len-1];
};

先写到这里吧,有时间了再更新~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值