哈希表算法总结

哈希表算法总结

著名的空间换时间算法。

leetcode1 两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        hashmap = {}
        for index,var in enumerate(nums):
            hashmap[var] = index
        for i,var in enumerate(nums):
            j = hashmap.get(target-var)
            if j and i != j:
                return [i,j]

例题二:(hashmap中搜索字符串,重点考察编程能力)
leetcode30

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 “barfoo” 和 “foobar” 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        if not s or not words:
            return []
        hashmap = {}
        res = []
        for i in words:
            if i not in hashmap:
                hashmap[i] =1
            else:
                hashmap[i] +=1
        one_word = len(words[0])
        all_words = one_word*len(words)
        for i in range(len(s)-all_words+1):
        	#之所以要新建一个字典是为了防止源字典被破坏
            temp ,d = s[i:i+all_words],dict(hashmap)
            for j in range(0,len(temp),one_word):
                key = temp[j:j+one_word]
                if key in d:
                    d[key] -=1
                    if d[key] == 0:
                        del d[key]
                else:
                    break 
            #保存首坐标
            if not d:
                res.append(i)
        return res

例题三:
leetcode37 解数独(hard级别的题目确实综合能力要求很高)

编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 ‘.’ 表示。

回溯+hashmap的综合应用

from collections import defaultdict
class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        """
        :type board: List[List[str]]
        :rtype: void Do not return anything, modify board in-place instead.
        """
        #判断这个位置是否在hashmap中已经存在
        def could_place(d, row, col):
            """
            Check if one could place a number d in (row, col) cell
            """
            return not (d in rows[row] or d in columns[col] or \
                    d in boxes[box_index(row, col)])
        #放置数字,并且更新三个hashmap
        def place_number(d, row, col):
            """
            Place a number d in (row, col) cell
            """
            rows[row][d] += 1
            columns[col][d] += 1
            boxes[box_index(row, col)][d] += 1
            board[row][col] = str(d)
            
        #移除数字和hashmap
        def remove_number(d, row, col):
            """
            Remove a number which didn't lead 
            to a solution
            """
            del rows[row][d]
            del columns[col][d]
            del boxes[box_index(row, col)][d]
            board[row][col] = '.'    
        #下一次的递归条件,判断是否回溯到表格末端 
        def place_next_numbers(row, col):
            """
            Call backtrack function in recursion
            to continue to place numbers
            till the moment we have a solution
            """
            # if we're in the last cell
            # that means we have the solution
            if col == N - 1 and row == N - 1:
                nonlocal sudoku_solved
                sudoku_solved = True
            #if not yet    
            else:
                # if we're in the end of the row
                # go to the next row
                if col == N - 1:
                    backtrack(row + 1, 0)
                # go to the next column
                else:
                    backtrack(row, col + 1)
                
        #回溯,其实就是调用上面的函数  
        def backtrack(row = 0, col = 0):
            """
            Backtracking
            """
            # if the cell is empty
            if board[row][col] == '.':
                # iterate over all numbers from 1 to 9
                for d in range(1, 10):
                    if could_place(d, row, col):
                        place_number(d, row, col)
                        place_next_numbers(row, col)
                        # if sudoku is solved, there is no need to backtrack
                        # since the single unique solution is promised
                        if not sudoku_solved:
                            remove_number(d, row, col)
            else:
                place_next_numbers(row, col)
                    
        # box size
        n = 3
        # row size
        N = n * n
        # lambda function to compute box index
        box_index = lambda row, col: (row // n ) * n + col // n
        
        # init rows, columns and boxes
        rows = [defaultdict(int) for i in range(N)]
        columns = [defaultdict(int) for i in range(N)]
        boxes = [defaultdict(int) for i in range(N)]
        #筛选出数字的位置
        for i in range(N):
            for j in range(N):
                if board[i][j] != '.': 
                    d = int(board[i][j])
                    place_number(d, i, j)
        
        sudoku_solved = False
        backtrack()

例题四
leetcode 76 最小覆盖子串

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”

from collections import defaultdict
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        # 初始化counter_s和counter_t
        counter_s = defaultdict(int)
        counter_t = defaultdict(int)
        for ch in t:
            counter_t[ch] += 1
        #左指针位置
        left = 0
        #有效位数很关键!!!
        valid = 0
        # 记录最小覆盖子串的起始索引及长度
        start = -1
        minlen = float('inf')
        for right in range(len(s)):
            # 移动右边界, ch 是将移入窗口的字符
            ch = s[right]
            if ch in counter_t:
                counter_s[ch] += 1
                #若t字符串中有n个重复字母,全部找到valid才加一
                if counter_s[ch] == counter_t[ch]:
                    valid += 1

            # 判断左侧窗口是否要收缩
            while valid == len(counter_t):
                # 更新最小覆盖子串
                if right - left < minlen:
                    minlen = right - left
                    start = left
                # left_ch 是将移出窗口的字符
                left_ch = s[left]
                # 左移窗口
                left += 1
                # 进行窗口内数据的一系列更新
                if left_ch in counter_s:
                    counter_s[left_ch] -= 1
                    #count_s中可能有n+m个left_ch 
                    #但是count_t中只有n个left_ch
                    if counter_s[left_ch] < counter_t[left_ch]:
                        valid -= 1
        # 返回最小覆盖子串
        if start == -1:
            return ""
        return s[start: start + minlen + 1]

例题五:快乐数
leetcode 202

编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。
示例:
输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

class Solution:
    def isHappy(self, n: int) -> bool:
        # 初始化 visited
        visited = set()
        # 当 n != 1,并且没见过 n 时进行判断(只要见过就会陷入死循环)
        while n != 1 and n not in visited:
            # 把 n 放入 visited
            visited.add(n)
            # 重新初始化总和
            nxt = 0
            # 计算 n 的各位数字平方和
            while n != 0:
                nxt += (n % 10) ** 2
                n //= 10
            # 把下一轮的数字设定为 n
            n = nxt
        # 判断 n 的最终结果是否为 1
        return n == 1

例题六:
leetcode 219 存在重复元素

给定一个整数数组和一个整数 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

class Solution:
    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
        if not nums:
            return False
        hashmap ={}
        for i in range(len(nums)):
            if nums[i] not in hashmap:
                hashmap[nums[i]] = i
            else:
                if k>=i-hashmap.get(nums[i]):
                    return True
                else:
                #这个就可以每次更新hashmap[nums[i]],剔除不符合要求的
                    hashmap[nums[i]] = i
        return False

例题7:前K个高频元素(本地着重掌握字典排序和取前K个元素的技巧)
leetcode219

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        if not nums:
            return []
        hashmap = {}
        for i in nums:
            if i not in hashmap:
                hashmap[i] = 1
            else:
                hashmap[i] +=1
        res = sorted(hashmap.items(), key= lambda x: x[1],reverse=True)
        print(res)
        result = []
        for i in range(k):
            result.append(res[i][0])
        return result

例题8(人生苦短,我用python!!!)
leetcode350 两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]

import collections
class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
    #python库真牛逼,自己创建字典,掉包侠的快乐你们体会不到,哈哈!!
        n1,n2 = collections.Counter(nums1),collections.Counter(nums2)
        res= []
        for i in n1:
        #若不存在n1[i]或n2[i]会返回0
            tmp = min(n1[i],n2[i])
            while tmp>0:
                res.append(i)
                tmp -=1
        return res

例题9:和为K的数组(前缀和)
leetcode560

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

import collections
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
    	#这里调用defaultdict的作用就是给每一个出现的key一个为0的value
    	#而hashmap={}则为null
        hashmap = collections.defaultdict(int)
        #hashmap是用来记录前N个位置的前缀和的,所以hashmap[0]==1
        hashmap[0] = 1
        #记录当前的前缀总和
        cur_num = 0
        res = 0
        for i in range(len(nums)):
            cur_num +=nums[i]
            #这个其实不难理解,若举个例子:[1,1,0,0,2,3] k=2
            #上面这个例子的hashmap={0:1,1:1,2:3,4:1,7:1}
            #所以当i在列表2的位置的时候,cur_num-k=2 在hashmap中
            #所以不管是[0,0,2],[0,2],[2]都可以满足题意
            if cur_num-k in hashmap:
                res +=hashmap[cur_num-k]
            #这里由于前面是collections.defaultdict()定义的,所以可以用+=1
            hashmap[cur_num] +=1
        return res

例题10 :
leetcode 567 字符串的排列(动态创建字典)

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
示例1:
输入: s1 = “ab” s2 = “eidbaooo”
输出: True
解释: s2 包含 s1 的排列之一 (“ba”).
示例2:
输入: s1= “ab” s2 = “eidboaoo”
输出: False

import collections
class Solution(object):
    def checkInclusion(self, s1, s2):
        """
        :type s1: str
        :type s2: str
        :rtype: bool
        """
        l1, l2 = len(s1), len(s2)
        c1 = collections.Counter(s1)
        c2 = collections.Counter()
        cnt = 0 #统计变量,全部26个字符,频率相同的个数,当cnt==s1字母的个数的时候,就是全部符合题意,返回真
        p = q = 0 #滑动窗口[p,q]
        while q < l2:
            c2[s2[q]] += 1
            if c1[s2[q]] == c2[s2[q]]: #对于遍历到的字母,如果出现次数相同
                cnt += 1               #统计变量+1
            if cnt == len(c1):         #判断结果写在前面,此时证明s2滑动窗口和s1全部字符相同,返回真
                return True
            q += 1                     #滑动窗口右移
            if q - p + 1 > l1:         #这是构造第一个滑动窗口的特殊判断,里面内容是维护边界滑动窗口
                if c1[s2[p]] == c2[s2[p]]:    #判断性的if写在前面,因为一旦频率变化,这个统计变量就减1
                    cnt -= 1
                c2[s2[p]] -= 1                #字典哈希表移除最前面的字符
                if c2[s2[p]] == 0:            #由于counter特性,如果value为0,必须删除它
                    del c2[s2[p]]
                p += 1                        #最前面的下标右移动
        return False

任务调度器

'''
找出列表中最多的元素,然后求出最多元素的个数nbucket,再求出最多元素个数的元素个数last_bucket_size
'''
from collections import Counter

class Solution:
    def leastInterval(self, tasks: List[str], n: int) -> int:
        ct = Counter(tasks)
        nbucket = ct.most_common(1)[0][1]
        last_bucket_size = list(ct.values()).count(nbucket)
        res = (nbucket - 1) * (n + 1) + last_bucket_size
        return max(res, len(tasks))

前K个高频词汇

class Solution:
    def topKFrequent(self, words: List[str], k: int) -> List[str]:
        if not words:
            return []
        hashmap = {}
        for i in words:
            if i not in hashmap:
                hashmap[i] = 1
            else:
                hashmap[i] +=1
        res = sorted(hashmap.items(), key= lambda x: x[1],reverse=True)
        result = []
        for i in range(k):
            result.append(res[i][0])
        return result

统计优美子数组

import collections
class Solution:
    def numberOfSubarrays(self, nums: List[int], k: int) -> int:
        hashmap = collections.defaultdict(int)
        if not nums:
            return 0
        res = 0
        hashmap[0] = 1
        cur_odd = 0
        for i in range(len(nums)):
            if nums[i] % 2 ==1:
                cur_odd +=1
            if cur_odd-k in hashmap:
                res +=hashmap[cur_odd-k]
            hashmap[cur_odd] +=1
        return res

电话号码的字母组合

'''
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
'''
class Solution(object):
	def letterCombinations(self, digits):
		"""
		:type digits: str
		:rtype: List[str]
		"""
		if not digits:
			return []
		# 一个映射表,第二个位置是"abc“,第三个位置是"def"。。。
		# 这里也可以用map,用数组可以更节省点内存
		d = [" ","*","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
		# 先往队列中加入一个空字符
		res = [""]
		for i in digits:
			size = len(res)
			# 由当前遍历到的字符,取字典表中查找对应的字符串
			letters = d[ord(i)-48]
			# 计算出队列长度后,将队列中的每个元素挨个拿出来
			for _ in range(size):
				# 每次都从队列中拿出第一个元素
				tmp = res.pop(0)
				# 然后跟"def"这样的字符串拼接,并再次放到队列中
				for j in letters:
					res.append(tmp+j)
		return res

连续子数组和

'''

给定一个包含非负数的数组和一个目标整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,
总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。

示例 1:

输入: [23,2,4,6,7], k = 6
输出: True
解释: [2,4] 是一个大小为 2 的子数组,并且和为 6。
示例 2:

输入: [23,2,6,4,7], k = 6
输出: True
解释: [23,2,6,4,7]是大小为 5 的子数组,并且和为 42。

'''
class Solution:
    def checkSubarraySum(self, nums, k):
        sumdict = {}
        sumN = 0
        sumdict[0] = -1
        for i in range(len(nums)):
            sumN += nums[i]
            if k != 0: sumN = sumN % k
            if sumN in sumdict:
                if i - sumdict[sumN] > 1:#判断是否间隔两位以上[5,0,0]与[5,0] K=0的比较
                    return True
            else:
                sumdict[sumN] = i
            print(sumdict)
        return False

nums = [2,5,33,6,7,25,15]
k = 13
A = Solution()
print(A.checkSubarraySum(nums,k))

属性哈希

hashmap = {}
n,k = list(map(int,input().strip().split()))
b = [0 for i in range(k-1)]
res= 0

for i in range(n):
    a = [int(i) for i in input().split()]
    for j in range(1,k):
        a[j] = a[j] - a[0]
        b[j-1] = -a[j]
    if tuple(b) in hashmap.keys():
        res +=1
        # hashmap[tuple(a[1:])] += 1
    else:
        hashmap[tuple(a[1:])] =1
print(res)
print(hashmap)

有效的括号

'''
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

示例 1:

输入: "()"
输出: true
示例 2:

输入: "()[]{}"
输出: true
示例 3:

输入: "(]"
输出: false

'''
class Solution:
    def isValid(self, s: str) -> bool:
        dic = {'{': '}',  '[': ']', '(': ')', '?': '?'}
        stack = ['?']
        for c in s:
            if c in dic: stack.append(c)
            elif dic[stack.pop()] != c: return False 
        return len(stack) == 1


砖墙

'''
你的面前有一堵方形的、由多行砖块组成的砖墙。 这些砖块高度相同但是宽度不同。你现在要画一条自顶向下的、穿过最少砖块的垂线。

砖墙由行的列表表示。 每一行都是一个代表从左至右每块砖的宽度的整数列表。

如果你画的线只是从砖块的边缘经过,就不算穿过这块砖。你需要找出怎样画才能使这条线穿过的砖块数量最少,并且返回穿过的砖块数量。

你不能沿着墙的两个垂直边缘之一画线,这样显然是没有穿过一块砖的。

'''

class Solution:
    def leastBricks(self, wall: List[List[int]]) -> int:
        hashmap = {}
        if not wall:
            return 0
        n = len(wall)
        temp = 0
        for i in range(n):
            for j in range(len(wall[i])-1):
                temp +=wall[i][j]
                if temp not in hashmap.keys():
                    hashmap[temp] = 1
                else:
                    hashmap[temp] += 1
            temp  =0
        if not hashmap:
            return n
        max_value  = max(hashmap.values())
        res = n - max_value
        return res

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值