代码随想录训练营day01 | 二分查找|数组part01

本文详细介绍了二分查找、搜索插入位置以及多种移除元素的方法,包括左闭右闭、左闭右开区间的不同实现,以及暴力解法、双指针法和相向指针法等,展示了在有序数组中高效查找和修改的操作技巧。
摘要由CSDN通过智能技术生成

一、二分查找

二分查找触发条件:1)有序数组;2)无重复元素(否则可能没有唯一解)

1.704二分查找

1)左闭右闭

while内判断为left<=right

缩小区间时right调整为mid-1,因为mid-1可以取到,而mid已经确认不是target

class Solution {
    //左闭右闭
    public int search(int[] nums, int target) {
        if (target < nums[0] || nums[nums.length - 1] < target) {
            return -1;
        }
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            }
        }
        return -1;
    }
}

2)左闭右开

while内判断为left<right

缩小区间时right调整为mid,因为mid取不到,而mid已经确认不是target

本方法用例通过但提交没过

提交没过解决:right初始化为nums.length不用-1,因为是右开区间取不到

class Solution {
    //左闭右开
    public int search(int[] nums, int target) {
        if (target < nums[0] || nums[nums.length - 1] < target) {
            return -1;
        }
        int left = 0;
        //right = nums.length不-1
        int right = nums.length;
        while (left < right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            }
        }
        return -1;
    }
}

2.35搜索插入位置

1)暴力解法

class Solution {
    // 暴力解法
    public int searchInsert(int[] nums, int target) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            // 三种情况
            // 1.target小于数组中所有元素
            // 2.target存在于数组中,等于数组某元素
            // 3.target不存在于数组中,大于数组某元素
            if (nums[i] >= target) {
                //=时为情况二,target直接插入即可
                // >时为情况三,target插入数组的位置就是刚刚大于它的元素的位置
                // 相当于顶替这个元素原本的位置,这个元素的位置变为i+1
                return i;
            }
        }
        return n;
    }
}

2)二分法(左闭右闭)

return right+1;

class Solution {
    // 二分法
    public int searchInsert(int[] nums, int target) {
        int left=0;
        int n = nums.length;
        int right=n-1;
        while(left<=right){
            int mid=left+((right-left)>>1);//防止溢出
            if(nums[mid]==target){//target存在于数组中
                return mid;
            }else if(nums[mid]<target){//target大于mid,在右区间
                left=mid+1;
            }else if(nums[mid]>target){//target小于mid,在左区间
                right=mid-1;
            }
        }
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前  [0, -1]
        // 目标值等于数组中某一个元素  return middle;
        // 目标值插入数组中的位置 [left, right],return right+1
        // 目标值在数组所有元素之后的情况 [left, right],因为是右闭区间,所以return right+1
        return right+1;
    }
}

3)二分法(左闭右开)

return right;

class Solution {
    // 二分法
    public int searchInsert(int[] nums, int target) {
        int left=0;
        int n = nums.length;
        int right=n;//右开区间,right=n不-1,与左闭右闭作区分
        while(left<right){
            int mid=left+((right-left)>>1);//防止溢出
            if(nums[mid]==target){//target存在于数组中
                return mid;
            }else if(nums[mid]<target){//target大于mid,在右区间
                left=mid+1;
            }else if(nums[mid]>target){//target小于mid,在左区间
                right=mid;
            }
        }
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前  [0, 0)
        // 目标值等于数组中某一个元素  return middle;
        // 目标值插入数组中的位置 [left, right),return right+1
        // 目标值在数组所有元素之后的情况 [left, right),因为是右开区间,所以return right+1
        return right;
    }
}

3.34在排序数组中查找元素的第一个和最后一个位置 

寻找target在数组里的左右边界,有如下三种情况:

  • 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
  • 情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
  • 情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}

1)解法一,只调一次二分查找函数

// 解法2
// 1、首先,在 nums 数组中二分查找 target;
// 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1};
// 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间

自己写的,没ac

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int index = binarySearch(nums, target);

        //nums中存在target
        if (index == -1) {
            return new int[] { -1, -1 };// 匿名数组
        }
        //nums中不存在target
        int left = index;
        int right = index;
        //防止数组越界,防止逻辑短路,两个条件顺序不能改变
        if (left - 1 >= 0 && nums[left - 1] == nums[index]) {
            left--;
        }
        if (right + 1 < nums.length && nums[right + 1] == nums[index]) {
            right++;
        }
        return new int[] { left, right };
    }

    public int binarySearch(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;// 左闭右闭,循环不变量
        while (left <= right) {// 左闭右闭,循环不变量
            int mid = left + ((right - left) >> 2);
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid - 1;// 左闭右闭,循环不变量
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }
}

教程解答,ac了...

class Solution {
	public int[] searchRange(int[] nums, int target) {
		int index = binarySearch(nums, target); // 二分查找
		
		if (index == -1) { // nums 中不存在 target,直接返回 {-1, -1}
			return new int[] {-1, -1}; // 匿名数组 
		}
		// nums 中存在 targe,则左右滑动指针,来找到符合题意的区间
		int left = index;
		int right = index;
        // 向左滑动,找左边界
		while (left - 1 >= 0 && nums[left - 1] == nums[index]) { // 防止数组越界。逻辑短路,两个条件顺序不能换
			left--;
		}
        // 向右滑动,找右边界
		while (right + 1 < nums.length && nums[right + 1] == nums[index]) { // 防止数组越界。
			right++;
		}
		return new int[] {left, right};
    }
	
	/**
	 * 二分查找
	 * @param nums
	 * @param target
	 */
	public int binarySearch(int[] nums, int target) {
		int left = 0;
		int right = nums.length - 1; // 不变量:左闭右闭区间
		
		while (left <= right) { // 不变量:左闭右闭区间
			int mid = left + (right - left) / 2;
			if (nums[mid] == target) {
				return mid;
			} else if (nums[mid] < target) {
				left = mid + 1;
			} else {
				right = mid - 1; // 不变量:左闭右闭区间
			}
		}
		return -1; // 不存在
	}
}

2)解法二,分别找左边界右边界

个人认为没有解法一好理解,这个方法还没在力扣上自己敲

class Solution {
    int[] searchRange(int[] nums, int target) {
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        // 情况一
        if (leftBorder == -2 || rightBorder == -2) return new int[]{-1, -1};
        // 情况三
        if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};
        // 情况二
        return new int[]{-1, -1};
    }

    int getRightBorder(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] > target) {
                right = middle - 1;
            } else { // 寻找右边界,nums[middle] == target的时候更新left
                left = middle + 1;
                rightBorder = left;
            }
        }
        return rightBorder;
    }

    int getLeftBorder(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
                right = middle - 1;
                leftBorder = right;
            } else {
                left = middle + 1;
            }
        }
        return leftBorder;
    }
}

二、移除元素

1.27

注:题干允许数组元素顺序发生变化,故只需要用!=val的元素覆盖掉==val的元素,在原数组上直接操作就行,因为要求O(1)的空间复杂度

1)暴力解法

class Solution {
    //暴力解法
    //时间复杂度O(n^2)
    //空间复杂度O(1)
    public int removeElement(int[] nums, int val) {
        int size=nums.length;
        for(int i=0; i<size;i++){
            if(nums[i]==val){
                for(int j=i+1;j<size;j++){
                    //数组元素集体前移一位操作
                    nums[j-1]=nums[j];  
                }
                i--;//下表i后的元素都向前移动了一位,故i也减少1
                size--;
            }
        }
        return size;
    }
}

2)双指针(快慢指针)法

精髓方法,快指针指向当前遍历到的元素,慢指针指向当前要填充元素的位置

class Solution {
    //双指针(快慢指针)法
    //时间复杂度O(n)
    //空间复杂度O(1)
    //本题要求返回数组下标的同时,对数组本身进行修改,将==val的元素覆盖掉
    //注:返回的数组中元素可以改变
    public int removeElement(int[] nums, int val) {
        int slowIndex=0;
        //终止条件为fastIndex<nums.length而不是fastIndex<nums.length-1
        //因为是<,刚好取到nums.length-1的元素
        for(int fastIndex=0;fastIndex<nums.length;fastIndex++){
            if(nums[fastIndex]!=val){
                nums[slowIndex]=nums[fastIndex];
                slowIndex++;
            }
        }
        return slowIndex;
    }
}

3)相向指针法(一)

修正right判断条件:right>0修正为right>=0

注:用right--操作来移除right位置元素

class Solution {
    // 相向双指针法(一)
    // 时间复杂度O(n)
    // 空间复杂度O(1)
    // 本题要求返回数组下标的同时,对数组本身进行修改,将==val的元素覆盖掉
    // 注:返回的数组中元素可以改变
    public int removeElement(int[] nums, int val) {
        int left = 0;
        int right = nums.length - 1;
        // 将right易到第一个!=val的元素位置
        while (right >= 0 && nums[right] == val)
            right--;
        while (left <= right) {
            if (nums[left] == val) {// left位置元素可移除
                // 用right位置元素覆盖left位置元素
                nums[left] = nums[right];
                right--;// right位置移除
            }
            left++;
            while (right >= 0 && nums[right] == val)
                right--;
        }
        return left;
    }
}

4)相向指针法(二)

class Solution {
    // 相向双指针法(二)
    // 时间复杂度O(n)
    // 空间复杂度O(1)
    // 本题要求返回数组下标的同时,对数组本身进行修改,将==val的元素覆盖掉
    // 注:返回的数组中元素可以改变
    public int removeElement(int[] nums, int val) {
        int left = 0;
        int right = nums.length - 1;
        // 将right易到第一个!=val的元素位置
        while (right >= 0 && nums[right] == val)
            right--;
        while (left <= right) {
            if (nums[left] == val) {// left位置元素可移除
                // 用right位置元素覆盖left位置元素
                nums[left] = nums[right];
                right--;// right位置移除
            }else{//兼容法(一)的两种情况
                left++;
            }
        }
        return left;
    }
}

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值