动态规划

动态规划

动态规划(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. 最长公共子序列和最长公共子串
原文地址:https://www.cnblogs.com/elisha/p/14021817.html