代码随想录算法训练营第七天 | 哈希表part02

64 阅读4分钟

代码随想录算法训练营第七天 | 哈希表part02

454 四数相加II

image.png

可以想到用字典的方法来做。把前两个数组各位相加的和存到一个字典里面。

然后遍历后两个数组,看看相加后的相反数在不在字典里面,如果在字典里面,就加上键对应的值。

res = dict()
for i in nums1:
    for j in nums2:
        res[i+j] = res.get(i+j,0) + 1
count = 0
for i in nums3:
    for j in nums4:
        key = i + j
        if -key in res:
            count += res[-key]
return count

image.png

383 赎金信

方法1:字典

同样,使用字典,首先把后者中所有的字符存到字典里。

同时,也可以把前者所有的字符都存到字典里,可以对对应键的值进行比较。

如果前者的值小于等于后者的,那就return true

counts = {}
for c in magazine:
    counts[c] = counts.get(c, 0) + 1
for c in ransomNote:
    if c not in counts or counts[c] == 0:
        return False
    counts[c] -= 1
return True

方法2:数组

    ransom_count = [0] * 26
    magazine_count = [0] * 26
    for c in ransomNote:
        ransom_count[ord(c) - ord('a')] += 1
    for c in magazine:
        magazine_count[ord(c) - ord('a')] += 1
    return all(ransom_count[i] <= magazine_count[i] for i in range(26))

方法3:使用count

    for char in ransomNote:
        if char in magazine and ransomNote.count(char) <= magazine.count(char):
            continue
        else:
            return False
    return True

15 三数之和

image.png

思路:很巧妙的一道题目,如果三层for循环是可以做的,但是去重很难。

所以考虑快慢指针法。使用三个指针,i left right

然后计算每次的三个数之和,如果>0的话,那么就right--,如果<0,那么就left++

每轮完了之后让i++

可是如何保证去重呢?

因为每轮完了之后有i++,所以nums[i] == nums[i-1] continue

因为left和right也有可能重复。

那么就

while right > left and nums[left] == nums[left + 1] :

left += 1

right方向也是同理。

跳出while循环之后,让left += 1 ,right -= 1。

完整代码:

    result = []
    nums.sort()
    for i in range(len(nums)):
        if nums[i] >= 0 :   
            return result
        if nums[i] == nums[i-1]:
            continue
        left = i + 1
        right = len(nums) - 1
        while left < right:
            sum_ = nums[i] + nums[left] + nums[right]
            if sum_ > 0:
                right -= 1
            elif sum_ < 0:
                left += 1
            else :
                result.append([nums[i],nums[left],nums[right]])
                while right > left and nums[left] == nums[left + 1] :
                    left += 1
                while right > left and nums[right] == nums[right - 1] :
                    right -= 1
                left += 1
                right -= 1
    return result

上面代码中有一个致命的问题,那就是如果是[0,0,0]是过不去的。

改变判断条件如下即可:

    for i in range(len(nums)):
        if nums[i] > 0 :    
            return result
        # i > 0 是为了确保我们不会在第一次循环时就跳过数组的第一个元素。
        if i > 0 and nums[i] == nums[i-1]: 
            continue

第二种方法:使用字典(时间复杂度高)

    result = []
    nums.sort()
    # 找出a + b + c = 0
    # a = nums[i], b = nums[j], c = -(a + b)
    for i in range(len(nums)):
        # 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
        if nums[i] > 0:
            break
        if i > 0 and nums[i] == nums[i - 1]: #三元组元素a去重
            continue
        d = {}
        for j in range(i + 1, len(nums)):
            if j > i + 2 and nums[j] == nums[j-1] == nums[j-2]: # 三元组元素b去重
                continue
            c = 0 - (nums[i] + nums[j])
            if c in d:
                result.append([nums[i], nums[j], c])
                d.pop(c) # 三元组元素c去重
            else:
                d[nums[j]] = j
    return result

18 四数之和

image.png

初见时感觉和三数之和差不多,不过就是又多加了一个数。但是如果我们用快慢指针的话显然是不行了。

ps:还是得用快慢指针,只不过多加一个遍历就可以了。

一定记住,剪枝去重

方法1:双指针

对于 if j > i+1 and nums[j] == nums[j-1]:

对于 j = i+1(即第一次进入第二层循环时),我们不需要检查 nums[j] 是否与 nums[j-1] 相同,因为 j 是在 i+1 位置,第一个可能不同的元素。所以我们从 j > i+1 开始去重,确保我们不会在刚进入循环时就跳过所有元素。

    nums.sort()
    n = len(nums)
    result = []
    for i in range(n):
        if nums[i] > target and nums[i] > 0 and target > 0:# 剪枝(可省)
            break
        if i > 0 and nums[i] == nums[i-1]:# 去重
            continue
        for j in range(i+1, n):
            if nums[i] + nums[j] > target and target > 0: #剪枝(可省)
                break
            if j > i+1 and nums[j] == nums[j-1]: # 去重
                continue
            left, right = j+1, n-1
            while left < right:
                s = nums[i] + nums[j] + nums[left] + nums[right]
                if s == target:
                    result.append([nums[i], nums[j], nums[left], nums[right]])
                    while left < right and nums[left] == nums[left+1]:
                        left += 1
                    while left < right and nums[right] == nums[right-1]:
                        right -= 1
                    left += 1
                    right -= 1
                elif s < target:
                    left += 1
                else:
                    right -= 1
    return result

方法2:字典

    freq = {}
    for num in nums:
        freq[num] = freq.get(num, 0) + 1# 创建一个集合来存储最终答案,并遍历4个数字的所有唯一组合
    ans = set()
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            for k in range(j + 1, len(nums)):
                val = target - (nums[i] + nums[j] + nums[k])
                if val in freq:
                    # 确保没有重复
                    count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val)
                    if freq[val] > count:
                        ans.add(tuple(sorted([nums[i], nums[j], nums[k], val])))
    ​
    return [list(x) for x in ans]