647. 回文子串
暴力三循环,一共有 n(n+1)/2个子串,分别判断每个子串是否是回文串
1 class Solution { 2 public int countSubstrings(String s) { 3 // 暴力三循环,一共有 n(n+1)/2个子串,分别判断每个子串是否是回文串 4 if(s == null || s.length() == 0){ 5 return 0; 6 } 7 int count = 0; 8 for(int i = 0; i < s.length(); i++){ 9 for(int j = i; j < s.length(); j++){ 10 boolean flag = true; 11 for(int k = 0; k < (j - i + 1)/2; k++){ 12 if(s.charAt(k+i) != s.charAt(j-k)){ 13 flag = false; 14 break; 15 } 16 } 17 if(flag == true){ 18 count++; 19 } 20 } 21 } 22 return count; 23 } 24 }
leetcode 执行用时:620 ms > 6.67%, 内存消耗:37 MB > 59.58%
复杂度分析:
时间复杂度:很明显三个for循环,所以时间复杂度为O(n3)
空间复杂度:O(1)
思路二:动态规划
动态规划,二维数组,dp[i][j]是个布尔值,表示[i, j]之间的子串是否是回文串 区间只有一个字符肯定是回文串 区间只有两个字符,且两个字符相等,那也是回文串 如果[i,j]两个端点的字符相等且内部子串dp[i+1][j-1]也是回文串,那整个区间都是回文串其实上面三种情况可以归纳为,如果区间两端字符相等,且区间长度小于等于2或者大于2但是内部是回文串,那么整个区间都是回文串 因为转态转移方程中求dp[i][j]时用到了dp[i+1][j], 所以我们不按行遍历,而是按列遍历
1 class Solution { 2 public int countSubstrings(String s) { 3 4 if(s == null){ 5 return 0; 6 } 7 int count = 0; 8 int len = s.length(); 9 boolean[][] dp = new boolean[len][len]; 10 // 因为转态转移方程中求dp[i][j]时用到了dp[i+1][j], 所以我们不按行遍历,而是按列遍历 11 for(int j = 0; j < len; j++){ 12 for(int i = 0; i <= j; i++){ 13 // 如果区间两端字符相等,且区间长度小于等于2或者大于2但是内部是回文串,那么整个区间都是回文串 14 if(s.charAt(i) == s.charAt(j) && ((j-i) < 2 || dp[i+1][j-1])){ 15 count++; 16 dp[i][j] = true; 17 } 18 } 19 } 20 return count; 21 } 22 }leetcode 执行用时:12 ms > 41.28%的用户, 内存消耗:39 MB > 29.20% 复杂度分析: 时间复杂度:两个循环,遍历了 len * len /2个元素,所以时间复杂度为O(n2) 空间复杂度:需要一个n* n的矩阵,所以孔家复杂度为O(n2)
其实这个空间复杂度还可以再降低一些,那就是借助一维矩阵而非二维矩阵,因为dp[i][j]只与 dp[i+1][j-1]有关,也就是只与前一列有关,所以我们用一个一维矩阵,只存储原来二维矩阵中一列的值,dp[i] 就只与dp[i+1]有关,因为上一个dp[i+1]肯定是比当前列小一的,所以默认就是dp[i+1][j-1], 很巧妙。我见过还有一题也用到了这样的一个技巧,就是求一个从矩阵左上角到右下角路径最大和。