Leetcode-DP(Dynamic Programming)

Leetcode 5. Longest Palindromic Substring. 【Medium】【Green】
palindrome: [n.]回文/回环。Palindromic, [adj.]。例如 "dad","cdadc", "abba"

题目简介:找出最长的回环substring。
一个典型写法:for遍历整个string,在for循环内用helper函数来计算以第i个或(i和i+1)个字符为中心的substring是不是回环。可以把max写到全局变量,写返回void的helper。若不写全局变量,就返回substring。有用boolean[][]的dp写法,但太难写。
Time:O(n^2), Space: O(1).
若不写全局变量:

class Solution {
    public String longestPalindrome(String s) {
        String max = "";
        for(int i=0; i<s.length(); i++){
            String s1 = helper(s,i,i); 
            String s2 = helper(s,i,i+1); 
            if(s1.length()>max.length()) max = s1;
            if(s2.length()>max.length()) max = s2;
        }
        return max;
        
    }
    private String helper(String s, int a, int b){
        while(a>=0 && b<s.length()){
            if(s.charAt(a) != s.charAt(b)) break;
            a--;
            b++;
        }
        return s.substring(a+1,b);        
    }
}

若写全局变量:

    String max = "";
    public String longestPalindrome(String s) {
        if(s==null || s.length()<=1) return s;
        for(int i=0; i<s.length(); i++){
            helper(s,i,i); 
            helper(s,i,i+1); 
        }
        return max;
        
    }
    private void helper(String s, int a, int b){
        while(a>=0 && b<s.length()){
            if(s.charAt(a) != s.charAt(b)) break;
            a--;
            b++;
        }
        if(b-a-1>max.length()) max = s.substring(a+1,b);     
        //return s.substring(a+1,b);     
    }

Leetcode 53. Maximum Subarray. 【Easy】【Green】
题目简介:找出sum最大的subarray; 有followUp,要求用divide & conquer.
设置dp[]来记录每一项的情况。(设置int curMax更好,因为只需要O(1)空间。) 遍历数组,判断cur,判断res。

public int maxSubArray(int[] nums) {
        int dp[] = new int[nums.length];
        dp[0] = nums[0];
        int res = nums[0];
        for(int i=1; i<nums.length; i++){
            dp[i] = dp[i-1]<0? nums[i]: dp[i-1]+nums[i];
            res = Math.max(res, dp[i]);
        }
        return res;
    }

FollowUp:" How would we solve this given that there is an endless incoming stream of numbers ?" 若是endless,我们就无法定义int[] dp。需要设置cur来记录当前的max。这就是Kadane's Algorithm. (发音/ka:'dein/)

public int maxSubArray(int[] nums) {
        int cur= nums[0];
        int res = nums[0];
        for(int i=1; i<nums.length; i++){
            cur = cur<0? nums[i]:nums[i]+cur;  // 或者用max写: cur = Math.max(nums[i],cur+nums[i])
            res = Math.max(res, cur);    // //或者用max写: res = Math.max(res,cur);
        }
        return res;
    }

FollowUp: 若是需要记录开始和结束位置呢?

    public int maxSubArray(int[] nums) {
        int curMax = nums[0];
        int globalMax = nums[0];
        
        int start=0, end=0,globalStart=0,globalEnd=0;
        
        for(int i=1; i<nums.length; i++){          
            if(curMax<0){
                curMax=nums[i];
                start=i;
                end=i;                
            } else {
                curMax += nums[i];
                end++;                               
            }
            if(curMax>globalMax){
                globalMax = curMax;
                globalStart=start;
                globalEnd = end;  //其实也可以写一个end就行,不需两个end
            }            
        }        
        return new int[globalMax,globalStart,globalEnd];
    }

FollowUp: subarray should have At least k number.

没答案。

原题FollowUp:用divide & conquer 解决(时间复杂度)。
https://blog.csdn.net/xshengh/article/details/12708291

    public int maxSubArray(int[] nums) {
        return divide(nums,0,nums.length-1);        
    }
    private int divide(int[] nums,int start, int end){
        if(start>=end) return nums[start];//大于的情况就当是中间情况了
        int mid=(start+end)/2;
        int maxleft=divide(nums,start,mid-1);//左边最大和
        int maxright=divide(nums,mid+1,end);
        //计算包含中间的连续最大和
        int midMax = nums[mid];    
        for(int temp=mid-1,leftSum=nums[mid];temp>=start;temp--){//计算左边
            leftSum+=nums[temp];
            midMax=midMax>leftSum?midMax:leftSum;
        }
        for(int temp=mid+1,sum=midMax;temp<=end;temp++){//计算右边
            sum+=nums[temp];
            midMax=midMax>sum?midMax:sum;
        }
        return Math.max(maxleft,Math.max(maxright,midMax));//最大子串在左边,最大子串在右边,或者包含中间那个数       
    }

Leetcode 121. Best Time to Buy and Sell Stock.【Green】【Easy】
题目简介:股票买一次卖一次,求最大利润。
Time: O(n), Space: O(1).
设置int min和profit,用Math.min和Math.max, 遍历一次即可,类似Leetcode 53的解法。

class Solution {
    public int maxProfit(int[] prices) {
        if(prices==null || prices.length<=1) return 0;
        int min = prices[0];
        int profit = 0;
        for(int price: prices){
            min = Math.min(min,price);  //参照123的写法,可以写: buy=Math.max(buy, -price) 找出-price最大即最小的price
            profit = Math.max(profit,price-min);
        }
        return profit;
    }
}

Follow Up1:
Leetcode 122. Best Time to Buy and Sell Stock II 【Easy】【Yellow】
题目简介:股票买卖多次,求最大利润。
贪心Greedy解法。设置profit,遍历数组,若prices[i] > prices[i-1] 就把差值加入到profit。
Time: O(n), Space: O(1).

class Solution {
    public int maxProfit(int[] prices) {
        if(prices==null || prices.length<2) return 0;
        int profit = 0;
        for(int i=1; i<prices.length; i++){
            //if(prices[i]>prices[i-1]) profit += prices[i]-prices[i-1];
            profit += prices[i]>prices[i-1]? prices[i]-prices[i-1]:0;
        }
        return profit;
    }
}

FollowUp2:
Leetcode 123. Best Time to Buy and Sell Stock III 【Hard】【Yellow】
题目简介:股票仅买卖两次, (sell1完成后才能进行buy2), 求最大利润。
https://blog.csdn.net/MebiuW/article/details/52764801
设置buy1, buy2为MIN_VALUE(这里是将price记为-price, 求的是max), sell1, sell2为0. 遍历数组,在for循环内写buy1,sell1;然后写buy2,sell2. 和121区别是,buy2里面写了sell1-price。也可以写Math.max().
Time: O(n), Space: O(1).

public int maxProfit(int[] prices) {
        int buy1 = Integer.MIN_VALUE, buy2 = Integer.MIN_VALUE;
        int sell1 = 0, sell2 = 0;
        for(int price : prices){
            //if ( buy1 < -price) buy1 = -price;    //The first buy, always choose the cheapest one currently to buy, same logic as Leetcode 121  
            //if ( sell1 < buy1 + price ) sell1 = buy1 + price;   //The max profit for the first buy, same logic as Leetcode 121 
            //if ( buy2 < sell1 - price) buy2 = sell1 - price;  //Notice that in buy2 we have added sell1. So the profit for first buy has been included in second buy 
            //if ( sell2 < buy2 + price ) sell2 = buy2 + price;  //The second buy, we can have profit for both first buy and second buy. 
            Math.max 格式:
            buy1 = Math.max(buy1, -price);
            sell1 = Math.max(sell1,buy1+price);
            buy2 = Math.max(buy2, sell1-price);
            sell2 = Math.max(sell2, buy2+price); 
        }
        return sell2;
}

若是按照121的写法(buy1和buy2记为正数price),就是这样的代码写法:

class Solution {
    public int maxProfit(int[] prices) {
        //Integer buy1 = Integer.MIN_VALUE, buy2 = Integer.MIN_VALUE;
        Integer buy1 = Integer.MAX_VALUE, buy2 = Integer.MAX_VALUE;
        Integer sell1 = 0, sell2 = 0; 
        for(int price: prices){
            // buy1 = Math.max(buy1, -price);
            // sell1 = Math.max(sell1,buy1+price);
            // buy2 = Math.max(buy2, sell1-price);
            // sell2 = Math.max(sell2, buy2+price); 
            
            buy1 = Math.min(buy1, price);
            sell1 = Math.max(sell1,price-buy1);
            buy2 = Math.min(buy2, -sell1+price);
            sell2 = Math.max(sell2, price-buy2);                         
        }
        return sell2;
    }
}

FollowUp3:
Leetcode 188. Best Time to Buy and Sell Stock IV. 【Hard】【Medium】
题目简介:


FollowUp4:
Leetcode 【】
题目简介:


FollowUp5:
Leetcode 【】
题目简介:


FollowUp6:
Leetcode 【】
题目简介:


Leetcode 【】
题目简介:


Leetcode 【】
题目简介:


Leetcode 975. Odd Even Jump. 【Hard】【Green】
题目简介:给一个int array, 从每一项开始跳,跳的规则是奇数次跳(第1,3,5,...)跳向后面的min 大于本项的值,偶数次跳(第2,4,6,...)跳向max 小于本项的值,求能到达最后一项的个数。
想法:最后一项肯定能到达最后一项,所以res可以设定为1.
(1)倒序遍历。
(2)需要用boolean[] jumpToHigher 和boolean[] jumpToLower 来记录从这一项出发能否通过跳到high和跳到lower,并最终达到目标。若本项的jumpToHigher为true,说明本项可以跳到最后一项。
(3)倒序遍历每一项时,需要找到下一步higher或lower的位置,那么就需要在内部进行顺序遍历。为了避免顺序遍历,就需要存储在map中。而要找到higher和lower的位置,最好是map内元素是有序的,那么TreeMap就满足规则。要满足跳的规则,需要用TreeMap来记录,key是某项的值(TreeMap默认对key值进行排序),value是该项的index。用TreeMap.floorKey()和ceilingKey()方法找到下次跳跃的值,然后用TreeMap.get()知道下次跳跃的index。
改进:或者用Map.Entry, floorEntry()和ceilingEntry(),直接获得entry,然后(int) Entry.getValue()方法获得index,这样可以避免get方法导致的O(logn). 】
TreeMap特点:
1.无序,不允许重复(无序指元素顺序与添加顺序不一致)
2.TreeMap集合默认会对键进行排序,所以键必须实现自然排序和定制排序中的一种
3..底层使用的数据结构是二叉树

class Solution {
    public int oddEvenJumps(int[] A) {
        if(A==null || A.length==0)  return 0;
        boolean[] jumpToHigher = new boolean[A.length], jumpToLower = new boolean[A.length];
        jumpToHigher[A.length-1] = true;
        jumpToLower[A.length-1] = true;
        int res = 1;
        TreeMap<Integer,Integer> treeMap = new TreeMap<>();
        treeMap.put(A[A.length-1],A.length-1);
        for(int i=A.length-2; i>=0; i--){
            //Map.Entry lowEntry = treeMap.floorEntry
            Integer lowKey = treeMap.floorKey(A[i]), highKey = treeMap.ceilingKey(A[i]);
            if(lowKey != null) jumpToLower[i] = jumpToHigher[treeMap.get(lowKey)];
            if(highKey != null) jumpToHigher[i] = jumpToLower[treeMap.get(highKey)];
            if(jumpToHigher[i]) res++;
            treeMap.put(A[i],i);
        }
        return res;        
    }
}

改进:由treeMap.floorKey()和ceilingKey()换为treeMap.floorEntry()和ceilingEntry()。

        for(int i=A.length-2; i>=0; i--){
            Map.Entry lowEntry = treeMap.floorEntry(A[i]), highEntry = treeMap.ceilingEntry(A[i]);
            //Integer lowKey = treeMap.floorKey(A[i]), highKey = treeMap.ceilingKey(A[i]);
            if(lowEntry != null) jumpToLower[i] = jumpToHigher[Integer.parseInt(lowEntry.getValue().toString())];
            if(highEntry != null) jumpToHigher[i] = jumpToLower[Integer.parseInt(highEntry.getValue().toString())];
            if(jumpToHigher[i]) res++;
            treeMap.put(A[i],i);
        }

Leetcode 10. Regular Expression Matching. 【Hard】【Green】
题目简介:模式匹配题,' . '匹配任意一个英文字符, ' * ' 匹配0或多个previous char(前一个字符)。
做法1,dp ( Time:O(mn) );做法2,Recursion(TimeComplexity高,不推荐)
dp的做法,
需要在双层for循环之前,加上一个for循环,用于排除 p的全端是 "a
bc ..."匹配了s的0个字符的情况【特殊情况K】。

需要用二维array:boolean[][]来记录匹配情况,之所以设置[sLength+1][pLength+1], 是因为需要dp[0][0]来表示s的前0个字符和p的前0个字符相匹配(we need to set dp[0][0]=true to present the starting state)。这样dp[i][j] 表示的是s的前i个字符和p的前j个字符是否匹配。

Base case:
(1) origin: dp[0][0]: they do match, so dp[0][0] = true.
(2) first row: dp[0][j]: for String p starts with 多个 任意字母+, all true. (p若以多个abc开头,可以匹配0个s的字符, 即dp[0][xx]=true (xx是连续多个abc*的位置))
Base case(1)要求 dp = boolean[s.length()+1][p.length()+1]
Base case(2)需要写一个for循环,遍历p。
然后写两层for循环,for循环内的判断条件是:

  • if(字符匹配或 . 匹配: sc[i-1]==pc[j-1] || pc[j-1]=='.') dp[i][j] = dp[i-1][j-1];
  • else if( * 匹配: pc[j-1]=='*')
    分两种情况:
    if (上一个p字符匹配当前s字符: pc[j-2] =='.' ||pc[j-2] == sc[i-1]) dp[i][j] = (dp[i][j-1]||dp[i][j-2]]||dp[i-1][j); 【 Condition1,2,3. 】
    else : dp[i][j] = dp[i][j-2] 【Condition2】
p[j]='*'条件下, p[j-1]==s[i]或'.'时, 3种Condition.jpg

3 conditions :
Condition1: dp[i][j]=dp[i][j-1], s前i个字符 matches p前j-1个字符
Condition2: dp[i][j]=dp[i][j-2], s前i个字符 matches p前j-2个字符
Condition3: dp[i][j]=dp[i-1][j], s前i-1个字符 matches p前j个字符
英文表述:
Condition1: (first i chars of s matches first j-1 chars of p)
Condition2: (first i chars of s matches first j-2 chars of p)
Condition3: (first i-1 chars of s matches first j chars of p)

把String转为char[] 的写法: 【也可以用String.charAt()。】

class Solution {
    public boolean isMatch(String s, String p) {
        char[] sc = s.toCharArray(), pc = p.toCharArray();
        int sLength = sc.length, pLength = pc.length;
        boolean[][] dp = new boolean[sLength+1][pLength+1];
        dp[0][0] = true;
        //Base case: 这个for循环是针对 p前端"a*b*e*"匹配了s前0个字符的情况,直接先写,是没有问题的
        for(int i=2; i<=pLength; i++){ // 注意:<= pLength。另外:
            if(pc[i-1]=='*'){
                dp[0][i] = dp[0][i-2];
            }
        }
        // 双层 for循环
        for(int i=1; i<=sLength; i++){
            for(int j=1; j<=pLength; j++){
                if(sc[i-1]==pc[j-1] || pc[j-1]=='.'){
                    dp[i][j] = dp[i-1][j-1];
                } else if(pc[j-1]=='*'){
                    if(pc[j-2] =='.' ||pc[j-2] == sc[i-1]){
                        dp[i][j] = (dp[i][j-1]||dp[i][j-2]]||dp[i-1][j); //3种特殊情况
                    }else {
                        dp[i][j] = dp[i][j-2]; //we have to cut j-1 and j   //只有特殊情况2
                    }
                }                
            }
        }
        return dp[sLength][pLength];                    
    }
}

本题也可以用 Recursion 来做。
Time: O(n 2^n)
核心判断是:p的第二个字符是否为'*'.若是,就有可能删除前两个字符(写if判断),也有可能是比较p第一个字符是否匹配s第一个字符(写if判断)。若否,就判断第一个字符是否匹配。

    public boolean isMatch(String s, String p) {
        if (p.length() == 0) {
            return s.length() == 0;
        }
        if (p.length() >=2 && p.charAt(1) == '*') {  // second char is '*'
            if (isMatch(s, p.substring(2))) {
                return true;
            }
            if(s.length() > 0 && (p.charAt(0) == '.' || s.charAt(0) == p.charAt(0))) {
                return isMatch(s.substring(1), p);
            }
            return false;
        } else {                                     // no second char Or second char is not '*'
            if(s.length() > 0 && (p.charAt(0) == '.' || s.charAt(0) == p.charAt(0))) {
              return isMatch(s.substring(1), p.substring(1));
            }
           return false;
        }
    }

为了避免substring导致的O(n), 可以用helper函数+charAt()来代替。
Time: O(2^n)

    public boolean isMatch(String s, String p) { 
        if(s.length()==0 && p.length()==0) return true;
        return helper(s,p,0,0);        
    }
    private boolean helper(String s, String p, int sIndex, int pIndex){
        if(pIndex == p.length()){   // 易错点,这里不是 == p.length()-1
            return sIndex == s.length();
        }
        if( pIndex <= p.length()-2 && p.charAt(pIndex+1) == '*'){
            if(helper(s,p,sIndex,pIndex+2)) return true;
            if(sIndex<=s.length()-1 && (p.charAt(pIndex)=='.' || s.charAt(sIndex)==p.charAt(pIndex))) return helper(s,p,sIndex+1,pIndex);
            return false;
        } else {
            if(sIndex<=s.length()-1 && (p.charAt(pIndex)=='.' || s.charAt(sIndex)==p.charAt(pIndex) )    ) return helper(s,p,sIndex+1,pIndex+1);
            return false;
        }        
    }

Leetcode 44. Wildcard Matching. 【Hard】【Yellow】
题目简介:模式匹配题,' ? '匹配任意一个字符,' * '匹配任意0或多个字符。
可以用二维dp做,也可以用普通的判断 。
做法1, dp。Time: O(mn).
dp的做法,用 boolean[][] 来记录情况。
dp[i][j]: true if the first i char in String s matches the first j chars in String p。
Base case:
(1) origin: dp[0][0]: they do match, so dp[0][0] = true.
(2) first row: dp[0][j]: for String p starts with , all true. (p若以多个开头,可以匹配0个s的字符, 即dp[0][xx]=true (xx是连续多个
的位置))
Base case(1)要求 dp = boolean[s.length()+1][p.length()+1]
Base case(2)需要写一个for循环,遍历p。
然后写双层for循环。逻辑是:

  • if(字符匹配或?匹配: s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1)=='?') dp[i][j] = dp[i-1][j-1] ;
  • else if (匹配: p.charAt(j-1) == '') dp[i][j] = dp[i-1][j] || dp[i][j-1];
    -for dp[i-1][j], means that * acts like an empty sequence. 例: ab, ab*
    -for dp[i][j-1], means that * acts like any sequences 例: abcd, ab*
    public boolean isMatch(String s, String p) {
//        if(s.length()==0 && p.length()==0) return true;
//        if(s.length()==0 || p.length()==0) return false; 别写这句,因为"" 匹配了"*"
        //模式匹配题,别写if null || length()==0 的判断
        boolean[][] dp = new boolean[s.length()+1][p.length()+1];        
        dp[0][0]=true;
        //base case:
        for(int i=1; i<=p.length(); i++){
            if(p.charAt(i-1)=='*'){
                dp[0][i] = true;
            } else break;
        }
        for(int i=1; i<= s.length(); i++){
            for(int j=1; j<= p.length(); j++){
                if(  (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1)=='?')){
                    dp[i][j] = dp[i-1][j-1] ;
                } else if (p.charAt(j-1) == '*'){
                    dp[i][j] = dp[i-1][j] || dp[i][j-1];
                }                
            }
        }
        return dp[s.length()][p.length()];      
    }   

做法2,不推荐。用两个pointer:starIndex和sAfterMatchingStar记录。Time: O(m*n)(最差情况)。

class Solution {
    public boolean isMatch(String s, String p) {
        int sIndex =0, pIndex = 0; 
        Integer starIndex = null,sAfterMatchingStar = null;
        while(sIndex < s.length()){
            //if(pIndex >= p.length()) return false; 错误,因为*可能是p的最后一位.例:"ab"与"*"匹配
            if(pIndex<p.length() && (s.charAt(sIndex)==p.charAt(pIndex) || p.charAt(pIndex)=='?')){
                sIndex++;
                pIndex++;
            } else if (pIndex<p.length() && p.charAt(pIndex)=='*'){
                starIndex = pIndex;
                pIndex++;
                sAfterMatchingStar = sIndex; //表示 * match 0 个字符
            } else if ( starIndex != null){
                pIndex = starIndex +1;
                sAfterMatchingStar++;
                sIndex = sAfterMatchingStar;  // 表示 * 多match了一个字符
            } else {
                return false;
            }            
        }
        while( pIndex<p.length() && p.charAt(pIndex)=='*'){
            pIndex++;
        }
        return pIndex == p.length();        
    }
}

Leetcode 72. Edit Distance. 【Hard】【Yellow】
题目简介:给定两个字符串word1,word2, 求word1转变为word2所需的最少操作数量。操作有三种: 插入,删除,取代。
用dp方法。Time: O( m*n ).
dp的做法,用 int[][] 来记录情况。
dp[i][j] : minimum cost (or steps) required to convert first i characters of word1 to first j characters of word2.
Base case:

  • word1的0个字符,匹配word2的0个字符,需要0步.不需要写代码。
  • word1的0个字符,对应word2的i个字符, 需要i步操作(即插入i个字符)
  • word2的0个字符,对应word1的i个字符, 需要i步操作(即插入i个字符)

然后写双层for循环。逻辑是:

  • if(匹配:word1.charAt(i-1)==word2.charAt(j-1) ) dp[i][j] = dp[i-1][j-1];
  • else : dp[i][j] = 1+ min(dp[ i-1 ][ j-1 ], dp[ i-1 ][ j ], dp[ i ] [ j-1 ] )

Leetcode72和Leetcode 221 共同点:写一个matrix来记录,并用min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1 (三者最小值+1)来计算。

class Solution {
    public int minDistance(String word1, String word2) {
        int[][] dp = new int[word1.length()+1][word2.length()+1];
        //dp[0][0] = 0;
        for(int i=1; i<=word1.length(); i++){
            dp[i][0] = i;
        }
        for(int i=1; i<=word2.length(); i++){
            dp[0][i] = i;
        }
        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()];
    }
}

Leetcode 322. Coin Change. 【Medium】【Green】
题目简介:给数组coins和数字amount,求 = amount的coins最小数量。
dp做法,用int[] 来记录。
dp[i]: amount= i 的coins最小数量。
Base case:

  • 由于求min,需要先把dp内的值赋为amount+1, 且dp[0]=0. 需要写for循环。

然后写双层for循环,遍历amount和遍历coins (哪个在内外都没关系)。双层for循环内,有一个if判断: if(i>=coin)dp[i] = Math.min(dp[i], dp[i-coin]+1);
在return时需要判断 dp[amount] 的值是否 == amount+1。

class Solution {
    public int coinChange(int[] coins, int amount) {
        //if(coints==)
        int[] dp = new int[amount+1];
        //Arrays.fill(dp, amount+1); //这个写法容易漏了dp[0]=0;所以还是建议直接写for循环
        //dp[0] = 0;   // 用amount+1而不是Integer.MAX_VALUE,因为MAX导致dp[i-coin]+1越界
        for(int i=1; i<=amount; i++){
            dp[i] = amount+1;
        }
        for(int coin: coins){ //双层for循环是可以内外互换的。
            for(int i=1; i<=amount; i++){
                if(i>=coin)dp[i] = Math.min(dp[i], dp[i-coin]+1);
            }        
        }
        return dp[amount] == amount+1 ? -1:dp[amount];
    }
}

Leetcode 91. Decode Ways 【Medium】【Green】
题目简介:A-Z 字母分别对应1-26. 给一个全数字的字符串,要求decoding ways数量。
dp做法,Time: O(n), Space: O(n).
设 dp:int[s.length()+1]来做。
dp[i]: 前i个数字的decoding ways有 dp[i]个。
Base Case:

  • dp[1] 需要先判断 s第一个字符是否能decode。
  • dp[0] = 1. 这里难解释,可以等到for循环中再处理。

for循环:截取两个substring,for循环内部两个if判断:

  • if ( first != 0 ) 就赋值给 dp[i] = dp[i-1].
  • if ( second 在10-26之间) dp[i] += ( ... ) 【需要判断 i==2】
class Solution {
    public int numDecodings(String s) {
        //It's a Non-Empty string.
        int n = s.length();
        int[] dp = new int[n+1];
        //dp[0] = 1; 
        dp[1] = s.charAt(0) != '0' ? 1 : 0;
        for(int i = 2; i <= n; i++) {
            int first = Integer.valueOf(s.substring(i-1, i));
            int second = Integer.valueOf(s.substring(i-2, i));
            if(first != 0) {
               dp[i] = dp[i-1];  
            }
            if(second >= 10 && second <= 26) {
                dp[i] += ( i==2? 1: dp[i-2] ); //重点,若不判断i==2就需要写dp[0].这里解释困难。
            }
        }
        return dp[n]; 
    }
}

Space: O(1) Time O(n)的方法,但稍微有点绕。设置s1,s2分别记录情况,返回的是s1.
s1, s2 store ways of the s(0 ~ i-1) and s( 0 ~ i-2 )

    public int numDecodings(String s) {
        //if(s.charAt(0)=='0') return 0;  //避免 '0'的情况
        int s1 = s.charAt(0)=='0'? 0:1, s2 = 1;  //这里很关键,若设s1=1就需要上面的一句判断
//s1, s2 store ways of the s(0 ~ i-1) and s( 0 ~ i-2 ) 
        for(int i=1; i<s.length(); i++){
            if(s.charAt(i)=='0') s1 =0; 
            if(s.charAt(i-1)=='1' || (s.charAt(i-1)=='2'&&s.charAt(i)<='6') ){
                int temp = s1;
                s1 = s1 + s2; 
                s2 = temp;
            } else {
                s2 = s1;
            }
        }
        return s1;         
    }

Leetcode 62. Unique Paths 【Medium】【Yellow】
题目简介:二维grid,从左上角到右下角的走法,有几种。
用dp做法。Time: O(mn), Space: O(mn).

    public int uniquePaths(int m, int n) {        
        int[][] dp = new int[m][n];
        dp[0][0]=1;
        for(int i=0; i<m;i++){
            dp[i][0] =1;
        }
        for(int j=0; j<n;j++){
            dp[0][j] =1;
        }
        for(int i=1; i<m; i++){
            for(int j=1; j<n; j++){
                dp[i][j] = dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }

dp做法改进:Space O(n, 或者说 min(m,n)).
ignore the space for the columns. Each column has only one space to store the information.

    public int uniquePaths(int m, int n) {     
        int[] dp = new int[n];
        dp[0]=1;
        for(int i=0; i<m; i++){  //从0开始,从而遍历m行
            for(int j=1; j<n; j++){
                dp[j] = dp[j]+dp[j-1];
            }
        }
        return dp[n-1];
    }

阶乘的做法。Time: O(min(m,n)), space: O(1).
计算公式: factorial(m+n-2)/(fact(m-1)fact(n-1)).
假设n>m, 约去(n-1), 得到 (m+n-2)
...n / (m-1)...*1. 都有m-1项,而且相差为n-1.

class Solution {
    public int uniquePaths(int m, int n) {
        double res = 1; 
        for(int i=1; i<=m-1; i++){
            res = res * (i+n-1)/i;
        }
        return (int)res;    
    }
}

Leetcode 70. Climbing Stairs.【Easy】【Yellow】
题目简介:爬楼梯,每次爬1步或2步, 求到达n的ways数量。
recursion做法,超时了。Time: O(2^n). 近似 费波那契数列.

dp做法,用 int[] dp来记录。Time: O(n), Space: O(n).

class Solution {
    public int climbStairs(int n) {
        if(n==1)return 1;
//        if(n==2)return 2;
        int[] dp = new int[n+1];
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n; i++){
            dp[i] = dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
}

改进版: Space:O(1).

class Solution {
    public int climbStairs(int n) {
        if(n==1) return 1;
        if(n==2) return 2;
        int k2 = 1;  // 2 steps before current stair
        int k1 = 2;  // 1 step before current stair
        int k = 0;       // current stair
        for(int i=3; i<=n; i++){  //注意index,从3到n。
            k = k1+k2; // calculate k.
            k2 = k1;  //k2 move forward
            k1 = k;   //k1 move forward
        }
        return k;
    }
}

Leetcode 509. Fibonacci Number. 【Easy】【Yellow】
题目简介:计算 Fibonacci Number.
用 Iterative. 设3个数:sum,a,b。写一个for循环就解决。

class Solution {
    public int fib(int N) {
        if(N<=1) return N;
        int sum = 0;
        int a=0, b=1;
        for(int i=2; i<=N; i++){
            sum = a+b; 
            a = b;
            b = sum;
        }
        return sum;
    }
}

Leetcode 139. Word Break. 【Medium】【Green】
题目简介:给一个字符串和List字典,问字符串能否分割为在字典中都能查到的单词。
dp题,关键是设置boolean[] 来记录dp[j],从而知道在 j 之前是否都能找到单词。两个for循环就解决。
做法1,用 List.contains()操作,Time O(n^3), 不推荐。
Time: O(n^3), 因为List.contains()是O(n)的 (String.substring()方法是O(1))。
Space: O(n).

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        boolean[] dp = new boolean[s.length()+1];
        dp[0]=true;
        for(int i=1; i<=s.length();i++){
            for(int j=0; j<i; j++){
                if(dp[j] && wordDict.contains(s.substring(j,i))){
                    dp[i] = true; 
                    break;
                }
            }
        }
        return dp[dp.length-1];
    }
}

改进:用 set 来实现 set.contains()。

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> set = new HashSet<>();
        set.addAll(wordDict);
        boolean[] dp = new boolean[s.length()+1];
        dp[0]=true;
        for(int i=1; i<=s.length();i++){
            for(int j=i-1; j>=0; j--){  //倒序遍历,会比顺序遍历好, improve performance.
                if(dp[j] && set.contains(s.substring(j,i))){
                    dp[i] = true; 
                    break;
                }
            }
        }
        return dp[dp.length-1];
    }
}

类似做法一,以下是dp的典型写法,for循环遍历其中一个input: String s, 另一个for循环遍历另外的一个input:wordDict。
String.equals()是O(n),但这里word的长度可以认为是一个常数,与s.length()没关系。所以: Time: O(m*n),或者说O(n^2).
缺点是wordDict过大的话会麻烦。

    public boolean wordBreak(String s, List<String> wordDict) {        
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;
        for (int i = 1; i <= s.length(); i++) {
            // 从这个位置向前考虑能否match某个word,如果match并且前面也为true的话,那么这一段我们可以将其设置为true
            for (String word : wordDict) {
                // 至少i要在word之后吧,如果这个位置根本放不下这个word,那就下一个
                if ( i >= word.length() && dp[i - word.length()] && word.equals(s.substring(i - word.length(),i))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }

Leetcode 140.Word Break II. 【Hard】【Yellow】
题目简介:给一个字符串和List wordDict,返回一个List<String>, 包含所有可能的分割方式。
需要用Recursion(back-tracking)。
设一个Map<String,List<String>> map,map记录的是 被截取前端一部分之后的s和它对应的多种分割方式。比如说,"... catsanddog", map中就记录了("catsanddog",对应的result记录的分割方式)。这样当 ... 解析到 "catsanddog" 时,就不需要再重新对它进行分割,而是直接调取之前已经记录在map中的代码。(若不使用map,就会TLE(Time Limit Exceeded.) )

最简洁的答案:

class Solution {
    public List<String> wordBreak(String s, List<String> wordDict) {
        return backtrack(s,wordDict,new HashMap<String, List<String>>());
    }
    // backtrack returns an array including all substrings derived from s.
    public List<String> backtrack(String s, List<String> wordDict, Map<String,List<String>> map){
        if(map.containsKey(s)) return map.get(s);                
        List<String> result = new ArrayList<String>(); //result 可理解为 mapValue
        for(String word: wordDict){             
            if(s.startsWith(word)) { 
                //s = "...catsxxx",word可以为cat/cats,word=cat时下面的代码会把"catxxxx"的各种break情况记录到result中;然后轮到word=cats,把"catsxxx"的情况记录到result中
                String next = s.substring(word.length());
                if(next.length()==0){ 
                    result.add(word);
                } else { 
                    List<String> nextStr = backtrack(next, wordDict, map);
                    for(int i=0; i<nextStr.size(); i++){
                        result.add(word + " " + nextStr.get(i));
                    }
                    // for(String subStr: backtrack(next, wordDict, map)) {  也可以用for(String str:)写法
                    //     result.add(word+" "+subStr);
                    // }
                }
            }
        }
        map.put(s, result); //含有:s="catsanddog", 对应的s的result: "cats and dog","cat sand dog"
        return result; 
    }  
}

分析1:

s = "catsanddog"
wordDict = ["cat", "cats", "and", "sand", "dog"]

s="catsanddog", NEW res,
                 (1)word=cat, next=sanddog,
                 backtrack1()得到list ("sand dog"),subStr处理,得到res("cat sand dog"),
                 backtrack1(), word=sand,next=dog,backtrack2()得到的是含有("dog")的List,对于每一个subStr,add进当前res,有res("sand dog"),map.put("sanddog",res),return res返回上一级
                 backtrack2(), s=dog, word=dog, next="",res.add("dog"),map.add("dog",res),return res返回上一级

                 (2)word=cats,next=anddog,backtrackA(),
                 backtrackA(), 得到("and dog")的list,subStr处理,res原基础("cat sand dog")上加入"cats and dog"
                 backtrackA(),word=and,next=dog,backtrackB()得到含有("dog")的List,有res("and dog"),map.put("anddog",res),return res返回上一级
                 backtrackB(),返回map.get("dog")
map.put("catsanddog",res) (res两个元素,"cat sand dog"和"cats and dog")
return res,就是答案

分析2:

s= "catsandcatsand"
dict = ["cat","cats","and","sand","dog"]

s="catsandcatsand", NEW res, (1)word=cat, next=sandcatsand,  
                                    backtrack1()得到list ("sand cat sand","sand cats and"),subStr处理,得到res("cat sand cat sand","cat sand cats and"),map.put("catsandcatsand",res)
                                    backtrack1(), word=sand,next=catsand,backtrack2()得到的是含有("cat sand","cats and")的List,对于每一个subStr,add进当前res,有res("sand cat sand","sand cats and"),map.put("sandcatsand",res),return res返回上一级
                                      backtrack2(), s=catsand,(1)word=cat,next=sand,下一级完成后res.add("cat sand"),res有("cat sand"),检验其他word
                                                          (2)word=cats,next=and,下一级完成后res.add("cats and"),res有("cat sand","cats and"),map.put("catsand",res)返回上一级             
                                         backtrack3(), (1)s=sand,word=sand,next="",result.add("sand"),map.put("sand",result) return res返回上一级
                                                       (2)s=and,word=and,next="",res.add("and"),map.put("and",result) return res返回上一级
                             (2) word = cats, next=andcatsand, backtrackA(),把subStr加入到res,res.ADD("cats and cat sand","cats and cats and")
                                                      backtrackA(),s=andsand,word=and,next=catsand,backtrackB()得到"cat sand","cats and"
                                                      backtrackB(),s=catsand,返回map.get("catsand")
map.put("catsandcatsand",res),res含有:"cat sand cat sand","cat sand cats and","cats and cat sand","cats and cats and"
RETURN res

Leetcode 410. Split Array Largest Sum. 【Hard】【Green】
题目简介:将一串数字截为m段,使得每段之和的最大值 最小(minimize the largest sum of the subarrays)。求这个值。
用二分法来解决。ruler是每段之和的最大值。
最开始以nums中最大值max为left,以nums的全体sum为right,需要通过helper()方法来判断 left增大还是right缩小。

class Solution {
    public int splitArray(int[] nums, int m) {
        int max = 0; 
        long sum = 0;
        for(int num: nums){
            max = Math.max(max,num);
            sum += num;
        }
        if(m==1) return (int)sum;
        long left = max, right = sum;
        while(left<right){ //left可取,right不可取:[left,right)
            long mid = left + (right-left)/2;
            if(binarySearch(mid,nums,m)){
                right = mid;
            } else {
                left = mid+1;
            }
        }
        return (int)left;
    }
    private boolean binarySearch(long target,int[] nums, int m){
        int count = 1; //这是因为最后一段是不经历count++的,比如说,只分为2段,那么只经历了一次count++。
        long sum = 0;
        for(int num: nums){
            sum += num;
            if(sum>target){  ///why? 因为target就代表了一段数字的max,是可以取等号的max
                count++;
                sum = num;
                if(count>m){
                    return false;
                }
            } 
        }
        return true;
    }        
}

Leetcode 84. Largest Rectangle in Histogram. 【Hard】【Yellow】
题目简介:给一串数组,代表柱形图,求最大矩形面积。
用 stack做。(无论stack还是queue,都用LinkedList初始化. ) 每次遇到递增的index,就offerFirst()加入到stack中; 一遇到减少的index i, 就把stack 中 >=heights[i]的一个个pollFirst()推出来,并计算面积。
Stack class 很慢,已经很少用了。
stack和queue的初始化:(Deque, interface of 双向链表)

  • Deque<Integer> stack = new LinkedList<>();
  • Deque<Integer> queue = new LinkedList<>();
    也可以写: Queue<Integer> queue = new LinkedList<>();用Queue的方法。
//每次遇到递增的index,就offerFirst()加入到stack中; 一遇到减少的index i, 就把stack中
// >=heights[i]的一个个pollFirst()推出来,并计算面积
class Solution {
    public int largestRectangleArea(int[] heights) {
        int res = 0; 
        Deque<Integer> stack = new LinkedList<>();
        //Deque<xxx> queue = new LinkedList<>(); 
        for(int i=0; i<= heights.length; i++){ //**注意: 从0到n,共n+1项
            int cur = i== heights.length? 0: heights[i]; //难点1,与**有关
            while( !stack.isEmpty() && heights[stack.peekFirst()] >= cur){
                int height = heights[stack.pollFirst()];  //难点2
                int left = stack.isEmpty()? 0:stack.peekFirst()+1; //难点3,+1可以从第一个情况推导出来.第一次情况,i-1被推出,peekFirst()得到的是i-2.而i-1处宽度是1.要i-left=1,那么应该让peekFirst()+1.
                // stack中只能存比cur小的,若是没有了,说明cur是全队最小,所以需要从index=0算起 
                res = Math.max(res, height * (i-left));  //这里只算位于i之前的面积,所以是从0遍历到n,需要最后补上一个0(**与难点一有关)                
            }
            stack.offerFirst(i);
        }
        return res;
    }
}

Leetcode 85. Maximal Rectangle. 【Hard】【Yellow】
题目简介:给一个二维char数组, 都是0和1的值,问元素都是1的最大矩形面积。
和84题做法基本相同。难点在于理解为何设置heights数组(长度为n+1):原因在于累计其高度,这样就可以转化为84题。
[
    [1,0,1,0,0]
    [1,0,1,1,1]
    [1,1,1,1,1]
    [1,0,0,1,0]
]
处理得到 heights数组:
(最后一位是补零的操作,参照84题的做法)
经历第1行后:[1,0,1,0,0,0]
经历第2行后:[2,0,2,1,1,0]
经历第3行后:[3,1,3,2,2,0]
经历第4行后:[4,0,0,3,0,0]

class Solution {
    public int maximalRectangle(char[][] matrix) {
        if(matrix.length==0 || matrix[0].length ==0) return 0;
        int m = matrix.length, n= matrix[0].length;
        int[] heights = new int[n+1];
        int res = 0;
        for(int i=0; i<m; i++){
            Deque<Integer> stack = new LinkedList<>();
            for(int j=0; j<=n; j++){
                if(j!=n){
                    if(matrix[i][j] == '1') heights[j]+=1;
                    else heights[j]=0;
                }
                while( !stack.isEmpty() && heights[stack.peekFirst()] >= heights[j]){
                    int height = heights[stack.pollFirst()];
                    int width = stack.isEmpty() ? j: j-stack.peekFirst()-1;
                    res = Math.max(res, height*width);
                }
                stack.offerFirst(j);
            }            
        }
        return res;
    }
}

Leetcode 935. Knight Dialer. 【Medium】【Yellow】
题目简介:一个knight骑士棋子有特定跳跃规则, 问能打多少种电话号码。

法1,Time: O(N), Space: O(1). 【推荐】
设置两个long array: pre和cur,记录上一步情况和当前情况。关键是理解: cur[0]=(pre[4]+pre[6])%mod,这一步跳到0的ways = 上一步跳到4和6的ways之和。

class Solution {
    public int knightDialer(int N) {
        long mod = 1000000007;
        if(N==1) return 10;
        long[] pre = new long[10];
        long[] cur = new long[10];
        Arrays.fill(pre,1);   // 记住Arrays.fill()
        pre[5] = 0;          //关键点,pre[5]=0
        while(--N>0){
            cur[0]=(pre[4]+pre[6])%mod;
            cur[1]=(pre[6]+pre[8])%mod;
            cur[2]=(pre[7]+pre[9])%mod;
            cur[3]=(pre[4]+pre[8])%mod;
            cur[4]=(pre[3]+pre[9]+pre[0])%mod;
            //cur[5]=0;
            cur[6]=(pre[1]+pre[7]+pre[0])%mod;
            cur[7]=(pre[2]+pre[6])%mod;
            cur[8]=(pre[1]+pre[3])%mod;
            cur[9]=(pre[2]+pre[4])%mod;
            //pre = cur; //Wrong for it's address copy. 错误,这是地址拷贝了,导致出错
            //for(int i=0; i<10; i++) pre[i]=cur[i]; 这个for循环也可以用,但不如下面方便
            int[] temp = pre;
            pre = cur;
            cur = pre; //把cur赋给pre,然后cur指向pre,这样就不需要新设数组int[]也不需要for循环赋值
        }
        long sum = 0;
        for(int i=0; i<10; i++){
            sum = (sum+cur[i])%mod;
        }
        return (int)sum;
    }
}

可以进一步将Time削减到O(logN). 用Matrix来记录,并使用Matrix的乘方(^)。太难写,所以不推荐。

(1) 为什么可以用Matrix的相乘来得到ways?
The m(i,j) in Matrix Mk means starting at i, ending at j, through k+1 hops.
(THE ways for first hop =0 because 10 numbers can be the starting point with equal possibility. So hop1 = M0 = 10. )
       M1 = np.matrix([[0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
                       [0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
                       [0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
                       [0, 0, 0, 0, 1, 0, 0, 0, 1, 0],
                       [1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [1, 1, 0, 0, 0, 0, 0, 1, 0, 0],
                       [0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
                       [0, 1, 0, 1, 0, 0, 0, 0, 0, 0],
                       [0, 0, 1, 0, 1, 0, 0, 0, 0, 0]])
M1 is the starting matrix, k=1, and the dots indicates where the horse can be after 2 hops. 
For example, the first row means starting at 0, a horse can jump to 4 and 6. 
And when k=2, we multiply two M1s, and get M2. 
       M2 = np.matrix([[2, 1, 0, 1, 0, 0, 0, 1, 0, 1],
                       [1, 2, 0, 1, 0, 0, 0, 1, 0, 0],
                       [0, 0, 2, 0, 1, 0, 1, 0, 0, 0],
                       [1, 1, 0, 2, 0, 0, 0, 0, 0, 1],
                       [0, 0, 1, 0, 3, 0, 1, 0, 1, 0],
                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                       [0, 0, 1, 0, 1, 0, 3, 0, 1, 0],
                       [1, 1, 0, 0, 0, 0, 0, 2, 0, 1],
                       [0, 0, 0, 0, 1, 0, 1, 0, 2, 0],
                       [1, 0, 0, 1, 0, 0, 0, 1, 0, 2]])
M2 (4,4)=3, which means there are 3 ways to start at 4, ending at 4 in 3 jumps. 【3ways: 4-3-4, 4-9-4, 4-0-4】
M2 (4,4) = M1(4,0)*M1(0,4)+M1(4,1)*M1(1,4)+M1(4,2)*M1(2,4)+...+M1(4,9)*M1(9,4).  【Matrix multiplying】
In this way we can use matrix to calculate number of paths. 

(2) 为什么可以将 O(N) 削减到 O(LogN)?
M60 = M30*M30 = M30^2,(even number of Matrix can reduce half the calculation.)
M61 = M1 * M30 * M30 = M1 * M30^2 = M1 * M60, (odd number Matrix can be modified to even number Matrix calculation. )

O(logN) Space O(1)的解法:

class Solution {
    public int knightDialer(int N) {
        if(N==1) return 10;
        long mod = 1000000007;
        long[][] matrix = {
            {0, 0, 0, 0, 1, 0, 1, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 1, 0, 1, 0},
            {0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
            {0, 0, 0, 0, 1, 0, 0, 0, 1, 0},
            {1, 0, 0, 1, 0, 0, 0, 0, 0, 1},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {1, 1, 0, 0, 0, 0, 0, 1, 0, 0},                     
            {0, 0, 1, 0, 0, 0, 1, 0, 0, 0},
            {0, 1, 0, 1, 0, 0, 0, 0, 0, 0},
            {0, 0, 1, 0, 1, 0, 0, 0, 0, 0},            
        };        
        long[][] resultMatrix = new long[10][10];
        boolean resultNotEmpty = false;
        N = N-1;
        while(N>0){    //这里最关键。处理方式很巧妙,Leetcode应该有同类型的二分法的题
            if(N%2==1){
                if(resultNotEmpty){
                    resultMatrix = matrixMultiply(resultMatrix, matrix);       
                } else {
                    resultNotEmpty = true;
                    resultMatrix = matrix;                                   
                }                 
            }
            matrix = matrixMultiply(matrix, matrix);
            N/=2;
        }
        long sum = 0;
        for(int i=0; i<10; i++){
            for(int j=0; j<10; j++){
                // sum += resultMatrix[i][j];
                // sum %= mod;
                sum = (sum+resultMatrix[i][j])%mod;
            }
        }
        return (int)sum;        
    }
    private long[][] matrixMultiply(long[][] matrix1, long[][]matrix2){
        long mod = 1000000007;
        long[][] resultMatrix = new long[10][10];
        for(int i=0; i<10; i++){
            for(int j=0; j<10; j++){
                for(int k=0; k<10; k++){
                    // resultMatrix[i][j] += matrix1[i][k]*matrix2[k][j];
                    // resultMatrix[i][j] %= mod;
                    resultMatrix[i][j] =  (resultMatrix[i][j]+matrix1[i][k]*matrix2[k][j])%mod;
                }                
            }
        }  
        return resultMatrix;
    }
}  

while() { N%2==0 ... } 的大致题意:【time (logN)内让res从0到达N】
给定一个数N,N就是res()方法或者res数字需要操作的次数。
( 用一个int:exponential 用来记录 2的指数 )
初始:res=0,exponential = 1.
典型处理代码:

while(N>0){
    if(N%2==1){
      if(res==0){
         res = exponential;
      else {   
         res += exponential;       
      }
    } 
    exponential = exponential * 2 ;     
    N = N/2;  
}

Leetcode 688. Knight Probability in Chessboard. 【Yellow】【Medium】
题目简介:棋子有特定的跳跃规则,问k步跳跃之后还留在棋盘上的概率。
dp方法。可以设置dp[K][N][N], 计算公式是:
dp[k+1][newX][newY] = dp[k][x][y].
视频:https://www.youtube.com/watch?v=4W1Gvahf_Hg
但我们可以发现, dp[k][x][y] 只会使用一次,所以我们不需要三维的array,只需要 两个二维array,double[][] pre和cur 即可。
用double[][] pre和cur 记录 probability chessboard, 写三层for循环,第四层for循环是指每一步都可以走八个位置,概率都是1/8. 难点在于pre和cur的处理,放在第一层for循环(因为pre和cur就是为了k和k+1步的关系而准备的),先设double[][] pre=cur,然后cur= new double[][],即把原cur标记为pre,并把cur清空。
Time: O(KN^2), Space: O(N^2).

class Solution {
    public double knightProbability(int N, int K, int r, int c) {        
        double[][] cur = new double[N][N]; //or new double[25][25]
        cur[r][c] = 1;
        int[] dx = {1,1,-1,-1,2,2,-2,-2};
        int[] dy = {2,-2,2,-2,1,-1,1,-1};
        for(int k=0; k<K; k++){
            double[][] pre = cur;   //mark cur as the previous matrix
            cur = new double[N][N]; //cur should be an empty matrix
            for(int x=0; x<N; x++){
                for(int y=0; y<N; y++){
                    for(int count=0; count<8; count++){
                        int newX = x + dx[count];
                        int newY = y + dy[count];
                        if(newX>=0 && newX<N && newY>=0 && newY<N) cur[newX][newY]+=pre[x][y]*0.125;
                    }
                }
            }
        }
        double res = 0;
        for(int i=0;i<N;i++){
            for(int j=0;j<N;j++){
                res += cur[i][j];         //sum of all possibility
            }
        }
        return res;
    }
}

Leetcode 32. Longest Valid Parentheses. 【Yellow】【Hard】
题目简介:给一个字符串, 只包含 '(' 和 ')',找出最长的合规substring的长度。
用 LinkedList<Integer> stack 来做。遍历一次数组,每次遇到 ( 就把index加入stack,遇到 ) 就先判断stack是否empty,若empty就设置start,不empty就直接pop一次,并计算res (分两种情况,一是stack变空了而是stack还有元素)。
特点:遇到 ) ,判断是否empty,然后pop,再判断是否empty。

class Solution {
    public int longestValidParentheses(String s) {
        int res = 0;
        LinkedList<Integer> stack = new LinkedList<>();
        int start = -1; 
        for(int i=0; i<s.length(); i++){
            if(s.charAt(i)=='('){
                stack.push(i);
            } else {
                if(stack.isEmpty()){
                    start = i;
                } else {
                    stack.pop();
                    if(stack.isEmpty()){
                      res = Math.max(res,i-start);
                    } else {
                       res = Math.max(res,i-stack.peek());
                    }                     
                }
               
            }
        }
        return res;
    }
}

Leetcode 221. Maximal Square. 【Yellow】【Medium】
题目简介: 给一个二维char数组, 都是0和1的值,问元素都是1的最大正方形面积。(和85题目相似,85题目是求矩形面积)
用原matrix来记录情况(或者设置一个dp[][] 来记录情况,但这样space更高所以不推荐),双层for循环内判断逻辑是:

  • if ( ==1 ), 就 说明它可以作为正方形的右下角,那么就去找三者最小值:
  • Math.min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])+1, 由于比如说dp[i-1][j]=k,说明dp[i-1][j]是边长side-length为k的正方形,依此类推到dp[i-1][j-1]和dp[i][j-1],说明dp[i][j]可以做边长为min+1的正方形的右下角。

可以设置一个dp[][] 来记录情况(最原始的解法1, time(mn), space(mn))。然后发现并不需要保存整个二维dp,将space提升到O(n),这是解法2. 最后,可以直接在原matrix上操作,将 space提升到 O(1).
由于是在原matrix操作,需要检查matrix[i-1][j-1],所以双层for循环都是从index=1开始,因此对于index=0的row和column,需要另外写两个单层for循环来遍历。

Leetcode72和Leetcode 221 共同点:写一个matrix来记录,并用min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1 (三者最小值+1)来计算。

class Solution {
    public int maximalSquare(char[][] matrix) {
        if(matrix.length == 0) return 0;    
        int m = matrix.length, n = matrix[0].length;
        int max = 0;
        for(int i = 1; i < m && max != 1; i++) if(matrix[i][0] == '1') max = 1; 
        for(int j = 0; j < n && max != 1; j++) if(matrix[0][j] == '1') max = 1;    
        for(int i = 1; i < m; i++){
            for(int j = 1; j < n; j++){
                if(matrix[i][j] == '1'){
                    int s = Math.min(matrix[i-1][j] - '0', Math.min(matrix[i - 1][j - 1] - '0', matrix[i][j-1] - '0')) + 1;
                    matrix[i][j] = (char)('0' + s);
                    max = Math.max(max, s);                    
                }
            }
        }
    return max * max;
    }
}

Leetcode 741. Cherry Pickup. 【Hard】【Yellow】
题目简介:有一个N*N的矩形Grid(网格), 一个人从左上角走到右下角,再返回左上角,每个网格cell 有两种可能,1) -1,说明这是栅栏,不能通过;2) 非负数(k),说明这个网格有k个樱桃cherry。问这个人可以摘到樱桃的最大数量。
解题思路:题意相当于由两个人同时从左上角走到右下角。用dp解法。设一个 int[][][] dp = new int[n+1][n+1][n+1], (dp常常都是设置n+1因为可以避免dp[x-1] x=0时的越界情况)。
1).Base case: 先赋值min_value, 赋值dp[1][1][1] = grid[0][0]; 2). 在三层for循环内,赋值y2, 判断y2是否越界以及grid是否为栅栏; 然后计算pre, 前一步的max值(max(四种可能)),若前一步<0, continue, 否则就根据pre 以及 x1和x2是否相等,来赋值给dp[x1][y1][x2].

class Solution {
    public int cherryPickup(int[][] grid) {
        int n = grid.length;
        int[][][]dp = new int[n + 1][n + 1][n + 1];
        for(int i = 0; i <= n; i++){
            for(int j = 0; j <= n; j++){
                Arrays.fill(dp[i][j], Integer.MIN_VALUE); //为了后面的判断.在这一题,设置任何负数都行
            }
        }
        dp[1][1][1] = grid[0][0];
        for(int x1 = 1; x1 <= n; x1++){
            for(int y1 = 1; y1 <= n; y1++){
                for(int x2 = 1; x2 <= n; x2++){
                    int y2 = x1 + y1 - x2;
                    //out of boundary || cannot access 
                    if( y2 < 1 || y2 > n || grid[x1 - 1][y1 - 1] == -1 || grid[x2 - 1][y2 - 1] == -1)continue;                                 
                    int pre = Math.max(Math.max(dp[x1 - 1][y1][x2], dp[x1 - 1][y1][x2 - 1]), Math.max(dp[x1][y1 - 1][x2], dp[x1][y1 - 1][x2 - 1]));
                    if(pre < 0) continue; // 1)使得起点是grid[0][0]; 2)对应grid中的那个点左边和上边若都是-1,那么这个点的值应该也是min_value 
                                        
                    if(x1 != x2){
                        dp[x1][y1][x2] = pre + grid[x1 - 1][y1 - 1] + grid[x2 - 1][y2 - 1];
                    } else {
                        dp[x1][y1][x2] = pre + grid[x1 - 1][y1 - 1];
                    }
                }
            }
        }
        return dp[n][n][n] <= 0 ? 0 : dp[n][n][n];
    }
}

Leetcode 300. Longest Increasing Subsequence.【Medium】【Yellow】
题目简介: 找到最长的递增subsequence。注意,没有规定consecutive相邻, 就是可以是不相邻的。
要求:Time O(nlogn). 所以需要用二分法。
用int[] sequence 记录情况。for循环遍历,并做以下处理:
(1) if x is larger than all tails, append it, increase the size by 1
(2) if tails[i-1] < x <= tails[i], update tails[i].
这种做法源于 patience sorting 耐心排序。
Time: O(nlogn), space: O(n).
推荐写法:

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums==null || nums.length==0) return 0;
        int[] sequence = new int[nums.length];
        int length = 0; 
        sequence[length++] = nums[0]; 
        for(int i=1; i<nums.length; i++){
            if(nums[i]>sequence[length-1]){
                sequence[length++] = nums[i];
            } else {
                int left = 0, right = length-1;
                while(left < right){  // 当left>=right时跳出循环.其实只能是left==right,不会出现left>right并跳出的情况
                    int mid = left+(right-left)/2;
                    if(sequence[mid]<nums[i]){
                        left = mid+1;
                    } else {
                        right = mid; 
                    }   
                }
                sequence[left] = nums[i];
            }
        }
        //System.out.print(Arrays.toString(sequence));  //Arrays.toString(xxArray)这个写法很好用,用于代码测试
        return length;        
    }
}

简化版:(不推荐)

public int lengthOfLIS(int[] nums) {
    int[] tails = new int[nums.length];
    int size = 0;
    for (int x : nums) {
        int i = 0, j = size;
        while (i != j) {
            int m = (i + j) / 2;
            if (tails[m] < x)
                i = m + 1;
            else
                j = m;
        }
        tails[i] = x;
        if (i == size) ++size;
    }
    return size;
}

Leetcode 97. Interleaving String. 【Hard】【Yellow】
题目简介:有两个string s1, s2, 判断另一个string s3 是否由s1和s2交错得到。interleaving: 交错。 注意:是将s1,s2分别截成多个substring,并拼接得到s3,但s1的多个substring的顺序仍是原顺序, s2同理。
解题思路:
用dp解决。设一个二维数组,boolean[][] dp = new boolean[s1.length()+1][s2.length()+1]; 来记录情况。dp[i][j]表示s1前i个字符,s2前j个字符,匹配s3的前i+j个字符。
Base case:

  1. dp[0][0] = true, 即s1前0个字符,s2前0个字符,匹配s3的前0+0个字符。
  2. 遍历第一列dp[i][0],即在s2不出字符的情况下,s1和s3匹配情况。
  3. 遍历第一行dp[0][j],即在s1不出字符的情况下,s2和s3匹配情况。
    双层for循环遍历,内部判断条件:
    1)s3[i+j+1] 字符匹配 来自s1的字符:dp[i-1][j] && s1.charAt(i-1)==s3.charAt(i+j-1).
    2)s3[i+j+1] 字符匹配 来自s2的字符:dp[i][j-1] && s2.charAt(j-1)==s3.charAt(i+j-1).
class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        if(s1 == null || s1.length()==0) return s2.equals(s3);
        if(s2 == null || s2.length()==0) return s1.equals(s3);
        if(s1.length()+s2.length() != s3.length()) return false;

        boolean[][] dp = new boolean[s1.length()+1][s2.length()+1];
        dp[0][0] = true;
        for(int i=1; i<dp.length; i++){
            dp[i][0] = dp[i-1][0] && s1.charAt(i-1)==s3.charAt(i-1);
        }
        for(int j=1; j<dp[0].length; j++){
             dp[0][j] = dp[0][j-1] && s2.charAt(j-1)==s3.charAt(j-1);
        }
        for(int i=1; i<dp.length; i++){
            for(int j=1; j<dp[0].length; j++){
                dp[i][j] = (dp[i-1][j] && s1.charAt(i-1)==s3.charAt(i+j-1) )
                    || (dp[i][j-1] && s2.charAt(j-1)==s3.charAt(i+j-1));
            }
        }
        return dp[s1.length()][s2.length()];
    }
}

Leetcode 312. Burst Balloons. 【Hard】【Yellow】
题目简介:扎气球,给一串int,每扎一个气球,就要加上气球两端数字的乘积,求max。
用二维int[][] dp来记录情况。dp[start][end]表示nums第start个到第end个气球被扎爆所得的max值。
Time:O(), Space: O(n^2).
核心逻辑是:若要扎第i个气球,dp[start][end] = dp[start][i-1] + 扎i气球得到的值 + dp[i+1][end].
先新建数组,给原数组两端加上1,然后helper方法,helper中for循环遍历一次arr,(核心逻辑)每次都求出cur并赋给dp[start][end].

dp[i][j]图示.png

class Solution {
    public int maxCoins(int[] nums) {
        int[] arr = new int[nums.length+2];
        for(int i=1; i<=nums.length; i++){
            arr[i] = nums[i-1];
        }
        arr[0] = 1; 
        arr[arr.length-1] = 1; 
        int[][] dp = new int[arr.length][arr.length]; 
        //dp[start][end]表示nums第start个到第end个气球被扎爆所得的max值
        return helper(1, nums.length, arr, dp);
    }
    private int helper(int start, int end, int[] arr, int[][] dp){
        if(start>end){
            return 0; 
        }
        if(dp[start][end]>0) return dp[start][end];
        for(int i=start; i<=end; i++){
            int cur = helper(start,i-1,arr,dp)+
                arr[start-1]*arr[i]*arr[end+1]+
                helper(i+1,end,arr,dp);
            dp[start][end] = Math.max(dp[start][end], cur); 
            //dp[start][end]会经历多次自我比较,需要以题目的例子[3,1,5,8]来理解
        }
        return dp[start][end];
    }
}

Leetcode 【】
题目简介:


Leetcode 1155. Number of Dice Rolls With Target Sum. 【Medium】【Blue】
题目简介:

class Solution {
    public int numRollsToTarget(int d, int f, int target) {
        int mod = 1000*1000*1000+7;
        int[][] dp = new int[d+1][target+1];
        dp[0][0]=1;
        for(int iDice=1; iDice<=d; iDice++){
            for(int jTarget=1; jTarget<=target; jTarget++){
                if(jTarget > f*iDice){
                    continue; // or dp[iDice][jTarget]=0;
                }else{
                    for(int kValue=1; kValue<=f && kValue<=jTarget; kValue++){
                        dp[iDice][jTarget]=(dp[iDice][jTarget]+dp[iDice-1][jTarget-kValue])%mod;
                    }
                }
            }
        }
        return dp[d][target]%mod;
    }
}

Leetcode 【】
题目简介:


```Leetcode       【】  
题目简介:


Leetcode       【】  
题目简介:


Leetcode       【】  
题目简介:


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容