目錄
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;
}