代码随想录算法训练营第一天 :LeetCode.704 二分查找,LeetCode.27移除元素

作者使用语言:c++

#LeetCode.704 二分查找

本题需要掌握数组的一些理论基础,以及数组的内存空间地址

关于本题 《 二分查找》首先要注意几点:
一、是“二分查找”的作用与目的:

        用于快速查找出一个所需的元素的位置(在数组中的下标)

二、是“二分查找”算法的使用前提:

        1.数组内没有重复元素

        2.数组是有序数组(升序或者降序,如果没有排序,让你找出数组中所需数的下标,二分法就不再适用,原因在下面“原理”段落讲;若想强行使用sort排序,则无法获取数组正确的下标)

三、“二分搜索法”的过程(原理):

        以一个升序的数组为例:

        二分法设定一个最左数left(对应数组下标0),设定一个最右数right(对应数组下标size()或size()-1,关于最右数的位置,涉及到 左闭右开 或者 左闭右闭 的写法,会在下面单独拿出来讲),然后设定一个中间数 mid((最右数+最左数)/2) 与目标数target做对比:此时将有三种情况:

        1.如果中间数比目标数要大,那么说明目标数target下标在中间数mid的左边,那么此时我们舍弃右边,更新最右数right下标的位置;

        2.如果中间数比目标数要小,那么说明目标数target下标在中间数mid的右边,那么此时我们舍弃左边,更新最左数left下标的位置;

        3.如果中间数等于目标数,直接返回目标数的下标即可;

        如此循环即可;

        简而言之:二分查找的过程就是:它每次考察数组当前部分的中间元素,如果中间元素刚好是要找的,就结束搜索过程;如果中间元素小于所查找的值,那么左侧的只会更小,不会有所查找的元素,只需到右侧查找;如果中间元素大于所查找的值同理,只需到左侧查找。

那么现在回归本题,二分查找看似简单,但在实际上解决本题时,总会出些差错:例如一些边界条件,left和right的范围及其变更后的位置等;

出现以上问题,是因为区间的定义没有想清楚,在while寻找中每一次边界的处理都要坚持根据区间的定义来操作

写二分法,市场上主流的区间定义方式一般为两种:

1.左闭右闭即[left, right];

2.左闭右开即[left, right);

先讲第一种定义方式:左闭右闭即[left, right];

左闭右闭,说明left和right都可以取到,那么left==right是可行的,因此

1.在写while循环时,应该写成while(left<=right)

2.在变更left或者right的位置时,应该使left=mid+1    right=mid-1 ;因为当目标数不等于nums[mid]时,说明目标数本身也不是要取的值,直接舍弃,变更left或right的值就行,使其等于mid+1或mid-1;

左闭右闭 代码如下:
// 版本一,左闭右闭
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) >> 1);// 防止溢出 等同于(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;
    }
};

关于middle的赋值中 >> 运算符的阐释

使用>>位运算符进行右移操作可以防止溢出的原因是因为它是逻辑右移,而不是算术右移。在C++中,算术右移>>对于有符号整数会保持符号位不变,并且用符号位进行填充,这意味着如果一个有符号整教为负数,右移操作会在高位填充1;

如果个有符号整数为正数,而逻辑右移(>>)则对于无符号整数和有符号整数都是使用0进行填充,不考虑符号位在这段代码中,(end - start)表示区间的长度,通过右移1位,相当于将区间长度除以2。由于我们所以使用逻辑右移操作可以避免溢出的问题。因此可以避免溢出的问题,并得到正确的中间值,等同于(left + right)/2。

第二种区间定义方式  左闭右开[left, right):

左闭右闭,说明left可以取到但right不可以取到,那么left==right将没有意义,因此

1.在写while循环时,应该写成while(left<right)

2.定义right时,应该使right=nums.size()  因为当我们定义成nums.size()-1时,数组中最后一个数我们将无法取到

3.在变更left或者right的位置时,应该使left=mid+1    因为当目标数不等于nums[mid]时,说明目标数本身也不是要取的值,直接舍弃,变更left的值就行,使其等于mid+1;然而因为right的值无法取到,我们变更其位置时,不可像left一样,因此right变更到mid的位置

左闭右开 代码如下:
// 版本二
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;
    }
};

#LeetCode.27移除元素

本题文章链接可参考:代码随想录 (programmercarl.com)

下面分享一些我总结的要点:

首先,拿到这题题目,要注意不能额外开辟一个新数组,所以我们只能对数组本身动刀,我最先想到的是暴力法,将不用移除的元素前移,而要删除的元素放到最后。

暴力写法 具体代码如下:
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 + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};

实际上,上面暴力写法使用了两个for循环,而用双指针写法可以做到一个for循环代替两个for循环的作用

        我们需要定义两个指针,一个快指针,一个慢指针:
        快指针:遍历数组,在遍历的过程中,寻找要到不用删除的元素

        慢指针:一步步(一个下标接着一个下标地改变(覆盖)数组元素的值),而最终形成新的子数组

双指针写法 具体代码如下:
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
            if (val != nums[fastIndex]) {
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }
};

        

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值