Leecode N个数的和合集【1、15、16、18、167、454、923】

问题描述:【Hash Table】1. Two Sum
解题思路:

两个数的和。给一个数组和目标 target,求数组中两个数的和为 target 的数的索引。

这道题用 Hash Table 求解,从左到右遍历数组,Hash Table 中存储每个数及其索引,如果再来一个数,先用 target 减去该数得到差值,然后判断差值是否在 Hash Table 中。如果在,说明差值和当前数的和等于 target,返回它们的索引即可。时间复杂度和空间复杂度均为 O(N)。

Python3 实现:
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        dic = dict()
        for k, v in enumerate(nums):
            t = target - v
            if t not in dic:  # 判断差值是否在dic中
                dic[v] = k
            else:
                return [dic[t], k]

问题描述:【Sort+Two Pointers】15. 3Sum
求解思路:

三个数的和。给一个数组,找出所有满足 a+b+c=0 的结果。

因为题目要求结果不包含重复的元组,因此先要对数组升序排序。 三个数的和问题,可以把第一个数当作目标数,然后在剩余的元素中求两个数的和,求解两个数的和的方法有上面的 Leetcode 1 哈希表法和下面的 Leetcode 167 双指针法。刚开始写了个哈希表法,结果超时了,换成双指针通过了。时间复杂度为 O(N^2),空间复杂度为 O(1)。

注意:只是升序排序还是不能完全去重,如 nums = [0,0,0,0,0],使用双指针法仍然可能得到多个 [0,0,0]。解决方法是可以将结果以元组的形式(因为元组可哈希)存入集合 set,每次产生一个结果判断结果是否在集合 set 中,没有再加进去。

Python3 实现:
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        N = len(nums)
        if N <= 2:
            return []
        nums.sort()  # 先升序排序
        ans = set()  # 保存在集合去重
        for i in range(N-2):  # nums[i] 为第一个数
            low, high = i + 1, N - 1
            while low < high:  # 首尾指针求两个数的和
                twosum = nums[low] + nums[high]
                if twosum == -nums[i]:  # -nums[i]为两个数的和的target
                    tmp = (nums[i], nums[low], nums[high])
                    if tmp not in ans:
                        ans.add(tmp)
                    low += 1  # 找到一组解还要继续向内部继续搜索,如 [-5,1,2,3,4]
                    high -= 1
                elif twosum < -nums[i]:
                    low += 1
                else:
                    high -= 1
        return list(ans)

print(Solution().threeSum([0,0,0,0]))  # [[0,0,0]]

问题描述:【Sort+Two Pointers】16. 3Sum Closest
解题思路:

这道题给一个数组和一个目标 target,求三个数的和最接近 target 的值。

做法同上面的 Leetcode 15,即先将数组升序排列,外层循环记录第一个数,使用首尾指针记录第二、第三个数。因为要找三个数的和最接近 target 的,如果等于 target 直接返回;如果不相等,那么就还需要一个变量 sub 来记录三个数的和与 target 的最小差值,每次去更新这个最小差值和对应结果,最后返回最小差值对应的结果即可。

时间复杂度为 O(N^2),空间复杂度为 O(1)。

Python3 实现:
class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        N = len(nums)
        nums.sort()
        ans = sub = float("inf")  # ans记录结果,sub记录最小差值
        for i in range(N-2):
            low, high = i + 1, N - 1
            while low < high:
                threesum = nums[i] + nums[low] + nums[high]
                if abs(threesum-target) < sub:  # 更新最接近的差值和结果
                    ans = threesum
                    sub = abs(threesum-target)
                if threesum == target:
                    return threesum
                elif threesum < target:
                    low += 1
                else:
                    high -= 1
        return ans

print(Solution().threeSumClosest([1,2,5,10,11], 12))  # 13

问题描述:【Sort+Two Pointers】18. 4Sum
解题思路:

这道题是给一个数组和 target,找到所有满足四个数的和为 target 的结果。

类似于上面的 Leetcode 15,四个数的和转化为三个数的和的问题,即先升序排序,然后前两层循环分别指向第一、第二个数,再使用首尾指针指向第三、第四个数,判断和 target 的大小关系。代码基本上和 Leetcode 15 一样。时间复杂度为 O(N^3)。

Python3 实现:
class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        N = len(nums)
        if N <= 3:
            return []
        nums.sort()  # 升序排列
        ans = set()  # 存储在集合去重 
        for i in range(N - 3):  # i指向第一个数
            for j in range(i + 1, N - 2):  # j指向第二个数
                low, high = j + 1, N - 1
                while low < high:
                    foursum = nums[i] + nums[j] + nums[low] + nums[high] 
                    if foursum == target:
                        tmp = (nums[i], nums[j], nums[low], nums[high])
                        if tmp not in ans:
                            ans.add(tmp)
                        low += 1
                        high -= 1
                    elif foursum < target:
                        low += 1
                    else:
                        high -= 1
        return list(ans)

拓展:

  • 实际上,这道题还有改进的方法,可以使用 Leetcode 1 中计算两个数的和的方法,做两次,时间复杂度可以降为 O(N^2)。
  • 如果计算 N 个数的和,可以使用 DFS 回溯法求解。

问题描述:【Two Pointers】167. Two Sum II - Input array is sorted
解题思路:

这道题是给一个排序好的数组,求数组中两个数的和为 target 的数的索引。

因为数组是排好序的(升序),因此可以使用首尾指针的方法。如果首尾指针数字之和小于目标,说明首指针指向的数字太小,首指针就往后移动;如果首尾指针数字之和大于目标,说明尾指针指向的数字太大,尾指针就往前移动;如果首尾指针数字之和等于目标,返回两个数的索引即可。时间复杂度为 O(N),空间复杂度为 O(1)。

Python3 实现:
class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        low, high = 0, len(numbers) - 1
        while low < high:
            t = numbers[low] + numbers[high]
            if t == target:
                return [low + 1, high + 1]
            elif t < target:
                low += 1
            else:
                high -= 1

问题描述:【Hash Table】454. 4Sum II
解题思路:

这道题是给四个数组 A、B、C、D,分别选一个数,求四个数的和为 0 的数目。

  • 很明显,如果是暴力,那么时间复杂度将会是 O(N^4),超时;
  • 进一步,我们可以将数组 D 存放在字典中,键为不同的数字,值为不同数字出现的次数;然后,三层循环判断前三个数的和的负值 tmp 是否在字典中,如果在,累加 tmp 的次数,这样时间复杂度为 O(N^3),写了一下,也超时了,pass;
  • 更近一步,我们可以对四个列表两两分组,先将 A 和 B 的结果相加,存入到字典中,键为 A + B 的和,值为 A + B 的和出现的次数;然后,两层循环判断后两个数的和的负值 tmp 是否在字典中,如果在,累加 tmp 的次数,这样时间复杂度为 O(N^2),就能 AC 了。

后两种想法类似于上面 Leetcode 1 使用哈希表法求解两个数的和的做法。

Python3 实现:
class Solution:
    def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
        N = len(A)
        ABsum = [A[i] + B[j] for i in range(N) for j in range(N)]
        ABdict = collections.Counter(ABsum)  # 统计A+B的次数
        ans = 0
        for i in range(N):
            for j in range(N):
                if - (C[i] + D[j]) in ABdict:  # 后两个数的和的负值在字典中能找到
                    ans += ABdict[- (C[i] + D[j])]
        return ans

更简洁的写法如下:

class Solution:
    def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
        N = len(A)
        ABsumDict = collections.Counter([-A[i]-B[j] for i in range(N) for j in range(N)])
        return sum([ABsumDict[C[i]+D[j]] for i in range(N) for j in range(N)])

问题描述:【Sort+Two Pointers+Math】923. 3Sum With Multiplicity
解题思路:

这道题是给一个数组和目标 target,求满足 i < j < k and A[i] + A[j] + A[k] == target 的数目。

这道题和上面的 Leetcode 15 很类似,可以先对数组升序排序,然后使用首尾指针。但是此题的结果可能非常大,因此如果一个一个统计的话, 肯定超时。因此,还需要找到一些规律。比如,现在找到一组解 A[i] + A[j] + A[k] = target

  • 如果 A[i] == A[j] == A[k],那么结果直接为 C(count(A[i]), 3)
  • 如果 A[i] == A[j] < A[k],那么结果直接为 C(count(A[i]), 2) * count(A[k])
  • 如果 A[i] < A[j] == A[k],那么结果直接为 C(count(A[k]), 2) * count(A[i])
  • 如果 A[i] < A[j] < A[k],那么结果直接为 count(A[i]) * count(A[j]) * count(A[k])

其中,C(m,n) 为组合数,count(x) 为数字 x 的个数。

因此,我们还要先对数组中各个数字进行计数,然后使用排序+首尾指针的方法,用上述规律可以很快计算出结果,最后别忘了对结果取余 10 ** 9 + 7时间复杂度为 O(N^2)。

注意:外层循环指向的第一个数以及使用首尾指针指向第二、第三个数时,每次找到一组解,要移动到下次不同的数字处,防止重复计算。

Python3 实现:
class Solution:
    def threeSumMulti(self, A: List[int], target: int) -> int:
        def count(i, j, k):  # 计算一组解 A[i],A[j] 和 A[k] 有多少种数目
            if i < j < k:
                return dic[i] * dic[j] * dic[k]
            elif i == j < k:
                return (dic[i] * (dic[i] - 1) // 2) * dic[k]
            elif i < j == k:
                return dic[i] * (dic[k] * (dic[k] - 1) // 2)
            else:  # i == j == k
                return dic[i] * (dic[i] - 1) * (dic[k] - 2) // 6
                
        N = len(A)
        A.sort()  # 升序排序
        dic = collections.Counter(A)  # 对各个数字计数
        ans = 0
        for i in range(N-2):
            if i > 0 and A[i] == A[i-1]:  # 移到下一个不同的数字处,防止重复计算
                continue
            low, high = i + 1, N - 1
            while low < high:
                if A[i] + A[low] + A[high] == target:
                    ans += count(A[i], A[low], A[high])
                    if A[i] == A[low]:  # 移动下一个不同的数字处,防止重复计算
                        low += dic[A[low]] - 1
                    else:
                        low += dic[A[low]]
                    high -= dic[A[high]]  # 移动下一个不同的数字处,防止重复计算
                elif A[i] + A[low] + A[high] < target:
                    if A[i] == A[low]:  # 移动下一个不同的数字处,防止重复计算
                        low += dic[A[low]] - 1
                    else:
                        low += dic[A[low]]
                else:
                    high -= dic[A[high]]  # 移动下一个不同的数字处,防止重复计算
        return ans % (10 ** 9 + 7)  # 对结果取余

改进(分三种情况考虑):

上述方法其实可以进一步改进。因为数组中可能有很多重复的元素,所以采取上述方法每次都要定位到下一个不同的数字,比较慢。想到能不能对不同数字进行遍历求解答案呢?答案是可以的。但是我们发现,对不同数字进行遍历,只能处理 A[i] != A[j] != A[k] 情况;而对于其他情况,我们可以单独考虑,把其他情况产生的数目累加到结果上即可。具体做法如下:

  • 准备工作:使用 setA = sorted(set(A)) 来对数组去重并按照升序排序,使用 dic = collections.Counter(A) 统计不同数字的次数。
  • 三个数都相同,对于 setA 中的每个数字 num,如果 dic[num] >= 3 and num * 3 == target,说明三个数都相同,我们根据上述数学规律能够得出三个数相同所产生的结果,并进行累加。
  • 只有两个数相同,对于 setA 中的每个数字 num,如果 dic[num] >= 2 and target - num * 2 != num,说明只有两个数相同,我们根据上述数学规律能够得出只有两个数相同所产生的结果,并进行累加。
  • 三个数都不同,对于 setA 中的每个数字 num,使其指向第一个数,使用首尾指针指向第二、第三个数,根据上述数学规律能够得出三个数都不同所产生的结果,并进行累加。
  • 最后,对三种情况累加的结果取余 10 ** 9 + 7,就是答案。

这种分情况考虑的做法效率更高,实现代码如下:

class Solution:
    def threeSumMulti(self, A: List[int], target: int) -> int:
        setA = sorted(set(A))
        dic = collections.Counter(A)
        ans = 0
        
        for num in setA:  # A[i] == A[j] == A[k]
            if dic[num] >= 3 and num * 3 == target:
                ans += dic[num] * (dic[num] - 1) * (dic[num] - 2) // 6
        
        for num in setA:  # A[i] == A[j] < A[k] or A[i] < A[j] == A[k]
            if dic[num] >= 2 and target - num * 2 != num:
                ans += (dic[num] * (dic[num] - 1) // 2) * dic[target - num * 2]
        
        for i in range(len(setA) - 2):  # A[i] < A[j] < A[k]
            low, high = i + 1, len(setA) - 1
            while low < high:
                if setA[i] + setA[low] + setA[high] == target:
                    ans += dic[setA[i]] * dic[setA[low]] * dic[setA[high]]
                    low += 1
                    high -= 1
                elif setA[i] + setA[low] + setA[high] < target:
                    low += 1
                else:
                    high -= 1
        return ans % (10 ** 9 + 7)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 数组 记录《剑指offer》中所有关于数组的题目,以及LeetCode中的相似题目 相关题目列表 说明 由于简书...
    wenmingxing阅读 1,512评论 1 12
  • 线性表中的双指针法是指通过两个指针(游标)来指示线性表中的元素的方法。双指针的使用本身并没有什么神奇之处,但是通过...
    Like_eb56阅读 510评论 0 0
  • 前言 2. 实现 Singleton 3. 数组中重复的数字 4. 二维数组中的查找 5. 替换空格 6. 从尾到...
    Observer_____阅读 2,909评论 0 1
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,320评论 0 2
  • 2017年的12月30日,我在广州机场写下这段文字,送给2018年活力四射的自己。 每年元旦时间都要来广州来看一下...
    服装厂梁姐阅读 87评论 0 0