代码随想录Day1

1,390 阅读3分钟

LeetCode-704.二分查找

题目链接:二分查找

文章讲解:704. 二分查找

视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili

数组基础

数组:存放在连续内存空间上的相同类型数据的集合。

  • 数组下标都是从0开始的
  • 数组内存空间的地址是连续的

image.png

  • c++中二维数组在地址空间中是连续的
  • 删除或添加数组时需要移动大量元素

LeetCode-704.二分查找

思考:本题中提到数组为有序数组数组无重复元素——>符合使用二分法的前提条件。

二分法的诀窍——>循环不变量:对于区间的定义是不变量,在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。 二分法中,区间的定义一般分为两种,左闭右闭区间和左闭右开区间,由此本题产生了两种二分解法。

注意:middle的初值计算可以使用位运算代替除运算,提高效率;注意middle的取值变化(与区间的取法紧密相关)。

左闭右闭

//两种方法都是二分法寻值
    //法一:左闭右闭区间
    int search1(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        while (right >= left) {
            int middle = left +( (right - left) >> 1);
            if (nums[middle] < target) {
                // 目标在middle右边
                left = middle + 1;
            }
            else if (nums[middle] > target) {
                // 目标在middle左边
                right = middle - 1;
            }
            else {
                // middle == target 即找到目标
                return middle;
            }
        }
        // 未找到目标
        return -1;
    }

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

左闭右开

int search2(vector<int>& nums, int target) {
        int left{0};
        //区间右开 所以right为区间末尾+1
        int right{int(nums.size()) };
        while (right > left) {
            int middle{left +( (right - left) >> 1)};
            if (nums[middle] < target) {
                left = middle + 1;
            }
            else if (nums[middle] > target) {
                right = middle;
            }
            else {
                return middle;
            }
        }
        return -1;
    }

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

LeetCode-27.移除元素

题目链接:移除元素

文章讲解:27 移除元素

视频讲解:数组中移除元素并不容易! | LeetCode:27. 移除元素_哔哩哔哩_bilibili

思路:数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。题目中提炼出信息点:不需要考虑数组中超出新长度后面的元素、不能使用额外的数组空间,只能用O(1)算法原地修改数组、数组顺序可以改变。

暴力解法

两层for循环,一层遍历数组元素,一层循环更新数组(后面元素向前覆盖)

// 暴力解法
    int removeElement1(vector<int>& nums, int val) {
        int count = nums.size();
        for (int i = 0; i < count; ++i) {
            if (nums[i] == val) {
                for (int j = i + 1; j < count; ++j) {
                    nums[j-1] = nums[j];
                }
                //删掉了一个元素,i应该往前移一位
                --i;
                --count;
            }
        }
        return count;
    }

快慢指针法*

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向新数组中等待更新的下标位置
  • 个人理解:快慢指针从低地址同时向高地址同步出发,遇见目标值则慢指针停留一步,快指针领先一步,将后面的元素向前移位覆盖,再次遇见目标值则重复以上步骤。
int removeElement2(vector<int>& nums, int val) {
        int slowPointer = 0;
        for (int fastPointer = 0; fastPointer < nums.size(); ++fastPointer) {
            if (val != nums[fastPointer]) {
                nums[slowPointer++] = nums[fastPointer];
            }
        }
        return slowPointer;
    }

时间复杂度:O(n),空间复杂度O(1).

相向双指针法

定义左右指针,左指针从低地址出发定位目标值,右指针从高地址出发定位非目标值,用右值覆盖左值,循环结束的条件为left <= right。具体见注释。

// 相向双指针  打乱了原顺序
    int removeElement3(vector<int>& nums, int val) {
        int leftPointer = 0;
        int rightPointer = nums.size() - 1;
        // 循环条件
        while (leftPointer <= rightPointer) {
            // 左指针找到为val的元素位置
            while (leftPointer <= rightPointer && nums[leftPointer] != val ) {
                ++leftPointer;
            }
            // 右指针找到不为val的元素位置
            while (leftPointer <= rightPointer && nums[rightPointer] == val) {
                --rightPointer;
            }
            // 用右指针的元素覆盖左指针值为val的元素
            // leftPointer == rightPointer则无动作
            if (leftPointer < rightPointer) {
                //覆盖后 指针移动一位
                nums[leftPointer++] = nums[rightPointer--];
            }
        }
        // 左指针一定为末尾元素下标加一,即是数组大小
        return leftPointer;
    }

时间复杂度:O(n),空间复杂度:O(1)。

第一天打卡,时间安排不太妥当,明天补上额外练习题,cpper-go!