前言:
为了鼓励自己学习算法相关的知识,选择跟代码随想录组织的为期60天训练营一起将常见的算法知识点和题目过一遍,今天是打卡第一天,主要学习的内容是二分查找和移除元素。
训练营涉及的内容和知识作者都已开源,自己记录的一些图可能直接来源于已开源的内容,如需要观看更详细的内容,可以参考代码随想录
目录
一、数组相关的知识
在数据结构的体系中,数组的定义是:
数组是存放在连续内存空间上的相同类型数据的集合。
个人理解下,所谓数据结构也就是如何去使用计算机所适配的内存管理机制去高效地组织不同类型的数据,而对任何的数据结构来说,学习的重点无非就是“增、删、改、查”这四个方面的内容,数组为了达到这个目的,建立了索引的机制,更方便以人的角度去理解在计算机内存空间上连续的这一串数据。
对于数组而言,有两个值得注意的地方:
- 其索引的下标是从0开始的
- 且存储在数组内部的空间是连续的
由于存在第二个特性,因而数组在内部进行数据的插入或者删除的时候,需要对此处地址向后的数据进行向前移位的操作:
比如这里想要删除103内存的数据J,在它被删除以后,后面的HJAB都向前移动了一位,进而保持数组存储数据在内存上的连续性。这也体现了,对于数组来说,数组的元素是不能删除的,而是直接将后一位不断向前覆盖得到的新元素。
对于二维数组而言,C++的存储方式下,其内存分布是连续的,每一个int类型的二维数组之间会相差4个字节,而相反,java 的内存就不是连续的。
二、题目打卡
2.1 二分查找
对应的leetcode题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
根据之前自己学习的一些二分的知识,直接进行答案的撰写,在这之前并没有接触过“左闭右开,左闭右闭”的概念,撰写的过程中自己会踩的一个坑是while的判断条件在第一次写的时候不是很确定,导致了错误,然后结合数据的案例,自己演练了一下发现了问题,是对区间的把控不是很准确。
class Solution {
public:
int search(vector<int>& nums, int target) {
int i = 0, j = nums.size() - 1;
while(i <= j){
int m = (i + j) / 2;
if(nums[m] == target) return m;
else if(nums[m] < target) i = m + 1;
else j = m - 1;
}
return -1;
}
};
接着阅读了代码随想录的内容,自己理解了一下“左闭右开,左闭右闭”,发现自己上面的做法是左闭右闭的方法,理解了一下左闭右开的方法,自己写了一次。
class Solution {
public:
//左闭右开
int search(vector<int>& nums, int target) {
int i = 0, j = nums.size();
while(i < j){
int m = (i + j) / 2;
if(nums[m] == target) return m;
else if(nums[m] > target) j = m;
else i = m + 1;
}
return -1;
}
};
对于这个条件里面的不同进行了对应的思考,首先是 i < j 这个条件,有两种理解的方法,第一个是本身定义的是一个右开的区间,那么 i == j 是没有意义的(这个是代码随想录的说法),对我来说不完全具有说服力,应该是自己没有理解到这个点,进一步用案例进行考虑,就是我尝试的第二种理解方法:
如果查找的结果为空,也就是需要返回-1时,这时可能会存在一种情况就是 i 是一直在涨的,但是由于 j 并 没有发生改变,那么就会导致无法跳出循环,比如输入的数组是 [-1,0,3,5,9,12],而 target 是 2 ,这时候 i 和 j 会一直在索引2的位置无法跳出,因为 nums[m]此时始终大于2,因而会不断执行 j = m ,导致 i == j 始终成立。
感想:
在题目条件写到有序数组以及数组无重复元素时对元素的查找时,可以往二分法的方向进行思考,其本质是不断划分为更小的搜索区间,这时候比较需要注意的问题是区间的闭合问题,做题和看过视频以后自己总结的,当开始的时候是闭区间的时候,那么是两端都进行减加1的操作,如果是右侧开区间,那么只需要左侧进行加1的操作,简记为,右开左加....感觉不流畅,算了不总结口诀了,差不多是那个意思🥲
2.2 移除元素
对于这个题目,按照自己的想法进行实现的时候,出现了一点问题,我尝试了暴力解法没有成功,其中出现了不限于越界,逻辑的问题,前者主要是循环条件设置的不是很合理,对数值的变化理解不到位,后者是emmm,反正没解决,大概就是我没处理好后者向前覆盖的过程,然后理解了一下答案双指针的方法,然后自己写了一下。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
// 一些错误的尝试
// int change_times = 0;
// for(int i = 0; i < nums.size(); i++){
// // 需要向后一直找到不为val的索引为止,所以用while不用if
// if(nums[i] == val){
// int j = i;
// while(nums[j] == val){
// change_times++;
// j++;
// }
// for(int it = i;j < nums.size() - 1; j++){
// nums[it] = nums[j];
// }
// }
// }
// return nums.size() - change_times;
// int i = 0, j = 0, len = nums.size();
// while(true){
// while(nums[i] != val) i++;
// if(i >= nums.size()) break;
// j = i;
// while(nums[j] == val){
// j++;
// len--;
// }
// for(int it = i; it < j; it++){
// nums[i] = nums[j];
// i++;
// j++;
// }
// if(i >= nums.size() || j >= nums.size()) break;
// }
// return len;
// int i = 0, j = 0, len = nums.size();
// while(i < nums.size()){
// if(nums[i] == val){
// len--;
// j = max(i + 1,j);
// }
// if(j == nums.size()) break;
// while(nums[j] == val && j < nums.size()) j++;
// while(nums[i] == val && i < nums.size()){
// nums[i] = nums[j];
// // i++;
// j++;
// }
// i++;
// }
// cout << len;
// return len;
// 看了解析以后写的第一个方法
int i = 0, j = 0, len = nums.size();
while(i < nums.size() && j < nums.size()){
if(nums[j] != val){
nums[i] = nums[j];
i++;
}
j++;
}
return i;
// 尝试的第二个方法
int i = 0, j = nums.size() - 1;
while(i <= j){
if(nums[i] == val){
swap(nums[i],nums[j]);
--j;
}
// 这里需要写成或的条件,因为交换过来的i有可能等于val
else ++i;
}
return i;
}
};
然后看了答案,发现自己最开始的暴力解法没有解出来,对照答案理解了一下,写了下面的内容:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int nums_size = nums.size();
for(int i = 0; i < nums_size; i++){
if(nums[i] == val){
for(int j = i + 1; j < nums_size;j++){
nums[j - 1] = nums[j];
}
nums_size--;
i--;
}
}
return nums_size;
}
};
感想:
对于暴力解法,首先,发现自己没有写出来的关键是对于判断条件的理解不够到位,特别关键的是for循环中的判断条件,一开始一直写的是 i < nums.size(),经过debug以后,发现如果这个size没有动态变化的话,因为存在i--的情况,因而会陷入无限循环,因而需要在移除元素以后,对区间进行缩减,也就是利用这里的区间动态大小,第二个,没注意到的是在移除一位以后,需要将i--,这里就是为了避免删除的是两个连续的元素,这样覆盖以后,如果直接进行i++,那么最后就没有删除完所有的移除元素。
对于双指针的解法,重点是理解快慢指针分别是指向的什么内容,而从两端缩减时,需要理解为什么需要设置else的条件,因为交换过来的目标有可能是需要删除的目标值,如果直接进行i++的操作,那么就会出错,所以需要根据条件再次进行判断,如果 nums[i] 依然是目标值,那么就需要再次进行交换。