关于数组的面试笔试题
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
2.数组循环移位:将一个含有N个元素的数组循环右移K位,时间复杂度O(N),只允许使用两个附加变量
tips:
1)每个元素右移N位后都会回到自己的位置上
2)abcd1234->1234abcd
逆序abcd:abcd1234->dcba1234
逆序1234:dcba1234->dcba4321
全部逆序dcba4321->1234abcd
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]}
时间复杂度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
伪代码:
延伸题:输入一个正数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为止。
5.寻找数组中的最大值和最小值
method1:利用max和min存储当前最大值和最小值,奇数位和偶数位相比完再分别和max和min比,只用遍历一次
method2:分治算法:只需分别求出前后N/2个数的Min和Max,然后取较小的Min和较大的Max
伪代码:
6.求数组中出现次数超过一半的数,时间复杂度O(N),空间复杂度O(1)
使用一个计数器,如果下一个数和本数相同则counter++,若不相同则counter--;如果counter等于0,则number设置为该下标对应的数,因为其出现次数大于一半,所有counter肯定>0
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]
时间复杂度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个数所能产生的和之集合的堆
伪代码:
解法二:
给定Sk的可能值v和a[k],查找v-a[k]是否在Sk-1中
isOk[i][v]表示是否可以找到i个数,使得它们之和等于v
伪代码:
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)
解法:首先选取数组中右上角(或左下角)的数字,如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在列;如果该数字小于要查找的数字,剔除这个数字所在的行。
11.递增旋转数组的最小数字,旋转:将一个数组最开始的若干个元素搬到数组的末尾。如{1,2,3,4,5},{3,4,5,1,2}
二分查找:二个排过序的子数组
12.输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,偶数位于数组的后半部分;
分析:维护两个指针,第一个指针指向数组的第一个元素,指向的元素是奇数时移动;第二个指针指向数组的第二个元素,指向的元素是偶数时移动;两个指针都停止时且i<j时交换元素
C++中可以利用函数指针来操作,
java利用函数对象来操作,即策略模式;或者java8的λ表达式
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
分析:把矩阵看成若干个顺时针的圈组成
打印一圈分成四步:从左到右,从上到下,从右到左,从下往上;第一步必定会执行
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
15.把数组排成最小的数:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接处的所有数字中的最小的一个。如{3,32,321},输出321323
分析:即定义一个比较规则,数字m和n,如果mn<nm,则m排在n前,另外防止数溢出,使用字符串存储数字
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
17.求一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5},则3出现的次数为4
分析:本题是二分查找的增强版,即找出k第一次出现和最后一次出现的位置。
求k第一次出现的位置:根据二分查找的思想,如果中间的数字比k大,k出现在数组的前半段;如果中间的数字比k小,则k出现在数组的后半段;如果中间的数字等于k,并且前一个数字不等于k则中间位置是第一次出现k,否则第一次k在数组前半段。k最后一次出现的位置分析类似。
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把原数组分成两个子数组。
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;
}
如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
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) 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;
}
相关文章推荐
- 笔试面试中关于数组的常见算法
- 笔试面试之有序数组中查找和为定值的两个数
- 关于数组的一些面试题目
- 关于面试笔试的相关问题通知
- 关于最近的一些笔试面试的小结
- 一道关于二维数组和指针数组的C语言笔试题目
- 关于09年迅雷面试+笔试+上机题目的总结(解答来自网络)
- 笔试面试中关于库函数实现的总结
- 关于前端学习和笔试面试的总结
- 关于前端学习和笔试面试的总结
- 关于简历,网申,笔试,面试过程的分享
- 关于截取字符串的java笔试(面试)题简化代码实现
- 初入职场,关于计算机专业毕业生简历,笔试,面试的一些心得
- 面试笔试-两个数组的数据分类
- 关于前端学习和笔试面试的总结
- C++指针数组和数组指针--笔试面试系列
- 笔试面试之求子数组最大和
- 【笔试面试知识点查缺补漏深入理解之C与C++篇】C语言中的指针数组与数组指针
- 关于Web前端及百度web前端笔试面试题目
- 关于前端学习和笔试面试的总结