LeetCode Top 100 題解
1. 最長迴文子串
給定一個字符串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度爲 1000。
示例 1:
輸入: “babad”
輸出: “bab”
注意: “aba” 也是一個有效答案。
示例 2:
輸入: “cbbd”
輸出: “bb”
迴文串就是正讀反讀都一樣的字符串,最長迴文串就是在一個字符串中最長的迴文串,想要找到迴文串,一眼以每一個字符爲中心,向兩邊擴散來查找回文串,時間複雜度爲O(n2),需要注意的就是奇偶迴文串,對於奇數形式的,從遍歷到的位置爲中心向兩邊擴展;對於偶數形式的,以當前遍歷到的元素和下一個元素爲迴文串的最中間的兩個字符,向兩邊擴展搜索。
class Solution {
public:
void searchPalindrome(string s, int left, int right, int& start, int& max_len)
{
//向兩邊擴散
while (left >= 0 && right < s.size() && s[left] == s[right])
{
left--;
right++;
}
//判斷找到的字符串是否是最長字符串
if (max_len < right - left - 1)
{
start = left + 1;
max_len = right - left - 1;
}
}
string longestPalindrome(string s) {
if (s.size() < 2)
return s;
int l = s.size(), max_len = 0, start = 0;
for (int i = 0; i < l - 1; ++i)
{
searchPalindrome(s, i, i, start, max_len);//奇數形式
searchPalindrome(s, i, i + 1, start, max_len);//偶數形式
}
return s.substr(start,max_len);
}
};
若是不實用子函數,避免函數開銷的話,直接在一個函數中搞定。定義start和max_len,分別表示最長迴文子串的起點和長度,遍歷s中字符時,先判斷剩餘的字符數是否小於等於max_len的一半,若是則表明從當前到末尾到子串是半個迴文串,那麼整個迴文串長度最多也就是max_len,也就是說當前位置之後的字符沒有搜索的意義,直接break即可。否則繼續判斷,利用兩個變量left和right指向當前位置,然後向右遍歷跳過重複項,然後更新max_len和start。
class Solution {
public:
string longestPalindrome(string s) {
if (s.size() < 2)
return s;
int l = s.size(), max_len = 0, start = 0;
for (int i = 0; i < l;)
{
if (l - i <= max_len / 2)
break;
int left = i, right = i;
while (right < l - 1 && s[right + 1] == s[right])//考慮到偶數位迴文串,跳過相同的字符
++right;
i = right + 1;
while (right < l - 1 && left > 0 && s[left - 1] == s[right + 1])
{
++right;
--left;
}
if (max_len < right - left + 1)
{
max_len = right - left + 1;
start = left;
}
}
return s.substr(start,max_len);
}
};
利用動態規劃也可解決這個問題。維護一個二維數組dp,其中
dp[i][j]
表示字符串區間[i,j]
是否爲迴文串,當i = j時,只有一個字符,肯定是迴文串,如果i= j + 1,說明是相鄰字符,此時需要判斷s[i]是否等於s[j],如果i和j不相鄰,即i - j > 2時,除了判斷s[i]和s[j]相等之外,dp[i + 1][j - 1]若爲真,就是迴文串,可得
dp[i,j] = 1 if i == j
= s[i] == s[j] if j = i +1
= s[i] == s[j] && dp[i+1][j-1] if j > i + 1
class Solution {
public:
string longestPalindrome(string s) {
if (s.empty()) return "";
int n = s.size(), dp[n][n] = {0}, left = 0, len = 1;
for (int i = 0; i < n; ++i) {
dp[i][i] = 1;
for (int j = 0; j < i; ++j) {
dp[j][i] = (s[i] == s[j] && (i - j < 2 || dp[j + 1][i - 1]));
if (dp[j][i] && len < i - j + 1) {
len = i - j + 1;
left = j;
}
}
}
return s.substr(left, len);
}
};
馬拉車算法,時間複雜度爲O(n)
class Solution {
public:
string longestPalindrome(string s) {
string t ="$#";
for (int i = 0; i < s.size(); ++i) {
t += s[i];
t += '#';
}
int p[t.size()] = {0}, id = 0, mx = 0, resId = 0, resMx = 0;
for (int i = 1; i < t.size(); ++i) {
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
while (t[i + p[i]] == t[i - p[i]]) ++p[i];
if (mx < i + p[i]) {
mx = i + p[i];
id = i;
}
if (resMx < p[i]) {
resMx = p[i];
resId = i;
}
}
return s.substr((resId - resMx) / 2, resMx - 1);
}
};
2.正則表達式匹配
給你一個字符串 s 和一個字符規律 p,請你來實現一個支持 .
和 *
的正則表達式匹配。
.
匹配任意單個字符
*
匹配零個或多個前面的那一個元素
所謂匹配,是要涵蓋 整個 字符串 s的,而不是部分字符串。
說明:
- s 可能爲空,且只包含從 a-z 的小寫字母。
- p 可能爲空,且只包含從 a-z 的小寫字母,以及字符 . 和 *。
示例 1:
輸入:
s = “aa”
p = “a”
輸出: false
解釋: “a” 無法匹配 “aa” 整個字符串。
示例 2:
輸入:
s = “aa”
p = “a*
”
輸出: true
解釋: 因爲*
代表可以匹配零個或多個前面的那一個元素, 在這裏前面的元素就是a
。因此,字符串 “aa” 可被視爲 ‘a’ 重複了一次。
示例 3:
輸入:
s = “ab”
p = “.*
”
輸出: true
解釋: “.*” 表示可匹配零個或多個(*
)任意字符(.
)。
示例 4:
輸入:
s = “aab”
p = “c*
a*
b”
輸出: true
解釋: 因爲*
表示零個或多個,這裏c
爲 0 個,a
被重複一次。因此可以匹配字符串 “aab”。
示例 5:
輸入:
s = “mississippi”
p = “mis*
is*
p*
.”
輸出: false
用遞歸Recursion解決:若p爲空,若s也爲空,返回true,反之返回false;若p的長度爲1,若s的長度也爲1,且相同或是p爲
.
則返回true,反之返回false;若p的第二個字符不爲*
,若s爲空則返回false,否則判斷首字符是否匹配,且從各自的第二個字符開始調用遞歸函數匹配;若p的第二個字符爲*
,則進行循環:條件爲若s不爲空且首字符匹配(包括p[0]爲點),調用遞歸函數匹配s和去掉前兩個字符的p,若匹配的返回true,否則s去掉字母,繼續進行循環。返回調用 遞歸函數匹配s和去掉前兩個字符的p的結果。
class Solution {
public:
bool isMatch(string s, string p) {
if (p.empty())
{
return s.empty();
}
if (p.size() == 1)
{
return (s.size() == 1 && (s[0] == p[0] || p[0] == '.'));
}
if (p[1] != '*')
{
if (s.empty())
{
return false;
}
return (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p.substr(1));
}
while (!s.empty() && (s[0] == p[0] || p[0] == '.'))
{
if (isMatch(s, p.substr(2)))
return true;
s = s.substr(1);
}
return isMatch(s, p.substr(2));
}
};
更加簡潔的方法:先判斷p是否爲空,若爲空則根據s爲空的情況返回結果。當p的第二個字符爲
*
號時,由於*
前面的字符的個數可以任意,可以爲0,則先用遞歸來調用爲0的狀況。
class Solution {
public:
bool isMatch(string s, string p)
{
if (p.empty())
return s.empty();
if (p.size() > 1 && p[1] == '*')
{
return isMatch(s, p.substr(2)) || (!s.empty() && (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p));
}
else
{
return !s.empty() && (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p.substr(1));
}
}
};
使用DP來解決,定義一個二位的DP數組,dp[i][j]表示s[0,i]和p[0,j]是否match,則有一下三種情況:
- P[i][j] = P[i - 1][j - 1], if p[j - 1] !=
*
&& (s[i - 1]==
p[j - 1] || p[j - 1]==
.
); - P[i][j] = P[i][j - 2], if p[j - 1]
==
*
and the pattern repeats for 0 times; - P[i][j] = P[i - 1][j] && (s[i - 1]
==
p[j - 2] || p[j - 2]==
.
), if p[j - 1]==
*
and the pattern repeats for at least 1 times.
class Solution {
public:
bool isMatch(string s, string p)
{
int m = s.size(), n = p.size();
vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
dp[0][0] = true;
for (int i = 0; i <= m; ++i)
{
for (int j = 1; j <= n; ++j)
{
if (j > 1 && p[j - 1] == '*')
{
dp[i][j] = dp[i][j - 2] || (i > 0 && (s[i - 1] == p[j - 2] || p[j - 2] == '.') && dp[i - 1][j]);
}
else
{
dp[i][j] = i > 0 && dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '.');
}
}
}
return dp[m][n];
}
};
3.盛最多水的容器
給你 n 個非負整數 a1,a2,…,an,每個數代表座標中的一個點 (i, ai) 。在座標內畫 n 條垂直線,垂直線 i 的兩個端點分別爲 (i, ai) 和 (i, 0)。找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。
說明:你不能傾斜容器,且 n 的值至少爲 2。
圖中垂直線代表輸入數組 [1,8,6,2,5,4,8,3,7]。在此情況下,容器能夠容納水(表示爲藍色部分)的最大值爲 49。
定義兩個指針分別指向數組的左右兩邊,然後兩個指針向中間搜索,每移動一次計算值並和結果比較去較大值。
class Solution {
public:
int maxArea(vector<int>& height) {
int res = 0, i = 0, j = height.size() - 1;
while (i < j)
{
res = max(res, min(height[i], height[j]) * (j - i));
height[i] < height[j] ? ++i : --j;
}
return res;
}
};
對上述代碼進行優化,若高度相同則直接移動,不用計算容量。
class Solution {
public:
int maxArea(vector<int>& height)
{
int res = 0, i = 0, j = height.size() - 1;
while (i < j)
{
int h = min(height[i], height[j]);
res = max(res, h * (j - i));
while (i < j && h == height[i]) ++i;
while (i < j && h == height[j]) --j;
}
return res;
}
};