【筆試練習題】leetcode前100道題裏面的hard題解法(更新到第45題)

第4題(2個排序數組的中位數)

找到兩個已排序數組的中位數(https://leetcode.com/problems/median-of-two-sorted-arrays/

解法一:

合併列表,再排序,時間複雜度爲O(n+m);

解法二:

① 在nums1和nums2中各找到一個數,保證合併之後的數組中nums1[k_1]與nums2[k_2]相鄰,且nums[k_1] < nums[k_2]

int k_1 = nums1.size() / 2;
vector<int>::iterator it2 = upper_bound(nums2.begin(), nums2.end(), nums1[k_1]);
int k_2 = it2 - nums2.begin();
vector<int>::iterator it1 = lower_bound(nums1.begin() + k_1, nums1.end(), nums2[k_2]);
k_1 = it1 - nums1.begin() - 1;

② 遞歸查找

時間複雜度最好O(log(n + m))(log(n)*log(n+m))

代碼:

#include <vector>
#include <iostream>
#include<algorithm>
using namespace std;
class Solution {
public:
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		if (nums1.size() + nums2.size() & 1) { // 是奇數
			return findK(nums1, nums2, (nums1.size() + nums2.size()) / 2);
		}
		else {
			double m1 = findK(nums1, nums2, (nums1.size() + nums2.size()) / 2);
			double m2 = findK(nums1, nums2, (nums1.size() + nums2.size()) / 2 - 1);
			return (m1 + m2) / 2;
		}
	}
private:
	// 找到nums1和nums2合併之後的vector中序號爲k的數(序號從0開始)
	int findK(vector<int>& nums1, vector<int>& nums2, int k) {
		if (nums1.empty()) {
			return nums2[k];
		}
		if (nums2.empty()) {
			return nums1[k];
		}
		int k_1 = nums1.size() / 2;
		vector<int>::iterator it2 = upper_bound(nums2.begin(), nums2.end(), nums1[k_1]);
		int k_2 = it2 - nums2.begin();
		if (k_2 == nums2.size()) {
			// nums1[k_1]是分界線,nums2[k_2]是分界線
			if (k_1 + k_2 == k) {
				return nums1[k_1];
			}
			else if (k_1 + k_2 > k) {
				vector<int> nums1_new(nums1.begin(), nums1.begin() + k_1);
				return findK(nums1_new, nums2, k);
			}
			else {
				return nums1[k - k_2];
			}
		}
		// nums1[k_1]是分界線,nums2[k_2]是分界線,排序之後的數組:nums1[k_1]與nums2[k_2]相鄰,且nums1[k_1]更小
		vector<int>::iterator it1 = lower_bound(nums1.begin() + k_1, nums1.end(), nums2[k_2]);
		k_1 = it1 - nums1.begin() - 1;
		if (k_1 + k_2 + 1 == k) {
			return nums2[k_2];
		}
		else if (k_1 + k_2 == k) {
			return nums1[k_1];
		}
		else if (k_1 + k_2 + 1 > k) {
			vector<int> nums1_new(nums1.begin(), nums1.begin() + k_1);
			vector<int> nums2_new(nums2.begin(), nums2.begin() + k_2);
			return findK(nums1_new, nums2_new, k);
		}
		else {
			vector<int> nums1_new(nums1.begin() + k_1 + 1, nums1.end());
			vector<int> nums2_new(nums2.begin() + k_2 + 1, nums2.end());
			return findK(nums1_new, nums2_new, k - (k_1 + k_2 + 2));
		}
	}
};

代碼實測在leetcode上跑出來解法二並沒有比解法一快。

解法三:

推薦解法,代碼是一個博客裏的:https://segmentfault.com/a/1190000015034975?utm_source=tag-newest

代碼:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();
        int l = (m + n + 1) >> 1;
        int r = (m + n + 2) >> 1;
        int* ln = m > 0 ? &nums1[0] : nullptr;
        int* rn = n > 0 ? &nums2[0] : nullptr;
        return ( getKth(ln, m, rn, n, l) + getKth(ln, m, rn, n, r) ) / 2.0;
    }
    int getKth(int* ln, int m, int* rn, int n, int k) {
        if (m > n) {
            return getKth(rn, n, ln, m, k);
        }
        if (m == 0) {
            return rn[k - 1];
        }
        if (k == 1) {
            return min(ln[0], rn[0]);
        }
        int i = min(m, k / 2), j = min(n, k / 2);
        if (ln[i - 1] > rn [j - 1]) {
            return getKth(ln, m, rn + j, n - j, k - j);
        } else {
            return getKth(ln + i, m - i, rn, n, k - i);
        }
    }
};

第10題(正則表達式)

正則匹配(https://leetcode.com/problems/regular-expression-matching/

方法一:使用C++<regix>庫中的正則類

代碼:

class Solution {
public:
    bool isMatch(string s, string p) {
        // 使用正則表達式直接求解
        if(s.empty())
        {
            if(isMatchEmpty(p))
            {
                return true;
            }
            else return false;
        }
        regex reg(p);
        smatch r;
        if(regex_match(s, reg))
            return true;
        else return false;
    }
private:
    bool isMatchEmpty(string p)
    {
        if(p.empty())
            return true;
        if( (p.length()==1) || (p[1]!='*'))
            return false;
        return isMatchEmpty( p.substr(2,p.length()-2) );
    }
};

但是這個解法運行的很慢,需要144ms,動態規劃的解法只需要約20ms

方法二:動態規劃

代碼(別人實現的):

class Solution {
public:
    bool isMatch(string s, string p) {
        /**
         * f[i][j]: if s[0..i-1] matches p[0..j-1]
         * if p[j - 1] != '*'
         *      f[i][j] = f[i - 1][j - 1] && s[i - 1] == p[j - 1]
         * if p[j - 1] == '*', denote p[j - 2] with x
         *      f[i][j] is true iff any of the following is true
         *      1) "x*" repeats 0 time and matches empty: f[i][j - 2]
         *      2) "x*" repeats >= 1 times and matches "x*x": s[i - 1] == x && f[i - 1][j]
         * '.' matches any single character
         */
        int m = s.size(), n = p.size();
        vector<vector<bool>> f(m + 1, vector<bool>(n + 1, false));
        
        f[0][0] = true;
        for (int i = 1; i <= m; i++)
            f[i][0] = false;
        // p[0.., j - 3, j - 2, j - 1] matches empty iff p[j - 1] is '*' and p[0..j - 3] matches empty
        for (int j = 1; j <= n; j++)
            f[0][j] = j > 1 && '*' == p[j - 1] && f[0][j - 2];
        
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
                if (p[j - 1] != '*')
                    f[i][j] = f[i - 1][j - 1] && (s[i - 1] == p[j - 1] || '.' == p[j - 1]);
                else
                    // p[0] cannot be '*' so no need to check "j > 1" here
                    f[i][j] = f[i][j - 2] || (s[i - 1] == p[j - 2] || '.' == p[j - 2]) && f[i - 1][j];
        
        return f[m][n];
    }
};

第23題(合併K個有序鏈表)

合併K個有序鏈表(https://leetcode.com/problems/merge-k-sorted-lists/

思路:分治法,兩個兩個合併。

Divide_and_Conquer

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size() == 0)
        {
            return NULL;
        }
        if(lists.size() == 1)
            return lists[0];
        if(lists.size() == 2)
            return merge2Lists(lists[0], lists[1]);
        vector<ListNode*> lists1;
        vector<ListNode*> lists2;
        lists1.assign(lists.begin(), lists.begin() + (lists.size() + 1) / 2);
        lists2.assign(lists.begin() + (lists.size() + 1) / 2, lists.end());
        return merge2Lists(mergeKLists(lists1), mergeKLists(lists2));
    }
    ListNode* merge2Lists(ListNode* l1, ListNode* l2)
    {
        ListNode head(0);
        ListNode* p = &head;
        while(l1 != NULL || l2 != NULL)
        {
            if(l1 == NULL || (l2 != NULL && l2->val < l1->val)) 
            {
                p->next = l2;
                l2 = l2 ->next;
            }
            else //(l2 == NULL || (l1 != NULL && l1->val < l2->val))(l1 && (!l2 || l1->val < l2->val))
            {
                p->next = l1;
                l1 = l1 ->next;
            }
            p = p->next;
        }
        return head.next;
    }
};

第25題(每K個節點逆轉鏈表)

逆轉鏈表每k個節點(https://leetcode.com/problems/reverse-nodes-in-k-group/submissions/

解法:高贊答案——先遞歸地實現後續每k個節點的反轉,在實現一開始的k個節點的反轉

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        // 參考討論中高人氣的java解法
        ListNode* p = head;
        int count = 0;
        while(p != NULL && count != k)
        {
            p = p->next;
            count++;
        }
        if(count != k)    // 如果不足k個,直接返回
            return head;
        p = reverseKGroup(p, k);  // 傳入的是第k+1個節點
        while(count > 0)
        {
            count--;
            ListNode* tmp = head->next; 
            head->next = p;
            p = head;
            head = tmp;
        }
        head = p;
        return head;
    }
};

第30題(尋找由集合中所有元素組成的子串)

https://leetcode.com/problems/substring-with-concatenation-of-all-words/

給一個字符串和一個字符串集合(集合中所有的字符都一樣長(重要)),這題需要考慮的特殊情況比較多,不容易一次通過。

代碼:

class Solution {
public:
    vector<int> findSubstring(string S, vector<string> &L) {
        vector<int> ans;
        if(S.empty() || L.empty()){
            return ans;
        }
        unordered_map<string, int> m_L0;
        for(int i = 0; i < L.size(); ++i){
            if(m_L0.count(L[i]) == 0){
                m_L0.insert(pair<string, int> (L[i], 1));
            }
            else{
                m_L0[L[i]]++;
            }
        }
        unordered_map<string, int> m_L(m_L0); // m_L0存做備份
        int L_len = L[0].size(); // 由於L中每個單詞都一樣長,因此記錄該長度
        for(int i = 0; i < L_len; ++i){ // 開始匹配的單詞不一定是第0個,也可能是第1個、第2個
            int S_i = i;
            int cnt = L.size();
            int S_j = 0;
            bool is_find = false;
            for(auto it = m_L0.begin(); it != m_L0.end(); ++it){ // 重新初始化map
                m_L[it->first] = it->second;
            }
            while(S_i + L_len * L.size() <= S.size()){
                while(cnt > 0 && S_i + S_j < S.size()){
                    unordered_map<string, int>::iterator it = m_L.find(S.substr(S_i + S_j, L_len));
                    if(it == m_L.end() || it->second == 0){ // 找不到匹配的單詞
                        if(it == m_L.end())
                            S_i = S_i + S_j;
                        break;
                    }
                    else{
                        it->second--;
                        cnt--;
                        if(cnt == 0){ // 如果找全了
                            ans.push_back(S_i);
                            is_find = true;
                            break;
                        }
                    }
                    S_j += L_len;
                }
                if(is_find){ // 由於找到終止了循環
                    m_L[S.substr(S_i, L_len)]++;
                    S_i += L_len;
                    cnt++;
                    is_find = false;
                }
                else{ // 由於沒有找到終止了循環
                    S_i += L_len;
                    S_j = 0;
                    cnt = L.size();
                    for(auto it = m_L0.begin(); it != m_L0.end(); ++it){ // 重新初始化map
                        m_L[it->first] = it->second;
                    }
                }
            }
        }
        sort(ans.begin(), ans.end());
        return ans;
    }
};
   

第32題 最長的匹配括號數

https://leetcode.com/problems/longest-valid-parentheses/

這題leetcode解析裏面有詳細的解法。

這篇博客裏有詳細的解釋:https://www.cnblogs.com/ilovezyg/p/6418106.html

方法一、暴力運算

時間複雜度:O(n^3)

方法二、動態規劃

核心表達式:

定義f[i]表示以s[i]結尾的LVP的長度

class Solution {
public:
    int longestValidParentheses(string s) {
        int n = s.size();
        if (n == 0) return 0;
        
        int ret = 0;
        int f[n];
        f[0] = 0;
        for (int i = 1; i < n; ++i) {
            f[i] = 0;
            if (s[i] == '(') {
                f[i] = 0;
            } else {
                int idx = i - f[i-1] - 1;
                if (idx >= 0 && s[idx] == '(') {  // detect "...((..))"
                    f[i] = f[i-1] + 2;
                    if (idx - 1 >= 0) f[i] += f[idx-1];
                }
            }
            ret = max(ret, f[i]);
        }
        
        return ret;
    }
};

方法三、使用棧:

class Solution {
public:
    int longestValidParentheses(string s) {
        int n = s.size();
        if (n == 0) return 0;
        
        int ret = 0;
        stack<int> stk;  // to store the index!
        for (int i = 0; i < n; ++i) {
            if (s[i] == ')' && !stk.empty() && s[stk.top()] == '(') {
                stk.pop();
                if (stk.empty()) ret = max(ret, i + 1);
                else ret = max(ret, i - stk.top()); // skt.top()記錄的是上一個沒有配對的括號(包括左括號和右括號)
            } else {
                stk.push(i);
            }
        }
        
        return ret;
    }
};

第37題 數獨的解法

回溯法

代碼:

class Solution {
public:
    void solveSudoku(vector<vector<char>>& board) {
       if(board.size()<9)
           return; 
        solve(board);
    }
    
    bool solve(vector<vector<char>>& board)
    {
        for(int i = 0; i < board.size(); i++){
            for(int j = 0; j < board[0].size(); j++){
                if(board[i][j] == '.'){
                    for(char c = '1'; c <= '9'; c++){   // try 1-9
                        if(isValid(board, i, j, c)){
                            board[i][j] = c;
                            if(solve(board))
                                return true;
                            else{
                                board[i][j] = '.'; // otherwise go back
                            }
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }
    
    bool isValid(vector<vector<char>>& board, int row, int col, char c) {
        for(int i=0; i < 9; i++){
            if(board[i][col] == c) return false;
            if(board[row][i] == c) return false;
            if(board[3*(row/3) + i/3][3*(col/3) + i%3] == c) return false;
        }
        return true;
    }
};

Grandyang博客上所寫的:“這道求解數獨的題是在之前那道 Valid Sudoku 驗證數獨的基礎上的延伸,之前那道題讓我們驗證給定的數組是否爲數獨數組,這道讓我們求解數獨數組,跟此題類似的有 Permutations 全排列Combinations 組合項 N-Queens N皇后問題等等,其中尤其是跟 N-Queens N皇后問題的解題思路及其相似,對於每個需要填數字的格子帶入1到9,每代入一個數字都判定其是否合法,如果合法就繼續下一次遞歸,結束時把數字設回'.',判斷新加入的數字是否合法時,只需要判定當前數字是否合法,不需要判定這個數組是否爲數獨數組,因爲之前加進的數字都是合法的,這樣可以使程序更加高效一些。”

參考資料:https://www.cnblogs.com/grandyang/p/4421852.html

 

第41題 第一個缺失的最小正整數

用了桶排序的思想,非常巧妙:

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        vector<int> temp(nums.size() + 1, 0);
        for(int i = 0; i < nums.size(); i++)
        {
            if(nums[i] > 0 &&   nums[i] <= nums.size()){
                temp[nums[i]]++;
            }
        }
        for(int i = 1; i < temp.size(); i++)
        {
            if(temp[i] == 0)
                return i;
        }
        return temp.size();
    }
};

第42題 積水多少

這題有詳細的解析:https://leetcode.com/problems/trapping-rain-water/solution/

方法一、滑動窗口法:

class Solution {
public:
    int trap(vector<int>& height) {
        if(height.size()<3)
            return 0;
        int ans = 0;
        int left = 0;
        int i = 1;
        while(i < height.size())
        {
            // 上升
            if(i<height.size() && height[i-1] <= height[i])
            {
                i++;
                left++;
                continue;
            }
            //  開始下降 height[i-1] > height[i], left = i-1
            int max = height[i];
            int right = i;
            while(i < height.size() && height[i] < height[left])
            {
                if(height[i] > max)
                {
                    max = height[i];
                    right = i;
                }
                i++;
            }
            // 找到最後高的作爲右邊,或者比left高的(一樣高的)作爲右邊,左右左右算
            if(i >= height.size())
            {
                int summ = 0;
                for(int j = left + 1; j < right; j++)
                {
                    summ += height[j];
                }
                ans += height[right] * (right - left - 1) - summ;
                left = right;
                i = left + 1;
            }
            else{
                int summ = 0;
                right = i;
                for(int j = left + 1; j < right; j++)
                {
                    summ += height[j];
                }
                ans += height[left] * (right - left - 1) - summ;
                left = right;
                i = left + 1;
            }
        }
        return ans;
    }
};

方法二、數學方法,非常neat:

class Solution {
public:
    int trap(vector<int>& height)
    {
	    if(height.size() < 3)
		    return 0;
        int ans = 0;
        int size = height.size();
        vector<int> left_max(size), right_max(size);
        left_max[0] = height[0];
        for (int i = 1; i < size; i++) {
            left_max[i] = max(height[i], left_max[i - 1]);
        }
        right_max[size - 1] = height[size - 1];
        for (int i = size - 2; i >= 0; i--) {
            right_max[i] = max(height[i], right_max[i + 1]);
        }
        for (int i = 1; i < size - 1; i++) {
            ans += min(left_max[i], right_max[i]) - height[i];
        }
        return ans;
    }
};

第44題 字符串智能匹配

和第十題正則匹配有類似的地方,但不完全相同,都使用了動態規劃的算法

參考資料:https://www.cnblogs.com/yuzhangcmu/p/4116153.html

如果p.charAt(i)=='*','*'可以選擇匹配0個字符,此時flag[i][j]=flag[i-1][j];可以選擇匹配1個字符,此時flag[i][j]=flag[i-1][j-1];……所以,

flag[i][j]=flag[i-1][j]||flag[i-1][j-1]||……||flag[i-1][0]。

但是上面的公式可以化簡,當p.charAt(i)=='*'時,有

flag[i][j-1]=flag[i-1][j-1]||flag[i-1][j-2]||……||flag[i-1][0]

第45題 跳躍遊戲II

從一個數組頭部跳到尾部最多需要幾步?https://leetcode.com/problems/jump-game-ii/

經分析,可以採用貪心的思想:

class Solution {
public:
    int jump(vector<int>& nums) {
        int target = nums.size()-1;
        int i = 0;
        if(i==target) return 0;
        int cnt = 0;
        while(i + nums[i] < target)   // 位於位置i, 不可能跳到位置target, 則跳到下一步儘量能跳遠點的位置
        {
            int max = 0;
            int jump = 1;
            for(int j = 1; j<=nums[i]; j++)
            {
                if(max < j+nums[i+j])
                {
                    jump = j;
                    max = j+nums[i+j];
                }
            }
            i += jump;
            cnt++;
        }
        return cnt+1;
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章