• 动态规划 思路思考


    动态规划

    动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法

    动态规划在算法中,算的上是最考验思路的一种类型的题,也是我认为最能称之为算法的题型,要求得到状态转移方程,然后以状态转移方程去倒推具体的解.从而达到某一特定解恰好能满足题目.

    状态转移方程

    这里,引出「状态转移方程」这个名词,实际上就是描述问题结构的数学形式:

    可见列出「状态转移方程」的重要性,它是解决问题的核心。

    很容易发现,其实状态转移方程直接代表着暴力解法。

    千万不要看不起暴力破解,动态规划问题最困难的就是写出状态转移方程,

    经典题型思路

    • 01背包问题

      动态规划问题背包9问,学习还是要看大佬视频 AcWing 2. 01背包问题 - AcWing

      有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

      /**
       该问题属于01背包问题
       
       首先看2维怎么推到通用解
       
       如果最大价值是存在前i个物品的总体积j里面中的一个
       
       那么第i件物品有没有加入必要,有下面2个判断
       
       1. 如果不用的物品
       	f[i][j] = f[i -1][j]
       2. 如果需要i个物品,则剩下的最多能装 j -v[i]个体积,此时最大值是: 
       	f[i][j] = f[i-1][j - v[i]]+w[i]
      
      然后默认f[i][0]都是从0开始递增
       	
      最终答案在这2种情况中的最大值
       */
      func package(v,w []int){
          f := make([][]int,len(v))
          
          for _,val := range f{
              val := make([]int,n+1)
          }
          
          for i := 1; i <= len(v); i++ {
              for j := 0; j <= len(w); j++ {
                  f[i][j] =f[i -1][j]
                  if j >= v[i]{
                      f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i])
                  }
              }
          }
          return f[len(v) -1][len(w) -1]
      }
      
      func max(a,b int)int{
          if a > b {
              return a
          }else{
              return b
          }
      }
      

      滚动数组

      滚动数组是DP中的一种编程思想。

      简单的理解就是让数组滚动起来,每次都使用固定的几个存储空间,来达到压缩,节省存储空间的作用。

      因为 DP 题目是一个自底向上的扩展过程,我们常常需要用到的是连续的解,前面的解往往可以舍去,所以用滚动数组优化是很有效的。

      假设一个状态方程为 dp[i][j] = dp[i-1][j-1] + 1;
      1. 从前往后去设置值
        dp[i-1][j-1]的值的结果就是我们这次循环得到的值
      2. 从后往前去设置值
        dp[i-1][j-1]的值的结果就是我们上一次循环得到的值
      
      根据滚动数组的思想,我们在状态转移的过程中, 是需要上一轮得到的结果来得到我们现在这一轮的值的结果,这样在每次循环的时候, 上一轮的结果不会被覆盖重新计算
       
      
    • 01背包问题一维优化

      /**
        其实通过上面的推导的公式, 不难得到,它的f[i] 只与f[i-1]相关联,
        f[i]就表示在体积在i的情况下,它的最大价值是多少. 
        f[i] 在初始化时为0
        
        总体积k < m 时,f[k] 为 价值最大值
        
        有以下推导公式
        
        f[m - k] = f[m - k +v[0]] + w[0] 
        	
        
       */
      func package(v,w []int){
          f := make([]int,len(v))
          
          for i := 1; i <= len(v); i++ {
              for j := len(w) -1 ; j >= v(i); j-- {
                  if j >= v[i]{
                      f[j] = max(f[j],f[j-v[i]]+w[i])
                  }
              }
          }
          return f[len(w) -1]
      }
      
      
    • 最后一块石头的重量

      有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

      每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

      如果 x == y,那么两块石头都会被完全粉碎; 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。 最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

      /**
      
        这个问题就是就是求2堆之差最小问题,那么按照上面的题目思路,其状态转移方程为
      		 f[i] = f[i-stones[0]] + stone[0]
        然后需要自底而上的倒推,以免重复计算
        
       */
      func lastStoneWeightII(stones []int) int {
          //无脑动态规划 一转背包问题
          
          if len(stones) == 0{
              return 0
          }
          sum :=0
          for _,val:= range stones{
              sum +=val
          }
      
          dp := make([]int,sum / 2 + 1)
          
          for i:= 0;i< len(stones);i++{
              for j:= sum / 2 ;j >= stones[i];j--{
                  dp[j]  = max(dp[j],dp[j - stones[i]]+stones[i]) 
              }
          }
          return sum -dp[sum/2]*2
          
      }
      func max(a,b int)int{
          if a > b {
              return a
          }else
          {
              return b
          }
      }
      
    • 一和零

      给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

      请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。

      如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

      这道题就属于完全背包的题型,一样的解题模板

      func findMaxForm(strs []string, m int, n int) int {
          if len(strs) == 0{
              return 0
          }
      
          dp := make([][]int,m+1)
      
          for z:=0;z<m+1;z++{
              dp[z] = make([]int,n+1)
          }
      
          for _,val := range strs{
              one := 0
              zero := 0
              for _,val := range val{
                  if '0' == val{
                      zero ++
                  }else{
                      one ++
                  }
              }
      
              for i :=m;i >= zero;i--{
                  for j:= n;j>=one;j--{
                      dp[i][j] = Max(dp[i][j],1+ dp[i-zero][j-one])
                  }
              } 
          }
      
          return dp[m][n]
      
      }
      
      func Max(a ,b int) int{
              if a > b{
                  return a
              }
              return b
          }
      
  • 相关阅读:
    C#(二)变量
    WinForm(一)基础
    面向对象(二) 继承和多态
    面向对象 (三)抽象类和接口
    面向对象 基础
    面向对象(一)封装 命名空间 访问修饰符等
    C#跳转语句 迭代法 穷举法
    C# 异常处理语句
    C# while循环
    Request和Response
  • 原文地址:https://www.cnblogs.com/wzqshb/p/14878246.html
Copyright © 2020-2023  润新知