代码改变世界

使用“初中知识”实现查找重复最优算法 + 最终极限算法

2009-08-08 16:39 鹤冲天 阅读(...) 评论(...) 编辑 收藏
这是园子里讨论了好长时间的题目了:
1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?
发起文章:算法题,求高手. 作者: 莫贝特(MBetter) 
算法改进:利用异或的特性解决,找出重复数的问题,应该是目前最优算法。  作者:Ivony...

莫贝特给出的算法是:将所有数加起来,减去1+2+...+1000的和
Ivony给出的算法是: 
将所有的数全部异或,得到的结果与1^2^3^...^1000的结果进行异或,得到的结果就是重复数
两位算法的时间复杂度都是: 2n (莫贝特的算法可以用高斯算法简化)

我把题目扩展了一下,将1000换成n,让些题更通用一些:
1-n放在含有n+1个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?(n可能为奇数)(注意:数组是无序的,有序数组很好解决不讨论)

下面是我的算法,先给出代码,c#语言

        /// <summary>
        
/// 查找重复的数
        
/// </summary>
        
/// <param name="ints">1..n无序序列加上1..n之间的一个数</param>
        
/// <returns>重复的数</returns>

        public static int GetRepeated(int[] array)
        
{
            
int temp = 0;
            
foreach (int i in array)
                
if (i % 2 == 1) temp += i + 1;
                
else temp -= i;
            
if (array.Length % 2 == 0) temp -= array.Length;//n为奇数要处理一下
            return temp < 0 ? -temp : temp - 1;
        }
原理如下(n为偶数时):
(1-2)+(3-4)+(5-6)+(7-8)+....+(n-1 - n)  等于多少?先不管,再改进一下
((1+1)-2)+((3+1)-4)+((4+1)-5)+((5+1)-6)+...+((n-1+1) - n) 等于多少?零!这个公式证明初中生都会!

这是有序序列,对无序的情况我们可以这样理解:从数组中依次读数,如果读到是奇数则加上再加1,如果偶数则减去。
和代码中的foreach对照一下吧,应该比较好理解!
函数中倒数第二行对n是奇数的情况作了些处理。

最后返回处理:
      如果重复的数是偶数,则肯定被减了,则返回它的负值-temp;
      如果重复的数是奇数,则肯定被加了,而且还多加了1, 返回 temp-1。

这个算法时间复杂度是n,空间复杂度是1,代码行数是6,原理也简单。

利用初中(小学奥赛估计也会)的知识解决一个算法问题,感觉算不上高手。只能感叹我们以前的知识忘记了太多了!

----------------------------------------------------------------------------------------------------------
刚才又看前面两篇帖子的回复,发现了loogn 提供的一个算法,本人修正了一下:
        public static int GetRepeated2(int[] array)
        
{
            
int temp = 0;
            
for (int i = 0; i < array.Length; i++)
                temp 
+= array[i] - i ;
            
return temp;
        }
算法原理:改进莫贝特的算法,加减加减加减...,而不是(加加加...)减(加加加...)。出现溢出的可能性也小了很多!
更是精简,强人!!

不甘落后,我用lambda再改进一下:
        public static int GetRepeated3(int[] array)
        {
            
return array.Select((i, j) => i - j).Sum();
        }
简单说明一下:
      方法三中的“i”相当与方法二中的“array[i]”;
      方法三中的“j”相当于方法二中的“i”

其它大可不必写成一个函数,像下面这样直接调用就好了,也算是最后的极限算法了。
    int repeatedNum = array.Select((i, j) => i - j).Sum();
array就是包含n+1个数的数组,repeatedNum就是其中唯一重复的数。这应该是极限了吧!

大家可以调试运行一下这几种方法都可以通过,有问题拍我。

本人系列文章《c#扩展方法奇思妙用》,敬请关注!