关于数组的面试笔试题

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++;
				}
				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)
	{
		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;
	}






 






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值