题目描述

image.png

解题思路

dp

  • 确定dp含义

布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。

  • 递推公式

当i索引的字符和j索引的字符相同时,i,j区间是否为回文串的几种情况:

  • i==j时,例如a,是回文串
  • j - i == 1 时,例如aa,是回文串
  • 如果j - i > 1,此时需要根据i + 1 和 j - 1是否为回文字串推出,所以dp[i][j] = dp[i+1][j-1];

当i索引的字符和j索引的字符不相同时,不是回文串。

  • dp数组如何初始化

dp[i][j]可以初始化为true么? 当然不行,怎能刚开始就全都匹配上了。
所以dp[i][j]初始化为false。

  • 遍历顺序

首先从递推公式中可以看出,情况三是根据dp[i + 1][j - 1]是否为true,在对dp[i][j]进行赋值true的。
image.png
此时需要使用左下角的数才能推出右上角的数,所以一定注意是从下到上,从左到右遍历。

  • 举例推导dp数组

举例,输入:”aaa”,dp[i][j]状态如下:
image.png
注意因为dp[i][j]的定义,所以j一定是大于等于i的,那么在填充dp[i][j]的时候一定是只填充右上半部分

  1. /**
  2. * 动态规划
  3. *
  4. * @param s
  5. * @return
  6. */
  7. public int countSubstrings(String s) {
  8. // 表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
  9. boolean[][] dp = new boolean[s.length()][s.length()];
  10. char[] array = s.toCharArray();
  11. int result = 0; // 回文字串个数
  12. // 初始化,最开始一定不是回文字串
  13. for (int i = 0; i < s.length(); i++) {
  14. dp[0][i] = false;
  15. dp[i][0] = false;
  16. }
  17. // 从下到上,从左到右遍历
  18. for (int i = array.length - 1; i >= 0; i--) {
  19. for (int j = i; j < array.length; j++) {
  20. if (array[i] == array[j]) {
  21. if (j - i <= 1) { // 情况一:i=j,只有一个字母,a,为回文字串 情况二:两个字母,aa,也为回文子串
  22. result++;
  23. dp[i][j] = true;
  24. } else if (dp[i + 1][j - 1]) { // 情况三:此时大于三个字符,就需要判断中间的区间内是否为回文字串
  25. result++;
  26. dp[i][j] = true;
  27. }
  28. }
  29. }
  30. }
  31. return result;
  32. }

双指针法

大致思路:双指针如何比较回文数,从中间开始,向两边扩散,如果两边值相等,那么就是回文字串。此时,如何判断中心点,例如aabbaa,此时使用1作为中心点就会丢失部分回文串,所以应该使用2作为中心点,那为什么不使用3呢,因为3可以由1扩散得到。
首先确定回文串,就是找中心然后向两边扩散看是不是对称的就可以了。
在遍历中心点的时候,要注意中心点有两种情况
一个元素可以作为中心点,两个元素也可以作为中心点。
那么有人同学问了,三个元素还可以做中心点呢。其实三个元素就可以由一个元素左右添加元素得到,四个元素则可以由两个元素左右添加元素得到。

  1. /**
  2. * 双指针法
  3. *
  4. * @param s
  5. * @return
  6. */
  7. public int countSubstrings(String s) {
  8. // 首先确定回文串,就是找中心然后向两边扩散看是不是对称的就可以了。
  9. // 在遍历中心点的时候,要注意中心点有两种情况。
  10. // 一个元素可以作为中心点,两个元素也可以作为中心点。
  11. int result = 0;
  12. for (int i = 0; i < s.length(); i++) {
  13. result += extend(s, i, i, s.length()); // 以1个字符作为中心点
  14. result += extend(s, i, i + 1, s.length()); // 以2个字符作为中心点
  15. // 三个字符等于一个字符左右添加元素,所以奇数都可以由一个中心点推出来
  16. // 四个字符等于二个字符左右添加元素,所以偶数都可以由二个中心点推出来
  17. }
  18. return result;
  19. }
  20. /**
  21. * 循环扩散的判断是否为回文字串(向2边扩散,而不是收缩)
  22. *
  23. * @param s
  24. * @param i
  25. * @param j
  26. * @param n
  27. * @return
  28. */
  29. int extend(String s, int i, int j, int n) {
  30. int res = 0; // 回文串的个数
  31. while (i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)) { // 扩散查找,如果不满足相等直接退出循环
  32. i--; // i向左
  33. j++; // j向右
  34. res++;
  35. }
  36. return res;
  37. }