一、数组理论基础
-
数组是存放在连续内存空间上的相同类型数据的集合。
-
数组下标都是从0开始的。
-
数组内存空间的地址是连续的。
-
数组的元素是不能删的,只能覆盖。
二、LeetCode704 二分查找
力扣题目链接:https://leetcode.cn/problems/binary-search/
看到题目的第一眼,虽然知道二分法,但是不熟悉,第一眼想法就是暴力解法,用for循环将target跟数组中的每个值比较,显然效率是比较低的。
看完代码随想录之后,知道了二分法的原则之后,思路会更清晰,也不用死记硬背了
主要有一个疑问点,就是用mid = (left + right)/ 2时,为什么有时候会越界?且左闭右闭版本使用mid = left + ((right - left) / 2),左闭右开版本使用mid = left + ((right - left) >> 1)?
解答:(后续再研究)
题眼:
- 有序数组
- 无重复元素
见到这两点可以想二分法。
要点:
- 区间的定义:不变量
- 循环不变量法则:也就是说一旦确定了区间定义,在while循环中要坚持这个定义的不变量
要点解释:
如果一开始定义区间为[left,right],那么while循环的条件要使用 left <= right,因为在left == right时,在这个区间是有意义的;
如果一开始定义区间为[left,right),那么while循环的条件要使用 left < right,因为在left == right时,左边说该区间包含这个值,右边又说该区间是不包含这个值,故显然是没有意义的;
写二分法,区间定义一般为两种:左闭右闭、左闭右开
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
//暴力解法
int search(vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); i++)
{
if (nums.at(i) == target)
return i;
}
return -1;
}
//二分法,左闭右闭
int search1(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
{
return mid;
}
}
return -1;
}
//二分法,左闭右开
int search2(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); //右开----[left,right)
while (left < right)
{
int mid = left + ((right - left) >> 1); //??
if (nums[mid] > target)
{
right = mid;
}
else if (nums[mid] < target)
{
left = mid + 1;
}
else
{
return mid;
}
}
return -1;
}
//自己尝试,二分法,左开右闭-----还不知道是否正确
int search3(vector<int>& nums, int target) {
int left = -1; //左开
int right = nums.size() - 1;
/*当left == right时
左边说该区间包含这个值,右边又说该区间是不包含这个值
显然是没有意义的,故不取等*/
while (left < right)
{
int mid = (left + right) / 2;
if (nums[mid] > target) //target在左区间,更新right的值
{
right = mid - 1; //右闭
}
else if (nums[mid] < target)
{
left = mid; //左开
}
else
{
return mid;
}
}
return -1;
}
};
int main()
{
Solution solution;
vector<int> array = { -1,0,3,5,9,12 };
int result = solution.search(array,12);
int result1 = solution.search1(array, 12);
int result2 = solution.search2(array, 12);
int result3 = solution.search2(array, 12);
cout << result << endl;
cout << result1 << endl;
cout << result2 << endl;
cout << result3 << endl;
}
有关二分法,感觉理解还是不是很透彻,因为实在是时间过于紧迫,后续还会再研究。
三、LeetCode 27移除元素
暴力解法
刚开始暴力解法都没有思路,看了代码随想录的动图才有思路写完
写完之后去力扣运行发现超时,代码如图:
//正解
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
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; j < nums.size() - 1; j++)
{
nums[j] = nums[j + 1];
}
i--; //所有元素都向前移动了一位,如果i不向前移动一位的话,这一轮结束后i++,会漏掉该元素
size--; //数组大小-1
}
}
return size;
}
};
双指针法
力扣题目链接:https://leetcode.cn/problems/remove-element/
刚开始看完全没有思路,看了文字版的也不是很理解,看完视频才慢慢理解这种思想
核心要点就是要理解快指针和慢指针的用处、含义
- 快指针:寻找新数组中的元素,新数组就是不含要移除的元素的数组,快指针会遍历整个数组
- 慢指针:指向更新 新数组下标的位置,即指向新数组所需要元素
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];
nums[slowIndex] = nums[fastIndex]; //此时快指针指向的元素是新数组需要的,于是赋值给慢指针所指的下标
slowIndex++; //随后慢指针指向新数组的下一个元素
}
//如果此时快指针指向的元素是要移除的元素,则不赋值给慢指针指向的元素,故不进入if判断里面的内容
}
//最后快指针遍历完整个数组,便将要移除的元素覆盖了,直接返回慢指针的值即可
return slowIndex;
}
}
用例:
-1 | 0 | 3 | 5 | 9 | 12 | 3 | 8 | 2 | 3 | 9 |
---|
移除元素3
自己可以对该例子,多思考几次,走几次流程。
四、额外知识—库函数erase
- 底层原理即是双指针法
仅了解,后续学语法时再研究。