# 动态规划到底规划啥？

·  阅读 843

1.1 动态规划

## 一维动态规划

### 70. 爬楼梯

#### 题目描述

Input: 2

output: 2

1. 1 阶 + 1 阶
2. 2 阶。

Input: 3

output: 3

1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

#### 实现1

``````/**
* @param {number} n
* @return {number}
*/
// Runtime: 72 ms, faster than 88.54% of JavaScript online submissions for Climbing Stairs.
// Memory Usage: 38.3 MB, less than 56.43% of JavaScript online submissions for Climbing Stairs.
export default (n) => {
if (n === 0) return 0;
if (n === 1) return 1;
if (n === 2) return 2;
let pre1 = 1;
let pre2 = 2;
for (let i = 3; i <= n; i++) {
const temp = pre2;
pre2 = pre1 + pre2;
pre1 = temp;
}
return pre2;
};

### 198. 打家劫舍

#### 题目描述

Input: [1,2,3,1]

output: 4

Input: [2,7,9,3,1]

output: 12

1 0 <= nums.length <= 100
2 0 <= nums[i] <= 400

#### 实现1

``````/**
* @param {number[]} nums
* @return {number}
*/
// Runtime: 68 ms, faster than 97.89% of JavaScript online submissions for House Robber.
// Memory Usage: 38.2 MB, less than 91.14% of JavaScript online submissions for House Robber.
export default (nums) => {
if (!nums) return 0;
if (nums.length === 1) return nums[0];
if (nums.length === 2) return Math.max(nums[0], nums[1]);

let preN2money = nums[0];
let preN1money = Math.max(nums[0], nums[1]);
for (let i = 2; i < nums.length; i++) {
const temp = preN1money;
preN1money = Math.max(preN1money, preN2money + nums[i]);
preN2money = temp;
}
return preN1money;
};

### 198. 打家劫舍II

#### 题目描述

Input: nums = [2,3,2]

output: 3

Input: nums = [1,2,3,1]

output: 4

Input: nums = [0]

output: 0

1 0 <= nums.length <= 100
2 0 <= nums[i] <= 1000

#### 实现1

``````/**
* @param {number[]} nums
* @return {number}
*/
// Runtime: 68 ms, faster than 98.02% of JavaScript online submissions for House Robber II.
// Memory Usage: 38.5 MB, less than 53.03% of JavaScript online submissions for House Robber II.
export default (nums) => {
if (!nums) return 0;
if (nums.length === 1) return nums[0];
if (nums.length === 2) return Math.max(nums[0], nums[1]);
let preN2money = nums[0];
let preN1money = Math.max(nums[0], nums[1]);
for (let i = 2; i < nums.length - 1; i++) {
const temp = preN1money;
preN1money = Math.max(preN1money, preN2money + nums[i]);
preN2money = temp;
}
let preN2money2 = nums[1];
let preN1money1 = Math.max(nums[1], nums[2]);
for (let i = 3; i < nums.length; i++) {
const temp = preN1money1;
preN1money1 = Math.max(preN1money1, preN2money2 + nums[i]);
preN2money2 = temp;
}
return Math.max(preN1money1, preN1money);
};

### 343. 整数拆分

Input: 2

output: 1

Input: 10

output: 36

#### 思考

1 题目本身的状态转移方程一眼就可以看出来 dp[i] = Math.max(dp[i], dp[i-j] * d[j])

#### 实现1

``````/**
* @param {number} n
* @return {number}
*/
// Input: 2
// Output: 1
// Explanation: 2 = 1 + 1, 1 × 1 = 1.

// Input: 10
// Output: 36
// Explanation: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36.
// Runtime: 72 ms, faster than 98.97% of JavaScript online submissions for Integer Break.
// Memory Usage: 38.3 MB, less than 82.47% of JavaScript online submissions for Integer Break.
export default (n) => {
if (n <= 1) return 0;
if (n === 2) return 1;
const dp = new Array(n + 1).fill(0);
dp[1] = 1;
for (let i = 2; i <= n; i++) {
for (let j = 1; 2 * j <= i; j++) {
const maxJ = Math.max(j, dp[j]);
const maxDis = Math.max(i - j, dp[i - j]);
dp[i] = Math.max(dp[i], maxJ * maxDis);
}
}
// console.log(dp);
return dp[n];
};

### 650. 只有两个键的键盘

#### 题目描述

1 Copy All (复制全部) : 你可以复制这个记事本中的所有字符(部分的复制是不允许的)。
2 Paste (粘贴) : 你可以粘贴你上一次复制的字符。

Input: 3

output: 3

1 题目比较简单，就是单纯的dp

#### 实现1

``````/**
* @param {number} n
* @return {number}
*/
// Runtime: 88 ms, faster than 53.27% of JavaScript online submissions for 2 Keys Keyboard.
// Memory Usage: 39.3 MB, less than 31.78% of JavaScript online submissions for 2 Keys Keyboard.
export default (n) => {
const dp = new Array(n + 1).fill(Number.MAX_VALUE);
dp[0] = 0;
dp[1] = 0;
dp[2] = 2;
for (let i = 3; i <= n; i++) {
if (i % 2 === 0) {
dp[i] = Math.min(dp[i], dp[i / 2] + 2, i);
} else {
dp[i] = Math.min(dp[i], i);
for (let k = 3; k < Math.floor(i / 2); k++) {
if (i % k === 0) {
dp[i] = Math.min(dp[i], dp[k] + i / k);
}
}
}
}
return dp[n];
};

### 376. 摆动序列

#### 题目描述

Input: [1,7,4,9,2,5]

output: 6

Input: [1,17,5,10,13,15,10,5,16,8]

output: 7

Input: [1,2,3,4,5,6,7,8,9]

output: 2

#### 思考

1 如果是单纯的增长或者减少使用动态规划比较简单，但是这里是一个摆动序列，可能就比较麻烦了

#### 实现1

``````/**
* @param {number[]} nums
* @return {number}
*/

// Runtime: 76 ms, faster than 72.34% of JavaScript online submissions for Wiggle Subsequence.
// Memory Usage: 38.5 MB, less than 44.68% of JavaScript online submissions for Wiggle Subsequence.
export default (nums) => {
if (nums.length === 0 || !nums) return 0;
if (nums.length <= 1) return 1;
if (nums.length === 2) {
return nums[0] !== nums[1] ? 2 : 1;
}
const len = nums.length;
const up = new Array(len).fill(0);
const down = new Array(len).fill(0);

up[0] = 1;
down[0] = 1;

for (let i = 1; i < len; i++) {
if (nums[i] > nums[i - 1]) {
up[i] = down[i - 1] + 1;
down[i] = down[i - 1];
} else if (nums[i] < nums[i - 1]) {
down[i] = up[i - 1] + 1;
up[i] = up[i - 1];
} else {
down[i] = down[i - 1];
up[i] = up[i - 1];
}
}

return Math.max(down[nums.length - 1], up[nums.length - 1]);
};

#### 实现2

``````// Runtime: 76 ms, faster than 72.34% of JavaScript online submissions for Wiggle Subsequence.
// Memory Usage: 38.5 MB, less than 36.17% of JavaScript online submissions for Wiggle Subsequence.
export default (nums) => => {
if (nums.length < 2) return nums.length;
let down = 1;
let up = 1;
for (let i = 1; i < nums.length; i++) {
if (nums[i] > nums[i - 1]) up = down + 1;
else if (nums[i] < nums[i - 1]) down = up + 1;
}
return Math.max(down, up);
};

### 413. 等差数列划分

#### 题目描述

1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9

Input: A = [1, 2, 3, 4]

output: 3

#### 思考

1 第一种dp[n]表示n结尾的一共有多少种排列，所以dp[n+1]呢？

2 这里如果换一种看法，如果让dp[n]还是表示以下标为n结尾一共有多少种排列？

#### 实现1

``````/**
* @param {number[]} A
* @return {number}
*/
// Runtime: 84 ms, faster than 18.63% of JavaScript online submissions for Arithmetic Slices.
// Memory Usage: 41.1 MB, less than 5.88% of JavaScript online submissions for Arithmetic Slices.
export default (A) => {
if (!A || A.length < 3) return [];
let dp = [];
if (A[2] - A[1] === A[1] - A[0]) {
dp.push([A[0], A[1], A[2]]);
}
for (let i = 3; i < A.length; i++) {
const tempArr = [];
tempArr.push(A[i]);
const val = A[i] - A[i - 1];
for (let k = i - 1; k >= 0; k--) {
if (tempArr[0] - A[k] === val) {
tempArr.unshift(A[k]);
if (tempArr.length >= 3) {
dp.push([...tempArr]);
}
} else {
break;
}
}
}
return dp.length;
};

#### 实现2

``````// Runtime: 76 ms, faster than 76.47% of JavaScript online submissions for Arithmetic Slices.
// Memory Usage: 38.4 MB, less than 51.96% of JavaScript online submissions for Arithmetic Slices.
export default (A) => {
if (!A || A.length < 3) return [];
const len = A.length;
const dp = new Array(len).fill(0);
for (let i = 2; i < len; ++i) {
if (A[i] - A[i - 1] === A[i - 1] - A[i - 2]) {
dp[i] = dp[i - 1] + 1;
}
}
// console.log(dp);
return dp.reduce((a, b) => a + b);
};

### 53. 最大子序和

#### 题目描述

Input: [-2,1,-3,4,-1,2,1,-5,4]

output: 6

Input: [1]

output: 1

Input: [0]

output: 0

Input: [-1]

output: -1

Input: [-2147483647]

output: -2147483647

1 1 <= nums.length <= 2 * 10^4
2 -2^31 <= nums[i] <= 2^31 - 1

#### 思考

1 第一种dp[n]表示n结尾的一共有多少种排列，所以dp[n+1]呢？

2 这里如果换一种看法，如果让dp[n]还是表示以下标为n结尾一共有多少种排列？

#### 实现1

``````/**
* @param {number[]} A
* @return {number}
*/
// Runtime: 84 ms, faster than 18.63% of JavaScript online submissions for Arithmetic Slices.
// Memory Usage: 41.1 MB, less than 5.88% of JavaScript online submissions for Arithmetic Slices.
export default (A) => {
if (!A || A.length < 3) return [];
let dp = [];
if (A[2] - A[1] === A[1] - A[0]) {
dp.push([A[0], A[1], A[2]]);
}
for (let i = 3; i < A.length; i++) {
const tempArr = [];
tempArr.push(A[i]);
const val = A[i] - A[i - 1];
for (let k = i - 1; k >= 0; k--) {
if (tempArr[0] - A[k] === val) {
tempArr.unshift(A[k]);
if (tempArr.length >= 3) {
dp.push([...tempArr]);
}
} else {
break;
}
}
}
return dp.length;
};

#### 实现2

``````// Runtime: 76 ms, faster than 76.47% of JavaScript online submissions for Arithmetic Slices.
// Memory Usage: 38.4 MB, less than 51.96% of JavaScript online submissions for Arithmetic Slices.
export default (A) => {
if (!A || A.length < 3) return [];
const len = A.length;
const dp = new Array(len).fill(0);
for (let i = 2; i < len; ++i) {
if (A[i] - A[i - 1] === A[i - 1] - A[i - 2]) {
dp[i] = dp[i - 1] + 1;
}
}
// console.log(dp);
return dp.reduce((a, b) => a + b);
};

### 279. 完全平方数

Input: n = 12

output: 3

Input: n = 13

output: 2

#### 思考

1 动态规划最重要的是什么？

// Input: n = 12
// output: 3

// 解释：12 = 4 + 4 + 4.
//

// 例子2
// Input: n = 13
// output: 2
// 解释：13 = 4 + 9.

#### 实现1

``````/**
* @param {number} n
* @return {number}
*/

// Input:  n = 12<br/>
// output: 3<br/>

// 解释：12 = 4 + 4 + 4.
// <br/>

// 例子2<br/>
// Input:  n = 13 <br/>
// output: 2<br/>
// 解释：13 = 4 + 9.

// dp[13] = dp[]

// dp[1] = 1
// dp[2] = 1+1
// dp[n] = dp[n-]
export default (n) => {
if (n === 0) return 0;
if (n === 1) return 1;
if (n === 2) return 2;
const dp = [];
dp[0] = 0;
dp[1] = 1;

for (let i = 2; i <= n; i++) {
dp[i] = Number.MAX_VALUE;
const sqrtN = Math.floor(Math.sqrt(i));
for (let j = 1; j <= sqrtN; j++) {
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
};

### 646. 最长数对链

#### 题目描述

Input: [[1,2], [2,3], [3,4]]

output: 2

#### 思考

1 题目比较简单，先排序，然后使用动态规划缓存前面的状态就可以了。

#### 实现1

``````/**
* @param {number[][]} pairs
* @return {number}
*/
//  Runtime: 92 ms, faster than 92.21% of JavaScript online submissions for Maximum Length of Pair Chain.
//  Memory Usage: 42.8 MB, less than 74.03% of JavaScript online submissions for Maximum Length of Pair Chain.
export default (pairs) => {
if (!pairs || pairs.length === 0) return [];

if (pairs.length === 1 || pairs.length === 2) return [pairs[0]];
pairs.sort((a, b) => a[0] - b[0]);
const dp = [pairs[0]];
// console.log(pairs);
for (let i = 1; i < pairs.length; i++) {
if (pairs[i][0] > dp[dp.length - 1][1]) {
dp.push(pairs[i]);
} else if (pairs[i][1] < dp[dp.length - 1][1]) {
dp[dp.length - 1] = pairs[i];
}
}
// console.log(dp);
return dp.length;
};

### 91. 解码方法

'A' -> 1
'B' -> 2
...
'Z' -> 26

Input: s = "12"

output: 2

Input: s = "226"

output: 3

Input: s = "0"

output: 0

Input: s = "1"

output: 1

Input: s = "2"

output: 1

#### 思考

1 刚开始思考，通过"226" 这个测试用例，很容易的可以得出状态转移方程是dp[n] = dp[n-1] + dp[n-2] 或者dp[n] = dp[n-1]

“12” => 2
“226” => 3
“00” => 0
“2101” => 1
"27" => 1
"1201234" => 3
"10011" => 0
"123123" => 9
"230" => 0

2 通过上面的情况，可以发现各种情况需要各种处理，代码虽然不是很复杂，但是需要处理的各种情况，都需要处理，代码显得冗余，而且不优雅，显得代码难看

#### 实现1

``````/**
* @param {string} s
* @return {number}
*/
// dp[3] = dp[1]+dp[2]
// Runtime: 104 ms, faster than 22.88% of JavaScript online submissions for Decode Ways.
// Memory Usage: 42.5 MB, less than 15.00% of JavaScript online submissions for Decode Ways.
export default (s) => {
if (s.length === 1 && +s > 0) {
return 1;
} else if (s.length === 1 || s[0] === "0") {
return 0;
}
const dp = [];
dp[0] = +s[0] > 0 ? 1 : 0;
dp[1] = +s[1] > 0 ? dp[0] + 1 : dp[0];

if (+s.substring(0, 2) > 26 && +s[0] > 0 && s[1] !== "0") {
dp[1] = 1;
} else if (+s.substring(0, 2) > 26 && +s[0] > 0 && s[1] === "0") {
return 0;
}

for (let i = 2; i < s.length; i++) {
if (s[i] === "0" && +s.substring(i - 1, i + 1) < 27 && +s.substring(i - 1, i + 1) > 9) {
dp[i] = dp[i - 2];
} else if (+s[i - 1] > 0 && +s[i] > 0 && +s.substring(i - 1, i + 1) > 10 && +s.substring(i - 1, i + 1) < 27) {
dp[i] = dp[i - 1] + dp[i - 2];
} else if ((+s[i - 1] === 0 && +s[i] > 0) || (+s[i - 1] > 0 && +s[i] > 0 && +s.substring(i - 1, i + 1) > 26)) {
dp[i] = dp[i - 1];
} else if ((+s[i] === 0 && +s.substring(i - 1, i + 1) > 26) || (+s[i - 1] === 0 && +s[i] === 0)) {
return 0;
}
}
// console.log(dp);
return dp[dp.length - 1];
};

#### 实现2

``````/**
* @param {string} s
* @return {number}
*/
export default (s) => {
if (s.length === 0) return 0;

const len = s.length;
const dp = new Array(len + 1).fill(0);

dp[0] = 1;
// 当有一个字符的时候
dp[1] = s[0] === "0" ? 0 : 1;

for (let i = 2; i <= len; i++) {
// 如果不等于0，肯定是等于dp[n-1]
if (s[i - 1] !== "0") {
dp[i] += dp[i - 1];
}
// console.log(dp[i], i);
// 如果等于0或者小于6的情况下加上dp[n-2]
if (s[i - 2] === "1" || (s[i - 2] === "2" && s[i - 1] <= "6")) {
dp[i] += dp[i - 2];
}
// console.log(dp[i], i);
}
// console.log(dp);
return dp[len];
};

### 139. 单词拆分

#### 题目描述

1 拆分时可以重复使用字典中的单词。
2 你可以假设字典中没有重复的单词。

Input: s = "leetcode", wordDict = ["leet", "code"]

output: true

Input: s = "applepenapple", wordDict = ["apple", "pen"]

output: true

Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]

output: false

#### 思考

1 这里使用动态规划比较简单，很容易就可以想到dp[n]表示长度为n的字符串是否可以用wordDict表示，那么dp[n+1]呢？

#### 实现1

``````/**
* @param {string} s
* @param {string[]} wordDict
* @return {boolean}
*/

// Runtime: 88 ms, faster than 57.62% of JavaScript online submissions for Word Break.
// Memory Usage: 40.5 MB, less than 62.66% of JavaScript online submissions for Word Break.
export default (s, wordDict) => {
// dp[i] 表示s中前i个字符是否可以在wordDict中表示
const dp = new Array(s.length).fill(0);

for (let i = 0; i < s.length; i++) {
for (let j = i; j >= 0; j--) {
const subStr = s.substring(j, i + 1);
if (wordDict.includes(subStr) && ((dp[j - 1] === 1 && j > 0) || j === 0)) {
dp[i] = 1;
}
}
}
return dp[s.length - 1];
};

### 300. 最长上升子序列

#### 题目描述

Input:
[10,9,2,5,3,7,101,18]

output: 4

Input:
nums = [0,1,0,3,2,3]

output: 4

Input:
nums = [7,7,7,7,7,7,7]

output: 1

1 1 <= nums.length <= 2500
2 -10^4 <= nums[i] <= 10^4<br

#### 思考

1 题目使用动态规划，dp[i]表示以nums[i]结尾的增长子数列

2 然后题目还要求实现O（n * lgn）的时间复杂度，涉及到lgn肯定是二分查找，可以在哪里查找呢？

num dp
10 [10] 10加入dp
9 [9] 9加入的时候，发现9比10小，为了更长子数列，9替换10
2 [2] 2加入的时候，发现2比9小，为了更长子数列，2替换 9
5 [2，5] 5 加入，发现比大，变为 [2，5]
3 [2,3] 3比5小
7 [2,3,7] 7 比 3大
101 [2,3,7,101] 101比7大
18 [2,3,7,18] 18比101小

#### 实现1

``````/**
* @param {number[]} nums
* @return {number}
*/
//  [10,9,2,5,3,7,101,18]
//  4
// Runtime: 184 ms, faster than 18.89% of JavaScript online submissions for Longest Increasing Subsequence.
// Memory Usage: 39.6 MB, less than 25.03% of JavaScript online submissions for Longest Increasing Subsequence.
export default (nums) => {
const len = nums.length;
const dp = new Array(len).fill(1);
let max = 1;
for (let i = 1; i < nums.length; i++) {
for (let j = i - 1; j >= 0; j--) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[j] + 1, dp[i]);
max = Math.max(dp[i], max);
} else if (nums[i] === nums[j]) {
dp[i] = Math.max(dp[i], dp[j]);
max = Math.max(dp[i], max);
}
}
}
return max;
};

#### 实现2

``````/**
* @param {number[]} nums
* @return {number}
*/
//  [10,9,2,5,3,7,101,18]
//  4

const binarySearchPosition = (dp, target, high) => {
let low = 0;
while (low <= high) {
let mid = Math.floor(low + (high - low) / 2);
if (target === dp[mid]) return mid;
else if (target < dp[mid]) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return low;
};
// [10, 9, 2, 5, 3, 7, 101, 18];
// Runtime: 84 ms, faster than 90.03% of JavaScript online submissions for Longest Increasing Subsequence.
// Memory Usage: 40.2 MB, less than 19.47% of JavaScript online submissions for Longest Increasing Subsequence.
export default (nums) => {
const len = nums.length;
if (!nums || len === 0) return 0;
if (len === 1) return 1;
let dp = new Array(len).fill(Number.MAX_VALUE);
for (let i = 0; i < len; i++) {
let pos = binarySearchPosition(dp, nums[i], i);
dp[pos] = nums[i];
console.log(dp);
}

for (let i = dp.length - 1; i >= 0; i--) {
if (dp[i] !== Number.MAX_VALUE) return i + 1;
}

return 0;
};

## 二维动态规划

### 1143. 最长公共子序列

#### 题目描述

Input: text1 = "abcde", text2 = "ace"

output: 3

Input: text1 = "abc", text2 = "abc"

output: 3

Input: text1 = "abc", text2 = "def"

output: 0

1 1 <= text1.length <= 1000
2 1 <= text2.length <= 1000
3 输入的字符串只含有小写英文字符。

#### 思考

1 这里是求公共子序列，动态规划中一般dp[i]是指i结尾的子序列

dp[i][j] 可以用来表示第一个字符串中i结尾和第二个字符串j结尾的包含的公共子序列

dp[i+1][j] 是什么？
dp[i][j+1]是什么？
dp[i][j]是什么？

#### 实现1

``````/**
* @param {string} text1
* @param {string} text2
* @return {number}
*/

//  Runtime: 116 ms, faster than 62.14% of JavaScript online submissions for Longest Common Subsequence.
//  Memory Usage: 48.4 MB, less than 91.40% of JavaScript online submissions for Longest Common Subsequence.
// export default (text1, text2) => {
const m = text1.length;
const n = text2.length;
const dp = [];
for (let i = 0; i <= m; i++) {
dp[i] = new Array(n + 1).fill(0);
}
// console.log(dp);
for (let i = 1; i <= m; ++i) {
for (let j = 1; j <= n; ++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]);
}
}
}
// console.log(dp);
return dp[m][n];
};

### 583. 两个字符串的删除操作

#### 题目描述

Input: "sea", "eat"

output: 2

1 给定单词的长度不超过500。
2 给定单词中的字符只含有小写字母。

#### 思考

1 这里是上面的1143的变种题目，可以想下两者有那些不同? 做题不是目的，举一反三才是本质

#### 实现1

``````/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/

// "sea", "eat";

// Runtime: 108 ms, faster than 93.48% of JavaScript online submissions for Delete Operation for Two Strings.
// Memory Usage: 45 MB, less than 75.00% of JavaScript online submissions for Delete Operation for Two Strings.
export default (word1, word2) => {
if (word1 === word2) {
return 0;
}
if (word1.length === 0) {
return word2.length;
}
if (word2.length === 0) {
return word1.length;
}
const m = word1.length;
const n = word2.length;
const dp = [];
for (let i = 0; i <= m; i++) {
dp[i] = new Array(n + 1).fill(0);
}
for (let i = 1; i <= n; i++) {
dp[0][i] = i;
}
for (let i = 1; i <= m; i++) {
dp[i][0] = i;
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
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], dp[i][j - 1]) + 1;
}
}
}
// console.log(dp);
return dp[m][n];
};

### 64. 最小路径和

#### 题目描述

Input: grid = [[1,3,1],[1,5,1],[4,2,1]]

output: 7

Input: grid = [[1,2,3],[4,5,6]]

output: 12

1 m == grid.length
2 n == grid[i].length<br 3 1 <= m, n <= 200
4 0 <= grid[i][j] <= 100

#### 思考

1 题目比较简单，状态转移方程很容易就可以看出来

dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];

#### 实现1

``````/**
* @param {number[][]} grid
* @return {number}
*/
// Runtime: 84 ms, faster than 68.34% of JavaScript online submissions for Minimum Path Sum.
// Memory Usage: 41.1 MB, less than 30.49% of JavaScript online submissions for Minimum Path Sum.
export default (grid) => {
const m = grid.length;
const n = grid[0].length;
if (m === 1 && n === 1) {
return grid[0][0];
}
const dp = [];
for (let i = 0; i < m; i++) {
dp[i] = new Array(n).fill(0);
}
dp[0][0] = grid[0][0];
for (let i = 1; i < n; i++) {
dp[0][i] = dp[0][i - 1] + grid[0][i];
}
for (let i = 1; i < m; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}

// dp[0];
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];
}
}
return dp[m - 1][n - 1];
};

### 542. 01 矩阵

#### 题目描述

Input:
[[0,0,0], [0,1,0], [0,0,0]]

output: [[0,0,0], [0,1,0], [0,0,0]]

Input:
[[0,0,0], [0,1,0], [1,1,1]]

output: [[0,0,0], [0,1,0], [1,2,1]]

1 给定矩阵的元素个数不超过 10000。
2 给定矩阵中至少有一个元素是 0。<br 3 矩阵中的元素只在四个方向上相邻: 上、下、左、右。

#### 思考

1 状态转移方程很好找，如果matrix[i][j]等于1，则dp[i][j]是四个方向中最小的值加1

#### 实现1

``````/**
* @param {number[][]} matrix
* @return {number[][]}
*/
// Runtime: 148 ms, faster than 96.10% of JavaScript online submissions for 01 Matrix.
// Memory Usage: 46.8 MB, less than 79.22% of JavaScript online submissions for 01 Matrix.
export default (matrix) => {
const m = matrix.length;
const n = matrix[0].length;
if (m === 0) {
return [[]];
}
const dp = [];
for (let i = 0; i < m; i++) {
dp[i] = new Array(n).fill(Number.MAX_VALUE);
}
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (matrix[i][j] === 0) {
dp[i][j] = 0;
} else {
if (i > 0) {
dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + 1);
}
if (j > 0) {
dp[i][j] = Math.min(dp[i][j], dp[i][j - 1] + 1);
}
}
}
}
for (let i = m - 1; i >= 0; i--) {
for (let j = n - 1; j >= 0; j--) {
if (matrix[i][j] != 0) {
if (i < m - 1) {
dp[i][j] = Math.min(dp[i][j], dp[i + 1][j] + 1);
}
if (j < n - 1) {
dp[i][j] = Math.min(dp[i][j], dp[i][j + 1] + 1);
}
}
}
}
return dp;
};

### 221. 最大正方形

#### 题目描述

Input:
matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]

output: 4

Input:
matrix = [["0","1"],["1","0"]]

output: 1

Input:
matrix = matrix = [["0"]]

output: 0

1 m == matrix.length
2 n == matrix[i].length<br 3 1 <= m, n <= 300
4 matrix[i][j] 为 '0' 或 '1'

#### 思考

1 题目使用动态规划，这里一看就是使用二维动态规划

dp[i][j]表示以matrix[i][j]为结尾的1的最大正方形

#### 实现1

``````/**
* @param {character[][]} matrix
* @return {number}
*/

// Runtime: 84 ms, faster than 94.48% of JavaScript online submissions for Maximal Square.
// Memory Usage: 42.2 MB, less than 29.75% of JavaScript online submissions for Maximal Square.

export default (matrix) => {
const m = matrix.length;
const n = matrix[0].length;
if (m === 0 || n === 0) {
return 0;
}
const dp = [];
for (let i = 0; i < m; i++) {
dp[i] = new Array(n).fill(0);
}

let max = 0;
for (let i = 0; i < n; i++) {
dp[0][i] = +matrix[0][i];
max = Math.max(max, dp[0][i]);
}

for (let i = 0; i < m; i++) {
dp[i][0] = +matrix[i][0];
max = Math.max(max, dp[i][0]);
}

for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
if (matrix[i][j] === "1") {
dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]) + 1;
}
max = Math.max(max, dp[i][j]);
}
}
return max * max;
};

### 72. 编辑距离

#### 题目描述

``````插入一个字符

Input: word1 = "horse", word2 = "ros"

output: 3

rorse -> rose (删除 'r')
rose -> ros (删除 'e')

Input:word1 = "intention", word2 = "execution"

output: 5

inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

1 0 <= word1.length, word2.length <= 500
2 word1 和 word2 由小写英文字母组成

#### 思考

1 已经做了这么多动态规划的题目，很容易想到dp[i][j] 表示word1的i个字符变成word2的j个字符最小距离

dp[i][j] = dp[i-1][j-1]

dp[i][j] = dp[i][j-1]+1

#### 实现1

``````/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/

// (word1 = "horse"), (word2 = "ros");
// Runtime: 124 ms, faster than 40.13% of JavaScript online submissions for Edit Distance.
// Memory Usage: 42.8 MB, less than 90.97% of JavaScript online submissions for Edit Distance.
export default (word1, word2) => {
const m = word1.length;
const n = word2.length;

const dp = [];
for (let i = 0; i <= m; i++) {
dp[i] = new Array(n + 1).fill(0);
}
for (let i = 0; i <= n; i++) {
dp[0][i] = i;
}
for (let i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
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], dp[i][j - 1], dp[i - 1][j - 1]) + 1;
}
}
}
// console.log(dp);
return dp[m][n];
};

### 10. 正则表达式匹配

#### 题目描述

``````'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素

Input: s = "aa" p = "a"

output: false

Input: s = "aa" p = "a*"

output: true

Input: s = "ab" p = ".*"

output: true

Input: s = "aab" p = "cab"

output: true

Input: s = "mississippi" p = "misisp*."

output: false

2 0 <= p.length <= 30
3 s 可能为空，且只包含从 a-z 的小写字母。
4 p 可能为空，且只包含从 a-z 的小写字母，以及字符 . 和 * 。
5 保证每次出现字符 * 时，前面都匹配到有效的字符

#### 思考

1 说实话这题本来还是很有难度的

1 为了考虑s为“”的情况，所以定义了一个dp[p.length + 1][s.length + 1]

2 就是各种情况下的处理，这个确实不是很好处理，但是如何看下代码你会发现很简单

#### 实现1

``````/**
* @param {string} s
* @param {string} p
* @return {boolean}
*/

// Runtime: 92 ms, faster than 93.90% of JavaScript online submissions for Regular Expression Matching.
// Memory Usage: 41.5 MB, less than 53.93% of JavaScript online submissions for Regular Expression Matching.
export default (s, p) => {
const m = p.length;
const n = s.length;

const dp = [];

for (let i = 0; i <= m; i++) {
dp[i] = new Array(n + 1).fill(false);
}

dp[0][0] = true;

for (let i = 1; i <= m; i++) {
if (p[i - 1] === "*") {
if (i >= 2) {
dp[i][0] = dp[i - 2][0];
}
}
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (p[i - 1] === ".") {
dp[i][j] = dp[i - 1][j - 1];
} else if (p[i - 1] === "*") {
if (i >= 2) {
dp[i][j] = dp[i - 2][j] || dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
if (dp[i][j - 1] && (p[i - 2] === "." || p[i - 2] === s[j - 1])) {
dp[i][j] = true;
}
} else {
if (p[i - 1] === s[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
}

return dp[m][n];
};

#### 实现2

``````/**
* @param {string} s
* @param {string} p
* @return {boolean}
*/

// dp[i][j];
// Runtime: 92 ms, faster than 93.90% of JavaScript online submissions for Regular Expression Matching.
// Memory Usage: 42.1 MB, less than 42.54% of JavaScript online submissions for Regular Expression Matching.
export default (s, p) => {
const m = s.length;
const n = p.length;
const dp = [];
for (let i = 0; i <= m; i++) {
dp[i] = new Array(n + 1).fill(false);
}

dp[0][0] = true;

for (let i = 1; i <= n; i++) {
if (p[i - 1] == "*") {
dp[0][i] = dp[0][i - 2];
}
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
//如果是.则只要dp[i - 1][j - 1] 为true则为true
if (p[j - 1] == ".") {
dp[i][j] = dp[i - 1][j - 1];
// p[j-1]等于字母
} else if (p[j - 1] != "*") {
dp[i][j] = dp[i - 1][j - 1] && p[j - 1] == s[i - 1];
// p[j-1] 等于“*”,
} else if (p[j - 2] != s[i - 1] && p[j - 2] != ".") {
dp[i][j] = dp[i][j - 2];
} else {
dp[i][j] = dp[i][j - 1] || dp[i - 1][j] || dp[i][j - 2];
}
}
}
return dp[m][n];
};

## 0和1背包问题

0和1背包问题如果使用动态规划应该很容易解决，dp[i][j]表示把i个物品放到容量为j的背包的最大的价值

``````/**
* @param {number[]} weights
* @param {number[]} values
* @param {number} n
* @param {number} w
* @return {number}
*/
export default (weights, valuse, n, w) => {
const dp = [];
for (let i = 0; i <= n; i++) {
dp[i] = new Array(w + 1).fill(0);
}

for (let i = 1; i <= n; i++) {
for (let j = 1; j <= w; j++) {
if (j >= weights[i-1]) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i-1]] + valuse[i-1]);
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][w];
};

dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + valuse[i]);

``````/**
* @param {number[]} weights
* @param {number[]} values
* @param {number} n
* @param {number} w
* @return {number}
*/
export default (weights, valuse, n, w) => {
const dp = new Array(w + 1).fill(0);

for (let i = 1; i <= n; i++) {
for (let j = w; j >= 0; j--) {
if (j >= weights[i-1]) {
dp[j] = Math.max(dp[j], dp[j - weights[i-1]] + valuse[i-1]);
}
}
}
return dp[w];
};

### 416. 分割等和子集

#### 题目描述

1. 每个数组中的元素不会超过 100

2. 数组的大小不会超过 200

Input: [1, 5, 11, 5]

output: true

Input: [1, 2, 3, 5]

output: false

#### 思考

1 这里要转换成0和1背包问题，其实还是有些难度，如何想到如何转换成0和1背包就按照0和1背包问题解决就可以了

#### 实现1

``````/**
* @param {number[]} nums
* @return {boolean}
*/
// [1,5,11,5]
// 155 11
// 1235

// [1, 2, 3, 5];
// [1, 1, 2, 2];
// Runtime: 244 ms, faster than 45.49% of JavaScript online submissions for Partition Equal Subset Sum.
// Memory Usage: 70.8 MB, less than 31.03% of JavaScript online submissions for Partition Equal Subset Sum.
export default (nums) => {
const len = nums.length;
const sum = nums.reduce((a, b) => a + b);
if (sum % 2 !== 0) return false;
const target = sum / 2;
const dp = new Array(len);
for (let i = 0; i < len; i++) {
dp[i] = new Array(target + 1).fill(false);
}
for (let i = 0; i < len; ++i) {
dp[i][0] = true;
}
for (let i = 1; i < len; i++) {
for (let j = 0; j <= target; ++j) {
if (j >= nums[i - 1]) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
} else {
dp[i][j] = dp[i - 1][j];
}
if (j === target && dp[i][j]) {
return true;
}
}
}
// console.log(dp);
return dp[len - 1][target];
};

#### 实现2

``````/**
* @param {number[]} nums
* @return {boolean}
*/
// [1,5,11,5]
// 155 11
// 1235

// [1, 2, 3, 5];
// [1, 1, 2, 2];
// Runtime: 108 ms, faster than 91.72% of JavaScript online submissions for Partition Equal Subset Sum.
// Memory Usage: 40.9 MB, less than 75.58% of JavaScript online submissions for Partition Equal Subset Sum.
export default (nums) => {
const len = nums.length;
const sum = nums.reduce((a, b) => a + b);
if (sum % 2 !== 0) return false;
const target = sum / 2;
const dp = new Array(target + 1).fill(false);
dp[0] = true;

for (let i = 1; i < len; i++) {
for (let j = target; j >= 0; j--) {
if (j >= nums[i - 1]) {
dp[j] = dp[j] || dp[j - nums[i - 1]];
} else {
dp[j] = dp[j];
}
if (j === target && dp[j]) {
return true;
}
}
}
// console.log(dp);
return dp[target];
};

### 494. 目标和

#### 题目描述

Input: nums: [1, 1, 1, 1, 1], S: 3

output: 5

+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

``````数组非空，且长度不会超过 20 。

#### 思考

1 这里也是典型的0和1背包问题变形，也就是要么选择+nums[i]或者选择-nums[i]

``````if (j + nums[i - 1] < maxN) {
dp[i][j] += dp[i - 1][j + nums[i - 1]];
}
// 选择+nums[i - 1]
if (j - nums[i - 1] >= 0) {
dp[i][j] += dp[i - 1][j - nums[i - 1]];
}

#### 实现1

``````/**
* @param {number[]} nums
* @param {number} S
* @return {number}
*/

// Runtime: 176 ms, faster than 76.29% of JavaScript online submissions for Target Sum.
// Memory Usage: 44.4 MB, less than 52.32% of JavaScript online submissions for Target Sum.
export default (nums, S) => {
let sum = 0;
for (let i of nums) {
sum += i;
}

// 如果大于最大的和小于最小的
if (S > sum || S < -sum) {
return 0;
}
const dp = [];
const len = nums.length;
const maxN = 2 * sum + 1;
for (let i = 0; i <= len; i++) {
dp[i] = new Array(maxN).fill(0);
}
// 这里指全部选择负数的时候，只有一种选择
dp[0][0 + sum] = 1;
for (let i = 1; i <= nums.length; i++) {
for (let j = 0; j < maxN; j++) {
// 选择-nums[i - 1]
if (j + nums[i - 1] < maxN) {
dp[i][j] += dp[i - 1][j + nums[i - 1]];
}
// 选择+nums[i - 1]
if (j - nums[i - 1] >= 0) {
dp[i][j] += dp[i - 1][j - nums[i - 1]];
}
}
}
// console.log(dp);
return dp[nums.length][sum + S];
};

#### 实现2

``````/**
* @param {number[]} nums
* @param {number} S
* @return {number}
*/

// Runtime: 96 ms, faster than 96.39% of JavaScript online submissions for Target Sum.
// Memory Usage: 44.7 MB, less than 41.49% of JavaScript online submissions for Target Sum.
export default (nums, S) => {
let sum = 0;
for (let i of nums) {
sum += i;
}

//
if (S > sum || S < -sum) {
return 0;
}
const len = 2 * sum + 1;
let dp = new Array(len).fill(0);
// 所有都选择负数
dp[sum] = 1;

for (let i = 0; i < nums.length; i++) {
const next = new Array(len).fill(0);
for (let k = 0; k < len; k++) {
if (dp[k] != 0) {
// 如果k有n中选择，那么当选择+ nums[i]的时候，肯定有n种，当选择- nums[i]的时候，肯定也有n种
next[k + nums[i]] += dp[k];
next[k - nums[i]] += dp[k];
}
}
dp = next;
}
return dp[sum + S];
};

### 474. 一和零

#### 题目描述

Input: strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3

output: 4

Input: strs = ["10", "0", "1"], m = 1, n = 1

output: 2

1 1<= strs.length <= 600
2 1<= strs[i].length <= 100
3 strs[i] 仅由 '0' 和 '1' 组成
4 <= m, n <= 100

#### 思考

1 这里题目感觉还是比较难的，如果是第一次接触，直接看答案就可以

Math.max(1 + memo(i), memo(i));

2 第二种方法就是定义dp[m][n] 表示已经遍历过strs从0到i的最大的子集的长度

#### 实现1

``````/**
* @param {string[]} strs
* @param {number} m
* @param {number} n
* @return {number}
*/

// ["10", "0001", "111001", "1", "0"];
// 5;
// 3;

// ["011111", "001", "001"], 4, 5;
const memo = (dp, start, m, n, size, strs) => {
if (start >= size || m < 0 || n < 0) return 0;
if (m === 0 && n === 0) return 0;
// console.log(dp[start][m], start, m, n);
if (dp[start][m][n] != -1) return dp[start][m][n];

let res = 0;
let i = start;
let ones = 0;
for (let k1 = 0; k1 < strs[i].length; k1++) {
if (strs[i][k1] === "1") {
ones++;
}
}
let zeros = strs[i].length - ones;
if (zeros <= m && ones <= n) {
// 如果选择，则选择其中选择或者不选择中的最大的
res = Math.max(1 + memo(dp, i + 1, m - zeros, n - ones, size, strs), memo(dp, i + 1, m, n, size, strs));
} else {
// 如果不符合规则，则直接下一个
res = memo(dp, i + 1, m, n, size, strs);
}
dp[start][m][n] = res;
return res;
};
// Runtime: 496 ms, faster than 34.21% of JavaScript online submissions for Ones and Zeroes.
// Memory Usage: 103 MB, less than 28.95% of JavaScript online submissions for Ones and Zeroes.
export default (strs, m, n) => {
const len = strs.length;
const dp = [];
for (let i = 0; i < len; i++) {
dp[i] = [];
for (let k = 0; k <= m; k++) {
dp[i][k] = new Array(n + 1).fill(-1);
}
}
return memo(dp, 0, m, n, len, strs);
};

#### 实现2

``````/**
* @param {string[]} strs
* @param {number} m
* @param {number} n
* @return {number}
*/

// Runtime: 140 ms, faster than 92.11% of JavaScript online submissions for Ones and Zeroes.
// Memory Usage: 40.9 MB, less than 78.95% of JavaScript online submissions for Ones and Zeroes.
export default (strs, m, n) => {
const len = strs.length;
const dp = [];
for (let i = 0; i <= m; i++) {
dp[i] = new Array(n + 1).fill(0);
}

for (let i = 0; i < len; i++) {
let ones = 0;
for (let k1 = 0; k1 < strs[i].length; k1++) {
if (strs[i][k1] === "1") {
ones++;
}
}
let zeros = strs[i].length - ones;
for (let k2 = m; k2 >= zeros; --k2) {
for (let j = n; j >= ones; --j) {
dp[k2][j] = Math.max(dp[k2][j], 1 + dp[k2 - zeros][j - ones]);
}
}
}
return dp[m][n];
};

## 完全背包问题

dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-wi*k]+vi * k) k>=1 && wi * k <=j

dp[2][5] = Math.max(dp[1][5], Math.max(dp[1][3] , dp[1][1] + 3) + 3)

dp[2][3]= Math.max(dp[1][3] , dp[1][1] + 3)

dp[2][5] = Math.max(dp[1][5],dp[2][3] + 3)

dp[i][j] = Math.max(dp[i-1][j], dp[i][j-wi]+vi)

``````/**
* @param {number[]} weights
* @param {number[]} values
* @param {number} n
* @param {number} w
* @return {number}
*/
export default (weights, valuse, n, w) => {
const dp = [];
for (let i = 0; i <= n; i++) {
dp[i] = new Array(w + 1).fill(0);
}

for (let i = 1; i <= n; i++) {
for (let j = 1; j <= w; j++) {
// 如果选择，
if (j >= weights[i - 1]) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weights[i - 1]] + valuse[i - 1]);
} else {
//如果不选择
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][w];
};

``````/**
* @param {number[]} weights
* @param {number[]} values
* @param {number} n
* @param {number} w
* @return {number}
*/
export default (weights, valuse, n, w) => {
const dp = new Array(w + 1).fill(0);
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= w; j++) {
// 如果选择，
if (j >= weights[i - 1]) {
dp[j] = Math.max(dp[j], [j - weights[i]] + valuse[i]);
}
}
}
return dp[w];
};

### 322. 零钱兑换

#### 题目描述

Input: coins = [1, 2, 5], amount = 11

output: 3

Input:coins = [2], amount = 3

output: -1

Input:coins = [1], amount = 0

output: 0

Input:coins = [1], amount = 1

output: 1

Input:coins = [1], amount = 2

output: 2

1 1 <= coins.length <= 12
2 1 <= coins[i] <= 2^31 - 1
3 0 <= amount <= 10^4

#### 思考

1 这是典型的完全背包问题，所以可以直接按照完全背包来解决就可以了

[186, 419, 83, 408], 6249 结果是20

2 这里也可以优化空间，优化空间有个技巧，就是自己画图，把dp的图每步的画出来，可以看下那些不需要，那些需要，就很容易可以看出那些可以优化

dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1， dp[i-2* coins[j]] +2 ...)

dp[5]= Math.min(dp[5], dp[3]+1， dp[1]+2)

#### 实现1

``````/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
// Runtime: 184 ms, faster than 25.00% of JavaScript online submissions for Coin Change.
// Memory Usage: 44.9 MB, less than 19.34% of JavaScript online submissions for Coin Change.
export default (coins, amount) => {
const len = coins.length;
const max = amount + 1;
const dp = [];
if (amount === 0) return 0;
for (let i = 0; i <= len; i++) {
dp[i] = new Array(max).fill(max);
}
// coins.sort((a, b) => a - b);
for (let i = 0; i <= len; i++) {
dp[i][0] = 0;
}
for (let i = 0; i <= amount; i++) {
dp[0][amount] = max;
}
for (let i = 1; i <= len; i++) {
for (let j = 0; j <= amount; j++) {
if (j >= coins[i - 1]) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1);
} else {
dp[i][j] = dp[i - 1][j];
}
}
}

return dp[len][amount] === max ? -1 : dp[len][amount];
};

#### 实现2

``````/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
// [1, 2, 5], 11;
// Runtime: 124 ms, faster than 63.93% of JavaScript online submissions for Coin Change.
// Memory Usage: 42.7 MB, less than 88.09% of JavaScript online submissions for Coin Change.
export default (coins, amount) => {
const Max = amount + 1;
const dp = new Array(amount + 1).fill(Max);
dp[0] = 0;
for (let i = 1; i <= amount; i++) {
for (let j = 0; j < coins.length; j++) {
if (i >= coins[j]) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
};