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

题目描述

在一个长度为 n+1 的数组里的所有数字都在 1n 的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为 8 的数组 {2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字 2 或者 3

解法

解法一

创建长度为 n+1 的辅助数组,把原数组的元素复制到辅助数组中。如果原数组被复制的数是 m,则放到辅助数组第 m 个位置。这样很容易找出重复元素。空间复杂度为 O(n)

解法二

数组元素的取值范围是 [1, n],对该范围对半划分,分成 [1, middle], [middle+1, n]。计算数组中有多少个(count)元素落在 [1, middle] 区间内,如果 count 大于 middle-1+1,那么说明这个范围内有重复元素,否则在另一个范围内。继续对这个范围对半划分,继续统计区间内元素数量。

时间复杂度 O(n * log n),空间复杂度 O(1)

注意,此方法无法找出所有重复的元素。

  1. /**
  2. * @author bingo
  3. * @since 2018/10/27
  4. */
  5. public class Solution {
  6. /**
  7. * 不修改数组查找重复的元素,没有则返回-1
  8. * @param numbers 数组
  9. * @return 重复的元素
  10. */
  11. public int getDuplication(int[] numbers) {
  12. if (numbers == null || numbers.length < 1) {
  13. return -1;
  14. }
  15. int start = 1;
  16. int end = numbers.length - 1;
  17. while (end >= start) {
  18. int middle = start + ((end - start) >> 1);
  19. // 调用 log n 次
  20. int count = countRange(numbers, start, middle);
  21. if (start == end) {
  22. if (count > 1) {
  23. return start;
  24. }
  25. break;
  26. } else {
  27. // 无法找出所有重复的数
  28. if (count > (middle - start) + 1) {
  29. end = middle;
  30. } else {
  31. start = middle + 1;
  32. }
  33. }
  34. }
  35. return -1;
  36. }
  37. /**
  38. * 计算整个数组中有多少个数的取值在[start, end] 之间
  39. * 时间复杂度 O(n)
  40. * @param numbers 数组
  41. * @param start 左边界
  42. * @param end 右边界
  43. * @return 数量
  44. */
  45. private int countRange(int[] numbers, int start, int end) {
  46. if (numbers == null) {
  47. return 0;
  48. }
  49. int count = 0;
  50. for(int e : numbers) {
  51. if (e >= start && e <= end) {
  52. ++count;
  53. }
  54. }
  55. return count;
  56. }
  57. }

测试用例

  1. 长度为 n 的数组中包含一个或多个重复的数字;
  2. 数组中不包含重复的数字;
  3. 无效测试输入用例(输入空指针)。

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