数据结构-动态规划【1】- 基本的思路和实例分析

动态规划(dynamic programming) 是通过组合子问题的解来求解原问题,动态规划应用于子问题重叠的情况,即不同的子问题具有公共的的子子问题。动态规划在这个过程中对每一个子问题只求解一次,将其保存在一个表格中,从而无需每次求解一个子问题时都重新计算,避免不必要的计算工作。

一、基本概念

用途:动态规划通常用来求解最优化问题(多阶段决策问题)。这类问题可以有很多可行解,每一个解都有一个值,我们希望寻找具有最优解(最大值或最小值)的解,我们称这样的解为问题的一个最优解,而不是最优解,因为可能有多个解都达到最优值。
特点:

  • 重复子问题:因为存在大量重复子问题,才需要记录之前计算的结果
  • 最有子结构:不同规模问题之间的关系
  • 无后效性:只记录阶段结构,而不关心这个结果怎么来的

思路:

  • 思路一:【自顶向下】:递归+记忆:少数问题这样做
  • 思路二:【自底向上】:递推求解:绝大多问题都可以这样做,需要多思考这样方式,多用

二、状态转移方程

关键点在于【分类讨论】:状态转移很多时候就是在做【分类讨论】,把当前问题分成几个小问题,这些小问题的最优解构成当前问题的最优解,尝试思考大问题如何分类(分成多个小问题)

三、初始化

初始化过程同样重要,要重视.。
方法如下:

  • 直接从语义出发定义初始化
  • 从状态转移方程的下表出发思考初始化状态,注意数组下边不能越界,或者思考是否可以通过给状态数组(矩阵)多加一行(一列)从而避免复杂的初始化讨论

通常按如下4个步骤来设计一个动态规划算法:

  • 刻画一个最优解的结构特征
  • 递归地定义最优解的值
  • 计算最优解的值,通常采用自底向上的方法
  • 利用计算出的信息构造一个最优解

下面我们通过一个实例来说说:

  1. 最长回文子串
    给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

当看到这个题目时,有什么想法呢?自己先思考思考!
首先我们来看下题目:

  • 回文串:什么是回文串?回文串就是不管你是从左往右还是从右往左读,它的结果都是一样的,这就是回文串。
  • 子串:那什么又是字串呢?就是这个串必须是连续的。
  • 所以我们呢就可以理解回文子串了:”a“是一个回文子串,”bb“也是,”aabbaa“也是。

接下来我们用动态规划的问题来思考这个问题:
首先:(记S[i:j]为包括索引位置i和j在内的,i,j之间的字符串)
由上面的内容我们知道了什么是回文子串,我们继续思考,假如我们要去判断子串S[i:j]是不是子串,我们先判断S[i]是不是等于S[j],如果不等于,那么S[i:j]就肯定不是回文子串,如果是则继续判断S[i+1:j - 1]是不是回文子串。注意是不是把问题的规模缩小了,将原问题转为问小一点的子问题。如下图所示:
在这里插入图片描述
接着,我们用dp[i][j]来记录S[i:j]是不是回文子串,
由上面的内容我们便可以得出递推公式:
dp[i][j] = False if s[i] not equl s[j] else dp[i + 1][j - 1]
有了上面的公式我们来处理下边界问题:
由于数组长度如果小于2则为真,即 j - 1 - (i + 1) + 1 < 2 ==> j - i < 3
首先给定i,j,如果此时j - i = 2,此时这个字串由三个长度,先是去判断s[i]是不是等于s[j],如果不是就为False,如果是,那就一定是回文子串,你想一想是不是(自己在草稿纸上画图理解);如果j - i = 1,此时长度为二,先是去判断s[i]是不是等于s[j],如果不是就为False,如果是,那就一定也是回文子串。

好了边界条件处理好了,我们再来看看这个dp矩阵。
这个dp矩阵就是存储子问题的解,但是这里要注意自底向上的思路。最关键的就是那个递归公式:
dp[i][j] = dp[i + 1][j - 1],则就引导我们要先去计算dp[i][j]的左下角的值,因此要从左往右一列一列的计算。

在这里插入图片描述

下面给出Python代码:

class Solution:
    def longestPalindrome(self, s: str) -> str:
        # 计算字符串s的长度
        len_s = len(s)
        # 特殊处理
        if len_s < 2:
            return s
        
        # 标记回文子串的起点索引和该回文子串的长度
        start, maxLen = 0, 1

        # 初始化dp,对角线的的值都为True,长度为1的子串都是回文子串
        dp = [[False] * len_s for _ in range(len_s)]
        for i in range(len_s):
            dp[i][i] = True
        
        # 动态求解dp矩阵
        for j in range(1, len_s):
            for i in range(j):
                if s[i] != s[j]:
                    dp[i][j] = False
                else:
                    # 边界的处理
                    if j - i < 3:
                        dp[i][j] = True
                    else:
                        dp[i][j] = dp[i + 1][j - 1]

                if j - i + 1 > maxLen and dp[i][j] :
                    maxLen = j - i + 1
                    start = i
         
        return s[start: start + maxLen]

参考资料:
【1】算法导论-第三版
【2】weiwei1419
【3】最长回文子串

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值