【笔试练习题】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;
    }
};

 

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