美文网首页
LeetCode 923. Three Sum With Mul

LeetCode 923. Three Sum With Mul

作者: Sisyphus235 | 来源:发表于2019-02-23 14:05 被阅读0次

    Question 923.
    Given an integer array A, and an integer target, return the number of tuples i, j, k such that i < j < k and A[i] + A[j] + A[k] == target.

    As the answer can be very large, return it modulo 10^9 + 7.

    Example 1:

    Input: A = [1,1,2,2,3,3,4,4,5,5], target = 8
    Output: 20
    Explanation: 
    Enumerating by the values (A[i], A[j], A[k]):
    (1, 2, 5) occurs 8 times;
    (1, 3, 4) occurs 8 times;
    (2, 2, 4) occurs 2 times;
    (2, 3, 3) occurs 2 times.
    

    Example 2:

    Input: A = [1,1,2,2,2,2], target = 5
    Output: 12
    Explanation: 
    A[i] = 1, A[j] = A[k] = 2 occurs 12 times:
    We choose one 1 from [1,1] in 2 ways,
    and two 2s from [2,2,2,2] in 6 ways.
    

    Note:

    1. 3 <= A.length <= 3000
    2. 0 <= A[i] <= 100
    3. 0 <= target <= 300

    题目分析

    tag

    类别是 arrays and strings,特点是连续存储空间中 members 的调度。

    题目

    本题要在连续存储空间中寻找 3 个元素,要求 3 个元素的和等于目标值的个数,元素可能重复。

    思路

    示例:
    A = [1,1,2,2,3,3,4,4,5,5]
    target = 8

    1. 相比于之前的三数和不同条件的题目,本题叠加了相同元素组合数量的求解。总体思路是先排序,找到目标组合,获得组合中元素的数量计算组合由不同元素组成的数量,再加总所有可能性。
      首先排序,A = [1,1,2,2,3,3,4,4,5,5];
      接着取第一个元素 1,1 * 3 = 3,小于目标值 8。最小指针指向 1,最大指针指向 5,当前和 1 + 1 + 5 = 7,小于目标值 8,最小指针右移指向 2。当前和 1 + 2 + 5 = 8,等于目标值 8,可能组合记录 (1, 2, 5)。接着最大指针和最小指针分别向左、右移动跳过重复的元素,最小指针指向 3, 最大指针指向 4,当前和 1 + 3 + 4 = 8, 等于目标值 8,可能组合记录 (1, 3, 4)。接着最大指针和最小指针分别向左、右移动跳过重复的元素,最小指针指向 4, 最大指针指向 3,最小指针大于了最大指针,当前结束;
      然后取第二个元素 1,等于前面的元素,跳过;
      继续取第三个元素 2,2 * 3 = 6,小于目标值 8。最小指针指向 2,最大指针指向 5,当前和 2 + 2 + 5 = 9,大于目标值 8,最大指针左移跳过重复元素指向 4。当前和 2 + 2 + 4 = 8,等于目标值 8,可能组合记录 (2, 2, 4)。接着最大指针和最小指针分别向左、右移动跳过重复的元素,最小指针指向第一个 3, 最大指针指向第二个 3,当前和 2 + 3 + 3 = 8,等于目标值 8,可能组合记录 (2, 3, 3);
      接着取第四个元素 2,等于前面的元素,跳过;
      再取第五个元素 3,3 * 3 = 9,大于目标值 8,终止,当前可能组合记录是 (1, 2, 5), (1, 3, 4), (2, 2, 4), (2, 3, 3);
      统计各个元素的个数 { 1: 2, 2: 2, 3: 2, 4: 2, 5: 2};
      计算所有可能性:(1, 2, 5) 的可能性是 2 * 2 * 2 = 8,(1, 3, 4) 的可能性是 2 * 2 * 2 = 8,(2, 2, 4) 的可能性是 1 * 1 * 2 = 2,(2, 3, 3)的可能性是 1 * 1 * 2 = 2,总计 8 + 8 + 2 + 2 = 20。
      算法复杂度是 O(n^2)
    import sys
    from typing import List
    from collection import defaultdict
    
    def three_sum_with_multiplicity(nums: List[int], target: int) -> int:
        def _skip_left(left: int, right: int) -> int:
            """
            略过重复的最小指针
            """
            while left < right and nums[left] == nums[left + 1]:
                left += 1
            return left
    
        def _skip_right(left: int, right: int) -> int:
            """
            略过重复的最大指针
            """
            while left < right and nums[right] == nums[right - 1]:
                right -= 1
            return right
    
        # 初始化符合条件记录个数为 0
        count = 0
        # 根据题中条件返回 bound 的余数
        bound = 10 ** 9 + 7
        # 记录符合目标值的三个数的组合情况
        record = []
        # 输入 array 排序
        nums.sort()
        length = len(nums)
        # 三个数之和,所以遍历到倒数第三个数
        for i in range(length)[:-2]:
            # 如果不是第一个数且等于 array 前一个数,不会产生新的组合,跳过
            if i == 0 or nums[i] != nums[i - 1]:
                # 如果第一个数的三倍已经大于目标值,则跳出循环,因为后面的数至少都等于第一个数
                if nums[i] * 3 > target:
                    break
                # 当前值下一个是最小指针, array 最后一个是最大指针
                left, right = i + 1, length - 1
                # 最小最大指针相交前不断循环
                while left < right:
                    # 当前三个数的和
                    current_sum = nums[i] + nums[left] + nums[right]
                    # 当前和等于目标值,则记录当前组合,移动最小/大指针,跳过重复
                    if current_sum == target:
                        record.append((nums[i], nums[left], nums[right]))
                        left = _skip_left(left, right)
                        right = _skip_right(left, right)
                        left += 1
                        right -= 1
                    # 如果当前和小于目标值,则最小指针右移,跳过重复
                    elif current_sum < target:
                        left = _skip_left(left, right)
                        left += 1
                    # 如果当前和大于等于目标值,则最大值指针左移
                    else:
                        right = _skip_right(left, right)
                        right -= 1
        # 统计元素个数
        element = defaultdict(int)
        for e in nums:
            element[e] += 1
        # 计算符合条件组合的个数
        for a, b, c in record:
            if a == b and b == c:
                # 如果三个数相等,则可能性是组合的计算 n * (n-1) * (n-2) / (3 * 2)
                count = (count + element[a] * (element[a] - 1) * (element[a] - 2) // 6) % bound
            elif a == b:
                # 如果两个数相等,则相等两个数的可能性是组合的计算 n * (n - 1) / 2
                count = (count + element[a] * (element[a] - 1) // 2 * element[c]) % bound
            elif b == c:
                count = (count + element[c] * (element[c] - 1) // 2 * element[a]) % bound
            else:
                # 如果都不相等,连乘即可
                count = (count + element[a] * element[b] * element[c]) % bound
    
        return count
    
    1. 发现上面的 python 代码非常繁琐,还有优化空间。主要冗余的部分是指针检索的时候跳过重复的部分。调整上面解法的逻辑顺序,先获得每个元素个数的统计,接着去重后再排序,这样的列表更容易处理。

    Python

    def three_sum_with_multiplicity_optimize(A: List[int], target: int) -> int:
        from collections import Counter
        bound = 10 ** 9 + 7
        element = Counter(A)
        A = sorted(element.items(), key=lambda x: x[0])
        res = 0
        for i in range(len(A)):
            j = i
            k = len(A) - 1
            new_target = target - A[i][0]
            while j <= k:
                if A[j][0] + A[k][0] < new_target:
                    j += 1
                elif A[j][0] + A[k][0] > new_target:
                    k -= 1
                else:
                    if A[i][0] == A[k][0]:
                        res = (res + A[i][1] * (A[i][1] - 1) * (A[i][1] - 2) // 6) % bound
                    elif A[i][0] == A[j][0]:
                        res = (res + A[k][1] * A[i][1] * (A[i][1] - 1) // 2) % bound
                    elif A[j][0] == A[k][0]:
                        res = (res + A[i][1] * A[j][1] * (A[j][1] - 1) // 2) % bound
                    else:
                        res = (res + A[i][1] * A[j][1] * A[k][1]) % bound
                    j += 1
                    k -= 1
        return res
    

    Java

    // TODO
    

    相关题目

    LeetCode 1. Two Sum
    LeetCode 167. Two Sum II
    LeetCode 170. Two Sum III
    LeetCode 15. Three Sum
    LeetCode 16. Three Sum Closest
    LeetCode 259. Three Sum Smaller

    相关文章

      网友评论

          本文标题:LeetCode 923. Three Sum With Mul

          本文链接:https://www.haomeiwen.com/subject/idcpyqtx.html