【剑指offer】面试题39:数组中出现次数超过一半的数字

题目

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

思路

解法1
最直观的解法,用哈希表来做,数字映射出现的次数
时间复杂度O(n),空间复杂度O(n),数组不会乱序

解法2
也是很直观的解法,将数组排序后,遍历查看是否有符合条件的数字
时间复杂度O(n),空间复杂度O(1),数组会乱序

解法3
题目中透露了一个信息:如果把这个数组排序,那么排序之后位于数组中间的数字一定就是那个出现次数超过数组长度一半的数字
使用快排中的partition算法,时间复杂度为O(n),可以得到数组中任意第k大的数字。
得到第k大的数字后,这里要反过来检查这个第k大的数字是否真的出现次数超过数组长度一半
时间复杂度O(n),空间复杂度O(1),数组会乱序

解法4
根据数组特点找出时间复杂度为O(n)的算法
数组中有一个数字出现的次数超过数组长度的一半,说明它出现的次数比其他所有数字出现的次数和还要多。

因此我们可以使用以下解法:
1.保存两个值:一个是数组中的一个数字val,一个是次数count。
2.当我们遍历到下一个数字时,
 if count=0,则val设置为当前数字,count设置为1;
 else if 这个数字和val相同,则count+1;
 else count-1;

由于我们要找的数字出现的次数比其他所有数字出现的次数和多,
那么要找的数字肯定是最后一次把数组设置为1时对应的数字,即val可能就是我们要的值
之所以说是可能,是因为还要验证val出现的次数是否真正超过了数组长度的一半

解法1

/**
 * 题目:
 * 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
 * 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。
 * 由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
 * 
 * 解法1:使用哈希表
 * 时间复杂度O(n)
 * 空间复杂度O(n)
 * 数组不会乱序
 * 
 * @author peige
 */
public class _39_MoreThanHalfNumber_01 {

    public static int MoreThanHalfNum_Solution(int [] array) {
        if(array == null || array.length == 0)
            return 0;
        if(array.length == 1)
            return array[0];
        int num = array.length / 2 + 1;
        Map<Integer, Integer> map = new HashMap<>();
        for(int i : array) {
            if(map.containsKey(i)) {
                int n = map.get(i) + 1;
                if(n >= num)
                    return i;
                map.put(i, n);
            }
            else
                map.put(i, 1);
        }
        return 0;
    }
}

解法2

/**
 * 题目:
 * 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
 * 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。
 * 由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
 * 
 * 解法2:排序后统计
 * 时间复杂度O(nlogn)
 * 空间复杂度O(1)
 * 数组会乱序
 * 
 * 可以直接调用Array.sort,但是复习一下快排,这里就直接自己写了
 * 
 * @author peige
 */
public class _39_MoreThanHalfNumber_02 {

    public int MoreThanHalfNum_Solution(int [] array) {
        if(array == null || array.length == 0)
            return 0;
        if(array.length == 1)
            return array[0];
        quickSort(array);
        int num = array.length / 2 + 1;
        int number = array[0];
        int count = 0;

        for(int i : array) {
            if(i == number) {
                ++count;
                if(count >= num)
                    return i;
            }
            else {
                number = array[i];
                count = 1;
            }
        }
        return 0;
    }

    private void quickSort(int[] array) {
        quickSort(array, 0, array.length - 1);
    }

    private void quickSort(int[] array, int low, int high) {
        if(low >= high)
            return;
        int p = partition(array, low, high);
        quickSort(array, low, p - 1);
        quickSort(array, p + 1, high);
    }

    private int partition(int[] array, int low, int high) {
        int val = array[low];
        int i = low + 1;
        int j = high;
        while(true) {
            while(i <= high && array[i] < val) 
                ++i;
            while(j >= low && array[j] > val) 
                --j;
            if(i > j) 
                break;
            swap(array, i++, j--);
        }
        swap(array, low, j);
        return j;
    }

    private void swap(int[] array, int indexA, int indexB) {
        int t = array[indexA];
        array[indexA] = array[indexB];
        array[indexB] = t;
    }

}

解法3

/**
 * 题目:
 * 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
 * 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。
 * 由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
 * 
 * 解法3:基于Partition函数的时间复杂度为O(n)的算法
 * 时间复杂度O(n)
 * 空间复杂度O(1)
 * 数组会乱序
 * 
 * 思路:
 * 如果把这个数组排序,那么排序之后位于数组中间的数字
 * 一定就是那个出现次数超过数组长度一般的数字
 * 
 * @author peige
 */
public class _39_MoreThanHalfNumber_03 {

    public int MoreThanHalfNum_Solution(int [] array) {
        if(array == null || array.length == 0)
            return 0;
        int midIndex = array.length / 2;
        int start = 0;
        int end = array.length - 1;
        int p = partition(array, start, end);
        while(p != midIndex) {
            if(p > midIndex)
                end = p - 1;
            else if(p < midIndex)
                start = p + 1;
            p = partition(array, start, end);
        }
        if(checkMoreThanHalf(array, array[p]))
            return array[p];
        return 0;
    }

    private boolean checkMoreThanHalf(int[] array, int val) {
        int count = 0;
        for(int i : array) {
            if(i == val)
                ++count;
        }
        return count > array.length / 2;
    }

    private int partition(int[] array, int low, int high) {
        int val = array[low];
        int i = low + 1;
        int j = high;
        while(true) {
            while(i <= high && array[i] < val)
                ++i;
            while(j >= low && array[j] > val)
                --j;
            if(i > j)
                break;
            swap(array, i++, j--);
        }
        swap(array, low, j);
        return j;
    }

    private void swap(int[] array, int indexA, int indexB) {
        int t = array[indexA];
        array[indexA] = array[indexB];
        array[indexB] = t;
    }
}

解法4

/**
 * 题目:
 * 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
 * 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。
 * 由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
 * 
 * 解法4:根据数组特点找出时间复杂度为O(n)的算法
 * 数组中有一个数字出现的次数超过数组长度的一半,说明它出现的次数比其他所有数字出现的次数和还要多。
 * 
 * 因此我们可以使用以下解法:
 * 1.保存两个值:一个是数组中的一个数字val,一个是次数count。
 * 2.当我们遍历到下一个数字时,
 *   if count=0,则val设置为当前数字,count设置为1;
 *   else if 这个数字和val相同,则count+1;
 *   else count-1;
 *   
 * 由于我们要找的数字出现的次数比其他所有数字出现的次数和多,
 * 那么要找的数字肯定是最后一次把数组设置为1时对应的数字,即val可能就是我们要的值
 * 之所以说是可能,是因为还要验证val出现的次数是否真正超过了数组长度的一半
 * 
 * @author peige
 */
public class _39_MoreThanHalfNumber_04 {

    public int MoreThanHalfNum_Solution(int [] array) {
        if(array == null || array.length == 0)
            return 0;
        int val = 0;
        int count = 0;
        for(int i : array) {
            if(count == 0) {
                val = i;
                count = 1;
            }
            else if(val == i)
                ++count;
            else
                --count;
        }
        if(checkMoreThanHalf(array, val))
            return val;
        return 0;
    }

    private boolean checkMoreThanHalf(int[] array, int val) {
        int count = 0;
        for(int i : array) {
            if(i == val)
                ++count;
        }
        return count > array.length / 2;
    }
}

测试(这里只放解法3的,用例都一样)

public class _39_03_Test {

    public static void main(String[] args) {
        test1();
        test2();
        test3();
    }

    /**
     * 功能测试
     * 1.存在
     * 2.不存在
     */
    private static void test1() {
        _39_MoreThanHalfNumber_03 m = new _39_MoreThanHalfNumber_03();
        int res = m.MoreThanHalfNum_Solution(new int[] {2,4,1,3,4,4,1,4,4,6,4});
        MyTest.equal(res, 4);

        res = m.MoreThanHalfNum_Solution(new int[] {2,4,1,3,4,4,1,4,4,6});
        MyTest.equal(res, 0);
    }

    /**
     * 边界测试
     * 1.只有一个元素
     */
    private static void test2() {
        _39_MoreThanHalfNumber_03 m = new _39_MoreThanHalfNumber_03();
        int res = m.MoreThanHalfNum_Solution(new int[] {2});
        MyTest.equal(res, 2);
    }

    /**
     * 极端测试
     * 1.输入null
     * 2.输入数组长度为0
     */
    private static void test3() {
        _39_MoreThanHalfNumber_03 m = new _39_MoreThanHalfNumber_03();
        int res = m.MoreThanHalfNum_Solution(null);
        MyTest.equal(res, 0);

        res = m.MoreThanHalfNum_Solution(new int[0]);
        MyTest.equal(res, 0);
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值