LeetCode刷题Medium篇寻找字符串中最长的回文子串(动态规划解法)

题目

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

Input: "cbbd"
Output: "bb"

我的尝试

解释一下,回文子串就是正读和反读都是一样的字符串。这个题目也可以改成数字,待会解决数字的情况,先看字符串如何处理。

我思路如下,发现回文就是两个指针,向里移动,如果对应元素都相同,就是回文。判断方法就这样,但是对于任意一个字符串,可能有多个回文,你并不知道中心点是那个字符,无法比较。

所以发现我需要新的算法武器了,除了两个指针,滑动窗口,空间换时间三个思路。看了提示后,发现需要学习动态规划算法才能有解题思路,下面我们先来学习一下动态规划算法

动态规划算法

动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。

基本思想:

与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

    由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。

    与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)

适用的情况

能采用动态规划求解的问题的一般要具有3个性质:

    (1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。

    (2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。

    (3) 有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)

算法步骤

动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示。动态规划的设计都有着一定的模式,一般要经历以下几个步骤。

    初始状态→│决策1│→│决策2│→…→│决策n│→结束状态

                      图1 动态规划决策过程示意图

    (1) 划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。

    (2) 确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。

    (3) 确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。

    (4) 寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。

    一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)。

实际应用中可以按以下几个简化的步骤进行设计:

    (1)分析最优解的性质,并刻画其结构特征。

    (2)递归的定义最优解。

    (3)以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值

    (4)根据计算最优值时得到的信息,构造问题的最优解

引入-斐波那契数列

The Fibonacci numbers are the numbers in the following integer sequence.

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ……..

In mathematical terms, the sequence Fn of Fibonacci numbers is defined by the recurrence relation

    Fn = Fn-1 + Fn-2

with seed values

   F0 = 0 and F1 = 1.

方法一:递归

class fibonacci 
{ 
    static int fib(int n) 
    { 
    if (n <= 1) 
       return n; 
    return fib(n-1) + fib(n-2); 
    } 
       
    public static void main (String args[]) 
    { 
    int n = 9; 
    System.out.println(fib(n)); 
    } 
} 

Time Complexity: T(n) = T(n-1) + T(n-2) which is exponential. 时间复杂度是指数级别

方法二:动态规划

  • 寻找最优子结构
  • 利用已经运算的结果,避免重复运算
// Java program for Fibonacci Series using Space 
// Optimized Method 
class fibonacci 
{ 
	static int fib(int n) 
	{ 
		int a = 0, b = 1, c; 
		if (n == 0) 
			return a; 
		for (int i = 2; i <= n; i++) 
		{ 
			c = a + b; 
			a = b; 
			b = c; 
		} 
		return b; 
	} 

	public static void main (String args[]) 
	{ 
		int n = 9; 
		System.out.println(fib(n)); 
	} 
} 
 

回文子串问题

  • 拆解问题,一个子串从i到j如果是回文串,那么一种情况是子串(j+1,i-1)是回文串并且i等于j。另一种是长度如果小于3(i-j=1或者i-j=0),不需要判断(j+1,i-1)子串状态,因为a,和aa直接判断i和j相同就可以

LeetCode刷题Medium篇寻找字符串中最长的回文子串(动态规划解法)_第1张图片

  • 下一次判断依赖上一次到判断结果,典型到动态规划问题
  • 用一个二维数组存储以前的状态,其实只需要上次的 不需要记录这么多,是否可以优化?
  • 空间利用有些多,是否可以优化?
class Solution {
    public String longestPalindrome(String s) {
        if (s == null || s.length() == 0) {
            return s;
        }
        int n = s.length();
        //substring(i,j) is panlidrome
        boolean[][] dp = new boolean[n][n];
        String res = null;
        //[j, i]
        for (int i = 0; i < n; i++) {
            for (int j = i; j >= 0; j--) {
                //从i到j如果为回文,那么一种情况是i和j元素相等并且i-1到j+1为回文,另外一种是i,j相等并且长度小于2(这个条件必需长度>=3,如果是aa,或者a这个情况(长度小于3)只需要ij相同就行)
                if (s.charAt(i) == s.charAt(j) && (i - j < 2 || dp[j + 1][i - 1])) {
                    dp[j][i] = true;
                    if (res == null || i - j + 1 > res.length()) {
                        res = s.substring(j, i + 1);
                    }
                }
            }
        }
        return res;
    }
}

注意两层for循环,这个是二维数组,第一层循环是列,第二层是行,这样才能保证前面的dp元素被填充,不是第一层是行,第二层是列,这样无法保证被填充。比如babad字符串,dp二维数组的表示为

1 0 1 0 0
  1 0 1 0
    1 0 0
      1 1
        1

 

其实就是求dp为1并且i和j差值最大的情况。比如dp[0][2]为true,表示bab为回文字符,的确是,在动态转换方程中,我们知道必须dp[1][1]为true,并且s[0]=s[2]。所以必须先计算dp[1][1]。所以必须面相列填充!!!!!

马拉车算法

O(n)的解法

https://www.cnblogs.com/grandyang/p/4475985.html

你可能感兴趣的:(架构设计,java常见知识,Leetcode算法)