数据结构(Python实现)------ 哈希表

数据结构(Python实现)------ 哈希表)

哈希表是一种使用哈希函数组织数据,以支持快速插入和搜索的数据结构。

有两种不同类型的哈希表:哈希集合和哈希映射。

哈希集合是集合数据结构的实现之一,用于存储非重复值。
哈希映射是映射 数据结构的实现之一,用于存储(key, value)键值对。
在标准模板库的帮助下,哈希表是易于使用的。大多数常见语言(如Java,C ++ 和 Python)都支持哈希集合和哈希映射。

通过选择合适的哈希函数,哈希表可以在插入和搜索方面实现出色的性能。

设计哈希表

基本概念

哈希表的原理

正如我们在介绍中提到的,哈希表是一种数据结构,它使用哈希函数组织数据,以支持快速插入和搜索。在本文中,我们将简要说明哈希表的原理。

哈希表的原理
哈希表的关键思想使用哈希函数将键映射到存储桶。更确切地说,

1、当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中;

2、当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。
在这里插入图片描述

设计哈希表的关键

在设计哈希表时,你应该注意两个基本因素。

1. 哈希函数

在这里插入图片描述

冲突解决

在这里插入图片描述
插入搜索是哈希表中的两个基本操作。

此外,还有基于这两个操作的操作。例如,当我们删除元素时,我们将首先搜索元素,然后在元素存在的情况下从相应位置移除元素。

复杂度分析 - 哈希表

复杂度分析
如果总共有 M 个键,那么在使用哈希表时,可以很容易地达到 O(M) 的空间复杂度。

但是,你可能已经注意到哈希表的时间复杂度与设计有很强的关系。

我们中的大多数人可能已经在每个桶中使用数组来将值存储在同一个桶中,理想情况下,桶的大小足够小时,可以看作是一个常数。插入和搜索的时间复杂度都是 O(1)。

但在最坏的情况下,桶大小的最大值将为 N。插入时时间复杂度为 O(1),搜索时为 O(N)。

内置哈希表的原理
内置哈希表的典型设计是:

1、 键值可以是任何可哈希化的类型。并且属于可哈希类型的值将具有哈希码。此哈希码将用于映射函数以获取存储区索引。
2、每个桶包含一个数组,用于在初始时将所有值存储在同一个桶中。
3、如果在同一个桶中有太多的值,这些值将被保留在一个高度平衡的二叉树搜索树中。

插入和搜索的平均时间复杂度仍为 O(1)。最坏情况下插入和搜索的时间复杂度是 O(logN),使用高度平衡的 BST。这是在插入和搜索之间的一种权衡。

Python实现

设计哈希集合

不使用任何内建的哈希表库设计一个哈希集合

具体地说,你的设计应该包含以下的功能

add(value):向哈希集合中插入一个值。
contains(value) :返回哈希集合中是否存在这个值。
remove(value):将给定值从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。

class MyHashSet(object):
    def __init__(self):
        self.buckets = 1000
        self.itemsPerBucket = 1001
        self.table = [[] for _ in range(self.buckets)]

    def hash(self,key):
        return key % self.buckets

    def pos(self,key):
        return key // self.buckets

    def add(self,key):
        hashkey = self.hash(key)
        if not self.table[hashkey]:
            self.table[hashkey] = [0] * self.itemsPerBucket
        self.table[hashkey][self.pos(key)] = 1

    def remove(self,key):
        hashkey = self.hash(key)
        if self.table[hashkey]:
            self.table[hashkey][self.pos(key)] = 0

    def contains(self,key):
        hashkey = self.hash(key)
        return (self.table[hashkey] != [])and(self.table[hashkey][self.pos(key)] == 1)

设计哈希映射

不使用任何内建的哈希表库设计一个哈希映射

具体地说,你的设计应该包含以下的功能

put(key, value):向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值。
get(key):返回给定的键所对应的值,如果映射中不包含这个键,返回-1。
remove(key):如果映射中存在这个键,删除这个数值对。

class MyHashMap(object):
    def __init__(self):
        self.hashmap = [-99999 for _ in range(1000005)]

    def put(self,key,value):
        self.hashmap[key] = value

    def get(self,key):
        if self.hashmap[key] != -99999:
            return self.hashmap[key]
        return -1

    def remove(self,key):
        self.hashmap[key] = -99999

实际应用-哈希集合

基本概念

哈希集-用法

哈希集是集合的实现之一,它是一种存储不重复值的数据结构。

使用哈希集查重

我们知道,插入新值并检查值是否在哈希集中是简单有效的。

因此,通常,使用哈希集来检查该值是否已经出现过。

示例
让我们来看一个例子:

给定一个整数数组,查找数组是否包含任何重复项。

这是一个典型的问题,可以通过哈希集来解决。

你可以简单地迭代每个值并将值插入集合中。 如果值已经在哈希集中,则存在重复。

Python实现

存在重复元素

给定一个整数数组,判断是否存在重复元素。

如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。

示例 1:

输入: [1,2,3,1]	
输出: true

示例 2:

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

示例 3:

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

使用set函数实现

def containsDuplicate2(self,nums):
    return len(set(nums)) != len(nums)

使用map实现

class Solution(object):
    def containsDuplicate(self,nums):
        numDist = {}
        for i in range(len(nums)):
            if nums[i] not in numDist:
                numDist[nums[i]] = 0

            else:
                return True
        return False

只出现一次的数字

普通方法

class Solution(object):
    def singleNumber(self,nums):
        s = {}
        for i in nums:
            if i in s.keys():
                s.pop(i)
            else:
                s[i] = 1

        return list(s.keys())[0]

高级用法异或 ^
0异或任何数不变,任何数与自己异或为0。a⊕b⊕a=b。异或满足加法结合律和交换律。


    def singleNumber2(self,nums):
        res = 0
        for i in nums:
            res ^= i
        return res

两个数据的交集

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]

示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]

说明:

输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。

暴力法:

class Solution(object):
    def intersection(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: List[int]
        """


        res = []
        for i in nums1:
            for j in nums2:
                if i == j:
                    res.append(i)
                    del(j)#插入过的值不再出现
                    
                    break
        return set(res)

快乐数

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

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

class Solution(object):
    def isHappy(self,n):
        temp = []
        while True:
            n = self.get_add(n)
            if n == 1:
                return True
            elif n in temp:
                return False
            else:
                temp.append(n)

    def get_add(self, n):

    
        ret = 0
    
        while n != 0:
        
            g = n % 10
    
            ret += g ** 2
    
            n = int(n / 10)
    
        return ret

实际应用-哈希映射

基本概念

哈希映射 - 用法

哈希映射是用于存储 (key, value) 键值对的一种实现。

Python实现

场景 I - 提供更多信息

使用哈希映射的第一个场景是,我们需要更多的信息,而不仅仅是键。然后通过哈希映射建立密钥与信息之间的映射关系。

示例
让我们来看一个例子:

给定一个整数数组,返回两个数字的索引,使它们相加得到特定目标。

在这个例子中,如果我们只想在有解决方案时返回 true,我们可以使用哈希集合来存储迭代数组时的所有值,并检查 target - current_value 是否在哈希集合中。

但是,我们被要求返回更多信息,这意味着我们不仅关心值,还关心索引。我们不仅需要存储数字作为键,还需要存储索引作为值。因此,我们应该使用哈希映射而不是哈希集合。

更重要的是
在某些情况下,我们需要更多信息,不仅要返回更多信息,还要帮助我们做出决策。

在前面的示例中,当我们遇到重复的键时,我们将立即返回相应的信息。但有时,我们可能想先检查键的值是否可以接受。

两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

for循环的方法

class Solution(object):
    def twoSum(self,nums,target):
        n = len(nums)
        for x in range(n):
            b = target-nums[x]
            if b in nums:
                y = nums.index(b)
                if y!=x:
                    return x,y

哈希映射的方法

    def twoSum2(self,nums,target):
        nums_hash = {}
        nums_len = len(nums)
        for i in range(nums_len):
            dif = target -nums[i]
            if dif in nums_hash:
                return [nums_hash[dif],i]
            nums_hash[nums[i]]=i
        return []

同构字符串

给定两个字符串 s 和 t,判断它们是否是同构的。

如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。

所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。

示例 1:

输入: s = "egg", t = "add"
输出: true

示例 2:

输入: s = "foo", t = "bar"
输出: false

示例 3:

输入: s = "paper", t = "title"
输出: true

说明:
你可以假设 s 和 t 具有相同的长度。

常规方法

class Solution(object):
    def isIsomorphic(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: bool
        """
        mapping = {}
        for i ,char in enumerate(s):
            if char in mapping:
                if mapping[char] != t[i]:
                    return False

            else:
                if t[i] in mapping.values():
                    return False
                mapping[char] = t[i]

        return True

利用Python特性

  return len(set(zip(s,t))) == len(set(s)) == len(set(t))

列表的最小索引总和

假设Andy和Doris想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示。

你需要帮助他们用最少的索引和找出他们共同喜爱的餐厅。 如果答案不止一个,则输出所有答案并且不考虑顺序。 你可以假设总是存在一个答案。

示例 1:

输入:
["Shogun", "Tapioca Express", "Burger King", "KFC"]
["Piatti", "The Grill at Torrey Pines", "Hungry Hunter Steakhouse", 		"Shogun"]
输出: ["Shogun"]
解释: 他们唯一共同喜爱的餐厅是“Shogun”。

示例 2:

输入:
["Shogun", "Tapioca Express", "Burger King", "KFC"]

[ “KFC”, “Shogun”, “Burger King”]
输出: [“Shogun”]
解释: 他们共同喜爱且具有最小索引和的餐厅是“Shogun”,它有最小的索引和1(0+1)。
提示:

1、两个列表的长度范围都在 [1, 1000]内。
2、两个列表中的字符串的长度将在[1,30]的范围内。
3、下标从0开始,到列表的长度减1。
4、两个列表都没有重复的元素。

class Solution(object):
    def findRestaurant(self,list1,list2):
        res = []
        m = len(list1)
        n = len(list2)
        length = m+n
        for i in range(m):
            if list1[i] in list2:
                ss = i +list2.index(list1[i])
                if ss<length:
                    length=ss
                    res = [list1[i]]
                elif ss==length:
                    res.append(list1[i])

        return  res

场景 II - 按键聚合

另一个常见的场景是按键聚合所有信息。我们也可以使用哈希映射来实现这一目标。

示例
这是一个例子:

给定一个字符串,找到它中的第一个非重复字符并返回它的索引。如果它不存在,则返回 -1。

解决此问题的一种简单方法是首先计算每个字符的出现次数。然后通过结果找出第一个与众不同的角色。

因此,我们可以维护一个哈希映射,其键是字符,而值是相应字符的计数器。每次迭代一个字符时,我们只需将相应的值加 1。

更重要的是
解决此类问题的关键是在遇到现有键时确定策略。

在上面的示例中,我们的策略是计算事件的数量。有时,我们可能会将所有值加起来。有时,我们可能会用最新的值替换原始值。策略取决于问题,实践将帮助您做出正确的决定。

字符串中的第一个唯一字符

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

案例:

s = "leetcode"
返回 0.

s = "loveleetcode",
返回 2.

注意事项:您可以假定该字符串只包含小写字母。

利用python字符串的内置函数find和rfind可以快速解决本题,时间复杂度相对不好,需要考虑内置函数的效率;

class Solution(object):
    def firstUniqChar(self,s):
        for i in range(len(s)):
            if s.find(s[i]) == s.rfind(s[i]):
                return i

        return -1

首先构建一个dict,遍历一次字符串,统计每个字符出现的次数,然后再遍历第二次,字符出现的字数是1的时候返回该字符的索引即可,时间复杂度O(n)。

class Solution2(object):
    def firstUniqChar(self,s):
        my_dict={}
        for char in s:
            if char in my_dict:
                my_dict[char]+=1

            else:
                my_dict[char] = 1


        for i in range(len(s)):
            if my_dict[s[i]] == 1:
                return i

        return -1

两个数组的交集 II

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]

示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]

说明:

输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
我们可以不考虑输出结果的顺序。
进阶:

如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

class Solution(object):
    def intersect(self,nums1,nums2):
        nums1.sort()
        nums2.sort()

        new_list = []
        i,j = 0,0
        while i < len(nums1) and j < len(nums2):
            if nums1[i] == nums2[j]:
                new_list.append(nums1[i])
                i+=1
                j+=1
                
            elif nums1[i] < nums2[j]:
                i+=1
                
            else:
                j+=1
                
        return new_list

存在重复元素 II

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k。

示例 1:

输入: nums = [1,2,3,1], k = 3
输出: true

示例 2:

输入: nums = [1,0,1,1], k = 1
输出: true

示例 3:

输入: nums = [1,2,3,1,2,3], k = 2
输出: false
class Solution(object):
    def containsNearbyDuplicate(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: bool
        """
        d = {}
        for i,num in enumerate(nums):
            if num not in d:
                d[num] = i
            elif i-d[num]<=k:
                return True
            else:
                d[num] = i
        return False

实际应用-设计键

基本概念

在以前的问题中,键的选择相对简单。不幸的是,有时你必须考虑在使用哈希表时设计合适的键。

示例
我们来看一个例子:

给定一组字符串,将字母异位词组合在一起。

众所周知,哈希映射可以很好地按键分组信息。但是我们不能直接使用原始字符串作为键。我们必须设计一个合适的键来呈现字母异位词的类型。例如,有字符串 “eat” 和 “ate” 应该在同一组中。但是 “eat” 和 “act” 不应该组合在一起。

解决方案

实际上,设计关键是在原始信息和哈希映射使用的实际键之间建立映射关系。设计键时,需要保证:

  1. 属于同一组的所有值都将映射到同一组中。

  2. 需要分成不同组的值不会映射到同一组。

此过程类似于设计哈希函数,但这是一个本质区别。哈希函数满足第一个规则但可能不满足第二个规则。但是你的映射函数应该满足它们。

在上面的示例中,我们的映射策略可以是:对字符串进行排序并使用排序后的字符串作为键。也就是说,“eat” 和 “ate” 都将映射到 “aet”。

有时,设计映射策略可能是非常棘手的。我们将在本章为您提供一些练习,并在此之后给出总结。

Python实现

字母异位词分组

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:

输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]

说明:

所有输入均为小写字母。
不考虑答案输出的顺序。

思路:利用哈希表-哈希表的操作效率很高,在遍历列表时,先将排序后的字符串作为键,然后检查改建是否存在哈希表中,如存在,则将原字符串加入到该键对应的列表中;反之则建立新的键对。

class Solution(object):
    def groupAnagrams(self, strs):
        """
        :type strs: List[str]
        :rtype: List[List[str]]
        """
        res_dict = {}
        for s in strs:
          sorted_s = ''.join(sorted(s))
          if sorted_s in res_dict:
              res_dict[sorted_s].append(s)
          else:
              res_dict[sorted_s]=[s]
        return list(res_dict.values())

有效的数独

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
在这里插入图片描述

上图是一个部分填充的有效的数独。

数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

示例 1:

输入:
[
[“5”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
输出: true
示例 2:

输入:
[
[“8”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
输出: false

解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
说明:

一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 ‘.’ 。
给定数独永远是 9x9 形式的。

题目分析:
要检验,几个特定区域中是否有重复的值,最容易想到的方法当然是将这几个区域中的复制出来,放入新创建的数组(或其他数据结构中),再判断,其中是否有重复的值,当然,为避免不必要的计算,可以选择边复制,边检验,一旦发现重复,直接停止复制操作,返回False。

此题中:

行和列两个区域的复制比较简单,此处不赘述。

99的坐标格划分成9个格子是此题难点,我的解决方案是:输入横纵坐标i、j,计算:top_id = i//33 + j//3 相同top_id 的点属于同一区域。(“//”表示取整数的除法操作)

class Solution:
    def isValidSudoku(self, board):
        """
        :type board: List[List[str]]
        :rtype: bool
        """
        row = [[] for _ in range(9)]  # 行
        col = [[] for _ in range(9)]  # 列
        area = [[] for _ in range(9)]  # 子区域
        for i in range(9):
            for j in range(9):
                element = board[i][j]
                if element != '.':  # 检验是否为数字
                    top_id = i//3*3 + j//3  # 子区域的计算
                    if element in row[i] or element in col[j] or element in area[top_id]:  # 如果在这一行列子区域重读出现过了
                        return False
                    else:  # 没有出现过就加进去
                        row[i].append(element)
                        col[j].append(element)
                        area[top_id].append(element)
        return True

寻找重复的子树

给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。

两棵树重复是指它们具有相同的结构以及相同的结点值。
在这里插入图片描述
解题思路:乍一看,会觉得有点麻烦,不知道从何下手,不过这道题本身是一个查重的问题,那么就很容易想到,存**放的数据结构是哈希表,**然后,题目要求找到的是重复出现的子树,也就是说,我们要存放在哈希表中的元素就是每一棵子树,那么问题就转换成,怎么表示一棵子树,或者是怎么判断子树是否重复的问题。

这里就需要明确一个结论,前序遍历相等的树,其树结构也相同(这里的遍历结果包含叶节点为None的叶节点)。那么这就好办了,只需要记录每个节点的前序遍历就ok了

class Solution(object):
    def DLR(self,root,orders,res):
        if root is None:
            return '#END#'
        #前序,后续都可以
        my_order = str(root.val) + '\t' + self.DLR(root.left,orders,res) + '\t' + self.DLR(root.right,orders,res)
        if orders.get(my_order,0) == 1:
            res.append(root)
        orders[my_order] = max(1,orders.get(my_order,0) + 1)
        return my_order

    def findDuplicateSubtrees(self,root):
        orders={}
        res=[]
        self.DLR(root,orders,res)
        return res

小结

宝石与石头

给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。

J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。

示例 1:

输入: J = “aA”, S = “aAAbbbb”
输出: 3
示例 2:

输入: J = “z”, S = “ZZ”
输出: 0
注意:

S 和 J 最多含有50个字母。
J 中的字符不重复。

class Solution(object):
    def numJewelsInStones(self,J,S):
        count = 0
        for x in J:
            count += S.count(x)
        return count

无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

解题思路:
这道题需要借助哈希查找key的O(n) 时间复杂度, 否则就会超时

初始化一个 哈希表\字典 dic

头指针start 初始为0

当前指针 cur 初始为0

最大长度变量 l 初始为0

用cur变量从给定字符串str的开头开始 一位一位的向右查看字符,直到整个字符串遍历完, 对每一位字符进行如下:

当前位置的字符为 c = str[cur]

查询当前字符 c 是否 在哈希表dic的键 当中,表示 当前字符c 是否之前遍历到过

如果 当前字符还没出现过,就 在dic中记录一个键值对 (当前字符c,当前位置cur )

cur 后移一位

如果 当前字符出现过, 获取 当前字符串c 上次出现的位置 pre = dic[c]

如果pre 在 start后面即 pre>start, 则把start 移动到 pre的下一位, start = pre + 1, 这样保证cur继续向后遍历中 从start到cur没有重复元素

否则 start不动,start移动到某一个位置,说明在这个位置之前有重复的元素了,所以start只往后移动不往回移动

这时候在衡量一下 如果 cur - start + 1 (衡量当前没重复子串开头到结尾的长度) 比 长度变量 l 大, 那就替换 l 为 cur - start + 1

class Solution(object):
    def lengthOfLongestSubstring(self, s):
        l = 0
        start = 0
        dic = {}

        for i in range(len(s)):
            cur = s[i]
            if cur not in dic.keys():
                dic[cur] = i
            else:
                if dic[cur] + 1 >start:
                    start = dic[cur] + 1
                dic[cur] = i
            if  i - start +1 > l:
                l = i - start + 1


        return l

四数相加II

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。

例如:

输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]

输出:
2

解释:
两个元组如下:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

方法一:
用dict去存,key是和,value是出现的次数,这样就不用count了,


class Solution(object):
    def fourSumCount(self, A, B, C, D):
        """
        :type A: List[int]
        :type B: List[int]
        :type C: List[int]
        :type D: List[int]
        :rtype: int
        """
        map1 = dict()
        res = 0
        for a in A:
            for b in B:
                t = a + b
                map1[t] = map1.get(t, 0) + 1
                
        for c in C:
            for d in D:
                t = - c - d
                
                if t in map1:
                    res += map1[t]
                    
        return res

方法二:
用defaultdict 代替dict,就不用判断每次key存不存在,可以直接调用defaultdict【key】。

class Solution(object):
    def fourSumCount(self, A, B, C, D):
        """
        :type A: List[int]
        :type B: List[int]
        :type C: List[int]
        :type D: List[int]
        :rtype: int
        """
        from collections import defaultdict
        map1 = defaultdict(int)
        res = 0
        for a in A:
            for b in B:
                t = a + b
                map1[t] += 1
                
        for c in C:
            for d in D:
                t = - c - d
                res += map1[t]
                    
        return res

前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

说明:

你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。

解法1:通过字典排序的方式,时间复杂度 O(n log n)

class Solution(object):
    def topKFrequent(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        if len(nums) == 0:
            return []
        
        dic = {}
        for num in nums:
            if num not in dic.keys():
                dic[num] = 1
            else:
                dic[num] += 1
                
        li = sorted(dic.items(),key = lambda  x:x[1],reverse=True)
        return [item[0] for item in li[:k]]

解法2#: 通过Counter函数

import collections
class Solution2(object):
    def topKFrequent(self,nums,k):
        if len(nums) == 0:
            return []
        li = collections.Counter(nums)
        return [x[0] for x in li.most_common(k)]

解法3:通过heapq方法

class Solution(object):
    def topKFrequent(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        if len(nums) == 0:
            return []
        
        dic = {}
        for num in nums:
            if num not in dic.keys():
                dic[num] = 1
            else:
                dic[num] += 1
                
        li = sorted(dic.items(),key = lambda  x:x[1],reverse=True)
        return [item[0] for item in li[:k]]

常数时间插入、删除和获取随机元素

设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构。

insert(val):当元素 val 不存在时,向集合中插入该项。
remove(val):元素 val 存在时,从集合中移除该项。
getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。
示例 :

// 初始化一个空的集合。
RandomizedSet randomSet = new RandomizedSet();

// 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomSet.insert(1);

// 返回 false ,表示集合中不存在 2 。
randomSet.remove(2);

// 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
randomSet.insert(2);

// getRandom 应随机返回 1 或 2 。
randomSet.getRandom();

// 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
randomSet.remove(1);

// 2 已在集合中,所以返回 false 。
randomSet.insert(2);

// 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
randomSet.getRandom();

解题思路:使用列表存储元素值,使用字典存储元素值及其在列表中的索引。重点关注删除操作:先将要删除的val对应的索引值赋给列表中最后一个元素,接下来对列表中两个元素进行交换,最后在列表和字典中分别移除val即可

class RandomizedSet(object):
 
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.values = []
        self.index = {}
 
    def insert(self, val):
        """
        Inserts a value to the set. Returns true if the set did not already contain the specified element.
        :type val: int
        :rtype: bool
        """
        if val in self.index:
            return False
        self.values.append(val)
        self.index[val] = len(self.values)-1
        return True
 
    def remove(self, val):
        """
        Removes a value from the set. Returns true if the set contained the specified element.
        :type val: int
        :rtype: bool
        """
        if val not in self.index:
            return False
        self.index[self.values[-1]] = self.index[val]
        self.values[-1],self.values[self.index[val]] = self.values[self.index[val]],self.values[-1]
        self.values.pop()
        self.index.pop(val)
        return True
 
    def getRandom(self):
        """
        Get a random element from the set.
        :rtype: int
        """
        return self.values[random.randint(0,len(self.values)-1)]
发布了64 篇原创文章 · 获赞 9 · 访问量 4345

猜你喜欢

转载自blog.csdn.net/Avery123123/article/details/103583115
今日推荐