十三、leetcode - 动态规划(JS)

281 阅读7分钟

509. 斐波那契数

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

给你 n ,请计算 F(n) 。

输入: 4
输出: 3
解释: F(4) = F(3) + F(2) = 2 + 1 = 3
dp初始化
[ 0, 1 ]
dp打印
[ 0, 1, 1 ] 
[ 0, 1, 1, 2 ] 
[ 0, 1, 1, 2, 3 ]
/**
 * @param {number} n
 * @return {number}
 */
var fib = function(n) {
    let dp = [0, 1];
    for(let i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
        console.info(dp);
    }
    return dp[n];
};

70. 爬楼梯

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

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

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

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶
dp[i]:表示到达i阶台阶时的方法数
dp[i]的值只来自这两种:dp[i] = dp[i-1] + dp[i-2];
    ①从倒数一个台阶到达
    ②从倒数两个台阶到达
输入: 5
输出: 8
初始化
[ null, 1, 2 ] 
dp打印
[ null, 1, 2, 3 ] 
[ null, 1, 2, 3, 5 ] 
[ null, 1, 2, 3, 5, 8 ]
/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    let dp = [null, 1, 2];
    for(let i = 3; i <= n; i++) {
        dp[i] = dp[i-1] + dp[i-2];
        console.info(dp);
    }
    return dp[n];
};

746. 使用最小花费爬楼梯

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

每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。

请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。

输入: cost = [10, 15, 20]
输出: 15
解释: 最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。
输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。
dp[i]:表示到达第i个台阶所花费的最少体力为dp[i]
初始化
[ 1, 100 ]
dp打印
[ 1, 100, 2 ] 
[ 1, 100, 2, 3 ] 
[ 1, 100, 2, 3, 3 ] 
[ 1, 100, 2, 3, 3, 103 ] 
[ 1, 100, 2, 3, 3, 103, 4 ] 
[ 1, 100, 2, 3, 3, 103, 4, 5 ] 
[ 1, 100, 2, 3, 3, 103, 4, 5, 104 ] 
[ 1, 100, 2, 3, 3, 103, 4, 5, 104, 6 ]
/**
 * @param {number[]} cost
 * @return {number}
 */
var minCostClimbingStairs = function(cost) {
    const dp = [ cost[0], cost[1] ];
    for (let i = 2; i < cost.length; i++) {
        dp[i] = Math.min(dp[i -1] + cost[i], dp[i - 2] + cost[i]);
        console.info(dp);
    }
    return Math.min(dp[cost.length - 1], dp[cost.length - 2]);
};

62. 不同路径

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

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

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

image.png

输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
初始化
[   [ 1, 1 ], 
  [ 1, 0 ], 
  [ 1, 0 ] 
] `
dp打印
[   [ 1, 1 ], 
  [ 1, 2 ], 
  [ 1, 0 ] 
] 
[   [ 1, 1 ], 
  [ 1, 2 ], 
  [ 1, 3 ] 
]
dp[i][j]:表示到达i,j位置时的路径
dp[i][j]的值只来自两个方向:dp[i][j] = dp[i-1][j] + dp[i][j-1];
    ①从i,j位置上方到达
    ②从i,j位置左侧到达
/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) {
    let dp = new Array(m).fill().map(()=>new Array(n).fill(0));
    for(let i = 0; i < m; i++) dp[i][0] = 1;
    for(let i = 0; i < n; i++) dp[0][i] = 1;
    console.info(dp);
    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.info(dp);
        }
    }
    return dp[m-1][n-1];
};

63. 不同路径 II

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

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

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。 网格中的障碍物和空位置分别用 1 和 0 来表示。

image.png

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
初始化:
[   [ 1, 1, 1 ], 
  [ 1, 0, 0 ], 
  [ 1, 0, 0 ] 
] 
dp打印
[   [ 1, 1, 1 ], 
  [ 1, 0, 0 ], 
  [ 1, 0, 0 ] 
] 
[   [ 1, 1, 1 ], 
  [ 1, 0, 1 ], 
  [ 1, 0, 0 ] 
] 
[   [ 1, 1, 1 ], 
  [ 1, 0, 1 ], 
  [ 1, 1, 0 ] 
] 
[   [ 1, 1, 1 ], 
  [ 1, 0, 1 ], 
  [ 1, 1, 2 ] 
]
/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
var uniquePathsWithObstacles = function(obstacleGrid) {
    let m = obstacleGrid.length;
    let n = obstacleGrid[0].length;
    let dp = new Array(m).fill().map(()=>new Array(n).fill(0));
    for(let i = 0; i < m && obstacleGrid[i][0] === 0; i++) dp[i][0] = 1;
    for(let i = 0; i < n && obstacleGrid[0][i] === 0; i++) dp[0][i] = 1;
    console.info(dp);
    for(let i = 1; i < m; i++) {
        for(let j = 1; j < n; j++) {
            dp[i][j] = obstacleGrid[i][j] === 1 ? 0 : dp[i-1][j] + dp[i][j-1];
            console.info(dp);
        }
    }
    return dp[m-1][n-1];
};

118. 杨辉三角

给定一个非负整数 numRows 生成「杨辉三角」的前 numRows 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
[ [] ] 
[ [ 1 ] ] 
------- 
[ [ 1 ], [] ] 
[ [ 1 ], [ 1, 1 ] ] 
------- 
[ [ 1 ], [ 1, 1 ], [] ] 
[ [ 1 ], [ 1, 1 ], [ 1, 2, 1 ] ] 
------- 
[ [ 1 ], [ 1, 1 ], [ 1, 2, 1 ], [] ] 
[ [ 1 ], [ 1, 1 ], [ 1, 2, 1 ], [ 1, 3, 3, 1 ] ] 
------- 
[ [ 1 ], [ 1, 1 ], [ 1, 2, 1 ], [ 1, 3, 3, 1 ], [] ] 
[ [ 1 ], [ 1, 1 ], [ 1, 2, 1 ], [ 1, 3, 3, 1 ], [ 1, 4, 6, 4, 1 ] ] 
-------
/**
 * @param {number} numRows
 * @return {number[][]}
 */
var generate = function(numRows) {
    let dp = [];
    for (let i = 0; i < numRows; i++) {
        dp[i] = [];
        console.info(dp);
        for(let j = 0; j <= i; j++){
            if(j ===  0  || j === i) {
                // 是杨辉三角的两边时,直接赋值为 1
                dp[i][j] = 1;
            } else {
                // 否则当前值为左上方和右上方的值的和
                dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
            }
        }      
        console.info(dp);
        console.info('-------');
    }
    return dp;
};
dp[i]:表示分拆数字i可以得到的最大乘积为dp[i]

647. 回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

输入: s = "aaa"
输出: 6
解释: 6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
/**
 * @param {string} s
 * @return {number}
 */
var countSubstrings = function(s) {
    let res = 0;
    let len = s.length;
    // dp[i][j]:表示区间范围[i,j]的子串是否是回文子串
    // 如果是dp[i][j]为true,否则为false。初始化为false
    let dp = new Array(len).fill().map(() => new Array(len).fill(false));
    // 从下往上、从左往右遍历。保证dp[i + 1][j - 1]都是经过计算的
    for (let i = len - 1; i >= 0; i--) {  
        for (let j = i; j < len; j++) {
            if (s[i] == s[j]) {
                // 当字符串为1个或2个时,如a或者aa时,那就时一个回文子串
                // 否则看它内部是否也是回文子串
                if (j - i <= 1) { 
                    res++;
                    dp[i][j] = true;
                } else if (dp[i + 1][j - 1]) { 
                    res++;
                    dp[i][j] = true;
                }
            }
        }
    }
    return res;
};
dp1是辅助代码,用来打印,实际ac代码可以去掉
[   [ false, false, false ], 
  [ false, false, false ], 
  [ false, false, true ] 
] 
[   [ false, false, false ], 
  [ false, false, false ], 
  [ false, false, 'a' ] 
] 
=========
[   [ false, false, false ], 
  [ false, true, true ], 
  [ false, false, true ] 
] 
[   [ false, false, false ], 
  [ false, 'a', 'aa' ], 
  [ false, false, 'a' ] 
] 
=========
[   [ true, true, true ], 
  [ false, true, true ], 
  [ false, false, true ] 
] 
[   [ 'a', 'aa', 'aaa' ], 
  [ false, 'a', 'aa' ], 
  [ false, false, 'a' ] 
]
=========
/**
 * @param {string} s
 * @return {number}
 */
var countSubstrings = function(s) {
    let res = 0;
    let len = s.length;
    let dp = new Array(len).fill().map(() => new Array(len).fill(false));
    let dp1 = new Array(len).fill().map(() => new Array(len).fill(false));
    for (let i = len - 1; i >= 0; i--) {  
        for (let j = i; j < len; j++) {
            if (s[i] == s[j] && (j - i <= 1 || dp[i + 1][j - 1])) {
                res++;
                dp[i][j] = true;
            }
            dp1[i][j] = s.substring(i, j + 1);
        }
        console.info(dp);
        console.info(dp1);
        console.info('=========');
    }
    return res;
};

5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。
dp[i][j]:表示子串s[i]到s[j]是否为回文子串
dp[i][j] = s[i] === s[j] && (j - i < 2 || dp[i + 1][j - 1]); 表示回文串的性质

1111.png

44, 33, 34, 22, 23, 24, 11, 12, 13, 14, 00, 01, 02, 03, 04的顺序进行遍历

遍历到24时,按照dp[i][j] = s[i] === s[j] && (j - i < 2 || dp[i + 1][j - 1]),
4-2<2 || dp[2+1][4-1], 4-2<2不满足要求,所以需要知道dp[3][3],
而33在之前的遍历中已经有结果了(为true),所以24的结果也可以得到。
dp1是辅助代码,用来打印,实际ac代码可以去掉
[   [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, false, false, true ] 
] 
[   [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, false, false, 'd' ] 
] 
========= 
[   [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, false, true, false ], 
  [ false, false, false, false, true ] 
] 
[   [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, false, 'a', 'ad' ], 
  [ false, false, false, false, 'd' ] 
] 
========= 
[   [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, true, false, false ], 
  [ false, false, false, true, false ], 
  [ false, false, false, false, true ] 
] 
[   [ false, false, false, false, false ], 
  [ false, false, false, false, false ], 
  [ false, false, 'b', 'ba', 'bad' ], 
  [ false, false, false, 'a', 'ad' ], 
  [ false, false, false, false, 'd' ] 
] 
========= 
[   [ false, false, false, false, false ], 
  [ false, true, false, true, false ], 
  [ false, false, true, false, false ], 
  [ false, false, false, true, false ], 
  [ false, false, false, false, true ] 
] 
[   [ false, false, false, false, false ], 
  [ false, 'a', 'ab', 'aba', 'abad' ], 
  [ false, false, 'b', 'ba', 'bad' ], 
  [ false, false, false, 'a', 'ad' ], 
  [ false, false, false, false, 'd' ] 
] 
========= 
[   [ true, false, true, false, false ], 
  [ false, true, false, true, false ], 
  [ false, false, true, false, false ], 
  [ false, false, false, true, false ], 
  [ false, false, false, false, true ] 
] 
[   [ 'b', 'ba', 'bab', 'baba', 'babad' ], 
  [ false, 'a', 'ab', 'aba', 'abad' ], 
  [ false, false, 'b', 'ba', 'bad' ], 
  [ false, false, false, 'a', 'ad' ], 
  [ false, false, false, false, 'd' ] 
] 
=========
/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function(s) {
    let res = '';
    let len = s.length;
    let dp = new Array(len).fill().map(() => new Array(len).fill(false));
    let dp1 = new Array(len).fill().map(() => new Array(len).fill(false));
    for (let i = len - 1; i >= 0; i--) {
        for (let j = i; j < len; j++) {
            dp[i][j] = s[i] === s[j] && (j - i <= 1 || dp[i + 1][j - 1]);
            // 是回文串且大于上次回文串长度,则更新结果
            if (dp[i][j] && j - i + 1 > res.length) {
                res = s.substring(i, j + 1);
            }
            dp1[i][j] = s.substring(i, j + 1);
        }
        console.info(dp);
        console.info(dp1);
        console.info('=========');
    }
    return res;
};

516. 最长回文子序列

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

输入: s = "bbbab"
输出: 4
解释: 一个可能的最长回文子序列为 "bbbb" 。
i=4 
[   [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 1 ] ] 
[   [ 0, 0, 0, 0, 0 ],
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 'b' ] ] 
========= 
i=3 j=4 
[   [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 1, 1 ], 
  [ 0, 0, 0, 0, 1 ] ] 
[   [ 0, 0, 0, 0, 0 ],
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 'a', 'ab' ], 
  [ 0, 0, 0, 0, 'b' ] ] 
========= 
i=2 j=3 j=4 
[   [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 1, 1, 3 ], 
  [ 0, 0, 0, 1, 1 ], 
  [ 0, 0, 0, 0, 1 ] ] 
[   [ 0, 0, 0, 0, 0 ],
  [ 0, 0, 0, 0, 0 ], 
  [ 0, 0, 'b', 'ba', 'bab' ], 
  [ 0, 0, 0, 'a', 'ab' ], 
  [ 0, 0, 0, 0, 'b' ] ] 
========= 
i=1 j=2 j=3 j=4 
[   [ 0, 0, 0, 0, 0 ], 
  [ 0, 1, 2, 2, 3 ], 
  [ 0, 0, 1, 1, 3 ], 
  [ 0, 0, 0, 1, 1 ], 
  [ 0, 0, 0, 0, 1 ] ] 
[   [ 0, 0, 0, 0, 0 ],
  [ 0, 'b', 'bb', 'bba', 'bbab' ], 
  [ 0, 0, 'b', 'ba', 'bab' ], 
  [ 0, 0, 0, 'a', 'ab' ], 
  [ 0, 0, 0, 0, 'b' ] ] 
========= 
i=0 j=1 j=2 j=3 j=4 
[   [ 1, 2, 3, 3, 4 ], 
  [ 0, 1, 2, 2, 3 ], 
  [ 0, 0, 1, 1, 3 ], 
  [ 0, 0, 0, 1, 1 ], 
  [ 0, 0, 0, 0, 1 ] ] 
[   [ 'b', 'bb', 'bbb', 'bbba', 'bbbab' ], 
  [ 0, 'b', 'bb', 'bba', 'bbab' ], 
  [ 0, 0, 'b', 'ba', 'bab' ], 
  [ 0, 0, 0, 'a', 'ab' ], 
  [ 0, 0, 0, 0, 'b' ] ] 
=========
/**
 * @param {string} s
 * @return {number}
 */
var longestPalindromeSubseq = function(s) {
    let len = s.length;
    let dp = Array.from(new Array(len)).map(() => new Array(len).fill(0));
    let dp1 = Array.from(new Array(len)).map(() => new Array(len).fill(0));
    // i从下到上,j从左到右遍历
    for (let i = len - 1; i >= 0; i--) {
        dp[i][i] = 1; // 单个字符一定是回文串
        dp1[i][i] = s.substring(i, i + 1);
        console.info('i='+i);
        for (let j = i + 1; j < len; j++) {
            console.info('j='+j);
            if (s[i] === s[j]) {
                // 说明在子序列中,+2
                dp[i][j] = dp[i + 1][j - 1] + 2;
            } else {
                // dp[i][j]表示字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]
                dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
            }
            dp1[i][j] = s.substring(i, j + 1);
        }
        console.info(dp);
        console.info(dp1);
        console.info('=========');
    }
    return dp[0][len-1];
};

53. 最大子序和

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

示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
dp[i]:表示包括下标i之前的最大连续子序列和
dp[i]的值来自两种情况的较大值:dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);
    ①是i之前的最大子序和为正时,i的最大子序和为dp[i-1] + 当前值nums[i]
    ②是i之前的最大子序和为负时,i的最大子序和为当前值nums[i]
    
算出每个下标i时的最大子序和,并不断更新出其中最大的一个。
[ -2 ] 
[ -2, 1 ] 
[ -2, 1, -2 ] 
[ -2, 1, -2, 4 ] 
[ -2, 1, -2, 4, 3 ] 
[ -2, 1, -2, 4, 3, 5 ] 
[ -2, 1, -2, 4, 3, 5, 6 ] 
[ -2, 1, -2, 4, 3, 5, 6, 1 ] 
[ -2, 1, -2, 4, 3, 5, 6, 1, 5 ]
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    let res = nums[0];
    let dp = [nums[0]];
    console.info(dp);
    for(let i = 1; i < nums.length; i++) {
        dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);
        console.info(dp);
        res = Math.max(res, dp[i]);
    }
    return res;
};
迭代nums数组
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    let res = nums[0];
    let sum = 0;
    for(let num of nums) {
        if(sum > 0) {
            sum += num;
        } else {
            sum = num;
        }
        res = Math.max(res, sum);
    }
    return res;
};

198. 打家劫舍

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

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

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4
dp[i]:表示偷窃到第i间房屋时能够偷窃到的最高金额
dp[i]的值来自两种情况的较大值:dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
    因为必须隔一间房屋进行偷窃,所以
    ①当i是不能偷窃的房屋时,偷窃的金额为偷窃到前一间房屋时的金额,即dp[i-1]
    ②是i是可以偷窃的房屋时,偷窃的金额为偷窃到上上一间房屋时的金额+偷窃到当前房屋的金额
     即dp[i-2] + nums[i]
[ 1, 2 ] 
[ 1, 2, 4 ] 
[ 1, 2, 4, 4 ]
/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function(nums) {
    let len = nums.length;
    let dp = [nums[0], Math.max(nums[0], nums[1])];
    console.info(dp);
    for(let i = 2; i < len; i++){
        dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
        console.info(dp);
    }
    return dp[len-1];
};

213. 打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

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

输入: nums = [2,3,2]
输出: 3
解释: 
    你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 
    因为他们是相邻的。
/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function(nums) {
    let len = nums.length;
    if(len === 0) return 0;
    if(len === 1) return nums[0];
    let res1 = 0; let res2 = 0;
    let dp = [];
    // 不偷第一家,从第0、第2间开始偷
    dp = [0, nums[1]];
    for(let i = 2; i <= len - 1; i++) {
        dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
    }
    res1 = dp[len - 1];
    // 不偷最后一家,从第1、第3间开始偷
    dp = [nums[0], Math.max(nums[0], nums[1])];
    for(let i = 2; i <= len - 2; i++) {
        dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
    }
    res2 = dp[len - 2];
    return Math.max(res1, res2);
};

337. 打家劫舍 III

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

输入: [3,2,3,null,3,null,1]

     3
    / \
   2   3
    \   \ 
     3   1

输出: 7 
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
/**
 * 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 dfs = (node) => {
        if (!node) return [0, 0];
        let l = dfs(node.left);
        let r = dfs(node.right);
        // 当前节点被偷,孩子节点不能被偷
        let select = node.val + l[1] + r[1];
        // 当前节点不被偷,孩子节点可以偷或不偷
        let notSelect = Math.max(l[0], l[1]) + Math.max(r[0], r[1]);
        return [select, notSelect];
    }
    let res = dfs(root);
    return Math.max(...res);
};

322. 零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1
dp[i]:表示凑到i金额时所需的最少的硬币个数 
dp[i]的值来自两种情况的较小值:dp[i] = Math.min(dp[i], dp[i-coin] + 1);
    ①不包含当前的硬币个数
    ②包含当前的硬币,即当前硬币数量1 + 扣除当前硬币金额coin后的硬币个数dp[i-coin];
初始化
[0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11]
dp打印
[0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11]
[0, 1, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11]
[0, 1, 1, 11, 11, 11, 11, 11, 11, 11, 11, 11]
[0, 1, 1, 2, 11, 11, 11, 11, 11, 11, 11, 11]
[0, 1, 1, 2, 2, 11, 11, 11, 11, 11, 11, 11]
[0, 1, 1, 2, 2, 1, 11, 11, 11, 11, 11, 11]
[0, 1, 1, 2, 2, 1, 2, 11, 11, 11, 11, 11]
[0, 1, 1, 2, 2, 1, 2, 2, 11, 11, 11, 11]
[0, 1, 1, 2, 2, 1, 2, 2, 3, 11, 11, 11]
[0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 11, 11]
[0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 2, 11]
[0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 2, 3];
/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var coinChange = function(coins, amount) {
    let dp = new Array(amount + 1).fill(Infinity);
    dp[0] = 0;
    for(let i = 0; i <= amount; i++) {
        for(let coin of coins) {
            if(i-coin >= 0) {
                dp[i] = Math.min(dp[i], dp[i-coin] + 1);
            }
        }
    }
    return dp[amount] === Infinity ? -1 : dp[amount];
};

121. 买卖股票的最佳时机

买卖1次,不需要间隔,不收手续费

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

输入:[7,1,5,3,6,4]
输出:5
解释:
    在第 2 天(股票价格 = 1)的时候买入,
    在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
    注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;
    同时,你不能在买入前卖出股票。
/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    let profits = 0; // 最大利润,初始化为0
    let min = prices[0]; // 最小价格,初始化为当前第一天的价格
    for (let i = 0; i < prices.length; i++) {
        // 不断维持最小价格
        min = Math.min(min, prices[i]);
        // 不断更新最大利润
        profits = Math.max(profits, prices[i] - min);
    }
    return profits;
};

122. 买卖股票的最佳时机 II

买卖多次,不需要间隔,不收手续费

给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    let profits = 0; // 最大利润初始化为0
    for (let i = 0; i < prices.length; i++) {
        // 当有利润时就添加上,相当于当天卖出再当天买入
        if(prices[i + 1] > prices[i]) {
            profits += prices[i + 1] - prices[i]
        }
    }
    return profits;
};