算法day1 | LeetCode 704. 二分查找,LeetCode 27. 移除元素

LeetCode 704. 二分查找

题目链接:力扣704. 二分查找

题目思路

这道题的难点主要在于如何确定边界,区间的开闭决定了二分法的代码如何去写。

解题方法

方法一:
假如 target 是在一个在左闭右闭的区间里,即[left, right]

  • while (left <= right) 要使用 <= ,因为 left == right 是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个 nums[middle] 一定不是 target,那么接下来要查找的左区间结束下标位置就是 middle - 1
class Solution {
public:
    int search(vector<int>& nums, int target) {
        if(nums.size() == 0) return NULL;
        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 是在一个在左闭右开的区间里,即 [left, right)

  • while (left < right),这里使用 < ,因为 left == right 在区间 [left, right) 是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为当前 nums[middle] 不等于 target,去左区间继续寻找,而寻找区间是左闭右开区间,所以 right 更新为 middle,即:下一个查询区间不会去比较 nums[middle]
class Solution {
public:
    int search(vector<int>& nums, int target) {
        if(nums.size() == 0) return NULL;
        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; //未找到目标值
    }
};

(right - left) / 2 可能会造成越界,例如 left + right = 33亿 > int 上限值,已经超上限值了,除 2 就没意义了,而用 left 加 right - left 差值这种形式就相当于 15 亿加 3 亿,结果才 18 亿,没有超,所以可以得到正确结果

left + (right - left) / 2 和 left + ((right - left) >> 1) 的区别:
在这里插入图片描述

总结

这道题在一开始的时候我也是因为没有确定好边界,导致时间一直超出限制,后来看了讲解之后,才成功解决了这个边界的设定的问题。还有就是运算符的优先级的问题,“+” 的优先级高于 “>>”,所以在移位运算的时候要加括号。

LeetCode 27. 移除元素

题目链接:力扣27. 移除元素

题目思路

可以考虑到的就是暴力,找到要删除的元素,将其后面的元素向前覆盖,还有就是利用双指针。

解题方法

方法一:(暴力)
暴力解法,首先循环遍历待操作的数组,找到要删除的元素,然后再遍历将该元素后面的元素都向前移动一位。时间复杂度:O(n^2),空间复杂度:O(1)

// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int n = nums.size();
        if(n == 0) return 0;
        for(int i = 0; i < n; i++)
        {
            if(nums[i] == val) // 发现需要移除的元素,就将数组集体向前移动一位
            {
                for(int j = i + 1; j < n; j++)
                {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                n--; // 此时数组的大小-1
            }
        }
        return n;
    }
};

方法二:(快慢双指针)

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新新数组下标的位置
    快指针寻找不是要删除的元素赋值给 nums[slow],就相当于是将删除后的数组元素存放进一个新的 nums[slow] 数组中,只不过没有额外定义一个数组。时间复杂度:O(n),空间复杂度:O(1)
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slow = 0;
        for(int fast = 0; fast < nums.size(); ++fast){
            if(nums[fast] != val){
                nums[slow++] = nums[fast];
            }
        }
        return slow;
    }
};

元素的相对位置没有改变

方法三:(双向双指针)
把左边需要剔除的值和右边不需要剔除的值颠倒一下,这样右边的就都是数组中需要剔除的值, left 存的就是新数组的长度。时间复杂度:O(n),空间复杂度:O(1)

// 时间复杂度:O(n)
// 空间复杂度:O(1)
// 双向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int left = 0;
        int right = nums.size() - 1;
        while(left <= right){
            // 找左边等于 val 的元素
            while(left <= right && nums[left] != val) ++left;
            // 找右边不等于 val 的元素
            while(left <= right && nums[right] == val) --right;
            // 将右边不等于 val 的元素覆盖左边等于 val 的元素
            if(left <= right){
                nums[left++] = nums[right--];
            }
        }
        return left; // leftIndex一定指向了最终数组末尾的下一个元素
    }
};

元素的相对位置改变了

总结

这道题目让我对双指针解题有了新的认识,双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值