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

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值