题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现5次,超过数组长度的一半,因此输出2.
解法一:基于Partition函数的O(n)算法:
- 通 过Partition()返回index,如果index==mid,那么就表明找到了数组的中位数;如果index<mid,表明中位数在 [index+1,end]之间;如果index>mid,表明中位数在[start,index-1]之间。知道最后求得index==mid循 环结束。
- 根据求得的index,遍历一遍数组,每当出现一个等于index所指向的数时time++,最后判断time是否大于数组长度的一半,如果大于则表明index所指向的数就是所求的数,如果不是,则表明不存在一个数出现的次数超过数组长度的一半。
我们的算法是受快速排序的算法的启发。在随机快速排序的算法中,我们 先在数组中随机的选择一个数字,然后调数组中数字的顺序,使得比选中的数字小数字排在它的左边,比选中的数字大的数字都排在它的右边。比如这个选中的数字 的下标刚好是n/2,那么这个数字就是数组中的中位数。如果它的下标大于n/2,那么中位数应该位于它的左边,我们可以接着在它的左边部分的数组中查找。 如果它的下标小于n/2,那么中位数应该在它的右边,我们可以接着在它的右边部分中查找。这是一个典型的递归过程。
Java代码实现过程如下:
package cglib;
public class DeleteNode{
public int partition(int[] arr,int left,int right){
int result = arr[left]; //选中的数字
System.out.println("arr[left="+left+"]"+arr[left]);
System.out.println("arr[right="+right+"]"+arr[right]);
if(left > right)
return -1;
while(left <right){
while(left <right && arr[right]>= result){
right --;
System.out.println("right--="+right);
}
System.out.println("交换前arr[right="+right+"]"+arr[right]);
arr[left] = arr[right];
System.out.println("交换后arr[left="+left+"]"+arr[left]);
while(left <right && arr[left] <result){
left++;
System.out.println("left++="+left);
}
System.out.println("交换前arr[left="+left+"]"+arr[left]);
arr[right] = arr[left];
System.out.println("交换后arr[right="+right+"]"+arr[right]);
}
arr[left] = result;
System.out.println("最后:arr[left="+left+"]"+arr[left]);
System.out.println("最后返回left:"+left);
return left;
}
public int moreThanHalfNum(int[] arr){
if(arr.length ==0)
return -1;
int length = arr.length;
int middle = length >>1;
int start = 0;
int end = length -1;
int index = partition(arr,start,end);
System.out.println("中间下标index:"+index);
while(index != middle){
if(index >middle){//选中的数字,数组下标大于中位数,则往左边找
end = index - 1;
System.out.println("进入if数组下标大于中位数,中间下标index:"+index);
index = partition(arr,start,end);
System.out.println("进入partition后数组下标大于中位数,中间下标index:"+index);
}
else{ //选中的数字,数组下标小于中位数,则往右边找
start = index + 1;
System.out.println("进入else数组下标小于中位数,中间下标index:"+index);
index = partition(arr,start,end);
System.out.println("进入partition后数组下标小于中位数,中间下标index:"+index);
}
}
int result = arr[middle];
if(!checkMoreThanHalf(arr,result)){
result = -1;
}
return result;
}
//验证是否存在
public boolean checkMoreThanHalf(int[] arr,int number){
int times = 0;
for(int i = 0;i<arr.length;i++){
if(arr[i] == number)
times ++;
}
boolean isMoreThanHalf = true;
if(times *2 <= arr.length){
isMoreThanHalf = false;
}
return isMoreThanHalf;
}
public static void main(String[] args){
int[] arr= {1,2,3,3,2,5,4,2,2,2,2};
DeleteNode test = new DeleteNode();
System.out.println(test.moreThanHalfNum(arr));
}
}
输出:
arr[left=0]1
arr[right=10]2
right--=9
right--=8
right--=7
right--=6
right--=5
right--=4
right--=3
right--=2
right--=1
right--=0
交换前arr[right=0]1
交换后arr[left=0]1
交换前arr[left=0]1
交换后arr[right=0]1
最后:arr[left=0]1
最后返回left:0
中间下标index:0
进入else数组下标小于中位数,中间下标index:0
arr[left=1]2
arr[right=10]2
right--=9
right--=8
right--=7
right--=6
right--=5
right--=4
right--=3
right--=2
right--=1
交换前arr[right=1]2
交换后arr[left=1]2
交换前arr[left=1]2
交换后arr[right=1]2
最后:arr[left=1]2
最后返回left:1
进入partition后数组下标小于中位数,中间下标index:1
进入else数组下标小于中位数,中间下标index:1
arr[left=2]3
arr[right=10]2
交换前arr[right=10]2
交换后arr[left=2]2
left++=3
交换前arr[left=3]3
交换后arr[right=10]3
right--=9
交换前arr[right=9]2
交换后arr[left=3]2
left++=4
left++=5
交换前arr[left=5]5
交换后arr[right=9]5
right--=8
交换前arr[right=8]2
交换后arr[left=5]2
left++=6
交换前arr[left=6]4
交换后arr[right=8]4
right--=7
交换前arr[right=7]2
交换后arr[left=6]2
left++=7
交换前arr[left=7]2
交换后arr[right=7]2
最后:arr[left=7]3
最后返回left:7
进入partition后数组下标小于中位数,中间下标index:7
进入if数组下标大于中位数,中间下标index:7
arr[left=2]2
arr[right=6]2
right--=5
right--=4
right--=3
right--=2
交换前arr[right=2]2
交换后arr[left=2]2
交换前arr[left=2]2
交换后arr[right=2]2
最后:arr[left=2]2
最后返回left:2
进入partition后数组下标大于中位数,中间下标index:2
进入else数组下标小于中位数,中间下标index:2
arr[left=3]2
arr[right=6]2
right--=5
right--=4
right--=3
交换前arr[right=3]2
交换后arr[left=3]2
交换前arr[left=3]2
交换后arr[right=3]2
最后:arr[left=3]2
最后返回left:3
进入partition后数组下标小于中位数,中间下标index:3
进入else数组下标小于中位数,中间下标index:3
arr[left=4]2
arr[right=6]2
right--=5
right--=4
交换前arr[right=4]2
交换后arr[left=4]2
交换前arr[left=4]2
交换后arr[right=4]2
最后:arr[left=4]2
最后返回left:4
进入partition后数组下标小于中位数,中间下标index:4
进入else数组下标小于中位数,中间下标index:4
arr[left=5]2
arr[right=6]2
right--=5
交换前arr[right=5]2
交换后arr[left=5]2
交换前arr[left=5]2
交换后arr[right=5]2
最后:arr[left=5]2
最后返回left:5
进入partition后数组下标小于中位数,中间下标index:5
2
或者:
先将数组进行快排,如果数组中有一个数字出现的次数超过数组长度的一半,那么快排后数组中间的数就一定是这个数字。遍历快排后的数组,如果最中间的数的个数大于数组长度的一半,则输出这个数,否则输出0;
package cglib;
import java.util.Arrays;
public class DeleteNode{
public int moreThanHalfNum(int [] array) {
if(array.length == 0 || array == null){
return 0;
}
Arrays.sort(array);
int mid = array[array.length/2];
int j = 0;
for (int i : array){
if (i == mid){
j++;
}
}
return j > array.length/2 ? mid : 0;
}
public static void main(String[] args){
int[] arr= {1,2,3,3,2,5,4,2,2,2,2};
DeleteNode test = new DeleteNode();
System.out.println(test.moreThanHalfNum(arr));
}
}
输出2
解法二:根据数组的特点找出O(n)的算法:
接下来我们从另外一个角度来解决这个问题。数组中有一个数字出现的次 数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现的次数的和还要多。因此我们可以遍历数组的时候保存两个值:一个是数组中的一个数字,一个 是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;如果下一个数字和我们之前保存的数字不同,则次数减1.如 果次数为0,我们需要保存下一个数字,并把次数设为1.由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次 把次数设为1时对应的数字。
package cglib;
public class DeleteNode{
public int moreThanHalfNum2(int[] arr){
if(arr.length == 0)
return -1;
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(arr[i] == result)
times++;
else
times--;
}
if(!checkMoreThanHalf(arr,result))
result = -1;
return result;
}
//验证是否存在
public boolean checkMoreThanHalf(int[] arr,int number){
int times = 0;
for(int i = 0;i<arr.length;i++){
if(arr[i] == number)
times ++;
}
boolean isMoreThanHalf = true;
if(times *2 <= arr.length){
isMoreThanHalf = false;
}
return isMoreThanHalf;
}
public static void main(String[] args){
int[] arr= {1,2,3,3,2,5,4,2,2,2,2};
DeleteNode test = new DeleteNode();
System.out.println(test.moreThanHalfNum2(arr));
}
}
输出
2