关于数组的面试笔试题

来源:互联网 发布:vb应用程序 编辑:程序博客网 时间:2024/05/22 01:01

1.求数组中最长递增子序列LIS的长度

如1,-1,2,-3,4,-5,6,7得到1,2,4,6长度为4

动态规划:LIS[i+1] = max{1, LIS[k] + 1},array[i + 1] > array[k], for any k <= i

public static int LIS(int[] a){int[] lis = new int[a.length];//lis[i]表示前i元素的最长递增子序列的长度int maxLength = 1;for(int i = 0; i < a.length; i++){lis[i] = 1;for(int j = 0; j < i; j ++){if (a[i] > a[j] && lis[j] + 1 > lis[i]){lis[i] = lis[j] + 1;}}}for (int i : lis){if (i > maxLength){maxLength = i;}}return maxLength;}
时间复杂度O(N2),空间复杂度O(N)


2.数组循环移位:将一个含有N个元素的数组循环右移K位,时间复杂度O(N),只允许使用两个附加变量

tips:

1)每个元素右移N位后都会回到自己的位置上

2)abcd1234->1234abcd 

逆序abcd:abcd1234->dcba1234

逆序1234:dcba1234->dcba4321

全部逆序dcba4321->1234abcd

public void rightShift(int[] a, int k){int n = a.length;k %= n;reverse(a, 0, k - 1);reverse(a, k, n - 1);reverse(a, 0, n - 1);}public void reverse(int[] a, int left, int right){int temp = 0;while (left <= right){temp = a[left];a[left++] = a[right];a[right--] = temp;}}

3.求数组的连续子数组之和的最大值

动态规划:

数组的第一个元素A[0]以及最大的一段数组(A[i],...,A[j])的之间的关系:

1)当0 = i = j , A[0]本身构成和最大的一段;

2)当0 = i < j , 和最大的一段以A[0]开始;

3)当0 < i , 元素A[0]跟和最大的一段没有关系

A[0],...A[n - 1] 中问题的解All[0] = max{A[0], A[0] + Start[1], All[1]}

public int maxSum(int[] a){int nStart = a[a.length - 1];int nAll = nStart;for(int i = a.length - 2; i >=0; i--){nStart = Math.max(a[i], nStart + a[i]);nAll = Math.max(nStart, nAll);}return nAll;}

时间复杂度O(N),空间复杂度O(1)


4.找出数组中两个数之和等于一个给定值,时间复杂度O(n)

1)对数组进行排序

2)令i = 1, j = n - 1, 看a[i] + a[j] 是否等于sum,若等于,则结束;若小于sum,则i = i + 1; 若大于sum,则j = j - 1

伪代码:

searchNumbers(int[] a, int sum){quickSort();for(int i = 0, j = a.length - 1; i < j;){if (a[i] + a[j] == sum){return (i,j);}else if (a[i] + a[j] < sum) {i ++;}else {j --;}}return (-1, -1);}

延伸题:输入一个正数s,打印出所有和为s的连续整数序列(至少有2个数),例如输入15,1+2+3+4+5 = 4+5+6 = 7 + 8

分析:使用small和big初始化1和2,如果从small到big的序列和小于s,增大big,让这个序列包含更多的数字;如果和大于s,则去掉small的值,即增大small,一直到small到(1+s)/2为止。

public void findContinuousSequeue(int sum){if (sum < 3){return;}int small = 1, big = 2;int middle = (1 + sum) / 2;int curSum = small + big;//在前一个序列的和的基础上求操作之后的序列的和while (small < middle){if (curSum == sum){printContinuesSequeue(small, big);}while(curSum > sum && small < middle)//直到cursum小于等于sum,big再增加{curSum -= small;small++;if (curSum== sum){printContinuesSequeue(small, big);}}big++;curSum += big;}}


5.寻找数组中的最大值和最小值

method1:利用max和min存储当前最大值和最小值,奇数位和偶数位相比完再分别和max和min比,只用遍历一次

method2:分治算法:只需分别求出前后N/2个数的Min和Max,然后取较小的Min和较大的Max

伪代码:

(max, min) SearchMaxAndMin(int[] a, int left, int right){if (left - right <= 1){if (a[left] < a[right]){return (a[right], a[left]);}else return (a[left], a[right]);}(maxLeft, minLeft) = SearchMaxAndMin(a, left, (left + right) / 2);(maxRight, minRight) = SearchMaxAndMin(a, (left + right) / 2 + 1, right);if (maxLeft > maxRight){maxV = maxLeft;}else {maxV = maxRight;}if (minLeft < minRight){minV = minLeft;}else {minV = minRight;}return (maxV, maxRight);}
复杂度1.5N


6.求数组中出现次数超过一半的数,时间复杂度O(N),空间复杂度O(1)

使用一个计数器,如果下一个数和本数相同则counter++,若不相同则counter--;如果counter等于0,则number设置为该下标对应的数,因为其出现次数大于一半,所有counter肯定>0

public int find(int[] a){int number = -1, count = 0;for(int i = 0; i < a.length; i++){if (count == 0){number = a[i];count = 1;}else {if (number == a[i]){count++;}elsecount--;}}return number;}


7.子数组的最大乘积:计算任意N-1个数的组合中乘积最大的一组

解法一:

空间换时间

S[i]表示数组前i个元素的乘积,S[i] = a[0]*a[1]*...*a[i-1];s[0] = 1;

t[i]表示数组后N-i个元素的乘积,t[i] = a[i]*...*a[n-1];t[n+1] = 1;

P[i]表示数组除i个元素外,其他N-1个元素的乘积

P[i] = S[i] * t[i + 1]

public long[] multiply(long[] arrayA){if (arrayA == null || arrayA.length == 0){return null;}int length = arrayA.length;long[] arrayB = new long[length];for(int i = 0; i < length; i++)arrayB[i] = 1;for(int i = 1; i < length; i++)arrayB[i] = arrayB[i - 1] * arrayA[i - 1];//slong temp = 1;for(int i = length - 2; i > length; i--){temp *= arrayA[i + 1];//tarrayB[i] *= temp;//p}return arrayB;}


时间复杂度O(N)

解法二:

假设N个整数的乘积为P,N-1个整数的乘积为Q,对P的正负性进行分析

1)P为0

数组中至少有一个0:

若Q为0,数组中至少有两个0,那么N-1个数的乘积只能为0,返回0;

若Q为正,返回Q,因为以0替换此时其中任何一个数,所得Q’必然为0;

若Q为负,返回0,因为以0替换此时其中任何一个数,所得Q’必然为0;

2)P为负数

把绝对值最小的负数去掉;

3)P为正数

如果数组中存在正数,应该去掉最小的正整数,否则去掉绝对值最大的负整数;

tips:求n个数的乘积会有溢出风险->求出数组中正数、负数、0的个数


8.数组分割:将元素个数为2n的正整数数组分割为元素个数为n的两个数组,并使两个子数组的和最接近

动态规划:

解法一:把任务分成2N步,第k步定义为前k个元素中任意i个元素的和,所有可能的取值之集合为Sk,0<i<=n,



定义Heap[i]表示存储从a中取i个数所能产生的和之集合的堆

伪代码:

for(int k = 1; k <= 2 * n; k++){int i_max = min(k -1 , n - 1);for(int i = i_max; i >= 0; i--){for each v in heap[i]insert(v + a[k], heap[i + 1]);}}
时间复杂度O(2^N)


解法二:

给定Sk的可能值v和a[k],查找v-a[k]是否在Sk-1中

isOk[i][v]表示是否可以找到i个数,使得它们之和等于v

伪代码:

for(int k = 1; k <= 2 * n; k ++){for(int i = min(k,n); i >= 1; i--){for(int v = 1; v < sum / 2; v++){if (v > a[k] && isOk[i - 1][v - a[k]]){isOk[i][v] = true;}}}}
时间复杂度O(N2*sum)

9.给出平面上N个点的坐标,找出距离最近的两个点

分治算法:根据水平方向的坐标x=M把平面上的N个点分成Left和Right两部分,求出MinDist(Left)和MinDist(Right),

 再求出M-dist < x<M-dist,dist = min(MinDist(Left),MinDist(Right))之间的最小点对;然后和dist比较

tips:如果一个点对的距离小于dist,则它们一定在dist*(2dist)的区域内,一个dist*(2dist)的区域内最多有8个点,对于任意一个带状区域内的顶点,只要考察它与按Y坐标排序且紧接着的7个店之间的距离就可以了。

f(N) = 2*f(N/2) + O(N),(N>2)

时间复杂度:O(NlogN)


10.在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下的顺序排序。请完成这样一个函数,public boolean(int[][] a, number)

解法:首先选取数组中右上角(或左下角)的数字,如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在列;如果该数字小于要查找的数字,剔除这个数字所在的行。

public static boolean find(int[][] a, int number){boolean found = false;if (a != null && a.length > 0){int row = 0;int column = a[row].length - 1;while (row < a.length && column > 0){column = Math.min(a[row].length - 1, column);//不规则数组if (a[row][column] == number){found = true;break;}else if (a[row][column] > number){column--;}elserow++;}}return found;}

11.递增旋转数组的最小数字,旋转:将一个数组最开始的若干个元素搬到数组的末尾。如{1,2,3,4,5},{3,4,5,1,2}

二分查找:二个排过序的子数组

public int MinMumberInRotateArray(int[] a){if (a == null){try{throw new Exception("Invalid parameters");}catch (Exception e){e.printStackTrace();}}int i = 0, j = a.length - 1;int mid = i;while (a[i] > a[j]){if (j - i == 1){mid = j;break;}mid = (i + j) / 2;//如果下标为i,mid,j指向的三个数字相等,只能顺序查找if (a[i] == a[j] && a[j] == a[mid]){int result = a[i];for (int k = i + 1; k <= j; k++){if (result < a[k]){result = a[i];}}return result;}if (a[i] <= a[mid]){i = mid;}else if (a[mid] <= a[j]){j = mid;}}return a[mid];}

12.输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,偶数位于数组的后半部分;

分析:维护两个指针,第一个指针指向数组的第一个元素,指向的元素是奇数时移动;第二个指针指向数组的第二个元素,指向的元素是偶数时移动;两个指针都停止时且i<j时交换元素

public void Reorder(int[] a){int left = 0, right = a.length - 1;for(;;){while((a[left] & 0x1) != 0){left++;}while((a[right] & 0x1) == 0){right--;}if(left < right){int tmp = a[left];a[left] = a[right];a[right] = tmp;}elsebreak;}}
扩展性:按正负数排序,被3整除排序

C++中可以利用函数指针来操作,

java利用函数对象来操作,即策略模式;或者java8的λ表达式

interface Comparator{boolean isSatisfied(int n);}
public void Reorder(int[] a, Comparator comparator){int left = 0, right = a.length - 1;for(;;){while(!comparator.isSatisfied(a[left])){left++;}while(comparator.isSatisfied(a[right])){right--;}if(left < right){int tmp = a[left];a[left] = a[right];a[right] = tmp;}elsebreak;}}
Test test = new Test();int[] a = {1, 2, 3, 4, 5};test.Reorder(a, new Comparator(){@Overridepublic boolean isSatisfied(int n){return (n & 0x1) == 0;}});

13.顺时针打印矩阵:从外向里以顺时针的顺序依次打印出每一个数字

如输入

1  2  3  4

5 6  7  8

9 10 1112

13 14 15 16    

输出:1,2,3,4,8,12,16,15,14,13,9,5,5,7,11,15,14,10

分析:把矩阵看成若干个顺时针的圈组成

打印一圈分成四步:从左到右,从上到下,从右到左,从下往上;第一步必定会执行

public void printMatrixClockwisely(int[][] a){int rows = a.length, columns = a[0].length;//规则矩形int start = 0;while(columns > start * 2 && rows > start * 2){printMatrixInCircle(a, columns, rows, start);start++;}}public void printMatrixInCircle(int[][] a, int columns, int rows, int start){int endCol = columns - 1 - start;int endRow = rows - 1 - start;//从左到右打印一行for(int i = start; i <= endCol; i++){System.out.print(a[start][i]);}//从上到下打印一列if (start < endRow){for(int i = start + 1; i <= endRow; i++){System.out.print(a[i][endCol]);}}//从右到左打印一行if (start < endRow && start < endCol){for(int i = endCol - 1; i >= start; i--){System.out.print(a[endRow][i]);}}//从下到上打印一列if (start < endRow && start < endCol){for(int i = endRow - 1; i >= start + 1; i--){System.out.print(a[i][start]);}}}

14.最小的k个数,输入n个整数,找出其中最小的k个数,例如输入4,5,1,6,2,7,3,8,则最小的4个数为1,2,3,4

分析:

解法一:可以使用分割的算法,复杂度O(N),但是需要修改输入数据,不适合处理海量数据;

解法二:使用容器存储k个数字,使用最大堆(优先队列)或者红黑树;

如果容器中已有的数字小于k个,则直接把这次读入的整数放入容器;

如果容器中已有k个数字,此时我们找出k个数中的最大值,然后拿这次待插入的整数和最大值比较。

TreeSet不能处理重复元素,所有选用PriorityQueue

Comparator<Integer> comparator = Collections.reverseOrder();//降序PriorityQueue<Integer> queue = new PriorityQueue<Integer>(4, comparator);

public void  getLeastNumbers(int[] a, PriorityQueue<Integer> queue, int k){if (k < 1 || a.length < k){return ;}for (int i = 0; i < a.length; i++){if (queue.size() < k){queue.offer(a[i]);//入队,自动装箱}else{if (a[i] < queue.peek())//最大的元素{queue.poll();//出队queue.offer(a[i]);}}}}

15.把数组排成最小的数:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接处的所有数字中的最小的一个。如{3,32,321},输出321323

分析:即定义一个比较规则,数字m和n,如果mn<nm,则m排在n前,另外防止数溢出,使用字符串存储数字

public void printMinNumber(int[] a){int length = a.length;//若a中元素有负数,不打印for (int i : a){if (i <= 0){return;}}//初始化字符串数组String[] strArr = new String[length];for(int i = 0; i < length; i++){strArr[i] = new String(String.valueOf(a[i]));}Arrays.sort(strArr, new Comparator<String>()//策略模式,对数组进行排序{public int compare(String s1, String s2){return (s1 + s2).compareTo(s2 + s1);//升序}});//打印for(int i = 0; i < length; i++){System.out.print(strArr[i]);}}

16.求数组中的逆序对:在数组中两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对,输入一个数组,求出这个数组中的逆序对的总数。如{7,5,6,4},存在5个逆序对,分别是(7,6),(7,5),(7,4),(6,4),(5,4)

解析:先把数组分成两部分,分别统计出子数组内部的逆序对的数目,再统计出两个相邻子数组之间的逆序对数组。在统计逆序对的过程中,还需要对数组进行排序。其实就是由后向前的归并排序。
5 7 |  4  6     5  7 | 4  6    5  7 | 4  6

  i       j     i         j    i      j

public int inversePairs(int[] a){int[] tmpArray = new int[a.length];for (int i = 0; i < a.length; i++){tmpArray[i] = a[i];}return inversePairs(a, tmpArray, 0, a.length - 1);}private int inversePairs(int[] a, int[] tmpArray, int left, int right){if (left == right) // base case{tmpArray[left] = tmpArray[right];return 0;}int center = (left + right) / 2;int number = right - left + 1;int leftCount = inversePairs(a, tmpArray, left, center);int rightCount = inversePairs(a, tmpArray, center + 1, right);int leftPos = center;// 前半段最后一个数字的下标int rightPos = right;// 后半段最后一个数字的下标int count = 0, tmpPos = right;while (leftPos >= left && rightPos >= center + 1){if (a[leftPos] > a[rightPos]){tmpArray[tmpPos--] = a[leftPos--];count += rightPos - center;}else{tmpArray[tmpPos--] = a[rightPos--];}}while (leftPos >= left)tmpArray[tmpPos--] = a[leftPos--];while (rightPos >= center + 1)tmpArray[tmpPos--] = a[rightPos--];//copy tmpArray backfor(int i = 0; i < number; i++, right--)a[right] = tmpArray[right];return leftCount + rightCount + count;}

17.求一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5},则3出现的次数为4

分析:本题是二分查找的增强版,即找出k第一次出现和最后一次出现的位置。

求k第一次出现的位置:根据二分查找的思想,如果中间的数字比k大,k出现在数组的前半段;如果中间的数字比k小,则k出现在数组的后半段;如果中间的数字等于k,并且前一个数字不等于k则中间位置是第一次出现k,否则第一次k在数组前半段。k最后一次出现的位置分析类似。

public int getNumberOfK(int[] a, int k){int first = getFirstK(a, k);int last = getLastK(a, k);if (first > -1 && last > -1){return last - first + 1;}return 0;}public int getFirstK(int[] a, int k){int left = 0, right = a.length - 1, middle = 0;while (left <= right){middle = (left + right) / 2;if (a[middle] == k){if ((middle > 0 && a[middle - 1] != k) || middle == 0)//中间的数等于k,并且前面一个数不等于k则此时中间的数是第一个k{return middle;}elseright = middle - 1;}else if (a[middle] > k){right = middle - 1;}else{left = middle + 1;}}return - 1;}public int getLastK(int[] a, int k){int left = 0, right = a.length - 1, middle = 0;while (left <= right){middle = (left + right) / 2;if (a[middle] == k){if ((middle < a.length - 1 && a[middle + 1] != k) || middle == a.length - 1)//中间的数等于k,并且后面一个数不等于k则此时中间的数是最后一个k{return middle;}elseleft = middle + 1;}else if (a[middle] > k){right = middle - 1;}else{left = middle + 1;}}return - 1;

18.数组中只出现一次的数字:一个数组中除了两个数字之外,其他的数字都出现了两次,找出这两个只出现一次的数字。时间复杂度O(n),空间复杂度O(1)

分析:{2,4,3,6,3,2,5,5}得到4和6,一个数组中只出现一次的数字可以通过异或求得,x^x = 0, x^0 = x;所以可以通过把原数组分割成两个只含有只出现一次的子数组。所有数字异或后肯定不会0,找出一个出现为1的位置,记为n位,通过n位是不是1把原数组分成两个子数组。

public int[] findNumberAppearOnce(int[] a){int resultExclusiveOr = 0;for(int i = 0; i < a.length; i++)resultExclusiveOr ^= a[i];//从右到左找出第一个出现1的位int indexOf1 = 0;while(((resultExclusiveOr & 1) == 0) && indexOf1 < 32){resultExclusiveOr >>= 1;indexOf1++;}int[] number = new int[2];number[0] = number[1] = 0;for(int i = 0; i < a.length; i++){if (((a[i] >> indexOf1) & 1) != 0)//把数组分成两个子数组{number[0] ^= a[i];}else {number[1] ^= a[i];}}return number;}

19.数组中重复数字:在一个长度为n的数组里的所有数字都在0~n-1的范围内,找出数组中任意一个重复的数字。

解法一:采用hash表,时间复杂度O(n),空间复杂度O(n);

解法二:利用数字在0~n-1的条件,扫描到第i个数字时,首先比较这个数字是不是等于i,如果是扫描下一个数字;如果不是,再比较该数字(即a[i])和第a[i]个数字(即a[a[i]),如果相等就找到了第一个重复的,如果不等,则交换位置。每个数字最多交换两次,所有时间复杂度为O(n),空间O(1)

public int  duplicate(int[] number){if (number == null || number.length == 0){return -1;}for(int i = 0; i < number.length; i++){if (number[i] < 0 || number[i] > number.length - 1){return -1;}}for(int i = 0; i < number.length; i++){while(number[i] != i){if (number[i] == number[number[i]]){return number[i];}int temp = number[i];number[i] = number[temp];number[temp] = temp;}}return - 1;}






 






0 0