我想在数组中找到所有不同的三元组(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());
}
答案 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++;
}
,然后您可以将哈希集作为列表的末尾返回。