Problem
Given a string s, return the number of different non-empty palindromic subsequences in s. Since the answer may be very large, return it modulo 10⁹ + 7.
A subsequence of a string is obtained by deleting zero or more characters from the string.
A sequence is palindromic if it is equal to the sequence reversed.
Two sequences a₁, a₂, ... and b₁, b₂, ... are different if there is some i for which aᵢ != bᵢ.
https://leetcode.com/problems/count-different-palindromic-subsequences/
Example 1:
Input:
s = "bccb"
Output:6
Explanation: The 6 different non-empty palindromic subsequences are'b','c','bb','cc','bcb','bccb'.
Note that'bcb'is counted only once, even though it occurs twice.
Example 2:
Input:
s = "abcdabcdabcdabcdabcdabcdabcdabcddcbadcbadcbadcbadcbadcbadcbadcba"
Output:104860361
Explanation: There are 3104860382 different non-empty palindromic subsequences, which is 104860361 modulo10⁹ + 7.
Constraints:
1 <= s.length <= 1000s[i]is either'a','b','c', or'd'.
Test Cases
1 | class Solution: |
1 | import pytest |
Thoughts
看起来是 647. Palindromic Substrings 的进阶版,从 substring 升级为 subsequence,而且要去重,不过字符种类从所有英文小写字母缩减到只有 'a'、'b'、'c' 和 'd' 四个。
考虑任意一个子区间 [i, j](0 ≤ i ≤ j < n),定义 dp(i, j) 表示子字符串 s[i:j+1] 内各不相同的回文子序列总数。
关键在于如何避免重复。技巧是确定首(及尾)字母,首字母不同的一定是不同的回文。
不妨以 “a” 为例。先看 s[i:j+1] 内是否有 “a”,没有则跳过。如果有,那么单个字母 “a” 就是回文,计数加一。如果不止一个,那么 “aa” 也是回文,计数再加一。然后找到最左边和最右边的 “a”,设下标分别为 first 和 last(i ≤ first < last ≤ j),给 s[first+1:last] 中所有各不相同回文两头各加一个 “a”,仍然都是各不相同的回文。
可见 s[i:j+1] 中 以 “a” 开头 的回文总数为:
第三个式子的
+ 2,一个代表 “a”,一个代表 “aa”。
同理可以得到 s[i:j+1] 中分别以 “b”、“c”、“d” 开头的回文总数,累加即可。
易知这样可以找出来所有各不相同的回文,既不会有重复,也不会遗漏。
自顶向下计算的话,可以用带缓存的递归。另外可以事先扫描 s,对于每个位置 i,记录四个字母在 s[i:] 最左边分别的位置,以及在 s[:i+1] 最右边分别的位置,以便对于指定的区间 [i, j],可以用常数时间得到某个字母的 first 和 last。
整体时间复杂度为 O(C * n²),空间复杂度 O(n²)。其中 C 是 s 中可能出现的各不相同的字符总数,本题中为 4 可以认为是常数。
Code
1 | from functools import cache |