目录
11.pascals-triangle
题目:给出一个值numRows,生成杨辉三角的前numRows行。例如,给出 numRows = 5,返回[↵ [1],↵ [1,1],↵ [1,2,1],↵ [1,3,3,1],↵ [1,4,6,4,1]↵]
分析:动态规划。知道前一行,我们就能根据每对相邻的值轻松地计算出它的下一行,每一行的第一个和最后一个值均为1.
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> result = new ArrayList<>();
for(int i = 0;i < numRows;i++){
List<Integer> newRow = new ArrayList<>();
for(int j = 0;j <= i;j++) {
if (j == 0 || j == i)
newRow.add(1);
else
newRow.add(result.get(i - 1).get(j - 1) + result.get(i - 1).get(j));
}
result.add(newRow);
}
return result;
}
12.pascals-triangle-ii
题目:给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。例如,k=3,返回[1,3,3,1]。
分析:跟上题类似,依据上一行每对相邻的值计算出它的下一行,只是我们每次都在原来的行进行修改值,修改完之后再末尾加入1。每次更新i的值,需要的是i-1
和i
的信息,进行倒着更新就不会影响下一次i-1的更新。
public List<Integer> getRow(int rowIndex) {
List<Integer> cur = new ArrayList();
if(rowIndex == 0)
return cur;
cur.add(1);
for(int row = 1;row <= rowIndex;row++){
for(int i = row - 1;i > 0;i--){
cur.set(i,cur.get(i-1) + cur.get(i));
}
cur.add(1);
}
return cur;
}
13.word-break
题目:给定一个字符串s和一组单词dict,判断s是否可以用空格分割成一个单词序列,使得单词序列中所有的单词都是dict中的单词(序列可以包含一个或多个单词)。例如:给定s=“leetcode”;dict=["leet", "code"].返回true,因为"leetcode"可以被分割成"leet code".
分析:数组dp[i]表示前i个字符是否可以分割。dp[i] = dp[j] + dict.contains(s.subString(j,i))
public boolean wordBreak(String s, Set<String> dict) {
if (s.length() == 0 && dict.isEmpty())
return true;
Boolean[] dp = new Boolean[s.length() + 1];
dp[0] = true;
/* 状态转移方程:
f(i) 表示s[0,i)是否可以分词
f(i) = f(j) && f(j,i); 0 <= j < i;*/
for(int i = 1;i <= s.length();i++){
dp[i] = false;
for(int mid = i - 1;mid >= 0;mid--){
if(dp[mid] && dict.contains(s.substring(mid,i))){
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
14.word-break-ii
题目:给定一个字符串s和一组单词dict,在s中添加空格将s变成一个句子,使得句子中的每一个单词都是dict中的单词,返回所有可能的结果。例如:给定的字符串s ="catsanddog",dict =["cat", "cats", "and", "sand", "dog"].返回的结果为["cats and dog", "cat sand dog"].
分析:递归求解。将字符串切割为两部分左边s1和右边s2,如果s2包含在字典中,则递归计算s2切割生成的字符串集合。
//递归法
public ArrayList<String> wordBreak(String s, Set<String> dict) {
ArrayList<String> result = new ArrayList<>();
if(s.length() == 0 || dict.isEmpty())
return result;
f(result,s,dict,"",s.length());
return result;
}
private void f(ArrayList<String> result, String s, Set<String> dict, String cur, int index) {
if(index == 0){
result.add(cur.trim());
return;
}
for(int i = index - 1;i >= 0;i--){
if(dict.contains(s.substring(i,index)))
f(result,s,dict,s.substring(i,index) + " " + cur,i);
}
}
优化方法:动态规划,用一个HashMap来存储字符串分割的结果,避免重复计算。递归出口:如果s为空串需返回包含一个“”的ArrayList.
public ArrayList<String> wordBreak(String s, Set<String> dict) {
ArrayList<String> result = new ArrayList<>();
if(s.length() == 0 || dict.isEmpty())
return result;
HashMap<String,ArrayList<String>> map = new HashMap<>();
return dfs(s,dict,map);
}
private ArrayList<String> dfs(String s, Set<String> dict, HashMap<String, ArrayList<String>> map) {
if (map.containsKey(s))
return map.get(s);
ArrayList<String> list = new ArrayList<>();
if (s.equals("")) {//字符串到结尾时
list.add("");
return list;
}
int len = s.length();
for (int i = len - 1; i >= 0; i--) {
String sub = s.substring(i);//将字符串分成左右两部分
if (dict.contains(sub)) {
ArrayList<String> pre = dfs(s.substring(0,i), dict, map);
for(String s1 : pre)
list.add((s1 + " " + sub).trim());
}
}
map.put(s, list);//将求得结果存储到map中
return list;
}
15.scramble-string
题目:题目给出一个字符串s1,我们可以用递归的方法将字符串分成两个非空的子串来将s1表示成一个二叉树。下面是s1=“great”的一种二叉树的表现形式: great↵ / ↵ gr eat↵ / / ↵g r e at↵ / ↵ a t;将字符串乱序的方法是:选择任意的非叶子节点,交换它的两个孩子节点。例如:如果我们选择节点“gr”交换他的两个孩子节点,就会产生一个乱序字符串"rgeat". 我们称"rgeat"是"great"的一个乱序字符串。类似的:如果我们继续交换“eat”的两个孩子节点和“at”的两个孩子节点,会产生乱序字符串"rgtae".给出两个长度相同的字符串s1 和 s2,请判断s2是否是s1的乱序字符串。
分析:将字符串切分为前i个字符和后s.length()-i个字符,递归判断s1的前i个字符是否是s2的前i个字符或者s2的后i个字符的乱序。递归出口:统计两个字符串各个字符的数量,如果数量不相等则肯定是false,如果两个字符串相同,一定是true.
public boolean isScramble(String s1, String s2) {
if (s1.length() != s2.length())
return false;
if (s1.equals(s2))
return true;
int[] a = new int[26];
for (int i = 0;i < s1.length(); i++) {
a[s1.charAt(i) - 'a']++;
a[s2.charAt(i) - 'a']--;
}
for (int i = 0;i < a.length; i++) {
if (a[i] != 0) return false;
}
for(int i = 1;i < s1.length();i++){
if(isScramble(s1.substring(0,i),s2.substring(0,i)) &&
isScramble(s1.substring(i),s2.substring(i)))
return true;
if(isScramble(s1.substring(0,i),s2.substring(s1.length()-i)) &&
isScramble(s1.substring(i),s2.substring(0,s1.length()-i)))
return true;
}
return false;
}
16.edit-distance
题目:给定两个单词word1和word2,请计算将word1转换为word2至少需要多少步操作。你可以对一个单词执行以下3种操作:a)在单词中插入一个字符b)删除单词中的一个字符c)替换单词中的一个字符
分析:动态规划。dp[i][j]代表由word1的前i个子串变为word2的前j个子串的最少操作步数。若两个字符相等,dp[i][j] = dp[i-1][j-1];否则dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i-1][j-1]),dp[i][j-1]) + 1.
public int minDistance(String word1, String word2) {
int len1 = word1.length(),len2 = word2.length();
if(len1 == 0 || len2 == 0)
return len1 + len2;
int[][] dp = new int[len1+1][len2+1];
//初始化:当一个字符串为空时
for(int i = 0;i <= len1;i++)
dp[i][0] = i;
for(int i = 1;i <= len2;i++)
dp[0][i] = i;
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
dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]) + 1;
}
}
return dp[len1][len2];
}
17.distinct-subsequences
题目:给定一个字符串S和一个字符串T,计算S中的T的不同子序列的个数。字符串的子序列是由原来的字符串删除一些字符( 也可以不删除)在不改变相对位置的情况下的剩余字符(例如,"ACE"is a subsequence of"ABCDE"但是"AEC"不是)。例如:S ="rabbbit", T ="rabbit",返回3。
分析:需要一个二维数组dp[i][j]来记录长度为j的子串在长度为i的母串中出现的次数;首先初始化矩阵,当子串长度为0时,所有次数都是1;如果子串的最后一个字母和母串的最后一个字母不同,说明新加的母串字母没有产生新的可能性,可以沿用该子串在较短母串的出现次数,所以dp[i][j] = dp[i-1][j];否则dp[i][j] = dp[i-1][j] + dp[i-1][j-1] (考虑母串的第i位用不用的问题,如果不用,那dp[i][j]=dp[i-1][j];如果要用,就只需要考虑这个位置之前的匹配个数了,也就是dp[i][j]=dp[i-1][j-1])
public int numDistinct(String S, String T) {
int father = S.length(),son = T.length();
if(father < son)
return 0;
int[][] dp = new int[father+1][son+1];
//当T为空串时,只有一种子序列
for(int i = 0;i <= father;i++)
dp[i][0] = 1;
for(int i = 1;i <= father;i++){
for(int j = 1;j <= Math.min(i,son);j++){//子串长度大于父串,无子序列
if(S.charAt(i-1) == T.charAt(j-1))
dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
else
dp[i][j] = dp[i-1][j];
}
}
return dp[father][son];
}
18.interleaving-string
题目:给出三个字符串s1, s2, s3,判断s3是否可以由s1和s2交织而成。例如:给定s1 ="aabcc",s2 ="dbbca",如果s3 ="aadbbcbcac", 返回true;如果s3 ="aadbbbaccc", 返回false
分析:s3是由s1和s2交织生成的,意味着s3由s1和s2组成,在s3中s1和s2字符的顺序是不能变化的,和子序列题型类似,这种题一般是用动态规划来解。设dp[i][j]表示s3的前i+j个字符是否可以由s1的前i个字符和s2的前j个字符交织而成,状态转移方式为: dp[i][j] = (dp[i-1][j] && s3.charAt(i+j-1) == s1.charAt(i-1)) || (dp[i][j-1] && s3.charAt(i+j-1) == s2.charAt(j-1))
public boolean isInterleave(String s1, String s2, String s3) {
int len1 = s1.length(),len2 = s2.length();
if(len1 + len2 != s3.length())
return false;
boolean[][] dp = new boolean[len1+1][len2+1];
//初始化
dp[0][0] = true;
for(int i = 1;i <= len1;i++)
dp[i][0] = dp[i-1][0] && s1.charAt(i-1) == s3.charAt(i-1);
for(int i = 1;i <= len2;i++)
dp[0][i] = dp[0][i-1] && s2.charAt(i-1) == s3.charAt(i-1);
for(int i = 1;i <= len1;i++){
for(int j = 1;j <= len2;j++){//注意不能拆开赋值
dp[i][j] = s1.charAt(i-1) == s3.charAt(i+j-1) && dp[i-1][j]
|| s2.charAt(j-1) == s3.charAt(i+j-1) && dp[i][j-1];
}
}
return dp[len1][len2];
}
19.palindrome-partitioning
题目:给定一个字符串s,分割s使得s的每一个子串都是回文串,返回所有的回文分割结果。例如:给定字符串s="aab",返回 [↵ ["aa","b"],↵ ["a","a","b"]↵ ]
分析:回溯法。将字符串分割成两部分,若前半部分为回文,递归求解后半部分的分割结果即可。
public ArrayList<ArrayList<String>> partition(String s) {
ArrayList<ArrayList<String>> result = new ArrayList<>();
if(s.length() == 0)
return result;
ArrayList<String> list = new ArrayList<>();
util(result,list,s,0);
return result;
}
private void util(ArrayList<ArrayList<String>> result, ArrayList<String> list,String s, int index) {
if(index == s.length()){
result.add(new ArrayList<>(list));
return;
}
for(int i = index + 1;i <= s.length();i++){
String cur = s.substring(index,i);
if(isPalindrome(cur)){
list.add(cur);
util(result,list,s,i);
list.remove(list.size() - 1);
}
}
}
boolean isPalindrome(String s){
for(int low = 0,high = s.length()-1;low < high;low++,high--){
if(s.charAt(low) != s.charAt(high))
return false;
}
return true;
}
20.palindrome-partitioning-ii
题目:给出一个字符串s,分割s使得分割出的每一个子串都是回文串,计算将字符串s分割成回文分割结果的最小切割数。例如:给定字符串s="aab",返回1,因为回文分割结果["aa","b"]是切割一次生成的。
分析:若字符串本身就是回文,切割数为0;否则将字符串分割成两部分,若前半部分为回文,递归求解后半部分最小切割数,根据不同切割结果求出最小值,另外将每次求出的最小值都存入到map中,避免重复计算。
public int minCut(String s) {
if(s.length() <= 1)
return 0;
HashMap<String,Integer> map = new HashMap();
return minCut(s,map);
}
private int minCut(String s, HashMap<String, Integer> map) {
if(map.containsKey(s))
return map.get(s);
if(isPalindrome(s)){
map.put(s,0);
return 0;
}
int min = s.length() - 1;
for(int i = 1;i < s.length();i++){
if(isPalindrome(s.substring(0,i))){
int num = 1 + minCut(s.substring(i),map);
if(num < min)
min = num;
}
}
map.put(s,min);
return min;
}