单词拆分1
给定一个非空字符串 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
动态规划
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> set = new HashSet<>();
int max_length = 0;
for(String str : wordDict){
set.add(str);
max_length = Math.max(max_length, str.length());
}
int len = s.length();
boolean[] dp = new boolean[len + 1];
dp[0] = true;
for (int i = 1; i <= len; i++) {
for (int j = i - 1; j >= 0 && j >= i - max_length; j--) {
if (dp[j] && set.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[len];
}
}
单词拆分2
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
说明:
分隔时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入:
s = “catsanddog”
wordDict = [“cat”, “cats”, “and”, “sand”, “dog”]
输出:
[
“cats and dog”,
“cat sand dog”
]
示例 2:
输入:
s = “pineapplepenapple”
wordDict = [“apple”, “pen”, “applepen”, “pine”, “pineapple”]
输出:
[
“pine apple pen apple”,
“pineapple pen apple”,
“pine applepen apple”
]
解释: 注意你可以重复使用字典中的单词。
示例 3:
输入:
s = “catsandog”
wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出:
[]
动态规划+回溯
class Solution {
private Set<String> wordSet = new HashSet<>();
private boolean[] dp;
public List<String> wordBreak(String s, List<String> wordDict) {
int max_length = 0;
for(String str : wordDict){
wordSet.add(str);
max_length = Math.max(max_length, str.length());
}
int len = s.length();
dp = new boolean[len + 1];
dp[0] = true;
for (int i = 1; i <= len; i++) {
for (int j = i - 1; j >= 0 && j >= i - max_length; j--) {
if (dp[j] && wordSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
List<String> result = new ArrayList<>();
if (dp[len])
backtrack(s, len, new LinkedList<String>(), result);
return result;
}
private void backtrack(String s, int len, Deque<String> path, List<String> res) {
if (len == 0) {
res.add(String.join(" ", path));
return;
}
for (int i = len - 1; i >= 0; i--) {
String suffix = s.substring(i, len);
if (wordSet.contains(suffix) && dp[i]) {
path.addFirst(suffix);
backtrack(s, i, path, res);
path.removeFirst();
}
}
}
}
解码方法
一条包含字母 A-Z 的消息通过以下映射进行了 编码 :
‘A’ -> 1
‘B’ -> 2
…
‘Z’ -> 26
要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“111” 可以将 “1” 中的每个 “1” 映射为 “A” ,从而得到 “AAA” ,或者可以将 “11” 和 “1”(分别为 “K” 和 “A” )映射为 “KA” 。注意,“06” 不能映射为 “F” ,因为 “6” 和 “06” 不同。
给你一个只含数字的 非空 字符串 num ,请计算并返回 解码 方法的 总数 。
题目数据保证答案肯定是一个 32 位 的整数。
示例 1:
输入:s = “12”
输出:2
解释:它可以解码为 “AB”(1 2)或者 “L”(12)。
示例 2:
输入:s = “226”
输出:3
解释:它可以解码为 “BZ” (2 26), “VF” (22 6), 或者 “BBF” (2 2 6) 。
示例 3:
输入:s = “0”
输出:0
解释:没有字符映射到以 0 开头的数字。含有 0 的有效映射是 ‘J’ -> “10” 和 ‘T’-> “20” 。由于没有字符,因此没有有效的方法对此进行解码,因为所有数字都需要映射。
示例 4:
输入:s = “06”
输出:0
解释:“06” 不能映射到 “F” ,因为字符串开头的 0 无法指向一个有效的字符。
动态规划
dp[i] 为前 i+1 位的解码方法的总数。
class Solution {
public int numDecodings(String s) {
if (s.charAt(0) == '0')
return 0;
int len = s.length();
char[] charArray = s.toCharArray();
int[] dp = new int[len + 1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= len; i++) {
if (charArray[i - 1] != '0')
dp[i] = dp[i - 1];
int num = (charArray[i - 2] - '0') * 10 + (charArray[i - 1] - '0');
if (num > 9 && num <= 26)
dp[i] += dp[i - 2];
}
return dp[len];
}
}
编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
示例 1:
输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)
示例 2:
输入:word1 = “intention”, word2 = “execution”
输出:5
解释:
intention -> inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention -> exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)
动态规划
对单词 A 删除一个字符和对单词 B 插入一个字符是等价的。例如当单词 A 为 doge,单词 B 为 dog 时,我们既可以删除单词 A 的最后一个字符 e,得到相同的 dog,也可以在单词 B 末尾添加一个字符 e,得到相同的 doge;同理,对单词 B 删除一个字符和对单词 A 插入一个字符也是等价的;对单词 A 替换一个字符和对单词 B 替换一个字符是等价的。
这样以来,本质不同的操作实际上只有三种:
- 在单词 A 中插入一个字符;
- 在单词 B 中插入一个字符;
- 修改单词 A 的一个字符。
用 D[i][j] 表示 A 的前 i 个字母和 B 的前 j 个字母之间的编辑距离。
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length();
int len2 = word2.length();
int[][] dp = new int[len1 + 1][len2 + 1];
for (int i = 0; i <= len1; i++)
dp[i][0] = i;
for (int j = 0; j <= len2; j++)
dp[0][j] = j;
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1]; // 不需要编辑
} else {
int insert1 = dp[i][j - 1] + 1; // A 插入 B 的第j个字符
int insert2 = dp[i - 1][j] + 1; // B 插入 A 的第i个字符
int replace = dp[i - 1][j - 1] + 1; // A 替换成 B 的第j个字符
dp[i][j] = Math.min(replace, Math.min(insert1, insert2));
}
}
}
return dp[len1][len2];
}
}
最小操作次数使数组元素相等
给定一个长度为 n 的 非空 整数数组,每次操作将会使 n - 1 个元素增加 1。找出让数组所有元素相等的最小操作次数。
示例:
输入:
[1,2,3]
输出:
3
解释:
只需要3次操作(注意每次操作会增加两个元素的值):
[1,2,3] => [2,3,3] => [3,4,3] => [4,4,4]
动态规划
class Solution {
public int minMoves(int[] nums) {
Arrays.sort(nums);
int add = 0, len = nums.length;
for (int i = 1; i < len; i++) {
nums[i] += add;
add += nums[i] - nums[i - 1];
}
return add;
}
}
利用差值
class Solution {
public int minMoves(int[] nums) {
Arrays.sort(nums);
int add = 0, len = nums.length;
for (int i = 1; i < len; i++)
add += nums[i] - nums[0];
return add;
}
}
数学法
将除了一个元素之外的全部元素+1,等价于将该元素-1,只需要将所有的数都减到最小的数即可。
class Solution {
public int minMoves(int[] nums) {
int min = Integer.MAX_VALUE, count = 0;
for (int num : nums)
min = Math.min(num, min);
for (int num : nums)
count += num - min;
return count;
}
}