1.二分法
二分法查找使用前提:有序数组,无重复元素
相关知识:vector的使用 动态分配数组 push_back,pop_back()
vector<int> v = {1, 2, 3};
v.push_back(4); // 添加 4
v.pop_back(); // 删除最后一个元素
v.insert(v.begin(), 0); // 在开头插入 0
v.erase(v.begin()); // 删除开头的元素
v.clear(); // 清空所有元素
vector<int> v2 = {5, 6, 7};
v.swap(v2); // 交换内容
解题代码
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0;
int right=nums.size()-1;
while(left<=right){
int middle=left+((right-left)/2);
if(target==nums[middle])
{
return middle;
}
else if(target<nums[middle])
{
right=middle-1;
}
else if(target>nums[middle])
{
left=middle+1;
}
}
return -1;
}
};
易错点:1.循环条件left<=right,等于仍有效,当数组只有一个元素时
2.关于数组的溢出问题
3.关于数组的左闭右开,左开右闭问题
需要修改的点、
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
2.移除元素(经典解法)
采用快慢指针解题,移除数组元素
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow=0;
for(int fast=0;fast<nums.size();fast++)
{
if(nums[fast]!=val)
{
nums[slow++]=nums[fast];
}
}
return slow;
}
};
注意点:1.return slow代表返回新数组的长度
2.该算法实现的是nums.erase()的底层逻辑,要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
3.
- 时间复杂度:O(n) 每个元素只被处理一次
- 空间复杂度:O(1) 仍使用原数组储存新元素
3.有序数组的平方
题目链接:977. 有序数组的平方 - 力扣(LeetCode)
文章讲解:代码随想录
视频讲解: 双指针法经典题目 | LeetCode:977.有序数组的平方_哔哩哔哩_bilibili
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int k=nums.size()-1;//新数组的最大索引
vector<int> result(nums.size(),0);//首先定义一下要返回的数组
int i=0;
int j=nums.size()-1;
while(i<=j)
{
if(nums[i]*nums[i]>nums[j]*nums[j])
{result[k--]=nums[i]*nums[i];
i++;}
else
{
result[k--]=nums[j]*nums[j];
j--;
}
}
return result;
}
};
加深对双指针的认识
时间复杂度和空间复杂度
-
时间复杂度:O(n)
- 双指针遍历整个数组,每个元素只被比较和处理一次。
-
空间复杂度:O(n)
- 使用额外的数组
result
存储平方结果。
- 使用额外的数组
总结
可以使用双指针简化的题目:
1. 两端向中间收敛(对撞指针)
适用场景:
- 处理有序数组或排序后的数组,解决与区间、和、积相关的问题。
- 主要用于查找满足某种条件的两个元素或多组元素。
典型问题:
-
求和问题:
- 如 两数之和(Two Sum) 的变种:数组有序,求两个数的和等于目标值。
- 思路: 用两个指针分别从数组两端向中间移动,调整两指针的和,直到找到目标值。
int twoSum(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; while (left < right) { int sum = nums[left] + nums[right]; if (sum == target) return {left, right}; else if (sum < target) left++; else right--; } return {}; }
-
三数之和问题:
- 如 三数之和(Three Sum):找到数组中三数相加等于零的所有组合。
- 思路: 固定一个数字,然后对剩下的数组使用对撞指针寻找两数之和。
-
有序数组中的平方排序:
- 如 977. 有序数组的平方。
- 思路: 从数组两端开始比较平方值,将较大的平方值放入结果数组的尾部。
2. 快慢指针(双指针中的一快一慢)
适用场景:
- 用于在数组或链表中查找循环、跳跃、间隔等问题。
- 常用于判定某些条件是否成立,例如是否有环、元素位置重排、过滤等。
典型问题:
-
移除元素:
- 如 27. 移除元素:删除数组中所有等于某值的元素,返回剩下元素的长度。
- 思路: 快指针遍历数组,慢指针记录有效元素的下标,快指针将非目标元素赋值到慢指针处。
-
判断链表是否有环:
- 如 141. 环形链表。
- 思路: 快慢指针,快指针每次走两步,慢指针每次走一步。如果链表有环,快慢指针最终会相遇。
-
寻找链表的中间节点:
- 思路: 快指针一次移动两步,慢指针一次移动一步,当快指针到达链表末尾时,慢指针正好在中间位置。
-
3. 滑动窗口(窗口双指针)
适用场景:
- 主要用于处理子数组或子字符串的问题,例如求固定窗口大小的最值、最优子区间等。
- 窗口是动态调整的,窗口的范围由两个指针控制。
- 等.......