数组理论基础
知识点
- 数组是存放在连续内存空间上的相同类型数据的集合
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的。
- 删除下标为3的元素,需要对下标为3的元素后面的所有元素都要做移动操作
- 二维数组在空间中的地址也是连续的
- 下一元素地址 = 该元素地址 + 元素所占大小
704.二分查找
该题的前提条件:给定的数组元素是升序的,而且数组内没有重复元素。
题目链接:704. 二分查找 - 力扣(LeetCode)https://leetcode.cn/problems/binary-search/description/
个人思路:直接简单暴力(效率极低)
int search(int* nums, int numsSize, int target) {
for (int i = 0; i < numsSize; i++) {
if (nums[i] == target) {
return i;
}
}
return -1;
}
注意:核心代码模式下,表示数组大小 numsSize
二分查找法
思路
定好左右边界(左闭右开或者左闭右闭),计算数组中间值mid,用mid表示的数组元素与目标值target比较,根据二者的大小关系不断缩小比较的边界
难点
- while(left <right)还是 while(left <=right)
- 缩小边界范围,直接等于mid还是mid + 1
情况一:(左闭右闭)
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) / 2;
if(nums[mid] > target) {
right = mid - 1;
}
else if(nums[mid] < target ) {
left = mid + 1;
}
else if (nums[mid] == target) {
return mid;
}
}
return -1;
}
};
代码分析:
- while() 由于是左闭右闭型(判断是哪种类型,看初始边界设定),循环条件应该为(left< =right),比如如果边界为[1,1],left = right成立,二者都能选中,条件合理。
-
缩小边界范围:以nums[mid]>target为例,因为大于,所以在边界范围中就没有mid,而且是左闭右闭,则将right = mid - 1即可
注意:
- c++表示数组的大小:nums.size()
- mid计算:(l+r)/2(慢) l+(r-l)/2(快且不会减少溢出风险)
- 进行判断,有大于,等于,小于三种情况,全部列出,并以else if的形式写出,不用写else
情况二:(左闭右开)
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] > target) {
right = mid;
}
else if(nums[mid] < target) {
left = mid + 1;
}
else if(nums[mid] == target) {
return mid;
}
}
return -1;
}
};
代码分析:
- while(),因为左闭右开型,循环条件为left<right,不能等于,如果边界条件为[1,1),不能出现既取到1又取不到1的情况
- 缩小边界范围:以 nums[mid] > target为例,因为大于,所以在边界范围中就没有mid,而且是左闭右开,所以直接取right = mid即可
现在是2024.10.2,23点,刚加入训练营,以前从来没有这样刷过题,感觉比较生疏,效率太低了,不过我觉得还是非常有效的,今天能做多少做多少,相信明天的自己比今天的更好!
27.移除元素
题目链接:
力扣—27.移除元素
假设 nums
中不等于 val
的元素数量为 k
,要通过此题,您需要执行以下操作:
-
更改
nums
数组,使nums
的前k
个元素包含不等于val
的元素。nums
的其余元素和nums
的大小并不重要。 -
返回
k
。
-
暴力法
个人思路尝试:
循环暴力解决,新数组长度 = 原数组长度 —覆盖次数
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int k = 0;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == val) {
nums[i + 1] = nums[i];
k++;
}
}
return k;
}
};
代码疑点:
- 本代码中只使用一层循环,该循环用于遍历数组,但是元素的删改需要不断的用后一个元素删除前一个元素,需要再用一层循环,进行元素删除覆盖。
- 删除覆盖时,如果用nums[i + 1] = nums[i],数组中的最后一个元素会被数组外的空间替代,边界处理出现问题
正确代码:
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--;
size--;
}
}
return size;
}
};
代码分析:
- 用size代替数组长度函数nums.size(),以便后续数组的操作
- 使用双循环,一层用于遍历数组,另一层用于覆盖元素,时间复杂度为O(n^2)
- 修复了边界问题
- i--的解释:因为i之后的数组元素整体向前移动一位,如果不i--,下一步会进入第一层循环,进行i++,则新移动到i处的元素不会被判断,因此先i--,然后进入循环的i++,这样就不会漏掉数据
- 简化求新数组长度的代码,每进行一整个覆盖操作(整体移动前一行),数组中少一个元素,长度减一即可。
- 新增:返回的是size的值,在分析代码的过程中,发现:如果在数组的末尾有需要删除的数,就无法进行覆盖删除的操作,思考过后发现,由于每次删除覆盖都会改变size和i,j的大小,经过操作,i不会到达无法被删除的位置,而且题目要求是直接返回新数组的大小即可,不需要管没被删掉的元素。
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
看了卡尔的视频醍醐灌顶啊我去!!!
忘记写赋值的条件了,果然还是要自己写一遍呜呜呜
错误代码
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowindex = 0;
for (int fastindex = 0; fastindex < nums.size(); fastindex++) {
nums[slowindex++] = nums[fastindex];
}
return slowindex;
}
};
思路:先要搞清楚快慢指针的作用,快指针要获取新数组元素,并且要赋值给慢指针,相当于种花,而慢指针指的是新数组下标的位置,相当于花盆。
正确代码
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;
}
};
代码分析:
- nums[slowindex++] = nums[fastindex],表示先赋值再加,这里的加表示为下一个元素的存放做准备,相当于准备花盆,先有花盆才有花
- 返回的slowindex正好就是新数组的大小
- 时间复杂度:O(n)
977.有序数组的平方
题目链接: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];
}
}
};
代码分析:
把数组中的每一个数都平方,因为有负数存在,平方后要重新排序,再输出
疑点:不会排序,不会输出数组
正确代码:
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());
return nums;
}
};
这里涉及到一个快速排序,先去补一下,然后再看
双指针法
自己的代码
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> result(nums.size(), 0);
int i = 0;
int j = nums.size() - 1;
for (int k = result.size(); k > 0; k--) {
if(nums[i] * nums [i] < nums[j] *nums[j]) {
result[k] = nums[j] * nums[j];
j--;
}
else if (nums[i] * nums [i] > nums[j] * nums[j]) {
result[k] = nums[i] * nums[i];
i++;
}
}
return result;
}
};
在这里创建了一个新的数组,不知道是什么意思vector<int> result(nums.size(), 0);
代码分析:
- int k本质上是新数组的下标,旧数组的大小和新数组的大小相等,所以k的值等于nums.size() - 1,并且循环结束的范围是k大于等于0
- 进行判断操作时,因为题中说的是非递减数组,因此数组中的元素关系为递增,也可能有个别元素相等,相等的时候,j--和i++都可以,为了方便,把两个情况归作一类,故直接使用if-else即可