数组总结:


数组

数组是存放在连续内存空间上的相同类型数据的集合。数组下标都是从0开始的。数组地址连续,数组元素只能覆盖,不能删除

一、二分查找(关键词:有序数组)

1.1(704)二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。
二分法:
定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。
while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

二分法查找用while循环,定义三个变量,left=0;right=nums.size()-1;mid=left+(right-left)/2;注意:mid的定义!
在这里插入图片描述

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-left)/2;
           if(nums[mid]>target)
           {
               right=mid-1;
           } 
           else if(nums[mid]<target)
           {
               left=mid+1;
           } 
           else{
               return mid;
           }
        }
        return -1;
    }
};

1.2(35)二分查找,并把元素插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素
考虑四种情况:
1、比第一个数还小
2、比最后一个数还大

// if(target<nums[0]) return 0;
// if(target>nums[right]) return right+1;(这两行可不要,情况四涵盖了这两种情况)

3、等于数组里的一个数(普通的二分查找)

    while(left<=right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]<target)
            {
                left=mid+1;
            }
            else if(nums[mid]>target)
            {
                right=mid-1;
            }
            else{
                return mid;
            }
        }

4、插入数组中
当数组中只剩下一个元素时,m=l=r,如果t大于最后一个元素值,l=mid+1,此时left就是该插入的位置;t<最后一个元素值,r=mid-1,它插在最后一个元素值前面,最后一个元素值的位置后移,它应该插入的位置就是原先left的位置;故返回left;

    return left;

总体代码:
由于情况四涵盖情况一二,可屏蔽掉

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left=0;int right=nums.size()-1;
       // if(target<nums[0]) return 0;
       // if(target>nums[right]) return right+1;(这两行可不要)
        while(left<=right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]<target)
            {
                left=mid+1;
            }
            else if(nums[mid]>target)
            {
                right=mid-1;
            }
            else{
                return mid;
            }
        }
        return left;
    }
};

1.2(34)二分查找,在排序数组中,查找元素所在的第一个位置和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

c++初学者,提供一种比较容易理解的写法。
本题是 “704.二分查找” 的变形题,二分查找的一般写法是:

    int getRightBorder(vector<int> nums, int target)
        {
            int left = 0;
            int right = nums.size() - 1;
            int rightBorder = -3;

            while(left <= right){
                int middle = left + (right - left) / 2;
                if(nums[middle] < target)
                    left = middle + 1;
                else if(nums[middle] > target)
                    right = middle - 1;
                else{
                    // 此时 nums[middle] == target
                    return middle;
                }
            }
     
            return -1;     
        }

这道题的思路是利用二分法分别找到target的左右边界的下标,例如:
对于 nums = {5, 7, 7, 8, 8, 10} , target = 8 这个示例 , 左右边界分别是 7 和 10,对应的下标是 25. 注意,边界是不包含target的

先解决如何寻找右边界下标的问题。对此,我们只需在传统二分写法的基础上进行修改即可!会了右边界,左边界的寻找方法同理。

首先,简单地将该问题分为 数组中存在target数组中不存在target 这两种情况。
A. 数组中存在target
当找到target时,传统的二分查找算法会立刻返回middle,如下:
else{
// 此时 nums[middle] == target
return middle;
}
但是,本题和传统二分查找的区别在于,数组中可能存在多个target,因此在找到target后还要继续查找是否有其他target存在?那些
target又在哪里呢?target的边界在哪里?为了解决这几个问题,我们将上述代码修改成

        else{
            // 此时 nums[middle] == target
            left = middle + 1;
            rightBorder = left;
        }
这段代码的意思是,当我们发现一个target后,不要停止,继续将left右移(之所以右移是因为我们要找右边界,查找区间[left,right]
要不断向右缩小),并且因为已经知道middle处是target了,所以自然将left移到middle的右侧。反复这个过程,直到 left > right,
**此时,left就是右边界**,同时程序也从while循环中退出了,为了记录这个边界值,我还在循环中加入了第二句话,即
 **rightBorder = left;**,这样,记录的rightBorder之不断更新,直到推出循环时记录的才是最终的右边界下标。

这样,如果target存在于数组中,我们就能找到其右边界了。

B. 数组中不存在target
继续上面的思考,若数组中不存在target,即nums[middle]和target不可能是相等关系,那么程序在while循环中自然永远进不到上
述 else 的代码块
,只会会进入下面两个代码块之一:

        if(nums[middle] < target)
            left = middle + 1;
        else if(nums[middle] > target)
            right = middle - 1;
 **这部分代码是不用改的**,可以发现这两种情况下,**没有对rightBorde的赋值,不像上面的 else 语句中不断更新rightBorder的
值**。那么,我们不妨在进入循环前先给rightBorder一个不可能是下标的值,像 -3,-99 等,如下:

        int rightBorder = -3;

        while(left <= right){
               
                ......

        }
这样,程序退出循环后,若发现rightBorder的值还是-3或-99,那么说明程序只是进入了上述 if 和 else if 这两种形况,也就说明数组中没有target;相反,若是发现rightBorder更新成一个正常的下标值,那就说明程序曾经进入过 else 语句块,也就说明数组中有
target,并且此时rightBorder记录的就是右边界值.
综上所述,我们就得到了寻找右边界的函数:
int getRightBorder(vector<int> nums, int target)
        {
            int left = 0;
            int right = nums.size() - 1;
            int rightBorder = -3;

            while(left <= right){
                int middle = left + (right - left) / 2;
                if(nums[middle] < target)
                    left = middle + 1;
                else if(nums[middle] > target)
                    right = middle - 1;
                else{
                    //exsit
                    //return middle;
                    left = middle + 1;
                    rightBorder = left;
                }
            }

            return  rightBorder;      
        }

调用 getRightBorder 时,要看看找到的右边界是正常的下标值还是-3,若是-3,那么说明数组中压根没target,那么最终结果是
{-1,-1};若是正常的下标值,那么rightBorder就是右边界值,但是最终结果要减1,因为我们之前说过了,这个右边界是不包含
target的,减去1后的下标才是最后一个target所在位置!

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int leftBorder = getLeftBorder(nums, target); // target前一个位置,不包含target
        int rightBorder = getRightBorder(nums, target); // target后一个位置,不包含target

        if(leftBorder == -3 | rightBorder == -3)
            return {-1, -1};
        else
            return {leftBorder + 1, rightBorder - 1};
    }

private:
    int getRightBorder(vector<int> nums, int target)
    {
        int left = 0;
        int right = nums.size() - 1;
        int rightBorder = -3;

        while(left <= right){
            int middle = left + (right - left) / 2;
            if(nums[middle] < target)
                left = middle + 1;
            else if(nums[middle] > target)
                right = middle - 1;
            else{
                //exsit
                //return middle;
                left = middle + 1;
                rightBorder = left;
            }
        }

        return  rightBorder;      
    }

    int getLeftBorder(vector<int> nums, int target)
    {
        int left = 0;
        int right = nums.size() - 1;
        int leftBorder = -3;

        while(left <= right){
            int middle = left + (right - left) / 2;
            if(nums[middle] < target)
                left = middle + 1;
            else if(nums[middle] > target)
                right = middle - 1;
            else{
                //exsit
                //return middle;
                right = middle - 1;
                leftBorder = right;
            }
        }

        return  leftBorder;      
    }
};

1.4(69)二分查找,查找算术平方根

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
输入:x = 4
输出:2
两个点需要注意的:
1、mm可能需要longlong类型
2、如果输入为4,输出恰好为2,但如果输入是8,怎么解决
因为mid
mid小于x,可能是输出ans,但mid*mid大于x,肯定不可能是输出

  int left=1;int right=x;int ans=0;
        while(left<=right)//左闭右闭
        {
           long mid=left+(right-left)/2;
           long long n=mid*mid;
            if(n>x)
           {
               right=mid-1;
           } 
           else if(n<x)
           {
               left=mid+1;
               ans=mid;//因为不知道mid+1的平方是否还小于x,所以输出得为mid
           } 
           else{
               return mid;
           }
        }
        return ans;
    }
};

1.4总结:

1、什么时候用二分法?
一个有序数组,查找某元素,或是在此查找基础上插入数组中
注意对while循环的利用
2、查找时要分情况讨论,在不在数组中?在数组中的话,属不属于数组元素?不属于数组元素应该返回啥?返回left/right要画个例子看一看。

二、数组移除元素(双指针法)

2.1数组移除所有值为val的元素,并返回数组新长度

给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
1、暴力解法(不断覆盖n2时间复杂度)
在这里插入图片描述

代码如下:

// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下表i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};

2、双指针法
双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
在这里插入图片描述

// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0; 
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {  
            if (val != nums[fastIndex]) { 
                nums[slowIndex++] = nums[fastIndex]; 
            }
        }
        return slowIndex;
    }
};

2.2 双指针法求数组平方

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

数组其实是有序的, 只不过负数平方之后可能成为最大数了。
那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。
此时可以考虑双指针法了,i指向起始位置,j指向终止位置。
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。
在这里插入图片描述

class Solution {
public:
    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;
    }
};

2.3双指针法总结

双指针法的应用可以不占用额外空间和占用额外空间,实质都是对原数组进行两个for循环,两个均从前向后遍历或是两个一个从前往后一个从后向前遍历,将双重循环降为了单次循环for循环,两个变量去控制。

三、滑动窗口

3.1(209)求长度和满足大于s的长度最小的连续子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在返回0.
输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。符合条件的子数组,返回 0。

//暴力解法
int l=INT_MAX;
for(int s=0;s<nums.size();s++)
{
    int sum=0;
    for(int e=s;e<nums.size();e++)
    {
        sum+=nums[e];
        if(sum>=target)
        {
        l=min(l,e-s+1);break;
        }

    }
}
if(l==INT_MAX) return 0;
return l;

滑动窗口:
滑动窗口也是两个变量去控制,很多时候我们不知道两个变量不用双重for循环怎么去控制,可以只对末尾的那个for循环单次遍历,在while判断条件结束时对第一个变量加一。

int sum=0;
for(int e=0;e<nums.size();e++)
{
    sum+=nums[e];
    while(sum>=target)
    {
         l=min(l,e-s+1);
         sum-=nums[s];
         s++;
    }
}
if(l==INT_MAX) return 0;
return l;

四、螺旋矩阵(循环)

4.1(59)输出顺时针排列的正方形

给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
输入: 3
输出:
[
[ 1, 2, 3 ],
[ 8, 9, 4 ],
[ 7, 6, 5 ]
]
先定义四个维度,然后四个for循环,并重新设定上边界、右边界、下边界、左边界。

vector<vector<int>> matrix(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
 int u=0;int d=n-1;int l=0;int r=n-1;
 int count=1;
 while(true)
 {
for(int i=l;i<=r;i++)//从左到右
{
    matrix[u][i]=count++;
}
    u++;
    if(u>d) break;重新设定上边界,若上边界大于下边界,则遍历遍历完成,下同

for(int i=u;i<=d;i++)//从上到下
{
    matrix[i][r]=count++;
}
    r--;
    if(r<l) break;重新设定右边界,若右边界小于左边界,则遍历遍历完成,下同

for(int i=r;i>=l;i--)//从右到左
{
    matrix[d][i]=count++;   
}
    d--;
    if(d<u) break;重新设定下边界,若下边界小于上边界,则遍历遍历完成,下同

for(int i=d;i>=u;i--)//从下到上
{
    matrix[i][l]=count++;
}
    l++;
    if(l>r) break;//重新设定左边界,若左边界大于右边界,则遍历遍历完成,下同

}
return matrix;
    }
};

4.2(54)输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

道理和上述一样,但是有几点需要注意:
首先要判断的就是矩阵是否为空,为空如何处理?而且要把是否为空放在判断四个边界的上面,因为为空matrix[0]就不存在了,执行出错;另外在for循环执行结束后,是先进行加一减一的操作再判断是否大于边界,即++u>d,break;

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> ans;
        if(matrix.empty()) return ans;
        int u=0;int d=matrix.size()-1;;int l=0;int r=matrix[0].size()-1;


        while(true)
        {
            for(int i=l;i<=r;i++)
            {
                ans.push_back(matrix[u][i]);
            }
            if(++u>d) break;
            for(int i=u;i<=d;i++)
            {
                ans.push_back(matrix[i][r]);
            }
            if(--r<l) break;
            for(int i=r;i>=l;i--)
            {
                ans.push_back(matrix[d][i]);
            }
            if(--d<u) break;
            for(int i=d;i>=u;i--)
            {
                ans.push_back(matrix[i][l]);
            }
            if(++l>r) break;
        }
        return ans;
    }
};

4.3循环总结

先定义四个维度,然后四个for循环,并重新设定上边界、右边界、下边界、左边界。但是有几点需要注意:
首先要判断的就是矩阵是否为空,为空如何处理?而且要把是否为空放在判断四个边界的上面,因为为空matrix[0]就不存在了,执行出错;另外在for循环执行结束后,是先进行加一减一的操作再判断是否大于边界,即++u>d,break;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值