代码随想录 数组: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;
        }
    }
}

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

  • 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;
    }
}

左闭右开 二分法

四个注意点

  • [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(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        end_point = 0
        start_point = 0
        target_tag = 0
        for i in range(len(nums)):
            end_point = end_point + 1
        end_point = end_point - 1
        
        loop_flag = True
        while loop_flag:
            target_tag = int((end_point + start_point) / 2)
            if nums[target_tag] < target:
                start_point = target_tag + 1
            if nums[target_tag] > target:
                end_point = target_tag - 1
            if nums[target_tag] == target:
                return target_tag
            if start_point > end_point:
                return -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)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值