Leetcode刷题【数组】287. 寻找重复数 类比 142. 环形链表 II

先抛开leetcode 求重复数的各种限制而言,
题目

给定一个数组,求出数组中重复的数,并打印出来
解决方案有哪些呢?

方法1. 暴力求解

思路:
第1个数,和后面 n-1个数比较,是否有重复
第2个数,和后面n-2个数比较,是否有重复
一共执行n轮
有点像 选择排序,一直拿前面的数和后面的数做比较
特殊处理:
需要有个list存储已经重复的数,下次比较如果出现在list中,直接跳过,比如数组 [1,3,2,3,5,3],如果不过滤, 会 第2个3和 第3个3 都多判断一次

时间复杂度:
O(n^2)

代码如下:

def same_number(a):
    n=len(a)
    list=[]
    if n>1:
        for i in range(0,n-1):
            for j in range(i+1,n):
                if a[j]==a[i]:
                    if a[i] in list:   # 如果下一个数在list中,代表已经被记录过,程序进入下一个for 循环,不再打印
                        continue
                    else:
                        print(a[i])
                else:
                    continue
                list.append(a[j])  # 每次重复的数存储起来
    else:
        print("数组长度不足2,肯定没有重复的")


a=[1,3,4,3,5,3,5,7,2]
b=[1]
same_number(b)

方法2: 先排序,再比较

思路:
先进行排序,然后比较相邻的数即可
不足:会改变list
时间复杂度
sort排序的 O(n.logn)


# 方法5: 先排序,再比较,会改变list,时间复杂度为排序的  O(n.logn)
def same_number_sort(a):
    n=len(a)
    if n > 1:
        a.sort()
        for i in range(0,n-1):
            if a[i+1]==a[i]:
                print(a[i])
    else:
        print("数组长度不足2,肯定没有重复的")

a=[1,3,4,5,3,5,7,2]
same_number_sort(a)

方法3: 使用哈希表

思路:
先设置一个空的集合,用来存储重复的数据
比如:第1次,set为空,a[0]不在set中,set将a[0]存储进来
第2次,如果 a[1]在 set中,说明有重复,和a[0]相同
其实就是开辟一个新空间,然后让每个数和里面存储的已经遍历过的数做比较。
为啥用set不用list?
刚开始我用的list,也可以实现效果,但是需要注意 set和list 插入和查找元素的时间复杂度的问题
for循环中,每次in操作(即查找)在list中复杂度线性是O(n),在set和dict数据结构中复杂度为O(1)
为了实现整体线性时间复杂性,我们需要能够在恒定时间内将元素插入数据结构,并查找它们,set 很好地满足这些约束
时间复杂度:
O(n)
空间复杂度:
O(n)

def same_number_hash(a):
    n=len(a)
    s=set()
    for i in range(0,n):
        if a[i] in s:   # 查找,时间复杂度O(1),如果是列表,则时间复杂度O(n)
            print("重复的数为:",a[i])
        s.add(a[i])

a=[1,3,4,3,5,5,7,2]
same_number_hash(a)

方法4: 使用二分法 (适用于只有一个重复的数时)

思路:
对要定位的“数”做二分,而不是对数组的索引做二分。要定位的“数”根据题意在 1和 n 之间,每一次二分都可以将搜索区间缩小一半。

以 [1, 2, 2, 3, 4, 5, 6, 7] 为例,一共有 88个数,每个数都在 1和 7 之间。
11和 7 的中位数是 4,遍历整个数组,统计小于 44的整数的个数,至多应该为 3个,如果超过 3个就说明重复的数存在于区间 [1,4)[1,4) (注意:左闭右开)中;否则,重复的数存在于区间 [4,7][4,7](注意:左右都是闭)中。
这里小于 4的整数有 4个(它们是 1, 2, 2, 3),因此砍掉右半区间,连中位数也砍掉。
以此类推,最后区间越来越小,直到变成 1 个整数,这个整数就是我们要找的重复的数。

二分法详细介绍:链接

时间复杂度:
O(nlog(n))
空间复杂度:
O(1)

# 方法2: 二分查找,
def same_number_binary(nums):
    left = 1
    right = len(nums) - 1
    while (left < right):
        mid = (left + right) // 2
        count = 0
        for num in nums:
            if (num <= mid):
                count += 1
        if (count <= mid):
            left = mid + 1
        else:
            right = mid
    print("二分法查找结果是:",left)


a = [1, 3, 4, 5, 3, 5, 7, 2]

方法5: 快慢指针 (比较难理解)

思路:
分为两步:
1.找到环
2.找到环的入口(即重复元素)
找环:
1.定义快慢指针
slow=0
fast=0
2.进入循环:
slow每次走一步,即slow=nums[slow]
fast每次走两步,即fast=nums[nums[fast]]
3.当slow = = fast时,退出循环。
当快慢指针相遇时,一定在环内。此时假设slow走了k步,则fast走了2k步。
找环的入口:
1.定义新的指针find=0
2.进入循环:
find每次走一步,即find=nums[find]
slow每次走一步,即slow=nums[slow]
当两指针相遇时,即find==slow,返回find
为何相遇时,找到的就是入口:
假设起点到环的入口(重复元素),需要m步。此时slow走了n+mn+m步,其中n是环的周长c的整数倍,所以相当于slow走了m步到达入口,再走了n步。所以相遇时一定是环的入口。
快慢指针计算: 链接

时间复杂度:
O(n)
空间复杂度:
O(1)

# 方法4: 快慢指针
def same_number_point(nums):
    # 把nums看成是顺序存储的链表,nums中每个元素的值是下一个链表节点的地址
    # 那么如果nums有重复值,说明链表存在环,本问题就转化为了找链表中环的入口节点,因此可以用快慢指针解决

    # 初始时,都指向链表第一个节点nums[0]
    slow, fast = 0, 0
    # 慢指针走一步,快指针走两步
    slow, fast = nums[slow], nums[nums[fast]]
    # 循环退出时,slow与fast相遇,相遇节点必在环中
    while slow != fast:
        slow, fast = nums[slow], nums[nums[fast]]
    # 让before,after分别指向链表开始节点,相遇节点
    before, after = 0, slow
    # before与after相遇时,相遇点就是环的入口节点
    while before != after:
        before, after = nums[before], nums[after]
    print("快慢指针实现的方法为:",before)
a=[1,3,4,5,3,5,7,2]
same_number_point(a)

leetcode287. 寻找重复数 要求:

只有上面的方法4和5符合时间和空间复杂度的要求

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2
示例 2:

输入: [3,1,3,4,2]
输出: 3
说明:

不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。


扩展1: leetcode 136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1
示例 2:

输入: [4,1,2,1,2]
输出: 4

解决方案1:使用列表 处理重复的数

思路:
定义一个列表,
相同的,remove 出去, 不同的,append 进来
最后list剩下的是只出现一次是数
pop和remove区别:
注意这里是remove ,remove是删除一个元素值为x,对值进行操作
pop弹出操作的是下标元素 ,对索引进行操作
时间复杂度:
O(n^2)
遍历 nums 花费 O(n)的时间,在列表中遍历判断是否存在这个数字也是O(n)
空间复杂度:
O(n)

实现方式:

# 方法1 :使用列表,相同的,remove 出去, 不同的,append 进来
def same_number_hash(a):
    list = []
    for  x in a:
        if x in list:   # 查找,时间复杂度O(1),如果是列表,则时间复杂度O(n)
            list.remove(x)   
        else:
            list.append(x)
    print("使用列表存储只出现一次的数为:",list)   #  如果只有一个重复的数,就用list.pop弹出最后一个

a=[1,3,4,3,5,5,7,2]
same_number_hash(a)

解决方案2: 使用dic或者set ,降低时间复杂度

思路和1一样,不过时间复杂度降低了
思路:
dic查找,时间复杂度O(1)
先定义dic={}
如果 元素在dic中,说明已经存在,dic移除这个key值
否则,就添加到dic中

dic的添加和删除:
添加:直接赋值 dic[x]=1 value是出现的次数,这里只有不重复的key才存进来,所以value都是1
移除:dic.pop(x)
dic操作:
1.取key值:
for c in cc.keys(): 或者 for c in cc:
2.取value值:
for c in cc.values(): 或者 for c in cc:
3.弹出最后一个(key,value) :
dic.popitem() 是(2,1)
4.弹出最后一个的key
dic.popitem()[0]

时间复杂度:
O(n)

空间复杂度:
O(n)

实现方式:

# # 方法2 :使用set或dic哈希表都可以,
def same_number_hash(a):
    dic={}
    for x in a:
        if x in dic:   # 查找,时间复杂度O(1),如果不存在这个值,这个key的value存为1
            dic.pop(x)
        else:
            dic[x]=1
    # for c in dic.keys():    如果不止一个重复的数,获取字典所有的key可以用  for c in dic.keys():   或者 for c  in  dic:
    #     print(c)

    print("使用dic弹出只出现一次的数为:",dic)   # dic.popitem()  弹出的是最后一个(key,value)  是(2,1)


a=[1,3,4,3,5,5,7,2]
same_number_hash(a)

方法3: 使用异或 (比较特殊)

思路:
使用异或 ^ ,非常巧妙
所有相同的两个数异或之后等于0.
而0与唯一一个只出现一次的数异或,等于这个数本身,
所以所有的数按顺序进行异或,得到的就是这个唯一的数,非常简单
a⊕0=a
a⊕a=0
a⊕b⊕a=b

时间复杂度:
O(n)
空间复杂度:
O(1)

实现方式:

# 方法3: 使用异或 ^   所有相同的两个数异或之后等于0.而0与唯一一个只出现一次的数异或,等于这个数本身,所以所有的数进行异或,得到的就是这个唯一的数,非常简单
def same_number_yihuo(list):
    a=0
    for i in list:
        a=a^i
    print("异或得到的结果是:",a)

a=[1,3,1,3,2,7,2]
same_number_yihuo(a)

扩展2: 142. 环形链表 II

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。 

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

方法1: set存储节点,判断节点是否已存在

思路:
使用set存储节点
如果节点已在set中,说明有环,直接返回true
不管在不在都添加当前元素,并节点后移
时间复杂度:
O(n)
空间复杂度:
O(n)

实现方法:

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

def  have_clcle(head):
    s=set()
    while head:  # 当节点不为空时
        if head in s:   # 节点在set中,说明有环
            return True
        s.add(head)
        head=head.next
    return False
# 先添加各个node节点
node1=ListNode(3)
node2=ListNode(2)
node3=ListNode(0)
node4=ListNode(-4)

# 按照要求声明节点之间的关系
node1.next=node2
node2.next=node3
node3.next=node4
node4.next=node2

result=have_clcle(node1)
print("使用哈希结构 判断是否有环的结果是:",result)

方法2: 快慢指针

特殊场景考虑:
1.没有节点
2.只有一个节点
3. while fast :
进行指针后移,快的和慢的重合,说明有环,相遇点就是环的入口

# 2.快慢指针
def have_clcle_by_pointer(head):
    # 如果头结点为空,没有元素,直接输出没有环
    if head ==None:
        return False
    slow,fast=head  # 快慢指针都指向第一个节点
    while fast: # 因为每次fast走两步,所以以fast为临界条件
        if fast.next ==None:  # 如果第一个节点的下一个节点为空,只有一个节点,直接返回false
            return False
        else:
            slow=slow.next
            fast=fast.next.next
        if slow == fast:
            return True
    return False

# 先添加各个node节点
node1=ListNode(3)
node2=ListNode(2)
node3=ListNode(0)
node4=ListNode(-4)

# 按照要求声明节点之间的关系
node1.next=node2
node2.next=node3
node3.next=node4
node4.next=node2

result1=have_clcle_by_pointer(node1)
print("使用快慢指针判断是否有环的结果是:",result1)

总结:
像这种是否有重复数 和 是否有环 属于同一类 题目,考察的是对数组或链表 值的判断比较
这一类题一般需要 遍历 for a in b
这里特殊强调:
dic和set 查找,和删除,时间复杂度都是O(1)
list 为O(n)

set(集合)
set和dict类似,也是一组key的集合,但不存储value。
由于key不能重复,所以,在set中,没有重复的key。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值