动态规划案例解析

此文总结了本人在刷LeetCode是遇到的动态规划问题以及对应的解法。

word-break

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
For example, given
s =”leetcode”,
dict =[“leet”, “code”].
Return true because”leetcode”can be segmented as”leet code”.

给定一个字符串S以及一个字典dict,判断S是否可以由dict组成。
解题思路:使用动态规划,将字符串分割S,使用dp[i]来表示字符串的前i个字符组成的是否可以由dict组成。使用dp[j]表示前j个字符是否可由dict组成,且j小于i。此时可得状态转移方程:
dp[i] = dp[j]&&(字符串S从j→i组成的字符为单词)。
举例题描述作为例子:
dp[8] = dp[4]&&(字符串S从5→8组成的字符是code为单词)。
dp[4] = dp[0]&&(字符串S从1→4组成的字符是leet为单词)。
dp[0]我们默认为true。

java的AC代码如下:

public boolean wordBreak(String s, Set<String> dict) {
        //使用d[i]表示s[0-i]是否可以匹配
        //d[i] = d[j]&&(s[j+1-i]也是单词)
        //j表示分词座标,0<j<i.
        //以leetcode为例
        //d[8]=d[4]&&(s[5-8]=code也是单词) 所以为true
        //d[4] = d[0]&&(s[1-4]=leet也是单词) 所以为true
        int len = s.length();
        boolean[] dp = new boolean[len+1];
        dp[0] = true;
        for(int i=1;i<=len;i++){
            for(int j=0;j<=i;j++){
                //如果前面j个可由字典组成,并且j→i也是单词
                if(dp[j]==true&&dict.contains(s.substring(j,i))){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[len];
    }

palindrome-partitioning

Given a string s, partition s such that every substring of the partition is a palindrome.
Return the minimum cuts needed for a palindrome partitioning of s.
For example, given s =”aab”,
Return1since the palindrome partitioning[“aa”,”b”]could be produced using 1 cut.
给定一个字符串S,我们需要将S分割成一个一个回文的字符串,需要分割多少次?

解题思路:假设dp[i]表示字符串前i个(包括i)个字符串需要的分割次数。
1.如果字符串前i个形成的子串是回文,那么dp[i] = 0;f否则,我们先假设dp[i] = i-1,取最坏的情况;
2.设一个变量j,j小于i,如果S[j→i]的子串是回文,那么dp[i] = min(dp[i], dp[j-1]+1);
3.如果S[j→i]的子串不是回文,那么dp[i] = min(dp[i], dp[j-1]+1+i-j),dp[j-1]+1+i-j中的(i-j)表示对于S[j→i]我们需要分割i-j次,使其编程单个字符。

java的AC代码如下:

public int minCut(String s) {
        int len = s.length();
        int[] dp = new int[len];
        for(int i=0;i<len;i++){
            dp[i] = isPalindrome(s.substring(0,i+1))==true? 0:i;
            for(int j=1;j<=i;j++){
                if(isPalindrome(s.substring(j,i+1)))//如果j→i字符串是回文
                    dp[i] = Math.min(dp[i],dp[j-1]+1);
                else
                    dp[i] = Math.min(dp[i],dp[j-1]+i-j+1);
            }
        }
        return dp[len-1];
    }
    //判断字符串是否为回文
    public boolean isPalindrome(String s){
      for(int i=0,j=s.length()-1;i<j;i++,j--){
          if(s.charAt(i) != s.charAt(j))
              return false;
      }
      return true;
  }

distinct-subsequences

Given a string S and a string T, count the number of distinct subsequences of T in S.
A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie,”ACE”is a subsequence of”ABCDE”while”AEC”is not).
Here is an example:
S =”rabbbit”, T =”rabbit”
Return3.

通过删除字符(可以不删除),使得S变为T,计算出有几种不同的删除方法。

解题思路:
1.使用dp[i][j]表示将S.substring(0,i)变为T.substring(0,j)的方法数。
2.我们知道任何一个字符串变为空,只有一种方法,所以dp[i][0] = 1;
3.如果S的i字符和T的j字符相同,那么就有两种策略,如果不删除,那么dp[i][j] = dp[i-1][j-1];如果删除那么dp[i][j] = dp[i-1][j]
4.如果S的i字符和T的j字符不相同,那么只能讲S的i字符删除,dp[i][j] = dp[i-1][j];

java的AC代码:

public int numDistinct(String S, String T) {
        if(S==null||T==null)
            return 0;
        if(S.length()<T.length())
            return 0;
        int[][] dp = new int[S.length()+1][T.length()+1];
        dp[0][0] = 1;
        //任何一个字符串变为空只有一种方法
        for (int i = 1; i <S.length(); i++) {
            dp[i][0] = 1;
        }
        for (int i = 1; i <= S.length(); i++) {
            for (int j = 1; j <= T.length(); j++) {
                dp[i][j] = dp[i-1][j];
                if(S.charAt(i-1)==T.charAt(j-1))
                    dp[i][j] += dp[i-1][j-1];
            }
        }
        return dp[S.length()][T.length()];
    }

minimum-path-sum

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.
Note: You can only move either down or right at any point in time.

给定一个充满非负数的m×N网格,从左上角到右下角找到一条路径,它沿着路径将所有数的总和最小化。

解题思路:我们这里假设dp[i][j]表示从原点到(i,j)所有的数总和。
那么状态转移方程可以表示为dp[i][j] = min(dp[i][j-1],dp[i-1][j])+点(i,j)的数;
并且dp[i][0] = dp[i-1][0]+点(i,0)的数;dp[0][j] = dp[0][j-1]+点(0,j)的数;

java的AC代码如下:

public int minPathSum(int[][] grid) {
        if (grid == null)
            return 0;
        int[][] dp = new int[grid.length+1][grid[0].length+1];
        dp[0][0] = grid[0][0];
        for(int i=1;i<grid.length;i++)
            dp[i][0] = dp[i-1][0]+grid[i][0];//第一行初始化
        for(int j=1;j<grid[0].length;j++)
            dp[0][j] = dp[0][j-1]+grid[0][j];//第一列初始化
        for(int i=1;i<grid.length;i++){
            for(int j=1;j<grid[0].length;j++)
                dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
        }
        return dp[grid.length-1][grid[0].length-1];
    }

edit-distance

Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)
You have the following 3 operations permitted on a word:
a) Insert a character
b) Delete a character
c) Replace a character

给定两个字符串word1 and word2,求出从word1转化为word2需要的最小的步骤。
下列操作表示一步:
a)插入字符
b)删除字符
c)替换字符

解题思路:使用dp[i][j]表示word1的前i个转化为word2的前j个字符组要的最小步骤。
1.任何字符串转化为空的步骤=非空字符串的字符个数,也就是dp[i][0] = i;dp[0][j] = j;
2.如果word1.charAt(i) == word2.charAt(j ),那么此时步骤等同于dp[i][j] = dp[i-1][j-1],abc,dfc这种情况与ab,df花费是一样的
3.如果word1.charAt(i) != word2.charAt(j ),那么就取删、改、增三种操作最少的情况再+1。

AC代码:

public int minDistance(String word1, String word2) {
        if(word1==null&&word2==null) return 0;
        if(word1==null||word1.length()==0) return word2.length();
        if(word2==null||word2.length()==0) return word1.length();
        int[][] dp = new int[word1.length()+1][word2.length()+1];
        dp[0][0] = 0;
        //初始化dp
        for(int i=1;i<=word1.length();i++) dp[i][0] = i;
        for(int j=1;j<=word2.length();j++) dp[0][j] = j;
        for(int i=1;i<=word1.length();i++){
            for(int j=1;j<=word2.length();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[word1.length()][word2.length()];
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章