算法-动态规划-《算法导论3rd-P215》

1 动态规划简介

        动态规划主要用来解决最优化的问题,所以看到求解“最**”的问题时可以先往动态规划考虑下是否可行。

        使用动态规划解决最优化的问题的两个要素:

        第一:具有最优子结构,即问题的最优解由相关子问题的最优解组合而成,且这些个子问题可以独立求解。所谓的独立求解就是子问题之间是相互独立的,反例见《算法导论3rd-p218》。

        第二:子问题重叠,即可用备忘录优化穷举过程。

1.1 最优子结构

        如果一个问题的最优解包含其子问题的最优解,就称此问题具有最优子结构。可用子问题的最优解来构造原问题的最优解。   

1.2 重叠子问题

        子问题的空间应该是足够的小,即问题的递归算法会反复求解“相同”的子问题,而不是一直生成“新”的子问题。

        如果递归算法反复求解相同的子问题,就称最优化的问题具有重叠子问题的性质。

        针对重叠子问题就可以使用备忘录进行剪枝操作,防止对相同子问题的重复求解。

2 动态规划的两种实现方法

        动态规划主要有两种实现方式:

2.1 从上到下的实现方式

        此种方式是将大问题逐步的转化为小问题,并且在这个转化过程中会出现一些对小问题的重复求解,这个时候就是用备忘录进行剪枝操作,从而缩短执行时间。

        此种方式的实现就是靠“递归”来完成的,需要仔细考虑递归的出口。

2.2 从下到上的实现方式

        此种方式是先求解小问题,然后由已知的小问题的解来构建大问题的解,也就是说在求解大问题的解之前,小问题的解已经被提前计算了出来。此种实现方式也就不会出现子问题的重复求解的过程,所以这个动态规划的实现方式,可以将备忘录优化掉,更换成几个变量来保存上一步的结果即可。

        此种实现方式还有些不同的地方就是需要找到一个base case,其实也就是最小的那个子问题的解。

2.3 两种方法的一些对比

2.3.1 备忘录        

        两种方法都需要定义备忘录(从下到上可以优化掉备忘录,但是理解这个优化过程还需要引入备忘录),所以就一个非常重要的点就是“如何明确这个备忘录所表示的含义”。

        例如:LeetCode-1143-字符串-子序列-最长公共子序列_hclbeloved的博客-CSDN博客

        中dp[i][j]的定义。

2.3.2 实现方式

        从上到下使用的是递归,从下到上使用的是迭代。

        在做题的过程你会发现,如果使用的是从上到下的动态规划求解时,你也可以使用dfs进行解决,dfs本质上其实就是回溯,但是它的效率还不如动态规划高。

        注意:从上到下的动态规划使用的是递归,而dfs常常使用的也是递归。

        这里给出一个例子:字符串的交织 力扣

        方法一:使用dfs,具体代码如下:

    bool isInterleave(string s1, string s2, string s3) {
        // 方法一:递归,对字符串“从前到后”进行扫描,超出时间限制
        // 为了缩短时间,可将方法一中的“对字符串从前向后扫描的递归”改为“对字符串从后向前扫描的递归”,
        // 并配合备忘录的剪枝操作,形成从上到下的动态规划,具体参考方法二
        return dfs(s1,s2,s3,0,0,0);
    }

    bool dfs(const string& s1, const string& s2, const string& s3, int index1, int index2, int index3)
    {
        if ((s1.length()-index1)+(s2.length()-index2) != (s3.length()-index3))
            return false;

        if  (index1 == s1.length())
            return s2.substr(index2) == s3.substr(index3);
        else if (index2 == s2.length())
            return s1.substr(index1) == s3.substr(index3);

        if (s1[index1] != s3[index3] && s2[index2] != s3[index3])            
            return false;

        if (s1[index1] == s3[index3])
        {
            if (s2[index2] != s3[index3])
            {
                return dfs(s1,s2,s3,index1+1,index2,index3+1);
            }
            return (dfs(s1,s2,s3,index1+1,index2,index3+1) || dfs(s1,s2,s3,index1,index2+1,index3+1));
        }
        else
        {
            return dfs(s1,s2,s3,index1,index2+1,index3+1);
        }        
    }

方法二:从上到下的动态规划

    vector<vector<int>> dp;
    bool isInterleave(string s1, string s2, string s3) {
        // 方法一:递归,对字符串“从前到后”进行扫描,超出时间限制
        // 为了缩短时间,可将方法一中的“对字符串从前向后扫描的递归”改为“对字符串从后向前扫描的递归”,
        // 并配合备忘录的剪枝操作,形成从上到下的动态规划,具体参考方法二
        // return helper(s1,s2,s3,0,0,0);

        // 方法二:从上到下的动态规划,配合记录进行剪枝,此时对字符串的扫描是从后向前
        // dp[i][j]: 表示s1的前i个字符和s2的前j个字符是否可以交织得到s3
        // -1: 未初始化;0: s1和s2交织无法得到s3;1: s1和s2交织可以得到s3
        int n1 = s1.length(), n2 = s2.length(), n3 = s3.length();
        if (n1+n2 != n3)
            return 0;

        if (dp.empty())
            dp.assign(n1+1, vector<int>(n2+1, -1));

        do
        {
            if (-1 != dp[n1][n2])
                break;

            if (s1.empty())
            {
                dp[n1][n2] = (s2 == s3 ? 1 : 0);
                break;
            }
            else if (s2.empty())
            {
                dp[n1][n2] = (s1 == s3 ? 1 : 0);
                break;
            }

            if (s1.back() != s3.back() && s2.back() != s3.back())         
            {
                dp[n1][n2] = 0;
                break;
            }

            if (s1.back() == s3.back())
            {
                if (s2.back() != s3.back())
                    dp[n1][n2] = isInterleave(s1.substr(0,n1-1), s2, s3.substr(0,n1+n2-1));
                else
                    dp[n1][n2] = isInterleave(s1.substr(0,n1-1), s2, s3.substr(0,n1+n2-1)) || isInterleave(s1, s2.substr(0,n2-1), s3.substr(0,n1+n2-1));
            }
            else
            {
                dp[n1][n2] = isInterleave(s1, s2.substr(0,n2-1), s3.substr(0,n1+n2-1));
            }            
        }while(0);

        return dp[n1][n2];
    }

        子序列的数目:LeetCode-097-字符串-子序列-子序列的数目_hclbeloved的博客-CSDN博客

这个题目同样也有类似的解答,大家可以在力扣上找找,有很多题目存在类似的情况。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值