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

今日学习的文章和视频链接

704文章链接: link
704视频讲解链接: link
27文章链接: link
27视频讲解链接: link

704 二分查找

看到题目第一想法

因为题目是一个有序数组,且数组中无重复元素,所以查找目标值首先想到的方法就是二分法

看完代码随想录后的想法

代码随想录中采用的也是二分法

采用二分法的条件

本题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。

二分法查找的关键

  1. 区间定义
    二分法思路不清晰的主要原因就是对区间的定义没有搞清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。

    区间定义常见为两种:

    • 左闭右闭[left,right]
    • 左闭右开[left,right)
  2. 边界条件
    二分查找涉及的很多的边界条件,需要根据你选择的区间定义来设置边界条件。
    例如:
    while(left < right) or while(left <= right)
    right = middle or right = middle - 1

实现过程中遇到的困难

1.第一种写法,定义区间[left,right]
定义区间后如何设置边界条件?

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

2.第二种写法,定义区间[left,right)
定义区间后如何设置边界条件?

  • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

代码

第一种写法[left,right]

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) / 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;
    }
};

第二种写法[left,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;
    }
};

27 移除元素

看到题目第一想法

看到本题目要求原地移除所有数值等于val的元素,并返回移除后数组的新长度,想到的解决方法是双指针法,通过快慢指针删除对应的元素。

看完代码随想录后的想法

代码随想录推荐的也是双指针法。
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

删除过程如下:
在这里插入图片描述

实现过程中遇到的困难

双指针法(快慢指针法)的解法已经掌握,但相向双指针方法有点不理解。

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

代码

双指针法(快慢指针法)

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;
    }
};

今日收获

1.对二分法有了更深刻的理解,关键就是在循环中坚持根据查找区间的定义来做边界处理,即循环不变量规则。
2.加深了对双指针法中快慢指针定义的理解
今日学习时常3h

猜你喜欢

转载自blog.csdn.net/m0_46555669/article/details/126970834
今日推荐