day 1 | 704. 二分查找、27. 移除元素

目录:

学习链接

题目链接:

https://leetcode.cn/problems/binary-search/

https://leetcode.cn/problems/remove-element/

文章链接:

数组理论基础:https://programmercarl.com/数组理论基础.html#数组理论基础

时间复杂度基础:https://programmercarl.com/前序/关于时间复杂度,你不知道的都在这里!.html

文章讲解:https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html

https://programmercarl.com/0027.%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.html

视频讲解:

https://www.bilibili.com/video/BV1fA4y1o715

https://www.bilibili.com/video/BV12A4y1Z7LP

第一想法

二分查找

需要找到升序数组中目标值的序号,没有则返回-1。算法的复杂度为O(log n)。

如果暴力求解的话,从头找到尾,那算法复杂度应该是O(n)。这题用二分法来求解,先找到中间的值,进行判断。之后根据大小选择是左边还是右边部分,一直这样分下去,最后找到目标值。

移除元素

给出一个数组nums和一个整数val,要求删除数组中所有的val,然后返回剩下的数组k的长度和数组。

题目还有要求,不要为另一个数组额外分配空间,所以是就在这个数组上进行操作。

思考1: 暴力求解,从前往后或者从后往前遍历,如果数组中有值等于val,那么直接将后面的所有往前移一位。直到遍历完。

思考2:无

看完代码随想录之后的想法

二分查找

区间的定义就是不变量,那么在循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则。

要注意是按照左闭右闭的思路写的还是按照左闭右开的思路写的。

主要是因为**.size()函数可以返回容器中实际数据的个数,也就是向量数组的长度。**比如数组长度为6,但是由于从0开始技术,num[6] 是不存在的。所以根据右边区间在哪个位置,就有两种写法。

主要实现代码:

区间取左闭右闭的情况

class Solution {
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;
            if(target<nums[mid]){
                //当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
                right = mid-1;
            }
            else if(target>nums[mid]){
                //当前这个nums[middle]一定不是target,那么接下来要查找的右区间结束下标位置就是 middle + 1
                left = mid+1;
            }
            else{
                return mid;
            }
        }
        //未找到目标值
        return -1;

    }
};

区间取左闭右开的情况

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size();
        while(left<right){
           int mid = left + ((right-left)>>1);
           if(target < nums[mid]){
               right = mid;
           }
           else if(target > nums[mid]){
               left = mid + 1;
           }
           else{
               return mid;
           }
        }
        //未找到目标值
        return -1;
    }
};

两个方法实现的用时和内存消耗情况:

上面为左闭右开情况,下面为左闭右闭情况。可以看到,当使用左闭右开区间时,运行速度更快,初步推测是因为左闭右闭在while中,比左闭右开的情况多了移步的操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1P9VGkLO-1684937370361)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/90eed747-80ff-4654-9f51-9f97001a1c62/Untitled.png)]

  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)

移除元素

题目:给你一个数组 nums **和一个值 val,你需要 原地 移除所有数值等于 val **的元素,并返回移除后数组的新长度。

暴力解法,就是找到对应元素后,将后面所有的元素往前移一位。

这里有个坑,下面代码中,第二个for循环,j的初始值选取,如果直接等于i,需要注意数组的长度问题。要往后选大的一位。除此之外,还要注意找到对应元素并移除后,下标需要再退到之前的一位,并且总长度要减少1.

暴力解法:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int length = nums.size();
        for(int i = 0; i < length; i++){
            if(nums[i] == val){
                //for(int j = i; j
                  //  nums[j] = nums[j+1];
                  //  }     
                for(int j = i+1; j<length;j++){
                    nums[j-1] = nums[j];
                }
            i--;
            length--;
            }
        }
        return length;
    }
};

双指针法(快慢指针法):

通过一个快指针和慢指针在一个for循环下完成两个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;
    }
};

虽然两者运行时间差不多,但是暴力求解的时间复杂度为O(n^2),而双指针法的时间复杂度仅为O(n)。数据量大的时候就能有明显差距。

实现过程中遇到哪些困难

1、C++掌握不熟悉,主要是vector部分没看过

2、没有考虑到区间问题

3、left + ((right -left) >> 1) == (left + right) /2 相当于二进制右移,这两种写法是等价的

今日收获

1、掌握二分法,区间的注意事项。

2、双指针法

你可能感兴趣的:(LeetCode,刷题,C++,c++,leetcode)