Я хочу найти все различные триплеты (a, b, c) в массиве так, чтобы a + b + c = 0.

Я реализовал алгоритм в Java, но я получаю TLE, когда ввод большой (например, 100 000 нулей и т. Д.).

Для 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
chelsea 30 Апр 2019 в 19:34

4 ответа

Лучший ответ

2

Я написал «демультипликация», потому что «дедупликация» нежелательна: предположим, что массив [-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
2
Walter Tross 1 Май 2019 в 20:32

Вы можете создать Список (это 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 .

0
Lajos Arpad 30 Апр 2019 в 17:15

Большой компонент времени, который я вижу, состоит в том, что для примера 100 000 нулей вы будете нажимать блок if (y == sum) для каждого возможного случая . Это кажется худшим случаем для производительности, так как вы никогда не пропустите этот блок.

Самое большое улучшение, которое я вижу, - это сначала лишить дубликаты ввода . К сожалению, наборы не будут работать, так как нам нужно поддерживать до трех одинаковых записей. Таким образом, я рекомендую, после вашей сортировки, перебрать входной массив и всякий раз, когда вы встречаете более трех копий числа подряд, удаляйте лишние. Они не нужны для решения проблемы и просто тратить время.

1
Drake3 1 Май 2019 в 13:57

Фильтрация неуникальных триплетов в конце может быть устранена с помощью хеш-таблицы, в которой триплеты хранятся в отсортированном порядке, поэтому все комбинации триплета (с различным порядком) сохраняются ровно один раз.

Используйте hashmap / hashset вместо массива.

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++;
 }

И тогда вы можете просто вернуть хэш-набор в виде списка в конце.

0
Niranjan Nagaraju 2 Май 2019 в 08:00