【代码随想录笔记】数组

目录

1、二分查找

2、移除元素

3、有序数组的平方

4、螺旋矩阵II


1、二分查找

对于二分搜索法,有两个边界问题是容易把握不准的

  1. 是left < right还是left <= right

  2. 当nums[middle] > target时,需要更新右边界,那是right = middle,还是right = middle - 1

对于这两个问题,其实是需要根据二分法维护的区间来判断的,二分法维护的区间主要有两种,左闭右闭和左闭右开

首先,我们来看第一个问题,在讨论left和right之间是否能有等号时,实际上就是看等号成立时,对于这个区间是否有意义,当维护的区间是左闭右闭,此时是有意义的,相当于这个区间只有一个值,当维护的区间是左闭右开,此时是没有意义的,因为此时区间里面一个值都没有。并且,对于左闭右闭的区间,如果不加上等号,还会少判断一个值。所以,对于左闭右闭区间是left <= right,对于左闭右开区间是left < right

然后,我们来看第二个问题。当nums[middle] > target时,需要更新右边界,对于左闭右闭区间,因为nums[middle]这个值已经明确是大于target了,所以不需要再放在区间里面取判断,所以是right = middle - 1,对于左闭右开区间,则是right = middle。左边都是闭的,所以更新左边界时都是left = middle + 1

并且还要注意,初始化right时,对于左闭右闭区间,right = num.size() - 1,对于左闭右开区间,right = nums.size()

控制左闭右闭区间和左闭右开区间也称为控制循环不变量

左闭右闭的代码:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0,right = nums.size() - 1;
        while(left <= right)
        {
            int middle = (left + right) / 2;
            if(nums[middle] == target) return middle;
            else if(nums[middle] < target) left = middle + 1;
            else right = middle - 1;
        }
        return -1;
    }
};

左闭右开的代码:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0,right = nums.size();
        while(left < right)
        {
            int middle = (left + right) / 2;
            if(nums[middle] == target) return middle;
            else if(nums[middle] < target) left = middle + 1;
            else right = middle;
        }
        return -1;
    }
};

2、移除元素

这道题很容易就想到使用vector的erase接口来完成即可,但是一般直接使用库函数就可以解决的题目,出题者往往不是想让你用库函数,而是为了让你模拟实现库函数。vector的erase接口就是当遍历到的值是需要删除的元素时,后面全部的元素都往前挪动一位,覆盖掉需要删除的值

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int n = nums.size();
        for(int i = 0;i<n;i++)
        {
            if(nums[i] == val)
            {
                for(int j = i;j < n - 1;j++) nums[j] = nums[j + 1];
                n--;
                i--;
            }
        }
        return n;
    }
};

但是,这种方法的时间复杂度是O(N^2),这个时候我们可以使用双指针算法,来将时间复杂度降到O(N)

定义一个快指针和一个慢指针,当快指针从前向后遍历数组,当快指针指向的值不是val时,就将快指针指向的值赋值给慢指针指向的值,然后两指针都向后走,当快指针指向的值等于val时,就只让快指针向后走

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int n = nums.size(),left = 0,right = 0;
        while(right < n)
        {
            if(nums[right] != val) nums[left++] = nums[right++];
            else right++;
        }
        return left;
    }
};

3、有序数组的平方

这道题很容易就能想到暴力解法,就是先将数组中的每个数都平方了,然后再用sort对其进行排序,这样的时间复杂度是O(N*logN)

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int n = nums.size();
        for(int i = 0;i<n;i++) nums[i] = nums[i] * nums[i];
        sort(nums.begin(),nums.end());
        return nums;
    }
};

我们会发现,将数组中的每个数都平方之后,这个数组最大的数都是在数组的两边,所以我们可以利用这个性质,使用双指针算法将时间复杂度降到O(N)

额外创建一个数组,让两指针,一个从头开始,一个从尾开始,选择两指针指向的值中大的哪一个,将其放到额外创建的数组的后面

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int n = nums.size();
        for(int i = 0;i<n;i++) nums[i] *= nums[i];
        vector<int> v(n);
        int left = 0,right = n - 1,k = n - 1;
        while(left <= right)
        {
            if(nums[left] <= nums[right]) v[k--] = nums[right--];
            else v[k--] = nums[left++];
        }
        return v;
    }
};

注意,while循环的条件一定要是left <= right,有等号,否则可能会导致漏掉一个数据

4、螺旋矩阵II

首先,要做这道题就需要先定义好循环不变量,也就是每次在操作一行或者一列时,是左闭右开,或者是怎么样的,这样才能够保证在循环中不会出现泰复杂的边界问题。在这里,我们采用左闭右开的方式来解决问题。

while循环一次就是处理1圈,所以一个n行n列的数组,我们需要处理loop = n / 2圈,当n是偶数时,刚刚和,当n是奇数时,会剩下一个位置没有处理,最后再赋值即可

1圈当中我们会分成4个部分来处理,分别对应两行两列,并且对于每一行,都是左开右闭的,即每一行或每一列的最后一个位置是不在这一行或这一列处理的,留给下一次处理

会定义一个startx和starty来定义每一圈的起始位置,同时也是结束位置,最先开始都是0,一圈完成之后就都变成1,依次类推

还要定义一个offest来控制每一行或每一列的结束位置

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> vv(n, vector<int>(n, 0));
        int loop = n / 2; // 判断需要几圈
        int startx = 0,starty = 0; // 每一圈的起始位置
        int offest = 1; // 控制结束位置
        int count = 1; // 放入数组的数字
        int i,j;
        while(loop--)
        {
            for(j = startx;j < n - offest;j++)
            {
                vv[startx][j] = count++;
            }
            for(i = starty;i < n - offest;i++)
            {
                vv[i][j] = count++;
            }
            for(;j > starty;j--)
            {
                vv[i][j] = count++;
            }
            for(;i > startx;i--)
            {
                vv[i][j] = count++;
            }
            startx++;
            starty++;
            offest++;
        }
        if(n % 2 == 1) vv[n/2][n/2] = count;
        return vv;
    }
};
相关推荐
息流使用宝典1 小时前
FlowUs:强大图表功能与多维表结合,开启便捷办公新时代
笔记·信息可视化·数据分析·flowus·数据图表
一心赚狗粮的宇叔3 小时前
《深入浅出WPF》读书笔记.8路由事件
笔记·学习·microsoft·c#·wpf·visual studio
Fz@3 小时前
TD学习笔记————中级教程总结(NEW)
笔记·学习
The_Singing_Towers3 小时前
【摸鱼笔记】python 提取和采集 finereport 未绑定目录的报表模板
开发语言·笔记·python
weixin_466438684 小时前
【第三期实战营闯关作业##LMDeploy 量化部署进阶实践】
人工智能·笔记·学习
zhangrelay5 小时前
2024-2025-1秋学期课程任务和班课号
笔记·学习·持续学习
漆黑的莫莫5 小时前
经验笔记:选择消息中间件——RabbitMQ vs RocketMQ vs Apache Kafka
笔记·rabbitmq·rocketmq
IOT.FIVE.NO.16 小时前
Linux学习笔记4 重点!网络排障命令
linux·笔记·学习
Pandaconda7 小时前
【C++ 面试 - 内存管理】每日 3 题(十)
开发语言·c++·经验分享·笔记·后端·面试·职场和发展
小火柴棒8 小时前
Linux学习笔记(4)----通过网口灯判断网速是千兆还是百兆
笔记·学习