三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

力扣【二分查找】:287. 寻找重复数

力扣【二分查找】:287. 寻找重复数
  1. 题目描述:

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

  1. 算法思路:

证明一定存在重复元素:

令集合A = {1,2,3,4,...,n}, B = {bi| 1 <= i <= n + 1},有|A| = n,|B| = n + 1,

将n个数字放入到n + 1个成员的数组可以形式化为函数,f: B -> A,即f(bi) ∈ A,

首先假设映射f是单射,即∀bi, bj∈B, bi ≠ bj ⟹ f(bi) ≠ f(bj),

即f将B的n + 1个元素映射到A的n + 1个不同的元素上,即f(B) ⊆ A,|f(B)| <= |A|,

又因为|f(B)| = n + 1,|A| = n,有n + 1 <= n矛盾,所以映射f不是单射而是多射,

即一定存在重复元素。

首先思考在不使用常量级额外空间的情况下如何做,显然能想到的第一种方式就是设定一个长度为n的数组cnt,用下标对应1 ~ n的数字,

遍历数组nums,如果nums[i] == j其中i是遍历的下标,j是nums的元素值并且是辅助数组的对应下标 + 1,这样只需要遍历nums一次,记录每个元素的出现次数然后找出大于1的即可,

但题目要求常量空间复杂度,又因为重复数x一定有1 <= x <= n具有有序性,所以可以考虑用二分查找解决问题,难点在于二分逻辑,

假设重复数x,数组中的数要么大于x,要么小于等于x,这里统计小于等于x的元素个数用辅助数组cnt记录,

由于题目规定只有一个重复数,假设重复数x只有两个,另一个占据第n + 1个位置,

那么此时nums中小于等于x的元素个数应该等于x + 1,那么对于之后所有大于x的元素,假设是y而言,小于等于它们的元素个数也就应该是y + 1即cnt[y] > y,

而对于小于x的元素,假设是z,则cnt[z] <= z,

所以对一个数x,如果x > cnt[x],则x可能是重复数,如果x <= cnt[x],则x一定不是重复数,这样就得到了二分的逻辑,

如果重复数x的个数超过2,则必然有至少一个数被x替代,假设这个数i < x,对于[i, x - 1]的cnt都减1,依然满足cnt[z] <= z,

如果这个数i > x,对[x + 1, i]的cnt都加1,依然满足cnt[y] > y,

为满足限制额外空间为常量级,可以每次循环记录小于等于数值的元素的个数,这样相当于在二分查找中加入了一次O(n)的遍历,

时间复杂度变为O(n log(n))

  1. 代码:

时间复杂度:O(n log(n))

int findDuplicate(vector<int>& nums) {int lower_bound = 1, upper_bound = nums.size() - 1;while (lower_bound < upper_bound) {int mid = lower_bound + (upper_bound - lower_bound) / 2;int lower_equal_count = 0;for (const int &n : nums) {if (n <= mid) {++lower_equal_count;}}if (lower_equal_count > mid) {upper_bound = mid;} else {lower_bound = mid + 1;}}return lower_bound;}
← 返回列表