劍指offer :陣列類題目彙總
3.陣列中的重複數字:
在一個長度為n的數組裡的所有數字都在0到n-1的範圍內。 陣列中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出陣列中任意一個重複的數字。 例如,如果輸入長度為7的陣列{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。
解題思路:一維陣列在記憶體中佔據連續的空間,可以根據下標定位對應的元素。從頭掃到尾,只要當前元素值與下標不同,就做一次判斷,numbers[i]與numbers[numbers[i]],相等就認為找到了重複元素,返回true,否則就交換兩者,繼續迴圈,交換時切忌先將numbers[numbers[i]]值進行儲存(因為numbers[i值變會影響numbers[numbers[i]])。
public class Solution { // Parameters: // numbers: an array of integers // length: the length of array numbers // duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation; // Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++ // 這裡要特別注意~返回任意重複的一個,賦值duplication[0] // Return value: true if the input is valid, and there are some duplications in the array number // otherwise false public boolean duplicate(int numbers[],int length,int [] duplication) { int temp; if(length<=1) return false; for(int i=0 ;i<length;i++){ while(numbers[i]!=i){ if(numbers[i] == numbers[numbers[i]]){ duplication[0]=numbers[i]; return true; }else{ temp = numbers[numbers[i]] ; numbers[numbers[i]] =numbers[i] ; numbers[i] = temp; } } } return false; } }
39.陣列中出現次數超過一半的數字:
陣列中有一個數字出現的次數超過陣列長度的一半,請找出這個數字。例如輸入一個長度為9的陣列{1,2,3,2,2,2,5,4,2}。由於數字2在陣列中出現了5次,超過陣列長度的一半,因此輸出2。如果不存在則輸出0。
解題思路:陣列中出現次數超過陣列長度的一半,如果陣列進行了排序,那排序後位於陣列中間的數字即為所尋數字(也可能出現數組中沒有這樣的數字,注意考慮特殊情況。),陣列的排序可以利用java中的sort函式,注意匯入包。該方法改變了原有陣列。
import java.util.*; public class Solution { public int MoreThanHalfNum_Solution(int [] array) { if(array.length == 0) return 0; Arrays.sort(array); int result = array[array.length/2] ; int times = 0; for(int i=0 ; i< array.length ; i++){ if(result==array[i]) times++; } if(times>array.length/2) return result; else return 0; } }
如果不修改陣列,在遍歷的時候儲存兩個值,一個為陣列中的數字,另一個為次數,下一個數字如果和當前數字相同,則次數加一,否則減一,次數為0 ,儲存下一個值,並開始重新計數。最後一個把次數設為1的數字即為所找數。
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
if(array.length == 0)
return 0;
int result = array[0];
int time =1;
for(int j=0 ; j< array.length ; j++){
if(time==0){
result = array[j];
time=1;
}else if(result==array[j])
time++;
else
time--;
}
int times = 0;
for(int i=0 ; i< array.length ; i++){
if(result==array[i])
times++;
}
if(times>array.length/2)
return result;
else
return 0;
}
}
56.陣列中數字出現的次數:
1)陣列中只出現一次的兩個數字:
一個整型陣列中除兩個數字之外,其餘數字都出現了兩次,請寫程式找出這兩個只出現一次的數字。要求時間複雜度為n,空間複雜度為1.
解題思路:數字與自己的異或運算的結果為0,根據這一特性, 所有數字的異或結果為兩個只出現一次的數字的異或結果,尋找到結果中第一個為1的位數,本按照該位是否為1把陣列分成兩個陣列,每個陣列的異或結果即為兩個只出現一次的數字。
//num1,num2分別為長度為1的陣列。傳出引數
//將num1[0],num2[0]設定為返回結果
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int resultOR = 0;
//異或結果
for(int i=0;i<array.length;i++)
resultOR^=array[i];
//尋找第一個為1的位
int flag =1;
int index = 0;
while(true){
if((resultOR & flag)!=0){
index = flag;
break;
}
flag = flag<<1;
}
num1[0]=0;
num2[0]=0;
//按照第index位是否為1分為兩個陣列
for(int i=0;i<array.length;i++){
if((array[i] & index)!=0)
num1[0]^=array[i];
else
num2[0]^= array[i];
}
}
}
2)陣列中唯一隻出現一次的數字:
在一個數組中除一個數字只出現一次之外,其他數字都出現了三次,請找出那個只出現一次的數字。
解題思路:如果一個數字出現三次,則二進位制表示的每一位也出現3次,將所有出現三次的數字的二進位制表示的每一位都分別加起來,那麼每一位都可以被3整除。把陣列中所有數字的二進位制表示的每一位相加,如果某一位的和能被3整除,那麼只出現一次的數字的二進位制表示中對應的那一位為0,否則為1.
public class Solution {
public void findForm3(int [] a) {
int [] bits = new int[32];
//將陣列中的數的二進位制表示的每一位相加
for(int i =0;i<a.length;i++){
for(int j =0;j<32;j++){
bits[j] = bits[j] + ((a[i]>>>j) & 1);
}
}
//結果中某一位能被3整除,則出現一次的陣列該位為0,否則為1
int res =0;
for(int i =0;i<32;i++){
if(bits[i]%3!=0){
res = res | (1<<i);
}
}
return res;
}
}
53.在排序陣列中查詢數字:
1)數字在排序陣列中出現的次數:
統計一個數字在排序陣列中出現的次數。
解題思路:最直接的方法,從頭到尾掃描整個陣列,統計數字出現的次數,這樣的時間複雜度為O(n)。
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int times=0;
for(int i= 0;i<array.length;i++){
if(array[i]==k)
times++;
}
return times;
}
}
利用二分查詢法,找到數字第一次出現和最後一次出現的位置,這樣的時間複雜度為O(logn)。以尋找第一次出現位置為例,如果中間數字和k相同,則如果中間值為陣列的開始,或者中間數字的前面一個不是k,則中間值即為第一次出現k的位置;如果中間值比k大,則第一次出現位置在前半部分;如果中間值比k小,則第一次出現位置在後半段。
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array.length==0)
return 0;
int first = firstK(array ,0,array.length-1,k);
int last = lastK(array ,0,array.length-1,k);
if(first!=-1 && last!=-1)
return (last-first+1);
else
return 0;
}
//第一次出現的位置
public int firstK(int [] array , int start,int end, int k){
if(start>end)
return -1;
int mid = (start+end)/2;
if(array[mid]==k){
if(( mid==0 || array[mid-1]!=k && mid>0))
return mid;
else
end = mid-1;
}
else if(array[mid]>k)
end = mid-1;
else
start = mid+1;
return firstK(array ,start,end,k);
}
//最後一次出現的位置
public int lastK(int [] array , int start,int end, int k){
if(start>end)
return -1;
int mid = (start+end)/2;
if(array[mid]==k){
if(mid==array.length-1 || (array[mid+1]!=k && mid<array.length-1))
return mid;
else
start = mid+1;
}
else if(array[mid]>k)
end = mid-1;
else
start = mid+1;
return lastK(array ,start,end,k);
}
}
2)0-n-1中缺失的數字:
一個長度為n-1的遞增排序陣列中的所有數字都是唯一的,並且每個數字都在範圍0-n-1之內,在範圍0-n-1內的n個數字中有且只有一個數字不在該陣列中,請找出這個數字。
解題思路:最直觀的解決方案,利用公式n(n-1)/2,求出所有數字之和,然後求出陣列中所有數字的和,兩者之差即為不在陣列中的數字,這樣的時間複雜度為O(n)。細究題目可以發現,該問題可以轉化為在排序陣列中找出第一個值和下標不相同的元素,對於排序陣列,可以使用二分查詢的方式。如果中間元素的值和下標不相同,並且它前面一個元素的值和下標相同,則該元素即為缺失數字,如果前一個元素的值和下標也不同,則下一次考察陣列的左半邊;如果中間元素的值和下標元素的值相同,則考察陣列的右半邊,結束條件為left<=right。
import java.util.*;
public class Solution {
public int GetMissingNumber(int[] array){
if (array == null || array.length == 0) {
return -1;
}
int start = 0;
int end = array.length - 1;
while (start <= end) {
int mid = (start + end) / 2;
if (mid >= 0 && array[mid] != mid) {
if (mid == 0 || array[mid - 1] == mid - 1) {
return mid;
} else {
end = mid - 1;
}
} else if (array[mid] == mid) {
start = mid + 1;
}
}
if (start == array.length)
return array.length;
return -1;
}
}
3)陣列中數值和下標相等的元素:
假設一個單調遞增的數組裡的每個元素都是整數並且是唯一的,找出陣列中任意一個數值等於其下標的元素。
解題思路:最直觀的解決方案,從頭到尾掃描一遍陣列,逐一檢查數值與下標是否相等,這樣時間複雜度為O(n)。對於排序陣列,可以使用二分查詢的方式。如果第i個數字的值等於i,輸出即可;當第i個數字的值大於i,那麼它右邊的數字都大於對應的下標,考慮左邊陣列即可;當第i個數字的值小於i,那麼它左邊的數字都小於對應的下標,考慮右邊陣列即可,結束條件為left<=right。
import java.util.*;
public class Solution {
public int GetNumberSameAsIndex(int[] array){
if (array == null || array.length == 0) {
return -1;
}
int start = 0;
int end = array.length-1;
while (start <= end) {
int mid = (start + end) / 2;
if (array[mid] == mid) {
return mid;
}
if (array[mid] > mid) {
end = mid - 1;
}
if (array[mid] < mid) {
start = mid + 1;
}
}
return -1;
}
}
4.二維陣列中的查詢:
在一個二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。
解題思路:由於題目中的矩陣是有序的,選取陣列中右上角的數字開始進行判斷,逐步縮小查詢範圍。對於二維陣列,object [][] array ;array.length 就是行數;array [0].length 就是列數。
public class Solution {
public boolean Find(int target, int [][] array) {
Boolean flag = false;
int rows = array.length;
int colums = array[0].length;
if( rows>0 && colums>0){
int row =0;
int colum = colums-1;
while(row<rows && colum>=0){
if(target==array[row][colum]){
flag = true;
break;
}else if(target>array[row][colum]){
row++;
}else{
colum--;
}
}
}
return flag;
}
}
21.調整陣列順序使奇數位於偶數前面
輸入一個整數陣列,實現一個函式來調整該陣列中數字的順序,使得所有的奇數位於陣列的前半部分,所有的偶數位於位於陣列的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。
解題思路:要想保證交換順序後奇數與偶數的相對位置不變,只能在相鄰的位置進行交換,從陣列的第一個元素開始,每一次將最後一個位置的元素固定,類似於排序演算法。或者可以新建兩個list集合,分別將奇數和偶數放到集合中,最後在進行合併。
public class Solution {
public void reOrderArray(int [] array) {
for(int i = 0; i<array.length-1;i++){
for(int j = 0; j<array.length-i-1;j++){
if(array[j]%2==0 && array[j+1]%2!=0){
int temp = array[j];
array[j] =array[j+1];
array[j+1] =temp;
}
}
}
}
}
29.順時針列印矩陣
輸入一個矩陣,按照從外向裡以順時針的順序依次打印出每一個數字,例如,如果輸入如下矩陣: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 則依次打印出數字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
解題思路:矩陣順時針列印,需要考慮終止條件以及最後一圈的情況,可能只有一行,或只有一列或者只有一個數字,在列印時需考慮上下左右的情況,設定列印條件(尤其是最後的向上列印)
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> result = new ArrayList<Integer>();
int rows = matrix.length;
int colums = matrix[0].length;
if(rows==0 && colums==0)
return null;
int left = 0, right = colums-1,up = 0,down = rows-1;
while(left<=right && up<=down){
for(int i=left;i<=right;i++)
result.add(matrix[up][i]);
if(down>up){//第二步
for(int j=up+1;j<=down;j++)
result.add(matrix[j][right]);
if(right>left){//第三步
for(int k=right-1;k>=left;k--)
result.add(matrix[down][k]);
if(down-1>up){//第四步
for(int t=down-1;t>up;t--)
result.add(matrix[t][left]);
}}}
left++; right--; up++; down--;
}
return result;
}
}
11.旋轉陣列的最小數字:
把一個數組最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。 輸入一個非遞減排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。 例如陣列{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該陣列的最小值為1。 NOTE:給出的所有元素都大於0,若陣列大小為0,請返回0。
解題思路:利用二分查詢的思想,當兩個輔助變數相遇時,結束迴圈,第二個變數所指即為最小的元素,注意考慮特殊情況,(1)當旋轉陣列和本身陣列相同時,第一個數字即為最小的元素,因此mid初始化設定為0;(2)如果陣列中有相同的數字,start、end、mid下標所指的值恰巧相等,此時需要順序查詢的方法來尋找最小的值。
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array.length==0)
return 0;
int start = 0;
int end = array.length-1;
int mid = start;
while(array[start]>=array[end]){
if(end-start ==1)
return array[end];
mid = (end+start)/2;
//如果陣列中有相同的數字,例如{1,1,1,0,1}
if(array[mid]==array[end] && array[mid]==array[start]){
int result = array[0];
for(int i=0;i<array.length;i++){
if(result>array[i])
result = array[i];
}
return result;
}
if(array[mid]>=array[end])
start = mid;
else if(array[mid]<=array[start])
end = mid;
}
return array[mid];
}
}
42.連續子陣列的最大和:
輸入一個整型陣列,數組裡有整數也有負數,陣列中的一個或者連續多個整陣列成一個子陣列,求所有子陣列的和的最大值,要求時間複雜度為n。
解題思路:設定結果為陣列的第一個值,依次掃描陣列進行累加,當陣列中的元素值大於累加結果,重新進行累加;當累計結果比要輸出的結果大時,更新輸出結果。
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
if(array.length==0)
return 0;
else{
int total=array[0],maxSum=array[0];
for(int i=1;i<array.length;i++){
if(total>=0)
total+=array[i];
else
total=array[i];
if(total>maxSum)
maxSum=total;
}
return maxSum;
}
}
}
51.陣列中的逆序對:
在陣列中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個陣列中的逆序對的總數P。並將P對1000000007取模的結果輸出。 即輸出P%1000000007
解題思路:採用歸併排序的方式,時間複雜度為N(nlogn),先把陣列分隔成子陣列,統計出子陣列內部的逆序對數目,然後再統計出兩個相鄰子陣列之間的逆序對數目。用兩個輔助變數指向陣列的末尾,每次比較其數字大小,如果左陣列數字大於右陣列數字,逆序對的數目=右陣列中剩餘數字的個數。
public class Solution {
public int InversePairsCore(int [] array,int [] copy,int start,int end){
if(start==end)
return 0;
int mid = (start+end)/2;
int left = InversePairsCore(array,copy,start,mid)%1000000007;
int right = InversePairsCore(array,copy,mid+1,end)%1000000007;
int i = mid,j = end;//前半段末尾,後半段末尾
int index = end;//copy陣列末尾
int count=0;
while(i>=start && j>=mid+1){
if(array[i]>array[j]){
copy[index--]=array[i--];
count+=j-mid;
if(count>=1000000007)//數值過大求餘
count%=1000000007;
}else
copy[index--]=array[j--];
}
for(;j>=mid+1;j--)
copy[index--]=array[j];
for(;i>=start;i--)
copy[index--]=array[i];
for(int s=start;s<=end;s++)
array[s] = copy[s];
return (count+left+right)%1000000007;
}
public int InversePairs(int [] array) {
if(array ==null || array.length ==0)
return 0;
int [] copy = new int[array.length];
for(int i = 0; i<array.length;i++)
copy[i]=array[i];
int count = InversePairsCore(array,copy,0,array.length-1);
return count;
}
}
66.構建乘積陣列:
給定一個數組A[0,1,...,n-1],請構建一個數組B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。
解題思路:如果可以用除法,B的每一位即為所有數的乘積除以A中所對應的那一位的值;不可以用除法,最直觀的解法是用連乘n-1個數來得到每一位的結果,這樣的時間複雜度為O(n^2),更簡單的方法對於求第i位的結果,可以分為兩部分,先求出前i-1位的結果,然後再乘以第i位後的乘積。後面的乘積可以構建輔助變數,指向陣列的最後一位,依次向前乘。
import java.util.ArrayList;
public class Solution {
public int[] multiply(int[] A) {
int [] B = new int[A.length];
if(A.length ==0 || A ==null)
return B;
if(A.length ==1)
return A;
B[0]=1;
for(int i=1;i<A.length;i++)
B[i] = B[i-1]*A[i-1];
int temp =1;
for(int i = A.length-2 ;i>=0;i--){
temp *= A[i+1];
B[i] = B[i]*temp;
}
return B;
}
}
61.撲克牌順子:
從撲克牌中隨機抽取五張牌,判斷是不是一個順子,即這五張牌是不是連續的,2-10為數字本身,A為1,J為11,Q為12,K為13,而大小王可以看成任意數字。
解題思路:把撲克牌抽象成陣列,首先將陣列排序,其次統計陣列中0的個數,最後統計排序之後的陣列中相鄰數字之前的空缺總數,如果空缺總數小於或者等於0的個數,那麼,這個陣列就是連續的,否則不連續。注意,如果5張牌中存在相等的兩個數,則不可能成為順子。
import java.util.*;
public class Solution {
public boolean isContinuous(int [] numbers) {
boolean result = false;
if(numbers==null || numbers.length<=0)
return result;
Arrays.sort(numbers);
int numOfZero = 0;
for(int i =0;i<numbers.length;i++){
if(numbers[i]==0)
numOfZero++;
}
int min = numOfZero;
int max = min+1;
int dif =0;
while(max<numbers.length){
if(numbers[min]==numbers[max])
return result;//存在對子
dif += numbers[max]-numbers[min]-1;//差距
min = max;
max++;
}
if(dif<=numOfZero)
result = true;
return result;
}
}