二分查找(点名)(8)

https://blog.csdn.net/2601_95366422/article/details/158774021

上节课链接

一.题目

LCR 173. 点名 - 力扣(LeetCode)

二.思路讲解

第一步:找二分性

题目给出的数组 records 是升序排列,学号从 0 开始连续,但仅有一位同学缺席,因此数组长度比完整学号数少 1。正常情况下,如果没有人缺席,那么每个位置 i 上的数应该等于 i。缺席发生后,缺席点之后的所有数都会比下标大 1。这样,数组就呈现出一种规律:在缺席位置之前,有 records[i] == i;在缺席位置之后,有 records[i] > i 这种前后分段的特性,使得我们可以通过比较中间元素的值与它的下标,来判断缺失点位于左侧还是右侧,因此数组具备二分性,可以用二分查找解决。

第二步:采用左端点还是右端点

由于我们需要找到第一个 不满足条件的位置(即第一个下标与值不对应的元素),这是典型的寻找左边界 问题。采用左端点二分查找 模板,中点取向下取整 ,缩进规则为:如果中间位置满足条件(如下标等于值),说明正确部分在左边,目标在右边,left = mid + 1;否则(中间位置不满足条件),说明目标在左边或就是中间位置,right = mid。循环结束时,left 即为第一个不满足条件的位置。

第三步:注意边界情况

如果整个数组所有元素都满足条件(即每个下标都与值对应),说明缺失的值位于数组末尾之外,此时循环结束后 left 会指向数组长度(即最后一个元素的下一个位置)。因此,最终答案应为数组长度,表示缺失的元素就是该下标值。这种情况需要特别处理,确保算法返回正确结果。

三.代码演示

cpp 复制代码
class Solution {
public:
    int takeAttendance(vector<int>& records) 
    {
        int n = records.size()-1;
        int left = 0;
        int right = n;
        while(left < right)
        {
            int mid = left + (right - left)/2;
            if(mid == records[mid])
            {
                left = mid + 1;
            }
            else
                right = mid;
        }   
        
        if(records[left] == left)
            return left + 1;
        return left;
    }
};

四.代码讲解

一、循环条件与中点计算

采用 while (left < right) 作为循环条件,而不是 left <= right。这是因为当左右指针相遇时,区间内只剩一个元素,此时无需继续二分 ,可以直接在循环外进行判断。这种写法可以避免死循环,并符合左端点模板的常规写法。

中点计算采用向下取整 的公式,即 mid = left + (right - left) / 2。当区间长度为偶数时,中点会偏向左边 ,这确保了当 leftright 相邻时,中点指向 left,从而保证区间能够正确收缩,不会出现 right 原地不动的情况。向下取整 是左端点查找的精髓,也是避免死循环的关键。

二、区间缩进规则

在循环中,根据 records[mid]mid 的比较结果,按照以下规则更新指针:

  • records[mid] == mid :说明 mid 位置正常,缺席同学一定在 mid 的右侧。因为缺席点之后所有元素都会大于下标,而 mid 及左侧都满足相等,所以可以安全地将左指针移动到 mid + 1,即舍弃左半区间

  • records[mid] > mid :说明 mid 位置已经异常(因为正常情况下值应等于下标,现在值更大),那么缺席同学可能在 mid 左侧,也可能就是 mid 本身。因此将右指针移动到 mid保留 mid 在搜索区间内 ,继续向左寻找。注意这里不能用 right = mid - 1 ,因为 mid 有可能是缺席点,必须保留。

三、循环结束后的处理

循环结束时,leftright 相等,此时指向的位置是第一个可能的异常点。但需要进一步确认该位置是否真的异常:

  • 如果 records[left] == left,说明当前这个位置仍然是正常的,那么缺席同学应该是下一个学号,即 left + 1(因为数组长度比完整学号少1,所以缺失的是最后一个数)。 例如示例2中,数组为 [0,1,2,3,4,5,6,8],循环结束后 left 指向下标7,而 records[7] = 8,不相等,所以直接返回7;若数组为 [0,1,2,3,4,5,6,7] 则所有都相等,循环结束 left 指向最后一个下标7,且 records[7]==7,此时缺失的是8,即 left+1

  • 如果 records[left] > left,则当前 left 就是缺席学号,直接返回 left

四、细节注意

  • 循环中比较的是 mid == records[mid],由于数组元素是学号,且升序无重复,因此相等关系只出现在缺席点之前。

  • 最后判断条件 records[left] == left 用于处理整个数组都正常的情况,此时缺席学号等于数组长度(因为学号从0开始,数组长度为 n,缺席一个,完整学号应为 0~n,所以缺失的是 n)。这里返回 left + 1 恰好就是数组长度。

相关推荐
m0_662577972 小时前
C++中的RAII技术深入
开发语言·c++·算法
承渊政道2 小时前
【优选算法】(实战体验滑动窗口的奇妙之旅)
c语言·c++·笔记·学习·算法·leetcode·visual studio
lemonth2 小时前
图形推理----
人工智能·算法·机器学习
2401_891482172 小时前
C++代码复杂性分析
开发语言·c++·算法
keep intensify2 小时前
单词搜索-
算法·深度优先
zx_zx_1232 小时前
定长滑动窗口和不定长滑动窗口
数据结构·算法
mjhcsp2 小时前
C++ 梯度下降法(Gradient Descent):数值优化的核心迭代算法
开发语言·c++·算法
yunyun321232 小时前
跨语言调用C++接口
开发语言·c++·算法
m0_518019482 小时前
C++中的装饰器模式变体
开发语言·c++·算法