|
| 1 | +/** |
| 2 | + * @file |
| 3 | + * @brief Implementation of [Longest Palindromic |
| 4 | + * Substring](https://en.wikipedia.org/wiki/Longest_palindromic_substring) |
| 5 | + * using dynamic programming |
| 6 | + * |
| 7 | + * @details |
| 8 | + * The longest palindromic substring problem is to find the contiguous substring |
| 9 | + * of maximum length in a given string that is also a palindrome. A |
| 10 | + * [palindrome](https://en.wikipedia.org/wiki/Palindrome) is a string that |
| 11 | + * reads the same forwards and backwards, such as "racecar" or "noon". |
| 12 | + * |
| 13 | + * This is different from the longest palindromic subsequence, where characters |
| 14 | + * don't need to be contiguous. For example, for the string "banana": |
| 15 | + * - Longest palindromic substring: "anana" (length 5) |
| 16 | + * - Longest palindromic subsequence: "aanaa" (length 5, but not contiguous) |
| 17 | + * |
| 18 | + * ### Algorithm |
| 19 | + * The algorithm uses dynamic programming with a 2D table where dp[i][j] |
| 20 | + * represents whether the substring from index i to j is a palindrome. |
| 21 | + * |
| 22 | + * - All single characters are palindromes: dp[i][i] = true |
| 23 | + * - For two characters: dp[i][i+1] = (s[i] == s[i+1]) |
| 24 | + * - For longer substrings: dp[i][j] = (s[i] == s[j]) && dp[i+1][j-1] |
| 25 | + * |
| 26 | + * ### Time Complexity: O(n^2) where n is the length of the input string |
| 27 | + * ### Space Complexity: O(n^2) for the DP table |
| 28 | + * |
| 29 | + * ### Applications |
| 30 | + * - DNA sequence analysis for identifying repeating patterns |
| 31 | + * - Text processing and pattern recognition |
| 32 | + * - Bioinformatics and genome sequencing |
| 33 | + * - Data compression algorithms |
| 34 | + * |
| 35 | + * @author [Arya Singh](https://github.com/ARYPROGRAMMER) |
| 36 | + * @see longest_palindromic_subsequence.cpp |
| 37 | + */ |
| 38 | + |
| 39 | +#include <cassert> /// for assert |
| 40 | +#include <iostream> /// for IO operations |
| 41 | +#include <string> /// for std::string |
| 42 | +#include <vector> /// for std::vector |
| 43 | + |
| 44 | +/** |
| 45 | + * @namespace dynamic_programming |
| 46 | + * @brief Dynamic Programming algorithms |
| 47 | + */ |
| 48 | +namespace dynamic_programming { |
| 49 | +/** |
| 50 | + * @namespace longest_palindromic_substring |
| 51 | + * @brief Functions for the [Longest Palindromic |
| 52 | + * Substring](https://en.wikipedia.org/wiki/Longest_palindromic_substring) |
| 53 | + * implementation |
| 54 | + */ |
| 55 | +namespace longest_palindromic_substring { |
| 56 | + |
| 57 | +/** |
| 58 | + * @brief Find the longest palindromic substring in a given string |
| 59 | + * @param s input string to analyze |
| 60 | + * @return the longest palindromic substring found in the input |
| 61 | + * |
| 62 | + * @details |
| 63 | + * This function uses dynamic programming to find the longest contiguous |
| 64 | + * palindromic substring. It builds a table where dp[i][j] indicates whether |
| 65 | + * the substring from index i to j is a palindrome. |
| 66 | + * |
| 67 | + * The algorithm proceeds as follows: |
| 68 | + * 1. Initialize all single characters as palindromes |
| 69 | + * 2. Check all substrings of length 2 |
| 70 | + * 3. Check longer substrings by extending from the center |
| 71 | + * 4. Track the longest palindrome found |
| 72 | + * |
| 73 | + * ### Example |
| 74 | + * Input: "babad" |
| 75 | + * Output: "bab" or "aba" (both are valid) |
| 76 | + * |
| 77 | + * Input: "cbbd" |
| 78 | + * Output: "bb" |
| 79 | + */ |
| 80 | +std::string longestPalindromicSubstring(const std::string &s) { |
| 81 | + int n = s.length(); |
| 82 | + |
| 83 | + // Edge case: empty or single character string |
| 84 | + if (n <= 1) { |
| 85 | + return s; |
| 86 | + } |
| 87 | + |
| 88 | + // dp[i][j] will be true if substring s[i..j] is a palindrome |
| 89 | + std::vector<std::vector<bool>> dp(n, std::vector<bool>(n, false)); |
| 90 | + |
| 91 | + // Variables to track the longest palindrome |
| 92 | + int maxLength = 1; // Every single character is a palindrome |
| 93 | + int start = 0; // Starting index of longest palindrome |
| 94 | + |
| 95 | + // All substrings of length 1 are palindromes |
| 96 | + for (int i = 0; i < n; i++) { |
| 97 | + dp[i][i] = true; |
| 98 | + } |
| 99 | + |
| 100 | + // Check for substrings of length 2 |
| 101 | + for (int i = 0; i < n - 1; i++) { |
| 102 | + if (s[i] == s[i + 1]) { |
| 103 | + dp[i][i + 1] = true; |
| 104 | + start = i; |
| 105 | + maxLength = 2; |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + // Check for lengths greater than 2 |
| 110 | + // len is the length of substring |
| 111 | + for (int len = 3; len <= n; len++) { |
| 112 | + // Fix the starting index |
| 113 | + for (int i = 0; i < n - len + 1; i++) { |
| 114 | + // Get the ending index of substring from |
| 115 | + // starting index i and length len |
| 116 | + int j = i + len - 1; |
| 117 | + |
| 118 | + // Check if s[i..j] is palindrome |
| 119 | + // If s[i] == s[j] and s[i+1..j-1] is a palindrome |
| 120 | + if (s[i] == s[j] && dp[i + 1][j - 1]) { |
| 121 | + dp[i][j] = true; |
| 122 | + |
| 123 | + if (len > maxLength) { |
| 124 | + start = i; |
| 125 | + maxLength = len; |
| 126 | + } |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + // Return the longest palindromic substring |
| 132 | + return s.substr(start, maxLength); |
| 133 | +} |
| 134 | + |
| 135 | +/** |
| 136 | + * @brief Alternative approach using expand around center method |
| 137 | + * @param s input string to analyze |
| 138 | + * @param left starting left index of potential palindrome center |
| 139 | + * @param right starting right index of potential palindrome center |
| 140 | + * @return length of the palindrome centered at given indices |
| 141 | + * |
| 142 | + * @details |
| 143 | + * Helper function that expands around a center to find palindromes. |
| 144 | + * This is used by the optimized O(n^2) time, O(1) space approach. |
| 145 | + */ |
| 146 | +int expandAroundCenter(const std::string &s, int left, int right) { |
| 147 | + int n = s.length(); |
| 148 | + while (left >= 0 && right < n && s[left] == s[right]) { |
| 149 | + left--; |
| 150 | + right++; |
| 151 | + } |
| 152 | + // Return the length of the palindrome |
| 153 | + return right - left - 1; |
| 154 | +} |
| 155 | + |
| 156 | +/** |
| 157 | + * @brief Find longest palindromic substring with optimized space complexity |
| 158 | + * @param s input string to analyze |
| 159 | + * @return the longest palindromic substring found in the input |
| 160 | + * |
| 161 | + * @details |
| 162 | + * This is a space-optimized version that uses the expand around center |
| 163 | + * approach. It considers all possible centers (both single characters and |
| 164 | + * pairs of characters) and expands outward to find palindromes. |
| 165 | + * |
| 166 | + * ### Time Complexity: O(n^2) |
| 167 | + * ### Space Complexity: O(1) - only uses constant extra space |
| 168 | + * |
| 169 | + * ### Example |
| 170 | + * Input: "racecar" |
| 171 | + * Output: "racecar" |
| 172 | + */ |
| 173 | +std::string longestPalindromicSubstringOptimized(const std::string &s) { |
| 174 | + if (s.empty()) { |
| 175 | + return ""; |
| 176 | + } |
| 177 | + |
| 178 | + int start = 0; |
| 179 | + int maxLength = 0; |
| 180 | + int n = s.length(); |
| 181 | + |
| 182 | + for (int i = 0; i < n; i++) { |
| 183 | + // Check for odd length palindromes (single character center) |
| 184 | + int len1 = expandAroundCenter(s, i, i); |
| 185 | + // Check for even length palindromes (two character center) |
| 186 | + int len2 = expandAroundCenter(s, i, i + 1); |
| 187 | + |
| 188 | + int len = std::max(len1, len2); |
| 189 | + |
| 190 | + if (len > maxLength) { |
| 191 | + start = i - (len - 1) / 2; |
| 192 | + maxLength = len; |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + return s.substr(start, maxLength); |
| 197 | +} |
| 198 | + |
| 199 | +} // namespace longest_palindromic_substring |
| 200 | +} // namespace dynamic_programming |
| 201 | + |
| 202 | +/** |
| 203 | + * @brief Self-test implementations |
| 204 | + * @returns void |
| 205 | + */ |
| 206 | +static void test() { |
| 207 | + // Test case 1: Basic palindrome |
| 208 | + std::string test1 = "babad"; |
| 209 | + std::string result1 = |
| 210 | + dynamic_programming::longest_palindromic_substring:: |
| 211 | + longestPalindromicSubstring(test1); |
| 212 | + std::string result1_opt = |
| 213 | + dynamic_programming::longest_palindromic_substring:: |
| 214 | + longestPalindromicSubstringOptimized(test1); |
| 215 | + // "bab" or "aba" are both valid answers |
| 216 | + assert((result1 == "bab" || result1 == "aba")); |
| 217 | + assert((result1_opt == "bab" || result1_opt == "aba")); |
| 218 | + std::cout << "Test 1 passed: Input \"" << test1 << "\" -> Output \"" |
| 219 | + << result1 << "\"" << std::endl; |
| 220 | + |
| 221 | + // Test case 2: Even length palindrome |
| 222 | + std::string test2 = "cbbd"; |
| 223 | + std::string result2 = |
| 224 | + dynamic_programming::longest_palindromic_substring:: |
| 225 | + longestPalindromicSubstring(test2); |
| 226 | + std::string result2_opt = |
| 227 | + dynamic_programming::longest_palindromic_substring:: |
| 228 | + longestPalindromicSubstringOptimized(test2); |
| 229 | + assert(result2 == "bb"); |
| 230 | + assert(result2_opt == "bb"); |
| 231 | + std::cout << "Test 2 passed: Input \"" << test2 << "\" -> Output \"" |
| 232 | + << result2 << "\"" << std::endl; |
| 233 | + |
| 234 | + // Test case 3: Single character |
| 235 | + std::string test3 = "a"; |
| 236 | + std::string result3 = |
| 237 | + dynamic_programming::longest_palindromic_substring:: |
| 238 | + longestPalindromicSubstring(test3); |
| 239 | + std::string result3_opt = |
| 240 | + dynamic_programming::longest_palindromic_substring:: |
| 241 | + longestPalindromicSubstringOptimized(test3); |
| 242 | + assert(result3 == "a"); |
| 243 | + assert(result3_opt == "a"); |
| 244 | + std::cout << "Test 3 passed: Input \"" << test3 << "\" -> Output \"" |
| 245 | + << result3 << "\"" << std::endl; |
| 246 | + |
| 247 | + // Test case 4: Entire string is palindrome |
| 248 | + std::string test4 = "racecar"; |
| 249 | + std::string result4 = |
| 250 | + dynamic_programming::longest_palindromic_substring:: |
| 251 | + longestPalindromicSubstring(test4); |
| 252 | + std::string result4_opt = |
| 253 | + dynamic_programming::longest_palindromic_substring:: |
| 254 | + longestPalindromicSubstringOptimized(test4); |
| 255 | + assert(result4 == "racecar"); |
| 256 | + assert(result4_opt == "racecar"); |
| 257 | + std::cout << "Test 4 passed: Input \"" << test4 << "\" -> Output \"" |
| 258 | + << result4 << "\"" << std::endl; |
| 259 | + |
| 260 | + // Test case 5: No palindrome longer than 1 |
| 261 | + std::string test5 = "abcdef"; |
| 262 | + std::string result5 = |
| 263 | + dynamic_programming::longest_palindromic_substring:: |
| 264 | + longestPalindromicSubstring(test5); |
| 265 | + std::string result5_opt = |
| 266 | + dynamic_programming::longest_palindromic_substring:: |
| 267 | + longestPalindromicSubstringOptimized(test5); |
| 268 | + assert(result5.length() == 1); |
| 269 | + assert(result5_opt.length() == 1); |
| 270 | + std::cout << "Test 5 passed: Input \"" << test5 << "\" -> Output \"" |
| 271 | + << result5 << "\"" << std::endl; |
| 272 | + |
| 273 | + // Test case 6: Complex case with multiple palindromes |
| 274 | + std::string test6 = "bananas"; |
| 275 | + std::string result6 = |
| 276 | + dynamic_programming::longest_palindromic_substring:: |
| 277 | + longestPalindromicSubstring(test6); |
| 278 | + std::string result6_opt = |
| 279 | + dynamic_programming::longest_palindromic_substring:: |
| 280 | + longestPalindromicSubstringOptimized(test6); |
| 281 | + assert(result6 == "anana"); |
| 282 | + assert(result6_opt == "anana"); |
| 283 | + std::cout << "Test 6 passed: Input \"" << test6 << "\" -> Output \"" |
| 284 | + << result6 << "\"" << std::endl; |
| 285 | + |
| 286 | + // Test case 7: All same characters |
| 287 | + std::string test7 = "aaaa"; |
| 288 | + std::string result7 = |
| 289 | + dynamic_programming::longest_palindromic_substring:: |
| 290 | + longestPalindromicSubstring(test7); |
| 291 | + std::string result7_opt = |
| 292 | + dynamic_programming::longest_palindromic_substring:: |
| 293 | + longestPalindromicSubstringOptimized(test7); |
| 294 | + assert(result7 == "aaaa"); |
| 295 | + assert(result7_opt == "aaaa"); |
| 296 | + std::cout << "Test 7 passed: Input \"" << test7 << "\" -> Output \"" |
| 297 | + << result7 << "\"" << std::endl; |
| 298 | + |
| 299 | + // Test case 8: Two character palindrome |
| 300 | + std::string test8 = "ac"; |
| 301 | + std::string result8 = |
| 302 | + dynamic_programming::longest_palindromic_substring:: |
| 303 | + longestPalindromicSubstring(test8); |
| 304 | + std::string result8_opt = |
| 305 | + dynamic_programming::longest_palindromic_substring:: |
| 306 | + longestPalindromicSubstringOptimized(test8); |
| 307 | + assert(result8.length() == 1); |
| 308 | + assert(result8_opt.length() == 1); |
| 309 | + std::cout << "Test 8 passed: Input \"" << test8 << "\" -> Output \"" |
| 310 | + << result8 << "\"" << std::endl; |
| 311 | + |
| 312 | + std::cout << "\nAll tests have successfully passed!" << std::endl; |
| 313 | +} |
| 314 | + |
| 315 | +/** |
| 316 | + * @brief Main function |
| 317 | + * @returns 0 on exit |
| 318 | + */ |
| 319 | +int main() { |
| 320 | + test(); // run self-test implementations |
| 321 | + |
| 322 | + // Interactive example |
| 323 | + std::cout << "\n--- Interactive Examples ---" << std::endl; |
| 324 | + |
| 325 | + std::vector<std::string> examples = { |
| 326 | + "forgeeksskeegfor", |
| 327 | + "noon", |
| 328 | + "programming", |
| 329 | + "abacdcaba" |
| 330 | + }; |
| 331 | + |
| 332 | + for (const auto &example : examples) { |
| 333 | + std::string result = dynamic_programming::longest_palindromic_substring:: |
| 334 | + longestPalindromicSubstring(example); |
| 335 | + std::cout << "Longest palindromic substring in \"" << example |
| 336 | + << "\" is \"" << result << "\" (length: " << result.length() |
| 337 | + << ")" << std::endl; |
| 338 | + } |
| 339 | + |
| 340 | + return 0; |
| 341 | +} |
0 commit comments