Day1【数组】704. 二分查找、27. 移除元素

704.二分查找

文章讲解

二分查找着重关注几点

  1. 如何定义区间
  2. 如何定循环条件
  3. 如何缩减搜索区间

第一种方式:左闭右闭方式定义区间

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, 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 if (nums[middle] == target)
                return middle;
        }
        return -1;
    }
};

所谓左闭右闭方式定义区间,即按照如下方式描述区间

这样能够通过[left, right]来描述数组中的元素。因此,初始化right应该等于size - 1,这样才能通过左闭右闭方式描述

如何判定循环条件?即:区间中只要还有元素就要一直循环。很容易想到while (left < right),区间中肯定是有元素的。在这里,因为按照左闭右闭方式定义的区间,当left = right时,[left, right]区间中也有元素,故也需要进循环体。故while中条件应该为 while (left <= right)

如何缩减搜索区间?关键是缩减后的区间应该尽可能排除掉不可能的区间。左闭右闭区间中,right要赋值为middle - 1,因为当前这个middle一定不是target,接下来要查找的区间范围希望不包含当前这个middle

第二种方式 :左闭右开方式定义区间

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, 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 if (nums[middle] == target)
                return middle;
        }
        return -1;
    }
};

所谓左闭右开方式定义区间,即按照如下方式描述区间

这样能够通过[left, right)来描述数组中的元素。因此,初始化right应该等于size,这样才能通过左闭右开方式描述

如何判定循环条件?即:区间中只要还有元素就要一直循环。很容易想到while (left < right),区间中肯定是有元素的。在这里,和左闭右闭方式定义区间不同的是,当left = right的时候,区间[left, right)中已经没有元素了,因此不需要进循环体。故while中的条件就是while (left < right)

如何缩减搜索区间?关键是缩减后的区间应该尽可能排除掉不可能的区间,也不能漏掉可能的区间。左闭右开区间中,right赋值为middle即可,因为当前这个middle一定不是target,接下来要查找的区间范围[left, right)本来就不包含当前这个middle,这里和左闭右闭区间也是不一样的。如果这里也设置成right - 1了,那么会漏掉搜索middle前一个元素


27. 移除元素

力扣题目链接

文章讲解

 暴力解法

// 时间复杂度:O(n^2)
// 空间复杂度: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 + 1; j < size; ++j) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};

需要注意i--的操作和size--的操作,主打一个不漏遍历元素,也不多遍历元素。如果没有i--操作,那么

这种两层for循环,时间复杂度O(n^2) ,不妥

另一种解法:双指针法 

这种方法能够以O(n)时间复杂度实现移除元素,一层for循环即可

关键理解快指针和慢指针是干什么用的

  • 慢指针用于指向新数组需要更新的位置
  • 快指针用于遍历原数组,并寻找满足条件的作为新数组中的元素

只不过这里的逻辑上的新数组原数组都是在一个数组上操作

哪里体现移除元素了?在快指针指向的元素为非待删除值时,才将其赋值给慢指针所指向的位置。(即只把不等于待删除值的元素添加到新数组中,就相当于移除元素了)

看代码

// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slow = 0, fast = 0;
        while (fast < nums.size()) {
            if (nums[fast] != val) {    //在快指针指向的元素不为待删除值时
                nums[slow] = nums[fast];    //才将其赋值给慢指针所指向的位置
                slow++;    //慢指针向后移动,表示新数组中下一个待填充位置
            }
            ++fast;
        }
        return slow;
    }
};

主打一种思想:快指针往前找,看看哪些元素是符合条件的(这里是不等于val值的),然后把符合条件的填充进慢指针所指位置,然后慢指针指向下一个待填充位置

注意几个点:

  1. 为了找到所有能够填入新数组的值,fast应遍历完原数组,故while (fast < size)
  2. fast每次循环都要往后移动,fast++,因为fast一直在找 

一种简化的实现

// 时间复杂度:O(n)
// 空间复杂度:O(1)
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;
    }
};

 回顾总结

二分查找题目应该熟悉两种区间定义方式以及连带影响

移除元素中应该掌握快指针和慢指针分别是干什么的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林沐华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值