← Home

730. Count Different Palindromic Subsequences

LeetCode article · C++ solution
Website made by wuisabel-gif · Original C++ code by keineahnung2345
dynamic programmingC++Markdown
730

A good way into this one is to ask: what do we need to remember so we never redo work blindly? For 730. Count Different Palindromic Subsequences, the solution in this repository is mainly a dynamic programming solution.

Guide

What?

The first job is to translate the English prompt into state, transition, and stopping conditions. Instead of trying to be clever immediately, read the code as a sequence of questions:

  • What state are we keeping?
  • How do we move from one state to the next?
  • When do we know the answer is already determined?

For this file, the main tools are: dynamic programming, two pointers, sliding window.

The notes already sitting in the source point us in the right direction:

  • DP, O(N^3)
  • https://leetcode.com/problems/count-different-palindromic-subsequences/discuss/109507/Java-96ms-DP-Solution-with-Detailed-Explanation
  • time: O(N^3), space: O(N^2)

Guide

When?

This is the kind of solution you want when the problem has structure hiding inside a messy-looking input. The accepted code reduces that pressure by storing exactly the information that remains useful later.

The important function names to track are countPalindromicSubsequences, nextRight, char2pos.

Guide

Why?

The win comes from making each line carry responsibility: store the useful state, discard the rest, keep moving.

  • The two-dimensional vector is the memory of the solution: grid state, DP state, or adjacency shape.
  • The final return is not magic; it is the invariant after the loops or recursion have finished doing their accounting.

Guide

How?

Walk through the solution in this order:

  1. Read the setup variables first.
  2. Follow the main loop or recursive helper next.
  3. Watch where invalid states get skipped.
  4. Check which value survives to the return statement.

The most important competitive-programming habit here is to trust the invariant. Once the invariant is right, the loops become much less scary.

Guide

Complexity

  • Time: O(N^3), space: O(N^2)
  • Space: O(n) in the usual case for auxiliary containers or recursion

Guide

C++ Solution

Your submission

The accepted solution

solution.cpp
01//DP, O(N^3)
02//https://leetcode.com/problems/count-different-palindromic-subsequences/discuss/109507/Java-96ms-DP-Solution-with-Detailed-Explanation
03//Runtime: 164 ms, faster than 60.90% of C++ online submissions for Count Different Palindromic Subsequences.
04//Memory Usage: 36.4 MB, less than 100.00% of C++ online submissions for Count Different Palindromic Subsequences.
05//time: O(N^3), space: O(N^2)
06class Solution {
07public:
08    int countPalindromicSubsequences(string S) {
09        int n = S.size();
10        const int MOD = 1e9+7;
11        // vector<vector<long long>> dp(n, vector(n, 0LL)); //don't need this!!
12        vector<vector<int>> dp(n, vector(n, 0));
13        
14        for(int dist = 0; dist < n; dist++){
15            for(int i = 0; i + dist < n; i++){
16                int j = i+dist;
17                //dp[i][j]: count of palindrome in S[i...j]
18                if(i == j){
19                    //base case: only one char
20                    dp[i][j] = 1;
21                    continue;
22                }
23                
24                if(S[i] != S[j]){
25                    /*
26                    dp[i+1][j-1] is included in both 
27                    dp[i][j-1] and dp[i+1][j],
28                    so we need to deduce it
29                    */
30                    dp[i][j] = dp[i][j-1] + dp[i+1][j] - dp[i+1][j-1];
31                }else{
32                    //S[i] == S[j]
33                    int lo = i+1, hi = j-1;
34                    
35                    /*
36                    try to find S[i] in S[i+1...j-1],
37                    if found,
38                    S[lo] will be the first S[i] in S[i+1...j-1]
39                    if not found,
40                    lo will > hi
41                    */
42                    while(lo <= hi && S[lo] != S[i]){
43                        lo++;
44                    }
45                    
46                    /*
47                    either found: S[hi] will be S[i]
48                    or not found: hi < lo
49                    */
50                    while(lo <= hi && S[hi] != S[i]){
51                        hi--;
52                    }
53                    
54                    /*
55                    now there are 3 cases:
56                    lo > hi: not found S[i] in S[i+1...j-1]
57                    lo == hi: only one S[i] in S[i+1...j-1]
58                    lo < hi: at least two S[i] in S[i+1...j-1]
59                    */
60                    
61                    if(lo > hi){
62                        /*
63                        axxxa
64                        dp[i+1][j-1]*2: 
65                        both the palindrome count in xxx itself and
66                        axxxa, so two times
67                        
68                        +2:
69                        we can construct two palindrome with the 2 'a's:
70                        'a' and 'aa'
71                        note that since there is no 'a' in S[i+1...j-1],
72                        so 'a' and 'aa' are not duplicate
73                        */
74                        dp[i][j] = dp[i+1][j-1]*2+2;
75                    }else if(lo == hi){
76                        /*
77                        axxaxxa
78                        dp[i+1][j-1]*2:
79                        both the palindrome count in xxaxx itself and
80                        axxaxxa, so two times
81                        
82                        +1:
83                        we can construct the palindrome 'aa' 
84                        with S[i] and S[j]
85                        note that palindrome 'a' will be duplicate since
86                        S[i+1...j-1] already contains one 'a'
87                        */
88                        dp[i][j] = dp[i+1][j-1]*2+1;
89                    }else if(lo < hi){
90                        /*
91                        axxayyaxxa
92                        
93                        dp[i+1][j-1]*2:
94                        both the palindrome count in xxayyaxx itself and
95                        axxayyaxxa, so two times
96                        
97                        -dp[lo+1][hi-1]:
98                        when constructing palindrome using the two 'a's
99                        (S[i] and S[j]),
100                        we may use S[i],S[j] and the yy(S[lo+1...hi-1]),
101                        but since the palindrome constructed from
102                        S[i],S[j],S[lo+1...hi-1] will be the same of 
103                        that constructed from S[lo],S[hi],S[lo+1...hi-1]
104                        so we need to subtract dp[lo+1][hi-1] to remove
105                        the duplicate
106                        */
107                        dp[i][j] = dp[i+1][j-1]*2 - dp[lo+1][hi-1];
108                    }
109                }
110                
111                /*
112                in 
113                "dp[i][j] = dp[i][j-1] + dp[i+1][j] - dp[i+1][j-1];"
114                or
115                "dp[i][j] = dp[i+1][j-1]*2 - dp[lo+1][hi-1];"
116                
117                because dp[i][j-1], dp[i+1][j], dp[i+1][j-1] and 
118                dp[lo+1][hi-1] are after mod operation
119                (they are not original value),
120                so the subtraction may lead to negative number
121                (the negative number doesn't come from integer overflow)
122                knowning the reason why negative number appears,
123                it's naturally to add the number MOD back!
124                */
125                dp[i][j] = (dp[i][j] < 0) ? dp[i][j] + MOD: dp[i][j] % MOD;
126            }
127        }
128        
129        return dp[0][n-1];
130    }
131};
132
133//DP, O(N^2)
134//optimize the previous method, finding lo and hi in O(1) time
135//https://leetcode.com/problems/count-different-palindromic-subsequences/discuss/109507/Java-96ms-DP-Solution-with-Detailed-Explanation/111365
136//Runtime: 120 ms, faster than 84.48% of C++ online submissions for Count Different Palindromic Subsequences.
137//Memory Usage: 36.7 MB, less than 100.00% of C++ online submissions for Count Different Palindromic Subsequences.
138//time: O(N^2), space: O(N^2)
139class Solution {
140public:
141    int countPalindromicSubsequences(string S) {
142        int n = S.size();
143        const int MOD = 1e9+7;
144        vector<vector<int>> dp(n, vector(n, 0));
145        
146        //record the next right/left position of the same char
147        vector<int> nextRight(n), nextLeft(n);
148        //helper
149        vector<int> char2pos(4, -1);
150        
151        for(int i = 0; i < n; i++){
152            /*
153            initial value of char2pos are all -1,
154            meaning that we haven't found S[i] in S[0...i-1]
155            */
156            nextLeft[i] = char2pos[S[i]-'a'];
157            /*
158            record the position of S[i], 
159            this will be used in following iterations
160            */
161            char2pos[S[i]-'a'] = i;
162        }
163        
164        fill(char2pos.begin(), char2pos.end(), n);
165        for(int i = n-1; i >= 0; i--){
166            nextRight[i] = char2pos[S[i]-'a'];
167            char2pos[S[i]-'a'] = i;
168        }
169        
170        for(int dist = 0; dist < n; dist++){
171            for(int i = 0; i + dist < n; i++){
172                int j = i+dist;
173                if(i == j){
174                    dp[i][j] = 1;
175                    continue;
176                }
177                
178                if(S[i] != S[j]){
179                    dp[i][j] = dp[i][j-1] + dp[i+1][j] - dp[i+1][j-1];
180                }else{
181                    //the position of S[i] in S[i+1...]
182                    int lo = nextRight[i];
183                    //the position of S[j] in S[...j-1]
184                    int hi = nextLeft[j];
185                    /*
186                    note that lo and hi could be out of the range of 
187                    S[i+1...j-1], but this can be handled by lo > hi
188                   */
189                    if(lo > hi){
190                        dp[i][j] = dp[i+1][j-1]*2+2;
191                    }else if(lo == hi){
192                        dp[i][j] = dp[i+1][j-1]*2+1;
193                    }else if(lo < hi){
194                        dp[i][j] = dp[i+1][j-1]*2 - dp[lo+1][hi-1];
195                    }
196                }
197                dp[i][j] = (dp[i][j] < 0) ? dp[i][j] + MOD: dp[i][j] % MOD;
198            }
199        }
200        
201        return dp[0][n-1];
202    }
203};

Cost

Complexity

Time
O(N^3), space: O(N^2)
Dominated by the main traversal, recursion, or data-structure operations in the code.
Space
O(n) in the usual case for auxiliary containers or recursion
Auxiliary state plus the answer structure where the problem requires one.