云小杰

相对于绝对成功的汲汲渴求,越无杂质的奔赴,越是动人。

要一个黄昏, 满是风, 和正在落下的夕阳。如此, 足够我爱这破碎泥泞的人间。


Download the theme

分割字符串

分割字符串

131. 分割回文串

题目描述

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

示例 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 2:

输入:s = "a"
输出:[["a"]]

方法一:回溯+动态规划

需要求出字符串 $s$ 的所有分割方案,因此我们考虑使用搜索 + 回溯的方法枚举所有可能的分割方法并进行判断。

我们可以将字符串 $s$ 的每个子串 $s[i..j]$ 是否为回文串预处理出来,使用动态规划即可。设 $f(i, j)$ 表示 $s[i..j]$是否为回文串,那么有状态转移方程:

image-20220424142541953

其中 $\wedge$ 表示逻辑与运算,即 $s[i..j]$ 为回文串,当且仅当其为空串($i>j$),其长度为 1($i=j$),或者首尾字符相同且 $s[i+1..j-1]$ 为回文串。

预处理完成之后,我们只需要 $O(1)$ 的时间就可以判断任意 $s[i..j]$是否为回文串了。

代码

    public List<List<String>> partition(String s) {
        List<List<String>> ans = new ArrayList<>();
        List<String> list = new ArrayList<>();
        int length = s.length();
        boolean[][] dp = new boolean[length][length];
        for (int i = 0; i < length; i++) {
            Arrays.fill(dp[i], true);
        }

        for (int i = length - 1; i >= 0; i--) {
            for (int j = i + 1; j < length; j++) {
                dp[i][j] = (s.charAt(i) == s.charAt(j)) && dp[i + 1][j - 1];
            }
        }

        dfsForPartition(dp, s, ans, list, 0);
        return ans;

    }

    private void dfsForPartition(boolean[][] dp, String s, List<List<String>> ans, List<String> list, int curr) {
        if (curr == s.length()) {
            ans.add(new ArrayList<>(list));
            return;
        }
        for (int i = curr; i < s.length(); i++) {
            if (dp[curr][i]) {
                list.add(s.substring(curr, i + 1));
                dfsForPartition(dp, s, ans, list, i + 1);
                list.remove(list.size() - 1);
            }
        }
    }

方法二:回溯+记忆化搜索

class Solution {
    int[][] f;
    List<List<String>> ret = new ArrayList<List<String>>();
    List<String> ans = new ArrayList<String>();
    int n;

    public List<List<String>> partition(String s) {
        n = s.length();
        f = new int[n][n];

        dfs(s, 0);
        return ret;
    }

    public void dfs(String s, int i) {
        if (i == n) {
            ret.add(new ArrayList<String>(ans));
            return;
        }
        for (int j = i; j < n; ++j) {
            if (isPalindrome(s, i, j) == 1) {
                ans.add(s.substring(i, j + 1));
                dfs(s, j + 1);
                ans.remove(ans.size() - 1);
            }
        }
    }

    // 记忆化搜索中,f[i][j] = 0 表示未搜索,1 表示是回文串,-1 表示不是回文串
    public int isPalindrome(String s, int i, int j) {
        if (f[i][j] != 0) {
            return f[i][j];
        }
        if (i >= j) {
            f[i][j] = 1;
        } else if (s.charAt(i) == s.charAt(j)) {
            f[i][j] = isPalindrome(s, i + 1, j - 1);
        } else {
            f[i][j] = -1;
        }
        return f[i][j];
    }
}

132. 分割回文串 II

题目描述

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。

返回符合要求的 最少分割次数

示例 1:

输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。

示例 2:

输入:s = "a"
输出:0

示例 3:

输入:s = "ab"
输出:1

方法:动态规划

设 $f[i]$ 表示字符串的前缀 $s[0..i]$ 的最少分割次数。要想得出 $f[i]$ 的值,我们可以考虑枚举 $s[0..i]$分割出的最后一个回文串,这样我们就可以写出状态转移方程:

image-20220429101403610

即我们枚举最后一个回文串的起始位置 $j+1$,保证 $s[j+1..i]$ 是一个回文串,那么 $f[i]$ 就可以从 $f[j]$ 转移而来,附加 $1$ 次额外的分割次数。

另外:当 $s[o…i]$ 本身就是一个回文串的时候,$f[i]=0$。

class Solution {
    public int minCut(String s) {
        int length = s.length();
        boolean[][] dp = new boolean[length][length];
        for (int i = 0; i < length; i++) {
            Arrays.fill(dp[i], true);
        }

        for (int i = length - 1; i >= 0; i--) {
            for (int j = i + 1; j < length; j++) {
                dp[i][j] = (s.charAt(i) == s.charAt(j)) && dp[i + 1][j - 1];
            }
        }

        int[] ans = new int[length];
        Arrays.fill(ans, Integer.MAX_VALUE);
        for (int i = 0; i < length; i++) {
            if (dp[0][i]) {
                ans[i] = 0;
            } else {
                for (int j = 0; j < i; j++) {
                    if (dp[j + 1][i]) {
                        ans[i] = Math.min(ans[i], ans[j] + 1);
                    }
                }
            }
        }
        return ans[length - 1];
    }
}
最近的文章

Git

Git1. Git 简介Git是一个版本管理控制系统(缩写VCS),它可以在任何时间点,将文档的状态作为更新记录保存起来,也可以在任何时间点,将更新记录恢复回来。Git 有什么特点?简单来说就是:高端大气上档次!1.1 集中式 VS 分布式先说集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到...…

继续阅读
更早的文章

Springboot邮件

Springboot邮件1. 依赖 <!-- mail 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency><!--...…

继续阅读