重温hot100-day2
11. 239 滑动窗口最大值
经典单调栈问题。一个deque维护单调栈,front都是最大值,递减存储,因此最大值就是front。但问题就是有个窗口大小,更需要更新最大值。k窗口往右移动,弹出左边的值。因此直接给deque中存储索引i,如果i-front>=k,说明front不在k窗口范围内,需要弹出。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> result;
deque<int> q; //优先队列
for(int i=0; i<nums.size(); i++){
while(!q.empty() && nums[i]>=nums[q.back()]) //保持单调性,左往右是递减的
q.pop_back();
q.push_back(i);
if(i - q.front()>=k) //保证队列右边值在窗口范围内
q.pop_front();
if(i+1>=k) //符合位置就可以读结果了
result.emplace_back(nums[q.front()]);
}
return result;
}
};
12. 76 最小覆盖字串
滑动窗口问题。整体思路就是两个map,一个mapt记录的是目标字符串,maps表示滑动窗口得map,每次对比两个map就好。滑动窗口时,先更新窗口右边界,左边界默认为0,不断更新右边,直到窗口满足条件。
- 窗口满足要求就每次更新结果,结果left和len。就可以得到字符串
- 满足条件基础上开始尝试缩小窗口,因为题目要求最小,因此移动左边界,每次要判断最左边元素是不是在目标mapt中,如果在,需要改变maps,因为会涉及移动窗口是否满足条件。如果不再,那就不用管,说明可以缩小的。
class Solution {
public:
unordered_map<char, int> s_map, t_map;
bool check(){
for(const auto& m:t_map){
if(s_map[m.first] < m.second)
return false;
}
return true;
}
string minWindow(string s, string t) {
for(const auto& m:t)
t_map[m]++;
int l=0, r=-1;
int len = INT_MAX, anl=-1, anr = -1;
cout<<s.size()<<endl;
cout<<int(s.size())<<endl;
while(r<int(s.size())){
if(t_map.find(s[++r]) != t_map.end()) //滑动右边边界
s_map[s[r]]++;
while(check() && l<=r){ //满足条件的字符串,开始滑动左边边界
if(r-l+1 < len){ //长度更小,更新即可
len = r-l+1;
anl = l;
}
if(t_map.find(s[l]) != t_map.end()) //左边字母在目标t中
s_map[s[l]]--; //s—map弹出
l++; //如果上一步左边字母不在t中,smap不需要改变,l++即可。
}
}
return anl==-1 ? string() : s.substr(anl, len);
}
};
13. 53 最大子数组和
解法一:遍历求和,因为要求得最大子数组和,因此如果目前和已经是负数,那就说最终的子数组一定不会有前面的,因为负数会影响后面的子数组之和。因此,需要每次遍历加到和为负。那就清空为0,重新开始求和,就表示更新了子数组的左边界。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int result = INT_MIN;
int add = 0;
for(int i=0; i<nums.size(); i++){
add += nums[i];
if(add>result)
result = add;
if(add<0) //目前子数组和为负,更新左边界。
add = 0;
}
return result;
}
};
解法二,动态规划,每次求和更新dp,dp就在于是求和之后的大,还是本身num大,其实本质也是看之前和是否为负数。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int result = nums[0];
int add;
for(int i=0; i<nums.size(); i++){
add = max(add+nums[i], nums[i]);
result = max(add, result);
}
return result;
}
};
14. 56 合并区间
贪心问题,每次都取最优,先sort排序,就按照左边界从小到大排序了,因此结果得第一个区间左边界可以确定,就在于右边界。合并就是看区间有没有重叠,也就是第一个区间得右边界和第二个区间的左边界。现在结果已经放了第一个区间,那就从第二个区间(i=1)开始遍历,看看i区间的左边界和结果中back(最右边得区间)右边界大小,如果大于,那就说明没重叠,结果back不需要更新,可以确定,将i区间push即可。如果小于,说明有重叠,那么合并之后的右边界取决于,第两个右边界谁大!思路就清晰了。
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end());
vector<vector<int>> result;
result.emplace_back(intervals[0]);
for(int i=1; i<intervals.size(); i++){
if(intervals[i][0] <= result.back()[1])
result.back()[1] = max(result.back()[1], intervals[i][1]);
else{
result.emplace_back(intervals[i]);
}
}
return result;
}
};
15. 238 除自身以外数组的乘积
典型数组前缀和问题,乘积包括两部分,前面乘积和后面乘积,例如,第i处的结果就是i-1之前的乘积和i+1之后的乘积(闭区间),因此求两个数组,一个前缀乘积,一个后缀乘积,就可以得到所有结果
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int len = nums.size();
vector<int> Lnum(len), Rnum(len);
vector<int> result(len);
Lnum[0] = nums[0];
for(int i=1;i<len-1; i++){
Lnum[i] = Lnum[i-1]*nums[i]; //前缀乘积
}
Rnum[len-1] = nums[len-1];
for(int i=len-2; i>=0; i--)
Rnum[i] = Rnum[i+1]*nums[i]; //后缀乘积
for(int i=1;i<len-1;i++){
result[i] = Rnum[i+1] * Lnum[i-1]; //求结果
}
result[0] = Rnum[1]; //两个边界值需要特殊考虑
result[len-1] = Lnum[len-2];
return result;
}
};
16. 41 缺失的第一个数
这题真抽象,非得n时间复杂度和1空间复杂度,第一次做的时候没考虑空间复杂度,零申请了n大小用于存储哈希,思路很简单,就是查每个数据的+1是否存在,不存在,那么i+1就是结果(缺失得第一个正数)
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
map<int,int> ma;
int result = 0;
for(const auto& n :nums){
if(n>0 && ma.find(n)==ma.end())
ma.insert(make_pair(n,1));
}
if(ma.find(1) == ma.end()){
result = 1;
return result;
}
for(const auto& m : ma){
if(ma.find((m.first)+1) == ma.end()){ //找每个数的+1是否存在
result = (m.first)+1;
cout<<m.first<<endl;
break;
}
}
return result;
}
};
虽然能通过,但不符合题目要求的常数空间复杂度,所以需要思考进化版,其实,一定是从正数1开始找,因此从第一个正数与1对比,第二个与2对比,哪个不满足,哪个就是result。不过记得去重。但这样需要先排序 ,时间复杂度又超了
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int result = 1, i=0;
sort(nums.begin(), nums.end());
while(i<nums.size()){ //找到第一个正数
if(nums[i]<=0){
i++;
continue;
}
break;
}
while(i<nums.size()){
if(nums[i]==result){ //对比成功
result++;
while(i+1<nums.size() && nums[i] == nums[i+1]){ //去重剪枝
i++;
}
i++;
continue;
}
return result; //返回没有对比成功的数
}
return result; //都对比成功,result变成+1,返回即可
}
};
就这样吧,这才是正常时间和空间权衡的做法,leetcode要求的,太极限了。
17. 73 矩阵置零
这道题很简单了,如果某个为0,记录下来它的行列号,最后在遍历,如果是之前记录的行列号,那就把值变为0就好。用两个标志数组做记录,一个表示行,一个表示列
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
//vector<vector<bool>> flag(matrix.size(), vector<bool> (matrix[0].size(), false));
vector<bool> row(matrix.size()), col(matrix[0].size());
for(int i=0;i<matrix.size();i++)
for(int j=0;j<matrix[0].size();j++)
if(matrix[i][j] == 0)
row[i] = col[j] = true; //是0,记录两个标记数组
for(int i=0;i<matrix.size();i++)
for(int j=0;j<matrix[0].size();j++){
if(row[i] || col[j]){ //如果满足某个标记数组
matrix[i][j] = 0;
}
}
return;
}
};
18. 54螺旋矩阵
纯纯遍历边界考验,上下左右四个边界值,按照顺时针方向遍历,每次遍历完一个方向,判断是否越界,比如。刚开始从左往右的遍历,说明上面一行遍历完了,因此上界就要往下移动一格,比如从0到1.如果移动后的拆超过了下界,就说明遍历完了。break即可
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> result;
if(matrix.empty())
return result;
int u=0, d=matrix.size()-1, l=0, r=matrix[0].size()-1;
while(true){
for(int i=l;i<=r;i++)
result.push_back(matrix[u][i]);
if(++u>d)
break;
for(int i=u;i<=d;i++)
result.push_back(matrix[i][r]);
if(--r<l)
break;
for(int i=r;i>=l;i--)
result.push_back(matrix[d][i]);
if(--d<u)
break;
for(int i=d;i>=u;i--)
result.push_back(matrix[i][l]);
if(++l>r)
break;
}
return result;
}
};
19. 48 旋转图像
顺时针旋转90°,两种做法。首先就是基本的映射,列过去是行,行对应到n-i-1列.这个自己手推举个例子就明白了。
第二种做法就是变换,先行对称变换,再主对角线变换。代码只写了第一个方法的,因为简单
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
// C++ 这里的 = 拷贝是值拷贝,会得到一个新的数组
auto matrix_new = matrix;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
matrix_new[j][n - i - 1] = matrix[i][j];
}
}
// 这里也是值拷贝
matrix = matrix_new;
}
};
20. 240 搜索二维矩阵
基本遍历时间复杂度m*n,优化的话可以每一行采用二分
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
for(const auto& row:matrix){
auto it = lower_bound(row.begin(), row.end(), target); //二分查找
if(it != row.end() && *it==target)
return true;
}
return false;
}
};
这样就马马虎虎,直到我看到了评论一个神仙做法
于是尝试,只能说,自古网络出大神
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int i = 0, j = matrix[0].size()-1;
while(true){
if(matrix[i][j] == target)
return true;
else if(matrix[i][j]> target){ //遍历“左子树”
j--;
if(j<0) //越界就break
break;
}else{ // 遍历“右子树”
i++;
if(i>=matrix.size())
break;
}
}
return false;
}
};