代码随想录训练营第二天|977.有序数组的平方 、209.长度最小的子数组 、59.螺旋矩阵II

思路:双指针法

 对于有正有负的数值平方排序,无论是先取绝对值排序后平方,还是平方之后排序,直接排序这一步骤的时间复杂度均可视为O(nlogn)。减少时间复杂度的思路在于,利用数组的有序特性,从数组数值的两端(平方最大处)开始,使用双指针法对于数值的平方或绝对值进行比较。

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int size = nums.size();
        int leftIndex = 0;
        int rightIndex = size-1;
        vector<int> result(size, 0);
        while (leftIndex <= rightIndex){
            if (abs(nums[leftIndex]) >= abs(nums[rightIndex])){
                result[size-1]=nums[leftIndex]*nums[leftIndex];
                leftIndex++;
            }
            else{
                result[size-1]=nums[rightIndex]*nums[rightIndex];
                rightIndex--;
            }
            size--;
        }
        return result;
    }
};

时间复杂度为O(n),比起暴力排序的时间复杂度提升很多。

基本语法:动态数组的初始化

        vector<int> result(size, 0);
  • 在局部作用域中(如在函数体内),直接声明一个引用而没有初始化是非法的。这会导致编译错误,因为result引用没有绑定到任何实际的vector<int>对象。
  • 当创建一个新的vector<int>对象时,可以绑定一个现有对象,或者进行初始化。例如,上述代码创建了一个大小为sizeresult对象,且所有元素都被初始化为0

 思路:滑动窗口(双指针)

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。

但两个for循环时间复杂度太高, 如何使用一个for循环遍历可能的窗口呢?

只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置。

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口:满足其和 ≥ s 的长度最小的连续子数组

窗口的起始位置如何移动:如果当前窗口的值大于等于s了,起始位置前移从而缩小窗口;

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引

滑动窗口的精妙之处在于:固定结束位置后,对于已经确定不满足条件的窗口不再进行缩放的遍历,减少了可能的子序列的起始位置,从而将O(n^2)暴力解法降为O(n)。 

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int leftIndex = 0;
        int sum = 0;
        int result = INT32_MAX;
        int subLength = 0; // 滑动窗口的长度
        for (int rightIndex = 0; rightIndex<nums.size(); rightIndex++){
            sum += nums[rightIndex];
            while (sum >= target){
                subLength = rightIndex - leftIndex + 1; // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[leftIndex];
                leftIndex++;
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};

要注意在进行对于窗口求和sum的计算时,在for、while循环里跟随进行即可。不要使用while循环单独运算,会增加复杂度和出错率。

基本语法:比较算法&INT32_MAX

1.比较运算:

    result = result < subLength ? result : subLength;

这是一个比较运算,检查result是否小于subLength,相当于以下if-else语句:

    if (result < subLength) {
        result = result;
    } else {
        result = subLength;
    }

2.INT32_MAX:

        int result = INT32_MAX;

在C和C++中,INT32_MAX是一个预定义的宏,它定义在<limits.h>(C)或<climits>(C++)头文件中。INT32_MAX表示32位有符号整数能够表示的最大值。在大多数现代系统上,这通常是2147483647,也就是2^31 - 1

作为初始值:在寻找最小值的算法中,初始化一个变量为INT32_MAX,实际上是在将result设置为int类型所能表示的最大值,随着算法的执行任何小于INT32_MAXint值都将成功更新这个变量。

要点:

  • 坚持循环不变量原则
  • 设置填充时的横纵坐标i j,并按此设置循环条件,使用res[i][j]填充,一定程度上简化了算法
  • 要注意循环条件,包括循环不变原则、数组下标从0开始
  • 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
        // 后面使用这个二维数组就和正常数组一样,使用res[i][j]即可
        int dif = 0;
        int count = 1;
        int i,j;
        while (dif*2<n){
            // 保持左闭右开原则不变,按照横坐标为i、纵坐标为j填充
            // 设置填充时的横纵坐标ij,一定程度上简化了算法,要注意循环条件
            i = dif;
            j = dif;
            // 从左到右
            for (j; j<n-dif-1; j++){
                res[i][j] = count++;
            }
            // 从上到下
            for (i; i<n-dif-1; i++){
                res[i][j] = count++;
            }
            // 从右到左
            for (j; j>dif; j--){
                res[i][j] = count++;
            }
            // 从下到上
            for (i; i>dif; i--){
                res[i][j] = count++;
            }
            dif++;
        }
        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2 == 1) {
            res[n/2][n/2] = count;
            // n/2即取除法后商的整数位
        }
        return res;
    }
};

 基本语法:二维vector数组

vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
  1. vector<vector<int>>:这是嵌套vector的类型声明,意味着创建一个vector,其元素也是vector<int>。这实质上创建了一个可以动态调整大小的二维数组。

  2. res(n, vector<int>(n, 0)):这是使用vector构造函数来初始化res。这里的构造函数有两个参数:第一个参数n表示外层vector的大小,即有nvector<int>。第二个参数是另一个vector<int>,它本身由n个元素组成,每个元素都初始化为0

  3. vector<int>(n, 0):这是内部vector<int>的构造函数调用,它创建了一个大小为nvector<int>,并且每个元素都被初始化为0

数组总结

理论基础

数组是存放在连续内存空间上的相同类型数据的集合。 

  • 数组下标都是从0开始的
  • 数组内存空间的地址是连续的
  • 数组的元素是不能删的,只能覆盖

Java的二维数组在内存中不是 3*4 的连续地址空间,而是四条连续的地址空间组成 

经典方法 

二分法

  • 暴力解法时间复杂度:O(n)
  • 二分法时间复杂度:O(logn)

要注意循环不变量原则,常见左闭右开、左闭右闭

双指针法

  • 暴力解法时间复杂度:O(n^2)
  • 双指针时间复杂度:O(n)

 双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

数组中的元素为什么不能删除,主要是因为以下两点:

  • 数组在内存中是连续的地址空间,不能释放单一元素,如果要释放,就是全释放(程序运行结束,回收内存栈空间)。
  • C++中vector和array的区别一定要注意,vector底层实现是array,封装后使用更友好。

滑动窗口

  • 暴力解法时间复杂度:O(n^2)
  • 滑动窗口时间复杂度:O(n)

滑动窗口的精妙之处在于:固定结束位置后,对于已经确定不满足条件的窗口不再进行缩放的遍历,减少了可能的子序列的起始位置, 从而将O(n^2)的暴力解法降为O(n)。

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值