From 0b3383ad0350730cbaf272fe6a68e8b746e62572 Mon Sep 17 00:00:00 2001 From: Sainnhe Park Date: Wed, 2 Nov 2022 17:32:58 +0800 Subject: [PATCH] s0005 --- .../s0005_longest_palindromic_substring.hpp | 36 +++++ src/s0005_longest_palindromic_substring.cpp | 139 ++++++++++++++++++ tests/s0005_longest_palindromic_substring.cpp | 22 +++ 3 files changed, 197 insertions(+) create mode 100644 include/s0005_longest_palindromic_substring.hpp create mode 100644 src/s0005_longest_palindromic_substring.cpp create mode 100644 tests/s0005_longest_palindromic_substring.cpp diff --git a/include/s0005_longest_palindromic_substring.hpp b/include/s0005_longest_palindromic_substring.hpp new file mode 100644 index 0000000..af6962b --- /dev/null +++ b/include/s0005_longest_palindromic_substring.hpp @@ -0,0 +1,36 @@ +#ifndef S0005_LONGEST_PALINDROMIC_SUBSTRING +#define S0005_LONGEST_PALINDROMIC_SUBSTRING + +#include +#include +#include + +using namespace std; + +class Solution1 { + public: + /** + * @brief Longest Palindromic Substring + * + * Given a string s, return the longest palindromic substring in s. + * + * @param s the given string + * @return longest palindromic substring + */ + string longestPalindrome(string s); +}; + +class Solution2 { + public: + /** + * @brief Longest Palindromic Substring + * + * Given a string s, return the longest palindromic substring in s. + * + * @param s the given string + * @return longest palindromic substring + */ + string longestPalindrome(string s); +}; + +#endif diff --git a/src/s0005_longest_palindromic_substring.cpp b/src/s0005_longest_palindromic_substring.cpp new file mode 100644 index 0000000..351dbc2 --- /dev/null +++ b/src/s0005_longest_palindromic_substring.cpp @@ -0,0 +1,139 @@ +#include "s0005_longest_palindromic_substring.hpp" + +// 思路一:字符串翻转 + 动态规划 +// 看到回文串就要想到字符串翻转。 +// 字符串翻转之后问题就可以描述成找到 s 和 reverse(s) 的最长公共子字符串 +// 再看一个例子,S="abc435cba",S="abc534cba",最长公共子串是 "abc" 和 +// "cba",但很明显这两个字符串都不是回文串。 +// 所以我们求出最长公共子串后,并不一定是回文串,我们还需要判断该字符串倒置前的下标和当前的字符串下标是不是匹配。 + +// 思路二:直接动态规划 +// dp[i][j] 表示起始为 i ,终点为 j 的子字符串是否为回文字符串 +// 那么有: +// if s[i] == s[j]: +// dp[i][j] = dp[i - 1][j - 1] +// else: +// dp[i][j] = false +// 把 dp 这张二维数组的表全部填好,然后在里面找最长的子字符串。 +// 时间复杂度:O(n^2) 因为总共有 n^2 个状态,计算每个状态需要的时间为 O(1) +// 空间复杂度:O(n^2) 因为总共需要存储 n^2 个状态。 +string Solution1::longestPalindrome(string s) { + int len = s.length(); + vector> dp(len, vector(len)); + + if (len < 2) { + return s; + } + + // L 表示子字符串长度 + // L = 1, true + for (int i = 0; i < len; i++) { + dp[i][i] = true; + } + + // L = 2 + // if l == r, dp = true + // else, dp = false + for (int i = 0; i < len - 1; i++) { + if (s[i] == s[i + 1]) { + dp[i][i + 1] = true; + } else { + dp[i][i + 1] = false; + } + } + + // L >= 3 + // if l == r, dp = dp_prev + // else, dp = false + for (int L = 3; L <= len; L++) { + for (int i = 0; i + L - 1 < len; i++) { + if (s[i] == s[i + L - 1]) { + dp[i][i + L - 1] = dp[i + 1][i + L - 2]; + } else { + dp[i][i + L - 1] = false; + } + } + } + + // find max len + int maxLen = 1; + int beginIndex = 0; + for (int i = 0; i < len; i++) { + for (int j = i; j < len; j++) { + if (dp[i][j]) { + if (j - i + 1 > maxLen) { + maxLen = j - i + 1; + beginIndex = i; + } + } + } + } + + return s.substr(beginIndex, maxLen); +} + +// 思路三:中心扩散算法 +// 每个回文字符串都有一个“回文中心” +// 这个回文中心要么是一个长度为 1 的字符,要么是一个长度为 2 +// 的字符串(这个字符串的两个字符都相等) +// “回文中心”实际上是两种边界情况,也就是说每个可能的结果都是由边界情况扩展开来的 +// 因此我们可以遍历每个边界情况,对它进行扩展 + +typedef struct ResultStruct { + int left1; // 边界情况为长度为 1 的字符时,扩展完成后的左边界 + int right1; // 边界情况为长度为 1 的字符时,扩展完成后的右边界 + int left2; // 边界情况为长度为 2 的字符串时,扩展完成后的左边界 + int right2; // 边界情况为长度为 2 的字符串时,扩展完成后的右边界 +} Result; + +// 输入为字符串和边界情况的那一点 +// 返回值包含为扩展的结果 +Result expand(string s, int i) { + int len = s.length(); + + if (i == len - 1) { + return Result{i, i, i, i}; + } + + // 边界情况为长度为 1 的字符时 + int left1, right1; + for (int x = 0; i + x < len && i - x >= 0; x++) { + if (s[i - x] == s[i + x]) { + left1 = i - x; + right1 = i + x; + } else { + break; + } + } + + // 边界情况为长度为 2 的字符串时 + int left2, right2; + for (int x = 0; i + x + 1 < len && i - x >= 0; x++) { + if (s[i - x] == s[i + x + 1]) { + left2 = i - x; + right2 = i + x + 1; + } else { + break; + } + } + + return Result{left1, right1, left2, right2}; +} + +string Solution2::longestPalindrome(string s) { + int len = s.length(); + int left = 0, right = 0; + Result result; + for (int i = 0; i < len; i++) { + result = expand(s, i); + if (result.right1 - result.left1 > right - left) { + right = result.right1; + left = result.left1; + } + if (result.right2 - result.left2 > right - left) { + right = result.right2; + left = result.left2; + } + } + return s.substr(left, right - left + 1); +} diff --git a/tests/s0005_longest_palindromic_substring.cpp b/tests/s0005_longest_palindromic_substring.cpp new file mode 100644 index 0000000..e6c7fb7 --- /dev/null +++ b/tests/s0005_longest_palindromic_substring.cpp @@ -0,0 +1,22 @@ +#include "s0005_longest_palindromic_substring.hpp" + +#include + +TEST(Problem5, Case1) { + std::string s("babad"); + std::string r1("bab"); + std::string r2("aba"); + Solution1 solution1; + Solution2 solution2; + EXPECT_TRUE(solution1.longestPalindrome(s) == r1 || solution1.longestPalindrome(s) == r2); + EXPECT_TRUE(solution2.longestPalindrome(s) == r1 || solution2.longestPalindrome(s) == r2); +} + +TEST(Problem5, Case2) { + std::string s("cbbd"); + std::string r("bb"); + Solution1 solution1; + Solution2 solution2; + EXPECT_EQ(r.compare(solution1.longestPalindrome(s)), 0); + EXPECT_EQ(r.compare(solution2.longestPalindrome(s)), 0); +}