-
Notifications
You must be signed in to change notification settings - Fork 2
feat(algorithms, dynamic programming): dynamic programming problems #151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| # Palindromic Substring | ||
|
|
||
| ## Palindromic Substring | ||
|
|
||
| Given a string, s, return the number of palindromic substrings contained in it. A substring is a contiguous sequence of | ||
| characters in a string. A palindrome is a phrase, word, or sequence that reads the same forward and backward. | ||
|
|
||
| ### Constraints | ||
|
|
||
| - 1 <= `s.length` <= 1000 | ||
| - `s` consists of lower English characters | ||
|
|
||
| ### Examples | ||
|
|
||
|  | ||
|  | ||
|
|
||
| ### Solution | ||
|
|
||
| If we look at the example above, we notice that any substring of length 3 contains a substring of length 1 at the center. | ||
| Although we had already checked all substrings of length 1, our algorithm rechecked them for substrings of longer lengths. | ||
| This rechecking consumes a lot of time, which can be avoided by storing and reusing the results of the earlier computations. | ||
| To do this, we can create a lookup table, dp, of size n×n, where n is the length of the input string. Each cell dp[i][j] | ||
| will store whether the string s[i..j] is a palindromic substring. If the cell dp[i][j] holds the result of the earlier | ||
| computation, we will utilize it in O(1) lookup time instead of making a recursive call again. | ||
|
|
||
| 1. First, we initialize a count variable with 0, which will count the number of palindromic substrings in s. | ||
| 2. A lookup table is initialized with FALSE. | ||
| 3. Base case 1: The diagonal in the lookup table is populated with TRUE because any cell in the diagonal corresponds to | ||
| a substring of length one, and any string of length one is always a palindrome. The number of one-letter palindromic | ||
| strings (i.e., the number of elements in the diagonal) is also added to the count. | ||
| 4. Base case 2: We check whether all two-letter substrings are palindromes and update the count and dp accordingly. We | ||
| do this by iterating over the string, comparing s[i] and s[i+1], and storing the result at dp[i][i+1]. After that, we | ||
| also increment the count if the value of dp[i][i+1] is TRUE, which tells us that the two-letter substring was a | ||
| palindrome. | ||
| 5. After these base cases, we check all substrings of lengths greater than two. However, we only compare the first and | ||
| the last characters. The rest of the string is checked using the lookup table. For example, for a given string “zing”, | ||
| we want to check whether “zin” is a palindrome. We’ll only compare ‘z’ and ‘n’ and check the value of dp[1][1], which | ||
| will tell whether the remaining string “i”, represented by s[1..1], is a palindrome. We’ll take the logical AND of | ||
| these two results and store it at dp[0][2] because “zin” is represented by the substring s[0..2]. This way, we’ll | ||
| avoid redundant computations and check all possible substrings using the lookup table. | ||
|
|
||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|
|
||
| #### Solution Summary | ||
|
|
||
| To recap, the solution to this problem can be divided into the following five main parts: | ||
|
|
||
| 1. We count all one-letter substrings since any one-letter string is always a palindrome. These results are also stored | ||
| in a lookup table to be used later. | ||
| 2. Next, the algorithm checks all two-letter substrings and updates the count and the lookup table accordingly. | ||
| 3. After these base cases, the algorithm checks all possible substrings of lengths greater than three. However, it only | ||
| compares the first and last characters, and the rest of the substring is checked using the lookup table. | ||
| 4. Whenever a palindromic substring is found, the count and the lookup table are updated accordingly. | ||
| 5. After checking all possible substrings, the algorithm terminates and returns the count of the palindromic substrings. | ||
|
|
||
| #### Complexity Analysis | ||
|
|
||
| ##### Time Complexity | ||
|
|
||
| The time complexity of this algorithm is O(n^2), where n is the length of the input string. This is the time required to | ||
| populate the lookup table. | ||
|
|
||
| ##### Space Complexity | ||
|
|
||
| The space complexity of this algorithm is O(n^2), where n is the length of the input string. This is the space occupied | ||
| by the lookup table. | ||
|
|
||
| --- | ||
|
|
||
| ## Longest Palindromic Substring | ||
|
|
||
| Problem Description | ||
|
|
||
| Given a string A of size N, find and return the longest palindromic substring in A. | ||
|
|
||
| Substring of string A is A[i...j] where 0 <= i <= j < len(A) | ||
|
|
||
| Palindrome string: | ||
|
|
||
| A string which reads the same backwards. More formally, A is palindrome if reverse(A) = A. | ||
|
|
||
| Incase of conflict, return the substring which occurs first ( with the least starting index). | ||
|
|
||
| Input Format | ||
| First and only argument is a string A. | ||
|
|
||
| Output Format | ||
| Return a string denoting the longest palindromic substring of string A. | ||
|
|
||
| Example Input | ||
| A = "aaaabaaa" | ||
|
|
||
| Example Output | ||
| "aaabaaa" | ||
|
|
||
| Example Explanation | ||
| We can see that longest palindromic substring is of length 7 and the string is "aaabaaa". | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| def count_palindromic_substrings(s: str) -> int: | ||
| """ | ||
| Counts the number of palindromic substrings that can be found in the given string | ||
| Args: | ||
| s(str): string to check for palindromic substrings | ||
| Returns: | ||
| int: number of palindromic substrings | ||
| """ | ||
| n = len(s) | ||
| dp = [[False] * n for _ in range(n)] | ||
|
|
||
| count = 0 | ||
| # Set all the strings of length 1 to true, these are all palindromes | ||
| for i in range(n): | ||
| count += 1 | ||
| dp[i][i] = True | ||
|
|
||
| # iterate over pairs i, i+1, checking if they are equal, if they are, then they are palindromes | ||
| for i in range(n - 1): | ||
| if s[i] == s[i + 1]: | ||
| count += 1 | ||
| dp[i][i + 1] = True | ||
|
|
||
| for diff in range(2, n): | ||
| for i in range(n - diff): | ||
| j = i + diff | ||
| if s[i] == s[j] and dp[i + 1][j - 1]: | ||
| count += 1 | ||
| dp[i][j] = True | ||
|
|
||
| return count | ||
|
|
||
|
|
||
| def count_palindromic_substrings_2(s: str) -> int: | ||
| """ | ||
| Counts the number of palindromic substrings that can be found in the given string | ||
| Args: | ||
| s(str): string to check for palindromic substrings | ||
| Returns: | ||
| int: number of palindromic substrings | ||
| """ | ||
| n = len(s) | ||
| dp = [[False] * n for _ in range(n)] | ||
|
|
||
| count = 0 | ||
| # Set all the strings of length 1 to true, these are all palindromes | ||
| for i in range(n): | ||
| count += 1 | ||
| dp[i][i] = True | ||
|
|
||
| # iterate over pairs i, i+1, checking if they are equal, if they are, then they are palindromes | ||
| for i in range(n - 1): | ||
| dp[i][i + 1] = s[i] == s[i + 1] | ||
| # A boolean value is added to the count where True means 1 and False means 0 | ||
| count += dp[i][i + 1] | ||
|
|
||
| # Substrings of lengths greater than 2 | ||
| for length in range(3, n + 1): | ||
| i = 0 | ||
| # Checking every possible substring of any specific length | ||
| for j in range(length - 1, n): | ||
| dp[i][j] = dp[i + 1][j - 1] and (s[i] == s[j]) | ||
| count += dp[i][j] | ||
| i += 1 | ||
|
|
||
| return count |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,26 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import unittest | ||||||||||||||||||||||||||||||||||||||||||||||
| from parameterized import parameterized | ||||||||||||||||||||||||||||||||||||||||||||||
| from algorithms.dynamic_programming.palindromic_substring.longest_palindromic_substring import ( | ||||||||||||||||||||||||||||||||||||||||||||||
| longest_palindromic_substring, | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| LONGEST_PALINDROMIC_SUBSTRING_TEST_CASES = [ | ||||||||||||||||||||||||||||||||||||||||||||||
| ("mnm", "mnm"), | ||||||||||||||||||||||||||||||||||||||||||||||
| ("zzz", "zzz"), | ||||||||||||||||||||||||||||||||||||||||||||||
| ("cat", "c"), | ||||||||||||||||||||||||||||||||||||||||||||||
| ("lever", "eve"), | ||||||||||||||||||||||||||||||||||||||||||||||
| ("xyxxyz", "yxxy"), | ||||||||||||||||||||||||||||||||||||||||||||||
| ("wwwwwwwwww", "wwwwwwwwww"), | ||||||||||||||||||||||||||||||||||||||||||||||
| ("tattarrattat", "tattarrattat"), | ||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add edge cases for empty string, single character, and length-2 palindromes. The test suite is missing edge cases that could expose bugs in the implementation:
The last case is particularly important because the implementation in 🧪 Suggested additional test cases LONGEST_PALINDROMIC_SUBSTRING_TEST_CASES = [
+ ("", ""),
+ ("a", "a"),
+ ("aab", "aa"),
+ ("baa", "aa"),
("mnm", "mnm"),
("zzz", "zzz"),
("cat", "c"),
("lever", "eve"),
("xyxxyz", "yxxy"),
("wwwwwwwwww", "wwwwwwwwww"),
("tattarrattat", "tattarrattat"),
]📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| class LongestPalindromicSubstringTestCase(unittest.TestCase): | ||||||||||||||||||||||||||||||||||||||||||||||
| @parameterized.expand(LONGEST_PALINDROMIC_SUBSTRING_TEST_CASES) | ||||||||||||||||||||||||||||||||||||||||||||||
| def test_longest_palindromic_substrings(self, s: str, expected: str): | ||||||||||||||||||||||||||||||||||||||||||||||
| actual = longest_palindromic_substring(s) | ||||||||||||||||||||||||||||||||||||||||||||||
| self.assertEqual(expected, actual) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||||||||||||||||||||||||||
| unittest.main() | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import unittest | ||
| from parameterized import parameterized | ||
| from algorithms.dynamic_programming.palindromic_substring import ( | ||
| count_palindromic_substrings, | ||
| count_palindromic_substrings_2, | ||
| ) | ||
|
|
||
| COUNT_PALINDROMIC_SUBSTRINGS_TEST_CASES = [ | ||
| ("abc", 3), | ||
| ("aaa", 6), | ||
| ("mnm", 4), | ||
| ("zzz", 6), | ||
| ("cat", 3), | ||
| ("lever", 6), | ||
| ("xyxxyz", 9), | ||
| ("wwwwwwwwww", 55), | ||
| ("tattarrattat", 24), | ||
| ] | ||
|
|
||
|
|
||
| class CountPalindromicSubstringsTestCase(unittest.TestCase): | ||
| @parameterized.expand(COUNT_PALINDROMIC_SUBSTRINGS_TEST_CASES) | ||
| def test_count_palindromic_substrings(self, s: str, expected: int): | ||
| actual = count_palindromic_substrings(s) | ||
| self.assertEqual(expected, actual) | ||
|
|
||
| @parameterized.expand(COUNT_PALINDROMIC_SUBSTRINGS_TEST_CASES) | ||
| def test_count_palindromic_substrings_2(self, s: str, expected: int): | ||
| actual = count_palindromic_substrings_2(s) | ||
| self.assertEqual(expected, actual) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor text inconsistency: "three" should be "two".
The solution summary states "lengths greater than three" but should say "lengths greater than two" to be consistent with the algorithm description. Base cases handle lengths 1 and 2, so the loop checks lengths starting from 3 (i.e., greater than 2).
📝 Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents