Leetcode動態規劃(二)

目錄

11.pascals-triangle

12.pascals-triangle-ii

13.word-break

14.word-break-ii

15.scramble-string

16.edit-distance

17.distinct-subsequences

 18.interleaving-string

19.palindrome-partitioning

20.palindrome-partitioning-ii


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=3,返回[1,3,3,1]。

分析:跟上題類似,依據上一行每對相鄰的值計算出它的下一行,只是我們每次都在原來的行進行修改值,修改完之後再末尾加入1。每次更新i的值,需要的是i-1i的信息,進行倒着更新就不會影響下一次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;
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章