Day06

Day06 代码随想录刷题

知识点:哈希表、列表、字典、集合、指针

一、LeetCode242. 有效的字母异位词

1.解题思路

考点为哈希表的多种实现方式和使用方式

2.代码实现

2.1 方法一:列表哈希

根据题意仅包含小写字母,其Unicode码点值为连续的26位,可以利用列表list创建一个哈希表HashList,先利用字符串s填充,再利用字符串t消除,若出现多消少消的情况,则证明不是异位词

其中ord()可实现将变量转为Unicode码点int

class Solution:
    #解法一:列表哈希
    def isAnagram(self, s: str, t: str) -> bool:
        #数据较少且连续,可用list实现,直接初始化指定szie的哈希表
        HashList = [0]*26
        #填充哈希表
        for i in s:
            HashList[ord(i) - ord('a')] += 1
        #消除哈希表
        for i in t:
            HashList[ord(i) - ord('a')] -= 1
        #检查哈希表
        for i in range(26):
            if HashList[i] != 0:
                return False
        return True

该解法有三个循环,前两个循环是遍历字符串,时间复杂度O(n),最后一个是遍历哈希表,因为哈希表长度固定,故时间复杂度O(1),最终该解法的时间复杂度O(n)

该解法只使用了固定大小的额外空间来存储哈希表,不随输入规模的增长而增加空间消耗,所以空间复杂度 O(1)

2.2 方法二:defaultdic字典

defaultdictPython 标准库 collections 模块中的一个类,它是一个字典dict)的子类。

defaultdict接受一个工厂函数作为参数,这个factory_function可以是listsetstr等等。作用是当key不存在时,返回的是工厂函数的默认值,比如list对应[]str对应的是空字符串set对应set()int对应0

class Solution:
    #解法二:defaultdict字典
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        from collections import defaultdict
        
        s_dict = defaultdict(int)
        t_dict = defaultdict(int)
        for x in s:
            s_dict[x] += 1
        for x in t:
            t_dict[x] += 1
        return s_dict == t_dict

该解法开辟了两个字典,且分别遍历了一遍,其复杂度取决于列表nums的长度,故时间复杂度O(n)空间复杂度O(n)

2.3 方法三:Counter

CounterPython 标准库 collections 模块中的一个类,用于计数可哈希对象的出现次数。它是一个无序的容器类型,可以用于快速计数和统计元素,即自动生成哈希字典

可以通过向 Counter 构造函数传递一个可迭代对象(如列表字符串等)来创建一个 Counter 对象。 Counter 会统计该可迭代对象中每个元素出现的次数,并以字典的形式存储,其中key是元素,value是对应的出现次数。

class Solution:
    #解法三:Counter哈希字典
    def isAnagram(self, s: str, t: str) -> bool:
        from collections import Counter
        # Counter返回的是一个哈希表字典
        s_count = Counter(s)
        t_count = Counter(t)
        return s_count == t_count

该解法存在两个随nums变化的容器,创建和比较都是根据容器大小而定,所以时间复杂度O(n)空间复杂度O(n)

二、LeetCode349.两个数组的交集

1.解题思路

考察哈希表的不同创建方式和使用,考察了列表集合的特点

2.代码实现

2.1 方法一:字典哈希+列表

上一题讲到哈希字典,提到了defaultdictCounter两种方式,这2种方式都是封装好可以直接用的dict。面对大小未知,且具有可选默认值的哈希表可通过dict.get(key,default_value)来填充。

本解法不能通过HashDict[num] += 1来填充,因为第一个HashDict[num] 不存在,他没有初始化为0,而HashDict.get(key,default_value)顺带进行了初始化。

class Solution:
    #解法一:字典哈希+列表
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        #创建哈希字典
        HashDict = {}
        #用nums1填充哈希表
        for num in nums1:
            HashDict[num] = HashDict.get(num,0) + 1
        resList = []
        #将nums2的值与哈希表进行比较,重复值取出存入list后从哈希表中消除
        for num in nums2:
            if num in HashDict:
                resList.append(num)
                del HashDict[num]
        #返回结果list
        return resList

该解法创建并遍历了一个随nums1长度变化的哈希表,还遍历了nums2长度的列表,所以时间复杂度O(n);所以空间复杂度 O(n)

2.2 方法二:字典哈希+集合

上一解法通过列表进行存储交集元素,为了避免重复,每次存储后都在哈希表中进行了消除操作,方法二通过集合可避免重复,一步到位。

class Solution:
    #解法二:字典哈希+集合
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        HashDict = {}
        for num in nums1:
            HashDict[num] = HashDict.get(num,0) + 1
        #创建集合
        resSet = set()
        #将nums2的值和哈希表进行比较,重复值存入set
        for num in nums2:
            if num in HashDict:
                resSet.add(num)
        #转换格式后返回
        return list(resSet)

该解法和解法二一样,故时间复杂度O(n)空间复杂度O(n)

2.3 方法三:列表哈希

利用哈希列表实现取交集操作

class Solution:
    #解法三:列表哈希
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        #初始化哈希表
        HashList1 = [0]*1001
        HashList2 = [0]*1001
        resList = []
        #填充哈希表
        for num in nums1:
            HashList1[num] += 1
        for num in nums2:
            HashList2[num] += 1
        #取交集
        for i in range(1001):
            if HashList1[i]*HashList2[i] > 0:
                resList.append(i)
        return resList

该解法存在两个哈希列表,哈希表创建时空间复杂度O(1),遍历nums填充哈希表时时间复杂度O(n),最后取交集操作遍历了哈希表,时间复杂度O(1).虽然本题列表长度和元素范围都固定了,但是本解法还是倾向于认为时间复杂度O(n)空间复杂度O(n)

2.4 方法四:列表哈希

直接利用集合取交集的操作(&)完成任务

class Solution:
    #解法四:集合
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        return list(set(nums1) & set(nums2))

该解法将两个nums转化为集合,时间复杂度O(n),取交集操作时间复杂度最大不超过O(n),故时间复杂度O(n);创建了两个集合以及取交集三个空间,所以空间复杂度也为O(n)

三、LeetCode202.快乐数

1.解题思路

无限循环意味着重复出现同样的平方和,可用哈希表记录,考点还是哈希表的创建和使用。

2.代码实现

2.1 方法一:集合

num, r = divmod(num,10)函数输入除数num和被除数10,返回商num和余数r

class Solution:
    #解法一:集合
    def isHappy(self, n: int) -> bool:
        #创建集合
        resSet = set()
        while n not in resSet:
            #加入集合
            resSet.add(n)
            #更新n
            n = self.getNext(n)
            if n == 1:return True
        #出现重复,退出 while 循环
        return False

    #将当前num按每位取平方和
    def getNext(self, num):
        nextNum = 0
        while num:
            num, r = divmod(num,10)
            nextNum += r**2
        return nextNum

两个函数分别说明:

getNext函数的空间复杂度O(n),由于数字 n 最大有 log(n) + 1 位,所以 getNext 函数的时间复杂度可以表示为 O(log(n))

isHappy函数的空间复杂度O(k),其中 k 是最大的更新次数(k 取决于输入数字 n),时间复杂度O(log(n))

class Solution:
    #解法一的精简版:集合
    def isHappy(self, n: int) -> bool:
        #创建集合
        resSet = set()
        while n != 1:
            #出现重复,返回
            if n in resSet:return False
            #加入集合
            resSet.add(n)
            #按位求平方和,更新n
            n = sum(int(i)**2 for i in str(n))
        #出现数字1,结束循环
        return True

空间复杂度O(n)时间复杂度O(log(n))

2.2 方法二:列表

和方法一类似,几乎一样

class Solution:
    #解法二:列表
    def isHappy(self, n: int) -> bool:
        resList = []
        while n not in resList:
            #加入列表
            resList.append(n)

            #按位求平方和,更新n
            nextNum = 0
            n_str = str(n)
            for i in n_str:
                nextNum += int(i)**2
            n = nextNum

            if n == 1:return True
        return False

空间复杂度O(n)时间复杂度O(log(n)),其中 k 是更新次数的上限。

class Solution:
    #解法二的精简版:列表
    def isHappy(self, n: int) -> bool:
        #创建列表
        resList = []
        while n != 1:
            #出现重复,返回
            if n in resList:return False
            #加入列表
            resList.append(n)
            #按位求平方和,更新n
            n = sum(int(i)**2 for i in str(n))
        #出现数字1,结束循环
        return True

空间复杂度O(k)时间复杂度O(log(n)),其中 k 是更新次数的上限。

2.3 方法三:快慢指针

快乐数的按位求平方和出现循环这一情况与链表有环算法的思路类似。

class Solution:
    #解法三:快慢指针
    def isHappy(self, n: int) -> bool:
        slow = n
        fast = n
        while self.getNext(fast) != 1 and self.getNext(self.getNext(fast)) != 1:
            slow = self.getNext(slow)
            fast = self.getNext(self.getNext(fast))
            if slow == fast:
                return False
        return True
    
    def getNext(self, num):
        nextNum = 0
        while num:
            num, r = divmod(num,10)
            nextNum += r**2
        return nextNum

空间复杂度为O(1),对于这种方法,我们不需要哈希集来检测循环。指针需要常数的额外空间,时间复杂度O(log(n))

四、LeetCode1.两数之和

1.解题思路

只找两个数,在先选定一个数value的情况下,另一个目标数(target - value)就已经确定了,在哈希表中寻找该目标数,若不存在则将当前value和索引存入哈希表,否则输出结果。

2.代码实现

2.1方法一:暴力解法

从头开始遍历,知道找到目标索引

class Solution:
    #解法一:暴力解法(非常慢)
   def twoSum(self, nums: List[int], target: int) -> List[int]:
        for i in range(len(nums)):
            for j in range(i+1,len(nums)):
                if nums[i] + nums[j] == target:
                    return [i,j]

时间复杂度O(n^2)空间复杂度 O(1)

2.2方法二:字典

定义一个key为元素值,value为索引的反向哈希字典。

enumerate() 是一个内置函数,用于在迭代过程中同时获取索引和对应的元素。它接受一个可迭代对象作为参数,并返回一个由索引和元素组成的枚举对象。

class Solution:
    #解法二:字典{value:index, ...}
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        #创建字典
        resDict = {}
        #遍历nums的索引和元素值
        for index,value in enumerate(nums):
            #目标数在字典的key内,返回答案
            if target - value in resDict:
                return [resDict[target - value], index]
            #目标数不在字典key内,当前遍历的数和索引加入字典
            resDict[value] = index
        return []

时间复杂度O(n),最坏情况下可能涉及存储所有元素,故空间复杂度O(n)

2.3方法三:集合

解法三类似解法二的字典,不同之处在于定义了一个集合,用于存储元素值。最后通过对目标值求原list的index来找回去索引。

class Solution:
    #解法三:集合(记录value,根据value再回list里面找索引)
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        #创建集合
        resSet = set()
        #遍历nums的索引和元素值
        for index, value in enumerate(nums):
            temp = target - value
            #目标数在集合内,返回答案
            if temp in resSet:
                return [nums.index(temp),index]
            #目标数不在集合内,当前遍历的数加入集合
            resSet.add(value)

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

2.4方法四:双指针

其中sorted()函数使用的是Timsort算法,是一种混合了归并排序和插入排序的稳定排序算法,Timsort 在平均情况下具有 O(nlogn) 的时间复杂度,其中 n 是待排序列表的长度。

class Solution:
    #解法四:双指针
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        #列表升序处理
        nums_sorted = sorted(nums)
        left = 0
        right = len(nums) - 1
        while left < right:
            #计算当前两指针所指元素值之和
            curRes = nums_sorted[left] + nums_sorted[right]
            if curRes == target:
                #满足两数之和的要求后求解各自index
                left_index = nums.index(nums_sorted[left])
                right_index = nums.index(nums_sorted[right])
                #如果索引相同,代表list中存在2个元素值相同,且都是答案
                if left_index == right_index:
                    right_index = nums[left_index + 1:].index(nums_sorted[right]) + left_index + 1
                return [left_index, right_index]
            #不满足两束之和则指针移动
            elif curRes < target: left += 1
            else: right -= 1

该解法先将数组排序好O(nlogn),再利用双指针法遍历一遍O(n)得到结果,其实方法nums.index()也是O(n),但最终时间复杂度还是O(nlogn), 为了保存下标信息另开了一个数组nums_sorted,所以空间复杂度O(n)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值