代码随想录 数组:704.二分查找;27. 移除元素

本文围绕算法展开,详细讲解了704.二分查找和27.移除数组中的元素两道题。二分查找介绍了左闭右闭和左闭右开两种二分法,强调注意内存溢出和边界问题;移除数组元素先分析暴力解法的问题并优化,还介绍了双指针思想,对比了两种方法的复杂度。

数组是存放在连续内存空间的具有相同类型的数据的集合

704. 二分查找

**题目:**给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

二分法文章链接

在做二分查找的时候,起初并没有深入想到太多注意事项,直接使用了二分法的思想进行搜索,代码实现如下(当时还用了ceil这个奇怪的单词)

class Solution {
    public int search(int[] nums, int target) {
        int ceil = nums.length - 1;
        int floor = 0;
        int position = -1;
        int mid = 0 ;
        boolean loopFlag = true;
        while(loopFlag){
            mid = (ceil + floor) /2;
            if(nums[mid] == target){
                position = mid;
                loopFlag = false;
            }
            else if(nums[mid] < target){
                floor = mid;
            }else if(nums[mid] > target){
                ceil = mid;
            }
            if(mid == (floor + ceil) / 2){
                loopFlag = false;
            }
        }
        if(position == -1){
            return -1;
        }else{
            return position;
        }
    }
}
class Solution:
	def search(self,nums:List[int],target:int)->int:
		begin_index = 0
		end_index = len(nums) - 1
		loop_flag = True
		pos_index = (begin_index + end_index) // 2
		while loop_flag:
			if  begin_index > end_index:
				break
			elif nums[pos_index] > target:
				end_index = pos_index 
				pos_index = (begin_index + end_index) // 2
			elif nums[pos_index] < target:
				begin_index = pos_index + 1
				pos_index = (begin_index + end_index) // 2
			elif nums[pos_index] == target:
				return pos_index
		return -1

仔细观察所写的代码,发现了一些问题:

  • ceil + floor两个整型变量相加可能会存在内存溢出的问题
  • 程序陷入无限循环(应该也是边界问题
  • 边界问题没有考虑到

重新思考这个问题,将边界内存等因素考虑进去,便可以得到较为完善的二分法

左闭右闭 二分法

四个注意点:

  • [left, right],这个右边界是有意义的,为避免数组下标越界异常,right = nums.length - 1
  • 同样在闭区间的定义下,在while循环中,判断条件应为**(left <= right)**,因为 left = right 是有意义的
  • 如果nums[middle] > target,更新搜索范围右下标应更新为 right = middle - 1,因为middle位置上的数值一定不等于target,同理,更新left时,应更新为left = middle + 1
  • 考虑到内存溢出的问题 定义middle为left + (right - left)/2

代码实现如下

class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int middle = 0;
        while (left <= right) {
            middle = left + (right - left) / 2;
            if (nums[middle] == target) {
                return middle;
            } else if (nums[middle] < target) {
                left = middle + 1;
            } else if (nums[middle] > target) {
                right = middle - 1;
            }
        }
        return -1;
    }
}
class Solution:
	def search(self,nums:List[int],target:int)->int:
		left = 0
		right = len(nums) - 1
		while left <= right:
			middle = left + ( right - left ) // 2
			if nums[middle] > target:
				right = middle - 1
			elif nums[middle] < target:
				left = middle + 1
			elif nums[middle] == target:
				return middle
		return -1

左闭右开 二分法

四个注意点

  • [left, right),这个右边界是无意义的,**right = nums.length **
  • 同样在闭区间的定义下,在while循环中,判断条件应为**(left < right)**,因为 left = right 是无意义的
  • 如果nums[middle] > target,更新搜索范围右下标应更新为 right = middle ,但应注意,更新left时,应更新为left = middle + 1(似乎闭区间就要加1或减1)
  • 考虑到内存溢出的问题 定义middle为left + (right - left)/2

代码实现如下:

class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length;
        int middle = 0;
        while (left < right) {
            middle = left + (right - left) / 2;
            if (nums[middle] == target) {
                return middle;
            } else if (nums[middle] < target) {
                left = middle + 1;
            } else if (nums[middle] > target) {
                right = middle;
            }
        }
        return -1;
    }
}
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        begin_index = 0
        end_index = len(nums)

        while begin_index < end_index:
            pos_index = begin_index + (end_index - begin_index) // 2
            if nums[pos_index] < target:
                begin_index = pos_index + 1
            elif nums[pos_index] > target:
                end_index = pos_index
            elif nums[pos_index] == target:
                return pos_index
        return -1
        

总结:

  • 两个整型相加注意内存溢出问题
  • 注意边界问题的处理

2025年10月9日对区间易错点进行总结

1. 左闭右闭区间 [left, right]

  • 区间定义: leftright 都是有效索引(即 nums[left]nums[right] 均为数组中存在的元素)。
  • 初始值: left = 0right = nums.length - 1(因为最后一个元素索引是 length-1,且包含在区间内)。
  • 循环条件: while (left <= right)
    (当 left == right 时,区间 [left, right] 仍包含一个元素 nums[left],需要继续检查)。
  • 边界更新:
    • nums[mid] > target:目标在左半部分,且 mid 位置已排除,因此 right = mid - 1(新右边界不包含 mid)。
    • nums[mid] < target:目标在右半部分,且 mid 位置已排除,因此 left = mid + 1(新左边界不包含 mid)。

2. 左闭右开区间 [left, right)

  • 区间定义:left 是有效索引,right 是无效索引(即 nums[left] 有效,nums[right] 超出数组范围或不参与检查)。
  • 初始值: left = 0right = nums.length(因为 right 是开区间,初始范围需包含所有元素,length 是最后一个元素的下一位)。
  • 循环条件: while (left < right)
    (当 left == right 时,区间 [left, right) 为空,无需继续检查)。
  • 边界更新:
    • nums[mid] > target:目标在左半部分,由于右边界是开区间,right 直接设为 mid(新右边界 mid 本身不包含在区间内)。
    • nums[mid] < target:目标在右半部分,mid 位置已排除,因此 left = mid + 1(与左闭右闭一致)。

27.移除数组中的元素

题目:给你一个数组 nums 和一个值 val,你需要原地数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。


开始想的时候想法比较简单,想到了使用循环对数组中元素进行覆盖,使用暴力方式解决,但是在具体的代码实现过程中,做法就真的暴力了。在使用暴力实现的过程中,遇到了几个问题,括号里是一些不成熟的做法:

移除元素文章链接

移除数组中的元素,刚开始想的时候想法比较简单,想到了使用循环对数组中元素进行覆盖,使用暴力方式解决,但是在具体的代码实现过程中,做法就真的暴力了。在使用暴力实现的过程中,遇到了几个问题,括号里是一些不成熟的做法:

  • 最后一个元素如果等于目标值的话,会陷入无限循环中(如果这种情况就把最后一个元素赋值为-1)。
  • 如果数组为空的话,会发生数组下标越界异常(如果这种情况就直接返回0)
  • 在覆盖之后数组下标就移动到下一位了,不能检测移动过来的数组元素是不是也等于目标值(把for循环中的控制语句放到了选择结构中)

于是暴力解法代码实现如下

public int removeElement(int[] nums, int val) {
        int num = 0;
        if(nums.length == 0){//上文第二种情况
            return 0;
        }else{
            if (nums[nums.length - 1] == val){//上文第一种情况
                nums[nums.length - 1] = -1;
                num--;
            }
            for(int i = 0;i<nums.length;){
                if(nums[i] == val){
                    for(int j = i;j<nums.length - 1;j++){
                        nums[j] = nums[j+1];
                    }
                    num--;
                }else {//上文第三种情况
                    i++;
                }
            }
            return  nums.length + num;
        }
    }
class Solution(object):
    def removeElement(self, nums, val):
        """
        :type nums: List[int]
        :type val: int
        :rtype: int
        """
        start_point = 0
        end_point = 0
        loop_flag = True
        while loop_flag:
            if start_point == len(nums):
                loop_flag = False
                break
            if nums[start_point] != val:
                start_point = start_point + 1
                continue
            else:
                end_point = start_point + 1
                while end_point < len(nums) and nums[end_point] != None:
                    nums[start_point] = nums[end_point]
                    start_point += 1
                    end_point = start_point + 1
                del nums[start_point]
                start_point = 0
        return len(nums)      

之后重新学习了一下暴力解法, 得到了一些新想法:

  • 把数组的长度赋值给一个变量, 能够解决无限循环的问题和数组下标越界的问题(覆盖之后, 这个变量数值减小)(如果数组为空,循环很快就能退出来)
  • 循环结构中的控制语句每覆盖一次数值-1,就能保证在覆盖这个位置在一次检测

优化后的暴力解法如下

public int removeElement(int[] nums, int val) {
        int num = nums.length;
        for(int i = 0; i < num; i++){
            if(nums[i] == val){
                for(int j = i;j<nums.length - 1;j++){
                    nums[j] = nums[j+1];
                }
                num--;
                i--;
            }
        }
        return  num;

暴力解法时间复杂度 O ( n 2 ) , 空间复杂度为 O ( 1 ) 暴力解法时间复杂度O(n^2),空间复杂度为O(1) 暴力解法时间复杂度O(n2),空间复杂度为O(1)

下面是使用双指针思想解决移除数组中元素问题

思路:分别定义一个快指针慢指针快指针用于获得数组中所需要的元素(就是数组中不等于目标值的元素);慢指针用于接收数组中所需要的元素值,并将其赋值给所指向的数组位置。如果快指针遇到了等于目标值的数组元素,慢指针停下,快指针继续向前走,直到遇见不等于目标值的元素,把这个元素的值传递给慢指针,慢指针赋值给相应的位置,实现覆盖。循环结束之后,慢指针所在的位置就是所要求返回的数组长度。

代码实现:

public int removeElement(int[] nums, int val){
        int fastIndex = 0;
        int slowIndex = 0;
        for (fastIndex = 0; fastIndex < nums.length; fastIndex++) {
            if (nums[fastIndex] != val){//如果发现等与目标值的数组元素,慢指针停下,快指针继续向前走
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }

双指针思想移除数组中元素时间复杂度 O ( n ) , 空间复杂度 O ( 1 ) 双指针思想移除数组中元素时间复杂度O(n),空间复杂度O(1) 双指针思想移除数组中元素时间复杂度O(n),空间复杂度O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值