|
大家好,昨天和同事聊到了一个比较有意思的C语言面试题,感觉还不错,拿出来与大家分享一下,记得在以前的推文中我们写过此类问题,此题确实较为经典,我们也对它进行了一定的变形总结推广,希望对大家有所帮助,请看题: / K" D, ^* s' d9 w8 h" T- V
) D+ o) A2 B8 J1 g
题目是这样的:在一个整数数组中1个数出现了3次,其余的数都出现了2次,请找出出现3次的数。
- n2 _6 B7 Y5 j$ [" @% m4 P
建议大家自己先思考一下,我们下面直接给出了解法。) q+ l1 k# o4 l3 ?, x# P
- j8 r8 K) H; j i5 b: H( J
一、常规解法(3种); e7 C! Z! r5 H7 P, z8 o/ D
1.用两个循环,外层循环每次提供一个数,内层循环遍历数组进行比对,用另外的变量存储相等的次数,内层循环结束之后,如果存储次数的变量等于3,就输出这个元素,然后退出,否则外层循环提供下一个值,继续上述过程。
, Y# K% v; b- l: w/ A( T- for(i=0;i<SIZE;i++)! G @+ _; Q7 {" s: w
- {; K+ ]7 z! ?2 r- e' n
- n=0;
9 D- ~ G5 A3 z - for(j=0;j<SIZE;j++)
$ W& `+ E# c5 \; c; T - if(src[i]==src[j]) Z" R7 T& b! [3 X
- n++;! I( J& W6 L/ N* X' _2 [/ O% f$ o
- if (n==3)' T8 M, k/ O, p7 W ]! m
- {
% M. }" T: @, H! x* T# t+ B8 w2 g - printf("%d\n",src[i]);* H3 \4 l' J$ w
- return;
6 T; |1 p2 h+ r$ F8 k: T9 s - }
3 x: M5 e6 ?; j' d! | - }
复制代码 4 z0 W9 \5 ?6 r- z9 |" b$ C6 l* V
$ e. o5 d8 }! o" M8 r8 Q. \
2. 建立一个临时数组,初始化为0,遍历一遍题目所给数组,将数组对应的值记录到新数组中,出现一次就++,所给数组的值和新数组的下标一一对应,然后遍历新数组,读出记录的值。
2 v) N, k y" P0 R- W! g& X- for(i=0;i<SIZE_src;i++)/ J+ i& @4 l/ w" x
- tmp[src[i]]++; //临时数组tmp
; X% h4 G! J- e8 _" I! n - for(i=0;i<SIZE_tmp;i++)0 n, S9 u3 X7 G" C
- if (tmp[i]==3) g8 X. q6 K, M4 H3 L3 n( W
- {
* e5 y# p( J( D/ s - printf("%d\n",i);//打印下标1 z! Q2 F. x4 z! E$ Y" f
- return;
- x1 U! _$ |) V$ ]; x" |8 P - }
- G6 C) s- L4 X; A5 C, F
复制代码1 Q5 L$ ^7 A) R" \
3. 先用快排排序,再遍历数组,如果第i个数组元素与第i+1个相同就i+2,如果i与i+1,i+2都相同,就输出,否则继续下一轮循环。 - 9 e. A4 a* l# {+ U
- for(i=0;i<SIZE;i+=2) {
% W( j' \' o. J - if(src[i]==src[i+1]){" \) t8 O! @3 k. P/ b) T
- if(src[i]==src[i+2]) {
$ `5 X1 r; t, C% e - printf("%d\n",i);
4 ~! A" S% | {0 r0 V/ G - return;}}}
复制代码
: z, m$ h* Y- Q, r/ }; K' S1 E2 a* C1 x; t* Z ^$ `1 Y
二、最优解
3 ^/ s3 ]. `8 W. R. @* v: V' X) b 以上3种方法无疑都是可行的,但都不是最优解,有的时间复杂度有问题,有的无法估计边界,有的代码太复杂,也不是我们今天想讲的。其实本题采用位运算中异或是最简单的。 . f; ?! p6 d. r! I0 C( Z' U
讲之前要明确几个知识点:(1)整数与0异或为本身。与本身异或为0,即本身的奇数次异或还为本身,偶数次异或为0。(2)异或运算符合结合律和交换律。有了以上两点,本题很简单了,全部异或后,出现2次数的异或都为0,只剩出现3次的数了。
$ R8 Z4 m. z) X7 E3 ?- int singlenumber(int src[], int size)7 {4 a- g! C; N! _
- {; p! A1 s+ n6 c8 X) j
- int result=0;: f7 H6 Z. k; p- U7 _4 h
- for(int i=0; i<size; i++)
& t! c0 p# q: K4 T" E: x4 l2 K8 x - result ^= src[i]; //遍历数组全部异或' I5 ]8 E) d+ }' ^6 [
- return result;% K$ ?1 P& C: U1 z2 [" O b
- }
复制代码 3 O8 f) s4 u. ~2 h2 {2 ~7 A
. j) v. u" G3 S+ U( M. c3 U
三、变形推广7 f/ O/ c2 |, }; Y* J$ A8 }& f
看到这里,本题就可推广为一个数出现了奇数次,其他数全为偶数次,找出该数,以上代码依旧没有问题。其实还可以有很多经典变形,都是利用位运算解决,但难度有所提高。请看如下两个变形:
; N. C' b7 w& j( j/ [* ?8 T8 y( i' D
(1)若有两个数出现了1次,其他数出现了2次,找出这两个数。(原百度面试题) (2)若有一个数出现了1次,其他数出现了3次,找出这个数。 5 ^ x% T# }9 d3 T3 T( ~
我们先看第一题:现在数组中有两个数字只出现1次,直接异或一次只能得到这两个数字的异或结果,但光从这个结果肯定无法得到这个两个数字。如果能将两个数分开到二个数组中,那显然符合上述“异或”解法的关键点了。因此这个题目的关键点就是将这两个数分开到二个数组中。由于在二进制上必定有一位是不同的。我们只需找出第一位不同的就行,这根据异或的结果就可以知道了,这个结果在二进制上为1的位都说明这两个数在这一位上是不同的。 - 3 ^ w0 L! [0 `: j7 S ~4 n1 D
- void fun(int src[], int size, int *n1, int *n2)
5 ^5 r- @: I! j) ^1 q) F+ m - {
6 q; x. r; ^8 _% o; ? - int i, j, result;
3 C l3 l/ I6 E, V7 u - //计算这两个数的异或结果
# i8 x5 M1 |* H$ d8 r: ^1 r# [ - result = 0;0 ~. b6 \: v# e9 F" [$ `
- for (i = 0; i < size; i++)3 F* I9 e0 H; K! q
- result ^= src[i];% G& N9 G o3 j1 S- j/ k+ q
- 3 L; q) A+ f$ w9 j- ^3 E
- // 找第一个为1的位5 T# m4 c v. A: g* h
- for (j = 0; j < sizeof(int) * CHAR_BIT; j++)
3 ?6 ~* j* j0 h2 b) I9 t. h/ g4 E - if (((result >> j) & 1) == 1)
1 i; {" _( d9 T G3 S7 m" H5 T - break;. [: E( t8 {; b
- // 这两个数字在第j位上是不相同的,由此分组即可% a/ f( D6 j- [: s& ?& }6 ?* f
- *n1 = 0, *n2 = 0;0 G8 _) C+ a9 b
- for (i = 0; i < n; i++){
3 |; p% u* j# y- f" t - if (((src[i] >> j) & 1) == 0)6 T# C8 A6 k0 X, o( r
- *n1 ^= src[i];: I& i$ ]! J; V" c/ u- V
- else
9 d4 g7 H' U5 k* m/ L& z" @# _% P - *n2 ^= src[i];* ^# Y8 |; k+ I4 v
- }}! I$ T: [1 F6 E ^5 x
复制代码
K& \ h# W# y D, E
再来看第二题:假如二进制位不进位的话,对数组中除了那个数以外的所有数做累加,每一位上的值都是3的倍数。而在加上这个数后,每一位取模3后,不是1就是0,组合起来就是该数了。(当然还有更好的解法,能进一步降低时间复杂度,用到了类似于逻辑电路的相关知识,难度又有所增加,在这里就不讲了) - 3 o( c5 Y; B" z6 k
- int singlenumber(int src[], int n) {
% ^, Z5 J' a1 e: L8 t7 V+ M# C - int result = 0;6 ^, o4 A. g# F# i1 i$ u
- int i, j,sum;! o F$ J0 O6 k
- for(i=0;i< sizeof(int) * CHAR_BIT;i++)
9 u+ O! N, M- N5 H4 H9 X, d - {3 r0 r! D- o+ @
- sum = 0;
' {* U. `+ C( p$ H( D - for(j=0;j<n;j++)
3 `5 F. v! n2 ]. q9 z6 d% u+ k1 S$ Y - sum += ((src[j]>>i)&1); //每轮一位做累加
) Y" i+ a2 n# T - if(sum%3 == 1)
- S- V" P$ A* M( x m - result |= (1<<i);//确定每一位是否为1
; e; _8 D' H" r, ` I - }0 t' }9 E1 B) @( |" A2 y* p8 P N/ v
- return result;& P( m9 n! v" F/ j; ~
- }
* w' n' g7 I" v9 I
复制代码8 \2 B0 }7 x% j s
四、总结
+ m+ t. F) z% b1 q! Z 各位,今天我们讲的题目可以总称为“寻找出现N次的数”,最本质最核心的其实就是位运算的灵活运用,再怎么变换,到了最后都是巧用位运算,这也是这类题最终想考察的知识点,希望今天的文章对大家有所启发、有所帮助,就到这里吧,感谢耐心阅读。 |