查找

查找

查找的基本思路

第一类:查找有无--set

元素'a'是否存在,元素'a'是否存在,通常用set:集合
set只存储键,而不需要对应其相应的值。
set中的键不允许重复

第二类:查找对应关系(键值对应)--dict

元素'a'出现了几次:dict-->字典
dict中的键不允许重复

第三类:改变映射关系--map

通过将原有序列的关系映射统一表示为其他

应用

题目描述
给定两个数组,求两个数组的公共元素。

如nums1 = [1,2,2,1],nums2 = [2,2]

结果为[2]
结果中每个元素只能出现一次
出现的顺序可以是任意的

由于每个元素只出现一次,因此不需要关注每个元素出现的次数,用set的数据结构就可以了。记录元素的有和无。

把nums1记录为set,判断nums2的元素是否在set中,是的话,就放在一个公共的set中,最后公共的set就是我们要的结果。

代码如下:

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        nums1 = set(nums1)
        return set([i for i in nums2 if i in nums1])

也可以通过set的内置方法来实现,直接求set的交集:

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        set1 = set(nums1)
        set2 = set(nums2)
        return set2 & set1

题目描述

给定两个数组nums,求两个数组的交集。
-- 如nums1=[1,2,2,1],nums=[2,2]
-- 结果为[2,2]
-- 出现的顺序可以是任意的

元素出现的次数有用,那么对于存储次数就是有意义的,所以选择数据结构时,就应该选择dict的结构,通过字典的比较来判断;

记录每个元素的同时要记录这个元素的频次。

记录num1的字典,遍历nums2,比较nums1的字典的nums的key是否大于零,从而进行判断。

代码如下:

class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        from collections import Counter
        nums1_dict = Counter(nums1)
        res = []
        for num in nums2:
            if nums1_dict[num] > 0:
                # 说明找到了一个元素即在num1也在nums2
                res.append(num)
                nums1_dict[num] -= 1
        return res  

题目描述
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例1:
输入: s = "anagram", t = "nagaram"
输出: true

示例 2:
输入: s = "rat", t = "car"
输出: false

判断异位词即判断变换位置后的字符串和原来是否相同,那么不仅需要存储元素,还需要记录元素的个数。可以选择dict的数据结构,将字符串s和t都用dict存储,而后直接比较两个dict是否相同。

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        from collections import Counter
        s = Counter(s)
        t = Counter(t)
        if s == t:
            return True
        else:
            return False

题目描述
编写一个算法来判断一个数是不是“快乐数”。

一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。

示例: 
输入: 19
输出: true
解释: 
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1

此处难点在于什么时候跳出循环
将每次得到的结果存放在set中,下一次的结果在set中进行比较,若存在,则跳出循环,return false

class Solution:
    def isHappy(self, n: int) -> bool:
        already = set()
        while n != 1:
            sum = 0
            while n > 0:
                # 取n的最后一位数
                tmp = n % 10   
                sum += tmp ** 2
                # 将n的最后一位截掉
                n //= 10
            # 如果求的和在过程中出现过
            if sum in already:
                return False
            else:
                already.add(sum)
            n = sum
        return True

tips

#一般对多位数计算的套路是:
#循环从后向前取位数
while n >0 :
#取最后一位: 
tmp = n % 10
#再截掉最后一位:
n = n // 10

题目描述
给出一个模式(pattern)以及一个字符串,判断这个字符串是否符合模式

示例1:
输入: pattern = "abba", 
str = "dog cat cat dog"
输出: true

示例 2:
输入:pattern = "abba", 
str = "dog cat cat fish"
输出: false

示例 3:
输入: pattern = "aaaa", str = "dog cat cat dog"
输出: false

示例 4:
输入: pattern = "abba", str = "dog dog dog dog"
输出: false

抓住变与不变,笔者开始的思路是选择了dict的数据结构,比较count值和dict对应的keys的个数是否相同,但是这样无法判断顺序的关系,如测试用例:'aba','cat cat dog'。

那么如何能既考虑顺序,也考虑键值对应的关系呢?

抓住变与不变,变的是键,但是不变的是各个字典中,对应的相同index下的值,如dict1[index] = dict2[index],那么我们可以创建两个新的字典,遍历index对两个新的字典赋值,并比较value。其中两个字典的键分别是pattern和str里面出现的字母和单词,值则是字母和单词在pattern和str中出现的位置(索引)

还有一个思路比较巧妙,既然不同,那么可以考虑怎么让它们相同,将原来的dict通过map映射为相同的key,再比较相同key的dict是否相同。

代码实现如下:

class Solution:
    def wordPattern(self,pattern, str):
        str = str.split()
        return list(map(pattern.index,pattern)) == list(map(str.index,str))

tips

  1. 因为str是字符串,不是由单个字符组成,所以开始需要根据空格拆成字符list:
str = str.split()
  1. 通过map将字典映射为index的list:
map(pattern.index, pattern)
  1. map是通过hash存储的,不能直接进行比较,需要转换为list比较list

题目描述
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:
输入:
"tree"
输出:
"eert"

示例 2:
输入:
"cccaaa"
输出:
"cccaaa"

示例 3:
输入:
"Aabb"
输出:
"bbAa"

对于相同频次的字母,顺序任意,需要考虑大小写,返回的是字符串。

使用字典统计频率,对字典的value进行排序,最终根据key的字符串乘上value次数,组合在一起输出。

class Solution:
    def frequencySort(self, s: str) -> str:
        from collections import Counter
        s_dict = Counter(s)
        # sorted返回的是列表元组
        s = sorted(s_dict.items(), key=lambda item:item[1], reverse = True)
        # 因为返回的是字符串
        res = ''
        for key, value in s:
            res += key * value   
        return res

tips

  1. 通过sorted的方法进行value排序,对字典排序后无法直接按照字典进行返回,返回的为列表元组:
# 对value值由大到小排序
s = sorted(s_dict.items(), key=lambda item:item[1], reverse = True)

# 对key由小到大排序
s = sorted(s_dict.items(), key=lambda item:item[0])
  1. 输出为字符串的情况下,可以由字符串直接进行拼接:
# 由key和value相乘进行拼接
's' * 5 + 'd'*2

对撞指针

题目描述
给出一个整型数组nums,返回这个数组中两个数字的索引值i和j,使得nums[i] + nums[j]等于一个给定的target值,两个索引不能相等。

如:nums= [2,7,11,15],target=9 返回[0,1]

需要考虑:

  1. 开始数组是否有序;
  2. 索引从0开始计算还是1开始计算?
  3. 没有解该怎么办?
  4. 有多个解怎么办?保证有唯一解。
    分析实现
    暴力法O(n^2)
    时间复杂度为O(n^2),第一遍遍历数组,第二遍遍历当前遍历值之后的元素,其和等于target则return。
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        len_nums = len(nums)
        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)+O(nlogn)=O(n))

因为问题本身不是有序的,因此需要对原来的数组进行一次排序,排序后就可以用O(n)的指针对撞进行解决。

但是问题是,返回的是数字的索引,如果只是对数组的值进行排序,那么数组原来表示的索引的信息就会丢失,所以在排序前要进行些处理。

错误代码示例--只使用dict来进行保存:

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        record = dict()
        for index in range(len(nums)):
            record[nums[index]] = index 
        nums.sort()
        l,r = 0,len(nums)-1
        while l < r:
            if nums[l] + nums[r] == target:
                return [record[nums[l]],record[nums[r]]]
            elif nums[l] + nums[r] < target:
                l += 1
            else:
                r -= 1
当遇到相同的元素的索引问题时,会不满足条件:

如:[3,3] 6

在排序前先使用一个额外的数组拷贝一份原来的数组,对于两个相同元素的索引问题,使用一个bool型变量辅助将两个索引都找到,总的时间复杂度为O(n)+O(nlogn) = O(nlogn)

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        record = dict()
        nums_copy = nums.copy()
        sameFlag = True;
        nums.sort()
        l,r = 0,len(nums)-1
        while l < r:
            if nums[l] + nums[r] == target:
                break
            elif nums[l] + nums[r] < target:
                l += 1
            else:
                r -= 1
        res = []
        for i in range(len(nums)):
            if nums_copy[i] == nums[l] and sameFlag:
                res.append(i)
                sameFlag = False
            elif nums_copy[i] == nums[r]:
                res.append(i)
        return res

小套路:
如果只是对数组的值进行排序,那么数组原来表示的索引的信息就会丢失的情况,可以在排序前:
更加pythonic的实现
通过list(enumerate(nums))开始实现下标和值的绑定,不用专门的再copy加bool判断。

nums = list(enumerate(nums))
nums.sort(key = lambda x:x[1])
i,j = 0, len(nums)-1
while i < j:
    if nums[i][1] + nums[j][1] > target:
        j -= 1
    elif nums[i][1] + nums[j][1] < target:
        i += 1
    else:
        if nums[j][0] < nums[i][0]:
            nums[j],nums[i] = nums[i],nums[j]
        return num[i][0],nums[j][0]

查找表--O(n)
遍历数组过程中,当遍历到元素v时,可以只看v前面的元素,是否含有target-v的元素存在。

  1. 如果查找成功,就返回解;
  2. 如果没有查找成功,就把v放在查找表中,继续查找下一个解。
    即使v放在了之前的查找表中覆盖了v,也不影响当前v元素的查找。因为只需要找到两个元素,只需要找target-v的另一个元素即可。
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        record = dict()
        for i in range(len(nums)):
            complement = target - nums[i]
            # 已经在之前的字典中找到这个值
            if record.get(complement) is not None:
                res = [i,record[complement]]
                return res
            record[nums[i]] = i

引自DataWhale Leetcode练习

posted @ 2020-08-26 00:42  C_初学者  阅读(107)  评论(0)    收藏  举报