代码随想录算法训练营第一天|704.二分查找、35插入位置、27移除元素

Leetcode704二分查找

给定一个 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
提示:
  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。

看到题目后根据对二分查找的记忆写了如下代码,结果超出时间限制

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int mid, low = 0, high = nums.size()-1;
        while(true)
        {
            mid = (low+high)/2;
            if(target == nums[mid])
            {
                return mid;
            }
            else if(target < nums[mid])
            {
                high = mid - 1;
            }
            else
                low = mid + 1;
        }
        return -1;
    }
};

想了想,发现while的判断有问题,应该要保证low<=high,修改后运行通过。

但是看官方题解中,关于mid的值,一开始是这样求的:

int mid = (right - left) / 2 + left;

看评论区知道是为了防止溢出:在计算机里可能两个int相加会溢出,先减除后加确实可以用于更大的数组

之后我返回去看了一下卡尔哥的讲解,发现卡尔哥讲的很详细,下次我可以直接写完看卡尔哥的讲解。然后,卡尔哥讲到了二分法的两种写法

target在左闭右闭的区间内

也就是我一开始的思路,这里附上卡尔哥的代码,也就是一开始left=0,right=nums.size()-1,这里的right是数组最后一个元素的下标

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
        while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

target在左闭右开的区间内

这里对边界的处理方式产生了变化:

  • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

left还是mid+1

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在[middle + 1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

leetcode35插入位置

这里其实我还有一些疑问,今天有点来不及解决了,先存一下疑,之后去看一下卡尔哥的解析

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

自己写完一遍后,因为nums.size()没有减一,报错,修改乘high=nums.size()-1后,运行没有问题,但是解答错误

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int low = 0, high = nums.size()-1;
        int mid;
        while(low <= high)
        {
            mid = (high-low)/2 + low;
            int num = nums[mid];
            if(num == target)
                return mid;
            else if(target < num)
                high = mid - 1;
            else low = mid + 1;
        }
        if(target > nums[mid]) return mid+1;
        else return mid-1;
    }
};

当插入位置为0,也就是,要查找的数字小于数组里的所有数时,我的运行结果为插入位置为-1

我想了想,当target<nums[mid]时,插入位置应该就是mid

else return mid;

结合官方题解和评论区,原来二分查找最后返回的mid实际上就是left

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int l=0,r=n-1;
        while(l<=r){
            int mid=l+(r-l)/2;
            if(nums[mid]<target)
                l=mid+1;
            else r=mid-1;
        }
        return l;
    }
};

leetcode27移除元素

我的思路是,每次发现一个等于val的值,从这个位置开始,把后面的数整体前移,覆盖掉val,数组长度减1即newn减1。第一次我是用的两个for循环,第一层是数组从0开始循环到newn,这样的问题是如果后面移过来的值也等于val,就会被忽略掉。第二次我该用了while,然后每次循环都判断一遍原来的位置是不是不等于val了。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int n = nums.size()-1;
        int newn = n;
        int i = 0, pos = 0;
        while(i <= newn)
        {
            if(nums[i] == val)
            {
            --newn;
            for(int j = i; j <= newn; ++j)
            {
                nums[j] = nums[j+1];
            }
            }
            else ++i;
        }
        return newn+1;
    }
};

后面我看了卡尔哥暴力求解的代码,如果采用两层for循环,可以在第二层for循环中,将i也前移一位,解决了我采用两层循环遇到的问题。

另外,这道题我刚开始想过用如erase这样的函数不是直接就解决了吗,根据视频讲解,erase的时间复杂度实际上是O(n),最后size能返回删除后大小,是因为内部有一个计数用的count。这道题就是要我们实现erase的功能。

卡尔哥具体讲解了双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

  • 快指针:寻找新数组的元素,新数组就是不含有目标元素的数组
  • 慢指针:指向更新新数组下标的位置

具体代码就不附上了,在随想录里可以看,感觉有点复杂,还需要再理解理解

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值