关于数组的面试题总结(二)

来源:互联网 发布:炉石传说友谊赛淘宝 编辑:程序博客网 时间: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
原创粉丝点击