lc139. 单词拆分(MD)

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true

**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。

示例 2:

输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
注意,你可以重复使用字典中的单词。

示例 3:

输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

提示:

  • 1 <= s.length <= 300
  • 1 <= wordDict.length <= 1000
  • 1 <= wordDict[i].length <= 20
  • swordDict[i] 仅由小写英文字母组成
  • wordDict 中的所有字符串 互不相同

先定义子问题:当遍历字符串s到下标i时,是否能够用字典中出现的一个或多个单词拼接出s?

那么根据这个子问题就能推出状态转移方程dp[i]=dp[j]&&check(s[ji1])dp[i] = dp[j] \&\& check(s[j \dots i-1]),其中check(s[ji1])check(s[j \dots i-1]) 表示子串s[ji1]s[j\dots i-1] 是否出现在字典中

class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> hash = new HashSet<>(wordDict);
int len = s.length();
boolean[] dp = new boolean[len + 1];
dp[0] = true;
for (int i = 1; i <= len; i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && hash.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[len];
}
}

lc322. 零钱转换(MD)

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

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

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

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:0

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 2^31 - 1
  • 0 <= amount <= 104

先定义子问题:凑成金额为mm 最少的硬币个数?(mamountm \le amount)

我们以样例1为例,讲讲我们的转移方程要怎么推

样例1中,硬币金额分别为[1, 2, 5],总金额要求是11

那么我们可以知道,对于金额mm , 它可以由这么几个方式获得:假设凑成金额mm 的硬币个数为dp[m]dp[m] ,对于本题,应该有:

dp[m]=dp[m1]+1dp[m] = dp[m - 1] + 1

dp[m]=dp[m2]+1dp[m] = dp[m - 2] + 1

dp[m]=dp[m5]+1dp[m] = dp[m - 5] + 1

用文字来表述就是:对于金额mm , 我们可以通过m1m - 1 再加上一个金额为 1 的硬币凑成mm , 也可以通过m2m - 2 再加上一个金额为 2 的硬币凑成mm,还可以通过m5m - 5 再加上一个金额为 5 的硬币凑成mm

这样我们就能得到子问题之间的递推关系了:dp[i]=min(dp[icoins[j]]+j)dp[i] = min(dp[i - coins[j]] + j)

class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
for (int i = 1; i <= amount; i++) {
dp[i] = amount + 1;
}
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}

lc300. 最长递增子序列(MD)

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

  • 1 <= nums.length <= 2500
  • -104 <= nums[i] <= 104

进阶:

  • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?

定义子问题:遍历数组到ii 时,此时最长严格递增子序列的长度为dp[i]dp[i]

所以对于两个下标iijj (i<ji < j) ,若nums[i]<nums[j]nums[i] < nums[j] ,则dp[j]=max(dp[i]+1,dp[j])dp[j] = max(dp[i] + 1, dp[j])

class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
int[] dp = new int[len];
for (int i = 0; i < len; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
}
}
int res = 0;
for (int i = 0; i < len; i++) {
res = Math.max(res, dp[i]);
}
return res;
}
}

lc120. 三角形最小路径和(MD)

给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 ii + 1

示例 1:

输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
2
3 4
6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

示例 2:

输入:triangle = [[-10]]
输出:-10

提示:

  • 1 <= triangle.length <= 200
  • triangle[0].length == 1
  • triangle[i].length == triangle[i - 1].length + 1
  • -104 <= triangle[i][j] <= 104

进阶:

  • 你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题吗?

定义子问题:对于一个(i,j)(i, j) 的点,从顶到这个点的最小路径和是多少?

根据这个三角形的特征,我们发现:对于点(i,j)(i, j) ,它只能由(i1,j1)(i - 1, j - 1)(i1,j)(i - 1, j) 转化而来。由此定义出转移方程:dp[i][j]=min(dp[i1][j1],dp[i1][j])+triangle[i][j]dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j]

class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[][] dp = new int[n][n];
dp[0][0] = triangle.get(0).get(0);
for (int i = 1; i < n; i++) {
dp[i][0] = dp[i - 1][0] + triangle.get(i).get(0);
for (int j = 1; j < i; j++) {
dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle.get(i).get(j);
}
dp[i][i] = dp[i - 1][i - 1] + triangle.get(i).get(i);
}
int res = dp[n - 1][0];
for (int i = 1; i < n; i++) {
res = Math.min(res, dp[n - 1][i]);
}
return res;
}
}

lc64. 最小路径和(MD)

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

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

示例 1:

img

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

示例 2:

输入:grid = [[1,2,3],[4,5,6]]
输出:12

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 200
  • 0 <= grid[i][j] <= 200

我觉得这道题和上一题还是很像的。如果上一题做出来了,这一题应该也很容易就能做出来。整体思路一样,直接放代码

class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
for (int i = 1; i < m; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int i = 1; i < n; i++) {
dp[0][i] = dp[0][i - 1] + grid[0][i];
}
for (int i = 1; i < m; i++) {
for (int 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];
}
}