C++数据结构 第一天| 数组篇 ~ 二分查找、 移除元素(代码随想录算法训练营)

本文详细介绍了二分查找算法的工作原理,适用场景(有序数组),以及处理奇偶数长度数组和边界问题的方法。同时,通过比较暴力解法(BF)和双指针法在移除元素问题上的效率,展示了二分查找在优化搜索过程中的优势。
摘要由CSDN通过智能技术生成

二分查找(LeetCode 704)

如果我想从10本厚度逐渐增加的书中找到10 cm的书,正常情况下,我会一本一本的找,那么对于n本书,工作量(时间复杂度)就是O(n)。十本找一本,工作量可能较为轻松。切入现实:如果你是图书管理员,架子上有1000本按厚度摆放的书,馆长让你找到厚度是10 cm的书…

貌似"逐个查找"工作量过大,但二分查找却能很快解决这个问题。

什么是二分查找

二分查找:也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用**顺序存储结构**,而且表中元素按关键字有序排列。

结合上述定义和例子,我们知道了 二分查找的条件

1.用于查找的内容逻辑上是有序的

2.查找的数量只能是一个,不能是多个

比如对于一个有序数组[1,2,2,3,5,9],需要查找5的位置就可以使用二分查找

思路

思想很简单,默认数组是递增的

  • 选择数组中间的数和目标值进行对比
  • 如果中间数和目标值相等,则返回答案
  • 如果不相等
    • 如果中间的数字大于目标值,则中间数字向右所有数字都大于目标值,全部排除
    • 如果中间的数字小于目标值,则中间数字向左所有数字都小于目标值,全部排除

蓝色为排除区域

在这里插入图片描述

奇偶问题 和 边界问题

奇偶问题

刚学二分法的时候常常会纠结:数组长度是奇数,很容易找到最中间元素(中位数);但如果是偶数,两边数量就不一样了啊,会不会遗漏某些情况。

答案是:两个情况是一样的,无论是奇数还是偶数,都能正常使用二分法,因为:

  • 两边数量不一样是一定会出现的情况
  • 但是这种情况并不影响我们对中间数字和目标数字大小关系的判断
    • 只要中间数字大于目标数字,就排除右边的
    • 只要中间数字小于目标数字,就排除左边的

边界问题

看了很多不同平台的博主的博客和刷题网站的答案,给予的方法也是大不相同,其中主要集中在边界问题:

  • while循环中 left 和 right 的关系,到底是 left <= right 还是 left < right
  • 迭代过程中 middle 和 right 的关系,到底是 right = middle - 1 还是 right = middle
左闭右闭

对于left <= right,它对应的边界区间是左闭右闭的,那么对应每一次更新边界值时,**nums[middle]**的值是不在查找区间的,更新边界时就需要:right = middle - 1 & left = middle + 1

int search(int nums[], int size, int target)
{
    int left = 0;
    int right = size - 1;
    while (left <= right) {	
        int middle = left + ((right - left) / 2);
        if (nums[middle] > target) {
            right = middle - 1;	
        } else if (nums[middle] < target) {
            left = middle + 1;	
        } else {
            return middle;
        }
    }
    return -1;
}
左闭右开

同理,对于left < right,它对应的边界区间是左闭右开的,那么对应每一次更新边界值时,**nums[middle]**的值是在查找区间的两端,更新边界时就需要:right = middle & left = middle

int search(int nums[], int size, int target) 
{
    int left = 0;
    int right = size - 1;	
    while (left < right) {
        int middle = left + ((right - left) / 2);
        if (nums[middle] > target) {
            right = middle - 1;
        } else if (nums[middle] < target) {
            left = middle + 1;
        } else {	
            return middle;
        }
    }
    return -1;
}

移除元素(LeetCode 27)

思路

对于数组来说,元素是不能删除的,因为数组的元素在内存地址中是连续存储的,不能单独删除数组中的某个元素,只能覆盖。

BF 和 双指针

暴力解法就是套两层for循环,一个for循环遍历数组元素,查找目标元素。第二个for循环遍历剩余数组元素,更新数组。

暴力算法的时间复杂度是O(n^2)

易错点:两个for循环里面,i,j的循环结束条件都是“i,j < size”,很多人会写成“i,j < nums.size()”,这是不对的。size的大小是随着删除数组元素的过程中,不断变小的。是不断变化的一个边界条件。

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];
            }
            i--;
            size--;
        }
    }
    return size;
}

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

定义快慢指针

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

双指针的时间复杂度是O(n)

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

总结

苟日新,日日新,又日新

SUN YAT-SEN UNIVERSITY

2024/4/17

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值