数组浅学笔记

数组理论基础:

注:图片和内容均来自代码随想录,仅作为学习笔记使用

代码随想录链接:代码随想录 (programmercarl.com)

数组是存放在连续地址上的相同数据类型的数据集合。

数组的下标从零开始;

数组内存地址空间连续。

由于数组内存空间地址的连续性,在增删元素时,需要将增(删)索引index后的所有数据后移(前移)一位。

vector和array的区别:vector严格来说是容器,其底层为数组。

二维数组:如上,其在c++中的存储空间也是连续的可以看作是一维数组的一维数组(即按行存储);


二分查找:

原题链接:704. 二分查找 - 力扣(LeetCode)

本质是boundary(边界),每次寻找中间位置,判断中间位置处是否满足题目要去,以此来取区间。

模板:来自acwing

bool check() //用于判断是否满足条件;
int b_search1(int l, int r){
    while(l<r){
        mid = (l+r)/2;
        if(check(mid)) r = mid;
        else l = mid+1;
    }
    return l;
}

int b_search2(int l,int r){
    while(l<r){
        mid = (l+r+1)/2;
        if(check(mid)) l = mid;
        else r = mid -1;
    }
    return l;
}

力扣例题:

分析:

数组有序,也就是说可以划分边界(boundary)为>=target和<target的两部分,计算nums[mid]的值,若大于target,取左半边,若小于取右半边。

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

效果如下:

注:

模板是为了快速避免边界问题。


移除元素:

原题链接:. - 力扣(LeetCode)

由于数组在存储空间上是连续的,因此不能够单独地删除元素,而是要使用覆盖。

暴力流:

双重for循环,一个for用来循环遍历所有数组元素,一个用来挪动后继数组元素。

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指针要往前一位
                size--;     //删除了一个元素,所以size的大小要减一
            }
        }
        return size;
    }
};

双指针:

分为两个指针,一个为快指针(指向新数组中存储的元素),一个为慢指针(指向更新数组的下标位置)。

用该方法可以只使用一个for循环语句实现对数组元素的删除操作,是如何实现的呢?

首先当数组元素不等于要删除元素时,快慢指针一起后移,当数组元素等于要删除元素时,快指针继续移动,而慢指针停留在原位置处,直到后继元素不等于要删除元素后继续一起移动。其中慢指针在移动的过程中实际上也实现了赋值功能,具体代码如下。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int lowindex = 0;
        for(int fastindex =0;fastindex<nums.size();fastindex++){
            if(val!=nums[fastindex]){
                nums[lowindex++] = nums[fastindex]; 
                // 为什么这么写?为了实现快慢指针在遇到val时的同步移动
            }
        }
        return lowindex;
    }
};

有序数组的平方:

原题链接:977. 有序数组的平方 - 力扣(LeetCode)

暴力流:

原数组平方后直接进行排序。

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        for(int i = 0;i<nums.size();i++)
            nums[i] = nums[i]*nums[i];
        sort(nums.begin(),nums.end());
        //注意sort第二个参数为尾后迭代器
        return nums;
    }
};

双指针流:

该数组本身便是有序的,使用双指针关注第一个和最后一个元素的大小便可(为整数数组,不存在小于1的分数的情况)。

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

 高精度加法:

#include <iostream>
#include <vector>
#include <string>
using namespace std;


vector <int> AddFunction(vector<int> &A, vector<int> &B){
    if(B.size()>A.size())   return AddFunction(B,A);
    vector<int> C;
    int t = 0; //定义一个辅助进位符,计算为ai+bi+t,前一位无进位为0
    for(int i = 0;i<A.size();i++){
        t+=A[i];
        if(i<B.size())
            t+=B[i];        
        C.push_back(t%10);          //不能使用C[i]来进行幅值,因为在这是并未分配空间只能用push_back;
        t /= 10;
    }
    if(t)
        C.push_back(1);
    return C;
}

int main(){
    string a,b;
    cin >> a>>b;
    vector<int>A,B;
    for(int i = a.size()-1;i>=0;i--)      A.push_back(a[i]-'0');        //为什么要-'0'因为读取的是字符型,为ASCII码,故减去‘0’的ASCII来还原
    for(int i = b.size()-1;i>=0;i--)      B.push_back(b[i]-'0');
    auto C = AddFunction(A,B);
    for(int i =C.size()-1 ;i>=0;i--)      cout<<C[i];
    cout<<endl;
    return 0;
}

// 下面的错误原因,没有考虑到B的位数在小于A时未补0,故在循环内加一个if(i<B.size())便可解决

/*
#include <iostream>
#include <vector>
#include <string>
using namespace std;


vector <int> AddFunction(vector<int> &A, vector<int> &B){
    if(B.size()>A.size())   return AddFunction(B,A);
    vector<int> C;
    int t = 0; //定义一个辅助进位符,计算为ai+bi+t,前一位无进位为0
    for(int i = 0;i<A.size();i++){
        t+=A[i];
        t+=B[i];
        C.push_back(t%10);
        t /= 10;
    }
    if(t)
        C.push_back(1);
    return C;
}

int main(){
    string a,b;
    cin >> a>>b;
    vector<int>A,B;
    for(int i = a.size()-1;i>=0;i--)      A.push_back(a[i]-'0');        //为什么要-'0'因为读取的是字符型,为ASCII码,故减去‘0’的ASCII来还原
    for(int i = b.size()-1;i>=0;i--)      B.push_back(b[i]-'0');
    auto C = AddFunction(A,B);
    for(int i =C.size()-1 ;i>=0;i--)      cout<<C[i];
    cout<<endl;
    return 0;
}
输入:
1000000
1
输出:
14000001
*/

长度最小的子数组:

滑动窗口:

        不断地调节子序列的起始位置与终止位置,从而得出我们想要的结果。

在暴力算法中,一个for循环来遍历起始位置,一个for循环来遍历终止位置。

滑动窗口则通过一个for循环来实现这个操作。

但是只用一个for循环是用来表示起始位置还是终止位置呢?

若表示启示位置,依然需要暴力循环来找到终止位置;

所以选择表示终止位置,下面的关键在于初始位置如何去移动呢,可以在循环的外界定义一个sum值,在for循环寻找终止位置时进行一个累加,当sum>=target时,计算此时的长度,然后将起始位置进行++操作,当sum<target时结束。然后对上述部分进行循环操作,不断更新区间的长度。

(可以看做时双指针的一个变种)

·窗口是什么?——本题中的窗口位>=target的区间;

·如何移动窗口的起始位置?——当窗口的值>=target了就需要向前移动了;

·如何移动窗口的终止位置?——for循环中的索引。

题目链接:. - 力扣(LeetCode)

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int sum = 0;
        int result = 1e9+10; //防止巧合故加10,理论加1便可,习惯。
        int i = 0;
        int length = 0;
        for(int j = 0; j<nums.size(); j++){
            sum += nums[j];
            while(sum >= target){
                length = j-i+1;
                result = result < length ?result : length; //判断result的值是否为历史最短的区间
                sum -= nums[i++];
            }
        }
        if(result == 1e9+10) return 0;
        else return result;
    }
};

螺旋矩阵:

要坚持循环不变量:

模拟顺时针画出改矩形的过程:

·从左往右填充上行;

·从上往下填充右行;

·从右往左填充下行;

·从下往上填充左行。

为什么要坚持循环不变量呢,其实就是一个规范化的问题,如果每次的循环遵循的规则时不一致的很容易导致循环出现问题,在本题中坚持左闭右开的循环原则,结果如下图所示:

当n为奇数时,中间需要额外处理。

当n=1时,不需要循环,当n=2时,循环一次,当n=3时,循环一次,当n=4时循环两次,所以循环次数由n/2来决定。

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        int startx = 0, starty = 0; // 默认循环初始位置为(0,0)
        int loop = n/2;
        int default_loop = 1;   // 默认设置的循环值减数为1
        int i,j;
        int count = 1;
        vector<vector<int>>res(n,vector<int>(n));// 前行后列,村重空间连续,数组的数组
        while(loop--){
            i = startx;
            j = starty;

            for(j;j<n-default_loop;j++){
                res[i][j] = count++;
            }
            for(i;i<n-default_loop;i++){
                res[i][j] = count++;
            }
            for(j;j>startx; j--){
                res[i][j] = count++;
            }
            for(i;i>starty; i--){
                res[i][j] = count++;
            }
            startx ++;
            starty ++;
            default_loop++;
        }
        if(n%2)
            {res[n/2][n/2] = count;}
        return res;
    }
};

 



题目链接:59. 螺旋矩阵 II - 力扣(LeetCode)


STL库——Vector简单介绍:

Vector是STL库中的一种容器(方便记忆可以暂且将其记为动态数组)

1、创建一个vector:

// 在使用stl库中的vector时,要包含头文件
#include <vector>
vector<数据类型> 向量名(向量大小)

//比如声明一个int类型的向量:
vector<int>array1;
vector<int>array2(2);
vector<int>array3 {0,1,2};

2、访问vector中的元素:

和普通定义的数组元素一样使用[]访问,不过vector的[]是operator(重载)后的。

3、迭代器:

下介绍几个比较常用的迭代器

1.begin——返回容器中第一个元素的迭代器;

2.end——返回容器末元素后一个位置的迭代器;

可以这么理解,一个vector的范围为[begin,end)即[begin,end-1]。

3.rbegin——返回逆序的容器的第一个元素的迭代器;

4.rend——返回逆序的容器的最后一个元素前一个位置的迭代器;

5.cbegin——c代表const其余不变;

6.cend——c代表const其余不变;

7.crbegin

8.crend

4、容积:capacity(后根据用到的范围不断补充)

1.size——当前容器的实际大小;

2.capacity——总共可容纳的元素个数;

3.resize——改变size的大小。

5、一些操作:

1.push_back(数值):在vector的末尾(最高位)添加一个元素。

2.pop_back():删除最高位的的元素值。(可以用于消除首0)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江弦凤歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值