本系列总计六篇文章,是 基于STL实现的笔试题常考七大基本数据结构 该文章在《代码随想录》和《labuladong的算法笔记》题目中的具体实践,每篇的布局是这样的:开头是该数据结构的总结,然后是在不同场景的应用,以及不同的算法技巧。本文是系列第一篇,介绍了数组的相关题目,重点是要掌握双指针算法、二分查找算法。
下面文章是在《代码随想录》和《labuladong的算法笔记》题目中的具体实践:
【笔记】数组
【笔记】链表
【笔记】哈希表
【笔记】字符串
【笔记】栈与队列
【笔记】二叉树
0、总结
- 双指针,快慢双指针(用于数组覆盖),左右双指针(二分查找),滑动窗口(长度最小子数组)
- 有序数组+无重复,必然想到 二分查找 及其变体,找target,找左边界,找右边界,需要坚持闭区间
- 用循环不变量的模拟(螺旋数组)
1、二分查找-左右双指针
算法框架
注意,…号出现的四处位置,根据是否闭区间/左闭右开,是否返回一个索引/索引边界,而不同,下面我都坚持闭区间的写法
int binarySearch(int[] nums, int target) {
int left = 0, right = 1、...;
while(2、...) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
3、...
} else if (nums[mid] < target) {
left = 3、...
} else if (nums[mid] > target) {
right = 3、...
}
}
return 4、...;
}
704. 二分查找 - 力扣(LeetCode)
思路:二分查找最基础版本,采用左闭右闭区间,因此注意这四处
1、high初始化指向最末尾元素
2、while中的判断是 <= ,因为当 left == right 时区间依然有意义
3、mid每次必须加减1,相等即返回
4、未找到返回-1
另外,写成 if - else if 形式,将逻辑分支判断展开,更有助于理解
【不足】若target有多个值,该解法只能返回其中一个值
class Solution {
public:
int search(vector<int>& nums, int target) {
int low = 0;
int high = nums.size() - 1; // 1
// 当left == right,区间[left, right]依然有效,所以用 <=
while (low <= high) {
// 2
int mid = low + (high - low) / 2;
if (nums[mid] > target) high = mid - 1; // 3
if (nums[mid] < target) low = mid + 1; // 3
if (nums[mid] == target) return mid; // 3
}
return -1; // 4
}
};
35. 搜索插入位置 - 力扣(LeetCode)
思路:二分法的变体,能处理如下四种情况:
- 目标值在数组所有元素之前
- 目标值等于数组中某一个元素
- 目标值插入数组中的位置
- 目标值在数组所有元素之后
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int low = 0;
int high = nums.size() - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[mid] > target) high = mid - 1;
if (nums[mid] < target) low = mid + 1;
if (nums[mid] == target) return mid;
}
return high + 1;
}
};
!!!以上两种情况,只适合无重复元素+数组升序的版本
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
思路:二分查找变体,能找到target的左右边界
寻找target在数组里的左右边界,有如下三种情况:
- 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
- 情况二:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
- 情况三:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
注意:寻找右边界时,nums[mid] > target时,直接减小right = mid - 1,**当 nums[mid] == target
时,不要立即返回,而是增大「搜索区间」的左边界 left
,通过left = mid + 1,rightBorder = left,使得区间不断向右靠拢,达到锁定右侧边界的目的,nums[mid] < target时,left = mid + 1,因此小于和等于target的情况合并为一种。**左边界同理。
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
cout << leftBorder << endl;
cout << rightBorder << endl;
// 情况一,边界值从未被更新,说明 target 在数组范围的右边或者左边
if (leftBorder == -2 || rightBorder == -2) return {
-1, -1};
// 情况二,边界值相差大于1,说明中间至少夹着一个target
if (rightBorder - leftBorder > 1) return {
leftBorder + 1, rightBorder - 1};
// 情况三,边界值相邻,target 在数组范围中,但是数组中不存在target
return {
-1, -1};
}
// 寻找右边界,不包括target
int getRightBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
// 记录初始值
int rightBorder = -2;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] <= target) {
// 寻找右边界,找到target时先别返回,继续更新left
left = mid + 1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
// 记录初始值
int leftBorder = -2;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] >= target) {
// 寻找左边界,用 right 来逼近
right = mid - 1;
leftBorder = right;
}
}
return leftBorder;
}
};
69. x 的平方根 - 力扣(LeetCode)
思路:二分查找变体,在1到x中,用二分查找,寻找x的平方根。避免平方运算溢出,应用除法代替。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
class Solution {
public:
int mySqrt(int x) {
if (x == 0 || x == 1) return x;
int left = 1;
int right = x;
while (left <= right) {
int mid = left + (right - left) / 2;
// 用除法判断,避免溢出
int temp = x / mid;
if (temp > mid) {
// mid平方小于x,增大左边界
left = mid + 1;
} else if (temp < mid) {
// mid平方大于x,减小右边界
right = mid - 1;
} else if (temp == mid)