废话

一道力扣easy没做出来…我是fw, orz。看了题解觉得学到了很多,记录下来和大家分享一下。

题目

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

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

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

方法1

一开始我直接计算奇偶组的大小比较的,看到了[2,1,1,2]这个例子我意识到我是个哈皮。
先说一个很巧妙的方法,跟动态规划没关系,纯粹是利用了这题不能连续偷相邻的两个房间的设定想出的解法,思路非常棒!

解题人:

解法:
这个条件如果精简掉其他内容,很容易让人联想到奇偶数。这个解法就是从这点出发。

设置两个变量,sumOdd 和 sumEven 分别对数组的奇数和偶数元素求和。
最后比较这两个和谁更大,谁就是最优解。
至少在下面这个例子里,这么做是成功的了。

Index [0] [1] [2] [3]
nums 1 2 3 4
sumEven 1 1 4 4
sumOdd 0 2 2 6
接下来要解决的就是最优解不是纯奇数和或者偶数和的情况。
这种情况下,最优解可能前半段出现在这边,后半段出现在另一边。
那么只要找到一个时机,当这一段的最优解没有另一边好时,就复制对面的最优解过来。

举个例子:

Index [0] [1] [2] [3] [4]
nums 1 3 1 3 100
sumEven 1 1 2 2 => 3
sumOdd 0 3 3
当偶数和(奇偶指的数组下标)加到第二个 1 之后,发现还不如奇数和一个 3 大,就应该将对面的3复制过来替换掉自己的 2。

Index [0] [1] [2] [3] [4]
nums 1 3 1 3 100
sumEven 1 1 2 3 103
sumOdd 0 3 3 6 6
继续计算后得到最优解。

代码:

class Solution {public:int rob(vector<int>& nums) {int money1=0, money2=0;for(int i = 0; i < nums.size(); i++){money1 += nums[i];money1 = (money1 > money2) ? money1 : money2;i++;if(i < nums.size()){money2 += nums[i];money2 = (money2 > money1) ? money2 : money1;}}return max(money1, money2);}
};

废话:
就在我自暴自弃想着要不直接把组合全算出来算了的时候,这个思路真是妙的我呱呱叫,怎么避免有几个房间钱特别多导致间隔不固定的麻烦呢?先分两路计算,奇偶数组直接加,我们记为一组和二组。当其中一个加的房间数量比另一个多但是钱反而没另一个多的时候,比如一组间隔地偷了三个屋子,发现还没二组投的两个屋子钱多的时候,那么一组就直接改变之前投的屋子,调整为和二组相同,然后再继续间隔地偷,这样既保证了钱是最多地,又避免了触发警报。两个路线一边加一边相互比较,自然而然最后的结果就是正解了。

方法2

动态规划解法
新知识,直接上解说。

解题人:

解法:
动态规划的的四个解题步骤是:

定义子问题
写出子问题的递推关系
确定 DP 数组的计算顺序
空间优化(可选)
下面我们一步一步地进行讲解。

步骤一:定义子问题
稍微接触过一点动态规划的朋友都知道动态规划有一个“子问题”的定义。什么是子问题?子问题是和原问题相似,但规模较小的问题。例如这道小偷问题,原问题是“从全部房子中能偷到的最大金额”,将问题的规模缩小,子问题就是“从 kk 个房子中能偷到的最大金额”,用 f(k)f(k) 表示。

可以看到,子问题是参数化的,我们定义的子问题中有参数 kk。假设一共有 nn 个房子的话,就一共有 nn 个子问题。动态规划实际上就是通过求这一堆子问题的解,来求出原问题的解。这要求子问题需要具备两个性质:

原问题要能由子问题表示。例如这道小偷问题中,k=nk=n 时实际上就是原问题。否则,解了半天子问题还是解不出原问题,那子问题岂不是白解了。
一个子问题的解要能通过其他子问题的解求出。例如这道小偷问题中,f(k)f(k) 可以由 f(k-1)f(k−1) 和 f(k-2)f(k−2) 求出,具体原理后面会解释。这个性质就是教科书中所说的“最优子结构”。如果定义不出这样的子问题,那么这道题实际上没法用动态规划解。
小偷问题由于比较简单,定义子问题实际上是很直观的。一些比较难的动态规划题目可能需要一些定义子问题的技巧。

步骤二:写出子问题的递推关系
这一步是求解动态规划问题最关键的一步。然而,这一步也是最无法在代码中体现出来的一步。在做题的时候,最好把这一步的思路用注释的形式写下来。做动态规划题目不要求快,而要确保无误。否则,写代码五分钟,找 bug 半小时,岂不美哉?

我们来分析一下这道小偷问题的递推关系:

假设一共有 k 个房子,每个房子的金额分别是 H0 ~ Hk-1 ,子问题 f(k)表示从前 k 个房子中能偷到的最大金额。那么,偷 k 个房子有两种偷法:
k 个房子中最后一个房子是 k-1。
如果不偷这个房子,那么问题就变成在前 k-1 个房子中偷到最大的金额,也就是子问题 f(k-1)。
如果偷这个房子,那么前一个房子k−2 显然不能偷,其他房子不受影响。那么问题就变成在前k−2 个房子中偷到的最大的金额。两种情况中,选择金额较大的一种结果。
f(k) = max { f(k-1), f(k-2)+Hk-1 }
在写递推关系的时候,要注意写上 k=0和 k=1的基本情况:

当 k=0 时,没有房子,所以f(0)=0。
当 k=1 时,只有一个房子,偷这个房子即可,所以 f(1) = H0。
这样才能构成完整的递推关系,后面写代码也不容易在边界条件上出错。

步骤三:确定 DP 数组的计算顺序
在确定了子问题的递推关系之后,下一步就是依次计算出这些子问题了。在很多教程中都会写,动态规划有两种计算顺序,一种是自顶向下的、使用备忘录的递归方法,一种是自底向上的、使用 dp 数组的循环方法。不过在普通的动态规划题目中,99% 的情况我们都不需要用到备忘录方法,所以我们最好坚持用自底向上的 dp 数组。

DP 数组也可以叫”子问题数组”,因为 DP 数组中的每一个元素都对应一个子问题。如下图所示,dp[k] 对应子问题 f(k),即偷前 k 间房子的最大金额。

那么,只要搞清楚了子问题的计算顺序,就可以确定 DP 数组的计算顺序。对于小偷问题,我们分析子问题的依赖关系,发现每个 f(k) 依赖 f(k−1) 和 f(k-2)。也就是说,dp[k] 依赖 dp[k-1] 和 dp[k-2],如下图所示。

那么,既然 DP 数组中的依赖关系都是向右指的,DP 数组的计算顺序就是从左向右。这样我们可以保证,计算一个子问题的时候,它所依赖的那些子问题已经计算出来了。

代码:

class Solution {public:int rob(vector<int>& nums) {if(nums.size() == 0)return 0;vector<int> dp(nums.size()+1, 0);dp[0] = 0;dp[1] = nums[0];for(int i = 2; i <= nums.size(); i++)dp[i] = max(dp[i-1], dp[i-2] + nums[i-1]);return dp[nums.size()];}
};

废话:
学到了。感觉和数学里的数学归纳法很像啊,基本是同一个思想,理解起来应该没什么难度。也算是让我体验了一下学以致用的感觉,曾经学过的数学思想能在实际应用中有出色的发挥,这感觉很棒。让自己应试教育的学生生涯有了那么点色彩。


又遇到了一个可用动态规划解的题目,但我没想出来,我为什么这么蠢啊

题目

动态规划dp解法
正着想没法子,再逆着想试试

class Solution {public:int numSquares(int n) {vector<int> dp(n+1, INT_MAX);dp[0] = 0;for(int i = 1; i*i <= n; i++) dp[i*i] = 1;for(int i = 2; i <= n; i++){for(int j = 1; j*j <= i; j++){dp[i] =min(dp[i], dp[i-j*j] + 1);}}return dp[n];}
};

第一次接触动态规划解题相关推荐

  1. 第一次接触动态规划的心得和感悟

    动态规划是分治思想的延伸,通俗一点来说就是大事化小,小事化无的艺术. 在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接 使用这些结果. 动态规划具 ...

  2. 一文学会动态规划解题技巧

    前言 动态规划(dynamic programming,简称 dp)是工程中非常重要的解决问题的思想,从我们在工程中地图软件上应用的最短路径问题,再在生活中的在淘宝上如何凑单以便利用满减券来最大程度地 ...

  3. vue中常碰见的坑_Vue 与 Vuex 的第一次接触遇到的坑

    在 Vue.js 的项目中,如果项目结构简单, 父子组件之间的数据传递可以使用  props 或者 $emit 等方式 但是如果是大型项目,很多时候都需要在子组件之间传递数据,使用之前的方式就不太方便 ...

  4. 动态规划解题套路框架

    动态规划解题套路框架 另外!!!# define maxn 100005最好多5个 509.斐波那契数 322.零钱兑换 斐波那契数 #include <iostream> #includ ...

  5. 第一次接触终极事务处理——Hekaton

    在这篇文章里,我想给出如何与终极事务处理(Extreme Transaction Processing (XTP) )的第一次接触,即大家熟知的Hakaton.如果你想对XTP有个很好的概况认识,我推 ...

  6. 动态规划解题一般思路

    1.递归到动规的一般转化方法 递归函数有n个参数,就定义一个n为值的逆过程的数组,数组的下标是递归函数参数的取值范围,数组元素的值是递归函数的返回值,这样就可以从边界值开始,逐步填充数组,相当于计算递 ...

  7. Project Pacific的第一次接触(转)

    这是己亥年的最后一篇公众号更新,想谈谈自己与VMware Pacific产品的第一次接触,提供一些配置的参考,感兴趣的朋友们可以一起对照着在自己的环境中进行模拟. 首先我们来看几张演示用例: 这可能是 ...

  8. 百度地图API的第一次接触

    因为项目的需求,第一次接触了百度API. 第一步:引用百度地图API的脚本 如果在局域网环境中,要把地图文件和js文件都要下载下来 <script type="text/javascr ...

  9. 第一次接触APK【破解纪实】

    前言 昨天刚把公司任务弄得差不多.同事想学粤语,下了一款XX粤语手机APP,让我帮他弄下.之前也没接触过APK,这次就当学习好了. 先下个JDK装好  再把Android SDK NDK统统装好,配置 ...

最新文章

  1. Apriori算法、FP-Growth算法、顺序分析、PrefixSpan算法
  2. AQS分析(AbstractQueuedSynchronizer)(三)
  3. X509证书 指定了无效的提供程序类型 System.Security.Cryptography.CryptographicException 错误解决方法
  4. 嬴彻科技拿下SemanticKITTI榜单两项第一
  5. 使用python中的socket实现服务器和客户端,并完成图片的传输
  6. PHP5魔术函数与魔术常量
  7. 判断深度学习模型的稳定性_问题引领构建数学模型,讲练结合促进深度学习
  8. Linux Zero-copy零拷贝技术:源码示例
  9. TensorFlow——使用TensorFlowSharp创建C#应用程序
  10. 商品翻牌效果(纯css)
  11. 分布式文件系统HDFS原理篇
  12. 乐高EV3怎么运行Python?
  13. 华为首款血压手表WATCH D测评
  14. Google 按图搜索的原理
  15. 你想通过创业赚取人生中的第一桶金
  16. oracle dbms_utility.get_time,dbms_utility如何使用?
  17. Linux系统看门狗应用编程
  18. python正则爬取微信阅读总榜单写入csv
  19. CIO40:企业信息化为什么要进行顶层设计?
  20. 深入剖析Spring(一)——IoC的基本概念(从面向对象角度介绍)

热门文章

  1. python安装pyinotify模块
  2. 一切测试的基础——测试用例设计
  3. Hank的无线802.11学习笔记--part 3
  4. Plants vs. Zombies
  5. Java概述-Java技术体系标准:SE、EE、ME
  6. 电子计算机科学工程,计算机科学与技术学院
  7. 数据库系统概论笔记——第一章
  8. K-means实现图像聚类
  9. 强类型语言与弱类型语言/面向过程与面向对象
  10. python统计文件中每个单词出现的次数_Python统计单词出现的次数