最近在面试中遇到了一个问题。
题目描述如下:
题目描述 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。 由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
解题思路1 :
假设有超过一半的元素有相同的值,则必有中位数为该值。那么我们只要求未排序的数组的中位数即可。这里我想到求解中位数的方法是利用快速排序的思想。
解释: 因为快速排序每一次都会把一个数,放到这个数的最终位置上。所以,借助这个思路,我们可以快速的找到中位数。
然后再对该数遍历一遍数组,如果该元素确实超过了一半,则返回该数,否则返回0。
解题思路2:
假设有超过一半的元素有相同的值。
则我们首先对数组进行一次遍历。对第一个出现的元素,记录元素与出现的次数。接下来进入迭代:
如果之后的元素如该元素相同,则次数+1 , 否则次数 - 1。当之前记录的可能元素,次数为0 时。替换元素为当前的元素. 并设置次数为1.
遍历完成后,得到的元素可能为超过一般的相同元素的值。此时我们需要再遍历一遍数组,对该值进行检验。
答案:
package basic.special;
import java.util.Random;
/*
题目描述“
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。
由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
*/
/**
* Created by szh on 2019/3/15.
* 剑指Offer:数组中出现次数超过一半的数字
*/
public class MoreThanHalf {
public static boolean inputInvalid = false;
public static void main(String[] args) throws Exception {
// Scanner sc = new Scanner(System.in);
// while (sc.hasNext()) {
// String[] str = sc.nextLine().split(",");
// if (str == null || str.length <= 0)
// break;
// int[] num = new int[str.length];
// for (int i = 0; i < str.length; i++) {
// num[i] = Integer.parseInt(str[i]);
// }
// int result1 = MoreThanHalfNum_Solution(num);//方法1
// int result2 = MoreThanHalfNum(num);//方法2
// System.out.println(result1);
// System.out.println(result2);
// }
// sc.close();
{
int result = MoreThanHalf.MoreThanHalfNum(new int[]{1, 2, 3, 4, 5, 6});
System.out.println("new int[]{1, 2, 3, 4, 5, 6} : " + result);
int result2 = MoreThanHalf.MoreThanHalfNum(new int[]{2, 2, 3, 2, 2, 1});
System.out.println("new int[]{2, 2, 3, 2, 2, 1} : " + result2);
}
}
/**
* 方法1:基于Partition函数的时间复杂度为O(n)的算法
* 采用快速排序的思想
* 现在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小的数字都排在它的左边,比选中的数字大的数字都排在它的右边。
* 如果这个选中的数字的下标刚好是n/2,那么这个数字就是数组的中位数;
* 如果它的下标大于n/2,那么中位数应该位于它的左边,可以接着再它的左边部分的数组中查找;
* 如果它的下标小于n/2,那么中位数应该位于它的右边,可以接着在它的右边部分的数组中查找。
*
* @param array
* @return
* @throws Exception
*/
private static int MoreThanHalfNum_Solution(int[] array) throws Exception {
if (CheckInvalidArray(array, array.length))
return 0;
int middle = array.length >> 1;
int start = 0;
int end = array.length - 1;
int index = Partition(array, array.length, start, end);
while (index != middle) {
if (index > middle) {
end = index - 1;
index = Partition(array, array.length, start, end);
} else {
start = index + 1;
index = Partition(array, array.length, start, end);
}
}
int result = array[middle];
if (!CheckMoreThanHalf(array, array.length, result))
return 0;
return result;
}
/**
* 检查输入的数组是不是无效的
*
* @param numbers
* @param length
* @return
*/
private static boolean CheckInvalidArray(int[] numbers, int length) {
inputInvalid = false;
if (numbers == null || length < 0)
inputInvalid = true;
return inputInvalid;
}
/**
* 快速排序中的数组分割
*
* @param array
* @param length
* @param start
* @param end
* @return
* @throws Exception
*/
private static int Partition(int[] array, int length, int start, int end) throws Exception {
if (array == null || length <= 0 || start < 0 || end >= length)
throw new Exception("不合法的参数");
//产生[start,end]区间范围的随机数
Random random = new Random();
int index = random.nextInt(end - start + 1) + start;
swap(array[index], array[end]);
int small = start - 1;
for (index = start; index < end; ++index) {
if (array[index] < array[end]) {
++small;
if (small != index)
swap(array[index], array[small]);
}
}
++small;
swap(array[small], array[end]);
return small;
}
/**
* 数字交换,使用位运算
*
* @param a
* @param b
*/
private static void swap(int a, int b) {
a = a ^ b;
b = b ^ a;
a = a ^ b;
}
/**
* 检查输入的数组中出现频率最高的数字是否超过数组长度的一半
*
* @param array
* @param length
* @param result
* @return
*/
private static boolean CheckMoreThanHalf(int[] array, int length, int result) {
int times = 0;
for (int i = 0; i < length; i++) {
if (array[i] == result)
times++;
}
boolean isMoreThanHalf = true;
if (times * 2 <= length) {
inputInvalid = true;
isMoreThanHalf = false;
}
return isMoreThanHalf;
}
/**
* 方法2:根据数组特点找出时间复杂度为O(n)的算法
* 在遍历数组的时候保存两个值:一个是数组中的一个数字,另一个是次数。
* 当遍历到下一个数字时,如果下一个数字和之前保存的数字相同,则次数加1;
* 如果下一个数字与之前保存的数字不同,则次数减1;
* 如果次数为0,那么需要保存下一个数字,并把次数设为1。
* 要找的数字肯定是最后一次把次数设为1时对应的数字。
*
* @param array
* @return
*/
private static int MoreThanHalfNum(int[] array) {
if (CheckInvalidArray(array, array.length))
return 0;
int result = array[0];
int times = 1;
for (int i = 1; i < array.length; i++) {
if (times == 0) {
result = array[i];
times = 1;
} else if (array[i] == result) {
times++;
} else {
times--;
}
}
if (!CheckMoreThanHalf(array, array.length, result))
return 0;
return result;
}
}