不修改数组找出重复的数字
问题描述
¶给定一个长度为
其中
在满足以下限制的前提下找出数组中任意一个重复的数字:
- 不能修改输入的数组;
- 只能使用
的额外空间;
分治法
¶分治算法的思想是不断收紧区间范围以确定重复的数字其值所在的区间范围,当区间长度为
算法描述
¶根据鸽巢原理易知,原数组中必然至少有一个重复的数字,即答案所在区间为
程序实现
¶时间复杂度:
空间复杂度(额外):
123456789101112131415int duplicateInArray(std::vector<int>& nums) { int L = 1, R = nums.size() - 1; for (int M, cnt; L < R;) { M = (L + R) >> 1; cnt = 0; for (int x : nums) { if (x >= L && x <= M) cnt += 1; } if (cnt > (M - L + 1)) R = M; else L = M + 1; } return L;}
追击法
¶前置知识
¶如何判断一个链表有环呢?
采用双指针遍历链表,快指针每次走两步,慢指针每次走一步,若链表中存在环,则快指针一定能追上慢指针 [2]。
因为至多在环内多跑一圈就可以追上慢指针,所以算法复杂度为:
- 时间复杂度:
- 空间复杂度:
- 时间复杂度:
若链表中存在环,如何找到环的入口呢?
因为快指针(不妨记为
)的速度是慢指针(不妨记为 )的两倍,因此当慢指针在环内未走完一圈时就会被快指针追上 [3]。不妨假设从出发位置到达环的起点的距离是 步,从相遇位置出发再走 步首次到达环的起始点,环的长度为 ,则有:其中
为正整数,分表表示快指针和慢指针走的圈数 (根据前面的分析,我们知道此处 ,虽然这对算法并没有影响)。观察上式可以发现,若慢指针再行走 步,即可到达环的起点。这给了我们一个启发,即当慢指针和快指针相遇时,我们再放一个新的慢指针 让它从链表起点出发,在它走到环的起点时,恰好走了 步,并与慢指针 相遇。综上,我们仅需在快指针和慢指针相遇时,放一个新的慢指针从链表起点出发,当两个慢指针相遇时,即到达环的入口。
因为在找到环的基础上至多走
步,所以算法复杂度为:- 时间复杂度:
- 空间复杂度:
- 时间复杂度:
算法描述
¶因为原题中的数列的数值范围在
不难发现这个链表中一定存在环,因为若不存在环,且走过了
根据前面的分析,不难有:
- 从任意位置出发,都必然到达环;
- 环的起始位置对应的数值即为原问题中的重复的数;
不妨从位置
剩下的问题按照前文求链表的入口的算法求解即可,此处不再赘述。
程序实现
¶时间复杂度:
空间复杂度(额外):
123456789101112131415int duplicateInArray(std::vector<int>& nums) { int x = 0, y = 0;
while (x == 0 || x != y) { x = nums[nums[x]]; y = nums[y]; }
for (x = 0; x != y;) { x = nums[x]; y = nums[y]; }
return x;}
Related
¶