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

C++打卡学习第一天!

数组理论基础

vector

C++中的vectorarray是两种不同的数据结构,它们在很多方面都有区别。下面是它们之间的一些主要区别:

  1. 大小可变性

    • vector是一个动态数组,其大小可以在运行时动态增加或减少。您可以使用push_backpop_back等函数来添加或删除元素。
    • array是一个静态数组,其大小在创建时确定,不能直接更改。如果需要更改大小,您需要创建一个新的数组。
  2. 内存管理

    • vector使用堆内存来存储其元素,因此可以根据需要分配和释放内存。
    • array使用栈内存来存储其元素,其大小在编译时确定,通常较小。如果数组太大,可能会导致栈溢出,因此通常不建议存储大量数据。
  3. 传递给函数

    • 当将vector传递给函数时,通常传递引用或指针以避免复制整个容器。
    • 当将array传递给函数时,可以传递引用或指针,但由于其大小在编译时已知,也可以传递整个数组作为参数。
  4. 性能

    • vector的大小可变性和动态内存分配可能会导致一些性能开销,特别是在频繁插入和删除元素时。
    • array在访问和迭代元素方面可能更快,因为它是一个简单的连续内存块。

综上所述,如果需要动态大小、灵活性和方便的内存管理,vector通常是更好的选择。如果需要固定大小的数组,并且性能很重要,可以考虑使用array

地址

在C++中二维数组的空间地址在内存中是连续分布的

704. 二分查找

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

文章讲解:代码随想录

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

【有序数组+无重复元素】可以用二分法

左闭右闭写法

class Solution {
public:
    int search(vector<int>& nums, int target) {
        //左闭右闭写法
        int left = 0, right = nums.size()-1;
        while(left<=right){
            // int middle = (left + right)/2;  // 注意两个int相加可能越界
            int middle = left + ((right - left) >> 1);  
            // 计算数组的长度,也就是右边界和左边界之间的距离;
            // 将上述长度除以2的操作,使用位运算 >> 1 来实现。
            // 位运算的右移操作相当于将一个数除以2的n次方(这里是2的1次方,即除以2)
            if(nums[middle] > target){
                right = middle - 1;
            }
            else if(nums[middle] < target){
                left = middle + 1;
            }
            else return middle;
        }
        return -1;
    }
};

左闭右开写法

class Solution {
public:
    int search(vector<int>& nums, int target) {
        size_t numsize = nums.size();
        //左闭右开写法
        int left = 0;
        int right = numsize;
        int middle;
        while (left<right){
            // int middle = (left + right)/2;  // 注意两个int相加可能越界
            middle = left + ((right - left) >> 1);  
            // 计算数组的长度,也就是右边界和左边界之间的距离;将上述长度除以2的操作,使用位运算 >> 1 来实现。
            // 位运算的右移操作相当于将一个数除以2的n次方(这里是2的1次方,即除以2)
            if(nums[middle] > target){
                right = middle;
            }
            else if(nums[middle] < target){
                left = middle + 1;
            }
            else return middle;
        }
        return -1;
    }
};

时间复杂度:O(log n)

  1. 每次查找都将问题的规模减半:在二分查找中,每次比较都将搜索范围缩小到原来的一半。这是因为它利用了数组(或有序数据结构)的特性,根据中间元素与目标元素的大小关系,可以确定目标元素位于左半部分还是右半部分,从而将搜索范围缩小一半。

  2. 每一次操作都是常数时间:在每次查找中,只需执行一次比较操作来确定目标元素位于哪一半,这是一个常数时间操作,不依赖于数据集的大小。

由于每次查找都将问题规模减半,这导致了二分查找的时间复杂度是对数时间复杂度,即O(log n),其中n是要搜索的元素的数量(或数据集的大小)。这意味着无论数据集有多大,二分查找都可以在非常快的时间内找到目标元素,因为它以对数级别的速度增长,而不是线性地随数据集大小增加。这使得二分查找成为一种高效的查找算法,特别适用于大型有序数据集。

空间复杂度:O(1)

二分查找都不会随着数据集大小的增加而增加额外的内存消耗,空间复杂度始终保持在O(1)。这使得二分查找成为一种具有高效空间利用率的查找算法,特别适用于在内存受限的环境中运行。

35. 搜索插入位置

题目链接:

文章讲解:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        // 左闭右开
        int left=0, right=nums.size();
        while (left<right){
            int mid = left + ((right - left)>>1);
            if (nums[mid]<target){
                left = mid + 1;
            }
            else if (nums[mid]>target){
                right = mid;
            }
            else return mid;
        }
        return right;
    }
};
  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)

34. 在排序数组中查找元素的第一个和最后一个位置

题目链接:力扣链接

文章讲解:代码随想录 (programmercarl.com)

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        // 左闭右闭
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        // 情况一:target 在数组范围的右边或者左边,此时应该返回{-1, -1}
        if(leftBorder==-2 || rightBorder==-2) return {-1,-1};
        // 情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
        if(rightBorder-leftBorder>1) return {leftBorder+1,rightBorder-1};
        // 情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
        return {-1, -1};
    }
private:
    // 二分查找,寻找target的右边界(不包括target)
    int getRightBorder(vector<int>& nums, int target){
        int left=0, right=nums.size()-1;
        int RightBorder = -2;  // 记录一下rightBorder没有被赋值的情况
        while(left<=right){
            int mid = left + ((right-left)>>1);
            if(nums[mid]>target){
                right = mid - 1;
            }
            else{
                left = mid + 1;
                RightBorder = left;
            }
        }
        return RightBorder;
    }

    int getLeftBorder(vector<int>& nums, int target){
        int left=0, right=nums.size()-1;
        int LeftBorder = -2;  // 记录一下rightBorder没有被赋值的情况
        while(left<=right){
            int mid = left + ((right-left)>>1);
            if(nums[mid]<target){
                left = mid + 1;
            }
            else{
                right = mid - 1;
                LeftBorder = right;
            }
        }
        return LeftBorder;
    }
};

27. 移除元素

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

文章讲解:代码随想录

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

  1. --len:这是前置递减操作符。它表示先将变量len的值减1,然后返回减1后的值。这是一个前缀操作,因此在进行减法操作之前先执行减1操作。

    例如:

    int len = 5; int result = --len; // result现在等于4,len现在等于4

  2. len--:这是后置递减操作符。它表示先返回变量len的当前值,然后再将其减1。这是一个后缀操作,因此首先返回当前值,然后再执行减1操作。

    例如:

    int len = 5; int result = len--; // result等于5,len现在等于4

暴力解法

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int len = nums.size();
        for (int i=0; i<len; i++){
            if (nums[i]==val){
                len--;
                for (int j=i; j<len;j++){
                    nums[j] = nums[j+1];
                }
                i--;
            }
        }
        return len;
    }
};
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)

双指针法

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        // 双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
        // 定义快慢指针
        // 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
        // 慢指针:指向更新 新数组下标的位置
        int slow=0;
        for (int fast=0; fast<nums.size(); fast++){
            if (nums[fast]!=val){
                nums[slow] = nums[fast];
                slow++;
            }
        }
        return slow;
    }
};
// 时间复杂度:O(n)
// 空间复杂度:O(1)

总结

今日学习时间4.5h,34题还没有完全理解,二刷再来熟练掌握!

最近更新

  1. TCP协议是安全的吗?

    2023-12-09 23:32:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-09 23:32:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-09 23:32:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-09 23:32:01       20 阅读

热门阅读

  1. XMake构建Qt项目报错“undefined reference”

    2023-12-09 23:32:01       39 阅读
  2. TensorFlow的介绍

    2023-12-09 23:32:01       37 阅读
  3. Python处理Excel文件并与数据库匹配做拼接

    2023-12-09 23:32:01       31 阅读
  4. 单片机中的printf思考

    2023-12-09 23:32:01       36 阅读
  5. 分享一个用C#写的Aspose.Words生成word的工具类

    2023-12-09 23:32:01       31 阅读
  6. c语言编程题经典100例——(90~95例)

    2023-12-09 23:32:01       34 阅读
  7. [动态规划]最长公共子序列

    2023-12-09 23:32:01       31 阅读
  8. 从Android源码中生成系统签名文件

    2023-12-09 23:32:01       37 阅读
  9. 面向无组织点云中快速鲁棒的边缘提取方法

    2023-12-09 23:32:01       31 阅读
  10. 考研真题数据结构

    2023-12-09 23:32:01       32 阅读
  11. Centos7安装docker支持NVIDIA GPU

    2023-12-09 23:32:01       30 阅读
  12. 反向传播算法

    2023-12-09 23:32:01       34 阅读
  13. 《C++新经典设计模式》之第18章 备忘录模式

    2023-12-09 23:32:01       37 阅读
  14. 考研真题数据结构

    2023-12-09 23:32:01       37 阅读
  15. 数据科学:Scipy、Scikit-Learn笔记

    2023-12-09 23:32:01       33 阅读
  16. Kotlin关键字二——constructor和init

    2023-12-09 23:32:01       44 阅读
  17. python中星号(*)的作用

    2023-12-09 23:32:01       41 阅读