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

LeetCode704.二分查找

题目链接:704. 二分查找 - 力扣(LeetCode)

二分查找的难点在于边界处理

  • while循环条件是left < right还是left <= right
  • 更新区间:right = middle还是right = middle-1

边界处理的关键在于定义搜索区间,有两种常见的区间定义:左闭右闭[left, right]、左闭右开(left, right]。这些区间的定义是自己设定的,在while循环中每一次边界的处理都要坚持根据区间的定义来操作,即如果设定寻找区间为[left, right],则每次循环的搜索区间都是[left, right],我们每次在这个区间中寻找target元素,这就是循环不变量规则

左闭右闭

  • while循环条件,搜索区间被定义为左闭右闭的,允许出现类似[1,1]的搜索区间,因此left<=right是合法的
  • 如果nums[middle] > target,则需要更新right。更新时要坚持区间的左闭右闭原则,不能把已经确定不是target的元素(这次循环确定nums[middle]不是target)放到下一个循环的搜索区间中继续寻找,因此right = middle-1,不能包含middle处的元素
  • 同理,如果nums[middle] < target,则需要更新left,left = middle+1,不能将middle处的元素包含在下一个搜索区间中
class Solution
{
public:
    // 左闭右闭
    int search(vector<int> &nums, int target)
    {
        int front = 0;
        int back = nums.size() - 1;
        while (front <= back)
        {
            int mid = front + (back - front) / 2;
            if (nums[mid] < target)
            {
                front = mid + 1;
            }
            else if (nums[mid] == target)
            {
                return mid;
            }
            else
            {
                back = mid -1;
            }
        }
        // 只要数组内有target元素,必定会在while循环中找到并return
        return -1;
    }
};

左闭右开

  • while循环条件,搜索区间定义为左闭右开的,类似[1,1)这样的区间是不合法的,所以循环条件为left < right
  • 如果nums[middle] > target,更新right。middle处的元素不是target,而区间定义为左闭右开,区间不包含右边界的元素,因此middle处的元素可以包含在下一个搜索区间中,right = middle
  • 如果nums[middle] < target,更新left。middle处的元素不是target,而区间定义为左闭右开的,因此middle处的元素不能包含在下一个搜索区间中,left = middle+1
  • 初始时,right = nums.szie()而不是nums.szie()-1,因为区间被我们定义为左闭右开的,是不包含right处的元素的
// 版本二
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;
    }
};

LeetCode27.移除元素

题目链接:27 - 移除元素 - 力扣(LeetCode)

数组是一个内存上连续存放的元素集合,“删除数组的某个元素”实际上是把要删除的元素覆盖掉。

vector的erase函数用来删除某个元素,erase是一个O(n)的操作,它将数组整体前移,覆盖掉要删除的元素,完成删除

暴力解法

这也是我一开始想的方法,两层for循环,时间复杂度O(n2)

  • 外层for循环:遍历整个数组
  • 内层for循环:当外层循环遍历到某一个val元素时,内层for循环从这个val元素的后一个元素开始,将数组中各个元素向前移动一位,覆盖掉val元素

注意:内层循环执行后,要有size--,i--(外层循环的计数器)。原因:删除一个元素后需要size-1;同时,因为下标i以后的元素都向前移动了一位,所以i也向前移动一位

// 解法一:暴力解法
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];
                }
                size--; // nums整体向前移动一位,size-1
                i--;    // 要检验刚刚移过来的元素是否为val
            }
        }
        return size;
    }
};

双指针解法

O(n)时间就能移除元素

使用双指针法,关键在于明确两个指针的含义,它们的任务是什么。这里定义快慢指针的作用:

  • 快指针:遍历整个数组,挑出非val的元素,这些元素将组成新数组,新数组就是不含有val元素的数组

  • 慢指针:当前新数组的下标。这个指针实际上在收集非val的元素,遍历形成新数组:它将快指针挑出的非val的元素放到自己指向的位置,然后再向前移动一位,等待收集下一个非val的元素

    27.移除元素-双指针法

整个过程可以理解为:快指针在探测,获得新数组中的元素;慢指针在收集,是新数组的下标。二者同时在同一个数组上操作

  • 快指针遍历整个数组,挑出非val元素,然后慢指针紧随其后,将快指针挑出的非val元素放到自己指向的位置,二者一起向前移动一位
  • 如果快指针指向一个val元素,则慢指针不动,快指针继续向前移动,直至找到一个非val元素,然后将这个元素赋给慢指针指向的位置,二者再同时向前移动一位。这样的过程保证了慢指针左边的所有元素都是非val的元素
// 解法二:快慢指针
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        // 快指针遍历整个数组,慢指针指向更新,覆盖所有等于val的元素
        int slow = 0;
        for(int fast=0; fast<size; ++fast)
        {
            if(val != nums[fast])
            {
                nums[slow++] = nums[fast];	// fast指向非val元素,将这个值赋给slow指向的位置。if更新了新数组的元素
            }
        }
        return slow;
    }
};

总结

这两道题都是比较简单的。第一道题涉及到二分查找,看了Carl的视频学到了边界处理问题,这是我原来一直都不太清楚的。第二道题涉及到数组的删除操作,快慢指针法是很精巧的思想,这种方法在数组和链表的操作中是非常常见的。

  • 27
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值