第二天|977.有序数组的平方、209.长度最小的子数组、59.螺旋矩阵II

LeetCode 977.有序数组的平方

977我看到题目后第一个形成的思路是:
一轮中,慢指针对应要挪到后面排序的那个负值元素,快指针一直++到后面找一个使nums[慢]+nums[快]>=0。
也就是快对应的正值元素绝对值大于等于慢指针的负值元素绝对值,而且是第一个大于等于的,之前都是小于的。
然后把从nums[慢+1]到nums[快-1]的这一堆往前挪,空出的nums[快-1]用原先的nums[慢]去替代。
然后发现写起来很难搞,边界条件就绕到云了,压根执行不了,fast?fast-1?边界怎么搞?脑子转不动。
这个第一个思路很能说明问题,说明我对于题目关键信息是没有提取到的,也没有捕捉到双指针的使用方法,整个脑子思维是非常混乱的,非常云,已有条件不利用,没有最好的利用

    vector<int> sortedSquares(vector<int>& nums) {
    	错误的
        int slow,fast = 0;
        while(nums[slow]<0){
            while(nums[slow]+nums[fast]<0&&fast<nums.size()-1) fast+=1;
            int tmp = nums[slow];
            int i = slow;
            while(i<fast-1){
                nums[i]=nums[i+1];
                i+=1;
            }
            nums[i]=-tmp;
            if(slow<nums.size())slow+=1;
            else break;
        }
        for(int j = 0;j<nums.size();j++){
            nums[j]=nums[j]*nums[j];
        }
        return nums;
    }

然后想的思路不如全都先平方,然后当无序数组冒个泡。这样能解决问题,但就没有很好的利用已有的,负数是已经排好序的,这么一个条件,没有利用好,复杂度肯定高,不是优化的解法

    vector<int> sortedSquares(vector<int>& nums) {
        for(int i = 0;i < nums.size(); i++){
            nums[i] = nums[i]*nums[i];
        }
        for(int i = 0;i < nums.size(); i++){
            for(int j = 0; j < nums.size() - i - 1; j++){
                if(nums[j] > nums[j + 1]){
                    int tmp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = tmp;
                }
            }
        }
        return nums;
    }

虽然复杂度上烂完了,但还好的是冒泡自己能按直觉写出来,要是冒泡都忘记了就真烂完了
还是做不到吗…至少写个好点的,这个冒泡复杂度也太烂了
可能前天双指针也没太搞明白本质
刚才和鸡皮题聊了下,鸡皮题建议是一样先算平方值,但然后的排序是从两端到中间合并,合并过程用双指针
这个好啊,我有想法了
平方后,左指针=0和右指针=nums.size()-1的值比大小,左小于等于右则右指针左移动,直到左大于右,左大于右,就把左+1到右全都左挪1格,把原来的左放到空出来的右这里

vector<int> sortedSquares(vector<int>& nums) {
        for(int i = 0; i < nums.size(); i++){
            nums[i] = nums[i]*nums[i];
        }
        int left = 0;
        int right = nums.size() - 1;
        while(left < right){
            if(nums[left] <= nums[right]) right--;
            else {
                int tmp = nums[left];
                for(int i = left + 1;i<=right;i++){
                    nums[i-1]=nums[i];
                }
                nums[right] = tmp;
            }
        }
        return nums;
    }

可以,一遍过,思路清晰写起来还是比较流畅的,这个时间复杂度要节省下来很多,充分利用了原先有序的条件
看看卡哥的
卡哥这个好啊,拿空间换时间,新搞一个数组,节省了我挨个挪元素空位置这个大头时间花销。

vector<int> sortedSquares(vector<int>& A) {
        int k = A.size() - 1;
        vector<int> result(A.size(), 0);
        for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素
            if (A[i] * A[i] < A[j] * A[j])  {
                result[k--] = A[j] * A[j];
                j--;
            }
            else {
                result[k--] = A[i] * A[i];
                i++;
            }
        }
        return result;
    }

双指针这里的本质在于,充分利用了已有条件特性:平方后最大值只可能在两端取到,而不可能在中间取到

LeetCode 209.长度最小的子数组

这是过了测试的代码,漏一个等号耽搁了好久,一直查,查不出来,还怀疑是思路问题,最后调试出来的,发现这个边界不对劲,sum有问题,才发现这个漏掉了等号

    int minSubArrayLen(int target, vector<int>& nums) {
    	错的
        int left,right;
        long len=999999999;
        int sum;
        for(left = 0;left < nums.size(); left++){
            for(right = left;right < nums.size(); right++){
                sum = 0;
                for(int i = left; i <= right; i++){
                    //这里i<=right原来漏掉等号,憋着头查了好久好久,思维还是混乱
                    sum+=nums[i];
                }
                if(sum>=target) break;
            }
            if(sum>=target&&(right - left + 1)<len) len = right - left + 1;
        }
        if(len != 999999999)return len;
        else return 0;
    }

但这个仅仅是过了测试,没有通过,大数组的测试用例超时了,这个时间复杂度是高的啊,三层循环了都
咋能减减呢,卡在这儿了
看看卡哥的吧

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

这个sum明显我没玩明白,外层sum=0,内层sum累加,就不用求sum了
避免了我单独再内嵌一个循环,求sum,导致的套三层

数组操作中另一个重要的方法:滑动窗口

滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
滑动窗口就是要用一个for循环来完成这个操作
首先要思考
如果用一个for循环
那么应该表示 滑动窗口的起始位置,还是 终止位置
(这是个关键问题,能提出来很重要)

如果
只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?
此时难免再次陷入 暴力解法的怪圈
(上面这个思维很好,值得学习)

所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置
那么问题来了, 滑动窗口的起始位置如何移动呢?
滑动窗口
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
解题的关键在于 窗口的起始位置如何移动
滑动窗口起始位置
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX;
        int sum = 0; // 滑动窗口数值之和
        int i = 0; // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) {
            sum += nums[j];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }

不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

LeetCode 59.螺旋矩阵II

没写出来,思维很乱,也是之前21年时候写过痛过了,现在24年写通不过
错误的代码贴一下

vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> ans(n,vector<int>(n));
        //(1)i不变j++
        //(2)j不变i++
        //(3)i不变j--
        //(4)j不变i--但不到头而是准备进入内圈,进入内圈后i和j都小了2*1,起点(2,2)
        //再下个内圈少2*2,起点(3,3)
        //再下个内圈少2*3,起点(4,4)
        //终止条件是i=j=1,最后一个元素n方,在(n-1,n-1)
        //思路是这个思路,但ij具体的用法我很混乱,ij是下标啊,有意义的,不能随便给减去了
        int i,j = 0;int lun = 0;int num = 1;
        //原先while里是i!=(n-1) || j!=(n-1)
        while(lun < (n-1) ){
            i = lun;j = lun;
            while( j < (n-lun*2) ){ans[i][j++] = num++;}
            j--;i++;
            while( i < (n-lun*2) ){ans[i++][j] = num++;}
            i--;j--;
            while( j >= lun*2 ){ans[i][j--] = num++;    }
            if(i==lun){lun+=1;continue;}
            j++;i--;
            while( i > lun*2){ans[i--][j] = num++;}
            lun+=1;
        }
        if(n%2){
            if(lun>0)ans[lun-1][lun-1] = num;
            else ans[0][0] = num;
        }
        return ans;
    }

可以看出来我一直不停面向测试用例调试,但还是没整明白,思维混乱
看看卡哥的吧

要写出正确的二分法一定要坚持循环不变量原则。
而求解本题依然是要坚持循环不变量原则。
模拟顺时针画矩阵的过程:
填充上行从左到右
填充右列从上到下
填充下行从右到左
填充左列从下到上
由外向内一圈一圈这么画下去。
可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是一进循环深似海,从此offer是路人。
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。

左闭右开
左闭右开
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
这也是坚持了每条边左闭右开的原则。
一些同学做这道题目之所以一直写不好,代码越写越乱。
就是因为在画每一条边的时候,一会左开右闭,一会左闭右闭,一会又来左闭右开,岂能不乱。
代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开。

    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
        int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
        int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
        int count = 1; // 用来给矩阵中每一个空格赋值
        int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
        int i,j;
        while (loop --) {
            i = startx;
            j = starty;

            // 下面开始的四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for (j = starty; j < n - offset; j++) {
                res[startx][j] = count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for (i = startx; i < n - offset; i++) {
                res[i][j] = count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for (; j > starty; j--) {
                res[i][j] = count++;
            }
            // 模拟填充左列从下到上(左闭右开)
            for (; i > startx; i--) {
                res[i][j] = count++;
            }

            // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            startx++;
            starty++;

            // offset 控制每一圈里每一条边遍历的长度
            offset += 1;
        }

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }

卡哥的loop变量对应我的lun变量,这个奇偶的问题我没有处理好,我采用的是在结尾处判断奇偶,卡哥是定义轮次的时候给弄了,省得我在结尾判断套了两层if一堆分支搞得自己很云
矩阵中间位置、遍历边长度,这些我压根没想着去定义,都想靠着对lun变量的循环和对ij的小微操来调整好,但结果是很混乱,现在也没绕明白
说实话
数组这就搞完了,有点快,接下来今天要继续链表
前天的双指针法,今天现在的这个滑动窗口,还有模拟里用的这些定义变量的方法,循环不变量原则
目前没有吸收好,沉淀沉淀,卡哥的视频多看两遍

补充:vecotr创建二维数组

//1
//使用vector一次性完成二维数组的定义(注意:此种方法适用于每一行的列数相等的二维数组)
vector<vector<int>> matrix(m, vector<int>(n, -1));

//以下是拆分理解
//创建一维数组matirx,这个数组里有m个元素,元素是int型vector。
vector<vector<int>> matrix(m);
//除了定义数组类型及数组大小外,同时给数组中的元素赋值:将元素赋值为大小为n的int型vector。
vector<vector<int>> matrix(m, vector<int>(n));
//除了定义数组类型、数组大小、列的大小,同时给数组列中的元素(或者说,数组中的所有元素)赋值为-1。
vector<vector<int>> matrix(m, vector<int>(n, -1));

//比较具有普遍性的写法(注意:此种方法适用于每一行的列数相等的二维数组)
vector<vector<int>> matrix;//创建一维数组matirx,这个数组里的元素是int型vector。
int m = 3; //matrix有m行
int n = 10; //matrix有n列
int value = 1; //最终matrix成为二维数组后,其中每个元素的值为1(如果不需要进行初始化,此语句可以省略)
for (int i = 0; i < m; ++ i) {
    vector<int> tmp(n, value); //定义int型一维数组tmp,该数组有n个int型元素,且每个元素的初始值为value
    matrix.push_back(tmp); //将一维数组tmp(小容器)加入matrix(大容器)中,使之成为matrix的元素,令matrix成为二维数组
}

//2
//如果需要每一行的列数不同(虽然一般很少这样做),也可以使用下面这种写法进行定义、初始化
vector<vector<int>> matrix;

vector<int> a(10, 1); //单独定义每个小容器的元素个数和元素初始值
vector<int> b(5, 2);
vector<int> c(10, 3);

matrix.push_back(a); //将每个小容器加入matrix(大容器)中
matrix.push_back(b);
matrix.push_back(c);

//3
//使用vector的resize函数进行二维数组的定义(注意:此种方法适用于每一行的列数相等或不相等的二维数组,调整for循环内的resize函数的参数即可)
vector<vector<int>> matrix(m); //创建一维数组matirx,这个数组里有m个元素,元素是int型vector。不能省略m。
for (int i = 0; i < m; ++ i) {
	matrix[i].resize(10, 1); //使用vector的resize函数,对matrix(大容器)中的每个元素的大小进行更新(可以同时进行初始化)。此处表示:将matrix(大容器)中第i个int型vector的大小定义为10,且其元素均初始化为1。
    //如果不需要进行初始化,resize函数的第二个参数可以省略
}

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值