动态规划

维基百科对动态规划(Dynamic programming,简称DP)的定义是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法

斐波那契数列

斐波那契数列是一个典型的可以把原问题分解为相对简单的子问题的方式求解复杂问题的例子。下面是斐波那契数列的数学定义:

  • F(0)=0,F(1)=1
  • F(2)=F(1)+F(0)=1+0=1
  • F(n)=F(n-1)+F(n-2) (n>=2) 根据这个数学定义,我们可以用递归的方式很轻松的实现这个算法。
package mainimport ("fmt""time"
)func fib(n int) int {if n <= 1 {return n}return fib(n-1) + fib(n-2)
}func main() {start := time.Now().Unix()fib(45)end := time.Now().Unix()fmt.Println(end-start)
}
复制代码

上面代码我们求的是F(45),代码非常的简单,但发现计算时间达到了6秒左右,效率十分低下,下面是根据刚刚的代码画出的一个F(5)的树状图:

从图中可以看出F(3)计算了2次,F(2)计算了3次,F(1)计算了4次,发生了很多重复计算,这也是造成效率低下的原因,要优化的思路就是去除掉这些不必要的重复计算。现在我们将每个子问题的计算结果存储起来,当再次碰到同一个子问题时,就可以直接从之前存储的结果中取值,就不用再次计算了。比如第一次碰到计算F(2)时,可以用一个字典把F(2)的计算结果存储起来,当再次碰到计算F(2)时就可以直接从字典中取值,改造后的代码如下:

package mainimport ("fmt""time"
)var m = map[int]int{0:0, 1:1}
func fib(n int) int {if v, ok :=m[n]; ok {return v}m[n-1],m[n-2] =fib(n-1),fib(n-2)return m[n-1]+m[n-2]
}func main() {start := time.Now().UnixNano()fib(45)end := time.Now().UnixNano()fmt.Println(end-start)
}
复制代码

经过改造后再计算F(45)不到1秒。一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表这也是动态规划的重要内容。

所以动态规划两个最主要的点是:

  • 将一个复杂的问题分解为若干多个子问题。
  • 将每个子问题的结果存储起来,使每个子问题只解决一次。

House Robber

下面是用动态规划的方法来解决 LeetCode 上一道名为 House Robber 的题目:

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

我们用 P(n) 表示为总共有 n 间房屋时能偷取到的最大金额,用 r(n) 表示为第 n 间房屋中存放的金额。 当 n 为1时 P(1)=r(1),n 为2时 P(2)=Max(r(1), r(2))。因为题目要求不能打劫相邻两间房,所以当有 n 间房时 P(n)=Max(P(n-2)+r(n), P(n-1))。用方程来表示就是:

P(1)=r(1)
P(2)=Max(r(1), r(2))
P(n)=Max(P(n-2)+r(n), P(n-1))
复制代码

所以这个问题就被分解成了若干个子问题,下面是其代码实现:

package mainimport "fmt"var m = map[int]int{}func rob(arr []int) int {l := len(arr)if l <= 0 {return 0}if v,ok:=m[l-1];ok{return v}if l == 1 {m[0]=arr[0]return arr[0]}if l == 2 {if arr[0] >= arr[1] {m[1]=arr[0]return arr[0]} else {m[1]=arr[1]return arr[1]}}a, b:= rob(arr[:l-2])+arr[l-1],rob(arr[:l-1])if a>=b{m[l-1]=a} else {m[l-1]=b}return m[l-1]
}func main() {arr := []int{2,7,9,3,1}m[0]=arr[0]ret :=rob(arr)fmt.Println(ret)
}
复制代码

上面的代码就是我们根据方程无脑写出的算法就已经达到了偷窃最大金额的目的,但其实还是有一些优化空间的,我们要计算 P(n) 其实只需要记住之前的 P(n-2) 和 P(n-1)就够了,但我们其实将 P(1)、P(2)、...、P(n-2) 都记住了,带来了一些内存浪费,之所以会有这个问题是因为我们求解 P(n) 时会依次求解 P(n-1)、P(n-2)、...、P(1) 是一种自顶向下的求解方式,如果换成自底向上的求解方式可以写出如下代码:

package mainimport "fmt"func rob(arr []int) int {pre1, pre2 := 0, 0for _,v := range arr {if pre2+v >= pre1 {pre1,pre2 = pre2+v,pre1} else {pre1,pre2= pre1,pre1}}return pre1
}func main() {arr := []int{2,7,9,3,1}ret :=rob(arr)fmt.Println(ret)
}
复制代码

上面的变量 pre1 和 pre2 分别表示 P(n-1) 和 P(n-2),这样通过自底向上的方式求出了结果,比自顶向下的方式更节省内存。

所以动态规划需要记住的几个关键点是将复杂问题拆分成若干个子问题记住子问题的结果自顶向下自底向上

摩尔投票法

假如有10个人参与投票,有的人投给A,有的人投给B,有的人投给C,当我们想要找出A、B、C谁得票最多时,我们可以将两个不同的投票作为一对进行删除,直到不能再删时然后再查看结果中还剩下的投票就是得票最多的那个。比如上述10个人的投票情况是[A,B,C,C,B,A,A,A,B,A],下面是进行删除的过程:

[A,B,C,C,B,A,A,A,B,A]==>[C,C,B,A,A,A,B,A] //A,B为不同的投票所以可以//作为一对进行删除
[C,C,B,A,A,A,B,A]==>[C,A,A,A,B,A] //C,C为相同的投票所以不删除,然后// 再依次向后查找发现C,B不同可以删除
[C,A,A,A,B,A]==>[A,A,B,A]
[A,A,B,A]==>[A,A]
复制代码

通过不断的对不同的投票作为一对进行删除,投票结果中最后只剩下了[A,A],所以A就是得票最多的。摩尔投票法的核心就是将序列中两个不同的元素进行抵消或删除,序列最后剩下一个元素或多个相同的元素,那么这个元素就是出现次数最多的元素

Majority Element

求众数就是摩尔投票法的一个典型运用场景,比如有下面这道算法题:

给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 n/2 的元素。给定数组[2,2,1,1,1,2,2,4,5,2,3,2,2] 找出其众数。

实现代码如下:

package mainimport "fmt"func main() {arr := []int{2,2,1,1,1,2,2,4,5,2,3,2,2}maj, count := arr[0], 1for i:=1;i<len(arr);i++ {if maj == arr[i] {count++} else {if count == 0 {maj,count = arr[i],1continue}count--}}fmt.Println(maj)
}
复制代码

代码中先假定数组的第一个元素就是众数,并用一个变量 count 来记录这个众数出现的次数,当被迭代到的数与这个众数相同时 count 就加1,不同时就做抵消操作,即 count 减1,当 count 为0时,就将被迭代到的数设为新的众数并将 count 置1。

以上就是摩尔投票法的原理和应用。

转载于:https://juejin.im/post/5caffda35188251b0b7a6f1a

动态规划和摩尔投票法相关推荐

  1. 摩尔投票法(力扣- -229. 求众数 II)

    摩尔投票法(力扣- -229. 求众数 II) 文章目录 摩尔投票法(力扣- -229. 求众数 II) 一.题目描述 二.分析 摩尔投票法 总结 三.代码 一.题目描述 二.分析 这道题如果用O(N ...

  2. [剑指offer][JAVA]面试题第[39]题[数组中出现次数超过一半的数字][HashMap][摩尔投票法]

    [问题描述][简单] 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.你可以假设数组是非空的,并且给定的数组总是存在多数元素.示例 1:输入: [1, 2, 3, 2, 2, 2, 5, ...

  3. 【LeetCode笔记】169. 多数元素(Java、摩尔投票法、哈希表)

    文章目录 题目描述 思路 & 代码 思路一:哈希表 思路二: 摩尔投票法 题目描述 好家伙,这是今天最有意思的题目了 思路 & 代码 思路一:哈希表 先说缺点:空间复杂度O(n) 一次 ...

  4. 2020年 Moore majority vote algorithm 摩尔投票法知多少

    第一眼看到这个题目,想到的是使用Map来统计出现频次,然后遍历找出频次大于n/2的元素. class Solution {public int majorityElement(int[] nums) ...

  5. 摩尔投票法(Boyer–Moore majority vote algorithm)

    参考资料 论文MJRTY A Fast Majority Vote Algorithm 算法演示网站 维基百科 算法解读 概述 摩尔投票法(Boyer–Moore majority vote algo ...

  6. C语言刷题之摩尔投票法

    目录 1.引入 2.摩尔投票算法 3.基本步骤 摩尔投票法分为两个阶段: 1.抵消阶段 2.检验阶段 4.代码实现 5.扩展沿伸 6.总结 1.引入 我们来看一个问题: 假设有一个无序数组长度为n,要 ...

  7. 小小算法,可笑可笑——摩尔投票法(集万家之长)

    摩尔投票法 不多说,先上题目. 问题描述:leetcode 229题 给定一个大小为 n 的整数数组,找出其中的所有的出现超过 ⌊ n/3 ⌋ 次的元素. 这还不简单!直接暴力计数,上map,easy ...

  8. 剑指offer面试题39. 数组中出现次数超过一半的数字(数组)(摩尔投票法)

    题目描述 **数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字. 你可以假设数组是非空的,并且给定的数组总是存在多数元素.** 思路 详见链接 代码 class Solution:def ...

  9. 摩尔投票法——手撕“绝大多数“问题的算法金手指

    目录 传统艺能

最新文章

  1. xhr请求python_python爬取boss直聘职位数据,并保存到本地
  2. 【超简单】MySQL存储引擎的选择与配置
  3. xshell安装mysql步骤_数据库Mysql与禅道安装
  4. 天气正好,hello world!
  5. 如何停止一个正在运行的java线程
  6. 最好用的虚拟机软件----VMware详细图文教程
  7. [导入].net中设置系统时间
  8. WinAPI: Rectangle - 绘制矩形
  9. Unity Hub和Unity安装教程
  10. eterm php,eterm配置出租 eterm3 eterm黑屏
  11. Win10 VS2019+QT/OpenCV/灰点相机/函数信号发生器 配置及其使用
  12. 成功在神舟K650c-i7 d2(i7-4700MQ、HM87)上装好了Windows XP
  13. qcloud windows rtx cpu 100%定位
  14. “Win10 无法使用内置管理员账户打开(应用程序)“怎么办
  15. Java求解一元二次方程详解
  16. 【玩转CSS】盒子模型
  17. 主要是web开发和android开发 最好是选择一个方向先专一去学, 半瓶子醋什么都想学你会发现到最后什么都不会,或者比别人多花出了太多的精力 我曾经学java的时候好高骛远,因为当时老师是一个c+
  18. gitlab 安装以及卸载
  19. Linux 往事:一个不会像 GNU 那样大而专业的 OS 是如何成为主流的?
  20. 我的财富自由之路 - 财富的自我认知

热门文章

  1. 初学Java Web(4)——Servlet学习总结
  2. 浅析Spring事务传播行为和隔离级别
  3. IEEE CSO 2009 修订版论文要求
  4. 深度学习(十九)基于空间金字塔池化的卷积神经网络物体检测-ECCV 2014
  5. 深度学习(十二)稀疏自编码
  6. 正则表达式Regular Expression
  7. 鸟哥的Linux私房菜(基础篇)- 第十一章、认识与学习 BASH
  8. cout的输出格式初探3
  9. 向git库提交代码出现”There are no staged files怎么办?
  10. MariaDB Spider:实现MySQL横纵向扩展的小能手