leetcode 剑指 Offer 03. 数组中重复的数字
剑指 Offer 03. 数组中重复的数字
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3] 输出:2 或 3
限制:
2 <= n <= 100000
解法一:计数排序
思路:
利用计数排序,在统计次数的时候判断出现次数是否大于1,如果大于1直接返回该数
1 class Solution { 2 public int findRepeatNumber(int[] nums) { 3 int len = nums.length; 4 // 计数排序,如果数量大于1则返回 5 int[] counts = new int[len]; 6 for(int i = 0; i < len; i++){ 7 counts[nums[i]]++; 8 if(counts[nums[i]] > 1){ 9 return nums[i]; 10 } 11 } 12 return -1; 13 } 14 }
leetcode 运行时间为2ms, 空间为45.9MB
复杂度分析:
时间复杂度:计数排序,但是不需要统计完,所以平均时间复杂度为O(n/2)
空间复杂度:需要创建一个和nums数组等大的数组,所以空间复杂度为O(n)
解法二:
在元素不重复的情况下,但数组有序时,每个元素所在的位置和这个元素的值相等。所以我们遍历数组,把当前元素放到正确的位置,也就是以这个元素为下标的位置,把当前元素和他正确的位置的元素进行交换。重新安排位置的过程中,肯定就会有元素重复的情况。返回即可
class Solution { public int findRepeatNumber(int[] nums) { int len = nums.length; for(int i = 0; i < len; i++){ // 如果当前元素不在正确的位置 if(nums[i] != i){ // 如果重复,直接返回 if(nums[i] == nums[nums[i]]){ return nums[i]; } // 把当前元素放到正确的位置 int temp = nums[nums[i]]; nums[nums[i]] = nums[i]; nums[i] = temp; } } return nums[len - 1]; } }
leetcode运行时间为0ms, 空间为48.8MB
复杂度分析:
时间复杂度:最多完整的遍历一次数组,平均复杂度为O(n/2)
空间复杂度:O(1)
golang 版本
func findRepeatNumber(nums []int) int { // 在元素不重复且有序时,每个元素都跟它的下标相等,如果数组有序但存在重复元素,那么必定导致多个值因为抢占同一个下标位置而冲突,所以如果我们遍历元素的过程中,把当前元素放到它正确的位置, // 因为遍历数组的过程中,每次必定会把一个元素放到正确的位置,遍历完成后,刚好把 n 个元素都放到了正确的位置,由于恰好 n 等于数组长度,如果数组不存在重复,那么此时应该是有序的,如果数组存在重复,那么在遍历过程中必行存在正确位置已经有正确值的情况发生。 // 那么在数组优化的过程中,肯定会出现某元素对应的正确位置已经有正确值的情况,这时就是找到了重复元素 for i, val := range nums { // 如果当前元素恰好在正确位置,那么跳过,如果当前元素不在正确位置,那么判断它正确位置是否存在正确的值 if i != val { if val == nums[val] { return val } nums[i], nums[val] = nums[val], val // 如果正确位置存在正确的值,说明重复,否则交换当前元素和此时正确位置的值,把当前元素放到正确位置 } } return nums[len(nums) - 1] }
解法三:利用HashSet
判断当前元素是否存在于HashSet,如果存在说明是重复元素,直接返回,否则把这个元素存入HashSet
1 class Solution { 2 public int findRepeatNumber(int[] nums) { 3 int len = nums.length; 4 5 HashSet<Integer> hs = new HashSet<Integer>(); 6 for(int num : nums){ 7 if(hs.contains(num)){ 8 return num; 9 } 10 hs.add(num); 11 } 12 return -1; 13 } 14 }
leetcode运行时间为10ms, 空间为47.6MB, 可以看到这个运行时间远大于上面两种方法,所以知道对HastSet虽然也是以hash函数来存取元素的,但是效率远低于直接操作数组。
复杂度分析:
时间复杂度:最多完整的遍历一次数组,平均复杂度为O(n/2)
空间复杂度:hashset中存储的元素个数,O(n)
下面这个解法有问题
本来是打算把遇到的每个数,对应的下标的元素置为负数,如果某个数重复,那么对应下标的元素第二次碰到时就会发现是负数,从而把下标返回,但是发现有边界 case,如果重复的元素对应的下标的值为0,那么第一次不会被置为负数,第二次也因为识别到 0 不是负数而跳过,从而错过了正确结果。
边界 case 举例:[1, 0, 1, 4, 2, 5, 3],输出 -1,实际结果为 1。
func findRepeatNumber(nums []int) int { // 遍历数组,把当前值对应的下标对应的元素标记为负数,如果当前值为负数,则对当前值取绝对值 for _, val := range nums { if val < 0 { val *= -1 } if nums[val] < 0 { // 判断当前值对应的下标对应的元素是否为负数,如果为负数,直接返回当前值,说明之前碰到过一次 return val } else { nums[val] *= -1 // 否则把对应下标的元素置为负数 } } return nums[0] }