LeeCode 动态规划(四)

LeeCode 198:打家劫舍

题目描述

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

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

建立模型

  1. 确定 dp 数组及下标的含义,数组的含义为在下标 0~i 的房屋内,能偷窃的最高金额
  2. 初始化 dp 数组:dp[0] = nums[0], dp[1] = Math.max(nums[0], nums[1])
  3. 确定递推公式:dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]) (2 <= i <= n - 1)
  4. 确定遍历顺序,i -> 2 ~ n - 1

代码实现

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]);
} int[] dp = new int[n]; dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]); for (int i = 2; i < n; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
} return dp[n - 1];
}

LeeCode 213:打家劫舍II

题目描述

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

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

建立模型

本题与 LeeCode 198 打家劫舍 唯一的区别就是第一个房屋和最后一个房屋相连,不能同时偷取。所以可以同时考虑以下两种情况:

  • 第一个房屋 ~ 倒数第二个房屋
  • 第二个房屋 ~ 倒数第一个房屋

最后,取两者的最大值。

代码实现

public int robII(int[] nums) {
int n = nums.length; if (n == 1) {
return nums[0];
} if (n == 2) {
return Math.max(nums[0], nums[1]);
} // 第一个房屋 ~ 倒数第二个房屋
int[] dp1 = new int[n - 1]; // 第二个房屋 ~ 倒数第一个房屋
int[] dp2 = new int[n - 1]; dp1[0] = nums[0];
dp1[1] = Math.max(nums[0], nums[1]);
dp2[0] = nums[1];
dp2[1] = Math.max(nums[1], nums[2]); for (int i = 2; i < n - 1; i++) {
dp1[i] = Math.max(dp1[i - 2] + nums[i], dp1[i - 1]);
dp2[i] = Math.max(dp2[i - 2] + nums[i + 1], dp2[i - 1]);
} return Math.max(dp1[n - 2], dp2[n - 2]);
}

LeeCode 337:打家劫舍III

题目描述

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

建立模型

本题将前两题的链式房屋结构转换成了树形结构,但也可以考虑动态规划,只不过是在树上进行状态转移。

该问题描述等价于一棵二叉树,树上每个节点都有对应的权值,每个节点有两种状态(选择不与不选择),在不能同时选择相连的 “父子” 节点的情况下,选中的点最大权值和是多少。

首先需要明确该问题是后序遍历的,要先确定下层节点的值。对于树上的每一个节点 node ,有选中与不选中两种选择。使用哈希表 f 表示选择当前节点情况下,其子树上被选中的最大权值和,哈希表 g 表示不选择当前节点情况下,其子树上被选中的最大权值和。

递推公式:

\[\begin{cases}
f(o) = o.val + Math.max(g(o.left),\ g(o.right)) \\
g(o) = Math.max(f(o.left),\ g(o.left)) + Math.max(f(o.right),\ g(o.right))
\end{cases}
\]

代码实现

Map<TreeNode, Integer> f = new HashMap<>();
Map<TreeNode, Integer> g = new HashMap<>(); public int robIII(int[] nums) {
dfs(root);
return Math.max(f.getOrDefault(root, 0), g.getOrDefault(root, 0));
} public void dfs(TreeNode node) {
if (node == null) {
return;
} dfs(node.left);
dfs(node.right); f.put(node, node.val + g.getOrDefault(node.left, 0) + g.getOrDefault(node.right, 0));
g.put(node, Math.max(f.getOrDefault(node.left, 0), g.getOrDefault(node.left, 0)) +
Math.max(f.getOrDefault(node.right, 0), g.getOrDefault(node.right, 0)));
}

LeeCode 121:买卖股票的最佳时机

题目描述

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

建立模型

  1. 确定 dp 数组及下标的含义,数组的含义为第 i 天卖出能得到的最大利润
  2. 初始化 dp 数组,dp[i] = 0 (0 <= i < prices.length)
  3. 确定递推公式:dp[i] = prices[i] - buy
  4. 确定遍历顺序,i -> 0 ~ prices.length - 1

代码实现

public int maxProfit(int[] prices) {
// 使用 buy 记录当天之前的最低买入价
int buy = prices[0];
int[] dp = new int[prices.length]; for (int i = 1; i < prices.length; i++) {
dp[i] = prices[i] - buy;
buy = Math.min(buy, prices[i]);
} return Arrays.stream(dp).max().getAsInt();
}

LeeCode 122:买卖股票的最佳时机II

题目描述

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

建立模型

本题的最优解法是贪心选择,选择的策略是 只有今天的股价高于昨天,就交易,即将数组中所有递增的差值求和。该策略的有效性证明可以查看官方解答

如何使用动态规划解决本题呢?

  1. 确定 dp 数组及下标的含义,数组的含义为: dp[i][0] -> 当天未持有股票已获的最大利润dp[i][1] -> 当天持有股票已获的最大利润

  2. 初始化 dp 数组:dp[0][0] = 0, dp[0][1] = -prices[0]

  3. 确定递推公式:

    \[\begin{cases}
    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 - 1][0] - prices[i])
    \end{cases}
    \]
  4. 确定遍历顺序 i -> 1 ~ prices.length

代码实现

/**
* 贪心选择
*/
public int maxProfitII(int[] prices) {
int res = 0;
for (int i = 1; i < prices.length; i++) {
res += Math.max(0, prices[i] - prices[i - 1]);
} return res;
} /**
* 动态规划
*/
public int maxProfitII(int[] prices) {
if (prices.length < 2) {
return 0;
} int[][] dp = new int[prices.length][2]; // 0 表示未持有股票, 1表示持有股票
dp[0][0] = 0;
dp[0][1] = -prices[0]; for (int i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][0]);
dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
} return dp[prices.length - 1][0];
} /**
* 状态压缩,因为当前状态只与上一状态有关
*/
public int maxProfitII(int[] prices) {
if (prices.length < 2) {
return 0;
} // dp[0] 表示当前未持有股票的最大利润
// dp[1] 表示当前已持有股票的最大利润
int[] dp = new int[2]; dp[0] = 0;
dp[1] = -prices[0]; for (int i = 1; i < prices.length; i++) {
dp[0] = Math.max(dp[0], dp[1] + prices[i]);
dp[1] = Math.max(dp[1], dp[0] - prices[i]);
} return dp[0];
}

LeeCode 123: 买卖股票的最佳时机III

题目描述

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

建立模型

最多可以完成两笔交易,所以任意一天结束之后会处于以下五个状态中的一种:

  • 未操作
  • 第一次买操作
  • 第一次卖操作
  • 第二次买操作
  • 第二次卖操作

动态规划分析步骤

  1. 确定 dp 数组及下标函数,数组的含义为第 i 天结束后处于状态 j 的最大利润

  2. 初始化 dp 数组:

    \[\begin{cases}
    dp[0][0] = 0 \\
    dp[0][1] = -prices[0] \\
    dp[0][2] = 0 \\
    dp[0][3] = -prices[i] \\
    dp[0][4] = 0
    \end{cases}
    \]

  3. 确定递推公式:

    \[\begin{cases}
    dp[i][0] = dp[i - 1][0] \\
    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]) \\
    dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i])
    \end{cases}
    \]
  4. 确定递推顺序:i -> 1 ~ prices.length - 1

代码实现

public int maxProfitIII(int[] prices) {
int[][] dp = new int[prices.length][5]; /**
* 0 —— 未操作
* 1 —— 第一次买入
* 2 —— 第一次卖出
* 3 —— 第二次买入
* 4 —— 第二次卖出
*/
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
dp[0][3] = -prices[0];
dp[0][4] = 0 for (int i = 1; i < prices.length; i++) {
dp[i][0] = dp[i - 1][0];
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]);
dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
} return dp[prices.length - 1][4];
} /**
* 状态压缩,当前状态只和前一状态有关,且状态0不影响dp过程
*/
public int maxProfitIII(int[] prices) {
if (prices.length < 2) {
return 0;
} int[] dp = new int[4]; /**
* 0 -> 第一次买入
* 1 -> 第一次卖出
* 2 -> 第二次买入
* 3 -> 第二次卖出
*/
dp[0] = -prices[0];
dp[1] = 0;
dp[2] = -prices[i];
dp[3] = 0; for (int i = 1; i < prices.length; i++) {
dp[0] = Math.max(dp[0], -prices[i]);
dp[1] = Math.max(dp[1], dp[0] + prices[i]);
dp[2] = Math.max(dp[2], dp[1] - prices[i]);
dp[3] = Math.max(dp[3], dp[2] + prices[i]);
} return dp[3];
}

LeeCode 动态规划(四)的更多相关文章

  1. leecode第四十六题(全排列)

    class Solution { public: vector<vector<int>> permute(vector<int>& nums) { int ...

  2. leecode第四十三题(字符串相乘)

    class Solution { public: string multiply(string num1, string num2) { ";//特殊情况 ] == ] == ') retu ...

  3. leecode第四题(寻找两个有序数组的中位数)

    题解: class Solution { public: double findMedianSortedArrays(vector<int>& nums1, vector<i ...

  4. 《剑指offer》第十四题(剪绳子)

    // 面试题:剪绳子 // 题目:给你一根长度为n绳子,请把绳子剪成m段(m.n都是整数,n>1并且m≥1). // 每段的绳子的长度记为k[0].k[1].…….k[m].k[0]*k[1]* ...

  5. 6专题总结-动态规划dynamic programming

    专题6--动态规划 1.动态规划基础知识 什么情况下可能是动态规划?满足下面三个条件之一:1. Maximum/Minimum -- 最大最小,最长,最短:写程序一般有max/min.2. Yes/N ...

  6. homework-1

    看到这个题目开始我只能想到动态规划四个字,但具体采用什么方法,如何写成代码却还未成型.动态规划的典型特点就是利用之前的结果.于是我很快想到了之前一个比较典型的小程序,即求最长的连续字符串.这两个题目有 ...

  7. 764. Largest Plus Sign

    题目大意: 就是一个由1和0组成的正方形矩阵,求里面最大的加号的大小,这个大小就是长度. 什么鬼啊,本来想自己想的,结果看了半天没看懂具体什么意思,然后查了下题解,希望有人说一下意思,结果一上来就是思 ...

  8. 剑指offer题目解答合集(C++版)

    数组中重复的数字 二维数组中查找 字符串 替换空格 二叉树的编码和解码 从尾到头打印链表 重建二叉树 二叉树的下一个节点 2个栈实现队列 斐波那契数列 旋转数字 矩阵中的路径 机器人的运动范围 剪绳子 ...

  9. ApacheCN 深度学习译文集 20201229 更新

    新增了七个教程: TensorFlow 和 Keras 应用开发入门 零.前言 一.神经网络和深度学习简介 二.模型架构 三.模型评估和优化 四.产品化 TensorFlow 图像深度学习实用指南 零 ...

  10. 建模算法(四)——动态规划

    其实我们对着规划接触的最多最熟悉,简单来说就是一个递归问题,递归问题简单的在的地方,编程实现的难度下降了,难的地方是如何构造递归,不好的地方是资源的浪费,但是有些地方编程实现的简单的优势可以无视掉他的 ...

随机推荐

  1. PLC入门笔记10

    梯形图电路之顺序控制 顺序控制功能图 顺序控制功能图的梯形图表达 编程原则 实例分析 SMo0.1西门子首次扫描时为ON,常用作初始化脉冲 这是台达的 这是优控的..

  2. 01. JavaScript基础知识

    一.JavaScript简介   JavaScript 是一门解释型编程语言,解释型编程语言指代码不需要手动编译,而是通过解释器边解释边执行.所以,要运行 JS,我们需要在计算机中安装 JS 的解释器 ...

  3. Vivado工程常见报错及解决办法

    1. 在进行自定义 IP 后,将自定义 IP 添加到当前的工程时,出现如下报错: [IP_Flow 19-167] Failed to deliver one or more file(s). [IP ...

  4. 升级openssl版本

    一.安装步骤 1.下载openssl安装包 2.编译安装 3.备份旧版本openssl 4.添加软连接 5.添加OpenSSL动态链接库并使其生效 二.下载openssl安装包 [root@local ...

  5. Ansible 工具参数详解自动化运维

    一.Ansible基本概述: Ansible是一个配置管理系统(configuration management sysytem )你只需要可以使用ssh访问你的服务器或设备就行 Ansible 是近 ...

  6. FTP文件夹错误:【打开FTP服务器上的文件夹时发生错误。请检查是否有权限访问该文件夹】

    资源管理器访问FTP服务器报错,提示FTP文件夹错误:[打开FTP服务器上的文件夹时发生错误.请检查是否有权限访问该文件夹]. 详细信息: 200 Switching to ASCII mode. 2 ...

  7. IDEA设置自定义代码模板

    1. 进入IDEA界面,File–>Settings 注:其中, $END$代表打印字符串后光标所处的位置 如: System.out.println($END$); 表示输出后光标在()里面.

  8. vs2022和wsl2开发和调试c++代码(转载)

    看见一个不错的帖子(知乎) https://zhuanlan.zhihu.com/p/390757559 里面最主要就是要保证wsl里面安装的东西够了,第二就是vs2022已经安装了linux的相关模 ...

  9. 【MSSQL】AlwaysOn集群增加发布订阅

    在现有AlwaysOn集群增加发布订阅节点 配置 前提 节点1.节点2在AlwaysOn集群,节点3作为集群外节点使用订阅复制集群数据同步 发布对象必须要有主键 步骤 登录节点3配置分发distrib ...

  10. python之tk学习,闲鱼搜索-小记

    (如想转载,请联系博主或贴上本博地址) 编程,逻辑,总是让人如痴如醉. 下面进入正题. 火热的天气配上火热的python,python的入门友好性让门外汉们都看到了希望.当然自己写的程序如果没有GUI ...