5.14 力扣 利用数组索引判断元素是否存在 并查集DSU

442. 数组中重复的数据
在这里插入图片描述
法一:通过索引号排序,比如数字4放到索引3的位置,最后找排序后数组,与索引号没有相差1便是重复元素,从前到后依次将数据放到合适的索引

nums[nums[i]-1], nums[i] = nums[i], nums[nums[i]-1] 和 
nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i]不一样
class Solution:
    def findDuplicates(self, nums: List[int]) -> List[int]:
        n=len(nums)
        res=[]
        for i in range(n):
            while nums[i]!=nums[nums[i]-1]:
                nums[nums[i]-1],nums[i]=nums[i],nums[nums[i]-1]
        for idx,val in enumerate(nums):
            if idx !=val-1:
                res.append(val)
        return res

例:
nums=[4,3,2,7,8,2,3,1]
排序后的数组
在这里插入图片描述
法二:因为有元素出现两次,则找到数字 i 时,将索引为 i-1 处的数字翻转为负数。
如果位置i-1 上的数字已经为负,则i是出现两次的数字。依然是利用索引

class Solution:
    def findDuplicates(self, nums: List[int]) -> List[int]:
        n=len(nums)
        res=[]
        for i in range(n):
            idx=abs(nums[i])-1
            if nums[idx]<0:
                res.append(idx+1)
            nums[idx]=-nums[idx]
        return res

448 找到所有数组中消失的数字
在这里插入图片描述

class Solution:
    def findDisappearedNumbers(self, nums: List[int]) -> List[int]:
        n=len(nums)
        res=[]
        for i in range(n):
            while nums[nums[i]-1]!=nums[i]:
                nums[nums[i]-1],nums[i]=nums[i],nums[nums[i]-1]
        for idx,val in enumerate(nums):
            if val-1!=idx:
                res.append(idx+1)
        return res

遍历到数字i时,如果索引i-1处数字大于0,则将索引i-1处数字翻转,如果已经小于0,说明已经遍历过数字i,
重新遍历数组,数字大于0处的索引值+1即为不存在的数字

class Solution:
    def findDisappearedNumbers(self, nums: List[int]) -> List[int]:
        n=len(nums)
        res=[]
        for i in range(n):
            idx=abs(nums[i])-1
            if nums[idx]>0:
                nums[idx]=-nums[idx]
        for i in range(n):
            if nums[i]>0:
                res.append(i+1)
        return res

41 缺失的第一个正数
在这里插入图片描述

class Solution:
    def firstMissingPositive(self, nums: List[int]) -> int:
        n=len(nums)
        if 1 not in nums:
            return 1
        if n==1:
            return 2
        # 负数和超过N的数不需要考虑,因为是最小正整数
        for i in range(n):
            if nums[i]<=0 or nums[i]>n:
                nums[i]=1
        for i in range(n):
            idx=abs(nums[i])-1
            if nums[idx]>0:
                nums[idx]=-nums[idx]
        for i in range(n):
            if nums[i]>0:
                return i+1
        #遍历完无结果,说明数组长度内的正数数字不缺,返回n+1
        return n+1

851 喧闹和富有
在这里插入图片描述
在这里插入图片描述

class Solution:
    def loudAndRich(self, richer: List[List[int]], quiet: List[int]) -> List[int]:
        queue=defaultdict(list)
        n=len(quiet)
        for i,v in richer:
            queue[v].append(i)
        answer=[0]*n
        def dfs(node):
            # 如果已经知道node点的子树和该点中最安静的,则直接返回这个最安静的点
            if answer[node]:
                return answer[node]
            if not answer[node]:
                answer[node]=node
            for child in queue[node]:
                #子树中最安静的
                cur=dfs(child)
                #子树中最安静的人可以是 person 本身,或者是 person 的孩子结点的某个子树中最安静的人。
                if quiet[cur]<quiet[answer[node]]:
                    answer[node]=cur
            #返回钱数大于node点钱数的最安静的那个点
            return answer[node]
           #需要知道每个点的,拥有钱数多于等于该点的,最安静的点
        for i in range(n):
            dfs(i)
        return answer

507 完美数
在这里插入图片描述

import math
class Solution:
    def checkPerfectNumber(self, num: int) -> bool:
        if num<=0:
            return False
        sum=0
        for i in range(1,int(num**0.5)+1):
            if num%i==0:
                sum+=i
                if i*i !=num:
                    sum+=num/i
        return sum==2*num

728 自除数
在这里插入图片描述
在这里插入图片描述
结果应该不算空间复杂度

class Solution:
    def selfDividingNumbers(self, left: int, right: int) -> List[int]:
        def is_divid(n):
            for d in str(n):
                if d=='0' or n%int(d)>0 :
                    return False
            return True
        res=[]
        for i in range(left,right+1):
            if is_divid(i):
                res.append(i)
        return res

794. 有效的井字游戏
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution:
    def validTicTacToe(self, board: List[str]) -> bool:
        x_count,o_count=0,0
        for bo in board:
            x_count+=bo.count('X')
            o_count+=bo.count('O')
        def winner(board,player):
            for i in range(3):
                #有一行有三个相同字符
                if all(board[i][j]==player for j in range(3)):
                    return True
                #有一列有三个相同字符
                if all(board[j][i]==player for j in range(3)):
                    return True
            #对角线存在三个相同字符
            if player==board[0][0] and player==board[1][1] and player==board[2][2]:
                return True
            if player==board[0][2] and player==board[1][1] and player==board[2][0]:
                return True
        #O的数目和x相等或者差1
        if o_count not in {x_count-1,x_count}:
            return False
        #如果第一个人获胜,则X数目比O多一
        if winner(board,'X') and x_count-1!=o_count:
            return False
        #如果第二个人获胜,则X数目与O相同
        if winner(board,'O') and x_count!=o_count:
            return False
        return True

1286 字母组合迭代器
在这里插入图片描述

class CombinationIterator:
    def __init__(self, characters: str, combinationLength: int):
        self.queue=[]
        #获得字符串的排列组合
        def combination(num,k):
            if k==0:
                return ['']
            substr=[]
            for i in range(len(num)):
                for j in combination(num[i+1:],k-1):
                    substr+=[num[i]+j]
            return substr
        self.queue=combination(characters,combinationLength)
    def next(self) -> str:
        if self.queue:
            return self.queue.pop(0)
    def hasNext(self) -> bool:
        return self.queue !=[]

回溯法:

    def __init__(self, characters: str, combinationLength: int):
        # self.com = itertools.permutations(characters,combinationLength)
        # self.stack = []
        self.com = collections.deque()
        def helper(characters,res=''):
            if len(res)==combinationLength:
                self.com.append(res)
            
            for i in range(len(characters)):
                helper(characters[i+1:],res+characters[i])
        helper(characters)

622 设计循环队列
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
任何数据结构中都不存在环形结构,但是可以使用一维 数组 模拟,通过操作数组的索引构建一个 虚拟 的环。很多复杂数据结构都可以通过数组实现。
在这里插入图片描述

class MyCircularQueue:
    def __init__(self, k: int):
        self.queue=[0]*k
        #队首索引
        self.headindex=0
        #队列长度
        self.count=0
        self.k=k

    def enQueue(self, value: int) -> bool:
        if self.count==self.k:
            return False
        else:
        #队尾添加元素
            self.queue[(self.headindex+self.count)%self.k]=value
            self.count+=1
            return True

    def deQueue(self) -> bool:
        if self.count==0:
            return False
        else:
        #队首索引后移,因为先进先出,所以删除队首元素
            self.headindex=(self.headindex+1)%self.k
            self.count-=1
            return True

    def Front(self) -> int:
        if self.count==0:
            return -1
        else:
            return self.queue[self.headindex]
        

    def Rear(self) -> int:
        if self.count==0:
            return -1
        else:
        #队尾索引(self.headindex+self.count-1)%self.k
            return self.queue[(self.headindex+self.count-1)%self.k]
        

    def isEmpty(self) -> bool:
        return self.count==0

    def isFull(self) -> bool:
        return self.count==self.k

684. 冗余连接
在这里插入图片描述
在这里插入图片描述
并查集实现:
我们可以把相互连通的点看成一个组,如果现在查询的点对分别在不同的组中,则这个点对不连通,否则连通。
三步走:
1.初始化pre和rangks数组,pre为每个元素的父结点(上一级),ranks为每个元素作为根节点时的树的秩(树的深度),一开始设置pre的每个值为每个元素自己,设置ranks每个值为0.
2.find() 查询该元素的首级,顺便做路径压缩
3.join() 合并两个集合,按秩合并,秩小的树的根节点指向秩大的树的根节点。

————————————————
版权声明:本文为CSDN博主「AivenZ」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/AivenZhong/article/details/89148862

# 这里记录一下一种新的数据结构:并查集
def find(x, pre):
    """
    查找x的最上级(首级)
    :param x: 要查找的数
    :param pre: 每个元素的首级
    :return: 元素的上一级
    """
    root, p = x, x  # root:根节点, p:指针
    
    # 找根节点
    while root != pres[root]:
        root = pres[root]

    # 路径压缩,把每个经过的结点的上一级设为root(直接设为首级)
    while p != pres[p]:
        p, pres[p] = pres[p], root
    return root


def join(x, y, pre, ranks):
    """
    合并两个元素(合并两个集合)
    :param x: 第一个元素
    :param y: 第二个元素
    :param pre: 每个元素的上一级
    :param ranks: 每个元素作为根节点时的秩(树的深度)
    :return: None
    """
    h1, h2 = find(x, pre), find(y, pre)
    # 当两个元素不是同一组的时候才合并
    # 按秩合并
    if h1 != h2:
        if ranks[h1] < ranks[h2]:
            pre[h1] = h2
        else:
            pre[h2] = h1
            if ranks[h1] == ranks[h2]:
                ranks[h1] += 1

# 结点数
n = 10
# 边数据
data = [[0, 9], [9, 3], [1, 2], [2, 8], [4, 5], [6, 7], [0, 5], [6, 8]]
# pre一开始设置每个元素的上一级是自己,ranks一开始设置每个元素的秩为0
pre, ranks = [i for i in range(n)], [0] * n
for edge in data:
    join(edge[0], edge[1], pre, ranks)
print('pre:\t', pre)
print('ranks:\t', ranks)
print('idx:\t', list(range(n)))
# pre:	 [0, 6, 1, 0, 0, 4, 6, 6, 1, 0]
# ranks:	 [2, 1, 0, 0, 1, 0, 2, 0, 0, 0]
# idx:	 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

并查集的初始化就是每个点只属于自己标记的集合,即p[x]=x。作为索引的x是指当前节点,作为值的x是指当前节点所在集合的父节点(即地址指针)
遍历边,如果两个点集合不同,那就合并进同一个集合
如果遍历边的过程中,发现两个点已经加进过之前的集合了,那就说明成环了,这时就可以输出了。

class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        n=len(edges)
        p=[[i] for i in range(n+1)]#并查集初始化,每个点只属于自己标记的集合
        for x,y in edges:
            if p[x]!=p[y]:#如果两个集合地址不一致,根据权重合并
                if len(p[x])<len(p[y]):
                    x,y=y,x
                p[x].extend(p[y])
                for z in p[y]:#修改合并进去元素的地址指针
                    p[z]=p[x]
            else:
                return [x,y] #存在重复,说明之前已经连通,说明成环,这条边多余可以删除

721 账户合并
在这里插入图片描述
在这里插入图片描述
每个账户由名称和邮箱地址组成,不同账户中,只要有相同的一个邮箱地址,则合并为一个账户
所以可以把账户看作一个点,只要两个账户有相同的邮箱地址,则将两个点连起来

class union:
    def __init__(self,n):
        #记录上级
        self.pre=[i for i in range(n)]
        #记录权重
        self.rank=[1 for _ in range(n)]
    #查找上级
    def find(self,x):
        while x!=self.pre[x]:
            x=self.pre[x]
        return x
    #合并集合
    def union(self,p,q):
        parent_p=self.find(p)
        parent_q=self.find(q)
        if parent_p!=parent_q:
        #将权重小的并到权重大的里面
            if self.rank[parent_p]<self.rank[parent_q]:
                self.pre[parent_p]=parent_q
            elif self.rank[parent_p]>self.rank[parent_q]:
                self.pre[parent_q]=parent_p
            else:
                self.pre[parent_q]=parent_p
                self.rank[parent_p]+=1

class Solution:
    def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]:
        # 记录已经出现过的邮箱地址:账户
        lookup = {}
        n = len(accounts)
        un = union(n)
        # 第一步,找到相关联的账户,并使用并查集记录
        # 有一个相同的邮箱,则可以合并为同一个账户
        # 用lookup记录已经出现过的邮箱对应的账户(索引),以便于邮箱再次出现时合并两个账户
        for idx,account in enumerate(accounts):
            for email in account[1:]:
                if email in lookup:
                    un.union(idx,lookup[email])
                else:
                    #如果邮箱未出现过,则账户就是他的索引
                    lookup[email]=idx
        # 将相关联账户的邮箱地址合并
        join_count=defaultdict(set)
        for i in range(n):
            root=un.find(i)
            for em in accounts[i][1:]:
                #将属于同一个账户的所有邮箱合并起来
                join_count[root].add(em)
        res=[]
        #输出结果
        for key,value in join_count.items():
            res.append([accounts[key][0]]+list(sorted(value)))
        return res
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值