算法刷题笔记--数组篇

123.图书整理
vector是C++标准模板库中的部分内容,通常我们喊做“容器”,但并不准确。它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。

vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。

C++之Vector详解|即学即会

vector 类中的 push_back( ) 函数 (添加和删除函数)
push_back,算法语言里面的一个函数名,如:

  1. c++中的vector头文件里面就有这个push_back函数;

  2. 在vector类中作用为在vector尾部加入一个数据;

  3. string中也有这个函数,作用是字符串之后插入一个字符。
    (感觉就是Vector类中的添加方法,不知道为什么写那么玄乎)

void reverse(ListNode* head){
        if(head==nullptr){
            return;
        }
        reverse(head->next);
        res.push_back(head->val);
    }

题中的reverse(head->next);不能用head=head->next; reverse(head);代替
原因是用后者时当head=null时会从递归中跳出开始回溯,但此时的head依旧指向null,这就导致res导入一个空指针的值然后报错
用前者时则不会,因为此时head的指向不为空

递归
在这里插入图片描述

  • 递归函数定义
    明确函数的使命
    明确原问题和子问题
    兼顾原问题和子问题

  • 基础情况处理
    数据规模较小时直接返回答案

  • 递归调用
    超级操作

  • 递推到当前层
    微操作

递归法:
宗旨就是紧紧抓住原来的函数究竟返回的是什么?作用是什么即可,其余的细枝末节不要细究,编译器会帮我们自动完成
把递归拆解成,“递”和”归“ 两部分,根据业务判断,从头到尾每次递给下一层该是什么参数,从尾到头归来需要什么样的放返回值

unordered_set
寻找相交链表的结点
无序集(unorder sets)是一种不按特定顺序存储唯一元素的容器,允许根据元素的值快速检索单个元素。

在unordered_set中,元素的值同时也是唯一标识它的键。键是不可变的,因此,unordered_set中的元素在容器中不能被修改,但是它们可以被插入和删除。

在内部,unordered_set中的元素并不按照任何特定的顺序排序,而是根据它们的散列值组织到桶中,从而允许根据它们的值直接快速访问单个元素(平均时间复杂度为常数)。

与set容器相比,Unordered_set容器通过键访问单个元素的速度更快,尽管它们通常在通过元素的子集进行范围迭代时效率较低。

unordered_set基本用法
C++常用语法——unordered_set

unordered_map
涉及到C++STL相关的知识
寻找文件副本
unordered_map 是关联容器,含有带唯一键的键(key;it->first)-值(value;it->second) pair 。搜索、插入和元素移除拥有平均常数时间复杂度。

元素在内部不以任何特定顺序排序,而是组织进桶中。元素放进哪个桶完全依赖于其键的哈希。这允许对单独元素的快速访问,因为一旦计算哈希,则它准确指代元素所放进的桶。

C++中的unordered_map用法详解
在这里插入图片描述
大数越界问题
实现一个十进制数字报数程序,请按照数字从小到大的顺序返回一个整数数列,该数列从数字 1 开始,到最大的正整数 cnt 位数字结束。
报数
当 cnt较大时,end 会超出int32 整型的取值范围,超出取值范围的数字无法正常存储。

数组

数组是存放在连续内存空间上的相同类型数据的集合。

数组下标都是从0开始的。
数组内存空间的地址是连续的

正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。

C++中二维数组是连续分布的

二分查找

以后只要看到面试题里给出的数组是有序数组,都可以想一想是否可以使用二分法
同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的。
要在二分查找的过程中,保持不变量,这也就是循环不变量

循环不变量:在循环的过程中保持不变的性质
循环不变式主要用来帮助我们理解算法的正确性。关于循环不变式,我们必须证明三条性质:

  • 初始化:循环的第一次迭代之前,它为真。
  • 保持:如果循环的某次迭代之前它为真,那么下次迭代之前它仍为真。
  • 终止:在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的

二分查找时考虑两点边界问题
定义了什么样的区间就意味着边界怎么写
只有在循环中坚持对区间的定义,才能清楚的把握循环中的各种细节。
要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
1.循环的条件是left<right or left<=right
根据区间判断left=right是否合法
2.left=middle+1 or left=middle
根据区间判断left=middle是否合法
704二分查找
视频讲解

删除数组元素

要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。

暴力解法:双层循环,外循环遍历数组元素,内循环将删除元素后面的元素逐个前移
在暴力解法时遇到一个问题
我的内循环如下所示,在力扣中会报错

 if(nums[i]==val){
                for(int j=i;j<sum;j++){
                    nums[j]=nums[j+1];
                }
                i--;
                sum--;
            }

原因是j+1可能会超出数组的范围,应改为

if(nums[i]==val){
                for(int j=i+1;j<sum;j++){
                    nums[j-1]=nums[j];
                }
                i--;
                sum--;
            }

双指针法

快指针是为了获取新数组中的元素,慢指针获取我们新数组中需要更新的位置

理解了好几遍,总是有种奇怪的感觉,事实上不应该叫什么快慢指针,应该叫读写指针才对,因为事实上在循环中遇到要删除的值之前,写指针是比读指针先一步变化的,也就是说在这种时候写往往会走在读的前面
在删除目的元素后,才会表现出写在读的后面

双指针法类似题目
移动零
刚开始写的方法直接将元素覆盖,导致数组后的元素并不是0,写了一个循环直接将write当前指向的元素到数组末尾的元素都置为0,可以成功运行

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int read=0,write=0;
        while(read<nums.size()){
            if(nums[read]!=0){
                nums[write]=nums[read];
                write++;
            }
            read++;
        }
        while(write<nums.size()){
            nums[write]=0;
            write++;
        }
        
        
    }
};

看题解后改进版
直接将覆盖改为替换,就可以达到相同的效果

swap(nums[write],nums[read]);

长度最小的子数组

数组操作中另一个重要的方法:滑动窗口。

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。

滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

在这里插入图片描述

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result = INT32_MAX;
        int sum = 0;
        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;
                sum -= nums[i];
                i++;
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};

4.29日更新

在这次复习时将length = (j - i + 1);这条语句放在了外层循环中导致内容出错
该代码需要在进入内层循环时更新length的数据并进行判断,如果将他放在外层就会导致只会在外层循环时更新该值,因此在内层进行判断时得到的长度数据并不是当前正确length数据,而是比会它大1

螺旋矩阵

为什么要通过n/2计算圈数?
一个边长为n的正方形矩阵,每次转完外圈,螺旋向内收缩时,边长收缩为n1=n-2;挡转完这个圈,再向内时,边长再度收缩为n2=n1-2;以此类推。也就是n这个数包含几个2,就需要转几圈。也就是和n/2有关。

当n为奇数,需要转的完整圈数也是n/2,但是最后肯定回缩圈到一个3x3的矩阵,最后一个数字放在最中央。但是严格来说应该是(n/2)+1圈,最后一圈缩到一个1x1的矩阵。
在这里插入图片描述

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        //左闭右开,循环不变量
        //n是奇偶分开判断
        vector<vector<int>> nums(n,vector<int>(n,0));// 使用vector定义一个二维数组
        int startx=0,starty=0;// 定义每循环一个圈的起始位置
        int loop=n/2;
        int mid=n/2;
        int offect=1;
        int count=1;
        int i,j;
        while(loop--){
            i = startx;
            j = starty;
            //四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for(j=starty;j<n-offect;j++){
                nums[startx][j]=count;
                count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for(i=startx;i<n-offect;i++){
                nums[i][j]=count;
                count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for(;j>starty;j--){
                nums[i][j]=count;
                count++;
            }
            // 模拟填充左列从下到上(左闭右开)
            for(;i>startx;i--){
                nums[i][j]=count;
                count++;
            }
            //更改起始点xy
            startx++;
            starty++;
            offect++;
        }
         //如果n是奇数则需要在最后单独赋值
            if(n%2==1){
                nums[mid][mid]=count;
            }
        return nums;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值