力扣 132.分割回文串 II

问题描述

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

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

示例1:

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

示例2:

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

示例3:

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

提示:

  • 1 <= s.length <= 2000
  • s 仅由小写英文字母组成

问题思路

思路1:动态规划法+分支限界法

​ 此处利用和131.分割字符串类似的方法进行求解。

​ 首先,用动态规划法判断所有的子字符串是否为回文串。

​ 然后,用分支限界法对于字符串分割方式进行深度优先遍历,定义变量num用来表示分割次数,初始值为0。定义全局变量minnum表示当前已经搜索到的最小分割次数,其初始值为2000,因为字符串s最大长度为2000。

​ 该题为求解最小值,需要确定每次搜索的下界,这里我设置的下界为当前分割次数num。

​ 实现代码如下,空间复杂度O(n^ 2),动态规划时间复杂度O(n^2),分支限界法时间复杂度O(n . 2^n)。运行时间超时。

class Solution {
	int n;
	boolean[][] isPali;
	int minnum = 2000;
	int num = 0;

	public int minCut(String s) {
		n = s.length();
		isPali = new boolean[n][n];
		for (int i = 0; i < isPali.length; i++) {
			Arrays.fill(isPali[i], true);			
		}
		// 计算所有子字符串是否为回文串
		for (int i = n - 1; i >= 0; i--) {
			for (int j = i + 1; j < n; j++) {
				isPali[i][j] = (s.charAt(i) == s.charAt(j)) && isPali[i+1][j-1];
			}
		}
		// 计算最小分割次数
		segmentation(0);
		return minnum;
	}

	private void segmentation(int begin) {
		if (begin == n) {
			minnum = Integer.min(minnum, num - 1); //剩余子串不用分割,因此减1
		}else {
			for (int end = n - 1; end >= begin; end--) {
				if (isPali[begin][end] && num < minnum) {
					num++;
					segmentation(end + 1);
					num--;
				}
			}
		}
	}
}
思路2:动态规划法+分支限界法(改进)

​ 为了降低时间复杂度,我们考虑对思路1进行进一步剪枝。

​ 定义数组count,count[ i ]表示当搜索到下标 i 时,最小的分割次数。每次搜索时,与当前分割次数进行比较,如果当前分割次数已经大于count,则剪枝,如果小于,则更新count。

​ 实现代码如下,空间复杂度O(n^ 2),动态规划时间复杂度O(n^2),分支限界法时间复杂度O(n . 2^n)。运行时间31 ms。

class Solution {
	int n;
	boolean[][] isPali;
	int minnum = 2000;
	int num = 0;
	int[] count;

	public int minCut(String s) {
		n = s.length();
		isPali = new boolean[n][n];
		count = new int[n];
		for (int i = 0; i < isPali.length; i++) {
			count[i] = i + 1;
			Arrays.fill(isPali[i], true);
		}
		// 计算所有子字符串是否为回文串
		for (int i = n - 1; i >= 0; i--) {
			for (int j = i + 1; j < n; j++) {
				isPali[i][j] = (s.charAt(i) == s.charAt(j)) && isPali[i + 1][j - 1];
			}
		}
		// 计算最小分割次数
		segmentation(0);
		return minnum;
	}

	private void segmentation(int begin) {
		if (begin == n) {
			minnum = Integer.min(minnum, num - 1); // 剩余子串不用分割,因此减1
		} else if (num < count[begin]) {
			count[begin] = num;
			for (int end = n - 1; end >= begin; end--) {
				if (isPali[begin][end]) {
					num++;
					segmentation(end + 1);
					num--;
				}

			}
		}
	}
}
思路3:动态规划法+动态规划法

​ 同样,先用动态规划法判断所有的子字符串是否为回文串。

​ 然后,再次使用动态规划法计算最小分割次数。定义数组变量minnum,minnum[i] 表示从 s[0] 到 s[i] 的最小分割次数,且minnum[i] = min{ minnum[j] } + 1 , 0 < j < i,其中 s[j+1] 到 s[i] 为一个回文串。

​ minnum初始值为2000,因为字符串s最大长度为2000。

​ 需注意,当 s 本身就是回文串时,minnum = 0;

​ 实现代码如下,空间复杂度O(n^ 2),两次动态规划时间复杂度均为O(n^2)。运行时间24 ms。

class Solution {
	int n;
	boolean[][] isPali;
	int minnum[];
	int num = 0;

	public int minCut(String s) {
		n = s.length();
		isPali = new boolean[n][n];
		minnum = new int[n];
		Arrays.fill(minnum, 2000);
		for (int i = 0; i < isPali.length; i++) {
			Arrays.fill(isPali[i], true);			
		}
		// 计算所有子字符串是否为回文串
		for (int i = n - 1; i >= 0; i--) {
			for (int j = i + 1; j < n; j++) {
				isPali[i][j] = (s.charAt(i) == s.charAt(j)) && isPali[i+1][j-1];
			}
		}
		// 计算最小分割次数
		for (int i = 0; i < n; i++) {
			if (isPali[0][i]) {
				minnum[i] = 0;
			}else {
				for (int j = 0; j < i; j++) {
					if (isPali[j+1][i]) {
						minnum[i] = Integer.min(minnum[i], minnum[j]+1);
					}
				}
			}						
		}
		return minnum[n-1];
	}
}

你可能感兴趣的:(刷题,leetcode)