您的位置:首页 > 职场人生

关于数组的面试笔试题

2014-09-30 15:56 495 查看
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.l
4000
ength; i++)
{
if (count == 0)
{
number = a[i];
count = 1;
}
else {
if (number == a[i])
{
count++;
}
else
count--;
}
}
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];//s

long temp = 1;
for(int i = length - 2; i > length; i--)
{
temp *= arrayA[i + 1];//t
arrayB[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--;
}
else
row++;
}
}
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;
}
else
break;
}
}
扩展性:按正负数排序,被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;
}
else
break;
}
}
Test test = new Test();
int[] a = {1, 2, 3, 4, 5};
test.Reorder(a, new Comparator()
{

@Override
public boolean isSatisfied(int n)
{
return (n & 0x1) == 0;
}
});

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

如输入

1  2  3  4

6  7  8

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)
c9b7
{
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 back
for(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;
}
else
right = 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;
}
else
left = 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;
}

 



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: