DAY2:有序数组平方+滑动窗口+螺旋矩阵

1.有序数组平方和

1.暴力解法

主要思路:

平方之后直接排序

class solution{
public:
    vector<int> sortedSquares(vector<int>& A){
        for(int i=0;i<A.size();i++){
            A[i] *= A[i];//平方运算
        }
        sort(A.begin(),A.end());//快速排序库函数
       return A; 
    }
}

快速排序这里使用了库函数,其实最好不要用,自己实现快速排序代码。

补充部分见双指针法实现快速排序博客。

时间复杂度

​ 这里暴力解法用到的排序算法是快速排序,因此时间复杂度是平方计算本身n+快速排序的复杂度O(nlog n)。即为 O(n + nlogn), 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlog n)

补充:快速排序的时间复杂度

在这里插入图片描述
快速排序,都知道快速排序是O(nlogn),但是当数据已经有序情况下,快速排序的时间复杂度是O(n^2) 的,所以严格从大O的定义来讲,快速排序的时间复杂度应该是O(n^2)

但是我们依然说快速排序是O(nlogn)的时间复杂度,这个就是业内的一个默认规定,这里说的O代表的就是一般情况,而不是严格的上界
在这里插入图片描述
我们主要关心的还是一般情况下的数据形式。

面试中说道算法的时间复杂度是多少指的都是一般情况。但是如果面试官和我们深入探讨一个算法的实现以及性能的时候,就要时刻想着数据用例的不一样,时间复杂度也是不同的,这一点是一定要注意的。

2.双指针法

主要思路:

由于本题目nums 已按 非递减顺序 排序,因此,平方的最大值只有可能在数组的最左侧或者最右侧,没有在中间的可能性

因此,使用左右边缘的双指针法,左侧和右侧向中间逼近的同时进行对比。

建立一个新的数组,如果平方和左侧>右侧,那么新的数组中最大的在左侧。如果平方和右侧>左侧,那么新数组中最大的是右侧。

class solution{
public:
    vector<int> sortedSquares(vector<int>& nums){
        //int leftIndex=0;
        int rightIndex=nums.size()-1;
        vector<int>numsSort(nums.size(),0);
        int newIndex = nums.size()-1;
        for(int leftIndex=0;leftIndex<=rightIndex){ //注意这里不要用leftIndex++!后面已经+过了
            //如果平方和左侧>右侧,那么新的数组中最大的是左侧
            if(nums[leftIndex]*nums[leftIndex]*>nums[rightIndex]*nums[rightIndex]){
                numsSort[newIndex--]=nums[leftIndex]*nums[leftIndex];
                leftIndex++;
            }
            //如果平方和右侧>右侧,那么新的数组中最大的是右侧
            else{
                numsSort[newIndex--]=nums[rightIndex]*nums[rightIndex];
                rightIndex--;
            }

        }
        return numsSort;       
}
}

2.长度最小的子数组:滑动窗口法

1.暴力解法

主要思路:

直接定义两个for循环,一个for循环搜索起始位置,一个for循环搜索终止位置,然后判断大于等于S的最小长度是多少。

**注意:本题目中是正整数数组,一旦题目数组中有负数,就不能使用滑动窗口了,只能用暴力解法。**因此暴力解法也需要掌握。

  • 加入滑动窗口中有负数怎么办?
    如果有负数的话感觉也不能用滑动窗口了,因为有负数的话无论你收缩还是扩张窗口,你里面的值的总和都可能增加或减少,就不像之前收缩一定变小,扩张一定变大,一切就变得不可控了。如果要 cover 所有的情况,那每次 left 都要缩到 right,那就退化为暴力了哈哈。
class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX; // 最终的结果
        int sum = 0; // 子序列的数值之和
        int subLength = 0; // 子序列的长度
        for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i
            sum = 0;
            for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j
                sum += nums[j];
                if (sum >= s) { // 一旦发现子序列和超过了s,更新result
                    subLength = j - i + 1; // 取子序列的长度
                    result = result < subLength ? result : subLength;
                    break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
                }
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

2.滑动窗口

主要思路:

滑动窗口其实也属于双指针的做法,两个指针操作,但是我们取的是两个指针中间的集合

用一个for循环解决两个for循环需要做的事情。

滑动窗口:本质是满足了单调性,即左右指针只会往一个方向走且不会回头。收缩的本质即去掉不再需要的元素。也就是做题我们可以先固定移动右指针,判断条件是否可以收缩左指针算范围。

注意问题:

1.唯一一个for循环里面的j,表示的是滑动窗口的起始位置还是终止位置?

如果j是起始位置,终止位置不停向后遍历,得到所有长度>=S的集合再进行大小对比,那么就和暴力解法没有区别了。因此,滑动窗口for循环里面的j指向的一定是终止位置起始位置使用动态移动的策略,才能只用一个for循环来完成需求。

2.如何移动起始位置?

终止位置确定,终止位置向前的集合里面的元素和如果>=S的话,说明这是符合条件的集合,那么我们收集其长度之后,起始位置就可以向后移动了。(缩小目前的集合,来看下一个集合是不是符合条件。)

即为,集合元素和>=S的时候,再去移动起始位置

伪代码
//终止位置一开始从0开始

int i=0; //起始位置
for(j=0,j<=nums.size(),j++){
    //收集终止位置指向的元素
    sum+=nums[j]; 
    
    //下面这里是写if还是写while?
    //如果是if的话,只判断一次,循环就结束了,而滑动窗口的起始位置需要持续的向后移动。
    //所以这里必须用while
    
    while(sum>=s){ //如果收集的和满足基本条件
        subL=j-i+1; //区间长度
        result = min(result,subL); //result持续更新,符合最小长度
        //result初始为最大值,只有取最大值才能不断更新
        
        //开始移动起始位置
        sums = sums-nums[i];//更新当前窗口的和
        i++; //起始位置后移    
    }
   return result;  //	>=s的最短长度
    
}

代码扩展:

class solution{
public:
    int minSubArrayLen(int s,vector<int>&nums){
        int result = INT32_MAX; //result先取最大值,因为不知道固定值的大小范围
        int sum=0;
        int i=0;
        int subLength=0;  //滑动窗口的长度
        
        for(int j=0;j<nums.size();j++){  
            sum+=nums[j]; 
            //此处使用while,开始确保窗口的滑动
            while(sum>=s){
                subLength = j-i+1; //更新窗口的长度
                //将最新的长度与result结果比较,取更小的,result初始值是最大值
                result = result < subLength ? result : subLength;
                
                //此时窗口左边界左移,并且sums更新
                sum = sum-nums[i]; 
                i++;
            }
        }
        return result==INT32_MAX ? 0:result; 
        //注意这里不能返回subLength!因为result才是更小的那一个
        //如果while循环从头到尾都没执行,应返回0而不是result,因此在这里做result的判断,因为是条件判断,所以需要用==
        
        
    }
}
注意点:

1.sums>=s的判断条件必须用while而不是If。为了保证滑动窗口大小持续向后移动。

2.三元操作符:如果 result 小于 subLength,则返回 result,否则返回 subLength。三元操作符用法补充在后面。

3.滑动窗口的左边界是一直在滑动的,只要满足sum>=s的条件,左边界就会一直滑动,一直滑动到不满足为止;最后选出的result, 是滑动整个过程中所有length的最小值。

4.这个题目给的条件是正整数数组,如果第一轮,所有值加起来都没有大于目标值,那滑动也就没有意义了,所以只有当前面出现了和大于目标值的情况才滑动。 看看后面还有没有,到下一个满足大于目标值再滑动,这样下去

三元操作符用法:

在C++中,三元操作符也称为条件操作符,它是一种简洁的替代if-else语句的方式。它的一般形式如下:

condition ? expression_if_true : expression_if_false;

这里的condition是一个布尔表达式,expression_if_true是在conditiontrue时执行的表达式,expression_if_false是在conditionfalse时执行的表达式。

例如,假设我们有两个整数ab,我们希望获取两者之间的最大值。我们可以使用三元操作符来实现这一点:

cppCopy codeint a = 5;
int b = 10;
int max = (a > b) ? a : b;  // max 将被赋值为 10

在这个例子中,a > b就是我们的条件,如果它为true(也就是说,如果a大于b),那么max将被赋值为a,否则max将被赋值为b

例如本题目中:

return result==INT32_MAX? 0:result; //利用三元操作符选择返回值

需要注意的是,三元操作符的优先级较低,因此在复杂的表达式中使用它时,可能需要使用括号来确保正确的执行顺序。

时间复杂度

本题目的时间复杂度是O(n)。、

为什么这道题既有for又有while,时间复杂度还是O(n)?

时间复杂度并不是看有几个for,while。是看每个元素被操作了几次。每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

3.螺旋矩阵

注意点

边界条件非常多,处理边界容易出问题

和二分法一样,需要遵循循环不变量的原则

不变量:对每一条边的处理规则,每一条边保持同样的左闭右开/左闭右闭

方法一:自定义上下边界

题解链接:Spiral Matrix II (模拟法,设定边界,代码简短清晰) - 螺旋矩阵 II - 力扣(LeetCode)

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        int num = 1;
        int left = 0, top = 0, right = n - 1, bottom = n - 1;

        //初始化数组
        vector<vector<int>> res(n,vector<int>(n));
        while (num <= n*n ) {

            //left to right
            for (int i = left; i <= right; ++i) res[top][i] = num++;
            ++top;

            //top to bottom
            for (int i = top; i <= bottom; ++i) res[i][right] = num++;
            --right;

            //right to left
            for (int i = right; i >= left; --i) res[bottom][i] = num++;
            --bottom;

            //bottom to top
            for (int i = bottom; i >= top; --i) res[i][left] = num++;
            ++left;
        }
        return res;
    }
};

方法二:判断方向

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> ans(n, vector<int>(n));
        int t = 0;  // 初始方向
        int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};   // 四个方向
        for (int i = 0, j = 0, k = 1; k <= n * n; ++k) {
            ans[i][j] = k;
            int x = i + dx[t], y = j + dy[t];   // 计算下个方向
            // 判断下个点是否合理,不合理就改方向
            if (x < 0 || x >= n || y < 0 || y >= n || ans[x][y] != 0) t = (t + 1) % 4;      
            i += dx[t];
            j += dy[t];
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值