关于数组的面试题总结(二)
来源:互联网 发布:炉石传说友谊赛淘宝 编辑:程序博客网 时间:2024/06/07 02:21
1. 给定一个无序的整型数组,求出最小的k个数
两种思路:
(1)如果所有的数组可以全部装入内存的话,采用快速排序的思想进行划分,不断向第k个小的数靠近,当得到第k小的数(key)时就相当于得到了最小的k个数,因为划分的时候保证了比key小的数全部放在了左边。平均时间复杂度为O(N)。
(2)当数组太大无法一次性装入内存时,上面的方法失效,可以维持一个大小为k的大顶堆,取前k个数建堆后依次读取之后的n-k个数,当数字比堆顶的元素小的话就更新堆,否则直接丢弃,扫描完数组后堆中即保留了前k小的数字,时间复杂度为O(Nlogk)。
下面的代码时划分的思路:
void partition(vector<int> &num, int left, int right, int k){ if(left >= right) return; int pivot = num[right]; int l = left - 1, r; for(r = left; r < right; ++r) { if(num[r] <= pivot) { ++l; swap(num[l], num[r]); } } swap(num[l+1], num[right]); if(k == l + 1) return; else if(k < l+1) { partition(num, left, l, k); } else { partition(num, l+2, right, k); }}
2. 把数组排成以字典序来看的最小数字
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
主要思路:本题主要是考虑如何定义规则(数字间的比较规则),使得排列得到的数组为字典序最小。
bool myCmp(int a, int b){ char buffer[32]; sprintf(buffer, "%d", a); string sa = buffer; sprintf(buffer, "%d", b); string sb = buffer; string sab = sa + sb; string sba = sb + sa; return (sab.compare(sba) < 0);} sort(num.begin(), num.end(), myCmp);
3. 求数组中的逆序对的数目(扩展到基因上O(N)的时间)
主要思路:通过归并排序计算逆序数,当两个有序数组进行合并时,很容易得到对于后面的有序数组的每个数,前面有多少个数字比它大(逆序对)
void MergeSorted(vector<int> &num, int left, int right, long long &cnt){ int mid = left + ((right - left) >> 1); vector<int> tmp(right - left + 1, 0); int idx = 0, i = left, j = mid + 1; while(i <= mid && j <= right) { if(num[i] <= num[j]) { tmp[idx++] = num[i++]; } else { cnt += (mid - i + 1); // counting tmp[idx++] = num[j++]; } } while(i <= mid) { tmp[idx++] = num[i++]; } while(j <= right) { tmp[idx++] = num[j++]; } for(i = left; i <= right; ++i) { num[i] = tmp[i - left]; }} void MergeSortReversCnt(vector<int> &num, int left, int right, long long &cnt){ if(left < right) { int mid = left + ((right - left) >> 1); MergeSortReversCnt(num, left, mid, cnt); MergeSortReversCnt(num, mid + 1, right, cnt); MergeSorted(num, left, right, cnt); }}
变型(腾讯2015年校招笔试):
今年腾讯校园招聘的笔试一道填空题对本题进行了一点扩展,就可以完全换一种思路。
同样是求逆序对的数目,如果我们需要处理的数组是基因中的碱基ACGT,可以用O(N)的时间解决该问题。
解决方法是用c[4] = {0, 0, 0, 0}记录四个碱基的数目,当访问到第i个元素时,分四种情况考虑:
case 'A': cnt += (c[1] + c[2] + c[3] ); ++c[0]; break;case 'C': cnt += ( c[2] + c[3] ); ++c[1]; break;case 'G': cnt += c[3] ; ++c[2]; break;case 'T': ++c[3]; break;
4. 数字在排序数组中出现的次数
主要思路:两次二分搜索,分别查找上边界和下边界
int BinSearchLower(vector<int> &num, int target){ int left = 0, right = (int) num.size() - 1; while(left <= right) { int mid = left + ((right - left) >> 1); if(num[mid] < target) left = mid + 1; else right = mid - 1; } if(left < num.size() && num[left] == target) return left; else return -1;} int BinSearchHigher(vector<int> &num, int target){ int left = 0, right = (int) num.size() - 1; while(left <= right) { int mid = left + ((right - left) >> 1); if(num[mid] <= target) left = mid + 1; else right = mid - 1; } return right;} int countInSortedArr(vector<int> &num, int target){ if(0 == num.size()) return 0; int l = BinSearchLower(num, target); if(-1 == l) return 0; int r = BinSearchHigher(num, target); return (r - l + 1);}
5. 数组中只出现一次的数字(几种变型)
(1)最简单的情况,数组共2k+1个元素,只有一个数字只出现了一次,求出这个数字。(直接用异或运算)
int singleNumber(int A[], int n) { int ans = 0; for (int i = 0; i < n; i++) ans ^= A[i]; return ans; }
其实还有一种方法,就是利用快速排序的划分,每次丢弃一半的数字,写起来复杂很多,但平均时间复杂度也是O(N)。
int partition(int A[], int left, int right){ int i = left + 1; int j = right - 1; int temp, pos = -1; while(i <= j) { while(i <= j && A[i] <= A[left]) { if(A[i] == A[left]) pos = i; ++i; } while(i <= j && A[j] > A[left]) --j; if(i > j) break; swap(A[i], A[j]); } swap(A[i-1], A[left]); if(pos != -1 && i > left + 1) { swap(A[pos], A[i-2]); } return i; } int QuickPick(int A[], int left, int right){ if(left+1 == right) return A[left]; if(left + 3 == right) { if(A[left] != A[left+1] && A[left] != A[left+2]) return A[left]; else if(A[left] != A[left+1]) return A[left+1]; else return A[left+2]; } int pivot = partition(A, left, right); if((pivot-left) % 2 == 1) { return QuickPick(A, left, pivot); } else { return QuickPick(A, pivot, right); } } int singleNumber(int A[], int n) { return QuickPick(A, 0, n); }
(2)数组中有3k+1个数字,只有1个数字出现一次,其他的都出现3次。
对于32位的整数,可以数每个位置上为1的数字的个数。
int singleNumber(int A[], int n){ int count = 0; int result = 0; for (int i = 0; i < 32; i++) { count = 0; for (int j = 0; j < n; j++) { count += ((A[j] >> i) & 1); } result |= ((count % 3) << i); } return result;}
其实(1)中的划分方法依旧适用,只是每次丢弃3x个数字,leetcode上这题采用划分的方法也可以AC,跟上面一样看起来很复杂!
(3)(雅虎北研2015年校招笔试)数组共4k+2个元素,只有一个数字出现了2次,其他的都出现了4次,求出现2次的这个数字。
处理方法与(2)类似,依次数每位上n个数字中出现的1的数目,如果是4k个,目标值该位上就是0,如果是4k+个,那目标值该位上为1, T(n) = 32n。但是快排的划分思想也可以很好的运用。
每次选择一个主元pivot进行划分后,让其左边都是比pivot小的,右边都是不小于pivot的,确定pivot位置后。就需要看划分后的左边部分和右边部分(包括pivot)元素的数目,其中肯定有一部分是4x个元素,另外一部分是4y+2个元素,下次划分我们只需要在4y+2那部分去找了。但是有一个需要注意的问题是左边的4x可能是0,这样划分下去就死循环了,因此我们选主元的时候可以随机选,或者取left, right, mid三个元素选择居中的。
(4)数组共2k+2个元素,有两个数字都是只出现了一个,其他都是两次。求这两个数字。
主要思路:先求出所有数的异或结果, 找出最后一个1的位置, 根据该位是否为1把num数组分成两个部分,这两部分分别异或就得到要求的两个数字。
void FindNumsAppearOnce(vector<int> &num, int &num1, int &num2){ if(0 == num.size()) return; // 先求出两个目标数的异或结果 int retOR = 0; for(size_t i = 0; i < num.size(); ++i) { retOR ^= num[i]; } // 找出最后一个1的位置,最低位记为第0位 unsigned int last1Idx = 0; while(0 == (retOR & 1) && last1Idx < 8 *sizeof(int)) { retOR >>= 1; ++last1Idx; } // 根据last1Idx位是否为1把num数组分成两个部分,这两部分分别异或就得到要求的两个数字 for(size_t i = 0; i < num.size(); ++i) { if(((num[i] >> last1Idx) & 1)) { num1 ^= num[i]; } else num2 ^= num[i]; }}
6. 顺时针打印矩阵
主要思路:直接打印,考虑清楚边界
void printMatrix(vector<vector<int> > &matrix, int m, int n){ int r = n - 1; int d = m - 1; int len = 1 + (m < n ? m : n); len /= 2; for(int s = 0; s < len; ++s) { for(int j = s; j <= r; ++j) cout << matrix[s][j] << " "; for(int i = s + 1; i <= d; ++i) cout << matrix[i][r] << " "; if(d > s) { for(int j = r - 1; j >= s; --j) cout << matrix[d][j] << " "; } if(r > s) { for(int i = d - 1; i > s; --i) cout << matrix[i][s] << " "; } --r; --d; } cout << endl;}
7. 删除有序数组中的重复数字
见leetcode中的Remove Duplicates from Sorted Array(有序数组,重复的元素只保留一个)
int removeDuplicates(int A[], int n) { int cnt = 0; if(n == 0) return cnt; int elem = A[0]; ++cnt; for(int i = 1; i < n; ++i) { if(A[i] != elem) { if(cnt != i) A[cnt] = A[i]; elem = A[i]; ++cnt; } } return cnt;}
扩展版本,Remove Duplicates from Sorted Array II(有序数组,重复的元素最多保留2个)
int removeDuplicates(int A[], int n) { int cnt = 0; if(0 == n) return cnt; A[cnt++] = A[0]; int appeared = 1; for(int i = 1; i < n; ++i) { if(A[i] == A[cnt-1] && appeared == 2) { continue; } else if(A[i] == A[cnt-1] && appeared < 2) { A[cnt++] = A[i]; ++appeared; } else { A[cnt++] = A[i]; appeared = 1; } } return cnt; }
8. 有序数组中和为S的两个数字(Two Sum)
输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S。
vector<int> twoSum(vector<int> &numbers, int target) { vector<int> ret; if(0 == numbers.size()) return ret; map<int, int> minus2id; for(size_t i = 0; i < numbers.size(); ++i) { if(minus2id.find(numbers[i]) != minus2id.end()) { ret.push_back(1 + minus2id[numbers[i]]); ret.push_back(1 + i); } else { minus2id[target - numbers[i]] = i; } } return ret; }
关于该题的扩展还有很多,3sum和4sum都是leetcode上的题目。
而需要求出任意个数字之和为S的情况,可以参见我的另一篇博文[算法]子数组之和问题
9.加油站问题(leetcode中Gas Station)
There are N gas stations along a circular route, where the amount of gas at station i is gas[i].
You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station (i+1). You begin the journey with an empty tank at one of the gas stations.
Return the starting gas station's index if you can travel around the circuit once, otherwise return -1.
Note:
The solution is guaranteed to be unique.
方法一:初始值,start指向0,end指向n-1,如果油够用的话start依次往后扫描,否则end往前移动补充start位置不足的油量,直至前面的油够用。
int gasStation(vector<int> &gas, vector<int> &cost){ if(0 == gas.size()) return -1; if(1 == gas.size()) { if(gas[0] >= cost[0]) return 0; else return -1; } int start = 0, end = gas.size() - 1; int tank = 0; while(start <= end) { tank += gas[start] - cost[start]; if(tank < 0) { while(start < end && tank < 0) { tank += gas[end] - cost[end]; --end; } if(tank < 0) return -1; } ++start; } return start;}
方法二:从0至n-1扫描gas和cost数组,total统计每个位置的差值之和,用于判定是否有解。sum用来统计局部几个位置的差值之和,与上面思路不同的是,我们通过那里缺油来找终点。
int canCompleteCircuit(vector<int> &gas, vector<int> &cost){ int i = 0, end = -1; int sum = 0, total = 0; for(i = 0; i < gas.size(); ++i) { sum += (gas[i] - cost[i]); total += (gas[i] - cost[i]); if(sum < 0) { end = i; sum = 0; } } return total >= 0 ? end+1 : -1;}
10.求数组的“距离”,在给定数组a中找出这样的数对:i<j && a[i] < a[j],求最大的(j-i)
主要思路:构建min和max数组,min[i]表示数组a[0..i]的最小值,max[i]表示数组a[i..n-1]的最大值,这两个数组肯定都是非递增的,然后问题转变成从这两个有序数组中分别取一个数字x和y,使得x<y且两者的下标最小,可以用合并两个有序数组的思路。
int maxArrayDist(vector<int> &arr){ if(1 >= arr.size()) return -1; int n = arr.size(); vector<int> minIdx, maxIdx; minIdx.assign(n, 0); maxIdx.assign(n, 0); int tmp = arr[0]; for(int i = 0; i < n; ++i) { if(arr[i] < tmp) tmp = arr[i]; minIdx[i] = tmp; } tmp = arr[n-1]; for(int i = n-1; i >= 0; --i) { if(arr[i] > tmp) tmp = arr[i]; maxIdx[i] = tmp; } int ret = 0; int i = 0, j = 0; while(i < n && j < n) { if(maxIdx[j] > minIdx[i]) { ret = max(ret, j - i); ++j; } else ++i; } if(0 == ret) return -1; else return ret;}
0 0
- 关于数组的面试题总结(二)
- 关于数组的面试题总结(一)
- 关于数组的面试题总结(三)
- 关于数组的面试题
- 关于数组的面试题
- 关于数组的面试题
- 关于数组指针的一道面试题
- 关于数组的面试笔试题
- 关于字符串的面试题总结
- 关于线程的面试题总结
- 关于阶乘的面试题总结
- 面试题集合--关于数组
- JAVA面试题总结(二)
- Android 面试题总结(二)
- 面试题总结(二)
- Java面试题总结(二)
- Java面试题总结二
- 面试题总结(二)
- java面向对象
- Unofficial AirPlay Protocol Specification
- 进程优先级优化(避免休眠的时候进程被KILL,主要面向service)(笔记)
- phpcms v9 推送【删除】和【附加字段】
- 经典左右布局demo
- 关于数组的面试题总结(二)
- cocos2dx鼠标拖动与释放
- 【嵌入式】stm32架构
- Thrift源码分析(五)-- FrameBuffer类分析
- 基于Office Visio 2010 图表绘制
- (一)Python入门:环境变量和开发环境的配置
- 汇编语言学习心得
- Oracle RAC 10.2.0.5 Install For redhat 5.8(use raw device)
- ajax同步加载