两个有趣的小题目
@ehk 给了我两个很有趣的题目,感觉很不错哦。
Problem
出现次数最多的数
给定一个大小为 的数组,每个元素都是 里面的整数,且只有一个数出现两次或以上,不一定所有的数都出现过。要求在 时间 空间内找出这个数,且不允许修改数组。
Peak
给定一个二维矩阵 ,保证:
- ;
- 每个元素均不相同;
- 对于 ,
- 对于 ,
求一个点的坐标 ,使得 。
要求时间复杂度 空间复杂度 ,可以认为 是 black-box 访问。
Solution
出现次数最多的数
令 为原数组,考虑一个 条边的图 ,其中 。则在这个图中只有一个点入度大于 1 。
由图论知识知, 由若干个连通分量,其中有且仅有一个连通分量为 型,其余全部为一个环,而我们的目标就是找到 型分量中的那个入口。
注意到 没有入度,故 一定在 分量上,且不在这个分量的环上。我们可以模仿 Pollard-rho 算法,用两个指针 ,初始值均为 ,然后 ,即每次 跳一步, 跳两步,最后 时即为答案。
Extension
其实这个题可以有好多种不同的限制,可以得到很多不同的题目:
- 只有一个数出现两次,其余数只出现一次;
- 允许修改数组, 时间, 空间;
- 不允许修改数组, 时间, 空间;
Peak
首先我们有一个 naive 的做法:随机找一个点,然后每次找周围一个比其大的点走过去,直到走不动为止。
但是……这个算法是可以是被卡到 的。反例很容易构造就不举了。
我们考虑在此基础上设计一个递归算法,每次选择一行,然后把行数折半,选择一边递归下去。为了保证在保留的一半中一定存在答案,我们需要确定:按照以前这个游走算法,最后一定不会跨过我们选择的这一行。于是一个机智的想法就是:我们看找的这一行的最大值是不是一个 peak。如果不是则我们可以选择一边继续递归,否则返回答案。由于游走的过程中,答案会不断增加,因此如果当前行最大值都不是 peak 了,继续游走下去肯定不会跨过这一行。
于是这样我们就有了一个 的算法了。再稍稍优化一下就可以做到 。注意到每次我们是以 的代价把行数变成原来的一半。我们还可以对列也进行同样的操作:以 的代价把列数变成原来的一半。于是新算法为:行和列交替进行,每次去除一半的行或一半的列。这样第一次对行的操作时间复杂度为 第二次为 第三次为 总时间复杂度为 ,对列的也一样,为 ,故总时间为 。