LeetCode-Palindrome Partitioning

浅浅的花香味﹌ 2022-08-06 18:58 229阅读 0赞

Given a string s, partition s such that every substring of the partition is a palindrome.

Return all possible palindrome partitioning of s.

For example, given s = "aab",
Return

  1. [
  2. ["aa","b"],
  3. ["a","a","b"]
  4. ]

很显然题目要用回溯法,回溯法入门可以看之前的博客,自己的解法方案如下:

  1. public class PalindromePartitioning {
  2. boolean flag = true;
  3. boolean[] visit;
  4. Map<String, Boolean> map = new HashMap<>();
  5. public List<List<String>> partition(String s) {
  6. visit = new boolean[s.length()];
  7. List<List<String>> lists = new ArrayList<>();
  8. if (s != null && !s.isEmpty())
  9. partitionString(lists, s, 1);
  10. return lists;
  11. }
  12. public void partitionString(List<List<String>> lists, String s, int depth) {
  13. if (depth == s.length()) {
  14. List<String> list = new ArrayList<>();
  15. int pre = 0, i = 0;
  16. for (; i < s.length(); i++) {
  17. if (visit[i]) {
  18. String sub = s.substring(pre, i);
  19. if (!isPal(sub)) {
  20. return;
  21. }
  22. list.add(sub);
  23. pre = i;
  24. }
  25. }
  26. if (pre == 0) {
  27. if (!isPal(s)) {
  28. return;
  29. }
  30. list.add(s);
  31. } else {
  32. String sub = s.substring(pre, i);
  33. if (!isPal(sub)) {
  34. return;
  35. }
  36. list.add(sub);
  37. }
  38. lists.add(list);
  39. return;
  40. }
  41. visit[depth] = true;
  42. if (pruning(s, depth)) {
  43. partitionString(lists, s, depth+1);
  44. }
  45. visit[depth] = false;
  46. partitionString(lists, s, depth+1);
  47. }
  48. public boolean pruning(String s, int depth) {
  49. int i = depth-1;
  50. while (i > 0) {
  51. if (visit[i] == true) {
  52. break;
  53. }
  54. i--;
  55. }
  56. String sub = s.substring(i, depth);
  57. return isPal(sub);
  58. }
  59. public boolean isPal(String sub) {
  60. if (map.get(sub) == null || map.get(sub) == false) {
  61. boolean isPal = isPalindrome(sub);
  62. map.put(sub, isPal);
  63. if (!isPal) {
  64. return false;
  65. }
  66. }
  67. return true;
  68. }
  69. public boolean isPalindrome(String s) {
  70. if (s == null || s.isEmpty()) {
  71. return false;
  72. }
  73. int i = 0, j = s.length()-1;
  74. while (i < j) {
  75. if (Character.toLowerCase(s.charAt(i++)) != Character.toLowerCase(s.charAt(j--)))
  76. return false;
  77. }
  78. return true;
  79. }
  80. public static void main(String[] args) {
  81. String s = "efeef";
  82. PalindromePartitioning pp = new PalindromePartitioning();
  83. System.out.println(pp.partition(s));
  84. }
  85. }

首先本题跟之前求解一个集合的子集很像,或者说回溯的题都很像。在实现这个题目之前,最好能把一个字符串的所有字串的划分实现了。之后可以再此基础上修改,本题提交了开始提交的两次都超时了。看了上面的代码也很长,都是优化过的:

1)首先第一个优化是不要每次都去判断一个字符串是否是回文,把判断过的放进一个map中,是回文value值就标记为true,否则为false;下次先查找此map,如果有就直接返回,否则,调用isPalindrome(String s)方法进行判断,并且加入map中。这个优化的目的,因为字串的重复还是比较多的,不知道这个优化效率提升多少

2)第二个优化是用的剪枝的思想,方法pruning(String s, int depth)实现了这个功能,判断刚加进入的字串是否回文,不是的话,直接返回,就不用在尝试下面的字串了。

但是运行时间还是很长,460ms!

好,再次上个代码,更简洁的:

  1. public ArrayList<ArrayList<String>> partition(String s) {
  2. ArrayList<ArrayList<String>> result = new ArrayList<ArrayList<String>>();
  3. if (s == null || s.length() == 0) {
  4. return result;
  5. }
  6. ArrayList<String> partition = new ArrayList<String>();
  7. addPalindrome(s, 0, partition, result);
  8. return result;
  9. }
  10. private void addPalindrome(String s, int start, ArrayList<String> partition,
  11. ArrayList<ArrayList<String>> result) {
  12. if (start == s.length()) {
  13. ArrayList<String> temp = new ArrayList<String>(partition);
  14. result.add(temp);
  15. return;
  16. }
  17. for (int i = start + 1; i <= s.length(); i++) {
  18. String str = s.substring(start, i);
  19. if (isPalindrome(str)) {
  20. partition.add(str);
  21. addPalindrome(s, i, partition, result);
  22. partition.remove(partition.size() - 1);
  23. }
  24. }
  25. }
  26. private boolean isPalindrome(String s) {
  27. if (s == null || s.isEmpty()) {
  28. return false;
  29. }
  30. int i = 0, j = s.length()-1;
  31. while (i < j) {
  32. if (Character.toLowerCase(s.charAt(i++)) != Character.toLowerCase(s.charAt(j--)))
  33. return false;
  34. }
  35. return true;
  36. }

这个代码没有使用位向量的方式,这一般就是回溯法的两种方式,都在这里呈现了,这个效率要比之前那个更高。但是发现效率跟其他人比还不是很快,难道还有其他好的解法?好吧,看到我的第一程序用Map优化的方式根本就很长,明显用DP啊。

首先需要递归的填写一张表,这张表存储各个字串是否为回文,比如pair[i][j]=true表示sub(i,j)(包括边界i和j)是回文字串,所以可以写出一个递归的解:

if (i == j) pair[i][j]=true(此时为单个字符)

if (i != j) (此时为多个字符) then 需要考虑最后一个字符是否和第一字符相等,即s(i) == s(j),则pair[i][j] = pair[i+1][j-1],显然此时要保证i+1>= j-1

所以有第三种情况,i == j-1时,pair[i][j]=true(此时为两个字符,只需这个字符比较即可,不需要递归调用)。显然,如果s(i) != s(j)那么,pair[i][j]=false;所以在O(n^2)的复杂度下,可以把这张表填完,代码示例如下:

  1. for (int i = 0; i < s.length(); i++) {
  2. for (int j = 0; j <= i; j++) {
  3. if (j == i)
  4. pair[j][i] = true;
  5. else {
  6. if (s.charAt(j) != s.charAt(i))
  7. continue;
  8. if (j == i - 1)
  9. pair[j][i] = true;
  10. else
  11. pair[j][i] = pair[j + 1][i - 1];
  12. }
  13. }
  14. }

接下来就需要,根据这张表,快速找到符合条件的字串,下面这种事DP+DFS(249ms):

  1. public List<List<String>> partition(String s) {
  2. List<List<String>> lists = new ArrayList<>();
  3. boolean[][] pair = new boolean[s.length()][s.length()];
  4. for (int i = 0; i < s.length(); i++) {
  5. for (int j = 0; j <= i; j++) {
  6. if (j == i)
  7. pair[j][i] = true;
  8. else {
  9. if (s.charAt(j) != s.charAt(i))
  10. continue;
  11. if (j == i - 1)
  12. pair[j][i] = true;
  13. else
  14. pair[j][i] = pair[j + 1][i - 1];
  15. }
  16. }
  17. }
  18. List<String> partition = new ArrayList<>();
  19. addPalindrome(s, 0, partition, lists, pair);
  20. return lists;
  21. }
  22. private void addPalindrome(String s, int start, List<String> partition,
  23. List<List<String>> result, boolean[][] pair) {
  24. if (start == s.length()) {
  25. ArrayList<String> temp = new ArrayList<String>(partition);
  26. result.add(temp);
  27. return;
  28. }
  29. for (int i = start + 1; i <= s.length(); i++) {
  30. if (pair[start][i-1]) {
  31. String str = s.substring(start, i);
  32. partition.add(str);
  33. addPalindrome(s, i, partition, result, pair);
  34. partition.remove(partition.size() - 1);
  35. }
  36. }
  37. }

复杂度是多少O(n^2) OR O(2^n)

在思考下,根据DP的思想,在生成pair[][]的时候,其实我们已经知道怎么组装字符串了,为什么还用回溯,上代码:

  1. public List<List<String>> partition(String s) {
  2. @SuppressWarnings("unchecked")
  3. List<List<String>>[] lists = new List[s.length()+1];
  4. lists[0] = new ArrayList<List<String>>();
  5. lists[0].add(new ArrayList<String>());
  6. boolean[][] pair = new boolean[s.length()][s.length()];
  7. for (int i = 0; i < s.length(); i++) {
  8. lists[i+1] = new ArrayList<List<String>>();
  9. for (int j = 0; j <= i; j++) {
  10. if (j == i)
  11. pair[j][i] = true;
  12. else {
  13. if (s.charAt(j) != s.charAt(i))
  14. continue;
  15. if (j == i - 1)
  16. pair[j][i] = true;
  17. else
  18. pair[j][i] = pair[j + 1][i - 1];
  19. }
  20. if (pair[j][i]) {
  21. String str = s.substring(j, i+1);
  22. for (List<String> r : lists[j]) {
  23. List<String> ri = new ArrayList<>(r);
  24. ri.add(str);
  25. lists[i+1].add(ri);
  26. }
  27. }
  28. }
  29. }
  30. return lists[s.length()];
  31. }

但是这个是O(n^2)??????

发表评论

表情:
评论列表 (有 0 条评论,229人围观)

还没有评论,来说两句吧...

相关阅读