LeetCode> 121. 买卖股票的最佳时机(三种解法)

题目

对于非负整数 X 而言,X 的数组形式是每位数字按从左到右的顺序形成的数组。例如,如果 X = 1231,那么其数组形式为 [1,2,3,1]。

给定非负整数 X 的数组形式 A,返回整数 X+K 的数组形式。

示例 1:

输入:A = [1,2,0,0], K = 34
输出:[1,2,3,4]
解释:1200 + 34 = 1234

示例 2:

输入:A = [2,7,4], K = 181
输出:[4,5,5]
解释:274 + 181 = 455

示例 3:

输入:A = [2,1,5], K = 806
输出:[1,0,2,1]
解释:215 + 806 = 1021

示例 4:

输入:A = [9,9,9,9,9,9,9,9,9,9], K = 1
输出:[1,0,0,0,0,0,0,0,0,0,0]
解释:9999999999 + 1 = 10000000000

提示:

1 <= A.length <= 10000
0 <= A[i] <= 9
0 <= K <= 10000
如果 A.length > 1,那么 A[0] != 0

解题思路

方法一:暴力搜索
T(n)=O(n^2)
遍历价格数组, 求解以当天为卖出日期的最大值. 得到一个针对每天卖出股票的利润最大值数组, 数组的最大值, 即为利润最大值

    /**
     * 方法一: 暴力搜索, T(n)=O(n^2)
     * 遍历价格数组, 求解以当天为卖出日期的最大值. 得到一个针对每天卖出股票的利润最大值数组, 数组的最大值, 即为利润最大值
     */
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length < 1) return 0;
        int max = 0;

        for (int i = 1; i < prices.length; i++) {
            for (int j = 0; j < i && j < prices.length; j++) {
                int profit = prices[i] - prices[j];
                if (profit > max) max = profit;
            }
        }

        return max;
    }

方法二:动态规划
T(n) = O(n)
先求出到第i天时利润最大值dp[i], 到最后一天时, 利润最大值就是dp[i]。
dp[i]的求法, 分为两种可能情况:
1.第i天有卖出, 也就是0..i-1天中股价最小时买入, 第i天卖出;
2.第i天没有卖出, 利润最大值同第i-1天。

/**
 * 方法二: 动态规划, T(n) = O(n)
 * 先求出到第i天时利润最大值dp[i], 到最后一天时, 利润最大值就是dp[i]
 * dp[i]的求法, 分为两种可能情况: 
 * 1.第i天有卖出, 也就是0..i-1天中股价最小时买入, 第i天卖出;
 * 2.第i天没有卖出, 利润最大值同第i-1天.
  */
public int maxProfit(int[] prices) {
    if (prices == null || prices.length < 1) return 0;

    int min = prices[0];
    int[] dp = new int[prices.length];
    dp[0] = 0;
    for (int i = 1; i < dp.length; i++) {
        if (prices[i-1] < min) min = prices[i-1];
        dp[i] = Math.max(dp[i-1], prices[i] - min);
    }

    return dp[dp.length-1];
}

方法三:分治法
T(n) = O(nlgn)
先求出每天股票变化值(prices[i]-prices[i-1]), 问题转化为求解(和)最大连续子数组
数组A[low..high]中的最大连续子数组A[left..right]分为三种情况:

  1. 子数组全部落到左半部, 即low <= left < right <= mid;
  2. 子数组全部落到右半部, 即mid < left < right <= high;
  3. 子数组跨越中点, 即low <= left <= mid < right <= high;
    情形1和2, 是求解最大连续子数组的子问题, 可以递归求解. 情形3有特殊限制, 需要专门讨论如何处理
/**
 * 方法三: 分治法, T(n) = O(nlgn)
 * 先求出每天股票变化值(prices[i]-prices[i-1]), 问题转化为求解(和)最大连续子数组
 * 数组A[low..high]中的最大连续子数组A[left..right]分为三种情况:
 * 1. 子数组全部落到左半部, 即low <= left < right <= mid;
 * 2. 子数组全部落到右半部, 即mid < left < right <= high;
 * 3. 子数组跨越中点, 即low <= left <= mid < right <= high;
 * 情形1和2, 是求解最大连续子数组的子问题, 可以递归求解. 情形3有特殊限制, 需要专门讨论如何处理
 */
public int maxProfit(int[] prices) {
    if (prices == null || prices.length < 1) return 0;

    for (int i = prices.length-1; i > 0; i--) {
        prices[i] = prices[i] - prices[i-1];
    }
    prices[0] = 0;

    int[] res = findMaximumSubArray(prices, 0, prices.length-1);

    return res[2];
}

public int[] findMaxCrossingSubArray(int[] A, int low, int mid, int high) {

    int leftSum = Integer.MIN_VALUE;
    int maxLeft = mid;
    int sum = 0;
    for (int i = mid; i >= low ; i--) {
        sum += A[i];
        if (sum > leftSum) {
            leftSum = sum;
            maxLeft = i;
        }
    }

    int rightSum = Integer.MIN_VALUE;
    int maxRight = mid;
    sum = 0;
    for (int j = mid+1; j <= high; j++) {
        sum += A[j];
        if (sum > rightSum) {
            rightSum = sum;
            maxRight = j;
        }
    }

    return new int[]{maxLeft, maxRight, (leftSum + rightSum)};
}

public int[] findMaximumSubArray(int[] A, int low, int high) {
    if (low == high) {
        return new int[]{low, high, A[low]};
    }
    else {
        int mid = (low + high) / 2;
        int[] leftRes = findMaximumSubArray(A, low, mid);
        int[] rightRes = findMaximumSubArray(A, mid+1, high);
        int[] crossRes = findMaxCrossingSubArray(A, low, mid, high);

        if (leftRes[2] >= rightRes[2] && leftRes[2] >= crossRes[2])
            return leftRes;
        else if(rightRes[2] >= leftRes[2] && rightRes[2] >= crossRes[2])
            return rightRes;
        else return crossRes;
    }
}

Ref
LeetCode 121. 买卖股票的最佳时机

posted @ 2020-12-10 20:44  明明1109  阅读(247)  评论(0编辑  收藏  举报