如果一个题目限定了数据范围是[1, n]内的整数,那么这个题目可以思考的就是
nums[i]和 i 的关系,769. 最多能完成排序的块 这个题就使用到了子数组中最大值和 连续[0, n - 1]的关系
而对于本题来说,也可以思考[1, n] 和 nums[i] 的关系,想要观察他们之间的关系
以nums = [1,3,4,2,2]为例,可以画出如下散点图,下列散点图,因为这个题要求不能更改原数组,而且只能使用o(1)的空间复杂度设计算法,所以直接画出{1, 1}, {2, 3}, {3, 4}, {4, 2}, {5, 2} 这几个点
这个图 已经包含了所有nums中的信息,那么如何去解读这张图的信息来找到 2这个出现两次的纵坐标呢?
一个很自然的想法就是画一条水平线,让这条水平线从上往平移,一旦平移到穿过两个即以上的点,那么此时这条水平线的纵坐标就是要找的重复两次的数,这样的想法就是两个for循环来实现的o(n2) 的算法,能够满足题目要求,但是这样的算法不能通过全部测试用例
那么怎么解决这个问题?首先想一想有没有o(nlogn)的解法?
logn的解法中二分就是很常见的算法,可是二分只能针对有序数组进行啊,1 3 4 2 2 算哪门子的有序数组?那不如换个思路,在上图中画出四条平行线,y1 = 1, y2 = 2, y3 = 3, y4 = 4,观察后发现,
y1 穿过以及其下方的点有1个,y2 穿过以及其下方的点有3个,
y3 穿过以及其下方的点有4个,y4 穿过以及其下方的点有5个
正好是在 y2 处多穿过了两个点,
此时可以根据以上事实发现如下规律:
要找的数是[1,n-1]中的某个target,这个target满足下列性质
假设x是属于[1,n-1]中的一个数,记在nums中小于等于x的有yx个
如果yx <= x,则x 一定不是要找到的target,且x < target
如果yx > x,则纳入target备选集合中,且x >= target
而target就是备选集合中最小的那个数
那么就可以使用如下算法
class Solution {
public:int findDuplicate(vector<int>& nums) {int n = nums.size();int l = 1, r = n - 1;while(l < r){int mid = l + (r - l) / 2;int count = 0;for(int i = 0; i < n; ++i){if(nums[i] <= mid){count++;}}if(count <= mid){l = mid + 1;}else{r = mid;}}return l;}
};
其实现原理可以通过几张图演示