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; ireturn 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; ireturn 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;ifor(int j=i;j1;
            }
        }
        return robAux(0,n-1,nums,money);
    }

    public int robAux(int i,int j,int[] nums,int[][] money) {
        //边界条件
        if(jreturn 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; i1; 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;iif(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;i1], 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;i1;
            for(int j=0;jif(nums[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; i1] + 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//初始化第一行的从第1个开始的和
            sums[0][i] = sums[0][i-1] + matrix[0][i];
        }
        for(int i=1;i//初始化第一列的从第1个开始的和
            sums[i][0] = sums[i-1][0] + matrix[i][0];
        }

        for(int i=1;ifor(int j=1;j1][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];
    }
}

待续。。。

你可能感兴趣的:(LeetCode)