目录
704.二分查找
二分查找思路:
首先数组必须有序,在有序的数组中去寻找一个数,利用双指针的思想,mid索引处的值与target值做比较,相应的去缩哪个边界。
class Solution {
//二分查找
public int search(int[] nums, int target) {
int left=0,right=nums.length-1;//双指针
while(left<=right){
int mid=(left+right)>>>1;//防止地址越界
if(nums[mid]==target){
return mid;
}else if(nums[mid]<target){//mid索引处的值比目标值小,则在右边,缩左边界
left=mid+1;
}else {//在左边,缩right
right=mid-1;
}
}
return -1;//没找着
}
}
而今天在营里所得还是比较多,比如我写的时候就不会考虑为什么肯定是呢,因为我根本就没有对边界值有过考虑,一直写的就是这样,这可能也是为什么刷到双指针的变题,就感觉有些吃力的程度。
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。
区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
- while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。
有如下两点:
- while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
- if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
35.搜索插入位置
class Solution {
/**
*二分查找--最后停的位置
* @param nums
* @param target
* @return 找不到返回他应该按顺序插入的位置,找到返回他的索引
*/
public int searchInsert(int[] nums, int target) {
int left=0,right= nums.length-1;
int mid=0;
while(left<=right){
mid=(left+right)>>>1;
if(nums[mid]==target){
return mid;
}else if(nums[mid]<target){
left=mid+1;
}else {
right=mid-1;
}
}
return right+1;
}
}
就是在二分的基础上起码模拟两次最后停的时候right,left,mid值。
34.在排序数组中查找元素第一次和最后一次出现的位置
class Solution {
/**
*
* @param nums
* @param target
* @return 出现的范围数组,起始+终止,无的话返回[-1,-1]
*/
public int[] searchRange(int[] nums, int target) {
int[] ans=new int[2];
int left=0,right=nums.length-1;
int first=-1,second=-1;//记录ans数组里面第一个,第二个值;
while(left<=right){
int mid=(left+right)>>>1;
if(nums[mid]==target){
for (int i = left; i <=mid; i++) {
if(nums[i]==target){
//找前面,找到了就break,最坏就是mid所在的地方,是ans[0]
first=i;
break;
}
}
if(first==-1){//for循环无法执行,是整体会在上一次mid前面,所以第一个值是mid所在的地方
first=mid;
}
for (int i = right; i >mid ; i--) {
//找后面,找到了就break,找不到的话second值是-1的话,表明他后面没有
if(nums[i]==target){
second=i;
break;
}
}
if(second==-1){
second=mid;
}
break;
}else if(nums[mid]<target){
left=mid+1;
}else {
right=mid-1;
}
}
ans[0]=first;ans[1]=second;
return ans;
}
}
一开始有想要是不是要把上一次的mid值保存成temp临时变量,尽可能的去缩一下左边界和右边界,减少一下时间复杂度,但是mid值往前找和往后找的两种方式对temp变量怎么找还是有一些不同的界定。
不过总体时间用时还行,所以从头再搜和从后搜没有特别大的区别。
注:从后往前找的时候要找最后一个出现的,用
27.移除元素
很久之前刷过记得是用双指针但是怎么用双指针还是忘记了,只记得需要使用快慢指针,但是指针走几步还是记不清了,所以最后还是用的暴力方法
class Solution {
/**
* 移除nums数组中值为val的元素(原地移除)
* @param nums
* @param val
* @return 新数组长度
*/
public int removeElement(int[] nums, int val) {
int len=nums.length;
int i=0;
while (i<len){
if(nums[i]==val){
//后面的值覆盖
for (int j = i; j <len-1 ; j++) {
nums[j]=nums[j+1];
}
len--;
}else {
i++;
}
}
return len;
}
}
看题解之后大概得出思路
class Solution {
/**
* 双指针--->
* slow和fast同时移动
* @param nums
* @param val
* @return
*/
public int removeElement(int[] nums, int val) {
int slow=0,fast=0;
while (fast<nums.length){
if(nums[fast]!=val){
nums[slow++]=nums[fast];
}
fast++;
}
return slow;
}
}
快指针去找新数组中需要的元素,而慢指针维护新数组的边界位置。
2024补:用双指针,在两侧 left和right,left去找等于val的right找不等于val的,找到即swap;最后处理边界值,返回right值,如果right因为数组单一元素;
class Solution {
public int removeElement(int[] nums, int val) {
if (nums.length <= 0) {
return 0;
}
int left = 0, right = nums.length - 1;
while (left <= right) {
if (nums[left] == val && nums[right] != val) {
swap(nums,left,right);
}
if (nums[left] != val) {
left++;
}
if (nums[right] == val) {
right--;
}
}
return right == 0 && nums[0] == val ? right : right + 1;
}
private void swap(int[]nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
26. 删除有序数组中的重复项
双指针感觉跟动态规划差不多反正写的时候一定要搞清楚slow,fast指针是干嘛的
class Solution {
/**
* 因为有序所以相等的肯定相邻,slow保留新数组里面的值即每个数都不相等的数,而fast去扫哪些数不等
* @param nums
* @return 移除相同的元素后数组长度
*/
public int removeDuplicates(int[] nums) {
int slow=0,fast=1;
for ( fast = 1; fast < nums.length ; fast++) {
if(nums[fast]!=nums[slow]){//扫到了不相等的数,与slow的下一位交换
nums[++slow]=nums[fast];
}
}
return slow+1;//返回长度
}
}
283.移动零
slow是去先找第一个为0的数组的地方,fast不为0的处并且在他后面,就要交换。
class Solution {
/**
* 移动数组里面0元素到末尾
* 双指针,slow指针指向数组里面第一个为0的地方,fast去找不为0的和其交换
* @param nums
*/
public void moveZeroes(int[] nums) {
int slow=0,fast=1;
while (fast<nums.length){
while (nums[slow]!=0){
if(slow== nums.length-1){
break;
}
slow++;
}
if((slow<fast)&&nums[fast]!=0){
swap(nums,slow,fast);
slow++;
}
fast++;
}
}
public void swap(int[] arr,int a,int b){
arr[a]=arr[a]^arr[b];
arr[b]=arr[a]^arr[b];
arr[a]=arr[a]^arr[b];
}
}
注:边界值的判定,一开始找不到为0的slow直接break出来。跳出多重循环,在外面加标识,break这个标识、、