目录
剑指Offer 3.1 找出数组中重复的数字
剑指Offer 3.2 不修改数组找出重复的数字
LeetCode 442.数组中重复的数据
只出现一次的数字系列
剑指Offer 3.1 找出数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。
示例:
输出长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或3
题目解读
提取题目中的关键信息:
- 长度为n的数组,数字范围在0到n-1内,也就是数字的取值范围不会大于index范围;
- 不知道几个数字重复,也不知道每个数字重复几次,只要求返回任意一个重复的数字。
排序搜索
将输入的数组先排序,然后从头到尾进行扫描。
根据排序方法的不同,时间复杂度不太一样。一般情况下,排序一个长度为n的数组需要O(nlogn)的时间。
def duplicate(self, numbers, duplication):
# write code here
if len(numbers) < 2:
return False
# timesort排序
numbers.sort()
for i in range(1,len(numbers)):
if numbers[i] == numbers[i-1]:
duplication[0] = numbers[i]
return True
return False
哈希表
这个算法的时间复杂度是O(n),但提高时间效率的代价是需要O(n)的辅助空间。
def duplicate(self, numbers, duplication):
# write code here
if len(numbers) < 2:
return False
dic = {}
for i in range(len(numbers)):
# 没见过的数字存进字典里
if numbers[i] not in dic:
dic[numbers[i]] = 1
# 遇到重复的数字,返回
else:
duplication[0] = numbers[i]
return True
return False
下标交换
从头到尾扫描数组中的每个数字,首先判断当前数字m是否等于下标i;
如果是,则扫描下一个数字,如果不是,则比较以这个数字为下标的数字n;
如果n与m相同,则找到重复的数字,如果n与m不同,则交换两者的位置。
结合一个例子来说明:
原数组numbers为{2,3,1,0,2,5,3};
i从0开始计数,numbers[i]为2,number[i]与i不相等,因此比较numbers[numbers[i]]和numbers[i];
数字1和数字2不相等,因此交换位置,数组为{1,3,2,0,2,5,3};
同理,数字1和数字3不相等,继续交换为{3,1,2,0,2,5,3};
数字3和数字0不相等,交换为{0,1,2,3,2,5,3};
这时前4个数字都与下标相等,我们就判断下标为5的数字2,数字2与下标为2的数字相等,找到重复。
这个算法时间复杂度O(n),空间复杂度O(1),不需要额外分配内存。
def duplicate(self, numbers, duplication):
# 从头到尾扫描数字
for i in range(len(numbers)):
# 数字等于下标时跳过
while numbers[i] != i:
# 两个位置上的数字相等,找到重复
if numbers[numbers[i]] == numbers[i]:
duplication[0] = numbers[i]
return Ture
else:
# 两个位置上的数字不等,交换其位置
numbers[i], numbers[numbers[i]] = numbers[numbers[i]], numbers[i]
return False
剑指Offer 3.2 不修改数组找出重复的数字
在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至少有一个数字是重复的。 请找出数组中任意一个重复的数字,但不能修改输入的数组。
示例:
输出长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或3
题目解读
这题和上题类似,但题目要求不能修改输入的数组。
辅助数组
思路是创建长度为n+1的辅助数组,然后将原数组的数字逐一负责到辅助数组。原数组的数字m就复制到下标为m的位置,就能判断哪个数字是重复的。
这个算法的时间复杂度为O(n),空间复杂度为O(n)。
def duplication(nums):
# 这里用集合比较好,可能有多个重复的数字
res = set()
temp = len(nums) * [0]
for i in range(len(nums)):
if temp[nums[i]] == 0:
temp[nums[i]] = nums[i]
else:
res.add(nums[i])
return list(res)
if __name__ == "__main__":
nums = [2,3,5,4,3,2,6,7,2]
print(duplication(nums))
二分查找
我们知道数组的长度,因此可以取中间的数字m每次将数组一分为2,前一半为1 ~ m,后一半为m+1 ~ n。如果1 ~ m的长度超过m,证明重复在这一部分,反之,重复在另一部分。
这种方法函数countRange()将被调用O(logn)次,每次需要O(n)的时间,因此总的时间复杂度为O(nlogn),空间复杂度为O(1)。相当于以时间换空间。
# 计算处于范围内的数字有多少个
def countRange(nums, start, end):
if not nums:
return 0
count = 0
for i in range(len(nums)):
if nums[i] >= start and nums[i] <= end:
count += 1
return count
def getDuplication(nums):
start = 1
end = len(nums) - 1
while start <= end:
mid = (start + end) //2
count = countRange(nums, start, mid)
if end == start and count > 1:
return start
# 判断个数
if count > mid:
end = mid
else:
start = mid + 1
if __name__ == "__main__":
nums = [2,3,5,4,3,2,6,7,2]
print(getDuplication(nums))
LeetCode 442.数组中重复的数据
给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。找到所有出现两次的元素。
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[2,3]
题目解读
这题和上两题的都类似,区别在于:
- 规定了元素出现的次数,有些元素只出现两次而其他只出现一次;
- 要求找到所有出现两次的元素,而不是任意一个。
根据下标取反
这种方法跟第一题的第三种解法有异曲同工之妙,都是根据当前数字m的下标,去对相应的数字n作处理,达到区分出重复的效果。
结合例子说一下过程:
原数组为[4,3,2,7,8,2,3,1]
第一个数字为4,以4-1=3为下标的数字是7,对7取反,数组为[4,3,2,-7,8,2,3,1]
第二个数字为3,以3-1=2为下标的数字是2,对2取反,数组为[4,3,-2,-7,8,2,3,1]
第三个数字为-2,以abs(-2)-1=1为下标的数字是3,对3取反,数组为[4,-3,-2,-7,8,2,3,1]
第四个数字为7,以7-1=6为下标的数字是3,对3取反,数组为[4,-3,-2,7,8,2,-3,1]
第五个数字为8,以8-1=7为下标的数字是1,对1取反,数组为[4,-3,-2,7,8,2,-3,-1]
第六个数字为2,以2-1=1为下标的数字是-3,这个数字为负值,因此可以判断2是重复的;
第七个数字为-3,以abs(-3) -1=2为下标的数字是-2,这个数字夜为负值,因此可以判断3是重复的;
第八个数字为-1,以abs(-1) -1=0为下标的数字是4,对4取反,数组为[-4,-3,-2,7,8,2,-3,-1];
注意:
- 每次对下标减1是由数组取值范围决定的,保证不会越界,而且维持逻辑关系;
- 考察数组下标时,要对取反后的数字取绝对值,不然逻辑就会出错。
这个算法时间复杂度为O(n),空间复杂度为O(1)。
def findDuplicates(self, nums: List[int]) -> List[int]:
if len(nums) < 2:
return []
res = []
for i in range(len(nums)):
# 判断是否为负,为负表示这个数字已经被操作过了,有重复
if nums[abs(nums[i]) - 1] < 0:
res.append(abs(nums[i]))
else:
# 以当前数字m为下标的数字n,进行取反
nums[abs(nums[i]) - 1] = -nums[abs(nums[i]) - 1]
return res
只出现一次的数字系列
数组里有某几个元素只出现一次,其他均出现两次。
LeetCode 136.只出现一次的数字
只有某个元素出现一次,其他元素均出现两次,找出那个只出现一次的元素。
剑指Offer 56.数组中只出现一次的两个数字
只有某两个元素出现一次,其他元素均出现两次,找出那两个只出现一次的元素。
详见:https://blog.youkuaiyun.com/qq_39315740/article/details/89099796
2019.7.15 补充LeetCode 136
2019.7.31 修改部分表述