題目描述:
Given a string, your task is to count how many palindromic substrings in this string.
The substrings with different start indexes or end indexes are counted as different substrings even they consist of same characters.
Example 1:
Input: "abc"
Output: 3
Explanation: Three palindromic strings: "a", "b", "c".
Example 2:
Input: "aaa"
Output: 6
Explanation: Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa".
Note:
- The input string length won't exceed 1000.
解題思路:
該題計算字符串中有多少個不同的回文子串,注意:具有不同開始位置或結束位置的子串,即使是由相同的字符組成,也會被計為是不同的子串。
首先想到暴力,時間復雜度 O(N^3),pass。
方法1(雙指針):
回文串按照長度分為兩種:長度為奇數(shù)的的回文串(如 'a', 'aba')和長度為偶數(shù)的回文串(如 'aa', 'abba')。雙指針的做法利用了這個性質,將字符串的每個位置 i 分為兩種情況:
- 第一種是長度為 2 的子串(i, i+1)開始,如果這兩字符相等,則向兩邊拓展,判斷長度為 4、6... 是不是回文串;
- 第二種是長度為 3 的子串 (i, i+2) 開始,如果第一個和第三個字符相等,則向兩邊拓展,判斷長度為 5、7... 是不是回文串。
對于每個位置 i,累加兩種情況下的回文子串個數(shù),最后,再加上字符串的長度就是答案(因為單個字符也是回文串)。
舉例:
如 s = "aaaaa", 假設現(xiàn)在遍歷到 s[1] = 'a',分兩種情況:
- 取 [1,2] 構成 'aa',s[1] == s[2],是回文串,則向兩端移動;s[0] == s[3],是回文串,則向兩端移動;越界,則 s[1] 位置的偶數(shù)長度的子串為 2;
- 取 [1,3] 構成 'aaa',s[1] == s[3],是回文串,則向兩端移動;s[0] == s[4],是回文串,則向兩端移動;越界,則 s[1] 位置的奇數(shù)長度的子串為 2。
每個位置都從長度為 2 和長度為 3 的子串分別計算,向兩端擴展,計算偶回文串和奇回文串的個數(shù)。
時間復雜度 O(N^2),空間復雜度 O(1)。
Python3 實現(xiàn):
class Solution:
# 方法1:雙指針,分奇回文串和偶回文串
def countSubstrings(self, s: str) -> int:
lens = len(s)
even = odd = 0
for i in range(lens):
even += self.count(s, lens, i, i+1) # 統(tǒng)計偶回文串個數(shù)
odd += self.count(s, lens, i, i+2) # 統(tǒng)計奇回文串個數(shù)
return lens + even + odd # 單個字符也是回文串
def count(self, s, lens, i, j):
cnt = 0
while i >= 0 and j < lens and s[i] == s[j]:
cnt += 1
i -= 1
j += 1
return cnt
print(Solution().countSubstrings("aaa")) # 6
print(Solution().countSubstrings("aabba")) # 8
方法2(動態(tài)規(guī)劃):
動態(tài)規(guī)劃的思想是,用 dp[i][j]
表示子串 s[i,j]
是不是回文串,初始化為 False。最后,對 dp[lens(s)][len(s)] 所有的 True 求和,就是最后的答案。
接下來找狀態(tài)轉移方程:
當我們要確定 dp[i][j] 是否為 True,需要考慮:(1)dp[i+1][j-1]
是回文串嗎?;(2)s[i] == s[j]
嗎?
因此,我們得到轉移方程:dp[i][j] = True, if dp[i+1][j-1] == True and s[i] == s[j]
舉例:
如 s = "abcba", dp[0][4] = True,因為 dp[0+1][4-1] 為 True 且 s[0] == s[4]。
編程時注意點:
1、在進行雙循環(huán)遍歷時,應該按照 (0,0);(1,1)、(0,1);(2,2)、(1,2)、(0,2);(3,3)、(2,3)、(1,3)、(0,3) 的順序遍歷,這樣可以保證在計算 dp[i][j] 時 dp[i+1][j-1] 已經(jīng)計算出來了。
2、對于 s = 'aa' 這種,計算 dp[0][1] 為 True 時,由于 dp[0+1][1-1] 導致 dp[1][0] 無意義,因此對于這種情況,只需要保證 s[0] == s[1] 即可。
Python3 實現(xiàn):
class Solution:
# 方法2:dp[i][j] = True, if dp[i+1][j-1] == True and s[i] == s[j]
def countSubstrings(self, s: str) -> int:
lens = len(s)
dp = [[False] * lens for _ in range(lens)]
for j in range(lens): # 注意遍歷的順序
dp[j][j] = True
for i in range(j-1, -1, -1): # 注意遍歷的順序
if i+1 <= j-1 and dp[i+1][j-1] == True and s[i] == s[j]:
dp[i][j] = True
elif i+1 > j-1 and s[i] == s[j]:
dp[i][j] = True
cnt = 0
for i in range(lens):
cnt += sum(dp[i])
return cnt
print(Solution().countSubstrings("aaaa")) # 10
print(Solution().countSubstrings("aabbaa")) # 11