关于数组操作的常见题型

一、数组操作

缺陷:创建数组时,必须指定数组的容量大小,然后根据数据大小分配内存。所以空间效率不高,经常会有空闲的区域未得到充分利用。
优点:可在O(1)时间内读/写任何元素,利用数组来实现哈希表

二、考察内容

1. 排序
插入排序(直接插入、折半插入、希尔排序)3种:时间复杂度O(n^2),空间复杂度O(1)
交换排序(冒泡排序:时间复杂度O(n^2),空间复杂度O(1) 快速排序:时间复杂度O(nlogn),空间复杂度O(nlogn))
选择排序(简单选择排序:时间复杂度O(n^2),空间复杂度O(1)  堆排序:      时间复杂度O(nlogn)空间复杂度O(1)
归并排序:时间复杂度O(nlogn)空间复杂度O(n)

2. 查找
静态表查找:实现下面2个功能:(1查询某个数据是否在查找表中(2)检索满则条件的某个特定的数据元素的各种属性。
顺序查找:时间复杂度O(n)
有序数据的二分查找:时间复杂度O(logn)

动态表查找:需要动态插入或删除的查找表
哈希表查找:利用哈希表实现O(1)时间复杂度查找,需要辅助空间O(n)
二叉排序树查找:二叉平衡树和B树是二叉排序树的改进。

技巧提示:
3. 数组遍历:数组最好只遍历一次,从头开始 或 头尾同时进行
4. 在排序数组(或部分有序的数组)中查找一个数字或统计某个数字出现的次数,可以考虑用二分查找。

三、常见试题列表(剑指Offer)

1) 二维数组的查找
2) 旋转数组的最小数字(两个有序的子序列 => 二分查找的变形)
3) 调整数组顺序使其奇数位于偶数前面(快速排序partition函数变形)
4) 数组中出现次数超过一半的数字(1.快排2.利用数组性质,记录元素,与之前相同+1,不同减1,出现超过一半的数字肯定是最后设置为1的那个数)
5) 最小的k个数
6) 连续子数组的最大和
7) 把数组排成最小的数(大数问题O(nlogn)
8) 数组中的逆序对
9) 数字在排序中出现的次数
10) 数组中只出现一次的数字
11) 和为S的两个数&&和为S的连续正数序列
12) 扑克牌的顺子(转换为数组)
13) 数组中重复的数字
14)计数排序算法

四、参考代码

(1)二维数组的查找:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

public boolean findNum(int[][] arr,int target,int rows,int columns){
		boolean isFind = false;
		int row = 0;
		int column = columns-1;
		if(arr.length<1){
			return false;
		}
		
		while(row<rows&&column>=0){
			if(arr[row][column] == target){
				System.out.println("target:"+arr[row][column]);
				return true;
			}else if(arr[row][column]>target){
				column--;
			}else {
				row++;
			}
		}
		return isFind;
	}
	
	public static void main(String[] args){
		int[][] arr = {{1,2,3},{4,5,6},{7,8,9}};
		boolean result = new OfferTest_007().findNum(arr, 9, 3, 3);
		System.out.println(result?"找到":"未找到");
	}

2)旋转数组的最小数字输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。(两个有序的子序列 => 二分查找的变形)
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

思路1顺序查找:因为是递归序列因此旋转后遇到的第一个非递增的即为要找的最小值。查找第一个开始比第一个数小的数
思路2:二分查找:以为旋转数组是由两个有序数组组成的,使用两个指针分别指向low和high,mid=(low+high)/2.
如果 arr[mid]>arr[low]说明仍处于前半部分递增序列,low = mid
如果arr[mid]<arr[high]说明mid处于后半部分递增序列,high = mid
当high-low == 1 high指向的就是最小值。循环结束条件为low<high.
特殊情况:当arr[mid] = arr[low] = arr[high]时只能顺序查找,其第一个非递增的数

public int getMinNum(int[] arr){
		if(arr.length<1){
			System.out.println("输入为空!");
		}
		int temp = arr[0];
		int target = 0;
		int len = arr.length;
		int k = 0;
		
		while(arr[k]>=temp&&k<len){
			k++;
			if(k==len){
				//说明第一个元素为最小值,不能再判断,否则就越界了
				break;
			}
		}
		
		if(k<len){
			//找到第一个比第一个数小的
			target = arr[k];
		}else if(k ==len) {
			target = arr[0];
		}
		
		return target;
	}
	
	public int BinarySearch(int[] arr,int low,int high){
		while(arr[low]>=arr[high]){//边界条件:旋转以后low一定比high大,否则说明该数组未进行旋转。最小位为arr[0]
			int mid = (low+high)/2;
			
			if(high-low==1){
				return arr[high];
			}
			if(arr[low]== arr[high]&&arr[low]==arr[mid]){
				return getMinNum(arr);
			}
			else if(arr[low]<arr[mid]){
				low = mid;
			}
			else if(arr[high]>arr[mid]){
				high = mid;
			}
		}
		
		return arr[0];
	}

3)调整数组顺序使其奇数位于偶数前面(快速排序partition函数变形)

思路:设置两个指针low指向头 last指向数组尾。当low往后走遇到偶数,high往前遇到奇数时,交换位置。直到low和high相遇.
同类题型:a、将数组分为两部分,使其正数放在负数前面。b、将数组分为两部分,使能被3整除的放在前面,不能被3整除的放在后面

//当此问题扩展到将数组按某函数分为两部分时,在函数中传递一个(分组规则)这样既可无需更改函数
	public void adjustArry(int[] arr){
		int low = 0;
		int high = arr.length-1;
		while(low<high){
			//找到第一个偶数
			while(low<high&&(arr[low]&1)!=0){
				low++;
			}
			//找到第一个奇数,偶数的时候high--
			while(low<high&&(arr[high]&1)==0){
				high--;
			}
			if(low<high){
				//交换位置
				int temp = arr[low];
				arr[low] = arr[high];
				arr[high] = temp;
			}
		}
	} 

4)数组中出现次数超过一半的数字

思路1快排 。注意:要进行判断是否数组中有数字超过一半
思路2利用数组性质,记录元素,与之前相同+1,不同减1,出现超过一半的数字肯定是最后设置为1的那个数

boolean isInputInvalid = false;//是否输入无效
	public int partition(int[] arr,int low,int high){
		int priv = arr[low];
		while(low<high){
			//从右往左找到第一个小于priv的
			while(low<high&&arr[high]>priv){
				high--;
			}
			arr[low] = arr[high];
			while(low<high&&arr[low]<=priv){
				low++;
			}
			arr[high] = arr[low];
		}
		arr[low]=priv;
		return low;
	}
	
	public int getMoreThenHalfNum(int[] arr){
		int length = arr.length;
		if(length<1){
			isInputInvalid = true;
			return 0;
		}
		int middle = length>>1;
		int low = 0;
		int high = length-1;
		int index = partition(arr, low, high);
		System.out.println("index-0:"+index+"middle:"+middle);
		while(index!=middle){
			if(index<middle){
				low = index+1;
				index = partition(arr,low, high);
			}else {
				high = index-1;
				index = partition(arr, low, high);
			}
		}
		int result = arr[middle];
		if(!isHasMoreHalfNum(arr, result)){
			return 0;//无效输入时返回0,即数组中不包含出现次数大于一半的
		}
		return result;
	}
	
	public boolean isHasMoreHalfNum(int[] arr,int result){
		boolean isHas = true;
		int times = 0;
		for(int i=0;i<arr.length;i++){
			if(result == arr[i]){
				times++;
			}
		}
		
		if(times*2<arr.length){
			isHas = false;
			isInputInvalid = true;
		}
		return isHas;
	}

思路2代码:从头开始遍历,初始化一个数result和一个count,把第一个数保存为result,count=1,到下个数,如果和第一个数相等则count+1,否则count-1,result=新的数
因为超过一半的数字出现的次数大于其他数的次数。所以最后的设置的数肯定是要找的数。

boolean isInputInvalid = false;//是否输入无效
	
	public int getMoreThenHalfNum(int[] arr){
		int length = arr.length;
		if(length<1){
			isInputInvalid = true;
			return 0;
		}
		int result=arr[0];
		int times = 1;
		for(int i =1 ;i<arr.length;i++){
			if(times==0){
				result = arr[i];
				times = 1;
			}else if(result == arr[i]) {
				times++;
			}else {
				times--;
			}
		}
		
		if(!isHasMoreHalfNum(arr, result)){
			isInputInvalid = true;
			return 0;
		}
		return result;
	}
	
	public boolean isHasMoreHalfNum(int[] arr,int result){
		boolean isHas = true;
		int times = 0;
		for(int i=0;i<arr.length;i++){
			if(result == arr[i]){
				times++;
			}
		}
		
		if(times*2<arr.length){
			isHas = false;
			isInputInvalid = true;
		}
		return isHas;
	}

(5)最小的k个数:输入n个整数,找出其中最小的k个数,例如输入451627388个数,则最小的4个数为1234

思路1简单就是对n个数进行排序,拿到前4个数即可。时间复杂度为O(nlogn)
思路2当面试官允许可以修改输入数组时,采用快速排序算法中partition原理,使得比第k个数字小的所有数字位于数组的左边。时间复杂度为O(n)
思路3O(nlogk)算法-处理海量数据(堆、红黑树),若不允许改变数组,则创建一个k大小的容器arr[k],每次从数组中读一个数据。
1.当数组不满时,直接将数加到数组中,当数组满了,则将数与arr[k]中最大值比较,如果比最大值小,则将最大值删除,把该数放入数组中
2.每次都要从arr[k]中查找最大值并进行删除和插入操作,所以可以采用二叉树中特殊的 最大堆。
几种方法的比较:时间复杂度最小的是思路2。但是思路3的解法优点是(1)没有修改原数组(2)该方法适合海量数据的输入。假设题目是从海量数据中找到最小的k个数,由于内存大小有限,不能把这些海量数据一次性载入内存,这个时候需要借助存储空间(比如硬盘)每次读一个数字。因此此方法适合于n很大,k很小的情况。
方法2代码:

boolean isInputInvaild = false;
	public int partition(int[] arr,int low,int high){
		int priv = arr[low];
		while(low<high){
			//从high开始,找到第一个小于priv的数,放入arr[low]
			while(low<high&&arr[high]>priv){
				high--;
			}
			arr[low] = arr[high];
			while(low<high&&arr[low]<=priv){
				low++;
			}
			arr[high] = arr[low];
		}
		arr[low] = priv;
		return low;
	}
	public void findKNumFromArray(int[] arr,int k){
		int len = arr.length;
		if(len<0 || len-1<k || k<0){
			isInputInvaild = true;
			return;
		}
		int low = 0;
		int high = len-1;
		int index = partition(arr, low, high);
		
		while (index!=k-1) {
			if(index<k-1){
				low = index+1;
				index = partition(arr, low, high);
			}else {
				high = index-1;
				index = partition(arr, low, high);
			}
		}
		
		for(int i = 0; i<=index;i++){
			System.out.print(arr[i]+" ");
		}
	}

(6)连续子数组的最大和:输入一个整型数组,数组里有正数也有负数,数组中一个或连续的多个正数组成一个子数组,求所有子数组的和的最大值,要求时间复杂度为O(n)

思路1:分析数组的规律。当和为负数,置为当前值。和为正数时,相加当前值得到当前和,然后跟最大值比较。
思路2:应用动态规划法:函数f(i)表示以第i个数字结尾的子数组的最大和,得到递归公式
方法1—代码:

boolean isInputInvalid = false;
	public int getMaxOfAllArray(int[] arr){
		int result = 0x80000000;
		int curSum = 0;
		if(arr.length<1){
			isInputInvalid = true;
			return 0;
		}
		for(int i=0;i<arr.length;i++){
			if(curSum<=0){
				curSum = arr[i];
			}else {
				curSum+=arr[i];
			}
			
			if(curSum>result){
				result = curSum;
			}
		}
		return result;
	}

7)把数组排成最小的数:输入一个正整数数组,把数组中所有数字拼接成一个数,打印能拼接处的所有数字中最小的一个。例如输入数组{332321}则打印出这3个数能排成的最小数字321323

思路1最直接的做法,先求出数组中所有数字的全排列。
思路2大数问题:将数组转换为字符串,字符串的对比按照字符串大小的比较规则即可

简单方法:用字符串比较的规则

public static void main(String[] args) {
		int[] arr={3,32,321};
		System.out.println(getMinNumber(arr));
	}
	
	public static int getMinNumber(int[] arr){
		if(arr.length<1){
			return -1;
		}
		String minResult = "";
		for(int i=0;i<arr.length;i++){
			String str1 = arr[i]+minResult;
			String str2 = minResult+arr[i];
			
			if(str1.compareTo(str2)<0){
				minResult = str1;
			}else {
				minResult  = str2;
			}
		}
		
		return Integer.parseInt(minResult);
	}
方法2代码:

int minNum = 0;
	public void getMinNumFromAllArray(int[] arr,int begin){
		int len = arr.length;
		if(len<1||begin<0){
			return;
		}
		
		String curNumStr = "";
		if(begin == len-1){
			//说明已经组成完整的数字了,比较大小并打印
			for(int i=0;i<len;i++){
				System.out.print(arr[i]);
				curNumStr+=arr[i];
			}
			System.out.println();
			
			if(minNum==0){
				minNum = Integer.parseInt(curNumStr);
			}else if(Integer.parseInt(curNumStr)<minNum){
				minNum = Integer.parseInt(curNumStr);
			}
		}
		for(int i = begin;i<len;i++){
			//首先固定一个数,(第一个数与后面每一个数进行交换,得到所有的第一个数的情况)
			int temp = arr[i];
			arr[i] = arr[begin];
			arr[begin] = temp;
			//后面所有数字的全排列(递归调用)
			getMinNumFromAllArray(arr, begin+1);
			
			temp = arr[i];
			arr[i] = arr[begin];
			arr[begin] = temp;
		}
	}

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

思路把数组分隔为子数组,先统计出子数组内部(left+right)的逆序对的数目,然后统计出两个相邻子数组之间的数目。在统计逆序对的过程中,还需对数组进行排序,类似归并排序。

boolean isInputInvaild = false;//无效输入
	public int inversePairs(int[] arr){
		int count = 0;
		if(arr.length<1){
			isInputInvaild = true;
			return count;
		}
		int[] brr = new int[arr.length];
		
		//初始化brr与arr相等
		for(int i = 0;i<arr.length;i++){
			brr[i]=arr[i];
		}
		int length = arr.length;
		count = inversePairsCore(arr, brr, 0, length-1);
		return count;
	}
	//归并排序
	public int inversePairsCore(int[] arr,int[] brr,int low,int high){
		int count = 0;
		if(low<high){
			int mid = (high+low)>>1;
			count += inversePairsCore( arr,brr, low, mid);
			count += inversePairsCore(arr,brr, mid+1, high);
			count += mergeSort(arr, brr, low, mid, high);
		}
		
		return count;
	}
	//从两个数组的末尾开始扫描
	public int mergeSort(int[] arr,int[] brr,int low,int mid,int high){
		int count = 0;
		//归并排序时,求逆序个数
		int i = mid;
		int j = high;
		int k = high;//brr数组标记
		while(i>=low && j>=mid+1){
			if(arr[i]>arr[j]){
				brr[k--]=arr[i--];
				count += j-mid;//逆序
			}else {
				brr[k--]=arr[j--];
			}
		}
		//剩余部分放入数组brr中
		while(i>=low){
			brr[k--] = arr[i--];
		}
		while(j>=mid+1){
			brr[k--] = arr[j--];
		}
		//从辅助数组中将元素拷贝到原数组中,使其有序排列  
		for(i = high;i>k;i--){
			arr[i]= brr[i];
		}
		return count;
	}

(9)数字在排序中出现的次数:统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,输出4.

思路:首先看到排序数组,想到“二分查找”法。
利用二分查找到k,还必须判断是否为第一个k或 最后一个k
如果不是第一个k(左边相邻的也为k),则设置截止指针在左边子数组中继续找
如果不是最后一个k(右边相邻的也为k,则设置起始指针在右边子数组中继续找。

public int getFirstIndex(int[] arr,int target,int low,int high){
		if(low>high){
			return -1;
		}
		int mid = (low+high)>>1;
		if(arr[mid]==target){
			//判断其前个是否是target,如果是,则继续在前半段查找,如果不是则说明找到第一个数字出现的位置。
			if(mid>=1){
				if(arr[mid-1]!=target){
					return mid;
				}else {
					high = mid-1;
				}
			}else {
				//mid出现在0位
				return 0;
			}
		}else if(arr[mid]<target){
			//target在后半段
			low = mid+1;
		}else {
			high = mid-1;
		}
		
		return getFirstIndex(arr, target, low, high);
	}
	
	public int getLastIndex(int[] arr,int target,int low,int high){
		if(low>high){
			return -1;
		}
		int mid = (low+high)>>1;
		if(arr[mid]==target){
			if(mid<high){
				if(arr[mid+1]!=target){
					return mid;
				}else {
					low = mid+1;
				}
			}else {
				//mid=数组最后一位
				return high;
			}
		}else if(arr[mid]<target) {
			low = mid+1;
		}else {
			high = mid-1;
		}
		return getLastIndex(arr, target, low, high);
	}
	public int caculateCount(int[] arr,int target){
		int count = 0;
		if(arr.length<1){
			return 0;
		}
		int len = arr.length-1;
		int firstIndex = getFirstIndex(arr, target, 0, len);
		int lastIndex = 0;
		if(firstIndex!=-1){
			lastIndex = getLastIndex(arr, target, 0, len);
			count = lastIndex-firstIndex+1;
		}else {
			count = -1;
		}
		
		return count;
	}


(10)数组中只出现一次的数字(哈希表=>java HashMap):一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。时间复杂度为O(n),空间复杂度为O(1) 。例如:数组{2,4,3,6,3,2,5,5}中只有46只出现一次,输出4,6

思路1异或运算性质:任何一个数字异或自己为0,应用到此题是从头到尾异或的结果即时只出现一次的那个数。因为有两个数,所以要把数组分为2部分,每个里面包含一个只出现一次的数,即可利用异或运算性质。如何将两个数字分配到两个数组呢?如何把数子分到两个子数组中呢?*      将数组中所有数字进行异或,则得到的是结果是result = n^m,按result中第一个1出现的位置index。来将数组中数字进行分组。*      将index为1的分为一组,index为0的分为一组,这样(按n^m异或结果的1的位置分类,n\m肯定在相异的数组中),分别求子数组的异或即可

public void splitArray(int[] arr){
		//先求数组中所有数字的异或
		if(arr.length<1){
			return;
		}
		int result = 0;
		for(int i = 0; i<arr.length;i++){
			result = result^arr[i];
		}
		
		//将result向右移,直到为0,得到index
		int index = -1;//初始值要注意设置为-1,这样才能从0位开始。
		while(result!=0){
			index++;
			result=result>>1;
		}
		System.out.println("倒数第几位:"+index);
		//按index分组
		int flag = 1<<index;
		int[] brr1 = new int[arr.length];
		int[] brr2 = new int[arr.length];
		int m=0,n=0;
		for(int i = 0;i<arr.length;i++){
			if((arr[i]&flag)!=0){
				//说明index位为1
				brr1[m++]=arr[i];
			}else {
				brr2[n++]=arr[i];
			}
		}
		
		//分组完毕,将两个数组分别进行异或
		int target1 = 0;
		int target2 = 0;
		for(int num:brr1){
			target1=target1^num;
		}
		for(int num:brr2){
			target2=target2^num;
		}
		
		System.out.println("target1:"+target1+" target2:"+target2);
	}

(11)和为S的两个数&&和为S的连续正数序列
和为S的两个数:输入一个递增排序的数组和为一个数字S,在数组中查找两个数,使得它们的和正好为S,如果有多对和为S,则输出任意一对。
思路:时间复杂度为O(n),递增数组,尽量只遍历一次数组,定义两个指针,一个指向头Head = 0,一个指向数组尾Last = arr.length-1,然后根据当前和。只要Last>Head时,若和>S,Last--,和<S,则Head++,直到找到。

public void getNumOfSum(int[] arr,int sum){
		if(arr.length<1){
			return;
		}
		int low = 0;
		int high = arr.length-1;
		while(low<=high){
			if(arr[low]+arr[high]==sum){
				System.out.println(arr[low]+"+"+arr[high]+"="+sum);
				return;
			}
			else if(arr[low]+arr[high]<sum){
				low++;
			}else {
				high--;
			}
		}
	}

和为S的连续正数序列:输入一个正数S,打印所有和为S的连续正数序列(至少含有两个数)。Eg输入15,由于1+2+3+4+5 = 4+5+6=7+8=15,所以打印出3个连续序列1~54~67~8
思路:根据上题的思路,low和high初始化为1,2。判断arr[low]+arr[high]<s则high++,和累加
* 若和大于s,则将low++,删除序列前面的节点,再判断与s的大小。
* 找到以后继续增加high,根据和,重复前面的过程

技巧:用循环求一个连续序列的和,可以在前一个序列的和的基础上求操作之后的序列的和,可以减少不必要的运算,提高代码效率

public void findContinuousSequence(int target){
		if(target<3){
			return;//正数序列且至少有两个数
		}
		int low = 1;
		int high = 2;
		int sum = low;
		while(low<high){
			sum +=high;
			if(sum == target){
				System.out.println(low+"-"+high);
				high++;
			}
			else if(sum<target){
				high++;
			}else {
				sum=sum-low-high;//因为是累加,如果不减去high,每次还会累加一次high
				low++;
			}
		}
		
	}
	
	public void findContinuousSequence2(int target){
		if(target<3){
			return;
		}
		int small = 1;
		int big = 2;
		int middle = (1+target)/2;//保证序列中至少有两个数
		int curSum = small+big;
		while(small<middle){
			if(target == curSum){
				System.out.println(small+"-"+big);
			}
			while(curSum>target&&small<middle){
				curSum-=small;
				small++;
				
				if(curSum==target){
					System.out.println(small+"-"+big);
				}
			}
			big++;
			curSum+=big;
		}
	}
	
	public static void main(String[] args){
		int sum = 9;
		OfferTest_0472 test_047 = new OfferTest_0472();
		System.out.println("sum=9的正数序列");
		test_047.findContinuousSequence(sum);
		System.out.println("sum=15的正数序列");
		test_047.findContinuousSequence2(15);
		//不存在和为S的连续序列、边界值为3
		System.out.println("sum=4的正数序列");
		test_047.findContinuousSequence(4);
	}

(12)扑克牌的顺子:从扑克牌中随机抽取5张牌,判断是不是顺子,即这5张牌是不是连续的,2~10为数字本身,A1J11Q12K13,而大小王看作任意数字。

思路:
1.数组排序,
2.统计数组中0的个数(0代表大小王,可代表任意数字),
3.最后统计排序之后数组中相邻数字间的空缺数。如果空去总数小于或者等于0的个数,则这个数组即为连续,反之不连续。

public boolean isContinuous(int[] arr){
		if(arr.length<1){
			return false;
		}
		//将数组排序
		Arrays.sort(arr);
		//统计数组中0的个数以及空缺
		int count_0 = 0;
		int count_less = 0;
		for(int i=0;i<arr.length;i++){
			if(arr[i]==0){
				count_0++;
			}else {
				if(count_0>=1){
					//包含大小王时
					if(arr[i]-arr[i-1]!=1&&i>=count_0+1){
						count_less+=(arr[i]-arr[i-1]-1);
					}
				}else if(i>=1){
					//首位不是0,即没有大小王
					if(arr[i]-arr[i-1]!=1||arr[i]-arr[i-1]==0){
						return false;
					}
				}
					
			}
		}
		System.out.println("count_less:"+count_less+"count_0:"+count_0);
		if(count_0>=count_less){
			return true;
		}else {
			return false;
		}
	}

(13)数组中重复的数字在一个长度为n的数组里的所有数字都在0n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如当输入长度为7{2,3,1,0,2,53},那么对应的输出为2或者3.

思路1将数组进行排序,从排序数组中找到重复的数字,时间复杂度为O(nlogn)
思路2利用哈希表,对于java代码用HashMap
思路3考虑给出的数组中数字范围为0-n-1,则如果没有重复的数字,则排序后应该放对应下标的位置0-n-1。有重复数字,则会有位置重复值或空缺值。
从头到尾遍历数组,当下标indexm=arr[index]值相等,继续扫描下一个,当下标和值不等时,比较arr[m]处的值是否与m=arr[index]相等,相等则说明是重复的,若不等则交换,对应好坐标。重复比较直到找到。

boolean isHasRepeatNum = false;//判断是否有重复数字
	boolean isInputInvaild = false;//检查非法输入
	public int findRepeatNum(int[] arr){
		int result = 0;
		int len = arr.length;
		if(len<1){
			isInputInvaild = true;
			return -1;
		}
		for(int i = 0;i<len;i++){
			int m = arr[i];
			if(m!=i){
				if(m==arr[m]){
					result = m;
					isHasRepeatNum = true;
					break;
				}else {
					int temp = arr[i];
					arr[i] = arr[m];
					arr[m] = temp;
				}
			}
		}
		if(!isHasRepeatNum){
			//若找到重复的数字
			result = -1;
		}
		return result;
	}

(14)计数排序算法:对公司所有员工的年龄进行排序,公司总共有几万名员工,要求时间复杂度为O(n)。
 思路:
计数排序法:先汇总每个年龄的个数,年龄在0-99范围内,创建新的数组按序输出i年龄
 计数排序是建立在这样的前提条件下的:假设n个输入元素的每一个都是0到k区间内的一个整数,其中k为某个整数。因此我们后面所写的程序也只是针对0到k之间的元素进行排序,换句话说,排序元素中不能有负数。
计数排序的基本思想是:对一个输入元素x,先确定所有输入元素中小于x的元素个数,那么排序后x所在的位置也就明确了。
比如,所有的输入元素中有10个元素小于x,那么排好序后x的位置序号就应该是11。当然,如果有相同元素,自然要放到相邻的位置上。
	
	public  void caculateSort(int[] ageArry){
		if(ageArry.length<1){
			System.out.println("输入为空!");
		}
		
		Map<Integer, Integer> ageNumMap = new HashMap<Integer, Integer>();
		for(int age:ageArry){
			if(ageNumMap.containsKey(age)){
				ageNumMap.put(age, ageNumMap.get(age)+1);
			}else {
				ageNumMap.put(age, 1);
			}
		}
		//测试年龄次数
		for(int key:ageNumMap.keySet()){
			System.out.println("age:"+key+"count:"+ageNumMap.get(key));
		}
		
		int index = 0;
		int[] ageNew = new int[ageArry.length];
		for(int i = 0;i < 100; i++){
			int j = 0;
			if(ageNumMap.containsKey(i)){
				while(j<ageNumMap.get(i)){
					//说明包含此年龄
					ageNew[index++] = i;
					j++;
				}
			}
			
		}
		
		//打印
		for(int num:ageNew){
			System.out.print(num+",");
		}
		
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值