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

1,685 阅读3分钟

终于开始代码随想录day1了,之前也跟着ppt大概刷过一点,但是不全面也没有坚持下来,更没有写博客,那现在就当作从头开始,加油!

704. 二分查找

(704. 二分查找 - 力扣(LeetCode))

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9     
输出: 4       
解释: 9 出现在 nums 中并且下标为 4     

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2     
输出: -1        
解释: 2 不存在 nums 中因此返回 -1        

提示:

  • 你可以假设 nums 中的所有元素是不重复的。
  • n 将在 [1, 10000]之间。
  • nums 的每个元素都将在 [-9999, 9999]之间。

一、二分法易错点:

  1. while循环中的条件到底是left < right还是left <= right?
  2. 边界的取值问题,比如: if(nums[midddles] > target) { right = middle;(还是right = middle- 1;呢?) }

二、二分法的大概思路:

首先,二分法的前提是:有序数组,且数组中无重复元素。前者因为二分的比较基于大小的对比,后者则如果又重复元素会导致返回值不唯一。

然后就是正题,为什么写二分法会乱?最主要的原因就是区间的定义:区间的定义是不变量,不变量就是区间的定义。

循环不变量原则:在while中寻找每次边界的处理都要坚持根据区间的定义来操作。

回到易错点一: while的条件取决于区间的定义,二分法的区间定义一般为两种,要么是左闭右闭[left , right],要么是左闭右开[left, right)。 对于左闭右闭,[left, right]是有意义的(当left = right时)故while的条件得是(left <= right)。

易错点二: 在if (nums[middle] > target) 时right 要赋值为middle - 1,因为在if里已经判断出nums[middle] 一定不等于 target,不需要放到下一次的判断中,只需要取当前nums[middle] 的前一个当作为右边界,放入后续的判断。懂得这个道理之后对于nums[middle] < target的情况也很好解释。

那么对于左闭右开的情况,在理解前者的基础上,易错点一和二都很好理解:因为右边边界是开的,故右值不在区间内,开头的rgiht的定义就得改成int right = nums.size(); if (nums[middle] > target)时 right要赋值为middle,如果还是middle - 1的话就会漏判一个nums[middle - 1]。

以上就是对于二分易错点的基本解析,下面是代码:

public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;//数组的定义为左闭右闭
        while(left <= right){//终止条件为左指针大于右指针
            int mid = left + (right - left)/2;
            //取中点(两个int相加有时候会越界,故这种写法比(left + light)/2 要更加稳妥        
            if(nums[mid] == target) {
                return mid;//返回中点下标
            }
              else if (nums[mid] < target) {
                left = mid + 1;
            }
              else {
                right = mid -1;
            }
        }
        return -1;//不存在目标值则返回-1
    }
};

27. 移除元素

力扣题目链接(opens new window)

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。 示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。 你不需要考虑数组中超出新长度后面的元素。

首先明确数组的基础知识:数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。

那么我们可以先试试暴力解法,就是两层for第一层遍历数组元素,第二层更新数组。 代码如下:

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];
                    //暴力解法:把等于val值的nums[i]后面所有元素往前移一个单位顶替掉nums[i]
                }
                size--;
                i--;//防止两重复的导致漏判,得将i往前移保证下次的i循环是从当前位置开始
            }
        }
        return size;

    }
};

双指针的思路其实就是在一层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;
    }
};

另一种双指针

public:
    int removeElement(vector<int>& nums, int val) {
        int l = 0, r = nums.size();
        while(l < r) {
            if(nums[l] == val) {
                nums[l] = nums[r - 1];
                r--;
            } else {
                l++;
            }
        }
        return l;//因为l从0开始,每当有不等于val值的元素就+1,故为新数组长度

    }
};

今天的学习内容基本上都是代码随想录的,包括视频和文章,[这里大概放个链接](代码随想录 (programmercarl.com))因为是第一天在时间上没有安排充足,做得很仓促,明天再刷点今天的内容并且整理一下当作复习,再学习明天的内容和预习后天的内容,有时间还得看几遍c++primer。