leetcode题解日练--2016.7.9

日练三题,冰冻三尺非一日之寒。

今日题目:1、买卖股票III;2、格雷码;3、最大连续子序列和;4、矩形顶点之间的路径数。

今日摘录:一个人记得事情太多真不幸,知道事情太多也不幸,体会到太多事情也不幸。 ——沈从文《边城》

309. Best Time to Buy and Sell Stock with Cooldown | Difficulty: Medium

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]
相关题目:买卖股票I 买卖股票II
题意:买卖股票I是一共只买卖一次,买卖股票II是买卖不受限制,可以任意次的买卖。买卖股票III是说可以多次买卖,但是每次交易之后需要冷却一天不能进行交易。
思路:
因为这个限制条件使得找状态方程的难度变大了,自己尝试着去列了几次方程也都没有成功。最后看来下discuss中的一个方法,很巧妙,https://discuss.leetcode.com/topic/30421/share-my-thinking-process,加上我自己的语言理解了下。
一、首先定义三个状态,买,卖,冷却,因此我们可以用三个数组来表示这些状态:
buy[i]:第i天以及之前的最后以买状态结尾所有可能中的最大利润
sell[i]:第i天以及之前的最后以卖状态结尾所有可能中的最大利润
reset[i]:第i天以及之前的最后以冷却状态结尾所有可能中的最大利润
二、下面来列状态方程,
buy[i] = max(reset[i-1]-price,buy[i-1]) ,直观理解就是第i天以buy结尾的最大利润就是①、第i-1天冷却同时第i天买入;②、第i-1天买入的最大利润,之后不做操作,仍然是以买结尾的。
这么说可能不太好懂,看个栗子:
buy[i]的定义是在i之前(包含i)所有以i结尾的序列中的最大利润。
prices = [1,5,3,4]
buy[0] = -1 max of [buy]
buy[1] = -1 max of [buy], [rest, buy]
buy[2] = -1 max of [buy], [rest, buy], [rest, rest, buy]
buy[3] = 0 max of [buy](第0天买,后面无操作), [rest, buy](第1天买), [rest, rest, buy](第二天买), [rest, rest, rest, buy](第三天买), [buy, sell, rest, buy](买卖冷却之后再买)

照着思路来看,sell[i] = max(buy[i-1]+price, sell[i-1])就很好理解了,i-1天及之前最后是以卖结尾的,在第i天卖出与 第i-1天就卖出,第i天不做任何操作这两者中的最大利润。

rest[i] = max(sell[i-1], buy[i-1], rest[i-1])就是在i-1天是以买或者卖或者冷却结尾的最大值,因为其不影响利润。但是显然,buy[i]<=reset[i]<=sell[i-1],所以rest[i] = sell[i-1]
可以将方程改写成:
buy[i] = max(reset[i-1]-price, buy[i-1]) = max(sell[i-2],buy[i-1])
sell[i] = max(buy[i-1]+price, sell[i-1])
现在用buy和sell表示当前第i天的最大利润,prev_buy和prev_sell表示第i天之前的最大利润。
prices = [1,5,3,4]
三、代码实现
首先初始化,prev_buy = buy = -prices[0],sell=prev_sell=0;

  • i=0,price=1, prev_buy显然为buy(因为此时buy还没更新),buy应该是当前买与不买中的最大者,buy = max(prev_sell-price,buy),prev_sell为还没更新之前的sell,sell为当前卖与不卖的最大值sell=max(prev_buy+price,sell)。一次更新之后prev_buy = buy = -1;prev_sell=sell=0;
  • i=1,price=5, 更新之后prev_buy = -1,buy = -1;prev_sell=0,sell=4;
  • i=2,price=3, 更新之后prev_buy = -1,buy = -1;prev_sell=4,sell=4;
  • i=3,price=4, 更新之后prev_buy = -1,buy = 0;prev_sell=4,sell=4;

代码:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size()==0)    return 0;
        int prev_buy=-prices[0],prev_sell=0,buy = -prices[0],sell=0;
        for(int price: prices)
        {
            prev_buy = buy;
            buy = max(prev_sell-price,buy);
            prev_sell=sell;
            sell=max(prev_buy+price,sell);
        }
        return sell;
    }
};

结果:8ms

89. Gray Code | Difficulty: Medium

The gray code is a binary numeral system where two successive values differ in only one bit.

Given a non-negative integer n representing the total number of bits in the code, print the sequence of gray code. A gray code sequence must begin with 0.

For example, given n = 2, return [0,1,3,2]. Its gray code sequence is:

00 - 0
01 - 1
11 - 3
10 - 2
Note:
For a given n, a gray code sequence is not uniquely defined.

For example, [0,2,3,1] is also a valid gray code sequence according to the above definition.

For now, the judge is able to judge based on one instance of gray code sequence. Sorry about that.

题意:写出n位数字的格雷码序列。
参考资料:wiki_格雷码
思路:二进制转格雷码就是将相应的二进制数n与n/2做一次异或操作。

class Solution {
public:
    vector<int> grayCode(int n) {
        vector<int> res;
        for(int i=0;i<1<<n;i++)
            res.push_back(i^(i/2));
        return res;
    }
};

结果:4ms

53. Maximum Subarray | Difficulty: Medium

Find the contiguous subarray within an array (containing at least one number) which has the largest sum.

For example, given the array [−2,1,−3,4,−1,2,1,−5,4],
the contiguous subarray [4,−1,2,1] has the largest sum = 6.

题意:找最大连续子序列和
思路:
1、动态规划法
只要前i项的和还没有小于0那么子序列就一直向后扩展,否则丢弃之前的子序列开始新的子序列,同时我们要记下各个子序列的和,最后找到和最大的子序列。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int max=INT_MIN,sum=0;
        for(int num : nums)
        {
            if(sum<0)   sum=num;
            else        sum+=num;
            if(sum>max) 
                max = sum;
        }
        return max;
    }
};

结果:12ms

2、分治法
More practice:
If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.
题目说可以用分治法,不妨来思考下如何用分治来解决最大子序列和的问题。
最大序列和可能分布在什么地方?无非三种情况,整个数组的左半部分,整个数组的右半部分,横跨中间点既包含左半部分又包含右半部分的值。
这里需要注意全部都是负数的情况。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        return divideSubArray(nums,0,nums.size()-1);
    }
    int divideSubArray(vector<int>& nums,int left,int right)
    {
        if(left==right)         return nums[left];
        int mid =left+ (right-left)/2;
        int maxLeftSub = divideSubArray(nums,left,mid);
        int maxRightSub = divideSubArray(nums,mid+1,right);
        int midLeftMax = 0,midLeftSum=0;
        for(int i=mid;i>=left;i--)
        {
            midLeftSum+=nums[i];
            if(midLeftSum>midLeftMax)
                midLeftMax = midLeftSum;
        }
        int midRightMax = 0,midRightSum=0;
        for(int i=mid+1;i<=right;i++)
        {
            midRightSum+=nums[i];
            if(midRightSum>midRightMax)
                midRightMax = midRightSum;
        }
        if(midLeftMax==0&&midRightMax==0)   midLeftMax=min(nums[mid],nums[mid+1]);
        return max(max(maxLeftSub,maxRightSub),midLeftMax+midRightMax);

    }
};

结果:12ms

62. Unique Paths | Difficulty: Medium

A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).

The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).

How many possible unique paths are there?

题意:一个机器人在m×n矩形的左上角,机器人只能往右和下走,问它有多少种走法?
思路:
1、典型的动态规划思路,用一个dp[m][n]来表示所有坐标的点可能的路径数。这里以m=3,n=3为例说明。
0 0 0 0 1 0 0 1 0 0 1 1 0 1 1 0 1 1
0 0 0—->1 0 0—->1 2 0—->1 2 0—->1 2 3—->1 2 3
0 0 0 0 0 0 0 0 0 1 0 0 1 3 0 1 3 6
每次,我们可以通过一个递推式
除了dp[0][0]=0,其他第0行与第0列的元素都等于1之外,其他元素都可以用
dp[i][j]=dp[i-1][j]+dp[i][j-1]来计算。

class Solution {
public:
    int uniquePaths(int m, int n) {
        if(m==1 || n==1)    return 1;
        vector<vector<int>> dp(m,vector<int>(n,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];
    }
};

结果:0ms

2、方法1很容易想到,时间复杂度和空间复杂度都是m*n,明显还有改进的空间。
通过观察,首先我们可以通过认为的翻转使得矩形永远是宽度大于高度,这样并不会影响最终的结果。
一列一列去进行计算,计算第i列元素只需要保留i-1列的信息即可。也就是说可以用两列代替一个二位的向量。

class Solution {
public:
    int uniquePaths(int m, int n) {
        if(m==1 || n==1)    return 1;
        if(m>n) return uniquePaths(n,m);
        vector<int> pre(m,1);
        vector<int> cur(m,1);
        for(int j=1;j<n;j++)
        {
            for(int i=1;i<m;i++)
                cur[i] = cur[i-1]+pre[i];
            swap(pre,cur);
        }
        return pre[m-1];
    }
};

结果:0ms
3、方法2将空间复杂度降到了O(min(m,n)),https://discuss.leetcode.com/topic/15265/0ms-5-lines-dp-solution-in-c-with-explanations/2的方法将空间复杂度进一步降低了,只用了一个向量来保存。

class Solution {
    int uniquePaths(int m, int n) {
        if (m > n) return uniquePaths(n, m);
        vector<int> cur(m, 1);
        for (int j = 1; j < n; j++)
            for (int i = 1; i < m; i++)
                cur[i] += cur[i - 1]; 
        return cur[m - 1];
    }
}; 

结果:0ms

4、用数学的方法来思考。m*n的矩形,我们需要做m-1次向下的操作和n-1次向右的操作,总计m+n-2次操作。现在我们要找的是这些操作有多少种组合数。这显然等价于从m+n-2个空位中挑出指定的m-1个向下操作或者挑出指定n-1个向右操作。

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

结果:4ms

你可能感兴趣的:(LeetCode,编程,日记)