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入门笔记3

    熟悉开发环境 工具下载 官网失效 软件安装 官网失效 第一次PLC之旅 走廊灯两地控制案例 PLC型号确定 梯形图(LAD)和指令表(STL)两种编程方式 程序编辑 符号 变量类型 数据类型 注释 编 ...

  2. 阿里云Linux服务器安装Maven实战教程

    下载地址 https://maven.apache.org/download.cgi 文件上传 把下载的文件上传到阿里云服务器 /usr/local/software 的目录(使用工具) window ...

  3. maven远程debug

    1.修改tomcat服务器配置 打开tomcat/bin/catalina.sh 添加参数 CATALINA_OPTS="-Xdebug -Xrunjdwp:transport=dt_soc ...

  4. openvas在centos中扫描单项的python实现

    使用gvm_cli命令来实现 先创建一个空的配置 copy_id = '085569ce-73ed-11df-83c3-002264764cea' new_config = ''' <creat ...

  5. jsp第9个作业

    regist.jsp <%@ page language="java" import="java.util.*" pageEncoding="U ...

  6. json for python学习笔记

    1.json作用 存储数据与数据传输 2.python中的json可以在代码中用字符串表示,字符串内部类似于字典 如: json1 = '{"name":"Bob&quo ...

  7. ES2015常用知识点

    ES2015(又称ES6)部分1 let/const以及块作用域:2 循环语句 const arr=[1,2,3]; for(const item of arr){ console.log(item) ...

  8. mmdetection可视化工具-DetVisGUI

    保存数据 执行程序,需要保存输出结果的pkl文件或者json文件 下面以测试faster_rcnn示例: 在执行测试时可以使用下面这条命令,就会将结果保存到一个pkl文件中. python tools ...

  9. 任意的形如 z = F(x,y)的曲面生成与显示---基于OpenGL Core Profile

    运行结果:   (圆锥面) (抛物面) (马鞍面) 其中的做法是:从顶部看上去就是一个平面网格.每个点的 z.x的位置都是程序细分出来的(指定起始.结束.步长).比较固定.但高度 y 的计算使用 用户 ...

  10. 10.10 2020 实验 6:OpenDaylight 实验——OpenDaylight 及 Postman 实现流表下发

    一.实验目的 熟悉 Postman 的使用:熟悉如何使用 OpenDaylight 通过 Postman 下发流表.   二.实验任务 推荐阅读:SDNLAB 文章:OpenFlow 协议超时机制简介 ...