第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;
}
};