题目29:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。
分析:
数组中有一个数字出现次数超过数组长度的一半,也就是说它出现的次数比其它数字出现的次数的和还要多。因此可以在遍历数组的时候保留两个值:一个是数组中的一个数字,另一个是次数。当遍历到下一个数字的时候,如果下一个数字和之前保存的数字相同,则次数加1,不同则次数减1。如果次数为0,需要保存下一个数字,并把次数设置为1。由于要找出的数字出现比其它所有的数字出现次数之和还要多,那么要找的数字肯定是最后一次把数字设为1时对应的数字。
代码如下所示:
public static int moreThanHalfNum(int[] array) {
if (checkInvalidArray(array)) 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 (result == array[i])
times++;
else
times--;
}
if (!checkMoreThanHalf(array, result)) return 0;
return result;
}
// 全局变量判断数组是否无效
public static boolean inputInvalid = false;
public static boolean checkInvalidArray(int[] array) {
inputInvalid = false;
if (array == null || array.length <= 0) inputInvalid = true;
return inputInvalid;
}
public static boolean checkMoreThanHalf(int[] array, int result) {
int times = 0;
for (int i = 0; i < array.length; i++) {
if (array[i] == result) times++;
}
boolean isMoreThanHalf = true;
if (times * 2 <= array.length) {
inputInvalid = true;
isMoreThanHalf = false;
}
return isMoreThanHalf;
}
public static void main(String[] args) {
System.out.println(moreThanHalfNum(new int[]{1, 2, 3, 2, 2, 2, 5, 4, 2}));
System.out.println(moreThanHalfNum(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}));
System.out.println(moreThanHalfNum(new int[]{1}));
System.out.println(moreThanHalfNum(null));
}
题目30:输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数是1、2、3、4。
分析:
最直接的思路是把输入的n个数排序,位于最前的k个数就是最小的k个数,这种思路的时间复杂度是O(nlogn)。
可以先出创建一个大小为k的容器来存储最小的k个数字,如果容器已有的数字少于k个,则直接把读入的数字放进容器中;如果容器已有k个数字,此时不再是插入数字,而是替换容器中的数字。找出已有的k个数的最大值,然后拿待插入的数字和最大值比较,如果待插入的数字比最大值小,则替换最大值;如果待插入的数字比最大值大,那么不可能是最小的k个数,丢弃掉。
当容器满了之后需要做三件事:1.在k个整数中找出最大整数;2.可能删除这个最大整数;3.可能插入一个新的整数。如果用二叉树实现这个容器,能在O(logk)时间内实现这三步,对于n个数字而言,总的时间复杂度是O(nlogk)。
由于每次都需要找到k个数字的最大数字,很容易就会想到最大堆。在最大堆中,根结点的数字总是大于它的子树中的任意值。于是可以在O(1)时间内找到已有的k个数字的最大值,但需要O(logk)时间完成删除及插入操作。
红黑树通过把结点分为红、黑两种颜色并根据一些规则确保树在一定程度上是平衡的,从而保证在红黑树中查找、删除、和插入操作都只需要O(logk)时间。使用TreeSet来实现。
代码如下所示:
// array中有相同数字的待解决
public static void getLeastNumbers(int[] array, int k) {
if (array == null || array.length == 0 || k <= 0 || k > array.length) return;
TreeSet<Integer> set = new TreeSet<>();
for (int i = 0; i < array.length; i++) {
if (set.size() < k) set.add(array[i]);
else if (array[i] < set.last()) {
set.remove(set.last());
set.add(array[i]);
}
}
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
}
public static void main(String[] args) {
getLeastNumbers(new int[]{4, 5, 1, 6, 2, 7, 3, 8}, 4);
System.out.println();
getLeastNumbers(new int[]{4, 5, 1, 6, 2, 7, 3, 8}, 1);
System.out.println();
getLeastNumbers(new int[]{4, 5, 1, 6, 2, 7, 3, 8}, 8);
System.out.println();
getLeastNumbers(new int[]{4, 5, 1, 6, 2, 7, 3, 8}, 0);
System.out.println();
getLeastNumbers(new int[]{4, 5, 1, 6, 2, 7, 3, 8}, 9);
System.out.println();
getLeastNumbers(null, 9);
System.out.println();
// 待解决
getLeastNumbers(new int[]{1, 5, 1, 6, 2, 7, 1, 8}, 4);
}
这种解法适合海量数据处理,尤其是n较大且k较小的情况。
当需要在某数据容器内频繁查找及替换最大值时,二叉树是一个合适的选择,并能想到用堆或者红黑树等特殊情况的二叉树来实现。