算法---动态规划(编辑距离、不同子序列、动态规划总结)

目录

动态规划问题分析

动态规划问题总结

1、动归解决的问题

2、动归的特点

3、动归的一般过程


动态规划问题分析

第11题:编辑距离

  • 问题:将word1转换为word2的最少操作次数。
  • 问题划分:将整体划分为若干个子问题,每一个子问题都是一步可达的。即将word1的前i个字符转换为word2的前j个字符的最少操作次数。
  • 状态定义:将word1的前i个字符转化为word2的前j个字符的最少操作次数F(i , j)
  • 状态之间的转换方程:

                   f(i,j) = min(f(i-1,j-1),f(i-1,j),f(i,j-1))+1 (i != 0 && j != 0 && word1[i-1] != word2[j-1])

                    f(i,j) = min(f(i-1,j-1),f(i-1,j),f(i,j-1)) (i != 0 && j != 0 && word1[i-1] == word2[j-1])

                   f(i,j) = f(i,j-1)+1 (i == 0)

                   f(i,j) = f(i-1,j)+1 (j == 0)

     对于每一个状态F(i,j)的最少操作次数应该是对该字符进行替换、删除、插入三种操作中,步骤最少的一个。

     例:word1 = "ab"  word2 = "bc"

  1. 将word1的前1个字符转换成word2的前1个字符
  2. 将word1的前1个字符转换成word2的前2个字符
  3. 将word1的前2个字符转换成word2的前1个字符
  4. 将word1的前2个字符转换成word2的前2个字符(最终结果)

  • 初始状态:f(0,0) = 0  f(0,1) = 1  f(1,0) = 1
  • 返回结果:f(m,n) m为word1的长度,n为word2的长度
class Solution {
public:
    /**
     * 
     * @param word1 string字符串 
     * @param word2 string字符串 
     * @return int整型
     */
    int minDistance(string word1, string word2) {
        if(word1 == word2)
            return 0;
        if(word1.empty())
            return word2.size();
        if(word2.empty())
            return word1.size();
        int length1 = word1.size();
        int length2 = word2.size();
        //保存状态
        vector<vector<int>> status(1+length1,vector<int>(1+length2,0));
        //初始状态
        status[0][1] = 1;
        status[1][0] = 1;
        //状态转换
        //第一行
        for(int i = 2;i <= length2;i++)
        {
            status[0][i] = status[0][i-1]+1;
        }
        //第一列
        for(int i = 2;i <= length1;i++)
        {
            status[i][0] = status[i-1][0]+1;
        }
        for(int i = 1;i <= length1;i++)
        {
            for(int j = 1;j <= length2;j++)
            {
                if(word1[i-1] != word2[j-1])
                    status[i][j] = min(status[i-1][j-1],min(status[i-1][j],status[i][j-1]))+1;
                else
                    status[i][j] = status[i-1][j-1];
            }
        }
        return status[length1][length2];
    }
};

第12题:不同子序列

  • 状态定义:S的前i个字符中和T的前j个字符相等的子序列个数f(i,j)
  • 状态之间的转换:f(i-1,j-1)+f(i-1,j)  (S[i-1] == T[j-1])

                                    f(i-1,j)    (S[i-1] != T[j-1])

  1. S的第i个字符和T的第j个字符相等(选择是否使用第i个字符)

         使用:f(i-1,j-1)

         不适用:f(i-1,j)

  1. S的第i个字符和T的第j个字符不相等

          问题就退化为f(i-1,j)

  • 初始状态:i == 0时,f(i,j) = f(0,j) = 0;j == 0时,f(i,j) = f(i,0) = 1  f(0,0) = 1
  • 返回结果:f(m,n) m为S的长度,n为T的长度
class Solution {
public:
    /**
     * 
     * @param S string字符串 
     * @param T string字符串 
     * @return int整型
     */
    int numDistinct(string S, string T) {
        int length1 = S.size();
        int length2 = T.size();
        //保存状态
        vector<vector<int>> status(length1+1,vector<int>(1+length2,0));
        //初始状态
        for(int i = 0;i <= length1;i++)
            status[i][0] = 1;
        //状态之间的转换
        for(int i = 1;i <= length1;i++)
        {
            for(int j = 1;j <= length2;j++)
            {
                if(S[i-1] == T[j-1])
                    status[i][j] = status[i-1][j-1]+status[i-1][j];
                else
                    status[i][j] = status[i-1][j];
            }
        }
        return status[length1][length2];
    }
};

动态规划问题总结

1、动归解决的问题

  • 字符串:字符串分割、回文串分割、编辑距离、不同子序列等。

       字符串问题中,字符串可能是一个也可能是两个也可能是多个。一般情况下,只有一个字符串时状态定义一般为f(i),有两个字符串时状态定义为f(i,j)让两个字符串都进行动态的变化。

        字符串问题一般解决的是“是否可行”和“方案个数”问题。如果是两个字符串,首先需要判断是否满足,其次还需要判断有多少种满足方案。

  • 数组、最大值最小值:最大连续子数组和、背包问题等
  • 方法个数:背包问题、路径问题、三角矩阵等

2、动归的特点

  • 动归问题一定是可以分解的

       动归的思路就是将大问题分解成小问题,逐步求解的过程。在解决较大问题时,可能需要用到前边小问题的解,因此需要对小问题的解进行保存。因此,动态规划问题一定是可以分解成若干个相同的子问题的。

  • 动归问题分解后的子问题都是一步可达的,即通过一步操作就可以解决

        动态规划问题中,每一个子问题的解都是可以通过一步操作求解的。例如,在斐波纳妾数列问题中有任意一个子问题f(i) = f(i-1)+f(i-2)一步操作求得。这样,每一个子问题都可以使用相同的状态转换方程来表示,通过保存子问题的解进而求得大问题的解。

  • 动态规划问题中,对于子问题的解需要进行保存

       动态规划问题中,最重要的部分就是状态转换方程,每一个状态都需要使用到前边的一个或多个状态(子问题)的解,从而减少重复冗余的计算。因此,动态规划问题需要对前边可能用到的子问题的解进行保存,在解决更大问题中直接使用。

  • 动态规划本质是以空间换时间的做法

       在菲波那切数列中,我们可以使用递归求解:

int fib(int n)
{
    if(n == 0)
        return 0;
    if(n == 1)
        return 1;
    return fib(n-1)+fib(n-2);
}

        使用递归求解菲波那切数列的过程中,除了递归过程的栈操作占用较大空间外并没有使用额外的空间,但是递归的过成每次都要对前两次的过程重新进行计算,从而造成大量重复计算。如果使用动态规划,可以开辟额外的空间对子问题进行保存,从而减少额外的重复计算。

3、动归的一般过程

  • 状态定义:将大问题分解成的子问题就是需要定义的状态,以变量的形式将状态进行表示。状态定义是解决动态规划问题的关键,只有状态定义正确合理才能更好的推导出状态之间的转换方程。

                         一般状态定义包括两种,f(i)和f(i,j)。如果,只有一个需要动态变化的状态就使用f(i)例如:菲波那切数列、青蛙跳台阶,如果有两个需要动态变化的状态就要使用f(i,j)例如:编辑距离等。

                        一般情况下,在有两个字符串的问题和求方案个数的问题中使用f(i,j),当出现两种状态时问题会变得复杂。

  • 状态之间的转换:由子问题推导大问题的过程。也是利用保存的子问题的过程。状态之间的转换时动态规划问题中最难的部分,在推导转化方程时可以借助某个例题画图分析,总结转化方程。妆花方程一定要考虑到一些边界问题和特殊情况。
  • 初始状态:不需要借助转化方程就可以得到的或者是题目给定的值。例如:斐波那契数列的初始状态为:f(0) = 0,f(1) = 1
  • 返回结果:最终求得的大问题的解。

注意:动态规划求解时,为了方便理解和处理可以将所有子问题的解进行保存,然后在筛选有用的解,对程序的空间复杂度进行优化。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

疯狂嘚程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值