Java花式解决'分割回文串 ii'问题详解

目录
  • 前言
  • 题目
  • 思路分析
  • 案例说明
  • 初级代码
  • 代码升级
    • 1.回文串动归
    • 2.综合动归
    • 3.奇思妙想

前言

最学习动态规划思想的路上,遇见了‘分割回文串问题',如临大敌啊,题目听起来蛮简单,思考起来却也没那么容易,比解决问题更头疼的是如何将解决方法进行优化,使得时间空间复杂度尽量的小,经过了反复的挣扎思考,终于总结出来了这一篇 分割回文串 ii 的文章,花式解决该问题,总有一款适合你。

牛客链接

题目

给出一个字符串s,分割s使得分割出的每一个子串都是回文串

计算将字符串s分割成回文分割结果的最小切割数

例如: 给定字符串s=“aab”,

返回1,因为回文分割结果[“aa”,“b”]是切割一次生成的。

思路分析

首先,已知字符串s的长度为len,想要求前len个字符串被切割成回文串所需要的最小切割数,就会很自然的想到求前i个字符形成的字符串切割成回文串需要的最小切割数,即为状态F(i)。

然后,会想到前i个字符形成的字符串分割变成回文串需要的最大切割数为i-1。例如字符串"aab",切2刀形成长度为1的回文串"a",“a”,“b”。

再然后,关键在于求解最小切割数的过程,这里采取暴力求解,定义变量j,使之小于i,在我们已知状态F(j)的情况下(即前j个字符形成的字符串的最小切割次数),如果[j+1,i]是回文串,那么再来上一刀就可以求出当前用最少的切割次数。那么此时F(i)= min(F(i),F(j)+1),意思就是在上一次求得的前i个字符串的分割次数和这一次求得的次数进行对比,取最小值。(注:这里的[j+1,i]指的是字符串第j+1个字符到第i个字符的意思,并非字符串下标索引,写代码时,转换成索引就应该是求下标为j-1的字符到下标为i的字符形成的字符串是否为回文串)

紧接着,就该实现判断是否为回文串的方法,简单的思想就是,为该方法提供字符串s,提供子串的起始下标start与终点下标end,start<end的条件下,使start向后走,end向前走,但凡对应到字符串s中的字符不一样,就说明不是回文串,返回false,如果成功遍历完了循环,说明是回文串,返回true。

最后,将F(len)的值返回。

动归四角度:

1.状态定义F(i):字符串s的前i个字符的最小分割次数;

2.状态间的转移方程定义:F(i)=min(F(i),F(j)+1),0 <= j < i,且F(j)为已知状态,当[j+1,i]为回文串时,执行此状态转移方程

3.状态的初始化:F(i) = i-1,注意F(0)为-1;

例如,当字符串为"aa",因为[1,2]为回文串,F(2)= min(F(2),F(0)+1)=min(1,0)= 0,得到正确答案

4.返回结果 :F(s.length());

案例说明

为了方便理解,这里采取了更长的字符串"aabaa",一步步带你走过程。

初级代码

import java.util.*;
public class Solution {
    //判断是否为回文串
    public boolean func(String s,int start,int end) {
        while(start<end) {
            if(s.charAt(start)!=s.charAt(end)) {
                return false;
            }
            start++;
            end--;
        }
        return true;
    }
    public int minCut (String s) {
        int len = s.length();//字符串的长度
        if(len == 0) return 0;//当长度为0时直接返回0
        int[] count = new int[len + 1];//用于记录状态
        //状态初始化
        for(int i = 0;i <= len;i ++) {
            count[i] = i-1;
        }
        for(int i = 1;i <= len;i++) {
            for(int j = 0;j <= i-1;j++) {
                if(func(s,j,i-1)) {
                    count[i] = Math.min(count[i],count[j]+1);//状态转移方程
                }
            }
        }
        return count[len];//返回结果
    }
}

在整个进行状态计算的过程中,两层for循环时间复杂度为O(N2),判断是否为回文串的方法时间复杂度为O(N),因此总的来说,总的时间复杂度为O(N3)

代码升级

可以看出来,用上面的代码时间复杂度还是比较高的,因此代码还需升级才是

1.回文串动归

首先,关于回文串的判断方法,每次判断是否要进行状态转移方程时都要调用回文串方法,这真的有必要吗,或许也可以使用动态规划的思想将每种字符子串是否为回文串的状态记录下来。

状态四角度:

1.状态定义F(i,j):字符区间[i,j]是否为回文串

2.状态间的转移方程定义F(i,j):

如果i == j,表示单字符,F(i,j) = true;

如果j == i+1,表明俩字符是紧挨着的,如果在总字符串s中对应的字符相同,F(i,j)= true,反之F(i,j) = false;

其他的情况中,F(i,j) = (s.charAt(i) == s.charAt(j)) && F(i+1,j-1);

该转移方程的意思为字符首尾字符相同,且去掉字符区间的首位字符后的字符区间的状态F(i+1,j-1)仍然为回文串才证明[i,j]字符串区间为回文串即F(i,j)= true

3.状态的初始化:F(i,j) = false

4.返回结果状态二维布尔类型数组

注:由于在状态转移的过程中,求F(i,j)会只用到之前已经计算过的状态F(i+1,j-1),这就意味着i需要从后向前遍历,使用的是已经更新过结果的值

import java.util.*;
public class Solution {
    //判断是否为回文串
    public boolean[][] func2 (String s) {
        int len = s.length();//字符串的长度
        boolean[][] ret = new boolean[len][len];
        //记录状态的二维数组,默认值为false
        //由于i<=j<len,所以ret数组实际只更新了一半
        for(int i = len;i >= 0;i--) {
            for(int j = i;j<len;j++) {
                if(i == j) {
                    ret[i][j] = true; //单字符比为回文串
                }else if(j == i+1) {
                    if(s.charAt(i) == s.charAt(j)) {
                        ret[i][j] = true; //相邻字符相同为回文串
                    }else{
                        ret[i][j] = false;//相邻字符不同就不是回文串
                    }
                }else{
                    ret[i][j] = (s.charAt(i) == s.charAt(j)) && ret[i+1][j-1];
                    //其余转移情况
                }
            }
        }
        return ret;//返回结果
    }
    public int minCut (String s) {
        int len = s.length();
        if(len == 0) return 0;
        int[] count = new int[len + 1];
        //状态初始化
        for(int i = 0;i <= len;i ++) {
            count[i] = i-1;
        }
        boolean[][] ret = func2(s);//调用判断回文串方法,获得所有字符子串的是否为回文串的情况
        for(int i = 1;i <= len;i++) {
            for(int j = 0;j <= i-1;j++) {
                //直接在ret数组中找结果,避免反复调用回文串判断方法
                if(ret[j][i-1]) {
                    count[i] = Math.min(count[i],count[j]+1);//状态转移方程
                }
            }
        }
        return count[len];//返回结果
    }
}

在该方法中,判断回文串的方法时间复杂度为O(N2),但因为在主方法中只调用了一次,且回文串判断方法中只更新了一般的值,因此总的时间复杂度为O(N2)~O(2*N2)

2.综合动归

可以看的出来上面的代码还是比较长的,回文串判断方法用到了两层循环,主方法也用到了两层循环,这不也是优化的方向蛮,或许可以把它们放在同一个两层循环中。

注:由于回文串判断方法中的i是一定要从后向前遍历的,因此主函数的初识值就需要调整为count[i] = len - i - 1,返回的结果为F(0)

import java.util.*;
public class Solution {
    public int minCut(String s) {
        int len = s.length();//字符串的长度
        if(len == 0) return 0;
        int []count = new int[len+1]; //存放最小分割次数状态的数组
        boolean [][]p = new boolean[len][len];//存放[i,j]字符区间是否为回文串的二维数组
        for(int i = 0; i <= len; i++) count[i] = len - i - 1;//状态初始化
        for(int i = len-1;i >= 0;i--){
            for(int j = i;j < len;j++){
                //j-i<2 条件成立且第一个条件成立包含着单个字符串和相邻字符串的情况
                //p[i+1][j-1] 为 ture 且第一个条件成立则代表着其他的回文串状态转移类型
                //以上情况有一项成立则F(i,j)为 ture
                if(s.charAt(i) == s.charAt(j) && (j-i<2||p[i+1][j-1])){
                    p[i][j] = true;
                    count[i] = Math.min(count[i],count[j+1]+1);//状态转移方程
                }
            }
        }
        return count[0];//返回结果
    }
}

通过这样的方法,直接将时间复杂度降到了O(N2)

3.奇思妙想

上面几种方法,需要将回文串的判断状态都记录下来,且判断回文串的方法都是从子字符串的两头向中间进行判断,或许有一种方法,可以直接不用记录下来每种子字符串的是否为回文串的状态,并且从中间向两头进行判断回文串。

可以设置两个变量i和j,[i,j]且j==i代表着下标为i的单个字符,必定是回文串,F(j+1)= min(F(j+1),F(i)+1),以此为中心,i--,j++,如果区间两头的字符相同,说明[i-1,j+1]的区间字符串为回文串,在不超出原字符串s的总区间[0,len-1]的循环情况下,重复上面的操作,直到循环条件不成立

回文串可能是奇数个字符,也可能是偶数个字符,上面的情况是奇数个字符的情况,换成偶数个字符的情况只需要判断[i,i+1]是否为回文串,如果是,就参考上面的方式,以此为中心向两头展开,求以[i,i+1]为中心最长的回文串,从而求出每个状态的最小分割数。

案例说明:

import java.util.*;
public class Solution {
    public int minCut(String s) {
        int len = s.length();//字符串的长度
        if(len == 0) return 0;
        int[] count = new int[len + 1];
        for(int i = 0; i <= len; i++) count[i] = i - 1;//状态初始化
        for(int i = 0; i < len; i++) {
            func3(s, i, i, count);//奇数个字符的回文串
            func3(s, i, i + 1, count);//偶数个字符的回文串
        }
        return count[len];//返回结果
    }
    private  void func3(String s, int i, int j, int[] count) {
        //不超过字符串s的区间范围且下标i的字符和下标j的字符相等的条件下向两头扩展,得到最长的回文串,以此来求出状态
        while(i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)) {
            count[j + 1] = Math.min(count[j + 1], count[i] + 1); //状态转移
            --i;//左区间扩展一格
            ++j;//右区间扩展一格
        }
        return;
    }
} 

到此这篇关于Java花式解决'分割回文串 ii'问题详解的文章就介绍到这了,更多相关Java解决分割回文串 ii内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java动态规划之编辑距离问题示例代码

    动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移.一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划. 动态规划实际上是一类题目的总称,并不是指某个固定的算法.动态规划的意义就是通过采用递推(或者分而治之)的策略,通过解决大问题的子问题从而解决整体的做法.动态规划的核心思想是巧妙的将问题拆分成多个子问题,通过计算子问题而得到整体问题的解.而子问题又可以拆分成更多的子问题,从而用类似递推迭代的方法解决要求的问题.问题描述: 对于序列S和T,

  • Java动态规划之硬币找零问题实现代码

    动态规划的基本思想是将待求解问题分解成若干个子问题,先求解子问题,并将这些子问题的解保存起来,如果以后在求解较大子问题的时候需要用到这些子问题的解,就可以直接取出这些已经计算过的解而免去重复运算.保存子问题的解可以使用填表方式,例如保存在数组中. 用一个实际例子来体现动态规划的算法思想--硬币找零问题. 问题描述: 假设有几种硬币,并且数量无限.请找出能够组成某个数目的找零所使用最少的硬币数.例如几种硬币为[1, 3, 5], 面值2的最少硬币数为2(1, 1), 面值4的最少硬币数为2(1,

  • 用JAVA实现单链表,检测字符串是否是回文串

    一.需求 使用JAVA实现单链表,使用单链表检测字符串是否是回文串 二.需求分析 回文串最重要的就是对称,那么最重要的问题就是找到那个中心,用快指针每步走两格,当他到达链表末端的时候,慢指针刚好到达中心,慢指针在遍历过程中(快指针到达末端时)把走过的节点进行反向操作,此时从中位点分为前后两部分,此时前半部分的指针开始往回指(取next的时候,取的是前一个节点),而慢指针继续向前,跟前半部分的数据依次进行比对,当慢指针扫完整个链表,就可以判断这是回文串,否则就提前退出,同时在前半部分往回遍历的过程

  • Java实现查找当前字符串最大回文串代码分享

    先看代码 public class MaxHuiWen { public static void main(String[] args) { // TODO Auto-generated method stub String s = "abb"; MaxHuiWen(s); } //1.输出回文串 public static void MaxHuiWen(String s){ //存储字符串的长度 int length = s.length(); //存储最长的回文串 String M

  • Java花式解决'分割回文串 ii'问题详解

    目录 前言 题目 思路分析 案例说明 初级代码 代码升级 1.回文串动归 2.综合动归 3.奇思妙想 前言 最学习动态规划思想的路上,遇见了'分割回文串问题',如临大敌啊,题目听起来蛮简单,思考起来却也没那么容易,比解决问题更头疼的是如何将解决方法进行优化,使得时间空间复杂度尽量的小,经过了反复的挣扎思考,终于总结出来了这一篇 分割回文串 ii 的文章,花式解决该问题,总有一款适合你. 牛客链接 题目 给出一个字符串s,分割s使得分割出的每一个子串都是回文串 计算将字符串s分割成回文分割结果的最

  • C++/C 回文字符串的实例详解

    C++/C回文字符串的实例详解 判断输入的字符串是不是回文字符串,正反读一样. .C版 #include<stdio.h> int main() { char he[100]; char a; int i=0,flag=1; while((a=getchar())!='\n') { he[i]=a; i++; } int n=i; for(i=0;i<n/2;i++) { printf("%c\t%c\n",he[i],he[n-1-i]); if(he[i]!=he

  • 对python判断是否回文数的实例详解

    设n是一任意自然数.若将n的各位数字反向排列所得自然数n1与n相等,则称n为一回文数.例如,若n=1234321,则称n为一回文数:但若n=1234567,则n不是回文数. 上面的解释就是说回文数和逆序后的结果是相等的.这就是判断一个数值是否是回文数的标准. 代码也是根据这个思路来实现的. # -*- coding: utf-8 -*- """ Created on Sun Aug 5 09:01:38 2018 @author: FanXiaoLei ""

  • Java中将接口返回的字节串转为文件详解

    讲一下现在的需求场景 最近公司要在项目中访问一个第三方服务,在这个第三方服务中,需要下载一个报告文件,通过一个接口反馈回来. 这个下载接口返回了一个字节串,如[1,2,3,4,5,6,7],当然真实的数据不会是这个样子的. 但是我们如何将这个字节串转成文件流呢? 接下来就一起来看看吧,也跟大家分享一下我处理的思路. 试一下直接转字符串后转字节数组 我首先拿到了这个字节串,但是并没有办法直接转成字节数组byte[]. 这个时候我想到一个方法,那就是直接讲这个字节串转成字符串,也就是下面的代码: O

  • C++实现LeetCode(132.拆分回文串之二)

    [LeetCode] 132.Palindrome Partitioning II 拆分回文串之二 Given a string s, partition s such that every substring of the partition is a palindrome. Return the minimum cuts needed for a palindrome partitioning of s. Example: Input: "aab" Output: 1 Explan

  • python最长回文串算法

    给定一个字符串,要求在这个字符串中找到符合回文性质的最长子串.所谓回文性是指诸如 "aba","ababa","abba"这类的字符串,当然单个字符以及两个相邻相同字符也满足回文性质. 看到这个问题,最先想到的解决方法自然是暴力枚举,通过枚举字符串所有字串的起点,逐一判断满足回文性的子串,记录长度并更新最长长度.显然这种算法的时间复杂度是很高的,最坏情况可以达到O(N*N).所以呢,这里提出一个优化的方案,通过枚举字符串子串的中心而不是起点,向两

  • js如何找出字符串中的最长回文串

    本文实例为大家分享了js找出字符串中的最长回文串的具体代码,供大家参考,具体内容如下 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>回文</title> <link rel=&q

  • Python实现"验证回文串"的几种方法

    一.LeetCode--125.验证回文串 1.问题描述 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写. 说明:本题中,我们将空字符串定义为有效的回文串. 2.示例 示例 1: 输入: "A man, a plan, a canal: Panama" 输出: True 示例 1: 输入: "race a car" 输出: False 示例 3: 输入: "!!!" 输出: True 二.解题分析 在排除空格及特殊

随机推荐