Найти числа в списке <int> с заданной разницей

1

Для заданного пробела, разделенного пробелом, числа, наиболее эффективный способ подсчета суммарных пар чисел, имеющих разность N.

например, в командной строке:

5 2

где 5 - количество чисел, которым нужно следовать, и 2 - требуемая разница

1 5 3 4 2

5 номеров, подлежащих рассмотрению

Выход должен быть 3, потому что (5,3), (4,2) и (3,1) все имеют разность 2

Я могу заставить этот алгоритм работать, но есть ли более эффективный способ сделать это, если у вас есть большие наборы чисел для работы? Я включил три варианта сравнения, а второй должен быть лучше третьего, но есть ли что-то, что я забываю, что может сделать его намного быстрее?

private static void Difference()
{
    string[] firstInput = SplitInput(Console.ReadLine());

    int numberOfNumbers = int.Parse(firstInput[0]);
    int diffOfNumbers = int.Parse(firstInput[1]);

    string[] secondInput = SplitInput(Console.ReadLine());

    List<int> numbers = secondInput.Select(x => Int32.Parse(x)).ToList();

    int possibleCombinations = 0;

    // Option 1
    foreach (int firstNumber in numbers)
    {
        List<int> compareTo = numbers.GetRange(numbers.IndexOf(firstNumber) + 1, numbers.Count - numbers.IndexOf(firstNumber) - 1);

        foreach (int secondNumber in compareTo)
        {
            int diff = firstNumber - secondNumber;

            if (Math.Abs(diff) == diffOfNumbers)
            {
                possibleCombinations++;
            }
        }
    }

    // Option 2
    foreach (int firstNumber in numbers)
    {
        if (numbers.Contains(firstNumber + diffOfNumbers))
        {
                possibleCombinations++;
        }
    }

    // Option 3
    foreach (int firstNumber in numbers)
    {
        foreach (int secondNumber in numbers)
        {
            int diff = firstNumber - secondNumber;

            if(Math.Abs(diff) == diffOfNumbers)
            {
                possibleOptions++;
            }
        }
    }

    Console.WriteLine(string.Format("Possible number of options are: {0}", possibleCombinations));
    Console.ReadLine();
}

private static string[] SplitInput(string input)
{
    return input.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
  • 1
    А как насчет дубликатов, а именно: 5, 2, 5 5 3 3 3?
  • 0
    Вы можете предположить, что нет дубликатов
Показать ещё 1 комментарий
Теги:
generic-collections

4 ответа

4
Лучший ответ

Если дублирующиеся номера не допускаются или игнорируются (только подсчет уникальных пар), вы можете использовать HashSet<int>:

HashSet<int> myHashSet = ...
int difference = ...
int count;
foreach (int number in myHashSet)
{
    int counterpart = number - difference;
    if (myHashSet.Contains(counterpart))
    {
        count++;
    }
}
  • 0
    Таким образом, при условии отсутствия дубликатов, HashSet.Contains будет быстрее и эффективнее, чем List.Contains? Если так, то намного?
  • 0
    Зависит. Смотрите здесь для очень тщательного анализа производительности. stackoverflow.com/questions/150750/hashset-vs-list-performance
Показать ещё 3 комментария
2

Учитывая ограничения проблемы, где N - "количество чисел, которые следует следовать" [1..N], а M - разность (N = 5 и M = 2 в примере), почему бы просто не вернуть N - M?

  • 1
    Я не думаю, что вы предполагаете, что числа 1.N верны. Я предполагаю, что это просто пример данных, которые соответствуют этому формату.
  • 0
    Я думаю, что вы можете быть правы. Если перечитать его сейчас, то, похоже, он намеревается использовать «1 5 3 4 2» как вторую строку ввода, а не как выводимый набор чисел.
1

Это легко выполняется с помощью LINQ, что позволяет дублировать:

var dict = numbers.GroupBy(n => n).ToDictionary(g => g.Key, g => g.Count());
return dict.Keys.Where(n => dict.ContainsKey(difference-n)).Select(n => dict[difference - n]).Sum();

В первой строке мы создаем словарь, в котором ключи представляют собой разные числа в списке ввода (numbers), а значения - сколько раз они появляются.

Во втором случае для каждого отдельного числа в списке (эквивалентном ключам дикториала) мы посмотрим, содержит ли словарь словарь ключ для целевого номера. Если это так, мы добавляем количество раз, когда появился целевой номер, который мы ранее сохраняли как значение для этого ключа. Если нет, добавим 0. Наконец, суммируем все это.


Обратите внимание, что теоретически это может привести к арифметическим переполнениям, если нет никаких привязок, отличных от Int.MinValue и Int.MaxValue, от элементов в списке. Чтобы обойти это, нам нужно сделать "безопасную" проверку, которая сначала гарантирует, что разница не будет за пределами, прежде чем мы попытаемся ее вычислить. Это может выглядеть так:

int SafeGetCount(int difference, int number, Dictionary<int,int> dict)
{
    if(difference < 0 && number < 0 && int.MinValue - difference > number)
        return 0;
    if(difference > 0 && number > 0 && int.MaxValue - difference < number)
        return 0;
    return dict.ContainsKey(difference-number) ? dict[difference - number] : 0;
}

Обновить

Есть несколько вещей, которые яснее всего понимаю из вашего вопроса, например, хотите ли вы на самом деле рассчитывать повторяющиеся пары несколько раз, и свопинг чисел считается двумя разными парами. например, если (1,4) есть пара, то (4,1)? Мой ответ выше предполагает, что ответ на оба этих вопроса - да.

Если вы не хотите многократно подсчитывать повторяющиеся пары, перейдите к решению HashSet из других ответов. Если вы хотите подсчитать повторяющиеся пары, но не хотите считать дважды, заменив значения в паре, вам нужно немного усложниться. Например:

var dict = numbers.GroupBy(n => n).ToDictionary(g => g.Key, g => g.Count());
var sum = dict.Keys.Where(n => n*2 != difference)
      .Where(n => dict.ContainsKey(difference-n))
      .Select(n => dict[difference - n]).Sum()/2;
if(n%2 == 0)
{
    sum += dict.ContainsKey(n/2) ? dict[n/2] : 0
}    
return sum;
0

как насчет сортировки списка, а затем итерации по нему.

int PairsWithMatchingDifferenceCount(
        IEnumerable<int> source,
        int difference)
{
    var ordered = source.OrderBy(i => i).ToList();
    var count = ordered.Count;
    var result = 0;
    for (var i = 0; i < count - 1; i++)
    {
        for (var j = i + 1; j < count; j++)
        {
            var d = Math.Abs(ordered[j] - ordered[i]);
            if (d == difference)
            {
                result++;
            }
            else if (d > difference)
            {
                break;
            }
        } 
    }

    return result;
}

поэтому, в соответствии с примером, вы бы назвали это следующим образом:

PairsWithMatchingDifferenceCount(Enumerable.Range(1, 5), 2);

но, если генерация последовательности проста, поскольку вопрос подсказывает, почему не просто.

var m = 5;
var n = 2;

var result = Enumerable.Range(n + 1, m - n)
                 .Select(x => Tuple.Create(x, x - n)).Count();

или действительно,

var result = m - n;

Ещё вопросы

Сообщество Overcoder
Наверх
Меню