数组有两点要注意:
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的。
正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址(不能直接插入或删除)。
由于数组中的元素在内存中的地址是连续的,因此只需要使用O(1)的时间就可以随机访问数组中的任意元素。
数组经典题目常用到的方法有: 二分法、双指针法、滑动窗口、模拟行为
1.二分法
二分法的前提条件:被查数据必须有序(升序或降序)
二分法的三种情况(所有模板)
- 第一种:最基本情况
int binary_search(int[] nums,int target) {
int left=0,right=nums.length-1;
while(left<=right) {
int mid=left+(right-left)/2;
if(nums[mid] < target) {
left=mid+1;
}else if(nums[mid]>target){
right=mid-1;
}else if(nums[mid]==target) {
return mid;//直接返回
}
}
return -1;// 直接返回
}
- 第二种:取左边界(最左边的)
int left_bound(int[] nums,int target){
int left=0,right=nums.length-1;
while(left<=right) {
int mid=left+(right-left)/2;
if(nums[mid]<target){
left=mid+1;
}else if(nums[mid]>target) {
right=mid-1;
}else if(nums[mid]==target) {
right=mid-1;// 别返回,锁定左侧边界
}
}
// 最后要检查 left 越界的情况
if(left>=nums.length||nums[left]!=target)
return -1;
return left;
}
- 第三种右边界(最右边的)
int right_bound(int[] nums, int target) {
int left=0,right=nums.length-1;
while(left<=right) {
int mid=left+(right-left)/2;
if(nums[mid]<target){
left=mid+1;
}else if(nums[mid]>target){
right=mid-1;
}else if(nums[mid]==target) {
left=mid+1;// 别返回,锁定右侧边界
}
}
// 最后要检查 right 越界的情况
if(right<0||nums[right]!=target)
return -1;
return right;
}
一般情况下会用第一种情况
2.双指针
双指针一般两类,一类是「快慢指针」,一类是「左右指针」。前者主要解决链表中的问题,比如典型的判定链表中是否有环;后者主要解决数组(或字符串)中的问题,如二分查找。
141. 环形链表 - 力扣(LeetCode)
142. 环形链表 II - 力扣(LeetCode)
704. 二分查找 - 力扣(LeetCode)
3.滑动窗口
滑动窗口的应用场景
1.滑动:说明这个窗口是移动的,且窗口的移动是按照一定方向来的。
2.窗口:窗口大小并不是固定的,可以不断扩容直到满足一定的条件;也可以不断缩小,直到找到满足条件的最小窗口;当然可以是固定大小。
(用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题)
4.模拟行为
模拟顾名思义,就是按照题目给的操作,用代码依次描述出来即可,就是这么简单。
模拟类的题目在数组中很常见,不涉及到什么算法,就是单纯的模拟,十分考察大家对代码的掌控能力。
模拟过程:(1)读题;(2)建模;(3)代码实现;(4)调试、优化