第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/)
思路:分治法,兩個兩個合併。
/**
* 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;
}
};