【问题标题】:Java: How to implement 3 sum?Java:如何实现 3 sum?
【发布时间】:2017-04-15 19:10:52
【问题描述】:

我正在研究 3 Sum 以自己实现它,并遇到了以下带有规则的实现:

给定一个包含 n 个整数的数组 S,S 中是否存在元素 a、b、c 使得 a + b + c = 0?查找数组中所有唯一的三元组,其总和为零。

注意:三元组 (a,b,c) 中的元素必须按非降序排列。 (即a≤b≤c) 解决方案集不得包含重复的三元组。

For example, given array S = {-1 0 1 2 -1 -4},

A solution set is:
(-1, 0, 1)
(-1, -1, 2)

以及实现(对数组进行排序,遍历列表,并使用另外两个指针接近目标):

import java.util.*;

public class ThreeSum {
    List<List<Integer>> threeSum(int[] num) {
        Arrays.sort(num);
        List<List<Integer>> res = new LinkedList<>(); 
        
        for (int i=0; i<num.length-2; i++) {
            if (i==0 || (i>0 && num[i] != num[i-1])) { //HERE
                int lo = i+1;
                int hi = num.length-1;
                int sum = 0 - num[i];
                
                while (lo < hi) {
                    if (num[lo] + num[hi] == sum) {
                        res.add(Arrays.asList(num[i], num[lo], num[hi]));
                        while (lo < hi && num[lo] == num[lo+1]) lo++; //HERE
                        while (lo < hi && num[hi] == num[hi-1]) hi--; //HERE
                        lo++; hi--;
                        
                    } else if (num[lo] + num[hi] < sum) lo++;
                    else hi--; 
               }
            }
        }
        
        return res;
    }
    
    //Driver
    public static void main(String args[]) {
        ThreeSum ts = new ThreeSum();
        int[] sum = {-1, 0, 1, 2, -1, -4};
        
        System.out.println(ts.threeSum(sum));
    }
}

我的问题是(位于评论位置://HERE),检查num[i] != num[i-1]num[lo] == num[lo+1]num[hi] == num[hi-1] 的原因是什么?假设他们应该跳过相同的结果,但这意味着什么?例子真的很有帮助。

提前感谢您,我们将接受回答/赞成票。

【问题讨论】:

  • 这总是相同的逻辑,如果有两个相同的值,你会得到相同的三元组结果,所以你跳过它。这可以通过简单地删除我猜的列表中的重复项来完成。
  • @AxelH 不,不能删除重复项 - 具有重复 {-1-1,2} 的三元组有效
  • @MBo 确实如此!没想到

标签: java arrays algorithm list


【解决方案1】:

它适用于任何 NSum(3Sum、4Sum、5Sum,...)并且速度非常快。

public class ThreeSum {
private static final int RANDOM_RANGE = 20;
private Integer array[];
private Integer arrayIndex[];
private int result[];
private int bagLength;
private int resultIndex = 0;

private void generateData(int size) {
    array = new Integer[size];
    Random random = new Random();
    for (int i = 0; i < size; i++) {
        array[i] = random.nextInt(RANDOM_RANGE) - (RANDOM_RANGE/2);
    }
}

private void markArrayIndex(int size) {
    arrayIndex = new Integer[size];
    for (int i = 0; i < size; i++) {
        arrayIndex[i] = i;
    }
}

private void prepareBeforeCalculate(int size, int sumExpected, int bagLength) {
    this.bagLength = bagLength;
    result = new int[bagLength];
    generateData(size);
    markArrayIndex(size);
}

void calculate(int size, int sumExpected, int bagLength) {
    prepareBeforeCalculate(size, sumExpected, bagLength);

    Arrays.sort(arrayIndex, (l, r) -> array[l].compareTo(array[r]));
    System.out.println(Arrays.toString(array));

    long startAt = System.currentTimeMillis();
    if (sumExpected > 0) findLeft(sumExpected, 0, 0, array.length);
    else findRight(sumExpected, 0, 0 - 1, array.length - 1);

    System.out.println("Calculating in " + ((System.currentTimeMillis() - startAt) / 1000));
}

private void findLeft(int total, int indexBag, int left, int right) {
    while (left < array.length && array[arrayIndex[left]] < 0 && indexBag < bagLength) {
        navigating(total, arrayIndex[left], indexBag, left, right);
        left++;
    }
}

private void findRight(int total, int indexBag, int left, int right) {
    while (right >= 0 && array[arrayIndex[right]] >= 0 && indexBag < bagLength) {
        navigating(total, arrayIndex[right], indexBag, left, right);
        right--;
    }
}

private void navigating(int total, int index, int indexBag, int left, int right) {
    result[indexBag] = index;
    total += array[index];
    if (total == 0 && indexBag == bagLength - 1) {
        System.out.println(String.format("R[%d] %s", resultIndex++, toResultString()));
        return;
    }

    if (total > 0) findLeft(total, indexBag + 1, left + 1, right);
    else findRight(total, indexBag + 1, left, right - 1);
}

private String toResultString() {
    int [] copyResult = Arrays.copyOf(result, result.length);
    Arrays.sort(copyResult);

    int iMax = copyResult.length - 1;
    StringBuilder b = new StringBuilder();
    b.append('[');
    for (int i = 0; ; i++) {
        b.append(array[copyResult[i]]);
        if (i == iMax)
            return b.append(']').toString();
        b.append(", ");
    }
}

}

public class ThreeSumTest {
@Test
public void test() {
    ThreeSum test = new ThreeSum();
    test.calculate(100, 0, 3);
    Assert.assertTrue(true);
}

}

【讨论】:

    【解决方案2】:

    以下程序以 O(N*2) 找到成对的三个整数

    1. 对输入数组进行排序
    2. 并在 for 循环中迭代每个元素,并在为两个 sum 开发的程序中检查 sum。

    排序后线性时间的两个和 -> https://stackoverflow.com/a/49650614/4723446

    公共类 ThreeSum {

    private static int countThreeSum(int[] numbers) {
        int count = 0;
    
        for (int i = 0; i < numbers.length; i++) {
            int front = 0, rear = numbers.length - 1;
    
            while (front < rear) {
                if (numbers[front] + numbers[rear] + numbers[i] == 0) {
                    System.out.printf(String.format("Front : {%d}  Rear : {%d}  I : {%d}  \n", numbers[front],
                            numbers[rear], numbers[i]));
                    front++;
                    rear--;
                    count++;
                } else {
                    if (Math.abs(numbers[front]) > Math.abs(numbers[rear])) {
                        front++;
                    } else {
                        rear--;
                    }
                }
            }
        }
    
        return count;
    }
    
    public static void main(String[] args) {
        int[] numbers = { 1, 3, 5, 7, 12, 16, 19, 15, 11, 8, -1, -3, -7, -8, -11, -17, -15 };
        Arrays.sort(numbers);
        System.out.println(countThreeSum(numbers));
    }
    

    }

    【讨论】:

    • i 和 front 这里的值相同
    【解决方案3】:

    三个句子都用于避免重复输出。
    考虑一个排序列表

    {-2, -2 , 1, 1}

    如果没有检查num[i] != num[i-1],程序的输出将是
    (-2, 1, 1)(-2, 1, 1)

    它们是两个重复的三元组。

    检查num[lo] != num[lo + 1]num[hi] != num[hi - 1] 是出于同样的原因。 考虑一个排序列表

    {-2,-1,-1,0,3}
    

    如果没有检查 num[lo],您将得到 (-2,-1,3)(-2,-1,3) 作为输出。

    不过,我想为这个问题推荐一个更好的解决方案。您可以计算列表中两个数字的总和,并通过哈希或二进制搜索找到第三个数字。它将帮助您获得 O(n^2logn) 时间复杂度,而不是 O(n^3)。 (我错了,这个算法的时间复杂度是O(n^2),不好意思。)

    【讨论】:

    • 非常感谢您的澄清。在我回答这个问题之前,你是如何计算出时间复杂度为 O(n^3) 的?什么是快速查看实现并立即判断时间复杂度的好方法?
    • @JoKo 我同意,你的算法似乎是 O(n^2)。这是三和的最佳解决方案。对不起我的粗心。
    • 不用担心,但是 O(n^2) 怎么样?是什么提示的?
    • 忘记密码。让我们只关注算法本身。
    • 第一步排序将至少花费 O(nlogn)。在排序列表中查找三元组的第二步可以看作是在排序列表中查找总和为某个值的二元组的 O(n) 执行。显然,在排序列表中找到那些双峰是 O(n)。所以第二步的复杂度是 O(n*n) 。 O(n^2) + O(nlogn) 仍然是 O(n^2)。
    【解决方案4】:

    这将防止列表中出现重复的三元组。

    比如和你一起测试:

    int[] sum = {-1, 0, 1, 2, -1, -4};
    

    将按如下方式排序:

    sum = {-4, -1, -1, 0, 1, 2};
    

    你看到你有两次-1。如果没有这些测试,您将测试两次 if -1 = 0 + 1。这没什么用,所以算法只是搜索下一个不同的值。

    您可以删除排序列表中的重复项以防止这些测试。

    感谢 MBo,我们无法删除重复项,因为我们可以拥有具有相同值(但索引不同)的三元组

    【讨论】:

      【解决方案5】:

      假设您有{-1,-1,0,1,2,4} 并考虑三元组 num[0]、num[2]、num[3] (-1,0,1)。

      lo=0 在这里。要排除具有相同值的三元组 num[1]、num[2]、num[3],我们应该增加 lo 并传递重复项

      【讨论】:

      • 通过重复,是否意味着不需要两次计算相同的三元组和(例如(-1,0,1)并再次计算(-1,0,1),因为有两个 -1),或者三元组和即使 ==0 也不能有重复项,例如 (-1, -1, 2)?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-18
      • 2018-08-05
      • 2023-01-19
      • 1970-01-01
      • 2021-01-31
      • 2018-11-08
      相关资源
      最近更新 更多