1. 程式人生 > >LeetCode專題----動態規劃

LeetCode專題----動態規劃

70. Climbing Stairs

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Note: Given n will be a positive integer.
思路:這個其實和斐波那契數列是一樣的。

public class Solution {
    public int climbStairs
(int n) { if(n == 1) return 1; if(n == 2) return 2; int pp = 1, p = 2, cur = 0; for(int i=2; i<n; i++) { cur = pp + p; pp = p; p = cur; } return cur; } }

121. Best Time to Buy and Sell Stock

Say you have an array for which the ith element is the price of a given stock on day i.

If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.

分析:dp[i+1] = max{dp[i], prices[i+1] - minprices} ,minprices是區間[0,1,2…,i]內的最低價格。我們要求解的最大利潤 = max{dp[0], dp[1], dp[2], …, dp[n-1]}。由於dp[i+1]最多隻會用到dp[i],且最後的結果是求max,自然而然地可以把這個備忘陣列只用一個數來表示了。

public class Solution {
    public int maxProfit(int[] prices) {
        int max = 0;
        if(prices == null || prices.length == 0) return max;
        int min = prices[0];
        for(int i=1; i<prices.length; i++) {
            max = Math.max(max, prices[i] - min);
            min = Math.min(min, prices[i]);
        }
        return max;
    }
}

309. Best Time to Buy and Sell Stock with Cooldown

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions:

You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)
Example:

prices = [1, 2, 3, 0, 2]
maxProfit = 3
transactions = [buy, sell, cooldown, buy, sell]

思路:
這題有點繞。
buy[i]表示在前i天最後一個操作是買,此時的最大收益。
sell[i]表示在前i天最後一個操作是賣,此時的最大收益。
rest[i]表示在前i天最後一個操作是冷凍,此時的最大收益。
rest並不代表什麼都不做,而是特製冷凍這個操作。

我們寫出遞推式為:
(1)buy[i] = max(rest[i-1] - price, buy[i-1])
分析:第i天買(可以看出買之前的狀態一定是冷凍,說明已經賣掉了,不會出現連續買的情況)和前i-1天最後一次操作是買然後第天什麼都不做兩種情況
(2)sell[i] = max(buy[i-1] + price, sell[i-1])
分析:第i天賣(可以看出賣之前的狀態一定是買,不會出現連續賣的情況)和前i-1天最後一次操作是賣然後第i天什麼都不做兩種情況
(3)rest[i] = max(sell[i-1], rest[i-1])
分析:第i天是冷凍(那麼第i-1天的操作一定是賣)和前i-1天最後一次操作是冷凍然後第i天什麼都不做兩種情況。其實rest[i]就直接等於sell[i-1],因為兩個操作是連續的,rest操作一定接著發生在sell操作之後,由於rest是冷凍,當前最大利益不會變化。
由分析可知,我們可以將上面三個遞推式精簡到兩個:
buy[i] = max(sell[i-2] - price, buy[i-1])
sell[i] = max(buy[i-1] + price, sell[i-1])

我們還可以做進一步優化,由於i只依賴於i-1和i-2,所以我們可以在O(1)的空間複雜度完成演算法。

public class Solution {
    public int maxProfit(int[] prices) {
        int sell=0,preSell=0,buy=Integer.MIN_VALUE,preBuy;

        for(int price:prices) {
            //每次迴圈開始,上一次迴圈得到的buy和sell自然成為了buy[i-1],sell[i-1]
            //preBuy和preSell自然成為了buy[i-2],sell[i-2]
            preBuy = buy;
            buy = Math.max(preBuy, preSell-price);
            preSell = sell; //沒做這步之前,preSell中存的還是sell[i-2]
            sell = Math.max(preSell, preBuy+price);
        }

        return sell;
    }
}

198. House Robber

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
分析:這道題嘗試了兩種動態規劃的解法,但是兩個解法的效率差別有點大。

先放第一種比較慢的解法:這種解法是動態規劃中帶備忘的自頂下下的方法,使用遞迴。但是速度的減慢並不是由於這種方法本身,而是確定最優子結構的方法沒有最簡。這種方法的最優子結構是這樣的:標號為i到j的房子,中間我選擇偷第k個房子,那麼這個問題有兩個子問題,偷i到k-2的房子和偷k+2到j的房子。

public class Solution {
    public int rob(int[] nums) {
        if(nums==null || nums.length==0) return 0;
        if(nums.length==1) return nums[0];

        int n = nums.length;
        int[][] money = new int[n][n];
        for(int i=0;i<n;i++) {
            for(int j=i;j<n;j++) {
                money[i][j] = -1;
            }
        }
        return robAux(0,n-1,nums,money);
    }

    public int robAux(int i,int j,int[] nums,int[][] money) {
        //邊界條件
        if(j<i) return 0;
        if(i==j) return nums[i];
        //已經算過的直接返回
        if(money[i][j] >= 0) return money[i][j];

        int max = 0;
        for(int k=i;k<=j;k++) {
            int tmp = robAux(i,k-2,nums,money) + nums[k] + robAux(k+2,j,nums,money);
            if(tmp > max) max = tmp;
        }
        money[i][j] = max;
        return max;
    }
}

第二種較快的解題方法:這種方法是用另一種最優子結構的方法,且採用的是自底向上的動態規劃的解法,無遞迴。偷前i個房子的問題的子問題有兩個,這兩個子問題分別對應第i個房子是偷還是不偷。

public class Solution {
    public int rob(int[] num) {
        if(num==null || num.length==0)
            return 0;

        int n = num.length;

        int[] dp = new int[n+1]; //dp[i]儲存偷前i個房子的最大收益
        dp[0]=0;
        dp[1]=num[0];

        for (int i=2; i<n+1; i++){
            //前i個房子的最大收益就是選擇偷第i個房子和不偷第i個房子二者中的較大值
            dp[i] = Math.max(dp[i-1], dp[i-2]+num[i-1]); 
        }

        return dp[n];
    }
}

213. House Robber II

Note: This is an extension of House Robber.

After robbing those houses on that street, the thief has found himself a new place for his thievery so that he will not get too much attention. This time, all houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, the security system for these houses remain the same as for those in the previous street.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
分析:這道題和上面一道題唯一的區別是首尾相連,那麼我們可以分兩種情況來討論,其中每種情況都會把這個環打破,然後就可以繼續用上面一道題的解法。第一種情況是不偷第一家,第二種情況是不偷第n家。一開始本來想按偷第一家和偷第n家來討論的,但是這樣還存在第三種情況,兩家都不偷,所有還是按不偷的情況討論更好。

public class Solution {
    public int rob(int[] nums) {
        if(nums==null || nums.length==0) return 0;
        if(nums.length<=3) {
            int max = 0;
            for(int i=0;i<nums.length;i++) {
                if(nums[i]>max) max = nums[i];
            }
            return max;
        }

        int n = nums.length;

        int[] rst1 = new int[n]; //不偷第一家
        rst1[0] = 0;
        rst1[1] = nums[1];

        int[] rst2 = new int[n]; //不偷第n家
        rst2[0] = 0;
        rst2[1] = nums[0];

        for(int i=2;i<n;i++) {
            rst1[i] = Math.max(rst1[i-1], rst1[i-2]+nums[i]);
            rst2[i] = Math.max(rst2[i-1],rst2[i-2]+nums[i-1]);
        }

        return Math.max(rst1[n-1], rst2[n-1]);
     }
}

300. Longest Increasing Subsequence

Given an unsorted array of integers, find the length of longest increasing subsequence.

For example,
Given [10, 9, 2, 5, 3, 7, 101, 18],
The longest increasing subsequence is [2, 3, 7, 101], therefore the length is 4. Note that there may be more than one LIS combination, it is only necessary for you to return the length.

Your algorithm should run in O(n2) complexity.

Follow up: Could you improve it to O(n log n) time complexity?
思路:以第k項結尾(這裡的意思是這個遞增序列的最大的那一項)的LIS的長度是:保證第i項比第k項小的情況下,以第i項結尾的LIS長度加一的最大值,取遍i的所有值(i小於k)。

public class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums==null || nums.length==0) return 0;

        int len = 1;
        int[] rst = new int[nums.length];
        rst[0] = 1;
        for(int i=1;i<nums.length;i++) {
            rst[i] = 1;
            for(int j=0;j<i;j++) {
                if(nums[j]<nums[i] && rst[j]+1>rst[i]) {
                        rst[i] = rst[j] + 1;
                }
            }
            if(rst[i]>len) len = rst[i];
        }

        return len;
    }
}

303. Range Sum Query - Immutable

Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.

Example:
Given nums = [-2, 0, 3, -5, 2, -1]

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
Note:
You may assume that the array does not change.
There are many calls to sumRange function.

分析:一開始沒有定位到最簡的最優子結構,導致線上時間超時。一開始定位的子問題是計算nums對應下標為i到j之間的數的和,然後就真的開始一個個計算。但是忽略了一個問題,i到j之間的數的和可以通過0到j之間的和減去0到i-1之間的和來實現呀,這樣大大減少了前期所需要的計算,只需要算出所有0到i之間的和(0<=i<=n-1)即可。

public class NumArray {
    int[] sums;  

    public NumArray(int[] nums) {
        if(nums==null || nums.length == 0) {
            sums = null;
            return;
        }
        sums = new int[nums.length];
        sums[0] = nums[0];
        for(int i=1; i<sums.length; i++) {  
            sums[i] = sums[i-1] + nums[i];   
        }  
    }  

    public int sumRange(int i, int j) {  
        if(sums==null || i>j || i<0 || j>=sums.length) return 0;  
        return i==0 ? sums[j] : (sums[j] - sums[i-1]);  
    }  
}


// Your NumArray object will be instantiated and called as such:
// NumArray numArray = new NumArray(nums);
// numArray.sumRange(0, 1);
// numArray.sumRange(1, 2);

304. Range Sum Query 2D - Immutable

Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).

分析:和上一題思路類似,一定要注意使用最簡的子問題。上一題是求兩個下標之間的和,子問題的結果簡化為只用1個下標就能確定。這一題是求四個下標之間的和,子問題的結果簡化為只用2個下標就能確定。很多時候動態規劃求出來的並一定必須是問題中的直接結果,如果可以用常數時間轉化為問題的結果,而動態規劃的過程因此得到大大簡化,何樂而不為呢。

public class NumMatrix {
    int[][] sums;
    int lenRow,lenCol;

    public NumMatrix(int[][] matrix) {
        if(matrix==null) sums = null;
        lenRow = matrix.length;
        if(lenRow==0) return;
        lenCol = matrix[0].length;

        sums = new int[lenRow][lenCol];
        sums[0][0] = matrix[0][0];
        for(int i=1;i<lenCol;i++) { //初始化第一行的從第1個開始的和
            sums[0][i] = sums[0][i-1] + matrix[0][i];
        }
        for(int i=1;i<lenRow;i++) { //初始化第一列的從第1個開始的和
            sums[i][0] = sums[i-1][0] + matrix[i][0];
        }

        for(int i=1;i<lenRow;i++) {
            for(int j=1;j<lenCol;j++) {
                sums[i][j] = sums[i-1][j] + sums[i][j-1] - sums[i-1][j-1] + matrix[i][j];
            }
        }
    }

    public int sumRegion(int row1, int col1, int row2, int col2) {
        if(sums==null || row1<0 || row2>=lenRow || col1<0 || col2>=lenCol) return 0;

        if(row1==0 && col1==0) return sums[row2][col2];
        if(row1==0) return sums[row2][col2]-sums[row2][col1-1];
        if(col1==0) return sums[row2][col2]-sums[row1-1][col2];
        return sums[row2][col2]-sums[row1-1][col2]-sums[row2][col1-1]+sums[row1-1][col1-1];
     }
}


// Your NumMatrix object will be instantiated and called as such:
// NumMatrix numMatrix = new NumMatrix(matrix);
// numMatrix.sumRegion(0, 1, 2, 3);
// numMatrix.sumRegion(1, 2, 3, 4);

338. Counting Bits

Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1’s in their binary representation and return them as an array.

Example:
For num = 5 you should return [0,1,1,2,1,2].

Follow up:

It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass?
Space complexity should be O(n).
Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language.
思路:dp[index] = dp[index - offset] + 1;

public class Solution {
    public int[] countBits(int num) {
        int[] f = new int[num + 1];
        for (int i=1; i<=num; i++) f[i] = f[i >> 1] + (i & 1);
        return f;
    }
}

91. Decode Ways

A message containing letters from A-Z is being encoded to numbers using the following mapping:

‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
Given an encoded message containing digits, determine the total number of ways to decode it.

For example,
Given encoded message “12”, it could be decoded as “AB” (1 2) or “L” (12).

The number of ways decoding “12” is 2.
解法1:動態規劃,從前往後

public class Solution {
    public int numDecodings(String s) {
        if(s == null || s.length() == 0) {
            return 0;
        }
        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 >= 1 && first <= 9) {
               dp[i] += dp[i-1];  
            }
            if(second >= 10 && second <= 26) {
                dp[i] += dp[i-2];
            }
        }
        return dp[n];
    }
}

解法2:動態規劃,從後往前,更快

public class Solution {
    public int numDecodings(String s) {
        int n = s.length();
        if (n == 0) return 0;

        int[] memo = new int[n+1];
        memo[n]  = 1;
        memo[n-1] = s.charAt(n-1) != '0' ? 1 : 0;

        for (int i = n - 2; i >= 0; i--)
            if (s.charAt(i) == '0') continue;
            else memo[i] = (Integer.parseInt(s.substring(i,i+2))<=26) ? memo[i+1]+memo[i+2] : memo[i+1];

        return memo[0];
    }
}

待續。。。