类型题 Ⅲ:双指针及哈希表求和

类型题 Ⅲ:双指针及哈希表求和

题目类型:在数组中查找和为某个值的元素组合,有可能返回元素组合下标,也有可能只返回组合的个数。

1. 两数之和

思路一:暴力解法,双重循环,每次固定一个 i,用 j 循环向前走,判断两数之和是否为 target,若是则返回 ij 的坐标,时间复杂度为

    O


    (



     n


     2



    )



   O(n^2)


O(n2)。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        if not nums: return []
        n = len(nums)
        for i in range(n):
            for j in range(i+1, n):
                if nums[i] + nums[j] == target:
                    return [i, j]

思路二:哈希表。可以把这个问题想成一个配对儿问题,对于每个元素都有唯一的元素和它配对以形成 target 值。那么在找属于自己的另一半时,其实不用一个一个拉过来问,你是我的另一半吗?可以创建一个登记册,先在登记册找看有没有自己要的人,没有就把自己的信息登记上去,等着自己的另一半来找自己。这样只需要遍历数组一遍,就能得到匹配的两个数了,时间复杂度为

    O


    (


    n


    )



   O(n)


O(n)。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        if not nums: return []
        n = len(nums)
        dict = {<!-- -->}
        for i in range(n):
            cur = target - nums[i]
            if cur in dict:
                return [dict[cur], i]
            dict[nums[i]] = i

15. 三数之和

这个暴力法肯定是不行的了,时间复杂度就可太高了,达到了

    O


    (



     n


     3



    )



   O(n^3)


O(n3)。

思路:双指针。两数之和的暴力解法是固定一个下标 i,用 j 循环遍历找到与 i 位置元素配对的元素。对于三数之和,没办法直接用哈希表加速查找,故使用类似于两数之和的暴力法进行查找。

基本思路是先对原数组按照由小到大进行排序。固定一个下标 k,用指针 ij 来动态寻找目标解。假设初始时 k = 0,令 ij 分别指向 k 之后的数组两端,计算三个位置元素之和,若大于 target 就让 j 减 1,小于 target 就让 i 加 1,直到 i 不小于 j 为止,结束之后 k 加 1,开始下一轮判断。

需要注意的是,题目要求i不能包括重复的三元组,所以对于数组中重复的数字需要额外处理,也就是当 ik 加 1,以及 j 减 1 时,要越过和当前位置相同的数字。

时间复杂度

    O


    (



     n


     2



    )



   O(n^2)


O(n2),其中排序需要 




    O


    (


    n


    l


    o


    g


    n


    )



   O(nlogn)


O(nlogn)<br> **空间复杂度**:




    O


    (


    1


    )



   O(1)


O(1)

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        if n < 3: return []
        nums.sort()
        res = []
        k = 0
        while k < n-1:
            i, j = k+1, n-1
            while i < j:
                s = nums[k] + nums[i] + nums[j]
                if  s < 0:
                    # 略过相同的数字
                    while i < j and nums[i+1] == nums[i]: i += 1
                    i += 1
                elif s > 0:
                    # 略过相同的数字
                    while i < j and nums[j-1] == nums[j]: j -= 1
                    j -= 1
                else:
                    res.append([nums[k], nums[i], nums[j]])
                    while i < j and nums[i + 1] == nums[i]: i += 1
                    i += 1
                    while i < j and nums[j - 1] == nums[j]: j -= 1
                    j -= 1
            while k < n-1 and nums[k+1] == nums[k]: k += 1
            k += 1
        return res

类似的题还有 16. 最接近的三数之和

解题思路一样,先排序,固定一个 k,用 ij 双指针求解,每次记录当前三数之和并比较与 target 的差的绝对值,记录下绝对值最小时的答案 res,最后返回 res 即可。

class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        n = len(nums)
        if n < 3: return
        if n == 3: return sum(nums)

        nums.sort()
        res = float('inf')
        for k in range(n-2):
            i, j = k+1, n-1
            while i < j:
                cur = nums[k] + nums[i] + nums[j]
                res = cur if abs(cur-target) < abs(res-target) else res
                if cur > target:
                    j -= 1
                elif cur < target:
                    i += 1
                else:
                    break
            if cur == target:
                break
        return res

18. 四数之和

参考三数之和的解法,先固定前两个数的解,再用双指针找另外两个数。需要三重循环,时间复杂度为

    O


    (



     n


     3



    )



   O(n^3)


O(n3)。

思路:使用 4 个指针 m、n、i、j,固定 m 和 n,i = n + 1m,j = length -1,移动 i 和 j 包夹求解即可。即最外层是 m 指针遍历数组,嵌套 n 指针遍历 m 指针之后的子数组,再嵌套 i 和 j 双指针包夹求解。

时间复杂度

    O


    (



     n


     3



    )



   O(n^3)


O(n3),其中排序需要 




    O


    (


    n


    l


    o


    g


    n


    )



   O(nlogn)


O(nlogn)

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        length = len(nums)
        if length < 4: return []
        res, m = [], 0
        while m < length-3:
            n = m + 1
            while n < length-2:
                i, j = n+1, length-1
                while i < j:
                    s = nums[m] + nums[n] + nums[i] + nums[j]
                    if s < target:
                        while i < j and nums[i+1] == nums[i]: i += 1
                        i += 1
                    elif s > target:
                        while i < j and nums[j-1] == nums[j]: j -= 1
                        j -= 1
                    else:
                        res.append([nums[m], nums[n], nums[i], nums[j]])
                        while i < j and nums[i+1] == nums[i]: i += 1
                        i += 1
                        while i < j and nums[j-1] == nums[j]: j -= 1
                        j -= 1
                while n < length-2 and nums[n+1] == nums[n]: n += 1
                n += 1
            while m < length-3 and nums[m+1] == nums[m]: m += 1
            m += 1
        return res

454. 四数相加 II

思路:求前两个数组每两个元素之间的和,存在哈希表中,键为和,值为等于该和的个数。再求后两个数组每两个元素之间的和,在哈希表中查找该和的相反数。由于该题不要求返回具体的元素下标,所以只需要统计个数即可。

时间复杂度:

    O


    (



     n


     2



    )



   O(n^2)


O(n2),两个双重循环<br> 空间复杂度:




    O


    (



     n


     2



    )



   O(n^2)


O(n2),哈希表需要的额外存储空间

class Solution:
    def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
        nA, nB, nC, nD = len(A), len(B), len(C), len(D)
        sum, dict = 0, {<!-- -->}
        for i in range(nA):
            for j in range(nB):
                s = A[i] + B[j]
                dict[s] = dict.get(s, 0) + 1
        for i in range(nC):
            for j in range(nD):
                s = -(C[i] + D[j])
                sum += dict.get(s, 0)
        return sum
posted @ 2021-01-06 13:55  刘桓湚  阅读(76)  评论(0编辑  收藏  举报