LeetCode 剑指Offer 数据结构之 哈希表 总结 Part1

LeetCode 剑指Offer 数据结构之 哈希表 总结 Part1

python中的哈希表主要涉及到字典和集合,剑指Offer中涉及到3道题,今天来做一个总结。哈希表的优点是查找时间降低为O(1),主要出现在问题中涉及到“重复”,“不重复”,“唯一”等要求。一般哈希表的考察会和其他算法,数据结构相结合。

剑指Offer03:

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 1:

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof

解法1:不考虑索引,维护一个哈希数据结构:集合

def findRepeatNumber(self, nums):
    d = set()
    for i in nums:
        if i not in d:
            d.add(i)
        else:
            return i
    return None

算是python的语法糖了,一找到就返回
复杂度分析:
时间复杂度 O(N): 遍历数组使用 O(N) ,HashSet 添加与查找元素皆为 O(1)。
空间复杂度 O(N) : HashSet 占用 O(N) 大小的额外空间。

优化方向,哈希表大小可否替换为O(1)?

解法2:

充分利用题设,考虑索引,一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内,说明必然有 元素的 索引 和 值 是 一对多 的关系。
做法是,遇到一个数,将其替换到对应的索引位置上去。如果再遇到一个数,替换到索引的位置上时和原索引位置上的数一致,则找到重复。

def findRepeatNumber(self, nums):
    i = 0
    while i<len(nums):
        if nums[i]==i:
            i+=1
            continue
        if nums[nums[i]]==nums[i]:
            return nums[i]
        else:
            nums[nums[i]],nums[i] = nums[i],nums[nums[i]]
    return None

复杂度分析:
时间复杂度 O(N): 遍历数组使用 O(N) ,每轮遍历的判断和交换操作使用 O(1) 。
空间复杂度 O(1) : 使用常数复杂度的额外空间。

剑指 Offer 50:

第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

示例:

s = “abaccdeff”
返回 “b”

s = “”
返回 " "

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof

因为涉及到第x次出现+重复,如果明确要记录第x次出现做返回显然集合是做不了了,使用字典作为哈希数据结构。key为值,value为出现的位置索引。

解法1:

def firstUniqChar(self, s: str) -> str:
    if not s:
        return " "
    S = {}
    for index,i in enumerate(list(s)):
        if i in S.keys():
            S[i]=index
        else:
            S[i]=-1
    if min(S.values())==-1:
        return min(S.keys(), key=(lambda k: S[k]))
    else:
        return " "

维护字典S,发现再次出现就更新value,第一次出现就置value=-1
更新完字典以后,因为字典是无序的,需要找到字典value最小值对应的key进行返回,采用lambda表达式:key=(lambda k: S[k]) 取出S中key = k的值
min(object, function)
表示用function对object(可以是dict,list等……)做一轮映射,取出映射后的最小值对应的object元素
min(S.keys(), key=(lambda k: S[k])) 对S的key做key->value的映射,并取出value最小值对应的key

解法2:
考虑只求第一次找到,就可以返回,无需记录具体的出现位置,还是可以用集合来做。

def firstUniqChar(self, s: str) -> str:
    dic = {}
    for c in s:
        dic[c] = not c in dic
    for c in s:
        if dic[c]: return c
    return ' '


解法3:
python中具有有序set

import collections
def firstUniqChar(self, s):
    '''
    有序哈希表 插入的key有顺序
    :param s:
    :return:
    '''
    dic = collections.OrderedDict()
    for c in s:
        dic[c] = not c in dic
    for k, v in dic.items():# 遍历的是dict 不是s
        if v:
            return k
    return ' '

python 3.6后集合便是有序集合,所以:
dic = {} 等价于 dic = collections.OrderedDict()
集合的优化原理详见:
https://www.cnblogs.com/xieqiankun/p/python_dict.html

原先是一个83的二维数组 查询做遍历
现在是长度为8的一维数组。然后又生成了一个空的二维数组 None
3
前者存插入的索引,后者存hash值,key,value指针,查询时查后者不存在跳过,查询效率高!

剑指 Offer 48:

最长不含重复字符的子字符串
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

示例 1:

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

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

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof

一般出现找最长显然需要使用动态规划算法
解法1:

def lengthOfLongestSubstring(self, s):
    '''
    自己写个定义dp空间的
    :param s:
    :return:
    '''
    dic = {}
    dp = [0 for i in range(len(s))]  # dp[j] 表示以 j 为结尾的 最长的不重复的字符串的长度
    for j in range(len(s)):
        i = dic.get(s[j], -1)  # 历史上最近的一次出现s[j]的索引
        dic[s[j]] = j  # 更新索引
        if i < -1:
            # 没出现过
            dp[j] = dp[j - 1] + 1  # 必然更长+1
        elif j - i > dp[j - 1]:
            # 距离上次出现s[j]已经较dp[j-1]更长,所以s[j]不在dp[j-1]中,dp[j-1]可以+1
            dp[j] = dp[j - 1] + 1  # 必然更长+1
        else:
            # s[i]在dp[j-1]中,等于dp[j]的序列是基于dp[j-1]的序列,被s[i]截断,长度等于j-1
            dp[j] = j-i
    # dp空间需要判断下是否等于0
    return max(dp) if isinstance(dp,list) and dp else 0

定义状态空间dp : dp[j]是以 j 为结尾的 最长的不重复的字符串的长度
一般dp是把全局最长不断更新到j,最后返回dp[-1]
这个问题里最长问题涉及到两端的比较,dp只是右端结尾的最长值,最后返回全局max(dp),用哈希数据结构:字典(key值,value最近一次出现的位置索引)来判断状态转移的几个分支。

优化方向:
1)dp空间的优化
状态转移只和dp[j],dp[j-1]有关,可以简化为一个常量,并定义一个全局最优值不断更新
2)dp状态转移的优化
3个分支可以合并掉1,2

解法2:

def lengthOfLongestSubstring(self, s):
    '''
    该做法精简了DP空间 利用哈希表确定集中分支的状态转移
    :param s:
    :return:
    '''
    dic = {}
    res, tmp = 0, 0
    # tmp 意思是dp[j] 以字符s[j] 为结尾,最长不重复子字符串 的长度
    # 一般dp都更新到最后一位,返回df[-1]
    # 这里通过右边界的形式更新,更新的不是当前全局max,而是右边界max 最后还要取全局max
    # res表示历史最长 通过遍历不断更新保留当前全局max 最后返回
    for j in range(len(s)):
        i = dic.get(s[j], -1)  # 获取索引 i# get不到返回-1
        dic[s[j]] = j  # 更新哈希表 把s[j] 左边最近的相同字符 s[i]更新进去
        # 新的字符出现分支+s[i]~s[j] 中间是dp[j-1]的序列,所以dp[j-1]里没有s[j],+1
        if tmp < j - i:
            tmp = tmp + 1
        else:
            # s[i] 落在dp[j-1]的序列中,则dp[j] 应该等于s[i]的索引~j
            tmp = j - i  # dp[j - 1] -> dp[j]
        # 更新全局最优
        res = max(res, tmp)  # max(dp[j - 1], dp[j])
    return res

剑指Offer中哈希表的tag只有三道题,已经总结完毕,后续遇到好的题目将再做整理,开启Part2。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值