数组
1、写一个函数找出一个整数数组中第二大的数。
// 时间复杂度O(n) const int MINNUMBER = -32767; int find_sec_max(int data[], int count) { int maxnumber = data[0]; int sec_max = MINNUMBER; for(int i = 1; i < count; i++) { if(data[i] > maxnumber) { sec_max = maxnumber; maxnumber = data[i]; } else { if(data[i] > sec_max) sec_max = data[i]; } } return sec_max; }
2、寻找平衡点问题
平衡点:比如int numbers[]={1,3,5,7,8,25,4,20}; 25前面的总和为24,25后面的总和也是24,25这个点就是平衡点。假如一个数组中的元素,其前面的部分等于后面的部分,那么这个点的位序就是平衡点,要求返回任何一个平衡点。
int calcBalance(int arr[], int length) { int *left = new int[length]; //left[i]为从第0个到第i-1个的和 int *right = new int[length]; //right[i]为从第i+1个到第len-1个的和 int b = length-1; for(int i=0; i<length; i++) { if(i == 0) left[i] = 0; else left[i] = left[i-1]+arr[i-1]; } for(; b>=0; b--) { if(b == length-1) right[b] = 0; else right[b] = right[b+1]+arr[b+1]; if(left[b] == right[b]) { delete[] left; delete[] right; return b; } } delete[] left; delete[] right; return -1; }
3、输入两个整数序列。其中一个序列表示栈的push顺序,判断另一个序列有没有可能是对应的pop顺序。为了简单起见,我们假设push序列的任意两个整数都是不相等的。 比如输入的push序列是1、2、3、4、5,那么4、5、3、2、1就有可能是一个pop系列。
bool IsPossiblePopOrder(const int* pPush, const int* pPop, int nLen) { stack<int> stackData; int i = 0; int j = 0; while(i < nLen || j < nLen) { if(i < nLen) { stackData.push(pPush[i]); ++ i; } while(!stackData.empty() && stackData.top() == pPop[j]) { stackData.pop(); ++ j; } if(j == nLen) break; } return (stackData.empty() && i == nLen); }
4、一个int 数组,里面数据无任何限制,要求求出所有这样的数a[i],其左边的数都小于等于它,右边的数都大于等于它。
方法一:将数组a[]排序为数组b[],比较两个数组,如果a[i]==b[i],则a[i]即为一个这样的数。时间复杂度为O(NlogN)。
方法二:维护两个数组min[]和max[],max[i]为a[i]左边的数的最大值,min[i]为a[i]右边的最小值,扫描两遍数组就可得到,再比较,如果max[i]<=a[i]<=min[i],则a[i]是一个这样的数。
void FindElements(int *pArray, int len) { if(pArray == NULL || len <= 0 ) return ; int *pMin = new int[len]; int *pMax = new int[len]; int i; pMax[0] = pArray[0]; for(i = 1; i < len; i++) //计算自i往前最大值的辅助数组 pMax[i] = (pMax[i-1] >= pArray[i])? pMax[i-1]: pArray[i]; pMin[len-1] = pArray[len-1]; for(i = len - 2; i >= 0; i--) //计算自i开始最小值的辅助数组 pMin[i] = (pMin[i+1] <= pArray[i])? pMin[i+1]: pArray[i]; if(pArray[0] <= pMin[0]) //检查第1个元素是否满足条件 cout<<pArray[0]<<' '; for(i = 1; i < len - 1; i++) { if(pArray[i] >= pMax[i] && pArray[i] <= pMin[i]) //满足这个关系式的元素符合要求 cout<<pArray[i]<<' '; } if(pArray[len-1] >= pMax[len-1]) //检查第len个元素是否满足条件 cout<<pArray[i]; cout<<endl; delete [] pMin; delete [] pMax; pMin = pMax = NULL; }
5、N个元素的数组循环右移K位,要求时间复杂度为O(N)
//普通实现 void RightShift(int* arr, int N, int K) { K %= N; while(K--) { int t = arr[N-1]; for(int i = N-1; i > 0; i --) arr[i] = arr[i-1]; arr[0] = t; } } //递归实现 void MoveCirce(int *data, int n, int k) { int temp = data[n-1]; for(int i = n-1; i > 0; --i) data[i] = data[i-1]; data[0] = temp; -- k; if(k > 0) MoveCirce(data,n,k); }
假设原数组序列为abcd1234,要求变换成的数组序列为1234abcd,即循环右移了4位。比较之后,不难看出,其中有两段的顺序是不变的:1234和abcd,可把这两段看成两个整体。右移K位的过程就是把数组的两部分交换一下。变换的过程通过以下步骤完成:
1. 逆序排列abcd:abcd1234 → dcba1234; 2. 逆序排列1234:dcba1234 → dcba4321; 3. 全部逆序:dcba4321 → 1234abcd。
//翻转函数 void Reverse(int* arr, int b, int e) { for(; b < e; b++, e--) { int temp = arr[e]; arr[e] = arr[b]; arr[b] = temp; } } //循环右移 void RightShift(int* arr, int N, int K) { K %= N; Reverse(arr, 0, N-K-1); Reverse(arr, N-K, N-1); Reverse(arr, 0, N-1); } //循环左移 void LeftShift(int* arr, int N, int K) { K %= N; Reverse(arr, 0, K-1); Reverse(arr, K, N-1); Reverse(arr, 0, N-1); }
6、发帖水王:“水王”发帖数目超过了帖子总数的一半
如果一个ID出现的次数超过总数N的一半。那么,无论水王的ID是什么,这个有序的ID列表中的第N/2项(从0开始编号)一定会是这个ID(读者可以试着证明一下)。省去重新扫描一遍列表,可以节省一点算法耗费的时间。如果能够迅速定位到列表的某一项(比如使用数组来存储列表),除去排序的时间复杂度,后处理需要的时间为O(1)。
如果每次删除两个不同的ID(不管是否包含“水王”的ID),那么,在剩下的ID列表中,“水王”ID出现的次数仍然超过总数的一半。看到这一点之后,就可以通过不断重复这个过程,把ID列表中的ID总数降低(转化为更小的问题),从而得到问题的答案。新的思路,避免了排序这个耗时的步骤,总的时间复杂度只有O(N),且只需要常数的额外内存。伪代码如下:
int Find(int* ID, int N) { int candidate, nTimes, i; for(i = nTimes = 0; i < N; i++) { if(nTimes == 0) { candidate = ID[i], nTimes = 1; } else { if(candidate == ID[i]) nTimes++; else nTimes--; } } return candidate; }
扩展问题:统计结果表明,有3个发帖很多的ID,他们的发帖数目都超过了帖子总数目N的1/4。你能从发帖ID列表中快速找出他们的ID吗?
void Find(int* ID, int N, int & candidate1, int & candidate2, int & candidate3) { int nTimes1 = 0; int nTimes2 = 0; int nTimes3 = 0; for(int i = 0; i < N; i++) { if (nTimes1 == 0) { candidate1 = ID[i], nTimes1 = 1; } else { if (candidate1 == ID[i]) { nTimes1++; } else if (nTimes2 == 0) { candidate2 = ID[i], nTimes2 = 1; } else { if (candidate2 == ID[i]) { nTimes2++; } else { if (nTimes3 == 0) { candidate3 = ID[i], nTimes3 = 1; } else if (candidate3 == ID[i]) { nTimes3++; } else { nTimes1--; nTimes2--; nTimes3--; } } } } } }
7、寻找最大的K个数
寻找N个数中最大的K个数,本质上就是寻找最大的K个数中最小的那个,也就是第K大的数。
线性时间选择算法:RandomizedSelect()算法
//随机划分函数 int random_partition(int a[],int l,int r) { int i = l+rand()%(r-l+1);//生产随机数 int temp = a[i]; a[i] = a[l]; a[l] = temp; return partition(a,l,r);//调用普通划分函数 } //线性寻找第k大的数 int random_select(int a[],int l,int r,int k) { int i,j; if(l == r) //递归结束 { return a[l]; } i = random_partition(a,l,r);//划分 j = i-l+1; if(k == j) //递归结束,找到第K大的数 return a[i]; if(k < j) { return random_select(a,l,i-1,k);//递归调用,在前面部分查找第K大的数 } else return random_select(a,i+1,r,k-j);//递归调用,在后面部分查找第K大的数 }
具体实现:http://www.cnblogs.com/luxiaoxun/archive/2012/08/06/2624799.html
8、快速寻找和等于一个给定的数字的两个数
解法一:穷举法,从数组中取出任意两个数字,计算两者之和是否为给定的数字。时间复杂度为O(N^2)
解法二:假设和为Sum,对于数组中每个数字arr[i]都判断Sum-arr[i]是否在数组中,就变成一个查找问题。提高查找效率,先排序,再用二分查找法等方法进行查找,查找的时间复杂度从O(N)降到O(logN),总的时间复杂度为O(N*logN)。 更快的查找方法:hash表。给定的一个数字,根据hash映射查找另一个数字是否在数组中,只需O(1)的时间,这样总的时间复杂度降低到O(N),但这需要额外的O(N)的hash表存储空间。
解法三:先对数组排序sort(a,n),时间复杂度为O(N*logN),然后按下面的算法(O(N)的时间复杂度)查找,总的时间复杂度为O(N*logN)。用两个指针i和j指向第一和最后一个元素,如果a[j]+a[i]<sum时,++i,增大和值,否则--j,减小和值。
void FindTwoNumbers(int *a, int n, int sum) { sort(a,a+n); // use the STL sort algorithm int i = 0; int j = n-1; while(i < j) { if(a[i]+a[j] == sum) { cout<<a[i]<<" "<<a[j]<<endl; return; } else if(a[i]+a[j] < sum) ++ i; else -- j; } cout<<"No answer!"<<endl; }
类似的一道题目:求数组中差为给定数字的两个数,先对数组排序,然后用两个指针i和j指向第一和第二个元素,如果a[j]-a[i]<dif时,++j,增大差值,否则++i,减小差值。
void FindTwoNumbers(int *a, int n, int dif) { sort(a,a+n); // use the STL sort algorithm int i = 0; int j = 1; dif = abs(dif); while(i < j && j < n) { if(a[j]-a[i] == dif) { cout<<a[j]<<" "<<a[i]<<endl; return; } else if(a[j]-a[i] < dif) ++ j; else ++ i; } cout<<"No answer!"<<endl; }
9、子数组的最大乘积:给定一个长度为N的整数数组,只能用乘法,不能用除法,计算任意N-1个数组合中乘积最大的一组。
分析:如果可以用除法:那么用整个数组的乘积除以每个元素a[i],结果就是除了除数a[i]的剩下N-1个数的乘积。
方法一:不能用除法,数组为a[],s[i]表示数组前i个元素的乘积,s[0]=1(边界条件),s[i]=s[i-1]*a[i-1](1<=i<=N)。t[i]为数组后(N-i)个元素的乘积,t[N+1]=1(边界条件),t[i]=t[i+1]*a[i](1<=i<=N)。则除了第i个元素外,其他N-1个元素的乘积为:p[i]=s[i-1]*t[i+1]。 从头到尾扫描得到s[i],从尾到头扫描得到t[i],进而线性时间就可以得到p[i]。
方法二:利用N个数的正负分布情况。先扫描一遍,统计处数组中正数个数p,负数个数n,零的个数z,绝对值最小的正数a和负数 b。
如果 零的个数 z >= 2 结果为0
如果 零的个数 z = 1
如果负数个数n为奇数,结果为0
如果负数个数n为偶数,结果为除0外的乘积
如果 零的个数 z =0
如果负数个数n为奇数,结果为去掉绝对值最小负数后的乘积
如果负数个数n为偶数,结果为去掉绝对值最小正数后的乘积
10、题目:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。例如输入数组{32,321},则输出这两个能排成的最小数字32132。请给出解决问题的算法,并证明该算法。
根据题目的要求,两个数字m和n排成的数字mn和nm,如果mn<nm,那么我们应该输出mn,也就是m应该排在n的前面。但是m不一定就小于n,如{32, 321},321>32,但是32132<32321。 这道题其实是希望我们能找到一个排序规则,根据这个规则排出来的数组能排成一个最小的数字。要确定排序规则,就得比较两个数字,也就是给出两个数字m和n,我们需要确定一个规则来比较m和n组合的数哪个更小。我们知道m和n组合出来的数位数是一定的,如果直接通过组合出来的数的数值去比较,有点麻烦,需要考虑到的一个潜在问题是m和n都在int能表达的范围内,但把它们拼起来的数字mn和nm就不一定能用int表示了。所以一个直观的方法就是把数字转换成字符串,把组合出来的数转化字符串后,直接比较字符串的大小就可以,通过这个来确定m和n的组合顺序。
来自:http://zhedahht.blog.163.com/blog/static/25411174200952174133707/
#include<iostream> using namespace std; // 数字最大位数为10 const int g_MaxNumberLength = 10; // 比较 strNumber1 和 strNumber2 // if [strNumber1][strNumber2] > [strNumber2][strNumber1], return value > 0 // if [strNumber1][strNumber2] = [strNumber2][strNumber1], return value = 0 // if [strNumber1][strNumber2] < [strNumber2][strNumber1], return value < 0 int compare(const void* strNumber1, const void* strNumber2) { char g_StrCombine1[2*g_MaxNumberLength+1]; char g_StrCombine2[2*g_MaxNumberLength+1]; // [strNumber1][strNumber2] strcpy(g_StrCombine1, *(const char**)strNumber1); strcat(g_StrCombine1, *(const char**)strNumber2); // [strNumber2][strNumber1] strcpy(g_StrCombine2, *(const char**)strNumber2); strcat(g_StrCombine2, *(const char**)strNumber1); return strcmp(g_StrCombine1, g_StrCombine2); } // 输出正整数数组组合的最小数 void PrintMinNumber(int* numbers, int length) { if(numbers == NULL || length <= 0) return; // 将数字转换为字符串 char** strNumbers = new char*[length]; for(int i = 0; i < length; ++i) { strNumbers[i] = new char[g_MaxNumberLength + 1]; sprintf(strNumbers[i], "%d", numbers[i]); } // 以 compare() 函数为比较函数,对数字数组进行排序 qsort(strNumbers, length, sizeof(char*), compare); for(int i = 0; i < length; ++i) printf("%s", strNumbers[i]); printf("\n"); for(int i = 0; i < length; ++i) delete[] strNumbers[i]; delete[] strNumbers; } int main() { int a[] = {5,32,123}; PrintMinNumber(a,3); return 0; }
11、给定如下的n*n的数字矩阵,每行从左到右是严格递增, 每列的数据也是严格递增
1 3 7 15 16
2 5 8 18 19
4 6 9 22 23
10 13 17 24 28
20 21 25 26 33
现在要求设计一个算法, 给定一个数k 判断出k是否在这个矩阵中。 描述算法并且给出时间复杂度。
//从右上角开始(从左下角开始也是一样的),然后每步往左或往下走。时间复杂度O(N)。 bool stepWise(int mat[][N] , int key , int &row , int &col) { if(key < mat[0][0] || key > mat[N-1][N-1]) return false; row = 0; col = N-1; while(row < N && col >= 0) { if(mat[row][col] == key ) //查找成功 return true; else if(mat[row][col] < key ) ++row; else --col; } return false; }
12、给定一个整数数组a[],求最接近0的子数组和。
解法:构造从第一个元素开始的子数组和sum[i]=a[0]+..+a[i],那么sum[j]-sum[i]就是a[i+1]+...+a[j],即为一个子数组的和,原数组最接近0的子数组和就是sum[]中两数之差绝对值最小的值,两两比较,得到差值的最小值,就是最接近0的子数组和。
int compare(const void* key1, const void* key2) { return *(int*)(key1) - *(int*)(key2); } int SubArraySum(const int a[], int len) { int* pCumuSum = new int[len]; pCumuSum[0] = a[0]; for(int i = 1; i < len; ++i) { pCumuSum[i] = pCumuSum[i-1] + a[i]; } qsort(pCumuSum, len, sizeof(int), compare); int absDiff = abs(pCumuSum[0]); int tempAbsDiff; for(int i = 1; i < len; ++i) { tempAbsDiff = abs(pCumuSum[i] - pCumuSum[i-1]); if(tempAbsDiff < absDiff) { absDiff = tempAbsDiff; } } delete [] pCumuSum; return absDiff; }
13、小飞电梯调度问题:电梯从1层往上走,只允许电梯停在某一层,所有的乘客都从一楼上电梯,到达停下来的那层,所有的乘客从这里爬到自己的目的层。电梯停在哪一层,使得所有乘客爬的楼梯层数之和最少?
方法一:穷举所有的层,计算乘客爬的楼梯层数,得到最优的那一层。
方法二:假设停在第i层,所有爬的楼梯层数为Y。如果N1个乘客目的层在第i层楼以下,有N2个乘客在第i层楼,N3个乘客目的层在第i层楼以上。如果电梯改停在第i+1层,则爬层楼数为Y+(N1+N2-N3)。如果电梯改停在第i-1层,则爬层楼数为Y+(N2+N3-N1)。由此得出:当N1+N2<N3时,电梯改停在第i+1层好,当N2+N3<N1时,电梯改停在第i-1层好。
int getTargetFloor1(int N, int person[]) { int targetFloor = -1; int minFloor = 65535; int nFloor; for(int i = 1; i <= N; ++i) { nFloor = 0; for(int j = 1; j < i; ++j) nFloor += person[j]*(i-j); for(int j = i+1; j <= N; ++j) nFloor += person[j]*(j-i); if(targetFloor == -1 || nFloor < minFloor) { targetFloor = i; minFloor = nFloor; } } return targetFloor; } int getTargetFloor2(int N, int person[]) { int targetFloor = -1; int minFloor; int N1, N2, N3, i; targetFloor = 1; minFloor = 0; for(N1 = 0, N2 = person[1], N3 = 0, i = 2; i <= N; ++i) { N3 += person[i]; minFloor += person[i]*(i-1); } for(i = 2; i <= N; ++i) { if(N1 + N2 < N3) { targetFloor = i; minFloor += (N1+N2-N3); N1 += N2; N2 = person[i]; N3 -= person[i]; } else break; } return targetFloor; }