【代码随想录】数组篇 总结

代码随想录:https://www.programmercarl.com/

参考博客:陈童学哦 - C++中vector的用法

导图 - 数组篇

在这里插入图片描述


1.1 数组理论基础

  • 连续内存空间上的相同数据类型的集合

  • 下标从0开始;通过下标索引访问数据

  • 数组中的元素不能删除,只能覆盖

  • 二维数组在不同的编程语言中,存储方式不一样

    • C++:连续的内存空间地址

    • Java:不连续,可能是邻接表


1.2 704.二分查找

思路

  • 二分法的前提:①有序数组;②无重复元素

  • 二分法,即折半查找

  • 循环不变量:区间的定义(左闭右闭、左闭右开)

    • 区间如何定义,决定了循环条件、区间边界如何变化/区间如何调整

代码实现

左闭右闭(大多用)

class Solution {
public:
    int search(vector<int>& nums, int target) {
        // 左闭右闭

        int left = 0;
        int right = nums.size() - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] > target) {
                right = mid - 1;
            }
            else if (nums[mid] < target) {
                left = mid + 1;
            }
            else {
                return mid;
            }
        }
        return -1;
    }
};
// 时间复杂度:O(logn)
// 空间复杂度:O(1)

左闭右开

class Solution {
public:
    int search(vector<int>& nums, int target) {
        // 左闭右开

        int left = 0;
        int right = nums.size();        // right取不到
        int mid = 0;
        while (left < right) {          // right取不到,"="无意义
            mid = (left + right) / 2;
            if (nums[mid] > target) {
                right = mid;            // right取不到
            }
            else if (nums[mid] < target) {
                left = mid + 1;
            }
            else {
                return mid;
            }
        }
        return -1;
    }
};
// 时间复杂度:O(logn)	2^执行次数 = n 👉 执行次数 = logn
// 空间复杂度:O(1)

1.3 27.移除元素

思路

  1. 暴力法:一层循环找 ==val 的元素,找到后再一层循环把后面所有的元素前移(时:O(n^2))(具体代码见 代码随想录-移除元素

  2. 变量记录元素向前移动的长度(本质其实就是快慢双指针,不过是用变量代替了)(时:O(n))

  3. 快慢指针法

  4. 相向指针法

代码实现

变量记录元素向前移动的长度

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        // 变量记录元素向前移动的长度
        // 所有变量都向前移动mov的长度,每当遇到==val的元素,mov++
        // 当mov==0时,即不用移动,每次循环体中必有,nums[i]前移mov个长度

        int mov = 0;        
        for (int i = 0; i < nums.size(); i++) {
            nums[i-mov] = nums[i];  
            if (nums[i] == val) {
                mov++;
            }
        }
        return nums.size() - mov;
    }
};
// 时间复杂度:O(n)     假设数组长度为n,每个元素都遍历了一边,故O(n)
// 空间复杂度:O(1)     题目要求原地修改

快慢指针法

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        // 快慢双指针
        // 快的找新数组需要的元素,慢的更新数组下标: fast遇到 !=val 的元素,把fast处的值赋值给slow处,slow++
        // 每次循环都有fast++
        // fast遇到几个 !=val 的元素,slow就加几,即slow为最终长度

        int slow = 0;
        int fast = 0;
        int length = nums.size();
        while (fast < length) {
            if (nums[fast] != val) {
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }
        return slow;
    }
};
// 时间复杂度:O(n)     假设数组长度为n,每个元素都遍历了一边,故O(n)
// 空间复杂度:O(1)     题目要求原地修改

相向指针法

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        // 相向指针法
        // left找 ==val的
        // right找 !=val的
        // 都找到后,nums[left] = nums[right]
        // 无论如何,right最终指向新数组的最后一个元素,left指向新数组的最后一个元素的后一个元素

        int left = 0;
        int right = nums.size() - 1;    // 左闭右闭
        while (left <= right) {         // 相等时还有最后一个元素需要判断

            // 当left或right有连续变化时,一定要有前提:left<=tight,否则,可能会多移动
            while (left <= right & nums[left] != val)   // 此循环结束后,nums[left]==val
                left++;
            
            while (left <= right & nums[right] == val)  // 此循环结束后,nums[right]!=val
                right--;

            // 所以必然存在 left != right
            if (left < right){
                nums[left] = nums[right];
                left++;
                right--;
            }
        }
        return left;
    }
};
// 时间复杂度:O(n)     假设数组长度为n,每个元素都遍历了一边,故O(n)
// 空间复杂度:O(1)     题目要求原地修改

1.4 977.有序数组的平方

思路

  1. 暴力解法:先平方,再排序(时间复杂度取决于排序效率,快排 O(logn))
  2. 双指针法:O(n)

代码实现

双指针法

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        // 双指针法
        int left = 0;
        int right = nums.size() - 1;    // 循环不变量:左闭右闭
        
        vector<int> res(nums.size());   // 创建一个 nums相同长度 的数组
        int count = nums.size() - 1;    // 逆序添加元素至res数组中

        while (left <= right) {         // left==right时,还有最后一个元素要加到res数组中
            int num_left = nums[left] * nums[left];
            int num_right = nums[right] * nums[right];
            if (num_left > num_right) {
                res[count--] = num_left;
                left++;
            }
            else {                      // 等于的时候就无所谓添加谁了,并到其中一个分支即可  
                res[count--] = num_right;
                right--;
            }
        }
        return res;
    }
};
// 时间复杂度:O(n)
// 空间复杂度:O(n)

代码随想录参考答案(977.有序数组的平方),更简洁一点


1.5 209.长度最小的子数组

思路

  1. 暴力法:O(n^2)
  2. 滑动窗口(本质还是双指针)

代码实现

双指针法

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        // 双指针法
        // slow和fast都从0开始,求slow到fast之间所有元素之和sum
        // 大于等于target,sum减去下标为slow的值,slow++
        // 否则,fast++,sum加下标为fast的值
        // 设一个变量min_len不断更新最小长度,直至slow遍历至表尾结束,return min_len

        int slow = 0;
        int fast = 0;
        int min_len = INT_MAX;
        int sum = 0;

        // 循环不变量:左闭右开
        while (fast < nums.size()) {
            sum += nums[fast];

            // 这里用while循环,而非if,如果用if,每次还要 减去nums[fast],过于冗余
            // 之所以用循环就是可以不用更新fast对应的值,而每层循环必然会更新fast的值
            // 用第2层循环隔绝fast的更新,可以在不更新fast的前提下,更新slow,实现滑动窗口前侧的变化
            while (sum >= target) {
                min_len = min(min_len, fast-slow+1);
                sum -= nums[slow++];
            }
            fast++;
        }
        return min_len != INT_MAX? min_len : 0;     // 成立正常返回,==INT_MAX返回0
    }
};
// 时间复杂度:O(n)	每个元素至多被遍历2次 O(2n) 👉 O(n)	
// 空间复杂度:O(1)

遇到的问题

  1. int最大值不会表示
  2. C++三元运算符不会写
  3. 循环条件格式写错: while (slow <= fast < nums.size())(Python的条件判断可以这样,C++不行)
    导致一直报下图错误,我还以为超出int最大值了,但是给的测试数据又不可能,我还模拟了半天,提交也是0个测试用例通过在这里插入图片描述正确写法:while (slow <= fast && fast < nums.size())(而且实际上,本题如果用while循环的话,条件应该是fast < nums.size()就足够了,因为slow永远不可能超过fast
    代码随想录参考答案(209.长度最小的子数组)给的for循环的版本,我觉得更好使一点

个人猜想(未证实)

此题移动滑动窗口的前端时,为了保证另一端不动,直接用多套一层循环来移动
那么应该只移动前端,或者仅移动一端的时候,都可以直接用循环咯?

参考博客


1.6 59.螺旋矩阵Ⅱ

思路

模拟过程,考察代码掌控能力
本题的关键在于,画圈的同时,坚持循环不变量,每圈对每条边的处理就可以看成是相同的

代码实现

我的注释比较多,都是我写代码的过程中的思考
代码随想录参考答案(59.螺旋矩阵Ⅱ)会简洁一些

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        // 模拟转圈;循环不变量:左闭右开,每次的最后一个不处理,下一次的第一个会处理
        // 转几圈? n/2

        // 创建一个n*n的二维数组,初始化所有数据全为0,最终return result
        vector<vector<int>> result(n, vector<int>(n, 0));   

        int loop = n / 2;   // 转圈数
        int count = 1;      // 从1开始,每次赋值后++
        int start_x = 0, start_y = 0;   // 每一圈的起始位置,x对应行,y对应列
        int offset = 1;     // 偏移量
        

        while (loop) {      // loop不为0,就不还有圈要转
            // 每一圈i要从x开始,j要从y开始
            int i = start_x, j = start_y;       // i与start_x对应,j与start_y对应
            
            // 从上边的行开始,横向移动,变化的是列,即j
            while (j < n-offset) {  // 左闭右开,第1圈最后一个不处理,第2圈后俩不处理,所以要设置一个偏移量offset来记录
                result[start_x][j++] = count;   // 行不变,列随着j变
                count++;
            }                       // 遍历结束后 j == n-offset

            // 接下来处理右边的列,纵向移动,变化的是行,即i
            while (i < n-offset) {  // 左闭右开,同上
                result[i++][j] = count;
                count++;
            }                       // 遍历结束后 i == n-offset

            // 接下来处理下边的行,横向移动,变化的是列,即j。此时 j==n-offset
            while (j > start_y) {   // 左闭右开,start_y的地方留给左边的列的开头
                result[i][j--] = count;
                count++;
            }                       // 遍历结束后 j == start_y

            // 接下来处理左边的列,纵向移动,变化的是行,即i。此时 i==n-offset
            while (i > start_x) {   // 左闭右开,start_x的地方留给左边的列的开头
                result[i--][j] = count;
                count++;
            }                       // 遍历结束后 j == start_y

            // 进行完上面4个循环,即转了1圈
            loop--;             // 圈数--
            start_x++;start_y++;// 起始位置变成 1,1
            offset++;           // 偏移量也++
        }

        // 如果 n 是奇数,那么最后会漏一个元素
        if (n % 2 == 1) {
            result[n/2][n/2] = count;
        }
        return result;
    }
};
// 时间复杂度:O(n^2)
// 空间复杂度:O(n^2)

遇到的问题

  1. 不会用vector创建二维数组
  2. 最外层循环,ij的初始值不总是0,即每圈ij的初始值不为0;而分别是start_xstart_y

参考博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值