返回

庖丁解牛剑指 Offer 03 找到数组中重复的数字,窥探算法之美

前端

剑指 Offer 03:找出数组中的重复数字

剑指 Offer 03 题给定一个长度为 n 的数组 nums,其中所有数字都在 0~n-1 范围内,并且数组中某些数字是重复的。要求找出数组中任意一个重复的数字。

1. 暴力遍历(双重循环)

最直接的解法是使用暴力遍历法,通过两个 for 循环来比较数组中的每个数字与其他所有数字,一旦发现重复的数字,就立即返回。这种方法的时间复杂度为 O(n^2),空间复杂度为 O(1)。

def find_duplicate(nums):
  for i in range(len(nums)):
    for j in range(i+1, len(nums)):
      if nums[i] == nums[j]:
        return nums[i]

2. 哈希表(字典)

使用哈希表(字典)来存储数组中的数字,如果一个数字已经存在于哈希表中,则说明它是重复的,直接返回即可。这种方法的时间复杂度为 O(n),空间复杂度也为 O(n)。

def find_duplicate(nums):
  hash_table = {}
  for num in nums:
    if num in hash_table:
      return num
    else:
      hash_table[num] = True

3. 原地修改数组

我们可以通过修改数组中的元素来实现重复数字的查找。具体来说,我们将遍历数组中的每个数字,并将其作为下标来访问数组。如果当前数字作为下标访问到的数字等于当前数字,则说明当前数字是重复的,直接返回即可。这种方法的时间复杂度为 O(n),空间复杂度为 O(1)。

def find_duplicate(nums):
  for i in range(len(nums)):
    index = nums[i] % len(nums)
    if nums[index] == nums[i]:
      return nums[i]
    else:
      nums[index] = nums[i]

4. 位运算(异或)

位运算中的异或运算(XOR)具有以下性质:

  • a XOR a = 0
  • a XOR b XOR a = b
  • a XOR b = b XOR a

我们可以利用这些性质来解决本题。首先将数组中的所有数字异或一遍,然后将结果与数组中的每个数字异或一遍,如果结果为 0,则说明该数字是重复的。这种方法的时间复杂度为 O(n),空间复杂度为 O(1)。

def find_duplicate(nums):
  xor_result = 0
  for num in nums:
    xor_result ^= num

  for num in nums:
    xor_result ^= num

  return xor_result

5. 二分查找

我们还可以使用二分查找算法来解决本题。首先对数组进行排序,然后在排序后的数组中进行二分查找。如果当前二分到的数字等于二分到数字的前一个数字,则说明当前二分到的数字是重复的,直接返回即可。这种方法的时间复杂度为 O(n log n),空间复杂度为 O(1)。

def find_duplicate(nums):
  nums.sort()
  low, high = 0, len(nums) - 1

  while low <= high:
    mid = (low + high) // 2
    if nums[mid] == nums[mid-1]:
      return nums[mid]
    elif nums[mid] > nums[mid-1]:
      low = mid + 1
    else:
      high = mid - 1

  return -1

6. 性能分析

以上列出的五种方法的时间复杂度和空间复杂度如下:

方法 时间复杂度 空间复杂度
暴力遍历 O(n^2) O(1)
哈希表 O(n) O(n)
原地修改数组 O(n) O(1)
位运算 O(n) O(1)
二分查找 O(n log n) O(1)

在实际应用中,哈希表、原地修改数组和位运算方法都是比较高效的,因为它们的时间复杂度都为 O(n),而空间复杂度为 O(1)。在数组规模较大的情况下,这三种方法的性能优势会更加明显。

7. 总结

剑指 Offer 03 题的目的是查找数组中重复的数字。这个问题可以用多种方法来解决,包括暴力遍历、哈希表、原地修改数组、位运算和二分查找。这些方法的时间复杂度和空间复杂度各不相同,在实际应用中,需要根据具体的场景选择合适的方法。