Skip to content

Commit 0a37a36

Browse files
committed
feat: add longest palindromic substring algorithm
Signed-off-by: Arya Pratap Singh <[email protected]>
1 parent be550cd commit 0a37a36

File tree

2 files changed

+342
-0
lines changed

2 files changed

+342
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
* [Longest Increasing Subsequence](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/dynamic_programming/longest_increasing_subsequence.cpp)
113113
* [Longest Increasing Subsequence Nlogn](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/dynamic_programming/longest_increasing_subsequence_nlogn.cpp)
114114
* [Longest Palindromic Subsequence](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/dynamic_programming/longest_palindromic_subsequence.cpp)
115+
* [Longest Palindromic Substring](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/dynamic_programming/longest_palindromic_substring.cpp)
115116
* [Matrix Chain Multiplication](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/dynamic_programming/matrix_chain_multiplication.cpp)
116117
* [Maximum Circular Subarray](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/dynamic_programming/maximum_circular_subarray.cpp)
117118
* [Minimum Edit Distance](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/dynamic_programming/minimum_edit_distance.cpp)
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
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

Comments
 (0)