剑指Offer 3 | Leetcode 442 数组中重复的数字系列(Python)

目录

剑指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.csdn.net/qq_39315740/article/details/89099796


2019.7.15 补充LeetCode 136
2019.7.31 修改部分表述


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值