返回顶部

算法-动态规划-打家劫舍+买卖股票

1. 打家劫舍(LeetCode 198)

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,
影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。
class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if(n == 1)  return nums[0];
        if(n == 2)  return Math.max(nums[0], nums[1]);

        // dp[i]表示考虑0-i的最大金额
        int[] dp = new int[n];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);

        for(int i = 2; i<n; ++i) {
            // 选/不选 nums[i]
            dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
        }
        return dp[n-1];
    }
}

2. 打家劫舍II(LeetCode 213)

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。
这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的
同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

class Solution {
    // 因为首尾相连,所以只能在0-n-2 和 1-n-1偷
    // 子问题等价打家劫舍I。
    public int rob(int[] nums) {
        int n = nums.length;
        if(n == 1)  return nums[0];
        if(n == 2)  return Math.max(nums[0], nums[1]);

        return Math.max(robSimple(nums, 0, n-2), robSimple(nums, 1, n-1));
    }

    // 不考虑首尾相连的打家劫舍
    public int robSimple(int[] nums, int start, int end) {
        int n = end - start + 1;
        if(n == 1)  return nums[start];
        if(n == 2)  return Math.max(nums[start], nums[start+1]);
        int[] dp = new int[n];
        dp[0] = nums[start];
        dp[1] = Math.max(nums[start], nums[start+1]);
        for(int i = 2; i<n; ++i) {
            dp[i] = Math.max(dp[i-1], dp[i-2] + nums[start+i]);
        }
        return dp[n-1];
    }
}

3. 打家劫舍II(LeetCode 337)

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。
如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

class Solution {
    public int rob(TreeNode root) {
        int[] value = robTree(root);
        return Math.max(value[0], value[1]);
    }

    public int[] robTree(TreeNode root) {
        if(root == null)
            return new int[]{0, 0};
        
        // value[0]表示不选当前节点的最大价值
        // value[1]表示选当前节点的最大价值
        int[] value = new int[2];

        int[] left = robTree(root.left);
        int[] right = robTree(root.right);
        value[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        value[1] = root.val + left[0] + right[0];
        return value;
    }
}

4. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

思路

  • 只允许一买一卖
  • dp[i][0]表示第i天持有股票时的收益(负值),dp[i][1]表示第i天不持有股票时的收益
// 方法一(时间空间复杂度都由于方法二)
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int maxProfit = 0;

        // max[i]表示i 到 n-1中的最大元素
        int[] max = new int[n];
        max[n-1] = prices[n-1];
        for(int i = n-2; i>=0; --i) {
            max[i] = Math.max(max[i+1], prices[i]);
        }

        for(int i = 0; i<n; ++i){
            maxProfit = Math.max(maxProfit, max[i] - prices[i]);
        }

        return maxProfit;
    }
}

// 方法二(代码随想录)
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        // dp[i][0]代表第i天持有股票的最大收益
        // dp[i][1]代表第i天不持有股票的最大收益
        int[][] dp = new int[n][2];
        
        dp[0][0] = -prices[0];
        dp[0][1] = 0;

        for(int i = 1; i<n; ++i) {
            dp[i][0] = Math.max(dp[i-1][0], -prices[i]);
            dp[i][1] = Math.max(dp[i-1][1], dp[i][0] + prices[i]);
        }
        return dp[n-1][1];
    }
}

5. 买卖股票的最佳时机III(LeetCode 123)(有难度)

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

注意

  • 后三个递推式中的后半部分可以用dp[i]替换dp[i-1],例如dp[i][0] + prices[i]
  • 可以通过滚动数组进行空间优化,可以参考下一题
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        // dp[0]表示第一次买入,dp[1]表示第一次卖出
        // dp[2]表示第二次买入,dp[2]表示第二次卖出
        int[][] dp = new int[n][4];
        // 初始化
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        dp[0][2] = -prices[0];
        dp[0][3] = 0;

        for(int i = 1; i<n; ++i) {
            dp[i][0] = Math.max(dp[i-1][0], -prices[i]);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]);
            dp[i][2] = Math.max(dp[i-1][2], dp[i-1][1] - prices[i]);
            dp[i][3] = Math.max(dp[i-1][3], dp[i-1][2] + prices[i]);
        }

        return dp[n-1][3];
    }
}

6. 买卖股票的最佳时机IV(LeetCode 188)

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

class Solution {
    // 在只能交易两次的买卖股票问题上进行拓展
    public int maxProfit(int k, int[] prices) {
        int[] dp = new int[2*k];

        for(int i = 0; i<2*k; i+=2) {
            dp[i] = -prices[0];
        }

        for(int i = 1; i<prices.length; ++i) {
            dp[0] = Math.max(dp[0], -prices[i]);
            for(int j = 1; j<2*k-1; j+=2) {
                dp[j] = Math.max(dp[j], dp[j-1] + prices[i]);
                dp[j+1] = Math.max(dp[j+1], dp[j] - prices[i]);
            }
            dp[2*k-1] = Math.max(dp[2*k-1], dp[2*k-2] + prices[i]);
        }

        return dp[2*k-1];
    }
}

7. 买卖股票的最佳时机含冷冻期(LeetCode 309)

给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

img
思路:设计4个状态

  • dp[0]:买入状态(今天买入,昨天就是买入状态)
  • dp[1]:保持卖出状态(前天卖出昨天是冷冻期,昨天就卖出状态)
  • dp[2]:今天卖出股票
  • dp[3]:今天是冷冻期
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[][] dp = new int[n][4];

        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        dp[0][2] = 0;
        dp[0][3] = 0;

        for(int i = 1; i<n; ++i) {
            dp[i][0] = Math.max(Math.max(dp[i-1][0], dp[i-1][1]-prices[i]), dp[i-1][3]-prices[i]);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3]);
            dp[i][2] = dp[i-1][0] + prices[i];
            dp[i][3] = dp[i-1][2];
        }

        return Math.max(Math.max(dp[n-1][1], dp[n-1][2]), dp[n-1][3]);
    }
}

8. 买卖股票的最佳时机含手续费(LeetCode 714)

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格;整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。
如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。

  • 注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        // [0]表示持有股票的最大收益,[1]表示不持有股票的最大收益
        int[][] dp = new int[n][2];

        dp[0][0] = -prices[0];
        dp[0][1] = 0;

        for(int i = 1; i<n; ++i) {
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] - prices[i]);
            dp[i][1] = Math.max(dp[i-1][1], dp[i][0] + prices[i] - fee);
        }

        return dp[n-1][1];
    }
}
posted @ 2024-09-08 15:56  Frank23  阅读(4)  评论(0编辑  收藏  举报