3Sum With Multiplicity

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

 

题目理解:

给定一个数组,从其中找到三个数,使其相加之和为target。同一个数字不能用两次,但是不同位置的相同数字可以一起使用。

解题思路:

从这道题目的约束中规定,数字的个数为3000个,但是数字的种类只有100种,也就是说,数组中存在大量的重复数字。如果去除这些重复数字,那么数组长度为100,使用三重循环可以找到合适的i,j,k,复杂度是N^3。也可以使用两重循环,先对数组进行排序,在确定i和j之后,使用二分法找到k,这样复杂度降低为N^2 logN。

在找到i,j,k之后,在原始数组中查看这些数字是否有重复的,用这些数字的重复次数相乘,就是这种搭配情况出现的次数。

如果i,j,k本身就有重复的数字,那么需要使用排列组合当中的选择运算C,也能计算出当前搭配情况的出现次数。

将所有情况的出现次数相加就是答案

class Solution {
    int base = 1000000007;

    public int collect(int k, int n){
        if(k > n)
            return 0;
        if(k > n - k)
            k = n - k;
        long res = 1;
        for(int i = 0; i < k; i++) {
            res *= n--;
        }
        for(int i = 0; i < k; i++)
            res /= k--;
        res %= base;
        return (int)res;
    }

    public int threeSumMulti(int[] A, int target) {
        int[] record = new int[101];
        int count = 0;
        for(int num : A) {
            if(record[num] == 0)
                count++;
            record[num]++;
        }
        int[] nums = new int[count];
        for(int i = 0, j = 0; i < 101; i++){
            if(record[i] > 0)
                nums[j++] = i;
        }
        int res = 0;
        for(int i = 0; i < count; i++){
            for(int j = i; j < count; j++){
                int left = j, right = count - 1, k = -1;
                int cur = target - nums[i] - nums[j];
                while(left <= right){
                    int mid = left + (right - left) / 2;
                    if(nums[mid] > cur)
                        right = mid - 1;
                    else if(nums[mid] < cur)
                        left = mid + 1;
                    else{
                        k = mid;
                        break;
                    }
                }
                int temp = 0;
                if(k != -1){
                    if(i == j && j == k){
                        temp = collect(3, record[nums[i]]);
                        temp %= base;
                    }
                    else if(i == j && j != k){
                        temp = 1;
                        temp *= collect(2, record[nums[i]]);
                        temp %= base;
                        temp *= record[nums[k]];
                        temp %= base;

                    }
                    else if(i != j && j == k){
                        temp = 1;
                        temp *= collect(2, record[nums[j]]);
                        temp %= base;
                        temp *= record[nums[i]];
                        temp %= base;
                    }
                    else{
                        temp = 1;
                        temp *= record[nums[i]];
                        temp %= base;
                        temp *= record[nums[j]];
                        temp %= base;
                        temp *= record[nums[k]];
                        temp %= base;
                    }
                    //System.out.println(nums[i] + " " + nums[j] + " " + nums[k] + " " + temp);
                    res += temp;
                    res %= base;
                }
            }
        }
        return res;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值