Palindrome直接暴力解
一般采用 双指针 或者 reverse做比较,来判断是否为palindrome
680. 验证回文字符串 Ⅱ(Easy)
Solu:
因为最多只能删除一个,排除掉左右可以构成palindrome的部分后,内部剩下的部分分别删除头/尾,判断是否能构成palindrome
Code:
class Solution:
def validPalindrome(self, s: str) -> bool:
def is_palindrome(l, r):
while l < r:
if s[l] != s[r]:
return False
l += 1
r -= 1
return True
l, r = 0, len(s) - 1
while l < r and s[l] == s[r]:
l += 1
r -= 1
if l >= r:
return True
return is_palindrome(l + 1, r) or is_palindrome(l, r - 1)
9. 回文数(Easy)
Solu 1:双指针
x转换成str后,双指针判断
Code 1:
class Solution:
def isPalindrome(self, x: int) -> bool:
if x < 0:
return False
x = str(x)
l, r = 0, len(x) - 1
while l < r:
if x[l] != x[r]:
return False
l += 1
r -= 1
return True
Solu 2:
把整个数字x做翻转得到rev,并把rev和x做对比
Code 2:
class Solution:
def isPalindrome(self, x: int) -> bool:
if x < 0 or (x % 10 == 0 and x != 0):
return False
rev, temp = 0, x
while temp > 0:
rev = rev * 10 + temp % 10
temp //= 10
return rev == x
234. 回文链表(Easy)
Solu:双指针
- 三指针
prev, slow, fastfast:每次走两步,根据最后fast是停在ListNode/None来判断整串链表的length的奇偶性- 停在node上 -> len = odd
- 停在None上 -> len = even
prev和slow在找链表中点的同时,翻转链表的前半段- 中点找到时,链表前半段同时完成翻转。逐一对比
prev.val和slow.val
- 中点找到时,链表前半段同时完成翻转。逐一对比
Code:
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
prev, slow, fast = None, head, head
while fast and fast.next:
fast = fast.next.next
temp = slow.next
slow.next = prev
prev = slow
slow = temp
if fast: # odd length
slow = slow.next
while slow:
if slow.val != prev.val:
return False
slow = slow.next
prev = prev.next
return True
Palindrome Substring(连续) - DP or 中心扩散法
5. 最长回文子串(Medium)
Solu 1:DP
dp[i][j]=s[i:j+1]是否为palindromedp[i][j] = (s[i]==s[j]) and dp[i+1][j-1]
Code 1:
class Solution:
def longestPalindrome(self, s: str) -> str:
# dp[i][j] = True if s[i:j+1] is palindrome
# dp[i][j] = dp[i+1][j-1] if s[i] == s[j]
dp = [[False] * len(s) for _ in range(len(s))]
max_len, l, r = 1, 0, 0
for i in range(len(s)):
dp[i][i] = True # init
for i in range(len(s) - 2, -1, -1):
for j in range(i + 1, len(s)):
if s[i] == s[j] and (j - i == 1 or dp[i + 1][j - 1]):
dp[i][j] = True
if j - i + 1 > max_len:
max_len = j - i + 1
l, r = i, j
return s[l:r + 1]
Solu 2:中心扩散法(双指针)
- 对
每个idx,分别以l=r=idx(len=odd)和l=idx, r=idx+1(len=even)向两边扩散,寻找以s[l]和s[r]为中心点的最长的回文子串
Code 2:
class Solution:
def __init__(self):
self.max_len = 0
self.l = 0
self.r = 0
def longestPalindrome(self, s: str) -> str:
def extend(l, r):
while l >= 0 and r < len(s) and s[l] == s[r]:
l -= 1
r += 1
if self.max_len < r - l - 1:
self.max_len = r - l - 1
self.l = l + 1
self.r = r - 1
for i in range(len(s)):
extend(i, i)
extend(i, i + 1)
return s[self.l:self.r + 1]
647. 回文子串(Medium)
Solu 1:DP
和LC 5思路一致:一旦dp[i][j] = true,则increment palindrome_count
Code 1:
class Solution:
def countSubstrings(self, s: str) -> int:
# dp[i][j] = True if s[i:j+1] is palindrome
# dp[i][j] = dp[i+1][j-1] and s[i] == s[j]
dp = [[False] * len(s) for _ in range(len(s))]
count = 0
for i in range(len(s)):
dp[i][i] = True # init, single char is palindrome
count += 1
for i in range(len(s) - 2, -1, -1):
for j in range(i + 1, len(s)):
if s[i] == s[j] and (j - i < 3 or dp[i + 1][j - 1]):
dp[i][j] = True
count += 1
return count
Solu 2:中心扩散法(双指针)
和LC 5思路一致:中心扩散时,一旦s[l] == s[r](即:s[l:r+1]为palindrome),则increment palindrome_count
Code 2:
class Solution:
def __init__(self):
self.count = 0
def countSubstrings(self, s: str) -> int:
def expand(left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
self.count += 1
left -= 1
right += 1
for i in range(len(s)):
expand(i, i) # odd length
expand(i, i + 1) # even length
return self.count
Palindrome Subsequence(不一定连续)- DP
因为“中心扩散法”无法跳过某个char,所以子序列(不一定连续)直接用DP解
516. 最长回文子序列(Medium)
Solu 1:DP
dp[i][j]=s[i:j+1]中最长回文子序列的长度p[i][j] = max(dp[i][j-1], dp[i+1][j]) if s[i] != s[j] else dp[i+1][j-1] + 2
Code 1:
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
# dp[i][j] = s[i:j+1]中最长回文子序列的长度
# dp[i][j] = max(dp[i][j-1], dp[i+1][j]) if s[i] != s[j] else dp[i+1][j-1] + 2
dp = [[0] * len(s) for _ in range(len(s))]
for i in range(len(s) - 1, -1, -1):
dp[i][i] = 1 # init
for j in range(i + 1, len(s)):
if s[i] == s[j]:
dp[i][j] = dp[i + 1][j - 1] + 2
else:
dp[i][j] = max(dp[i][j - 1], dp[i + 1][j])
return dp[0][len(s) - 1]
Solu 2:记忆化DFS
dfs(l, r)= 在s[l:r+1]中能产生的最长回文子序列的长度
Code 2:
class Solution:
def __init__(self):
self.memo = {}
def longestPalindromeSubseq(self, s: str) -> int:
def dfs(l, r):
if l > r:
return 0
if l == r:
return 1
key = (l, r)
if key in self.memo:
return self.memo[key]
self.memo[key] = max(dfs(l + 1, r), dfs(l, r - 1)) if s[l] != s[r] else dfs(l + 1, r - 1) + 2
return self.memo[key]
return dfs(0, len(s) - 1)
1216. 验证回文字符串 III(Hard)
Solu:DP
是LC 516的变种:如果对于s的k >= min(#最少需要删除的字符) = len(s) - len(最长回文子序列),则是一个「k回文」
Code:
class Solution:
def isValidPalindrome(self, s: str, k: int) -> bool:
# dp[i][j] = s[i:j+1]中最长回文subsequence的长度
# dp[i][j] = max(dp[i][j-1], dp[i+1][j]) if s[i] != s[j] else dp[i+1][j-1] + 2
dp = [[0] * len(s) for _ in range(len(s))]
for i in range(len(s) - 1, -1, -1):
dp[i][i] = 1 # init
for j in range(i + 1, len(s)):
if s[i] == s[j]:
dp[i][j] = dp[i + 1][j - 1] + 2 if j > i + 1 else 2
else:
dp[i][j] = max(dp[i][j - 1], dp[i + 1][j])
return k >= len(s) - dp[0][-1]
Palindrome + Backtracking
131. 分割回文串(Medium)
Solu:DFS回溯
- 每次添加
s[start : split](如果是回文串的话),这样下次递归DFS时,分割的起始位置就是split - 优化:先用dp,对判断任意
s[i:j]是否为palindrome做预处理。这样在添加s[start : split]时可以直接从dp table中去取
Code:
class Solution:
def __init__(self):
self.ans = []
def partition(self, s: str) -> List[List[str]]:
def isPalindrome():
dp = [[False for _ in range(len(s))] for _ in range(len(s))]
# dp[i][j] = True if s[i:j+1] is palindrome
# dp[i][j] = True if dp[i+1][j-1] and s[i] == s[j]
for i in range(len(s)):
dp[i][i] = True # init: single char is palindrome
for i in range(len(s) - 2, -1, -1):
for j in range(i + 1, len(s)):
if s[i] == s[j] and (j - i < 3 or dp[i + 1][j - 1]):
dp[i][j] = True
return dp
def dfs(start, path, dp):
if start == len(s):
self.ans.append(path[:])
return
for split in range(start + 1, len(s) + 1):
if dp[start][split - 1]:
path.append(s[start:split])
dfs(split, path, dp)
path.pop() # backtracking
dp = isPalindrome() # 预处理,提前判断是否是回文
dfs(0, [], dp)
return self.ans
132. 分割回文串 II(Hard)
Solu 1:记忆化DFS
dfs(start)= min{使s[start:]分割出来的每个子串都是palindrome的分割次数}
Code 1:
class Solution:
def __init__(self):
self.memo = {}
def minCut(self, s: str) -> int:
def isPalindrome():
dp = [[False for _ in range(len(s))] for _ in range(len(s))]
# dp[i][j] = True if s[i:j+1] is palindrome
# dp[i][j] = True if dp[i+1][j-1] and s[i] == s[j]
for i in range(len(s)):
dp[i][i] = True # init: single char is palindrome
for i in range(len(s) - 2, -1, -1):
for j in range(i + 1, len(s)):
if s[i] == s[j] and (j - i < 3 or dp[i + 1][j - 1]):
dp[i][j] = True
return dp
def dfs(start) -> int:
if start == len(s):
return 0
if start in self.memo:
return self.memo[start]
res = float('inf')
for split in range(start + 1, len(s) + 1):
if dp[start][split - 1]:
res = min(res, 1 + dfs(split))
self.memo[start] = res
return res
dp = isPalindrome() # dp预处理
return dfs(0)
Solu 2:DP
dp[i]= min{使s[:i]分割出来的每个子串都是palindrome的分割次数}- 状态转移方程:
- If
s[:i]is NOT palindrome, thendp[i] = min{dp[j]} + 1 (if s[j+1 : i] is palindrome) - Otherwise,
dp[i] = 0
- If
Code 2:
class Solution:
def minCut(self, s: str) -> int:
def isPalindrome():
dp = [[False for _ in range(len(s))] for _ in range(len(s))]
# dp[i][j] = True if s[i:j+1] is palindrome
# dp[i][j] = True if dp[i+1][j-1] and s[i] == s[j]
for i in range(len(s)):
dp[i][i] = True # init: single char is palindrome
for i in range(len(s) - 2, -1, -1):
for j in range(i + 1, len(s)):
if s[i] == s[j] and (j - i < 3 or dp[i + 1][j - 1]):
dp[i][j] = True
return dp
isPalindrome = isPalindrome()
dp = [len(s) - 1] * len(s)
for i in range(len(s)):
if isPalindrome[0][i]:
dp[i] = 0
else:
for j in range(1, i + 1):
if isPalindrome[j][i]:
dp[i] = min(dp[i], dp[j - 1] + 1)
return dp[-1]
Reference: