LeetCode Hot 100(二) C++版


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,則有一下三種情況:

  1. P[i][j] = P[i - 1][j - 1], if p[j - 1] != *&& (s[i - 1] == p[j - 1] || p[j - 1]== .);
  2. P[i][j] = P[i][j - 2], if p[j - 1] == * and the pattern repeats for 0 times;
  3. 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;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章