给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2] 输出: 4
1.暴力法:排序后,以sizeof(int)*2为一个单元来遍历,当发现一个单元内的两个数字不同,输出该单元第一个数字。
int cmpfunc (const void * a, const void * b) { return ( *(int*)a - *(int*)b ); } int singleNumber(int* nums, int numsSize)
{ if(numsSize==1) return nums[0]; qsort(nums, numsSize, sizeof(int), cmpfunc); for(int j = 0; j <numsSize-1 ; j+=2 ) { if(nums[j]!=nums[j+1]) { return nums[j]; } } return nums[numsSize-1]; }
2.异或法:
①运用异或运算的交换律
即:a^b^c^d=a^c^d^b ,任意交换位置,结果都相等
②运用异或运算的本质
即:a^a=0,0^a=a
int singleNumber(int* nums, int numsSize)
{ int sum = nums[0]; for(int i = 1;i < numsSize;i++)
{ sum ^= nums[i]; } return sum; }
3.递归异或法:
核心要义,从数组最后一个元素开始异或到第一个元素,
擦边球:最先return的是numsSize=1,在这一层,由于该三目运算符的特性,并不会计算后面那个非法调用的函数,但语法规则是合法的。
实际上这种办法比方法②还要糟糕,甚至有时候比方法①还要糟糕,递归调用需要消耗大量函数栈,也就是栈空间,不过看上去是相当华丽。
int singleNumber(int* nums, int numsSize)
{ return numsSize==1?nums[0]:(singleNumber(nums+1,numsSize-1)^nums[0]); }
4.hash:
这里实现hash的关键是a[nums[i]-min]这一步,可以保证a数组的下标都为正。出现两次的位置的元素都会是累加两次,没出现的位置为0,仅出现一次的地方只会累加一次。
int singleNumber(int* nums, int numsSize){ if(numsSize == 1) return nums[0]; int i = 0; int max = nums[0]; int min = nums[0]; for(i = 0; i<numsSize; i++) { max = max > nums[i]? max:nums[i]; min = min < nums[i]? min:nums[i]; } max = max-min+1; int a[max];//声明一个考虑了最坏情况的最小数组,来承接数字 memset(a,0,sizeof(int)*(max)); for(i = 0; i<numsSize; i++) { a[nums[i]-min] += 1; } for(i = 0; i < numsSize; i++) { if(a[nums[i]-min] == 1) { return nums[i]; } } return 0; }
总结:出现概率型+数组的问题,首先考虑hash,其次考虑运算特性。