长大后想做什么?做回小孩!

0%

LeetCode——分隔回文串I&II

NO.131 分割回文串 中等

J4UKn1.png

思路一:回溯 就是暴力,逐位作为子串的起点,不同长度进行分割,是回文的子串的加入到一组结果中。过程中,对子串进行回文判断,不是回文的路径剪枝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
List<List<String>> res = new ArrayList<>();

public List<List<String>> partition(String s) {
if (s == null || s.equals("")) return res;
dfs(s, 0, new LinkedList<String>());
return res;
}

/**
* 回溯
*
* @param s 输入串
* @param start 分割起点
* @param track 路径上分割出来的子串
*/
private void dfs(String s, int start, LinkedList<String> track) {
//分割完毕
if (start == s.length()) {
res.add(new ArrayList<>(track));
return;
}
for (int i = start; i < s.length(); i++) {
//如果子串不是回文,剪枝
if (isPalindrome(s, start, i)) {
track.add(s.substring(start, i + 1));
dfs(s, i + 1, track);
track.removeLast();
}
}
}

//双指针判断子串是否回文
private boolean isPalindrome(String s, int start, int end) {
while (start < end) {
if (s.charAt(start) != s.charAt(end)) {
return false;
}
start++;
end--;
}
return true;
}

思路二:动态规划预处理 不再使用双指针判断[start,end]子串区间是否为回文,而是预处理生成dp数组,dp[left][right]表示[left,right]子串是否为回文串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
List<List<String>> res = new ArrayList<>();

public List<List<String>> partition(String s) {
if (s == null || s.equals("")) return res;
//预处理,得到dp数组
boolean[][] dp = new boolean[s.length()][s.length()];
for (int right = 0; right < s.length(); right++) {
//区间左端点left要小于等于区间右端点right
for (int left = 0; left <= right; left++) {
dp[left][right] = s.charAt(right) == s.charAt(left)
&& (right - left <= 2 || dp[left + 1][right - 1]);
}
}
dfs(s, 0, new LinkedList<String>(), dp);
return res;
}

/**
* 回溯
*
* @param s 输入串
* @param start 分割起点
* @param track 路径上分割出来的子串
*/
private void dfs(String s, int start, LinkedList<String> track, boolean[][] dp) {
//分割完毕
if (start == s.length()) {
res.add(new ArrayList<>(track));
return;
}
for (int i = start; i < s.length(); i++) {
//如果子串不是回文,剪枝
if (dp[start][i]) {
track.add(s.substring(start, i + 1));
dfs(s, i + 1, track, dp);
track.removeLast();
}
}
}

NO.132 分割回文串II 困难

J4UnXR.png

思路一:动态规划

dp[right]的含义:以第right字符结尾的子串[0,right],符合要求的最少分割次数。

初始化:dp[right]=right,即初始化为最多分隔次数;过程中需要判断子串是否为回文,所以像NO.131中那样预处理得到一个标记子串是否为回文的dp数组。

状态转移:如果[0,right]区间子串是回文串,则不需要分割,即dp[right]=0;

否则right作为子串的右端点,枚举[0,right]区间内的left作为分隔点,如果[left+1,right]子串不是回文继续枚举下一个分隔点left,否则是回文则dp[right]=dp[left]+1,即在[0,left]的基础上多分割一次,枚举完毕所有的left取最小的dp[left]再+1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public int minCut(String s) {
if (s == null || s.length() < 2) return 0;
int len = s.length();
int[] dp = new int[len];
//初始化
for (int right = 0; right < len; right++) {
dp[right] = right;
}
//预处理
boolean[][] isPalindrome = new boolean[len][len];
isPalindrome[0][0] = true;
for (int right = 0; right < s.length(); right++) {
for (int left = 0; left <= right; left++) {
isPalindrome[left][right] = s.charAt(left) == s.charAt(right)
&& (right - left <= 2 || isPalindrome[left + 1][right - 1]);
}
}
//状态转移
for (int right = 1; right < len; right++) {
if (isPalindrome[0][right]) {
dp[right] = 0;
} else {
//left是分隔点!分隔后的区间是[0,left]和[left+1,right]
for (int left = 0; left < right; left++) {
if (isPalindrome[left + 1][right]) {
dp[right] = Math.min(dp[right], dp[left] + 1);
}
}
}
}
return dp[len - 1];
}

时间复杂度:O(n^2)

空间复杂度:O(n^2)


本人菜鸟,有错误请告知,感激不尽!

更多题解和源码:github