在数组中查找三元组(a,b,c),使得a + b + c = 0

时间:2019-04-30 16:34:15

标签: java algorithm

我想在数组中找到所有不同的三元组(a,b,c),使得a + b + c =0。

我在Java中实现了该算法,但是当输入很大(例如100,000个零等)时,我就获得了TLE。

对于100,000个零,它应仅输出(0,0,0)。

有人可以提供一些有关如何加快速度的想法吗?

下面是我编写的函数。它以数组作为输入,并返回具有所需属性的所有唯一三元组作为列表。

public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> ll = new ArrayList<List<Integer>>();
        for(int i = 0; i < nums.length - 1; i++){
            int x = nums[i];
            int start = i + 1;
            int end = nums.length - 1;
            int sum = -x;
            while(start < end){
                int y = nums[start] + nums[end];
                if(y == sum){
                    List<Integer> list = new ArrayList<Integer>();
                    list.add(nums[start]);
                    list.add(nums[end]);
                    list.add(x);
                    Collections.sort(list);
                    ll.add(list);
                }
                if(y < sum)
                    start++;
                else
                    end--;
            }
        }
        return ll.stream()
                 .distinct()
                 .collect(Collectors.toList());
    }

4 个答案:

答案 0 :(得分:2)

我认为对于时间复杂度您无能为力。两个索引必须独立地探索数组(起点/终点除外),而第三个索引可以像在您的算法中那样受到约束,这意味着复杂度为O(n 2 )。这支配了对数组的初步排序,即O(n·log(n)),以及“去乘法”步骤,即O(n)。

我之所以写“去乘法”是因为不希望有“去重复”:假设数组是[-1,-1,0,2]。对它进行重复数据删除将消除唯一的解决方案。但是一个解决方案包含的整数不能超过两次,除非它是0,在这种情况下,[0,0,0]是一个解决方案。所有出现两次以上或在0情况下为三次的整数都是多余的,可以在排序之后和主算法之前一遍消除。

关于因素,可以通过将探索限制在有意义的范围内来加以改善。我会修改算法,方法是使成对移动的索引对一直移动到它们相遇,从它们相遇的地方向外开始,直到较低的索引到达主索引,或者较高的索引到达数组的末尾。扫描的起点可以在每次扫描中记住,随着主要索引的上升而向下调整。如果起始点(实际上是相邻索引的起始对)在当前范围之外,则可以省略扫描。寻找初始起点是算法的另一部分,排序后可以是O(log(n)),但是非常简单的O(n)版本也可以。

抱歉,我现在没有时间将以上所有内容转换为Java代码。我现在所能做的就是记下对数组进行排序之后的“去乘法”代码(未经测试):

int len = 1;
int last = nums[0];
int count = 1;
for (int i = 1; i < nums.length; i++) {
    int x = nums[i];
    if (x != last) {
        nums[len++] = x;
        last = x;
        count = 1;
    } else if (count < 2 || x == 0 && count < 3) {
        nums[len++] = x;
        count++;
    }
}
// use len instead of nums.length from this point on

答案 1 :(得分:1)

我看到的重要时间部分是,对于100,000个零的示例,您将在每种可能的情况下击中if(y == sum)块。这似乎是性能最差的情况,因为您永远不会跳过该块。

我能看到的最大改进是,首先消除重复输入。不幸的是,集合无法使用,因为我们仍然需要维护多达三个相同的条目。因此,我的建议是,在进行排序之后,循环遍历输入数组,并且每当连续遇到三个以上的数字副本时,请删除多余的部分。这些问题并不需要它们,只是浪费时间。

答案 2 :(得分:0)

您可以创建一个List(其实现是ArrayList)来存储您已有的组合。始终以

格式存储新值
a,b,c

其中 a <= b <= c

因此,无论何时获得可能已经找到或可能尚未找到的组合,都以相同的格式生成一个String并检查它是否存在于List中。如果是这样,则不要add。否则add到您的List。之后,您可以将找到的值转换为数字值。如果您想加快速度,可以创建一个class,例如:

class XYZ {
    public int x;
    public int y;
    public int z;

    public XYZ(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public isMatch(int x, int y, int z) {
        return (this.x == x) &&
               (this.y == y) &&
               (this.z == z);
    }

    public static boolean anyMatch(List<XYZ> list, int x, int y, int z) {
        for (XYZ xyz : list) {
            if (xyz.isMatch(x, y, z)) return true;
        }
        return false;
    }

    public static void addIfNotExists(List<XYZ> list, int x, int y, int z) {
        if (!anyMatch(list, x, y, z)) list.add(new XYZ(x, y, z));
    }
}

,您可以将此类用于您的目的,只需确保 x <= y <= z

答案 3 :(得分:0)

最后,可以通过使用哈希表来消除对非唯一三元组的过滤,该哈希表按已排序的顺序存储三元组,因此三元组的所有组合(具有不同的顺序)都将被存储一次。

使用哈希图/哈希集代替数组列表。

HashSet<List<Integer>> ll = new HashSet<List<Integer>>();
    .   .   .
    list.addAll(a,b,c)
    Collections.sort(list)
    ll.add(list)

除此之外,您还可以使用另一个查找表来确保nums []中的每个重复项仅用于计算三元组一次。

lookup_table = HashMap();
for(int i = 0; i < nums.length - 1; i++){
    // we have already found triplets starting from nums[i]
    // eg. [-1,-1,0,1], we don't need to calculate
    // the same triplets for the second '-1'. 
    if (lookup_table.contains(nums[i]))
        continue;

    // Mark nums[i] as 'solved'
    lookup_table.add(nums[i])

    // usual processing here
    int x = nums[i];

或者,由于您的nums []列表已经排序,因此您可以跳过重复的项目,而无需其他查找表。

i = 0;
while (i < nums.length - 1){
    // we have already found triplets starting from nums[i]
    // eg. [-1,-1,0,1], we don't need to calculate
    // the same triplets for the second '-1'.
    x = nums[i];

    // skip repeating items
    while (x == nums[i++]);

    // usual processing here
    .  .  .
    i++;
 }

,然后您可以将哈希集作为列表的末尾返回。