当前位置:  首页>> 技术小册>> 数据结构与算法(下)

题目描述

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

解法

解法一

利用快排中的 partition 思想。

数组中有一个数字出现次数超过了数组长度的一半,那么排序后,数组中间的数字一定就是我们要找的数字。我们随机选一个数字,利用 partition() 函数,使得比选中数字小的数字都排在它左边,比选中数字大的数字都排在它的右边。

判断选中数字的下标 index

  • 如果 index = n/2,那么这个数字就是中位数。
  • 如果 index > n/2,那么接着在 index 的左边进行 partition。
  • 如果 index < n/2,则在 index 的右边继续进行 partition。

注意:这种方法会修改输入的数组。时间复杂度为 O(n)

  1. /**
  2. * @author bingo
  3. * @since 2018/12/6
  4. */
  5. public class Solution {
  6. /**
  7. * 查找数组中出现次数超过一次的数字
  8. *
  9. * @param array 数组
  10. * @return 返回该数,不存在则返回0
  11. */
  12. public int MoreThanHalfNum_Solution(int[] array) {
  13. if (array == null || array.length == 0) {
  14. return 0;
  15. }
  16. int n = array.length;
  17. int start = 0, end = n - 1;
  18. int mid = n >> 1;
  19. int index = partition(array, start, end);
  20. while (index != mid) {
  21. if (index > mid) {
  22. end = index - 1;
  23. } else {
  24. start = index + 1;
  25. }
  26. index = partition(array, start, end);
  27. }
  28. return isMoreThanHalf(array, array[index]) ? array[index] : 0;
  29. }
  30. /**
  31. * 快排中的 partition 方法
  32. *
  33. * @param array 数组
  34. * @param start 开始位置
  35. * @param end 结束位置
  36. * @return
  37. */
  38. private int partition(int[] array, int start, int end) {
  39. int small = start - 1;
  40. for (int i = start; i < end; ++i) {
  41. if (array[i] < array[end]) {
  42. swap(array, i, ++small);
  43. }
  44. }
  45. ++small;
  46. swap(array, small, end);
  47. return small;
  48. }
  49. private void swap(int[] array, int i, int j) {
  50. int t = array[i];
  51. array[i] = array[j];
  52. array[j] = t;
  53. }
  54. /**
  55. * 判断val元素是否真的超过数组元素个数的一半
  56. *
  57. * @param array 数组
  58. * @param val 某元素
  59. * @return boolean
  60. */
  61. private boolean isMoreThanHalf(int[] array, int val) {
  62. int cnt = 0;
  63. for (int e : array) {
  64. if (e == val) {
  65. ++cnt;
  66. }
  67. }
  68. return cnt * 2 > array.length;
  69. }
  70. }

解法二

利用多数投票算法,从头到尾遍历数组,遇到两个不一样的数就把这两个数同时除去。除去的两个数可能都不是 majority,也可能一个是 majority 另一个不是,但是因为 majority 总数大于一半,所以这么删完最后剩下的肯定是 majority。

此方法时间复杂度为 O(n),且不会改变数组。

  1. /**
  2. * @author bingo
  3. * @since 2018/12/6
  4. */
  5. public class Solution {
  6. /**
  7. * 查找数组中出现次数超过一次的数字
  8. *
  9. * @param array 数组
  10. * @return 返回该数,不存在则返回0
  11. */
  12. public int MoreThanHalfNum_Solution(int[] array) {
  13. if (array == null || array.length == 0) {
  14. return 0;
  15. }
  16. int res = array[0];
  17. int times = 1;
  18. for (int i = 1; i < array.length; ++i) {
  19. if (times == 0) {
  20. res = array[i];
  21. times = 1;
  22. } else if (array[i] == res) {
  23. ++times;
  24. } else {
  25. --times;
  26. }
  27. }
  28. return isMoreThanHalf(array, res) ? res : 0;
  29. }
  30. /**
  31. * 判断val元素是否真的超过数组元素个数的一半
  32. *
  33. * @param array 数组
  34. * @param val 某元素
  35. * @return boolean
  36. */
  37. private boolean isMoreThanHalf(int[] array, int val) {
  38. int cnt = 0;
  39. for (int e : array) {
  40. if (e == val) {
  41. ++cnt;
  42. }
  43. }
  44. return cnt * 2 > array.length;
  45. }
  46. }

测试用例

  1. 功能测试(输入的数组中存在/不存在一个出现次数超过数组长度一半的数字);
  2. 特殊输入测试(输入的数组只有一个数字;输入空指针)。

该分类下的相关小册推荐: