【算法与数据结构】【力扣所有股票题】状态转移、动态规划

想说的话

  • 这类型的题,与其说是动态规划、状态转移,我更偏向认为它是贪心算法,是因为这类型的题十分人性化。推理过程也很贪心,局部最优形成总的最优。

  • 今天把股票题全部手写了一遍,手推了一遍,花了5个小时,量变产生质变。309题从推理状态转移方程到完成代码只花费了10分钟。详情见如309.最佳买卖股票时机含冷冻期。

  • 今晚我会发出打家劫舍、等差数列划分、背包的四种情况。

121. 买卖股票的最佳时机

做法一:状态解法、动态规划(4ms)每次的当前每个状态都是最优
// 第一版
public static int maxProfit(int[] prices) {
    int[][] states = new int[prices.length][2];//0:买,1:卖
    /*
    * 状态变化解释:
    * 卖:states[i][1] = Math.max(states[i-1][0]+prices[0] , states[i-1][1]);
    *      当前如果要卖,则卖后最优解为:Math.max(当前的价格 + 之前购买后的剩余金额(负数) , 之前卖后的最优价格)
    *      比如:当前价格为8,之前购买后手头金额最多为 -1,之前卖后的最优剩余为10,
    *      此时 Math.max( 8+(-1) , 10);//这个时候不适合卖,还是之前的10更多,所以不修改最优解。
    *
    * 买:由于只能买一次,则买后最优解为:Math.max(前面的买后手头金额,-当前的价格);
    *                states[i][0] = Math.max(states[i - 1][0],  - prices[i]);
    */
    
    states[0][0] = -prices[0];
    for (int i = 1; i < prices.length; i++) {
        states[i][1] = Math.max(states[i-1][1],states[i - 1][0] + prices[i]);
        //由于只能买一次,所以每次的买后的值,都要直接改变,而不是由之前的卖后值—当前的价格(二次购买)。
        states[i][0] = Math.max(states[i - 1][0],  - prices[i]);
    }
    return states[prices.length-1][1];
}


//优化空间,写完后会发现,我们只用了2个最优解的值。
public static int maxProfit(int[] prices) {
    int[] states = new int[2];//0:买,1:卖
    states[0] = -prices[0];
    for (int i = 1; i < prices.length; i++) {
        states[1] = Math.max(states[1],states[0] + prices[i]);
        //由于只能买一次,所以每次的买后的值,都要直接改变,而不是由之前的卖后值—当前的价格(二次购买)。
        states[0] = Math.max(states[0],  - prices[i]);
    }
    return states[1];
}
做法二:暴力解法、贪心算法,每次得到最优解(1ms)
public int maxProfit(int[] prices) {
    if(prices.length<2) return 0;
    int min = prices[0];
    int res = 0;
    for(int price:prices){
        if(min>=price){
            min = price;
            continue;
        }
        //每次直接得到最优解
        res = Math.max(price-min,res);
    }
    return res;
}

122. 买卖股票的最佳时机 II

做法一:状态解法、动态规划(2ms)
public int maxProfit(int[] prices) {
    int[] states = new int[2];
    states[0] = -prices[0];
    for (int i = 1; i < prices.length; i++) {
        int pre1 = states[1];
        states[1] = Math.max(states[1], states[0] + prices[i]);
        //只有这里有改动,即每次买后的价格,为上一次最优的买后价格-当前价格:states[1]-prices[i];即允许多次购买,基于以前的最优卖后结果进行购买
        states[0] = Math.max(states[0], pre1-prices[i]);
    }
    return states[1];
}
做法二:暴力解法、贪心算法,局部最优,得到整体最优。
public int maxProfit(int[] prices) {
    int res = 0;
    int n = prices.length;
    for(int i=0;i<n-1;i++){
        //1234,的局部最优是2-1=1,3-2=1,4-3=1,整体最优为 4-1 = 1+1+1 = 3
        if(prices[i]<prices[i+1]){//遇到升值,直接进行单次买卖:12341215:在2、3、4、2、5卖出,局部最优
            res = res + prices[i+1] - prices[i];
        }
    }
    return res;
}

123. 买卖股票的最佳时机 III

public int maxProfit(int[] prices) {
    int n = prices.length;
    int[] states = new int[]{-prices[0], 0, -prices[0], 0};
    for (int i = 1; i < n; i++) {
        //保存上轮第一次买的最优价格,上轮第一次买最优价格+当前价格,Math.max()后可能为第一次卖的最优价格
        int pre0 = states[0];
        //保存上轮第二次买的最优价格,上轮第二次买最优价格+当前价格,Math.max()后可能为第二次卖的最优价格
        int pre2 = states[2];
        states[0] = Math.max(-prices[i], states[0]);//第一次买最优
        states[2] = Math.max(states[2], states[1] - prices[i]);//第二次买最优
        states[1] = Math.max(prices[i] + pre0, states[1]);//第一次卖最优
        states[3] = Math.max(prices[i] + pre2, states[3]);//第二次卖最优
    }
    return states[3];
}


//解法二:最优解:不需要保存上一轮买后的最优解。
//如果这一轮的买后余额被替换,说明,这一轮买入是最便宜的,则这一轮的卖后余额不变,因为遇到最低价了!
//代码逻辑使得:它无法在最低价(亏本)的时候卖出!如下
// sell1 = Math.max(sell1, buy1 + prices[i]);
// sell2 = Math.max(sell2, buy2 + prices[i]); 

public int maxProfit2(int[] prices) {
    int n = prices.length;
    int buy1 = -prices[0], sell1 = 0;
    int buy2 = -prices[0], sell2 = 0;
    for (int i = 1; i < n; ++i) {
        buy1 = Math.max(buy1, -prices[i]);
        sell1 = Math.max(sell1, buy1 + prices[i]);
        buy2 = Math.max(buy2, sell1 - prices[i]);
        sell2 = Math.max(sell2, buy2 + prices[i]);
    }
    return sell2;
}

188. 买卖股票的最佳时机 IV

方法一:直接求解
public int maxProfit(int k, int[] prices) {
    if(k==0) return 0;
    if(prices.length<2) return 0;
    int[][] states = new int[k][2];//0:买入,1:卖出
    // 不设置初始值,Math.max(0,-当前价格)  的返回值永远为0.
    for (int i = 0; i < k; i++) {
        states[i][0] = -prices[0];
    }
    for (int price : prices) {
        for (int i = 0; i < k; i++) {
            //只有当i==0时,需要特殊处理。
            states[i][0] = Math.max(states[i][0], (i>0?states[i - 1][1]:0) - price);
            states[i][1] = Math.max(states[i][1], (i>0?states[i][0]:states[0][0]) + price);
        }
    }
    return states[k-1][1];
}
方法二:使用了上面两个卖股票的算法进行求解

(比方法一多判断了一种情况,并将这种情况进行特殊处理maxProfitNormal(prices),减少了分支)

public int maxProfit(int k, int[] prices) {
    if(k==0) return 0;
    if (k > prices.length / 2) {
        return maxProfitNormal(prices);
    }
    int[][] states = new int[k][2];//0:买入,1:卖出
    // [123. 买卖股票的最佳时机 III]的通用解
    for (int i = 0; i < k; i++) {
        states[i][0] = -prices[0];
    }
    for (int price : prices) {
        for (int i = 0; i < k; i++) {
            states[i][0] = Math.max(states[i][0], (i>0?states[i - 1][1]:0) - price);
            states[i][1] = Math.max(states[i][1], (i>0?states[i][0]:states[0][0]) + price);
        }
    }
    return states[k-1][1];
}

//[122. 买卖股票的最佳时机 II]的贪心求解
private int maxProfitNormal(int[] prices) {
    int res = 0;
    int n = prices.length;
    for (int i = 0; i < n - 1; i++) {
        //1234,的局部最优是2-1=1,3-2=1,4-3=1,整体最优为 4-1 = 1+1+1 = 3
        if (prices[i] < prices[i + 1]) {//遇到升值,直接进行单次买卖:12341215:在2、3、4、2、5卖出,局部最优
            res = res + prices[i + 1] - prices[i];
        }
    }
    return res;
}

309. 最佳买卖股票时机含冷冻期

// 开始发生质变,推理到完成,仅用时10分钟
public static int maxProfit(int[] prices) {
    int[] states = new int[3];//0买入,1卖出,2冷冻
    /**
     * states[0] = 当前买入最优解 = Math.max(当前买入最优解,当前冷冻最优解-当前价格)
     * states[1] = 当前卖出最优解 = Math.max(当前卖出最优解,当前买入最优解+当前价格)
     * states[2] = 当前冷冻最优解 = Math.max(当前冷冻最优解,上次卖出价格)
     */
    states[0] = -prices[0];
    for (int price:prices){
        int pre1 = states[1];
        states[0] = Math.max(states[0],states[2]-price);
        states[1] = Math.max(states[1],states[0]+price);
        states[2] = Math.max(states[2],pre1);
    }
    return states[1];
}

714. 买卖股票的最佳时机含手续费

public static int maxProfit(int[] prices, int fee) {
    int buy = prices[0] + fee;//代价
    int res = 0;
    for (int price : prices) {
        //代价更小,则
        if (price + fee < buy) {
            buy = price + fee;
        } else if (price > buy) {
            res += price - buy;
            buy = price;//下次购买代价的底线,就是今天卖出的价格
        }
    }
    return res;
}

剑指 Offer 63. 股票的最大利润

和第一道题一模一样

public int maxProfit(int[] prices) {
    if(prices.length<2) return 0;
    int min = prices[0];
    int res = 0;
    for(int price:prices){
        if(min>=price){
            min = price;
            continue;
        }
        //每次直接得到最优解二
        res = Math.max(price-min,res);
    }
    return res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值