• 动态规划


    动态规划

    动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。

    动态规划的特点

    • Dynamic programming solves problems by combining the solutions to sub-problems

    • A dynamic-programming algorithm solves every sub-problem just once and then saves its answer in a table, thereby avoiding the work of recomputing the answer every time the sub-problemis encountered.

    1. 最优子结构:通过子问题的最优解,推导问题最优解

    2. 无后效性:只关心前一个状态,不关心推导过程

    3. 重复子问题:不同的决策阶段有重复的状态


    经典问题:斐波那契数列,dijkstra最短路径,背包问题,项目规划等

    动态规划经典实例

    动态规划所解决的经典问题包括:斐波那契数列,dijkstra最短路径,背包问题,项目规划等,下面举几个的例子,以便更好理解其思想。

    斐波那契数列

    斐波那契数列的实现方法有多种,这里记录几个自己想到的方法。

    1. 【方法一】直接递归

      直接递归法,空间复杂度O(N),时间复杂度太高O(2^N),时间开销太大,一般的电脑只能算出前几十个数,不可行。

    2. 【方法二】使用历史计算结果的递归

      递归调用,但是存储已计算的结果以供使用。时间复杂度O(N),空间复杂度O(N)

      fi = {1:1, 2:1}         # 存储已计算的结果,{序号:值}
      
      def fib(n):
      
          if n in fi:         # 判断是否已经计算过
              value = fi[n]
          else:
              value = fib(n-1) + fib(n-2)
              fi[n] = value
          return value
      
      print(f(100))
      
    3. 【方法三】动态规划(使用一个辅助变量c)

      def fib(n):
          a, b = 1, 1
          c = 0
          for i in range(2, n):
              c = a + b
              a = b
              b = c
          return c
      
      print(fib(100))
      
    4. 【方法四】动态规划(不另开辟内存空间)

      从第3个数开始依次计算,不另开辟内存空间。时间复杂度O(N),空间复杂度O(1)

      num = 0                 #循环变量
      n = 100                 # 计算第几个Fibonanci数
      a = 1; b = 1            # 前两个数
      while num < n - 2:      # 减2是因为前两个数已知,只需循环n-2次
          if num % 2 == 0:    # 为了节省空间,只使用两个变量,奇偶交替更新
              a = a + b       # 更新a
              print(a)
          else:
              b = a + b       # 更新b
              print(b)
          num += 1
      

    连续子序列最大和问题(LeetCode 53)

    问题:给定一数组arr,寻找子数组使得它们的之和最大,比如(1,-1,-2,3,5,-1,4,-2)

    1. 【方法一】暴力解决

      找到所有可能的子序列,计算其和并记录下来,排序,返回最大值。

    2. 【方法二】动态规划

      用指针j记录序列的结尾处索引,则结尾元素表示为arr[j],用A[j]表示以j结尾的序列的最大和。

      递推公式:

      [A[j]=max{A[j-1]+arr[j], arr[j]} ]

      时间复杂度为O(N),空间复杂度O(1)。

    import numpy as np
    import sys
    
    def max_subseq_sum(arr):
        """
        寻找和最大的子序列,返回起止索引及最大和的值。
        最大和序列可能会有多个,这里仅考虑**最长**的子序列。
        :param arr:
        :return: 和最大的子序列的起止索引及最大和的值(all_idx_start,all_idx_end, max_all)
        """
    
        max_current = 0                      # 记录局部的最大和
        max_all = -sys.maxsize               # 记录全局最大和,初始化为最小的数
        cur_idx_start, cur_idx_end = 0, 0    # 局部最大和序列起止索引
        all_idx_start, all_idx_end = 0, 0    # 全局最大和序列起止索引
        for i in range(len(arr)):
            if max_current >= 0:             # 即max_current + arr[i] >= arr[i]
                cur_idx_end += 1             # 序列向后扩增一位
                max_current += arr[i]
                if max_current >= max_all:   # 【if分支A】
                    all_idx_start = cur_idx_start
                    all_idx_end = cur_idx_end
                    max_all = max_current
            else:
                cur_idx_start = i
                cur_idx_end = i
                max_current = arr[i]
    
            # 【if分支A也可以放在此处,但放在上面逻辑更严谨】
    
        return all_idx_start, all_idx_end, max_all
    
    
    arr1 = [-1, 1, 2, -3, 4, 5, 2, 4]
    result = max_subseq_sum(arr1)
    print(result)
    

    Coin Change Problem

    问题:假如有n种硬币,它们的价格分别是$ 1=v_1<v_2<v_3<v_n $。想把手里价值为C的纸币换成硬币,越少越好,如何设计方案?

    进阶:如果是要得到零钱方案呢,怎么得到?

    编辑距离

    Leetcode 72. 编辑距离

    给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
    
    你可以对一个单词进行如下三种操作:
    
    插入一个字符
    删除一个字符
    替换一个字符
    
    
    示例 1:
    
    输入:word1 = "horse", word2 = "ros"
    输出:3
    解释:
    horse -> rorse (将 'h' 替换为 'r')
    rorse -> rose (删除 'r')
    rose -> ros (删除 'e')
    
    示例 2:
    
    输入:word1 = "intention", word2 = "execution"
    输出:5
    解释:
    intention -> inention (删除 't')
    inention -> enention (将 'i' 替换为 'e')
    enention -> exention (将 'n' 替换为 'x')
    exention -> exection (将 'n' 替换为 'c')
    exection -> execution (插入 'u')
    
    
    提示:
    
    0 <= word1.length, word2.length <= 500
    word1 和 word2 由小写英文字母组成。
    

    进阶:如果需要得到编辑距离的操作步骤,怎么做?

    最长公共子序列

    1143. 最长公共子序列

    给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
    
    一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
    例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
    
    若这两个字符串没有公共子序列,则返回 0。
    
     
    
    示例 1:
    
    输入:text1 = "abcde", text2 = "ace" 
    输出:3  
    解释:最长公共子序列是 "ace",它的长度为 3。
    示例 2:
    
    输入:text1 = "abc", text2 = "abc"
    输出:3
    解释:最长公共子序列是 "abc",它的长度为 3。
    示例 3:
    
    输入:text1 = "abc", text2 = "def"
    输出:0
    解释:两个字符串没有公共子序列,返回 0。
     
    
    提示:
    
    1 <= text1.length <= 1000
    1 <= text2.length <= 1000
    输入的字符串只含有小写英文字符。
    

    最长公共子序列问题在机器翻译的评价中可以用到。

    参考资料

    1. Top 50 Dynamic Programming Practice Problems
    2. Leetcode 72. 编辑距离
    3. Leetcode 1143. 最长公共子序列
    4. 1143. 最长公共子序列和最长公共子串
  • 相关阅读:
    GitHub 和 Gitee 开源免费 10 个超赞后台管理面板,看完惊呆了!
    LeetCode234.回文链表
    LeetCode104.二叉树的最大深度
    LeetCode142.环形链表II(链表中环的入口节点)
    云原生动态周刊:你订阅 GitHub README 播客了吗?
    云原生爱好者周刊:Crossplane 成为 CNCF 孵化项目
    凌晨 12 点突发 istio 生产事故!一顿操作猛如虎解决了
    新东方在有状态服务 In K8s 的实践
    面向无人驾驶 “云端大脑” 可用性的云原生实践
    Qunar 云原生容器化落地实践
  • 原文地址:https://www.cnblogs.com/elisha/p/14021817.html
Copyright © 2020-2023  润新知