动态规划(1)——爬楼梯,最长回文子串

最近要开始练动态规划了,这个部分我一直都很害怕,很抗拒,因为一开始学的时候没有看概念直接扣题,书也是看的算法导论,导致我对这部分很害怕,所以决定系统的学习练习一下动态规划,想深入理解一下。结合了各路大佬以及一些书籍总结在此。(相看题的直接往下翻)

1.动态规划题怎么想思路?

动态规划不是某一种具体的算法,而是一种算法思想:若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。
既然是一种思想所以我们在解题的时候怎么能带入这种思想去想问题呢?
我们需要了解一下几个概念:
1.最优子结构:最优子结构规定的是子问题与原问题的关系
大问题的最优解可以由小问题的最优解推出,这个性质叫做“最优子结构性质”。
在这里插入图片描述
2.重复子问题
当我们在递归地寻找每个子问题的最优解的时候,有可能会会重复地遇到一些更小的子问题,而且这些子问题会重叠地出现在子问题里,出现这样的情况。当重复的问题很多的时候,动态规划可以减少很多重复的计算。
自底向上方法(本文只探讨自底向上):对于任何子问题,直到它依赖的所有子问题全部求解完成,才会求解他。
通俗的说就是大事化小,小事化了。小事求解完,大事自然迎刃而解。
说完了一些概念那么具体题中怎么分析呢?
用动态规划解决问题的过程有以下几个关键点:状态定义,状态的转移,初始化和边界条件。
步骤如下:
1.状态定义: 就是定义子问题,如何表示目标规模的问题和更小规模的问题。就是我们做题中的dp[ ][ ],或者dp[ ]。也可理解为我们表格的格子里都要填什么,横纵坐标代表什么。拿到一道题中我们要思考其具体含义。
2.状态转移方程:如何能写出状态转移方程,更多时候我们是卡在了这一步,通俗点就是:找到最后一步或者第一步,其他都不管。
3.初始化和边界条件:这个因题而异。
4.实现
举个例子:爬楼梯(leecode70)
在这里插入图片描述
1.状态定义:一次可以爬一阶或两阶问有多少种方法,第n个楼梯可以由n-1或者n-2爬上来。那我们不妨可以把dp[i]定义为到达第i个楼梯的路径总数。
2.状态转移方程:那我们就可以把第n阶有多少种方法转换成了第n-1阶和第n-2阶有多少种方法,方程为dp[i]=dp[i-1]+dp[i-2],我们只需要找到这个最后一步,至于前面我们不管。
3.当楼梯数等于0.1,2时,就是我们这个问题的初始化,因为有了这些初始值我们后面才会通过这些初始值递推得到。
4.代码:`

class Solution {
    public int climbStairs(int n) {
        if(n<3){
            return n;
        }
        int dp[]=new int[n+1];
        dp[0]=0;//初始化
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
}

2.最长回文子串

此题有五种解法,本文只举出两种。先看题目:
在这里插入图片描述

这个题我最开始的思路是用最长公共子串思想给他倒置一下,但是这个想法有缺陷,但是经修改也可以。本文不再讲述。当时去解题区一看竟有五种方法,直奔动态规划法看去了,一开始没看懂,所以我就想先写一下暴力解法再优化,写完暴力之后动态规划思想自然就出来了。
1.暴力解法
思路:我们可以列举出所有大于等于2的子串,判断它是不是回文串,再选出最长的。其中判断是否是回文数用到的思想是:回文串符合两边相等所以我们先判断他的最左left和最右right相不相等,若相等再判断里面即left++,right–;直到只剩一个数left=right或者全部比完直接返回true。若一开始就不相等则不必继续判断肯定不是回文串了。这部分如果不熟悉的小伙伴去做leecode 9题回文数用转为字符串的方法做。做完那道题就知道这个判断回文串函数怎么写啦。不知道有没有小伙伴有这样的疑虑,那我要是两个字符呢?就像例子里的ac,或者abcdefg,他在判断回文串函数里返回的是false啊?这就是为什么maxlen初值为1,的原因。所以maxlen的初值就是为了字符串没有回文串时准备的。当字符串没有回文串时直接return(0,1)就是ac中的a,abcdefg的a。

    public String longestPalindrome(String s) {
        int len = s.length();
        if (len < 2) {
            return s;
        }
        char s1[]=s.toCharArray();//转成数组,方便挨个字符比对
        int maxlen=1;
        int begin=0;
        for (int i=0;i<len-1;i++) {
            for (int j=i+1;j<len;j++){//j永远比i大至少一个
                if(j-i+1>maxlen&&validPalindromic(s1,i,j)){//若比上一个回文串长且经过判断也是回文串,我们就更新最长回文串
                    maxlen=j-i+1;//选出最长的回文串+1是因为长度而例如aba串j-i是2,但其实是三个字符,所以长度必须+1;
                    begin=i;
                }
            }
        }
        return s.substring(begin,begin+maxlen);
    }
    //判断回文串函数
    private boolean validPalindromic(char[] s, int left, int right) {
        while(left<right){
            if(s[left]!=s[right]){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

2.动态规划
上文说了,动态规划可以减少很多重复子问题的出现,不难发现我们暴力解法出现了很多重复操作如“cabac”我把这个字符串放进判断是否回文函数中,判断它先判断c和c,变成“aba”再判断a和a,直到回复true,但是我们这里“aba”串之前已经判断过了,所以暴力解法做了很多重复的事,导致他很慢。
不难发现如果一个串他是回文串,在它前后加上相等的字符,他还是回文串。如在回文串aba前后都加上c变成cabac也是回文串。
1.状态定义:
在这里插入图片描述
2.状态转换方程:
在这里插入图片描述

3.边界条件初始化:
在这里插入图片描述
初始化还是maxlen=1;begin=0;
4.实现:细说一下
在这里我们把暴力解法中的判断是否回文函数变成了布尔型dp二维数组。并且上面状态转移方程也推出来了,如果Si=Sj(串的第一个位置和最后一个位置相同)且dp[i+1][j-1](去掉头去掉尾)为true。则dp[i][j]=true(代表这个从i到j的串也为回文串);这就是核心的部分了,也是比暴力算法省时的部分。知道了核心,我们还需要注意一下边界问题。在当Si=Sj的前提下:j-i小于3的时候也返回true,j-i如果等于2的话有三个字符,例如aba,也返回true,因为单个字符也是回文串。j-i等于1有两个字符就更不用说了。有个图(盗的)可以助于理解(纵是j,横是i)。

在这里插入图片描述
看代码:

public class Solution {

    public String longestPalindrome(String s) {

        int len = s.length();
        if (len < 2) {
            return s;
        }

        int maxLen = 1;
        int begin = 0;

        boolean[][] dp = new boolean[len][len];
        char[] c = s.toCharArray();

        for (int i = 0; i < len; i++) {
            dp[i][i] = true;//此情况代表只有1个字符,肯定是回文串。
        }
        for (int j = 1; j < len; j++) {//这部分和暴力有所不同因为我们定义了i<j所以直接可以这样写,j初值等于1也是因为串必须大于等于2。小于2的情况直接返回了(上面写了)
            for (int i = 0; i < j; i++) {
                if (c[i] != c[j]) {
                    dp[i][j] = false;
                } else {
                    if (j - i < 3) {//之前解释拉
                        dp[i][j] = true;
                    } else {//dp[i][j]的最终值,依赖掐头去尾的值
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
            //此部分找最长
                if (dp[i][j] && j - i + 1 > maxLen) {
                    maxLen = j - i + 1;
                    begin = i;
                }
            }
        }
        return s.substring(begin, begin + maxLen);
    }
}

未完待续…本小白还会更新的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值