DAY2积压了两天没发
977 有序数组的平方
一开始只能想到暴力解法,就是先算出所有平方再统一sort
但是这样的时间复杂度有O(NlogN)
(看了一下可以在原数组nums的基础上做修改,这样空间复杂度是O(1))
看了点题解————双指针法
原理就是绝对值最大的肯定在两端其中之一,所以能先找到最大的平方值。
但是看了一下要求输出从小到大,先找到最大的放在哪啊(这里卡了一下,还是菜,没忍住又看了眼题解)
可以在初始化的时候就给vector数组前size个空间置0(先开辟出这些空间,后面可以直接在这些空间上修改值)
明白了,这样空间复杂度是O(N),时间复杂度是O(N)
于是上代码
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int size=nums.size();
int L=0;
int R=size-1;
int k=R;
vector<int> result(size, 0);
while(R>=L){
if(abs(nums[L])>abs(nums[R])){
result[k]=nums[L]*nums[L];
++L;
}
else{
result[k]=nums[R]*nums[R];
--R;
}
--k;
}
return result;
}
};
第一次在while的判定条件里出了点小小的问题,没加等于号导致在相等的时候直接跳出循环,差一位数没存。
第二道题
209 长度最小的子数组
看见题目的第一思路,可以先用sort排个序然后从后往前找
然后就写出了如下的:(错的)
//这是错的
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
sort(nums.begin(),nums.end());
int size=nums.size();
int R=size-1;
int L=R;
int count=nums[R];
while(count<target){
--L;
if(L<0) return 0;
count+=nums[L];
}
return R-L+1;
}
};
(第一次犯了两个特别特别低级的错误:忘写sort了,加上了以后发现位置放反了,干脆直接放在第一句了。)
除去上面两个低级错误,第一个过不去的测试用例出现了:
看了半天寻思这不是七个吗。。。啊原来题目说要连续的!排序完不再连续了!!
寸不己。。。于是重写。。。
首先想到依次以每个元素作为开头往后计数。。。时间复杂度可能不小
遂写暴力法:
C++:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int size=nums.size();
int L;
int R;
int len=size;
for(int i=0;i<size;++i){
int count=nums[i];
L=i;
R=L;
while(count<target){
++R;
if(R>=size){
if(L==0) return 0;
return len;
}
count+=nums[R];
}
len=min(len,R-L+1);
if(len==1) return len;
}
return len;
}
};
蒽。。。时间复杂度很大的一坨,还有一些一时兴起添加的莫名其妙的跳出循环的条件,不过AC了
时间复杂度O(N^2)
看了官方题解,
解法二给出了前缀和+二分法时间复杂度O(NlogN)
遍历复杂度是O(N),二分查找复杂度O(logN)
解法三滑动窗口,时间复杂度O(N)
解法二第一遍没看懂,先来写解法三
设置一个窗口头start和窗口尾end,初始化都指向0位置
end向右移动,直到窗口内的和大于等于target,将start也一步步向右移动,如果窗口内的和小于target了,停止移动start,开始移动end。
记录过程中每次窗口内和大于等于target时的最小窗口长度。
上代码
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int size=nums.size();
int start=0;
int end=0;
//int end=0;
int sum=0;
int len=size+1;
while(end<size){
sum+=nums[end];
while(sum>=target){
len=min(len,end-start+1);
sum-=nums[start];
++start;
}
++end;
}
return len==size+1 ? 0 : len;
}
};
一开始想不出来第一个循环的判断条件应该是什么,总觉得应该同时考虑start和end的位置才能保证遍历的时候不遗漏。
看了一下题解发现可以保证当前窗口下的end不溢出就可以了,如果end溢出了,证明当前start的位置下(无论这个start在哪)都无法满足窗口内的sum大于target,就没必要再移动start了(因为start只能向右移动,sum只会越来越小)就可以跳出循环了。
一个小问题:
sum+=nums[end];
这个语句我一开始写的是
sum+=nums[end++];
执行完这个语句end自动加1,但是第二个while语句中还需要用到end加1之前的值计算len。
老老实实把++end;放最后吧。
来看解法二:前缀法+双指针
本来看到解法二感觉很不好想,时间复杂度也不如解法三,想放弃来着。翻评论区的时候看到一句话:连续数组问题通常就是前缀法和滑窗。行吧,那我把解法二也搞懂。
解法二首先发现了解法一中存在重复计算的问题:
于是把数组中每个位置对应的前缀和都算出来存在数组中,这样计算sum可以直接下标索引和不用遍历。
满足条件的一段数组可能是任何一个位置开头的数组,比如从a位置到b位置。想要计算这一段数组的sum可以直接取b的前缀和与a的前缀和相减,再与target比较。
就记作满足sum[b]-sum[a] >= target的最小a到b的长度。
“
然后 ,我们可以很容易得改良问题为 求s[j] - s[i] >=target ,可是这种做法如果不加改变,就是在前缀和数组上进行类似方法一的暴力枚举,枚举每一个i后面的下标j
最后,我们发现稍作变化,像这种线性的求值问题,联合二分查找可以做到 求 s[j] >=s[i]+target.
”
(以上来自评论区,感觉说的很准确)
而且C++中有库函数lower_bound可以直接进行二分查找。
因此对于sum中每一个sum[i]都可以将其视为sum[a],在整个sum中寻找第一个大于sum[i]+target.的sum[j],如果能找到,可以存j到i的最短长度,遍历结束后返回。
注意:end() 返回的迭代器指向容器末尾之外的位置,因此不能解引用它。在代码中,通常将 end() 与其他迭代器进行比较,以确定是否已遍历完整个容器。
也就是说判断迭代器返回的是否为sum.end()就能判断是否找到
如果能找到,将迭代器返回的与sum.begin()相减就可以得到j的值。
这里有一个确保代码安全的操作:
“迭代器之间的差返回的是一个名为 std::iterator_traits::difference_type 的类型,它可能与 int 不同。显式转换可以确保代码中的数据类型一致性。”
因此相减以后需要强制转换成int型。
static_cast((bound - sums.begin())) 是一种显式类型转换,它将迭代器之间的差转换为 int 类型。
没什么问题了,上代码!
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int size=nums.size();
int len=size+1;
vector<int> sum(size+1,0);
sum[0]=0;
for(int i=1;i<=size;++i){
sum[i]=sum[i-1]+nums[i-1];
}
for(int i=1;i<=size;++i){
int s=target+sum[i-1];
auto it=lower_bound(sum.begin(),sum.end(),s);
if(it!=sum.end()){
len=min(len,static_cast<int>(it-sum.begin())-i+1);
}
}
return len==size+1 ? 0 : len;
}
};
需要注意的一些细节就是sum[0]中存0,表示前0个元素的和为0.
59.螺旋矩阵II
第一次写
想了半天没捋明白。。看题解吧
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> result(n,vector<int>(n,0));
int x=0,y=0;
int offset=1;
int loop=n/2;
int i,j;
int count=1;
while(loop--){
for(i=y;i<n-offset;++i ){
result[x][i]=count++;
}
for(j=x;j<n-offset;++j){
result[j][i]=count++;
}
for( ;i>y;--i){
result[j][i]=count++;
}
for( ;j>x;--j){
result[j][y]=count++;
}
++x;
++y;
++offset;
}
if(n%2!=0) result[n/2][n/2]=count;
return result;
}
};
这道题也没有很复杂的算法,就是在考察编码能力和逻辑
我和题解的思路差距在于,题解会有一个“找到循环不变量”的意识,而我只是一直在想每次循环情况都不一样,怎么写循环条件。
我一直想写出一个循环适用于每一条边,不管是从上到下还是从左到右从右到左,然而这样要考虑的因素就很复杂。
题解直接把每次绕圈的四条边都按顺序写出来了,很清晰。
还是菜就多练吧、、、不能再摆了