乐趣区

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

[TOC]

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为 9 的数组 {1,2,3,2,2,2,5,4,2}。由于数字 2 在数组中出现了 5 次,超过数组长度的一半,因此输出 2。如果不存在则输出 0。

思路一:利用 map 统计每个数字出现的次数

代码

import java.util.HashMap;
import java.util.Map;
public class Solution {public int MoreThanHalfNum_Solution(int [] array) {
        int num = 0;

        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        int len =  array.length ;
        if(len == 0) return 0;
        for(int i = 0; i < len ;i++){if(map.containsKey(array[i])){int count = map.get(array[i]);
                 ++count;
                map.put(array[i],count);
            }else{map.put(array[i],1);
            }
        }
        for(Map.Entry<Integer,Integer> entry: map.entrySet()){if(entry.getValue() > (len/2)){num = entry.getKey();
            }

        }
        return num;
    }
}

总结

  • map 的遍历方式
  • 使用 map 存储出现次数的映射关系
  • map.put(key,value)

思路二:先对数组进行排序,然后取出 arr[n/2]

由于出现次数超过一半,那么最中间的数一定是那个出现次数超过一半的数,也就是中位数

我们先用排序算法对该数组排序,若用快速选择排序,时间复杂度为 O(nlogn)

思路三:基于 partition 函数的 O(n) 的算法

我们知道快速排序中,每次划分都是基于 partition 函数返回的 index 作为划分点,使得数组左边的数小于 arr[index], 数组右边的数大于 arr[index]

这里其实 partition 返回的 index 也就是数组 arr 中第 k 大数据的坐标索引

所以借鉴过来,本题需要求出现次数超过一半的数,那么实际上也就是中位数,即索引为 n / 2 的数,

就等同于求第 n / 2 大的数据

快速排序算法中我们是通过 partition 函数求出索引,本题我们是知道了索引,求具体的数据

那么我们就需要以 n / 2 为基准,使得左边的数小于 arr[n/2], 右边的数大于 arr[n/2]

实现

public class Solution {
    // 检测无效输入,如果数字的次数不超过长度的一半
    public boolean isValid(int[] arr,int numer){
        int count = 0;
        for(int i = 0; i < arr.length; i++){if(numer == arr[i]) count++;
        }
        if(count * 2 <= arr.length) return false;
        return true;
    }

    public void swap(int[] arr,int i,int j){
        int temp = 0;
        temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;

    }

    public int partition(int[] arr,int low,int high){int base = arr[high];
        int i = low - 1;
        for(int j =low; j < high; j++){
            // 注意这里是小于等于
            if(arr[j] <= base){
                i++;
                swap(arr,i,j);
            }
        }
        swap(arr,i + 1,high);

        return i+1;
    }
    public int MoreThanHalfNum_Solution(int [] array) {if(array == null || array.length <= 0)return 0;
        int index = partition(array, 0, array.length - 1);
        int middle = (array.length - 1) >> 1;
        while (index != middle){if(index < middle){index = partition(array, index + 1, array.length - 1);
            }else {index = partition(array, 0, index - 1);
            }
        }
        boolean flg = isValid(array, array[middle]);
        if(!flg) return 0;
        return array[middle];
    }
}

该思路的时间复杂度:平均时间复杂度为 O(n * logn/2) = O(nlogn), 如果一次性就能找到 n / 2 的下标,那么时间复杂度可以达到 o(n)

但是该算法的缺点在于修改了原数组

思路四:根据数组特点找出 O(n)的算法

由于有个数字出现的次数超过数组长度的一半,那么也就是说它出现的次数比其他数字出现的次数之和还要多,如果我们将数组中的数字两两同或(相同为 1,相异为 0)那么两两抵消之后,最后只会剩下这个出现出现次数超过一半的数字,那么我们如何实现这个同或抵消的过程呢

首先我们需要保存当前正在读取的数,还有当前数字出现的次数,如果下一个数字和当前数字不相同,那么当前数字的次数就减 1,

public class Solution {public boolean isValid(int[] arr,int numer){
        int count = 0;
        for(int i = 0; i < arr.length; i++){if(numer == arr[i]) count++;
        }
        if(count * 2 <= arr.length) return false;
        return true;
    }
    public int MoreThanHalfNum_Solution(int [] array) {if(array == null || array.length <= 0) return 0;
        int cur = array[0];
        int count = 1;
        int res = array[0];// 初始化为 array[0] 有可能第一个数就是返回值
        for(int i = 1; i < array.length; i++){if(cur != array[i] ){if(count != 0){count--;}else {
                    count = 1;
                    res = array[i];
                }
                cur = array[i];
            }else {count++;}

        }
        boolean flg = isValid(array, res);
        if(!flg) return 0;
        return res;
    }
}

该算法的时间复杂度为:O(n)

参考

《剑指 offer 纪念版》

退出移动版