算法刷题笔记--数组篇
vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。在内部,unordered_set中的元素并不按照任何特定的顺序排序,而是根据它们的散列值组织到桶中,从而允许根据它们的值直接快速访问单个元素(平均时间复杂度为常数)。在unordered_set中,元素的值同时也是唯一标识它的键。键是不可变的,
123.图书整理
vector是C++标准模板库中的部分内容,通常我们喊做“容器”,但并不准确。它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。
vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。
vector 类中的 push_back( ) 函数 (添加和删除函数)
push_back,算法语言里面的一个函数名,如:
-
c++中的vector头文件里面就有这个push_back函数;
-
在vector类中作用为在vector尾部加入一个数据;
-
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;
}
};
更多推荐
所有评论(0)