代码随想录算法训练营第一天|LeetCode704.二分查找、LeetCode27.移除元素

LeetCode704.二分查找

题目链接

https://leetcode.cn/problems/binary-search/

视频讲解

https://www.bilibili.com/video/BV1fA4y1o715

题目分析

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

该题目给定的数组为有序数组,需要大家根据给定目标值target在数组中搜索该值并返回下标值,如没有则返回-1。该种题型属于典型的二分查找题型,对于二分查找大家需要熟练掌握。

二分查找代码分析

二分查找的主流写法区间有两种:

  • 左闭右闭[left , right]

  • 左闭右开[left , right)

对于两种不同的区间写法,其关键的区分点有以下三点:

  • 在初始设定右边界时,设定为right = nums.size() - 1还是right = nums.size()

  • while循环的判定条件应该应取left < right还是left <= right

  • 在更新右边界时,应设定right = middle - 1还是right = middle

  1. 关键点一

对于左闭右闭区间[left , right],

right右边界可取,说明right初始设定的最大范围应该恰好在数组的最大下边边界处,此时应设定为right = nums.size() - 1。

对于左闭右开区间[left , right),

right右边界不取,说明right初始设定的最大范围应该恰好比数组的最大下标边界大1,此时应设定为right = nums.size()。

  1. 关键点二

判断while循环的判定条件时,只需要将待取的条件left < right、left <= right代入设定的范围中看是否合理即可。

对于左闭右闭区间[left , right],

当left <= right时,区间仍然成立,此时判定条件即为left <= right。

对于左闭右开区间[left , right),

当left < right时,区间仍然成立,当left <= right时,区间不再成立,此时判定条件即为left < right。

对于左闭区间来说,更新左边界时均设定为left = middle + 1。

  1. 关键点三

更新右边界时,

对于左闭右闭区间[left , right],

已经将nums[middle]与target进行比较得出了相应结论,此时若更新右边界right = middle,则将再进行重复比较,不合理,故此时应该设置right = middle - 1。

对于左闭右开区间[left , right),

已经将nums[middle]与target进行比较得出了相应结论,此时若更新右边界right = middle,则恰好将右开的范围恰好将已比较的middle项屏蔽掉,故此时应该设置right = middle。

二分查找代码示例

左闭右闭

class Solution {
public:
    int search(vector<int>& nums, int target) {
        //左闭右闭写法
        int left = 0;
        int right = nums.size() - 1;
        while(left <= right) {
            int middle = left + (right - left) / 2;
            if(nums[middle] > target) {
                right = middle - 1;
            }
            else if(nums[middle] < target) {
                left = middle + 1;
            }
            else return middle; 
        }
        return -1;
    }
};

左闭右开

class Solution {
public:
    int search(vector<int>& nums, int target) {
        //左闭右开写法
        int left = 0;
        int right = nums.size();
        while(left < right) {
            int middle = left + (right - left) / 2;
            if(nums[middle] > target) {
                right = middle;
            }
            else if(nums[middle] < target) {
                left = middle + 1;
            }
            else return middle; 
        }
        return -1;
    }
};

额外补充

在求取middle值的时候,编者采取的操作是

int middle = left + (right - left) / 2

而没有写成

int middle = (left + right) / 2

其实这两种写法都是正确的,但是第二种写法以为涉及到左右边界相加的问题,容易导致溢出,故在此处采用第一种写法更加的方便和安全。

除此之外,第一种写法还有改进的空间,即——

int middle = left + (right - left) >> 1

这里是采用了位运算符的操作,具体内容可查看相关文章。

十进制数进行 >> 1的位运算就相当于一个除以2的十进制运算,>>2的位运算就相当于一个除以4的十进制运算......以此推类即可。

这种写法避免了浮点数溢出的问题,同时也比直接使用/计算更快一些。

LeetCode27.移除元素

题目链接

https://leetcode.cn/problems/remove-element/

视频讲解

https://www.bilibili.com/video/BV12A4y1Z7LP

题目分析

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

该题目涉及到元素的删除和移动,考察重点在于如何将保留的元素移动到被删除的元素的原处位置上,如下分享两种解法。

解法一——暴力解法

class Solution {
public:
    
    int removeElement(vector<int>& nums, int val) {
        //暴力解法
        int size = nums.size();
        for(int i = 0; i < size; i++) {
            if(nums[i] == val) {
                for(int j = i; j < size - 1; j++) {
                    nums[j] = nums[j + 1];
                }
                size--;
                i--;
            }
        }
        return size;
    }
};

该代码主要利用了元素的移动,时间复杂度达到O(n2),易错点在于将后部分元素移动到删除元素位置时数组的长度size和指针i都需要进行减一操作来重新匹配长度和位置。

解法二——双指针法

仅在nums数组上借助快慢指针进行操作

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        int slow = 0;
        for(int fast = 0; fast < size; fast++) {
            if(nums[fast] != val) {
                nums[slow] = nums[fast];
                slow++;
            } 
        }
        return slow;
    }
};

借助temp数组与nums数组匹配进行快慢指针的操作

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

    }
};

两种操作没有本质的不同,但在使用借助temp数组的操作时要注意拷贝操作。

总结

今日算法题目较为简单,之前已有练习,但考虑到这两个题目仅仅只是两种算法的最基本操作,后续还需要进行进行进一步的拓展练习进行巩固。

还有几道拓展题型没有尝试,在此标记一下,待之后继续攻克。

二分查找类型——

  • 35.搜索插入位置(opens new window)

  • 34.在排序数组中查找元素的第一个和最后一个位置(opens new window)

  • 69.x 的平方根

  • 367.有效的完全平方数

移除元素类型——

  • 26.删除排序数组中的重复项

  • 283.移动零

  • 844.比较含退格的字符串

  • 977.有序数组的平方

猜你喜欢

转载自blog.csdn.net/wyr1849089774/article/details/128666156
今日推荐