数组总结篇
什么是数组?
数组是存放在「连续内存空间」上的「相同类型数据」的集合。
数组的特点
索引从 0 开始
内存地址是连续
访问元素:
插入和删除元素:
二分法
二分查找模板 1
public int binarySearch(int[] nums, int target){
int left = 0;
int right = nums.length - 1; // 注意 1
while(left <= right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}
else if(nums[mid] < target) {
left = mid + 1; // 注意
}else if(nums[mid] > target){
right = mid - 1; // 注意
}
}
// End Condition: left > right
return -1;
}
初始化条件:
left = 0
,right = nums.length - 1
,相当于闭区间 ,而这个区间就是我们的「搜索区间」循环停止条件:
nums[mid] == target
如果没有找到的情况下,「搜索区间」不存在。即
left > right
=> ,区间不存在
如果非要用 while(left < right),我该怎么办?
//...
while(left < right) {
// ...
}
return nums [left] == target ? left : -1;
分析如下:
当退出循环时,存在 left == right
,不管是因为什么原因导致的, 左元素还是右元素最终有一个没做判断
left = mid + 1
,所以才有left == right
退出循环right = mid - 1
,所以才有left == right
退出循环
- 向左查找,向右查找:在上面的「搜索区间」情况下,当
nums[mid]
查找不到时,此时mid
已经被判断了。因此下次「搜索区间」应该是 或者
二分查找模版 2:找满足条件的最左侧的值
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length; // 注意
while (left < right) { // 注意
int mid = (left + right) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid; // 注意
}
}
return left;
}
- 初始化条件:
left = 0
,right = nums.length
,每次循环的「搜索区间」是 [left, right) 左闭右开 - 循环停止条件:
left == right
, 区间为空,搜索停止。 - 向左查找:
nums[mid] > target
,nums[mid] == target
都会改变right
的值。其实相当于告诉我们nums[mid]
的值都在target
的右侧。只有这样做,我们才能不断地 缩小「搜索区间」的上界right
,在区间 [left, mid) 中继续搜索,即不断向左收缩,达到锁定左侧边界的目的。
如果 target
在数组中,最后返回的结果 left
表示,数组中小于 target
的元素有 left
个,也可以表示 target
最左的下标为 left
如果 target
不在数组中,假设一种极端情况
[2,5,7,8]
target = 1
在循环过程中,left
一直保持不变,而 right
一直向左边靠近,最终 left == right
最后循环,最后返回 left
= 0。
含义是:数组中小于 1 的元素有 0 个。
综上可以看出,函数的返回值(即 left 变量的值)取值区间是闭区间
while (left < right) {
//...
}
// target 比所有数都大
if (left == nums.length) return -1;
// 类似之前算法的处理方式
return nums[left] == target ? left : -1;
//if(left != nums.length && nums [left] == target){
//return left;
//}
//return -1;
二分查找模版 3:找满足条件的最右侧的值
public int search(int[] nums,int target){
int left = 0;
int right = nums.length;
while(left < right){
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
left = mid + 1; // 注意
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
if(left != 0 && nums[left-1] == target){
return left - 1;
}
return -1;
}
当 nums[mid] == target
时,不要立即返回,而是 增大「搜索区间」的下界 **left**
,使得区间不断向右收缩,达到锁定右侧边界的目的。
由于我们更新 left
是 left = mid + 1
,那就出现一种情况,left = mid + 1 = right
越界退出循环。
nums[left]
一定不等于target
,否则也不会导致left = mid+ 1
的操作发生nums[left-1]
有可能是target
,所以要进行 后处理检查
双指针法
双指针法(快慢指针法):通过一个快指针和慢指针在一个 for 循环下完成两个 for 循环的工作。
滑动窗口
根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将 的暴力解法降为 。
- 最小/最大子数组问题
- 字符串模式匹配问题
- 固定长度的子数组/子字符串问题
- 固定长度的子数组/子字符串问题
模拟行为
模拟类的题目在数组中很常见,不涉及到什么算法,就是单纯的模拟。
循环不变量原则,其实这也是写程序中的重要原则。