數組的玩法挺多的
數組是最基本的數據結構,關於數組的面試題也屢見不鮮,本文羅列了一些常見的面試題,僅供參考。目前有以下 18 道題目。
-
數組求和
-
求數組的最大值和最小值
-
求數組的最大值和次大值
-
求數組中出現次數超過一半的元素
-
求數組中元素的最短距離
-
求兩個有序數組的共同元素
-
求三個數組的共同元素
-
找出數組中唯一的重複元素
-
找出出現奇數次的元素
-
求數組中滿足給定和的數對
-
最大子段和
-
最大子段積
-
數組循環移位
-
字符串逆序
-
組合問題
-
合併兩個數組
-
重排問題
-
找出絕對值最小的元素
數組求和
給定一個含有 n 個元素的整型數組 a,求 a 中所有元素的和。可能您會覺得很簡單,是的,的確簡單,但是爲什麼還要說呢,原因有二,第一,這道題要求用遞歸法,只用一行代碼。第二,這是我人生中第一次面試時候遇到的題,意義特殊。
分析
簡單說一下,兩種情況
-
如果數組元素個數爲 0,那麼和爲 0。
-
如果數組元素個數爲 n,那麼先求出前 n - 1 個元素之和,再加上 a[n - 1] 即可
代碼
// 數組求和
int sum(int*a, int n)
{
return n == 0 ? 0 : sum(a, n -1) + a[n -1];
}
求數組的最大值和最小值
給定一個含有 n 個元素的整型數組 a,找出其中的最大值和最小值
分析
常規的做法是遍歷一次,分別求出最大值和最小值,但我這裏要說的是分治法 (Divide and couquer),將數組分成左右兩部分,先求出左半部份的最大值和最小值,再求出右半部份的最大值和最小值,然後綜合起來求總體的最大值及最小值。
這是個遞歸過程,對於劃分後的左右兩部分,同樣重複這個過程,直到劃分區間內只剩一個元素或者兩個元素。
代碼
// 求數組的最大值和最小值,返回值在maxValue和minValue
void MaxandMin(int *a, int l, int r, int& maxValue, int& minValue)
{
if(l == r) // l與r之間只有一個元素
{
maxValue = a[l] ;
minValue = a[l] ;
return ;
}
if(l + 1 == r) // l與r之間只有兩個元素
{
if(a[l] >= a[r])
{
maxValue = a[l] ;
minValue = a[r] ;
}
else
{
maxValue = a[r] ;
minValue = a[l] ;
}
return ;
}
int m = (l + r) / 2 ; // 求中點
int lmax ; // 左半部份最大值
int lmin ; // 左半部份最小值
MaxandMin(a, l, m, lmax, lmin) ; // 遞歸計算左半部份
int rmax ; // 右半部份最大值
int rmin ; // 右半部份最小值
MaxandMin(a, m + 1, r, rmax, rmin) ; // 遞歸計算右半部份
maxValue = max(lmax, rmax) ; // 總的最大值
minValue = min(lmin, rmin) ; // 總的最小值
}
求數組的最大值和次大值
給定一個含有 n 個元素的整型數組,求其最大值和次大值
分析
思想和上一題類似,同樣是用分治法,先求出左邊的最大值 leftmax 和次大值 leftsecond,再求出右邊的最大值 rightmax 和次大值 rightsecond,然後合併,如何合併呢?分情況考慮
1 如果 leftmax > rightmax,那麼可以肯定 leftmax 是最大值,但次大值不一定是 rightmax,但肯定不是 rightsecond,只需將 leftsecond 與 rightmax 做一次比較即可。
2 如果 rightmax > leftmax,那麼可以肯定 rightmax 是最大值,但次大值不一定是 leftmax,但肯定不是 leftsecond,所以只需將 leftmax 與 rightsecond 做一次比較即可。
注意
這種方法無法處理最大元素有多個的情況,比如 3,5,7,7 將返回 7,7 而不是 7,5。感謝網友 從無到有靠誰人 指出。
代碼
// 找出數組的最大值和次大值,a是待查找的數組,left和right是查找區間,max和second存放結果
void MaxandMin(int a[], int left, int right, int&max, int&second)
{
if(left == right)
{
max = a[left] ;
second = INT_MIN;
}
elseif(left +1== right)
{
max = a[left] > a[right] ? a[left] : a[right] ;
second = a[left] < a[right] ? a[left] : a[right] ;
}
else
{
int mid = left + (right - left) /2 ;
int leftmax ;
int leftsecond ;
MaxandMin(a, left, mid, leftmax, leftsecond) ;
int rightmax ;
int rightsecond ;
MaxandMin(a, mid +1, right, rightmax, rightsecond) ;
if (leftmax > rightmax)
{
max = leftmax ;
second = leftsecond > rightmax ? leftsecond : rightmax ;
}
else
{
max = rightmax ;
second = leftmax < rightsecond ? rightsecond : leftmax ;
}
}
}
求數組中出現次數超過一半的元素
給定一個 n 個整型元素的數組 a,其中有一個元素出現次數超過 n / 2,求這個元素。據說是百度的一道題
分析
設置一個當前值和當前值的計數器,初始化當前值爲數組首元素,計數器值爲 1,然後從第二個元素開始遍歷整個數組,對於每個被遍歷到的值 a[i]
1 如果 a[i]==currentValue,則計數器值加 1
2 如果 a[i] != currentValue, 則計數器值減 1,如果計數器值小於 0,則更新當前值爲 a[i],並將計數器值重置爲 1
代碼
// 找出數組中出現次數超過一半的元素
int Find(int* a, int n)
{
int curValue = a[0] ;
int count = 1 ;
for (int i = 1; i < n; ++i)
{
if (a[i] == curValue)
count++ ;
else
{
count-- ;
if (count < 0)
{
curValue = a[i] ;
count = 1 ;
}
}
}
return curValue ;
}
另一個方法是先對數組排序,然後取中間元素即可,因爲如果某個元素的個數超過一半,那麼數組排序後該元素必定佔據數組的中間位置。
求數組中元素的最短距離
給定一個含有 n 個元素的整型數組,找出數組中的兩個元素 x 和 y 使得 abs(x - y) 值最小
分析
先對數組排序,然後遍歷一次即可
代碼
int compare(const void* a, const void* b)
{
return *(int*)a - *(int*)b ;
}
// 求數組中元素的最短距離
void MinimumDistance(int* a, int n)
{
// Sort
qsort(a, n, sizeof(int), compare) ;
int i ; // Index of number 1
int j ; // Index of number 2
int minDistance = numeric_limits<int>::max() ;
for (int k = 0; k < n - 1; ++k)
{
if (a[k + 1] - a[k] < minDistance)
{
minDistance = a[k + 1] - a[k] ;
i = a[k] ;
j = a[k + 1] ;
}
}
cout << "Minimum distance is: " << minDistance << endl ;
cout << "i = " << i << " j = " << j << endl ;
}
求兩個有序數組的共同元素
給定兩個含有 n 個元素的有序(非降序)整型數組 a 和 b,求出其共同元素,比如
a = 0, 1, 2, 3, 4
b = 1, 3, 5, 7, 9
輸出 1, 3
分析
充分利用數組有序的性質,用兩個指針 i 和 j 分別指向 a 和 b,比較 a[i] 和 b[j],根據比較結果移動指針,則有如下三種情況
-
a[i] < b[j],則 i 增加 1,繼續比較
-
a[i] == b[j],則 i 和 j 皆加 1,繼續比較
-
a[i] < b[j],則 j 加 1,繼續比較
重複以上過程直到 i 或 j 到達數組末尾。
代碼
// 找出兩個數組的共同元素
void FindCommon(int* a, int* b, int n)
{
int i = 0;
int j = 0 ;
while (i < n && j < n)
{
if (a[i] < b[j])
++i ;
else if(a[i] == b[j])
{
cout << a[i] << endl ;
++i ;
++j ;
}
else// a[i] > b[j]
++j ;
}
}
這到題還有其他的解法,比如對於 a 中任意一個元素,在 b 中對其進行 Binary Search,因爲 a 中有 n 個元素,而在 b 中進行 Binary Search 需要 logn。所以找出全部相同元素的時間複雜度是 O(nlogn)。
另外,上面的方法,只要 b 有序即可,a 是否有序無所謂,因爲我們只是在 b 中做 Binary Search。
如果 a 也有序的話,那麼再用上面的方法就有點慢了,因爲如果 a 中某個元素在 b 中的位置是 k 的話,那麼 a 中下一個元素在 b 中的位置一定位於 k 的右側,所以本次的搜索空間可以根據上次的搜索結果縮小,而不是仍然在整個 b 中搜索。也即如果 a 和 b 都有序的話,代碼可以做如下修改,記錄上次搜索時 b 中元素的位置,作爲下一次搜索的起始點。
求三個數組的共同元素
給定三個含有 n 個元素的整型數組 a,b 和 c,求他們最小的共同元素。
分析
如果三個數組都有序,那麼可以設置三個指針指向三個數組的頭部,然後根據這三個指針所指的值進行比較來移動指針,直道找到共同元素。
代碼
// 三個數組的共同元素-只找最小的
void FindCommonElements(int a[], int b[], int c[], int x, int y, int z)
{
for(int i = 0, j = 0, k = 0; i < x && j < y && k < z;)
{
if(a[i] < b[j])
{
i++ ;
}
else // a[i] >= b[j]
{
if(b[j] < c[k])
{
j++ ;
}
else // b[j] >= c[k]
{
if(c[k] < a[i])
{
k++ ;
}
else // c[k] >= a[i]
{
cout << c[k] << endl ;
return ;
}
}
}
}
cout << "Not found!" << endl ;
}
如果三個數組都無序,可以先對 a, b 進行排序,然後對 c 中任意一個元素都在 b 和 c 中做二分搜索。
代碼
// 找出三個數組的共同元素
// O(NlogN)
int UniqueCommonItem(int *a, int *b, int *c, int n)
{
// sort array a
qsort(a, n, sizeof(int), compare) ; // NlogN
// sort array b
qsort(b, n, sizeof(int), compare) ; // NlogN
// for each element in array c, do a binary search in a and b
// This is up to a complexity of N*2*logN
for (int i = 0; i < n; i++)
{
if(BinarySearch(a, n, c[i]) && BinarySearch(b, n, c[i]))
return c[i] ;
}
return - 1 ; // not found
}
也可以對 a 進行排序,然後對於 b 和 c 中任意一個元素都在 a 中進行二分搜索,但是這樣做是有問題的,你看出來了麼?感謝網友 yy_5533 指正。
代碼
// 找出三個數組唯一的共同元素
// O(NlogN)
int UniqueCommonItem1(int *a, int *b, int *c, int n)
{
// sort array a
qsort(a, n, sizeof(int), compare) ; // NlogN
// Space for time
bool *bb = new bool[n] ;
memset(bb, 0, n) ;
bool *bc = new bool[n] ;
memset(bb, 0, n) ;
// for each element in b, do a BS in a and mark all the common element
for (int i = 0; i < n; i++) // NlogN
{
if(BinarySearch(a, n, b[i]))
bb[i] = true ;
}
// for each element in c, do a BS only if b[i] is true
for (int i = 0; i < n; i++) // NlogN
{
if(b[i] && BinarySearch(a, n, c[i]))
return c[i] ;
}
return - 1 ; // not found
}
排序和二分搜索代碼如下
// Determine whether a contains value k
bool BinarySearch(int *a, int n, int k)
{
int left = 0 ;
int right = n - 1 ;
while (left <= right)
{
int mid = (left + right) ;
if(a[mid] < k)
left = mid + 1 ;
if(a[mid] == k)
return true ;
else
right = mid - 1 ;
}
return false ;
}
// Compare function for qsort
int compare(const void* a, const void* b)
{
return *(int*)a - *(int*)b ;
}
小小總結一下,對於在數組中進行查找的問題,可以分如下兩種情況處理
-
如果給定的數組有序,那麼首先應該想到 Binary Search,所需 O(logn)
-
如果給定的數組無序,那麼首先應該想到對數組進行排序,很多排序算法都能在 O(nlogn) 時間內對數組進行排序,然後再使用二分搜索,總的時間複雜度仍是 O(nlogn)。
如果能做到以上兩點,大多數關於數組的查找問題,都能迎刃而解。
找出數組中唯一的重複元素
給定含有 1001 個元素的數組,其中存放了 1-1000 之內的整數,只有一個整數是重複的,請找出這個數
分析
求出整個數組的和,再減去 1-1000 的和
代碼
略
找出出現奇數次的元素
給定一個含有 n 個元素的整型數組 a,其中只有一個元素出現奇數次,找出這個元素。這道題實際上是一個變種,原題是找出數組中唯一一個出現一次的元素,下面的方法可以同時解決這兩道提。所以題目就用這個廣義的吧。
分析
因爲對於任意一個數 k,有 k ^ k = 0,k ^ 0 = k,所以將 a 中所有元素進行異或,那麼個數爲偶數的元素異或後都變成了 0,只留下了個數爲奇數的那個元素。
代碼
int FindElementWithOddCount(int*a, int n)
{
int r = a[0] ;
for (int i =1; i < n; ++i)
{
r ^= a[i] ;
}
return r ;
}
求數組中滿足給定和的數對
給定兩個有序整型數組 a 和 b,各有 n 個元素,求兩個數組中滿足給定和的數對,即對 a 中元素 i 和 b 中元素 j,滿足 i + j = d(d 已知)
分析
兩個指針 i 和 j 分別指向數組的首尾,然後從兩端同時向中間遍歷。
代碼
// 找出滿足給定和的數對
void FixedSum(int* a, int* b, int n, int d)
{
for (int i = 0, j = n - 1; i < n && j >= 0)
{
if (a[i] + b[j] < d)
++i ;
else if (a[i] + b[j] == d)
{
cout << a[i] << ", " << b[j] << endl ;
++i ;
--j ;
}
else // a[i] + b[j] > d
--j ;
}
}
最大子段和
給定一個整型數組 a,求出最大連續子段之和,如果和爲負數,則按 0 計算,比如 1, 2, -5, 6, 8 則輸出 6 + 8 = 14
分析
編程珠璣上的經典題目,不多說了。
代碼
// 子數組的最大和
int Sum(int* a, int n)
{
int curSum = 0;
int maxSum = 0;
for (int i = 0; i < n; i++)
{
if (curSum + a[i] < 0)
curSum = 0;
else
{
curSum += a[i] ;
maxSum = max(maxSum, curSum);
}
}
return maxSum;
}
最大子段積
給定一個整型數組 a,求出最大連續子段的乘積,比如 1, 2, -8, 12, 7 則輸出 12 * 7 = 84
分析
與最大子段和類似,注意處理負數的情況
代碼
// 子數組的最大乘積
int MaxProduct(int *a, int n)
{
int maxProduct = 1; // max positive product at current position
int minProduct = 1; // min negative product at current position
int r = 1; // result, max multiplication totally
for (int i = 0; i < n; i++)
{
if (a[i] > 0)
{
maxProduct *= a[i];
minProduct = min(minProduct * a[i], 1);
}
else if (a[i] == 0)
{
maxProduct = 1;
minProduct = 1;
}
else // a[i] < 0
{
int temp = maxProduct;
maxProduct = max(minProduct * a[i], 1);
minProduct = temp * a[i];
}
r = max(r, maxProduct);
}
return r;
}
數組循環移位
將一個含有 n 個元素的數組向右循環移動 k 位,要求時間複雜度是 O(n),且只能使用兩個額外的變量,這是在微軟的編程之美上看到的一道題
分析
比如數組 1 2 3 4 循環右移 1 位 將變成 4 1 2 3, 觀察可知 1 2 3 的順序在移位前後沒有改變,只是和 4 的位置交換了一下,所以等同於 1 2 3 4 先劃分爲兩部分
1 2 3 | 4,然後將 1 2 3 逆序,再將 4 逆序 得到 3 2 1 4,最後整體逆序 得到 4 1 2 3
代碼
// 將buffer中start和end之間的元素逆序
void Reverse( int buffer[], int start, int end )
{
while ( start < end )
{
int temp = buffer[ start ] ;
buffer[ start++ ] = buffer[ end ] ;
buffer[ end-- ] = temp ;
}
}
// 將含有n個元素的數組buffer右移k位
void Shift( int buffer[], int n, int k )
{
k %= n ;
Reverse( buffer, 0, n - k - 1) ;
Reverse( buffer, n - k, n - 1 ) ;
Reverse( buffer, 0, n - 1 ) ;
}
稍微擴展一下, 如果允許分配額外的數組, 那麼定義一個新的數組, 然後將移位後的元素直接存入即可, 也可以使用隊列, 將移動後得元素出對, 再插入隊尾即可.
字符串逆序
給定一個含有 n 個元素的字符數組 a,將其原地逆序。
分析
可能您覺得這不是關於數組的,而是關於字符串的。是的。但是別忘了題目要求的是原地逆序,也就是不允許額外分配空間,那麼參數肯定是字符數組形式,因爲字符串是不能被修改的(這裏只 C/C++ 中的字符串常量)。
所以,和數組有關了吧,只不過不是整型數組,而是字符數組。用兩個指針分別指向字符數組的首位,交換其對應的字符,然後兩個指針分別向數組中央移動,直到交叉。
代碼
// 字符串逆序
void Reverse(char*a, int n)
{
int left =0;
int right = n -1;
while (left < right)
{
char temp = a[left] ;
a[left++] = a[right] ;
a[right--] = temp ;
}
}
組合問題
給定一個含有 n 個元素的整型數組 a,從中任取 m 個元素,求所有組合。比如下面的例子
a = 1, 2, 3, 4, 5
m = 3
輸出
1 2 3, 1 2 4, 1 2 5, 1 3 4, 1 3 5, 1 4 5
2 3 4, 2 3 5, 2 4 5
3 4 5
分析
典型的排列組合問題,首選回溯法,爲了簡化問題,我們將 a 中 n 個元素值分別設置爲 1-n
代碼
// n選m的所有組合
int buffer[100] ;
void PrintArray(int *a, int n)
{
for (int i = 0; i < n; ++i)
cout << a[i] << "";
cout << endl ;
}
bool IsValid(int lastIndex, int value)
{
for (int i = 0; i < lastIndex; i++)
{
if (buffer[i] >= value)
return false;
}
return true;
}
void Select(int t, int n, int m)
{
if (t == m)
PrintArray(buffer, m);
else
{
for (int i = 1; i <= n; i++)
{
buffer[t] = i;
if (IsValid(t, i))
Select(t + 1, n, m);
}
}
}
合併兩個數組
給定含有 n 個元素的兩個有序(非降序)整型數組 a 和 b。合併兩個數組中的元素到整型數組 c,要求去除重複元素並保持 c 有序(非降序)。例子如下
a = 1, 2, 4, 8
b = 1, 3, 5, 8
c = 1, 2, 3, 4, 5, 8
分析
利用合併排序的思想,兩個指針 i,j 和 k 分別指向數組 a 和 b,然後比較兩個指針對應元素的大小,有以下三種情況
-
a[i] < b[j],則 c[k] = a[i]。
-
a[i] == b[j],則 c[k] 等於 a[i] 或 b[j] 皆可。
-
a[i] > b[j],則 c[k] = b[j]。
重複以上過程,直到 i 或者 j 到達數組末尾,然後將剩下的元素直接 copy 到數組 c 中即可
代碼
// 合併兩個有序數組
void Merge(int *a, int *b, int *c, int n)
{
int i = 0 ;
int j = 0 ;
int k = 0 ;
while (i < n && j < n)
{
if (a[i] < b[j])// 如果a的元素小,則插入a中元素到c
{
c[k++] = a[i] ;
++i ;
}
else if (a[i] == b[j])// 如果a和b元素相等,則插入二者皆可,這裏插入a
{
c[k++] = a[i] ;
++i ;
++j ;
}
else // a[i] > b[j] // 如果b中元素小,則插入b中元素到c
{
c[k++] = b[j] ;
++j ;
}
}
if (i == n) // 若a遍歷完畢,處理b中剩下的元素
{
for (int m = j; m < n; ++m)
c[k++] = b[m] ;
}
else//j == n, 若b遍歷完畢,處理a中剩下的元素
{
for (int m = i; m < n; ++m)
c[k++] = a[m] ;
}
}
重排問題
給定含有 n 個元素的整型數組 a,其中包括 0 元素和非 0 元素,對數組進行排序,要求:
-
排序後所有 0 元素在前,所有非零元素在後,且非零元素排序前後相對位置不變
-
不能使用額外存儲空間
例子如下
輸入 0, 3, 0, 2, 1, 0, 0
輸出 0, 0, 0, 0, 3, 2, 1
分析
此排序非傳統意義上的排序,因爲它要求排序前後非 0 元素的相對位置不變,或許叫做整理會更恰當一些。我們可以從後向前遍歷整個數組,遇到某個位置 i 上的元素是非 0 元素時,如果 a[k] 爲 0,則將 a[i] 賦值給 a[k],a[k] 賦值爲 0。實際上 i 是非 0 元素的下標,而 k 是 0 元素的下標
代碼
void Arrange(int* a, int n)
{
int k = n -1 ;
for (int i = n -1; i >=0; --i)
{
if (a[i] !=0)
{
if (a[k] ==0)
{
a[k] = a[i] ;
a[i] =0 ;
}
--k ;
}
}
}
找出絕對值最小的元素
給定一個有序整數序列(非遞減序),可能包含負數,找出其中絕對值最小的元素,比如給定序列 -5, -3, -1, 2, 8 則返回 1。
分析
由於給定序列是有序的,而這又是搜索問題,所以首先想到二分搜索法,只不過這個二分法比普通的二分法稍微麻煩點,可以分爲下面幾種情況
-
如果給定的序列中所有的數都是正數,那麼數組的第一個元素即是結果。
-
如果給定的序列中所有的數都是負數,那麼數組的最後一個元素即是結果。
-
如果給定的序列中既有正數又有負數,那麼絕對值得最小值一定出現在正數和負數的連接處。
爲什麼?
因爲對於負數序列來說,右側的數字比左側的數字絕對值小,如上面的 - 5, -3, -1, 而對於整整數來說,左邊的數字絕對值小,比如上面的 2, 8,將這個思想用於二分搜索,可先判斷中間元素和兩側元素的符號,然後根據符號決定搜索區間,逐步縮小搜索區間,直到只剩下兩個元素。
代碼
單獨設置一個函數用來判斷兩個整數的符號是否相同。
bool SameSign(int a, int b)
{
if (a * b > 0)
return true;
else
return false;
}
主函數代碼。
// 找出一個非遞減序整數序列中絕對值最小的數
int MinimumAbsoluteValue(int* a, int n)
{
// Only one number in array
if (n ==1)
{
return a[0] ;
}
// All numbers in array have the same sign
if (SameSign(a[0], a[n -1]))
{
return a[0] >=0? a[0] : a[n -1] ;
}
// Binary search
int l =0 ;
int r = n -1 ;
while(l < r)
{
if (l +1== r)
{
return abs(a[l]) < abs(a[r]) ? a[l] : a[r] ;
}
int m = (l + r) /2 ;
if (SameSign(a[m], a[r]))
{
r = m -1;
continue;
}
if (SameSign(a[l], a[m]))
{
l = m +1 ;
continue;
}
}
}
這段代碼是有問題的,感謝網友 lingyunfish 的指正,你看出來了麼?修改後的代碼如下:
// 找出一個非遞減序整數序列中絕對值最小的數
int MinimumAbsoluteValue(int* a, int n)
{
// Only one number in array
if (n ==1)
{
return a[0] ;
}
// All numbers in array have the same sign
if (SameSign(a[0], a[n -1]))
{
return a[0] >=0? a[0] : a[n -1] ;
}
// Binary search
int l =0 ;
int r = n -1 ;
while(l < r)
{
if (l + 1 == r)
{
return abs(a[l]) < abs(a[r]) ? a[l] : a[r] ;
}
int m = (l + r) /2 ;
if (SameSign(a[m], a[r]))
{
r = m;
continue;
}
else
{
l = m ;
continue;
}
}
}
****原文來源於: 作者: ****翰墨小生
https://www.cnblogs.com/graphics/archive/2010/08/24/1761620.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/pzjZfpgzzacinPVHq-DB2g