【编程之路】面试必刷TOP101:哈希表(50-54,Python实现)

【面试必刷TOP101】系列包含:


50.两数之和

我们能想到最直观的解法,可能就是两层遍历,将数组所有的二元组合枚举一遍,看看是否是和为目标值,但是这样太费时间了,既然加法这么复杂,我们是不是可以尝试一下减法:对于数组中出现的一个数a,如果目标值减去a的值已经出现过了,那这不就是我们要找的一对元组吗?这种时候,快速找到已经出现过的某个值,可以考虑使用哈希表快速检验某个元素是否出现过这一功能。

class Solution:
    def twoSum(self , numbers: List[int], target: int) -> List[int]:
        a = []
        dic = dict()
        for i in range(len(numbers)):
            temp = target - numbers[i]
            if temp not in dic:
                dic[numbers[i]] = i
            else:
                a.append(dic[temp]+1)
                a.append(i+1)
                break
        return a

时间复杂度:O(n),仅仅遍历数组一次,每次查询哈希表都是O(1)。
空间复杂度:O(n),最坏情况下找到数组结尾才找到,其他都加入哈希表,因此哈希表最长为 n−1 的长度。

51.数组中出现次数超过一半的数字

51.1 哈希表法

class Solution:
    def MoreThanHalfNum_Solution(self , numbers: List[int]) -> int:
        dic = dict()
        for i in range(len(numbers)):
            if numbers[i] in dic:
                dic[numbers[i]] += 1
            else:
                dic[numbers[i]] = 1
            if dic[numbers[i]] > int(len(numbers) / 2):
                    return numbers[i]

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

51.2 排序法

可以先将数组排序,然后可能的众数肯定在数组中间,然后判断一下。

class Solution:
    def MoreThanHalfNum_Solution(self , numbers: List[int]) -> int:
        numbers.sort()
        return numbers[int(len(numbers)/2)]

时间复杂度:O(nlogn)
空间复杂度:O(1)

51.3 候选法

加入数组中存在众数,那么众数一定大于数组的长度的一半。
思想就是:如果两个数不相等,就消去这两个数,最坏情况下,每次消去一个众数和一个非众数,那么如果存在众数,最后留下的数肯定是众数。

class Solution:
    def MoreThanHalfNum_Solution(self , numbers: List[int]) -> int:
        cond = -1 # 候选人
        cnt = 0 # 投票次数
        for i in range(len(numbers)):
            if cnt == 0:
                cond = numbers[i]
                cnt = cnt + 1
            else:
                if cond == numbers[i]:
                    cnt = cnt + 1
                else:
                    cnt = cnt - 1
        return cond

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

52.数组中只出现一次的两个数字

52.1 哈希表

class Solution:
    def FindNumsAppearOnce(self , array: List[int]) -> List[int]:
        a = []
        dic = dict()
        for i in range(len(array)):
            if array[i] in dic:
                dic[array[i]] = dic[array[i]] + 1
            else:
                dic[array[i]] = 1
        for key, value in dic.items():
            if value == 1:
                a.append(key)
        a.sort()
        return a

时间复杂度:O(n),其中 n 为数组长度,两次单独的遍历数组每个元素。
空间复杂度:O(n),哈希表的长度应该为(n−2)/2。

52.2 异或运算

step 1:遍历整个数组,将每个元素逐个异或运算,得到 a⊕b。
step 2:我们可以考虑位运算的性质,找到二进制中第一个不相同的位,将所有数组划分成两组。
step 3:遍历数组对分开的数组单独作异或连算。
step 4:最后整理次序输出。
最好的理解方法是自己写一个例子,演算一遍,比如:3,3,4,5,5,6。

class Solution:
    def FindNumsAppearOnce(self , array: List[int]) -> List[int]:
        res = [0, 0]
        temp = 0
        # 遍历数组得到 a^b,Python中,^ 代表异或
        for i in range(len(array)):
            temp = temp ^ array[i]
        k = 1
        # 找到两个数不相同的第一位
        while k & temp == 0:
            k <<= 1  # << 代表左移
        for i in range(len(array)):
            if k & array[i] == 0:
                res[0] = res[0] ^ array[i]
            else:
                res[1] = res[1] ^ array[i]
        res.sort()
        return res

时间复杂度:O(n),遍历两次数组,找到两个数不相同的第一位循环为常数次。
空间复杂度:O(1),常数级变量使用,无额外辅助空间。

53.缺失的第一个正整数

53.1 哈希表

class Solution:
    def minNumberDisappeared(self , nums: List[int]) -> int:
        dic = dict()
        for i in range(len(nums)):
            dic[nums[i]] = 1
        res = 1
        while res in dic:
            res = res + 1
        return res

时间复杂度:O(n),第一次遍历数组,为 O(n),第二次最坏从1遍历到 n,为 O(n)。
空间复杂度:O(n),哈希表记录 n 个不重复的元素,长度为 n。

53.2 原地哈希

理想情况下每个元素都在自己的位置上,如下:
在这里插入图片描述
现在将元素对应位置打乱,并且缺失了一个元素,如下:
在这里插入图片描述
那么怎么找到这个缺失元素呢?我们可以依次遍历数组 nums ,每遍历到一个元素,比如 nums[i] ,就将 nums[i] 的值对应的位置做上标记,表示这个位置上的主人出现过,只是现在不在这个位置。 遍历结束后,如果某个位置上没有被标记,说明这个位置上的主人缺失了。

nums[1] = 3,将3这个位置做标记。
在这里插入图片描述
nums[2] = 1,将1这个位置做标记。
在这里插入图片描述
nums[3] = 5,将5这个位置做标记。
在这里插入图片描述

nums[4] = 1,将1这个位置做标记。
在这里插入图片描述
nums[5] = 6,将6这个位置做标记。
在这里插入图片描述
nums[6] = 4,将4这个位置做标记。

可以看到,最后 2 位置上是没有被标记的,所以 2 就是缺失元素。
怎么标记呢?对这一题,我们要找的是一个正数,所以如果 nums[i] <= 0 ,那么我们不用管,将它映射到 [1, n] 之外,所以我们先遍历一次数组将 负数 和 0 映射成 n + 1 (只要大于n都行)。

再一次遍历数组,每遍历到一个元素 nums[i] ,先判断它是否大于 n ,若大于,说明它原先是小于等于 0 的数,则忽略,继续往后遍历。若小于 n ,则将 nums[i] 的值对应的位置上的元素变为原来的相反数,即 nums[nums[i]] = -nums[nums[i]] ,相当于上面的绿色标记,如下:下标为 1 的元素值为 3 ,将 3 这个位置做标记(值取相反数)。
在这里插入图片描述
为什么要变成原来的相反数呢?因为这样方便我们获取它原来的值(只需取绝对值)。比如,当我们遍历到下标 3 这个位置时,我们只需要对 nums[3] 取绝对值就可以获取它原来的值 5 ,然后将 5 这个位置上做上标记(取相反数)。如下:
在这里插入图片描述

遍历结束之后,如果有某个位置上还是大于 0 的,说明该位置是缺失元素。

class Solution:
    def minNumberDisappeared(self , nums: List[int]) -> int:
        n = len(nums)
        for i in range(n):
            if nums[i] <= 0: # 负数全部记为 n+1
                nums[i] = n+1
        for i in range(n):
            if abs(nums[i]) <= n:
                nums[abs(nums[i]) - 1] = -1 * abs(nums[abs(nums[i]) - 1])
        for i in range(n):
            if nums[i] > 0:
                return i+1
        return n+1 

思考一下为什么是 -1 * abs(nums[abs(nums[i]) - 1]),为什么这里要使用abs,这样即使是多次处理某一位上的数,仍然保证是负数。

时间复杂度:O(n),多次遍历数组,都是单层循环。
空间复杂度:O(1),原地哈希,以索引为指向,没有额外空间。

54.三数之和

54.1 暴力搜索

首先最朴素的做法是先排序然后通过三重循环寻找答案,可以优化如果循环的当前数和之前一样则continue,可以卡过本题数据。

时间复杂度:O(nlogn) + O(n^3);排序算法O(nlogn) + 三重循环枚举。

空间复杂度:O(n);数组存储与读取数据。

class Solution:
    def threeSum(self , num: List[int]) -> List[List[int]]:
        num.sort() # 非降序
        a = []
        for i in range(len(num)-2):
            if i > 0 and num[i] == num[i-1]: continue
            for j in range(i+1,len(num)-1):
                if j > i+1 and num[j] == num[j-1]: continue
                for k in range(j+1,len(num)):
                    if k > j+1 and num[k] == num[k-1]: continue
                    if num[i] + num[j] + num[k] == 0:
                        a.append([num[i],num[j],num[k]])
        return a

54.2 双指针优化

我们在用朴素算法暴力解决问题时,通过挖掘某些性质,使得算法时间复杂度由 O( n 3 n^3 n3) 降为 O( n 2 n^2 n2) 。

我们进行循环选择第一个数 x,然后创建两个指针 i 和 j,指针 i 指向下一个数,指针 j 指向最后一个数。

若指针 i 加指针 j 大于当前数 x,则指针 j --,
若指针 i 加指针 j 小于当前数 x,则指针 i ++,
若指针 i 加指针 j 等于当前数 x,则答案为 x 和指针 i 与 j 的三元组。

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn) + O ( n 2 ) O(n^2) O(n2);排序算法 O ( n l o g n ) O(nlogn) O(nlogn) + 双指针

空间复杂度: O ( n ) O(n) O(n);数组存储与读取数据

class Solution:
    def threeSum(self , num: List[int]) -> List[List[int]]:
        num.sort() # 非降序
        a = []
        for i in range(0,len(num)-2):
            if i and num[i] == num[i-1]: continue
            l = i + 1
            r = len(num)-1
            while(l<r):
                if num[l] + num[r] == -num[i]:
                    a.append([num[i],num[l],num[r]])
                    # -------------------------------------------
                    while num[l] == num[l+1] and l+1 < r: l = l+1
                    while num[r] == num[r-1] and r-1 > l: r = r-1
                    # -------------------------------------------
                    l = l + 1
                    r = r - 1
                elif num[l] + num[r] > -num[i]:
                    r = r - 1
                else:
                    l = l + 1
        return a
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

G皮T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值