二分
二分查找是很经典的查找算法,用于在有序数列里面快速查找元素,时间复杂度为logn
时间复杂度解释
每次查找会将所有范围缩小一半,当查找k次之后如果搜索范围为1就得出结果
即n/2^k=1
可以推出n=2^k,即k=log2 n
给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
示例 1:
输入:nums
= [-1,0,3,5,9,12],target
= 9 输出: 4 解释: 9 出现在nums
中并且下标为 4
示例 2:
输入:nums
= [-1,0,3,5,9,12],target
= 2 输出: -1 解释: 2 不存在nums
中因此返回 -1
提示:
- 你可以假设
nums
中的所有元素是不重复的。 n
将在[1, 10000]
之间。nums
的每个元素都将在[-9999, 9999]
之间。
二分查找注意事项:区间的定义
这里普遍是两种区间,第一种左闭右闭,第二种左闭右开
1、左闭右闭
即left、right所在的索引我们认为是参与target判断的,所以首先定义left和right的时候分别为0和nums.length-1,并且查找的终止条件是left==right,并且由于left和right下标的数组都参与了target判断,我们缩小区间的时候left=mid+1,right=mid-1,当跳出循环,就代表没有target存在。
代码如下:
class Solution {
public int search(int[] nums, int target) {
int left=0;int right=nums.length-1;
while(left<=right){
int mid=left-(left-right)/2;
if(nums[mid]>target){
right=mid-1;
}
else if(nums[mid]<target){
left=mid+1;
}
else return mid;
}
return -1;
}
}
2、左闭右开
同理,左闭右开意味着left所在索引元素参与判断,right所在元素不参与判断,那么定义left=0,right=nums.length,并且定义循环终止条件为left<right即可,因为left==right是没有意义的。此外,当我们缩小区间的时候,因为left是闭,所以left=mid+1,right是开,所以right=mid,那么当最后循环终止,也就是left==right的时候,也同样没有找到元素,返回-1
代码如下:
class Solution {
public int search(int[] nums, int target) {
int left=0;int right=nums.length;
while(left<right){
int mid=left-(left-right)/2;
if(nums[mid]>target){
right=mid;
}
else if(nums[mid]<target){
left=mid+1;
}
else return mid;
}
return -1;
}
}
3、左开
那肯定有人疑惑为什么没有左开右闭或者左开右开
其实硬要写也是可以的,就是比较麻烦
说一下本菜鸟的思路
以左开右闭为例吧
首先:定义left=-1,right=nums.length-1,容易理解left==right是没有意义的,因为left不参与判断,如果写left<right,那么会有一种情况是left=right-1,此时mid必然等于left,因为mid=(left*2+1)/2,若target大于mid对应元素,那么会陷入死循环,left一直等于mid就出不来了。
所以这边,我想到的方法就是循环条件写成right-left>1,写成这个之后就有问题了,那就是最后left=right-1的情况被忽略了,就需要在循环外部手动再判断right对应的元素是否等于target。此外还有一种情况,就是target比所有元素都小,那么right最后会跑到-1去,会有数组越界的风险。
这是我随便写的代码,也顺利通过了,仅供参考
class Solution {
public int search(int[] nums, int target) {
int left=-1;int right=nums.length-1;
while(right-left>1){
int mid=left-(left-right)/2;
if(nums[mid]>target){
right=mid-1;
}
else if(nums[mid]<target){
left=mid;
}
else return mid;
}
if(right<0) return -1;
return nums[right]==target?right:-1;
}
}
再贴一个左开右开
class Solution {
public int search(int[] nums, int target) {
int left=-1;int right=nums.length;
while(right-left>1){
int mid=left-(left-right)/2;
if(nums[mid]>target){
right=mid;
}
else if(nums[mid]<target){
left=mid;
}
else return mid;
}
if(right-1<0) return -1;
return nums[right-1]==target?right:-1;
}
}
可以看到其实挺麻烦的,而且稍微不注意就要写错,很不好。
双指针
提示
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素。元素的顺序可能发生改变。然后返回 nums
中与 val
不同的元素的数量。
假设 nums
中不等于 val
的元素数量为 k
,要通过此题,您需要执行以下操作:
- 更改
nums
数组,使nums
的前k
个元素包含不等于val
的元素。nums
的其余元素和nums
的大小并不重要。 - 返回
k
。
这题没什么好说的,算是双指针,一个表示插入下标,一个用来遍历就行了
class Solution {
public int removeElement(int[] nums, int val) {
int p=0;
for(int i=0;i<nums.length;i++){
if(nums[i]!=val){
nums[p++]=nums[i];
}
}
return p;
}
}
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
简单写法,主要就是遍历一遍平方掉,然后Arrays.sort直接排序,时间复杂度为nlogn(快速排序)
class Solution {
public int[] sortedSquares(int[] nums) {
for(int i=0;i<nums.length;i++){
nums[i]*=nums[i];
}
Arrays.sort(nums);
return nums;
}
}
进阶:
- 请你设计时间复杂度为
O(n)
的算法解决本问题
选择用双指针的方法来实现On的时间复杂度,其实有点像是三指针了
主要思路就是先平方,然后left right双指针从两边向中间遍历,i记录插入位置。
因为原本的数组本来就是非递减的,所以最大值只会出现在两端,所以直接从两端找最大值插入到i的位置。left==right的时候就插入完成了
代码如下:
class Solution {
public int[] sortedSquares(int[] nums) {
int n=nums.length;
for(int i=0;i<n;i++) nums[i]*=nums[i];
int left=0;
int right=n-1;
int [] ans=new int[n];
int i=n-1;
while(left<=right){
if(nums[left]<nums[right]) ans[i--]=nums[right--];
else ans[i--]=nums[left++];
}
return ans;
}
}
新人第一篇博客,也是算法学习的开篇,其实之前粗浅刷过一次,这次系统的记录一下。如果有朋友愿意一起的话,可以关注我。