算法不是一蹴而就的,需要扎实的基本功。不仅要懂得算法的基本思想过程,更重要的是能动手实现。本篇博客主要说一些数组算法的小问题。
本篇使用C++,头文件如下 1 2 3
| #include <iostream> #include <vector> #include <algorithm>
|
特殊数组中的查找
题目要求:
一个整数数组中有一个元素出现了一次,其他元素都出现了两次,使用最小的时间复杂度找出出现一次的数
拓展问题:
(1)如果有两个数均出现了一次,其他都出现了两次,如何查找这两个数?
(2)如果一个数组中有一个数出现了一次,其他数都出现了三次,如何找到出现一次的数?
如果没有位操作在算法上应用的思想,这个问题还是蛮难的。废话少说,直接切入主题。
众所周知,两个相同的数异或结果为零,零与任何数异或为其本身。那么这个问题就很好解决了: 1 2 3 4 5 6 7 8 9 10
| int SelectNumber(vector<int>& v) { int res=v[0]; for (int i = 1; i < v.size(); i++) { res ^=v[i]; } return res; }
|
对于扩展(1),要是能把它变成两个原问题就好了。哈哈,问题就是这么解决的。
第一步:所有的数异或,其实就是那两个不同的数异或的结果
第二步:找这两个数,出现第一位不同的二进制位的位置
第三步:根据这个二进制位将原数组分成两个数组,且这两个数组都满足原题目的要求
第四步:运用原题目的方法找出这两个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| void SelectNumber2(vector<int>& v,int& num1,int& num2) { int tmp=0; int j; for (int i = 0; i < v.size(); i++) { tmp^=v[i]; } for (j = 0; j < sizeof(int)*8; j++) { if(((tmp>>j)&1)==1) break; } num1=0; num2=0; for (int i = 0; i < v.size(); i++) { if(((v[i]>>j)&1)==0) num1^=v[i]; else num2^=v[i]; } }
|
对于扩展(2),这个问题很不好解,因为前面的方法不顶用了。不过要是深刻了解到扩展(1)的精髓,那就是利用位操作对数组进行分类,那是可以解决的。还是先来一个例子吧。
数组:2 2 2 3 3 3 4. 首先用第一位(从低到高)来分类(与运算):
2 3 5
0010 0011 0101
0001 0001 0001
0000 0001 0001
根据相与的结果,可以把3和5分为①类,2分为另②类。通过类成员的个数是否为3的倍数来判断哪个类是我们需要的,显然这里是①类
然后用第二位对①类来分类,
3 5
0011 0101
0010 0010
0010 0000
判断结果是否为零,来进行分类(如果第二位无法分类,则用第三位、第四位…..),通过反复分类,最后所求的数分在一类,且类成员个数为1(就是它本身)
说了这么多,还是上代码吧: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| int SelectNumber3(vector<int>& v) { int i,j; int low=0,high=v.size()-1; int bit =1; while(low <= high) { if(low == high) return v[low]; i = low-1; for(j = low;j<= high;j++) { if((v[j]&bit) == 0) { i++; swap(v[i],v[j]); } } if(i >= low) { if((i-low+1)%3 == 0) low = i+1; else high =i; } bit = bit<<1; } return 0; }
|
第K大问题
题目要求:在一个整数数组中,查找该数组中第K大的元素。
拓展问题:Top K问题!
这两个问题都是计算机界经典问题。对于原题目,思路是这样的:
第一步:选择一个基准点,进行快排(也就是比它大的放在左边,比它小的放在右边),同时记录比它大的个数count;
第二步:然后比较k与count+1的大小,若k小,则所求的值在左边,接着对左边进行递归,若k大,则所求的值在右边,接着对右边进行递归,若相等,返回该值。
上面的思路很清晰,这里的关键是快排算法如何去实现:
第一步:首先将基准点交换到数组的首位,使其在比较过程中不参与交换
第二步:设立两个指针,一个用于遍历数组,另一个指针m用于指向比基准点大的值应该放的位置
第三步:遍历完数组后,将首位的基准点和最后m指向的值进行交换,这样一次快排就完成了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| int Find_k(vector<int>& v,int low,int high,int k) { if(k<=0) return -1; if(k>high-low+1) return -1; int pivot = low+rand()%(high-low+1); int count=0; int m=low; swap(v[pivot],v[low]); for (int i = low+1; i <= high; ++i) { if(v[i]>v[low]) { swap(v[++m],v[i]); count++; } } swap(v[m],v[low]); if(k<(count+1)) Find_k(v,low,m-1,k); else if(k>(count+1)) Find_k(v,m+1,high,k-(count+1)); else return v[m]; }
|
对于拓展问题,使用C++ STL模板中已有的数据结构堆和最大堆排序算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void FindMinTopK(vector<int>& vec,int k) { vector<int> heap(vec.begin(),vec.begin()+k); make_heap(heap.begin(),heap.end()); int i; for(i =k;i<vec.size();i++) { if(vec[i]<heap[0]) { pop_heap(heap.begin(),heap.end()); heap.pop_back(); heap.push_back(vec[i]); push_heap(heap.begin(),heap.end()); } }
|