数组a[N],存放了1 至N-1 个数,其中某个数重复一次

 数组a[N],存放了1 至N-1 个数,其中某个数重复一次。
写一个函数,找出被重复的数字.时间复杂度必须为o(N)函数原型:
int do_dup(int a[],int N)

 

方法一:

//好算法,因为第二个重复的数字其位置肯定不会变化,而a[a[0]]正好可以访问到每一个位置的数据。
int do_dup(int a[],int N){
	int temp;
	//a[0]为监视哨
	while (a[0]!=a[a[0]])
	{
		temp=a[0];
		a[0]=a[temp];
		a[temp]=temp;
	}
	return a[0];
}

void main(){
	int a[5]={1,2,3,2,0};
	cout<<do_dup(a,5);
	system("pause");
}

 

方法二:

#include <iostream>
using namespace std;

int do_dup(int a[],int N){
	int *p=new int[N];
	int i;
	for (i=0;i<N;i++)
		p[i]=-1;
	for (i=0;i<N;i++)
	{
		if (p[a[i]]==-1)
			p[a[i]]=a[i];
		else{
			cout<<a[i]<<endl;
		}
	}
	delete [] p;
	return 0;
}


void main(){
	int a[5]={1,2,3,2,0};
	do_dup(a,5);
	system("pause");
}


 

题目是这样的, 数组是无序的, 可能没有重复的数,但最多只可能有一个重复的数,要求用最快的方法找到是否有重复的数。

乍一想,挺难的,但是其实非常的简单。

解决办法:

 

数组a[N],1至N-1这N-1个数存放在a[N]中,其中某个数重复一次。写一个函数,找出被重复的数字。时间复杂度必须为o(N)函数原型:

int do_dup(int a[],int N)

 

××××××××××××××××××××××××××××××××××

假金条的数学思想

 

此算法题借鉴了假金条的思想,不过比那个过程简单,存放1至N-1,就相当于依次从各个袋中取出1至N-1个金条,但是有一个是重的,计算这N个数的和将相当于称总重量,减去1至N-1的和(用数学方法求)就可求出来重复的数。总之要充分利用数学知识

 

const int N = 5;

int a[N]={2,1,3,1,4};

int tmp1 = 0;

int tmp2 = 0;

for (int i=0; i<N; ++i)

{

tmp1+=(i+1);

tmp2+=a[i];

}

printf("重复的数:%d\n",N-(tmp1-tmp2));

 

上述方法求1~N的和,减去数组总和,即为N-x 的差值,x为待找的数

可以优化的是1-N的和不用程序算,数学方法直接算了

可定义一个宏,

#define sum(x)  (x(x+1)/2)

当然乘法操作是比较复杂的,当N较小时加几个数的效率可能更高

 

××××××××××××××××××××××××××××××××××

标志数组法

 

申请一个长度为n-1且均为'0'组成的字符串。然后从头遍历a[n]数组,取每个数组元素a[i]的值,将其对应的字符串中的相应位置置1,如果已经置过1的话,那么该数就是重复的数。就是用位图来实现的。

 

其实,只要数还是0 -- n-1里面的数,那么可以用这样的方法判断所有的重复数的位置和值。

比如这样的一个数组

{2,3,1,2}

我们生成一个字符串"000";

然后开始遍历,a[0] = 2;

所以,字符串的第二位为"0",那么我们将其置为"1"

字符串为"010"

重复,字符串为"011",,,,,"111"

然后,判断a[3] = 2 那么 第二位为"1"

所以,a[3]为重复数,并且位置为第4位。

 

上述map等各方法的思路是一致的,即访问过后做标记,再次访问时即可知道是否重复。如果考虑空间复杂度的话,其空间o(N)

int do_dup(int arr[],int NUM)

{

        int *arrayflag = malloc(NUM*sizeof(int));

 

        while(i++<NUM)

        arrayflag[i] = false;

 

        for(int i=0; i<NUM; i++)

        {

                if(arrayflag[arr[i]] >= false)

                       arrayflag[arr[i]] >= true;           // 置出现标志

                else

                       return  arr[i]; //返回已经出现的值

        }

}

 

××××××××××××××××××××××××××××××××××

固定偏移标志法

 

利用标记法单纯写个O(N)的方法并不难,关键是如何保证两点:

不改变A[]的初始值

函数内不开辟另外的O(N)内存空间.

 

很明显上述方法申请了O(N)内存空间,当N过大时,性能降低了

因此应利用a[N]本身中值和下标的关系来做标记,处理完成后再清除标记即可

 

a[N],里面是1至N-1。原数组a[i]最大是N-1,若a[i]=K在某处出现后,将a[K]加一次N,做标记,当某处a[i]=K再次成立时,查看a[K]即可知道K已经出现过。a[i]在程序中最大也只是N-1+N=2N-1溢出了怎么办啊???),此为一个限制条件

 

int do_dup(int arr[],int NUM)

{

int temp=0;

 

for(int i=0; i<NUM; i++)

{

  if(arr[i]>=NUM)

    temp=arr[i]-NUM;            // 该值重复了,因为曾经加过一次了

  else

    temp=arr[i];

 

  if(arr[temp]<NUM)

  {

    arr[temp]+=NUM; //做上标记

  }

  else

  {

    printf("有重复");   

    return temp;

  }

}

 

printf("无重复");

return -1;

}

上面就是时间复杂度O(N), 空间复杂度O(1)的算法!

 

××××××××××××××××××××××××××××××××××

符号标志法

 

上述方法将元素加一个固定的NUM来做标记,可能会造成溢出。下列方法中利用符号位来做标记,因为1~N都为正值,所以就无限制了。基本思想是用int数组的符号位作哈希表。(反正不是unsigned int符号位闲着也是闲着)

 

bool dup(int array[],int n)

{

     for(int i=0;i<n;i++)

     {

         if(array[i]>0) //可以以此作下标去判断其他值

                 {

               if(array[array[i]]<0)

                          {

                                  return array[i];//已经被标上负值了,有重复

                          }

              else

                         {

                                 array[array[i]]= -array[array[i]]; //否则标记为负

                         }

                 }

         else // |array[i]|代表的值已经出现过一次了

                 {

               if(array[-array[i]]<0)

                          {

                                  return -array[i];//有重复

                          }

              else

                         {

                                 array[-array[i]]=-array[-array[i]];

                         }

                 }

     }

      return -1;//没有重复

}

 

//用于修复数组

void restorearray(int array[],int n)

{

        for(int i=0;i<n;i++)

        {

        if(array[i]<0)array[i]= -array[i];

        }

}

时间复杂度o(n) 空间复杂度o(1)

不过时间复杂度o(n) 空间复杂度o(1)不只一个重复利用此法也是可以实现的

 

 

 

//附上我修改后的算法
bool do_dup_mal(int arr[], int n, int *pre, int *back)
{
int MAX = -1; 
int i = 0; 
int sameVal = -1; 
assert(pre && back); 
*pre = *back = -1; 

for (int j=0; j<n; j++)
{
if (arr[j] > MAX) MAX = arr[j]; 
}

char *arrayflag = new char[MAX+1] ;
if (NULL == arrayflag) 
return -1; 
//while(i++ < MAX) arrayflag[i] = '\0';
memset(arrayflag, 0, MAX+1 ); // '\0' == 0
for(i=0; i<n; i++)
{
if(arrayflag[arr[i]] == '\0')
arrayflag[arr[i]] = '\1'; // 置出现标志
else 

sameVal = arr[i]; //返回已经出现的值
*back = i; 
break; 
}
}
delete[] arrayflag;
if (i < n)
{
for (int j=0; j<n; j++)
{
if (sameVal == arr[j])
{
*pre = j;
return true;
}
}
}
return false; 
}





int main(int argc, char *argv[])
{
int prePos = -1, backPos = -1; 
int myArry[N];
myArry[0] = 1;
myArry[1] = 23;
myArry[2] = 3;
myArry[3] = 4;
myArry[4] = 5;
myArry[5] = 22;
myArry[6] = 7;
myArry[7] = 8;
myArry[8] = 9;
myArry[9] = 22;
myArry[10] = 12;


if (do_dup_mal(myArry, 11, &prePos, &backPos) )
printf("\n

 

参考:http://www.cnblogs.com/relang99/archive/2008/12/23/1360773.html

 

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
顺序结构   •顺序结构就是程序从上到下一行一行地执行,中间没有任何判断和跳转。   •如果main方法多行代码之间没有任何流程控制,则程序总是从上向下依次执行,排在前面的代码先执行,排在后 面的代码后执行。 分支结构 •Java提供了两种常见的分支控制结构: –if语句:使用布尔表达式或布尔值作为分支条件来进行分支控制。 –switch语句:用于对多个整型值进行匹配,从而实现分支控制。 if条件语句 •if条件语句的3种形式: • ▲ if (logic expression) { statements…} • ▲ if (logic expression) { statements…} • else { statements…} • ▲ if (logic expression) { statements…} • else if (logic expression) { statements…} • …//可以有0个或多个else if 语句 • else { statements…} //最后的else语句也可以省略 •注意:if、else、else if 后条件执行体要么是一个花括号括起来的语句块,则这个语句块整体作为条件执行体; 要么是以分号为结束符的一行语句,甚至可能是一个空语句(空语句就是一个分号)。 If语句常见的错误 •如果if、else、else if后的执行体只有一行语句时,则可以省略花括号,但我们最好不要省略花括号,因为保留花 括号会有更好的可读性,且还可以减少发生错误的可能。 •对于if 语句,还有一个很容易出现的逻辑错误,这个逻辑错误并不属于语法问题,但引起错误的可能性更大。如 后面程序TestIfError.java我们想打印的是中年人,但打印出来的结果是青年人。 • 对于任何的if else 语句,表面上看起来else后没有任何条件,或者else if后只有一个条件,但这不是真相:因为 else的含义是“否则”,else本身就是一个条件!else 的隐含条件就是对前面条件取反。 switch分支语句 •可以省略case后代码块的花括号 ◆使用break;语句,防止case穿透 ◆default可以省略,但不推荐省略 ◆switch语句中控制表达式的类型只能是byte、short、char、int、String(JDK7新增)和枚举 Switch语句容易导致的错误 •switch语句后的expression表达式的据类型只能是byte、short、char、int、String类型和枚举; •小心省略了case后代码块的break;时所引入的陷阱。 循环结构 •Java支持3种基本的循环语句: –while 循环语句 –do while 循环语句 – for 循环语句 while & do while 循环语句 ★ while 循环的语法格式如下: [init_statements] while (test_expression) { statements; [iteration_statements] } ★ 执行过程:先判断逻辑表达式的值,若为true 则执行其后面的语句,然后再次判断条件并反复 执行,直到条件不成立为止。 ★ do while 循环的语法格式如下: [init_statements] do {   statements; [iteration_statements] }while (test_expression); ※注意:do while 循环的循环条件必须有一个分 号,这个分号表明循环结束。 ★ 执行过程:先执行语句,再判断逻辑表达式的 值,若为true,再执行语句,否则结束循环 控制循环条件 •使用循环时,一定要保证循环条件有变成false的时候,如果循环条件永远为true,那就是死循环。使用while循   环时还有一个陷阱,while循环条件后紧跟一个分号。 •do while 循环语句里,即使test_expression循环条件的值开始是假,do while循环也会执行循环体。因此,   do while循环的循环体至少执行一次。 本文原创作者:pipi-changing 本文原创出处:http://www.cnblogs.com/pipi-changing/ for 循环语句 •for ([inint_statements] ; [test_expression] ; [iteration_statements]){ statements } •★ 执行过程:首先计算表达式1,即init_statements,接着执行表达式2,即test_expression,若表达式2的 值为true,则执行语句(statements),接着执行表达式3,即iteration_statements,再判断表达式2的值; 依次重复下去,直到表达式的值=false,则结束for循环。因此,for循环的循环条件(表达式2)比循环体(语 句)要多执行一次。 •注意:for循环的循环迭代语句并没有与循环体放在一起,因此即使在执行循环体时遇到continue语句结束本次 循环,循环迭代语句一样会得到执行。 for循环指定多个初始化语句 •for 循环允许同时指定多个初始化语句,循环条件也可以是一个包含逻辑运算符的表达式。但只能有一个声明语   句,因此如果需要在初始化表达式中声明多个变量,那么这些变量应该有相同的据类型。 •初学者使用for循环时也容易犯一个错误,他们以为只要在for后的括号内控制了循环循环迭代语句就万无一失,   但实际情况则不是这样的。 for循环的分号 •for 循环圆括号中只有两个分号是必须的,初始化语句、循环条件、迭代语句部分都可以省略,如果省略了循环   条件,则这个循环条件默认是true,将会产生一个死循环。 •使用for循环时,还可以把初始化条件定义在循环体之外,把循环迭代语句放在循环体内,这种做法将非常类似前   面的while循环。 嵌套循环 •各种基本类型的循环都可以作为外层循环,各种基本类型的循环也可以作为内层循环。 •假设外层循环的循环次为n次,内层循环的循环次为m次,那么内层循环的循环体实际上需要执行n*m次。 •实际上,嵌套循环不仅可以是两层嵌套,还可以是三层嵌套,四层嵌套…… break语句 •break用于完全结束一个循环,跳出循环体。不管是哪种循环,一旦在循环体中遇到break,系统将完全结束循   环,开始执行循环之后的代码。 •break不仅可以结束其所在的循环,还可结束其外层循环。此时需要在break后紧跟一个标签,这个标签用于标 识一个外层循环。Java中的标签就是一个紧跟着英文冒号(:)的标识符。且它必须放在循环语句之前才有作用。 continue 语句 •continue的功能和break有点类似,区别是continue只是中止本次循环,接着开始下一次循环。而break则是 完全中止循环。 return语句 • return关键字并不是专门用于跳出循环的,return的功能是结束一个方法。 •一旦在循环体内执行到一个return语句,return语句将会结束该方法,循环自然也随之结束。与continue和 break不同的是,return直接结束整个方法,不管这个return处于多少层循环之内。 数组类型 •在任何已有类型后加上方括号[ ],又变成一种新类型,这种类型统称为数组类型,所有的数组类型又称为引用类 型,所以又称引用类型。 •Java的数组要求所有数组元素具有相同的据类型。因此,在一个数组中,数组元素的类型是唯一的,即一个 组里只能存储一种据类型的据,而不能存储多种据类型的据。 •一旦数组的初始化完成,数组在内存中所占的空间将被固定下来,因此数组的长度将不可改变。即使把某个数组 元素的据清空,但它所占的空间依然被保留,依然属于该数组数组的长度依然不变。 •Java的数组既可以存储基本类型的据,也可以存储引用类型的据。 •值得指出的是:数组也是一种据类型,它本身是一种引用类型。 定义数组 •Java语言支持两种语法格式来定义数组: –type[ ] arrayName; –type arrayName[ ]; •对于这两种语法格式,一般推荐使用第一种格式。因为第一种格式不仅具有更好的语意,也具有更好的可读性。 •数组是一种引用类型的变量,因此使用它定义一个变量时,仅仅表示定义了一个引用变量(也就是定义了一个指   针),这个引用变量还未指向任何有效的内存,因此定义数组时不能指定数组的长度。 •※注意:定义数组时不能指定数组的长度。 数组的初始化 •静态初始化:初始化时由程序员显式指定每个数组的初始值,由系统决定需要的数组长度。 •动态初始化:初始化时程序员指定数组长度,由系统为数组元素分配初始值 动态初始化 •arrayName = new type[ length]; 在上面的语法中,需要指定一个int整型的length参,这个参指定了数组的长度,也就是可以容纳数组元素的 个数。 使用数组数组最常用的用法就是访问数组元素,包括对数组元素赋值和访问数组元素的值,访问数组元素是通过在数组引用变 量后紧跟一个方括号([ ]),方括号里是数组元素的索引值。 •Java语言的数组索引是从0开始的,也就是说,第一个数组元素的索引值为0,最后一个数组元素的索引为数组长度 减1。 •如果访问数组元素进指定的索引小于0,或者大于等于数组的长度,编译程序不会出现任何错误,但运行时出现异 常:java.lang.ArrayIndexOutOfBoundsException:2(数组索引越界异常),在这个异常提示信息后有一个int 整,这个整就是程序员试图访问的数组索引。 •所有数组都提供了一个length属性,通过这个属性可以访问到数组的长度,一旦获得了数组的长度后,就可以通过循 环来遍历该数组的每个数组元素。 JDK1.5 提供了foreach循环 •从JDK1.5 之后,Java提供了一种更简单的循环:foreach循环,这种循环遍历数组和集合更加简洁。使用 foreach循环遍历数组和集合元素时,无须获得数组和集合长度,无须根据索引来访问数组元素和集合元素, foreach循环自动遍历数组和集合的每个元素。 •当使用foreach循环来迭代输出数组元素或集合时,通常不要对循环变量进行赋值,虽然这种赋值在语法上是允 许的,但没有太大的意义,而且极容易引起错误。 深入数组数组元素和数组变量在内存里是分开存放的。 实际的数组元素是存储在堆(heap)内存中;数组引用变量是一个引用类型的变量,被存储在栈(stack)内存 中。 •如果堆内存中数组不再有任何引用变量指向自己,则这个数组将成为垃圾,该数组所占的内存将会被系统的垃圾 回收机制回收。因此,为了让垃圾回收机制回收一个数组所占的内存空间,则可以将该数组变量赋为null,也就切 断了数组引用变量和实际数组之间的引用关系,实际数组也就成了垃圾。 数组长度不可变 •只要类型相互兼容,可以让一个数组变量指向另一个实际的数组,这种操作会产生数组的长度可变的错觉。 •但由于数组变量整体赋值导致的数组的长度可以改变,只是一个假相。 基本类型数组的初始化 •对于基本类型数组而言,数组元素的值直接存储在对应的数组元素中,因此,初始化数组时,先为该数组分配内 存空间,然后直接将数组元素的值存入对应数组元素中, TestPrimitiveArray 引用类型数组的初始化 引用类型数组数组元素是引用,因此情况变得更加复杂:每个数组元素里存储的还是引用,它指向另一块内存, 这块内存里存储了有效据。 没有多维数组 •Java语言提供了多维数组语法,但多维数组实质上还是一维数组。 Java语言里的数组类型是引用类型,因此,数组变量其实是一个引用,这个引用指向真实的数组内存。数组元素 的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情形看上去很像多维数组。 •定义二维数组语法: •type[ ] [ ] arrName; TestTwoDimension 我们可以得到一个结论: 二维数组是一维数组,其数组元素是一维数组;三维数组也是一维数组,其数组元素是二维数组;四维数组还是一维 组,其数组元素是三维数组……从这个角度来看,Java语言里没有多维数组

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值