Leetcode数组篇总结(c++)

一、基础知识

一维数组:连续内存空间上的相同类型数据的集合
二维数组:对于二维数组nums,nums[0]实际上是一个指向第一行数组的指针,借用代码随想录中的图片如下:
二维数组示意图
同时,二维数组在内存中不一定连续,与语言特性有关,但对于静态数组来说,内存是连续的。
对于c++, 二维数组是连续的,下图为打印的2*3数组元素(int)的地址
在这里插入图片描述

二、经典题目

1、35:搜索插入位置 -二分查找-简单

题目:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

思路:在已排序数组中使用二分查找法,数组的常见操作
关键点:重在边界处理;且是已排序数组;防溢出操作
复杂度:时间O(log n), 空间O(1)

补充:防溢出操作: mid = l + (r - l)/2;
代码:

//二分查找算法,重在边界处理上
//关键字:已排序数组,
//闭区间[0, n-1] , whilr(l <= r), r = mid-1
//防溢出操作
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        //二分查找
        int r, l, mid;
        r = nums.size() - 1;
        l = 0;
        
        while(l <= r){
            mid = l + ((r - l)/2);//防止溢出,等同于mid = (r + l)/2;
            int temp = nums[mid];
            if(temp == target)
                return mid;
            else if(temp > target){
                r = mid - 1;
            }else
                l = mid + 1;

        }
        
        return l;
       
    }
};

2、27:移除元素-双指针-简单

题目:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。link

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

为什么返回数值是整数,但输出的答案是数组呢?请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

思路1:暴力解法双循环 时间O(n^2), 遇到等于val的值,则把后面的数都提前;代码略
思路2:双指针,代替双循环,时间O(n)
关键点:「双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组和链表操作的面试题,都使用双指针法。」

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int fastindex, lowindex = 0;

        for( fastindex = 0; fastindex < nums.size(); fastindex++){
             if(nums[fastindex] != val)
                 nums[lowindex++] = nums[fastindex];
         }
         return lowindex;

双指针优化,即对于第一个元素是要删除的值,则需要将整个数组提前一个的例子
因为元素的顺序可以改变,所以如果左指针指向的是val,可以考虑用右指针指向元素取代左指针元素,然后右指针向前一个,即【1,2,3,4,5】,val = 1,则用5取代1

  //参考官网
        int right = nums.size() -1, left = 0;
        while(left <= right){
            if(nums[left] == val){
                nums[left] = nums[right];
                right--;
            }else{
                left++;
            }
        }
        
        return left;
    }

3、 209:长度最小的子数组-滑动窗口-中等

题目:给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

思路1:暴力求解,双循环,时间O(n^2)

思路2: 前缀和数组+二分查找:O(nlogn), 即对于每一个位置i,查找后面某 个最小位置j,使得i和j之间的和大于等于target;位置j就需要在前缀和数组中二分查找;注意点是nums数组需要没有负数,即保证前缀和数组升序。 另外,在c++中有库函数low_bound实现二分查找位置j

(在解题评论区看到有人说明明有O(n)的解法,为什么还要考虑O9nlogn)的解法,有人回答现实中可能会因为各种考量,并不能只追求最优解法,其他的思想同样会用到。甚有道理。)

思路3:滑动窗口 时间O(n),空间O(1);滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。关键点在滑动窗口开端i和结尾j的配合上,

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int i , j, len; //滑动窗口起止,长度
        int result = nums.size() +1;
        int sum = 0;
        i = j = 0;
        int s = nums.size();
        for(j = 0; j < s; j++){//将窗口一直向后扩大
            sum += nums[j];
            while(sum >= target){ //达到扩大边界,则开端i向后移
                len = (j-i)+1;
                result = len < result ? len : result;
                sum -= nums[i++];
            }
        }
        if(result > nums.size())
            return 0;
        else
            return result;
    }
};

对于滑动窗口,有一个思想是先得到初始窗口,然后保证后面窗口只平移或缩小,那么就不用专门维护一个最小长度,但逻辑上没有原始的好理解

4、 59:螺旋矩阵II-模拟法-中等

题目内容
思路1: 模拟法,设定边界
关键点:循环不变量原则,前开后闭。即上下左右边都是填充相同的数量,对于上例,分别是12 34 56 78
「本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。」

补充:二维vector数组的初始化

  //直接初始化
  vector<vector<int>> ans(r,vector<int>(c,0));
  //或者用resize
  vector<vector<int> > res;
        res.resize(r);//r行
        for (int k = 0; k < r; ++k){
            res[k].resize(c);//每行为c列
        }

代码:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
       
        int l_num = n-1; //一边的元素个数,前开后闭
        int start = -1, end = -1; //start,end确切地说应该是每一圈左上角坐标
        int count = 1;
        //初始化二维的vector
        vector<vector<int>> ans(n,vector<int>(n,0));
        
        for(int i = n; i >0; i = i-2){
            l_num = i-1;
            start++;
            end++;
            //奇数n的最后一圈
            if(l_num == 0 && (n%2 != 0)){
                ans[start][end] = count++;
                break;
            }
            //对于每一圈,共四边
            //上边
            for(int j = 0; j < l_num ; j++ ){
                ans[start][end+j] = count++;
            }
            //右边
            for(int j = 0; j < l_num; j++){
                ans[start+j][end+l_num] = count++;
            }
             //下边
            for(int j = 0; j < l_num ; j++ ){
                ans[start + l_num][end+l_num - j] = count++;
            }
            //左边
            for(int j = 0; j < l_num; j++){
                ans[start+l_num - j][end] = count++;
            }
            
        }
        return ans;
    }
};

优化代码:通过改变left,top,right,bottom四个边界值,进行填充,取自精华帖krahets

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

5、977-有序数组的平方(简单)

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。link

示例 1:

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:

输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

1、简单解法:求平方后再排序,时间复杂度O(nlogn)

**2、双指针法:**根据数据的排序,可以先找到正负边界,然后两个指针分别向两侧扩展,哪个指针的数值小则优先加入数组ans(从左向右填ans)。时间复杂度O(n)

注意点:数组下标和数组值经常弄混
一个新的边界判断code方法,看注释部分和后面的版本

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int fast, low;
        fast = low = 0;
        vector<int> ans;
        //找到非负边界
        for(fast = 0; fast < nums.size(); fast++ ){
            if(nums[fast] >= 0){
                break;
            }
        }
       
        low = fast - 1;
       
        // while(low >= 0  && fast < nums.size()){
        //     if(abs(nums[low]) <= abs(nums[fast])){
        //         ans.push_back(nums[low] * nums[low]);
        //         low--;
        //     }else{
        //         ans.push_back(nums[fast] * nums[fast]);
        //         fast++;
        //     }
        // }
        // //处理边界问题
        // if(fast < nums.size()){
        //     for(; fast < nums.size(); fast++){
        //          ans.push_back(nums[fast] * nums[fast]);
        //     }
        // }else{
        //     for(; low >= 0; low--){
        //          ans.push_back(nums[low] * nums[low]);
        //     }
        // }
         while(low >= 0  || fast < nums.size()){
             //处理边界问题
            if(low < 0 ){           
                ans.push_back(nums[fast] * nums[fast]); 
                fast++;          
            }else if(fast == nums.size()){
           
                ans.push_back(nums[low] * nums[low]);
                low--;
            
            }else if(abs(nums[low]) <= abs(nums[fast])){
                ans.push_back(nums[low] * nums[low]);
                low--;
            }else{
                ans.push_back(nums[fast] * nums[fast]);
                fast++;
            }
        }
        */

**3、双指针法:**和2一样的思想,但是两个指针初始时指向nums两侧,数值大的优先放入ans右端(即从右向左填ans,所以需要提前设定ans大小)。和2相比,减少了正负交界处的寻找,且指针问题边界判断简单。时间复杂度O(n)

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int fast, low;
        fast = low = 0;
        vector<int> ans(nums.size(), 0);
        int k = nums.size() - 1;
        for(low = 0, fast = nums.size() - 1; low <= fast;){
            if(abs(nums[low]) > abs(nums[fast])){
                ans[k--] = nums[low] * nums[low];
                low++;
            }else{
                ans[k--] = nums[fast] * nums[fast];
                fast--;
            }
        }
        
    
        return ans;
    }
};

三、总结

  1. 边界的判断,比如空数组的判断
  2. 防溢出考虑
  3. 循环不变量
  4. 变量命名要有意义和规则
  5. 刷题中库函数的使用取决于这道题的考察重点,比如要考察二分查找,直接用库函数实现并没有意义。
  6. 数组下标和数组值不要搞混

注:按照代码随想录的刷题指南进行
参考链接:https://leetcode-cn.com/circle/article/wGp7Y9/
题目来源:力扣(LeetCode)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值