从 O(N) 到 O(log N):LCR 173 点名问题的五种解法与最优推导

上篇文章:二分查找进阶:巧用"二段性"寻找极值


想象一下,班里有n个同学按学号排队,唯独少了一个人,你怎么用最快的速度把他揪出来?很多同学看到这道题(LCR173.点名),第一反应是直接从头数到尾(遍历),或者算个总和减一下(高斯求和)。这些方法没错,但如果数据量是一千万呢?优秀的工程师永远在追求极致的效率。今天我们不讲废话,直接拆解这道题的5种解法。重点教你如何抓住"数组升序"这个隐藏的王炸条件,用二分查找实现从O(N)到O(logN)的性能飞跃!

目录

[LCR 173.点名(原:剑指offer:0~n-1 中缺失的数字)](#LCR 173.点名(原:剑指offer:0~n-1 中缺失的数字))

理解题意

哈希表法

算法原理

直接遍历找结果法

算法原理

位运算法

算法原理

数学(高斯求和公式)

算法原理

最优:二分查找算法

算法原理


LCR 173.点名(原:剑指offer:0~n-1 中缺失的数字)

https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/

理解题意

数组原本应该包含 0 到 n 的所有整数 (共 个数字,即原本班级有 人,但数组长度为-1)。

数组是严格升序排列的。

缺失了一个数字后,数组的元素值与其对应的下标索引会发生错位。在缺失数字之前的元素,满足records[i] == i。在缺失数字之后的元素,满足records[i] == i + 1 (即records[i] != i)。

我们要找的缺失数字,实际上就是第一个出现records[i]!= i 的下标索引 i

哈希表法

算法原理

最直观的方法是利用哈希表(或额外的一个布尔数组)来记录哪些数字出现过。

1.遍历给定的records 数组,将出现的数字存入哈希表或标记数组中。

  1. 从 0 遍历到 n(也就是数组长度),检查哪个数字不在哈希表/标记数组中。

3.不存在的那个数字即为缺失的学号。

时间复杂度:O(N) 空间复杂度:O(N)

直接遍历找结果法

算法原理

既然缺失数字之前的元素都满足records[i] == i,我们只需从头到尾遍历一次数组,对比元素值和下标。

1.遍历数组,对于当前的下标i,检查 records[i] == i 是否成立。

2.如果发现 records[i]!= i,那么当前的下标i 就是缺失的那个数字。

3.如果遍历完整个数组都没有发现不相等的元素,说明缺失的是最后一个数字(即数组的长度n)。

时间复杂度: O(N) 空间复杂度:O(1)

位运算法

算法原理

利用异或运算(XOR)的特性:a ^ a =0,且a ^ 0= a。我们知道完整的学号应该是 0 ~ n。

1.我们将0~ n 的所有数字进行异或操作,得到一个结果 res1。

2.再将records 数组中的所有元素进行异或操作,得到另一个结果res2。

3.将res1 ^ res2,所有在数组中出现的数字都会因为成对出现而相互抵消(变为 0),最后剩下的结果就是那个唯一没有成对的数字,也就是缺失的学号。

时间复杂度:O(N) 空间复杂度:O(1)

数学(高斯求和公式)

算法原理

利用等差数列求和公式(高斯求和公式)。

1.完整的学号集合是 0 ~ (共n + 1 个数字),其和可以通过公式求得:Sum = n *(n + 1)/ 2

(注意这里的n是指数组的长度)。

2.遍历给定的records 数组,计算当前数组所有元素的实际总和 actualSum

3.缺失的数字就是理论总和与实际总和的差值: Sum- actualSum

时间复杂度:O(N) 空间复杂度:O(1)

最优:二分查找算法

算法原理

利用数组严格升序排列的特性,我们可以将 O(N)的时间复杂度降维打击到 O(log N)。二分查找是解决有序数组搜索问题的最佳选择。

我们可以根据records[i]==i这个条件,将数组划分为两部分(具有二段性):

左半部分:满足records[i] == i。这说明缺失的数字在当前位置的右侧。

右半部分:满足records[i] !=i。这说明缺失的数字就是当前位置,或者在当前位置的左侧。我们要找的,实际上就是右半部分的左边界。

执行步骤:

1.定义左右指针:left = 0,right = recordssize()- 1 。

2.当left < right 时,进行循环:

计算中间索引l:mid = left +(right - left)/ 2。

如果records[mid] == mid,说明 mid 及之前的元素都是正常的,缺失元素在右边,因此更新left = mid + 1

如果records[mid] != mid,说明缺失元素在mid 处或 mid 的左边,因此更新right =mid 。

3.细节问题(边界处理):当循环结束时left ==right,跳出循环。此时指向的元素可能是缺失值对应的位置。我们需要进行最后一次判断:

如果records[left」 ==left,说明整个数组完全没有缺失前面的数字,缺失的是最后一个,应该返回 left + 1

否则,缺失的就是当前下标left。

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

本章完。

相关推荐
灰色小旋风2 小时前
力扣第11题C++盛最多水的容器
数据结构·算法·leetcode
一匹电信狗2 小时前
【LeetCode面试题17.04】消失的数字
c语言·开发语言·数据结构·c++·算法·leetcode·stl
xxxxxxllllllshi2 小时前
【LeetCode Hot100----12-栈(01-06),包含多种方法,详细思路与代码,让你一篇文章看懂所有!】
算法·leetcode·职场和发展
User_芊芊君子2 小时前
【LeetCode经典题解】平衡二叉树高效判断:从O(n²)到O(n)优化
算法·leetcode·职场和发展
五月茶2 小时前
力扣Hot100(Java版本)
java·算法·leetcode
自信150413057592 小时前
数据结构之单链表OJ复盘
c语言·数据结构·算法
仰泳的熊猫2 小时前
题目2265:蓝桥杯2015年第六届真题-移动距离
开发语言·数据结构·c++·算法·蓝桥杯
共享家95272 小时前
Java入门
java·开发语言
小曹要微笑2 小时前
C#中什么是类
开发语言·c#·面向对象·