【整理】数组面试题集锦
参考文献地址:
http://blog.csdn.net/hackbuteer1/article/details/8035261
http://happyprince.iteye.com/blog/1849152
http://blog.csdn.net/zhanglei8893/article/details/6230233
http://www.ahathinking.com/archives/135.html
http://blog.csdn.net/hackbuteer1/article/details/7542774
http://blog.csdn.net/v_JULY_v/article/details/6444021
微软面试100系列答案V0.4版[第41-60题答案]:http://blog.csdn.net/v_july_v/article/details/6171539
1、快速找出一个数组中的最大数、第二大数
2、试着用最小的比较次数去寻找数组中的最大值和最小值
3、重排问题
4、找出绝对值最小的元素
5、从长度为n的数组(元素互不相同)中任意选择m个数的所有组合。
6、一个int数组,里面数据无任何限制,要求求出所有这样的数a[i],其左边的数都小于等于它,右边的数都大于等于它
7、求数组中两个元素差的最大值
8、求数组中重复出现次数大于数组总个数一半的数
9、求逆序对
10、O(1)空间复杂度合并两个排好序的数组
11、找出重复的数字
12、求子数组的最大和
13、和为n连续正数序列
1、快速找出一个数组中的最大数、第二大数
如果当前元素大于最大数 max,则让第二大数等于原来的最大数 max,再把当前元素的值赋给 max。如果当前的元素大于等于第二大数secondMax的值而小于最大数max的值,则要把当前元素的值赋给 secondMax。
void GetSecondMaxNumber(int *arr , int n) { int i , max , second_max; max = arr[0]; second_max = 0x80000000; for(i = 1 ; i < n ; ++i) { if(arr[i] > max) { second_max = max; max = arr[i]; } else { if(arr[i] > second_max) second_max = arr[i]; } } cout<<max<<" "<<second_max<<endl; }
2、试着用最小的比较次数去寻找数组中的最大值和最小值
每次比较相邻两个数,较大者与MAX比较,较小者与MIN比较,找出最大值和最小值。方法如下:先将一对元素互相进行比较,然后把最小值跟当前最小值进行比较,把最大值跟当前最大值进行比较。因此每两个元素需要3次比较。如果n为奇数,那么比较的次数是3*(n/2)次比较。如果n为偶数,那么比较的次数是3n/2-2次比较。因此,不管是n是奇数还是偶数,比较的次数至多是3*(n/2),具体的代码如下:
void GetMaxAndMin(int *arr , int n , int &max , int &min) { int i = 0 ; if(n & 1) // 奇数 { max = min = arr[i++]; } else { if(arr[0] > arr[1]) { max = arr[0]; min = arr[1]; } else { max = arr[1]; min = arr[0]; } i += 2; } for( ; i < n ; i += 2) { if(arr[i] > arr[i+1]) { if(arr[i] > max) max = arr[i]; if(arr[i+1] < min) min = arr[i+1]; } else { if(arr[i+1] > max) max = arr[i+1]; if(arr[i] < min) min = arr[i]; } } }
3、重排问题
给定含有n个元素的整型数组a,其中包括0元素和非0元素,对数组进行排序,要求:
1、排序后所有0元素在前,所有非零元素在后,且非零元素排序前后相对位置不变
2、不能使用额外存储空间
例子如下
输入 0、3、0、2、1、0、0
输出 0、0、0、0、3、2、1
分析
此排序非传统意义上的排序,因为它要求排序前后非0元素的相对位置不变,或许叫做整理会更恰当一些。我们可以从后向前遍历整个数组,遇到某个位置i上的元素是非0元素时,如果arr[k]为0,则将arr[i]赋值给arr[k],arr[i]赋值为0。实际上i是非0元素的下标,而k是0元素的下标。
void Arrange(int *arr , int n) { int i , k = n-1; for(i = n-1 ; i >=0 ; --i) { if(arr[i] != 0) { if(arr[k] == 0) { arr[k] = arr[i]; arr[i] = 0; } --k; } } }
4、找出绝对值最小的元素
给定一个有序整数序列(非递减序),可能包含负数,找出其中绝对值最小的元素,比如给定序列 -5、-3、-1、2、8 则返回1。
分析:由于给定序列是有序的,而这又是搜索问题,所以首先想到二分搜索法,只不过这个二分法比普通的二分法稍微麻烦点,可以分为下面几种情况
如果给定的序列中所有的数都是正数,那么数组的第一个元素即是结果。
如果给定的序列中所有的数都是负数,那么数组的最后一个元素即是结果。
如果给定的序列中既有正数又有负数,那么绝对值的最小值一定出现在正数和负数的分界处。
为什么?因为对于负数序列来说,右侧的数字比左侧的数字绝对值小,如上面的-5、-3、-1,而对于整整数来说,左边的数字绝对值小,比如上面的2、8,将这个思想用于二分搜索,可先判断中间元素和两侧元素的符号,然后根据符号决定搜索区间,逐步缩小搜索区间,直到只剩下两个元素。
单独设置一个函数用来判断两个整数的符号是否相同
bool SameSign(int m , int n) { if((m>>31) == (n>>31)) return true; else return false; } // 找出一个非递减序整数序列中绝对值最小的数 int MiniNumAbsoluteValue(int *arr , int n) { if(n == 1) return arr[0]; if( SameSign(arr[0] , arr[n-1]) ) return arr[0] >= 0 ? arr[0] : abs(arr[n-1]); // 二分搜索 int mid , low = 0 , high = n - 1; while(low < high) { if(low + 1 == high) return abs(arr[low]) < abs(arr[high]) ? abs(arr[low]) : abs(arr[high]); mid = (low + high)>>1; if( SameSign(arr[low] , arr[mid]) ) { low = mid ; // 有可能分界点就在mid处 continue; } if( SameSign(arr[high] , arr[mid]) ) { high = mid; continue; } } return abs(arr[low]); }
5、从长度为n的数组(元素互不相同)中任意选择m个数的所有组合。
递归算法:
int arr[] = {1,2,3,4,5,8,5,8,9,10}; int len = 10 , m = 3; void dfs(int index , int num , vector<int> &vec) { int i ; if(index == len+1) return ; if(num == 0) { static int k = 1 ; cout<<k++<<": "; for( i = 0; i < vec.size() ; ++i) cout<<vec[i]<<" "; cout<<endl; return; } vec.push_back(arr[index]); // 选取第index个元素 dfs(index + 1 , num - 1 , vec); vec.pop_back(); // 不选取第index个元素 dfs(index + 1 , num , vec); } int main(void) { vector<int>vec; dfs(0 , m , vec); return 0; }
6、一个int数组,里面数据无任何限制,要求求出所有这样的数a[i],其左边的数都小于等于它,右边的数都大于等于它
能否只用一个额外数组和少量其它空间实现。
分析:最原始的方法是检查每一个数 array[i] ,看是否左边的数都小于等于它,右边的数都大于等于它。这样做的话,要找出所有这样的数,时间复杂度为O(N^2)。
其实可以有更简单的方法,我们使用额外数组,比如rightMin[],来帮我们记录原始数组array[i]右边(包括自己)的最小值。假如原始数组为: array[] = {7, 10, 2, 6, 19, 22, 32}, 那么rightMin[] = {2, 2, 2, 6, 19, 22, 32}. 也就是说,7右边的最小值为2, 2右边的最小值也是2。
有了这样一个额外数组,当我们从头开始遍历原始数组时,我们保存一个当前最大值 max, 如果当前最大值刚好等于rightMin[i], 那么这个最大值一定满足条件,还是刚才的例子。
第一个值是7,最大值也是7,因为7 不等于 2, 继续,
第二个值是10,最大值变成了10,但是10也不等于2,继续,
第三个值是2,最大值是10,但是10也不等于2,继续,
第四个值是6,最大值是10,但是10不等于6,继续,
第五个值是19,最大值变成了19,而且19也等于当前rightMin[4] = 19, 所以,满足条件。如此继续下去,后面的几个都满足。
int arr[100] , rightMin[100]; void smallLarge(int *arr , int *rightMin , int n) { int i , leftMax; rightMin[n - 1] = arr[n - 1]; for(i = n - 2 ; i >= 0 ; --i) { if(arr[i] < arr[i+1]) rightMin[i] = arr[i]; else rightMin[i] = rightMin[i + 1]; } leftMax = 0x80000000; for(i = 0 ; i < n ; ++i) { if(arr[i] >= leftMax) { leftMax = arr[i]; if(leftMax == rightMin[i]) printf("%d\n",leftMax); } } }
7、求数组中两个元素差的最大值
后面的元素减去前面的元素(你可以认为你在炒股票,买入价格和卖出价格就是你的盈利),要求:O(N)时间复杂度,O(1)空间复杂度
思路:首先从包含两个元素的数组开始考虑问题,当这个包含两个元素的问题解决了,那么加一个新的元素会造成什么影响?要改变哪些值?每次都添加一个元素,每次都将这些可能的改变考虑进来,这样就能做到遍历整个数组的时候,问题就解决了。
// 后面的元素减去前面的元素 差的最大值 int max_difference(int *arr , int n) { if(arr == NULL || n < 2) // 非法输入 return 0; int min = arr[0]; int maxDiff = arr[1] - arr[0]; for(int i = 2 ; i < n ; ++i) { if(arr[i-1] < min) min = arr[i-1]; if(arr[i] - min > maxDiff) maxDiff = arr[i] - min; } return maxDiff; }
8、求数组中重复出现次数大于数组总个数一半的数
int MoreThanHalfNum(int *a , int n ) { int i , k , num = a[0]; int times = 1; for(i = 1 ; i < n ; ++i) { if(times == 0) { num = a[i]; times = 1; } else if(a[i] != num) --times; else ++times; } k = 0; for(i = 0 ; i < n ; ++i) { if(a[i] == num) ++k; } if(k*2 <= n) return -1; //没有找到 else return num; //找到 }
9、求逆序对
设A[1...n]是一个包含n个不同数的数组,如果在i<j的情况下,有A[i]>A[j],则(i,j)就称为A中的一个逆序对(inversion)。
给出一个算法,它能用Θ(nlgn)的最坏情况运行时间,确定n个元素的任何排列中逆序对的个数。
合并排序是采用分治法的思想,若需要排序A[p...q],则可以对半分成A[p...r]和A[r...q],然后将这有序的两部分Merge。而Merge的过程为Θ(n)的时间复杂度,根据主定率T(n)=2(Tn/2)+Θ(n),时间复杂度为T(n)=Θ(nlgn)。而求整个序列中的逆序对,也可以利用分治法的思想,即
逆序对(A[p...q])= 逆序对(A[p...r])+逆序对(A[r...q])+逆序对(A[p...r], A[r...q]之间的)。
结合合并排序,关键是求如何在Θ(n)的时间有效的求出A[p...r], A[r...q]之间的逆序对。
因为在合并排序的Merge过程中,A[p...r]和A[r...q]已经有序,假设此时已经Merge到A[s...r]和A[t...q]。考虑接下来的一步:若从前者取出A[s],说明A[s]比后面的序列A[t...q]中的元素都小,不存在逆序对;若从后者取出A[t],则说明A[t]比前面的序列A[s...r]都小,即以t结尾的逆序对的数量为前者的剩余序列A[s...r]中元素的数量。
Merge的过程中即可得到A[p...r], A[r...q]之间的逆序对的数量,时间复杂度亦为Θ(n), 由主定律总的时间复杂为 Θ(nlgn)。
#include<iostream> using namespace std; /* 归并求逆序对数, arr 存储最终有序结果 * 在函数外申请一个临时数组作为参数传入 * 避免递归不断创建临时数组的开销 */ int Merge(int * arr, int beg, int mid, int end, int * tmp_arr) { memcpy(tmp_arr+beg,arr+beg,sizeof(int)*(end-beg+1)); int i = beg; int j = mid + 1; int k = beg; int inversion = 0; // 合并过程中的逆序数 while(i <= mid && j <= end) { if(tmp_arr[i] <= tmp_arr[j]) { arr[k++] = tmp_arr[i++]; }else { arr[k++] = tmp_arr[j++]; inversion += (mid - i + 1); } } while(i <= mid) { arr[k++] = tmp_arr[i++]; } while(j <= end) { arr[k++] = tmp_arr[j++]; } return inversion; } int MergeInversion(int * arr, int beg, int end, int * tmp_arr) { int inversions = 0; // 记录倒序数 if(beg < end) { int mid = (beg + end) >> 1; inversions += MergeInversion(arr, beg, mid, tmp_arr); inversions += MergeInversion(arr, mid+1, end, tmp_arr); inversions += Merge(arr, beg, mid, end, tmp_arr); } return inversions; } /* 测试序列 :answer: 10 */ int testPoint[10] = { 1, 4, 2, 9, 48, 15, 13, 44, 6, 90 }; void main() { int arrcopy[10]; // 临时数组 memcpy(arrcopy,testPoint,sizeof testPoint); printf("the num of inversions is: %d\n", MergeInversion(testPoint,0,9,arrcopy)); }
10、O(1)空间复杂度合并两个排好序的数组
数组al[0,mid-1]和al[mid,num-1]是各自有序的,对数组al[0,num-1]的两个子有序段进行merge,得到al[0,num-1]整体有序。要求空间复杂度为O(1)。注:al[i]元素是支持'<'运算符的。
/* 数组a[begin, mid] 和 a[mid+1, end]是各自有序的,对两个子段进行Merge得到a[begin , end]的有序数组。 要求空间复杂度为O(1) 方案: 1、两个有序段位A和B,A在前,B紧接在A后面,找到A的第一个大于B[0]的数A[i], A[0...i-1]相当于merge后的有效段,在B中找到第一个大于A[i]的数B[j], 对数组A[i...j-1]循环右移j-k位,使A[i...j-1]数组的前面部分有序 2、如此循环merge 3、循环右移通过先子段反转再整体反转的方式进行,复杂度是O(L), L是需要循环移动的子段的长度 */ #include<iostream> using namespace std; void Reverse(int *a , int begin , int end ) //反转 { for(; begin < end; begin++ , end--) swap(a[begin] , a[end]); } void RotateRight(int *a , int begin , int end , int k) //循环右移 { int len = end - begin + 1; //数组的长度 k %= len; Reverse(a , begin , end - k); Reverse(a , end - k + 1 , end); Reverse(a , begin , end); } // 将有序数组a[begin...mid] 和 a[mid+1...end] 进行归并排序 void Merge(int *a , int begin , int end ) { int i , j , k; i = begin; j = 1 + ((begin + end)>>1); //位运算的优先级比较低,外面需要加一个括号,刚开始忘记添加括号,导致错了很多次 while(i <= end && j <= end && i<j) { while(i <= end && a[i] < a[j]) i++; k = j; //暂时保存指针j的位置 while(j <= end && a[j] < a[i]) j++; if(j > k) RotateRight(a , i , j-1 , j-k); //数组a[i...j-1]循环右移j-k次 i += (j-k+1); //第一个指针往后移动,因为循环右移后,数组a[i....i+j-k]是有序的 } }
11、找出重复的数字
假设你有一个用1001个整数组成的数组,这些整数是任意排列的,但是你知道所有的整数都在1到1000(包括1000)之间。此外,除一个数字出现两次外,其他所有数字只出现一次。假设你只能对这个数组做一次处理,用一种算法找出重复的那个数字。
可以使用异或的办法,即先用1001个数异或,然后更1到1000的这1000个数再异或,结果即重复的数字。
/* N为数组元素个数减一,即为数组中最大的数,即数的范围为1-N 数组长度为N+1 */ void xor_findDup(int * a,int N) { int i; int result=0; for(i=0;i<N+1;i++) { result ^= a[i]; } for (i=1;i<=N;i++) { result ^= i; } printf("%d\n",result); }
12、求子数组的最大和
输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。
#include <iostream.h> #define n 4 //多定义了一个变量 int maxsum(int a[n]) { int max=a[0]; //全负情况,返回最大数 int sum=0; for(int j=0;j<n;j++) { if(sum>=0) //如果加上某个元素,sum>=0的话,就加 sum+=a[j]; else sum=a[j]; //如果加上某个元素,sum<0了,就不加 if(sum>max) max=sum; } return max; }
13、和为n连续正数序列
输入一个正数n,输出所有和为n连续正数序列。例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-5、4-6和7-8。
我们可用两个数small和big分别表示序列的最小值和最大值。首先把small初始化为1,big初始化为2。如果从small到big的序列的和大于n的话,我们向右移动small,相当于从序列中去掉较小的数字。如果从small到big的序列的和小于n的话,我们向右移动big,相当于向序列中添加big的下一个数字。一直到small等于(1+n)/2,因为序列至少要有两个数字。
void PrintContinuousSequence(int small, int big); // Find continuous sequence, whose sum is n void FindContinuousSequence(int n) { if(n < 3) return; int small = 1; int big = 2; int middle = (1 + n) / 2; int sum = small + big; while(small < middle) { // we are lucky and find the sequence if(sum == n) PrintContinuousSequence(small, big); // if the current sum is greater than n, // move small forward while(sum > n) { sum -= small; small ++; // we are lucky and find the sequence if(sum == n) PrintContinuousSequence(small, big); } // move big forward big ++; sum += big; } } // Print continuous sequence between small and big void PrintContinuousSequence(int small, int big) { for(int i = small; i <= big; ++ i) printf("%d ", i); printf("/n"); }