题目描述
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。请你判断是否可以利用字典中出现的单词拼接出 s
。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 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
s
和 wordDict[i]
仅由小写英文字母组成wordDict
中的所有字符串 互不相同public class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
// 题目已经说非空数组,可以不做非空判断
int sum = 0;
for (int num : nums) {
sum += num;
}
// 特判:如果是奇数,就不符合要求
if ((sum & 1) == 1) {
return false;
}
int target = sum / 2;
// 创建二维状态数组,行:物品索引,列:容量(包括 0)
boolean[][] dp = new boolean[len][target + 1];
// 先填表格第 0 行,第 1 个数只能让容积为它自己的背包恰好装满
if (nums[0] <= target) {
dp[0][nums[0]] = true;
}
// 再填表格后面几行
for (int i = 1; i < len; i++) {
for (int j = 0; j <= target; j++) {
// 直接从上一行先把结果抄下来,然后再修正
dp[i][j] = dp[i - 1][j];
if (nums[i] == j) {
dp[i][j] = true;
continue;
}
if (nums[i] < j) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
}
}
}
return dp[len - 1][target];
}
}
dp[i]
表示字符串 s
的前 i
个字符是否可以用 wordDict
中的单词拼接得到。
解题步骤如下:
初始化一个大小为 s.length() + 1
的布尔数组 dp
,并将 dp[0]
设置为 true
,表示空字符串总是可以拼接的。
对于每个从 1
到 s.length()
的索引 i
,遍历检查:
wordDict
中的单词 word
,如果 word
的长度小于等于 i
且 dp[i - word.length()]
为 true
,则检查 s
从位置 i - word.length()
到 i
的子串是否等于 word
。word
,则将 dp[i]
设置为 true
。在完成上述步骤后,dp[s.length()]
将给出答案,表示整个字符串 s
是否可以由 wordDict
中的单词拼接得到。
import java.util.List;
public class Solution {
public boolean wordBreak(String s, List wordDict) {
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (String word : wordDict) {
if (word.length() <= i) {
if (dp[i - word.length()]) {
if (s.substring(i - word.length(), i).equals(word)) {
dp[i] = true;
break;
}
}
}
}
}
return dp[s.length()];
}
}
题目描述
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
dp[i][j]
表示从数组的 [0, i]
这个子区间内挑选一些正整数,每个数只能用一次,使得这些数的和恰好等于 j
。
状态转移方程:很多时候,状态转移方程思考的角度是「分类讨论」,对于「0-1 背包问题」而言就是「当前考虑到的数字选与不选」。
状态转移方程:
dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]
public class Solution {
public boolean canPartition(int[] nums) {
int len = nums.length;
// 题目已经说非空数组,可以不做非空判断
int sum = 0;
for (int num : nums) {
sum += num;
}
// 特判:如果是奇数,就不符合要求
if ((sum & 1) == 1) {
return false;
}
int target = sum / 2;
// 创建二维状态数组,行:物品索引,列:容量(包括 0)
boolean[][] dp = new boolean[len][target + 1];
// 先填表格第 0 行,第 1 个数只能让容积为它自己的背包恰好装满
if (nums[0] <= target) {
dp[0][nums[0]] = true;
}
// 再填表格后面几行
for (int i = 1; i < len; i++) {
for (int j = 0; j <= target; j++) {
// 直接从上一行先把结果抄下来,然后再修正
dp[i][j] = dp[i - 1][j];
if (nums[i] == j) {
dp[i][j] = true;
continue;
}
if (nums[i] < j) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
}
}
}
return dp[len - 1][target];
}
}
本质上,这个问题是一个子集求和问题,类似于背包问题。问题可以被转化为:是否可以从数组中选择一些数字,使得这些数字的总和等于数组所有元素总和的一半。
以下是解决这个问题的步骤:
计算总和:首先,计算数组 nums
的总和。如果总和是奇数,那么不能分割成两个和相等的子集,因为两个整数和不能为奇数。
初始化动态规划数组:创建一个布尔型动态规划数组 dp
,其大小为 sum / 2 + 1
。dp[i]
表示数组是否可以形成总和为 i
的子集。初始化 dp[0]
为 true
,因为总和为 0 总是可能的。
填充动态规划数组:对于 nums
中的每个数字 num
,从 sum / 2
到 num
倒序遍历 dp
数组,更新 dp[j] = dp[j] || dp[j - num]
。
检查结果:最后,返回 dp[sum / 2]
的值。如果 dp[sum / 2]
为 true
,表示可以分割数组成两个和相等的子集。
//dp[i] 表示数组是否可以形成总和为 i 的子集。初始化 dp[0] 为 true,因为总和为 0 总是可能的。
public class Solution {
public boolean canPartition(int[] nums) {
// 计算数组总和
int sum = 0;
for (int num : nums) {
sum += num;
}
// 如果总和是奇数,则不能分割成两个和相等的子集
if (sum % 2 != 0) {
return false;
}
// 计算子集总和目标值(数组总和的一半)
sum /= 2;
// 初始化动态规划数组
boolean[] dp = new boolean[sum + 1];
dp[0] = true; // 总和为0总是可能的
// 遍历数组中的每个数字
for (int num : nums) {
// 从子集总和目标值向下遍历到当前数字
for (int i = sum; i >= num; i--) {
// 更新动态规划数组的值
// dp[i] = true 如果不包含当前数字就已经能组成子集(dp[i])
// 或者包含当前数字就能组成子集(dp[i - num])
//考虑 dp[i] 时,dp[i - num] 代表的是不包含当前考虑的 num 的解.num在外层循环,内层循环中对同一个位置的dp只有影响1次
dp[i] = dp[i] || dp[i - num];
}
}
// 如果dp[sum]为true,则表示可以分割数组成两个和相等的子集
return dp[sum];
}
}