Найти подмножество с элементами K, которые наиболее близки друг другу

Учитывая массив целых чисел N, как вы можете найти подмножество размера K с ближайшими друг к другу элементами?

Пусть близость для подмножества (x1, x2, x3,.. xk) определяется как:

enter image description here

2 <= N <= 10^5

2 <= K <= N

: Массив может содержать дубликаты и не может быть отсортирован.

Решение для грубой силы очень велико для больших N, и оно не проверяет, существует ли более 1 решение:

N = input()
K = input()
assert 2 <= N <= 10**5
assert 2 <= K <= N
a = []
for i in xrange(0, N):
    a.append(input())
a.sort()

minimum = sys.maxint
startindex = 0

for i in xrange(0,N-K+1):
    last = i + K
    tmp = 0
    for j in xrange(i, last):
        for l in xrange(j+1, last):
            tmp += abs(a[j]-a[l])
            if(tmp > minimum):
                break

    if(tmp < minimum):
        minimum = tmp
        startindex = i #end index = startindex + K?

<сильные > Примеры:

N = 7
K = 3
array = [10,100,300,200,1000,20,30]
result = [10,20,30]

N = 10
K = 4
array = [1,2,3,4,10,20,30,40,100,200]
result = [1,2,3,4]

Ответ 1

Ваше текущее решение O(NK^2) (предполагается K > log N). С некоторым анализом, я считаю, вы можете уменьшить это до O(NK).

Ближайший набор размеров K будет состоять из элементов, смежных в отсортированном списке. Вы должны сначала отсортировать массив, поэтому последующий анализ будет предполагать, что каждая последовательность чисел K сортируется, что позволяет упростить двойную сумму.

Предполагая, что массив отсортирован таким образом, что x[j] >= x[i], когда j > i, мы можем переписать метрику близости, чтобы исключить абсолютное значение:

enter image description here

Затем мы переписываем ваши обозначения в двойное суммирование с простыми оценками:

enter image description here

Обратите внимание, что мы можем переписать внутреннее расстояние между x[i] и x[j] как третье суммирование:

enter image description here

где я использовал d[l] для упрощения ввода нотации:

enter image description here

Обратите внимание, что d[l] - это расстояние между каждым смежным элементом в списке. Посмотрите на структуру внутренних двух суммирования для фиксированного i:

j=i+1         d[i]
j=i+2         d[i] + d[i+1]
j=i+3         d[i] + d[i+1] + d[i+2]
...
j=K=i+(K-i)   d[i] + d[i+1] + d[i+2] + ... + d[K-1]

Обратите внимание на треугольную структуру внутренних двух суммирований. Это позволяет переписать внутренние два суммирования как одно суммирование в терминах расстояний соседних членов:

total: (K-i)*d[i] + (K-i-1)*d[i+1] + ... + 2*d[K-2] + 1*d[K-1]

который сводит общую сумму к:

enter image description here

Теперь мы можем посмотреть на структуру этого двойного суммирования:

i=1     (K-1)*d[1] + (K-2)*d[2] + (K-3)*d[3] + ... + 2*d[K-2] + d[K-1]
i=2                  (K-2)*d[2] + (K-3)*d[3] + ... + 2*d[K-2] + d[K-1]
i=3                               (K-3)*d[3] + ... + 2*d[K-2] + d[K-1]
...
i=K-2                                                2*d[K-2] + d[K-1]
i=K-1                                                           d[K-1]

Опять же, обратите внимание на треугольный узор. Тогда общая сумма будет равна:

1*(K-1)*d[1] + 2*(K-2)*d[2] + 3*(K-3)*d[3] + ... + (K-2)*2*d[K-2] 
  + (K-1)*1*d[K-1]

Или, записанный как одно суммирование:

enter image description here

Это компактное единственное суммирование смежных различий является основой для более эффективного алгоритма:

  • Сортировка массива, порядок O(N log N)
  • Вычислить различия для каждого смежного элемента, порядок O(N)
  • Перейдем к каждой последовательности N-K различий и вычислим указанную сумму, порядок O(NK)

Обратите внимание, что второй и третий шаги могут быть объединены, хотя с Python ваш пробег может меняться.

Код:

def closeness(diff,K):
  acc = 0.0
  for (i,v) in enumerate(diff):
    acc += (i+1)*(K-(i+1))*v
  return acc

def closest(a,K):
  a.sort()
  N = len(a)
  diff = [ a[i+1] - a[i] for i in xrange(N-1) ]

  min_ind = 0
  min_val = closeness(diff[0:K-1],K)

  for ind in xrange(1,N-K+1):
    cl = closeness(diff[ind:ind+K-1],K)
    if cl < min_val:
      min_ind = ind
      min_val = cl

  return a[min_ind:min_ind+K]

Ответ 2

Эта процедура может быть выполнена с помощью O(N*K), если сортировка A. Если A не сортируется, время будет ограничено процедурой сортировки.

Это основано на 2 фактах (имеет значение только при заказе A):

  • Ближайшие подмножества всегда будут следующими
  • При расчете близости следующих элементов K сумма расстояний может быть рассчитана как сумма каждого двух последующих элементов времени (K-i)*i, где i составляет 1,...,K-1.
  • При повторении с помощью отсортированного массива избыточно пересчитывать всю сумму, мы можем вместо этого удалить K раз расстояние между двумя предыдущими наименьшими элементами и добавить K раз расстояние между двумя новые самые большие элементы. этот факт используется для вычисления близости подмножества в O(1) с использованием близости предыдущего подмножества.Дел >

Здесь псевдокод

List<pair> FindClosestSubsets(int[] A, int K)
{
    List<pair> minList = new List<pair>;
    int minVal = infinity;
    int tempSum;
    int N = A.length;

    for (int i = K - 1; i < N; i++)
    {
        tempSum = 0;

        for (int j = i - K + 1; j <= i; j++)
              tempSum += (K-i)*i * (A[i] - A[i-1]);

        if (tempSum < minVal)
        {
              minVal = tempSum;
              minList.clear();
              minList.add(new pair(i-K, i);
        }

        else if (tempSum == minVal)
              minList.add(new pair(i-K, i);
    }

    return minList;
}

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

Ответ 3

попробуйте следующее:

N = input()
K = input()
assert 2 <= N <= 10**5
assert 2 <= K <= N
a = some_unsorted_list
a.sort()

cur_diff = sum([abs(a[i] - a[i + 1]) for i in range(K - 1)])
min_diff = cur_diff
min_last_idx = K - 1
for last_idx in range(K,N):
    cur_diff = cur_diff - \
               abs(a[last_idx - K - 1] - a[last_idx - K] + \
               abs(a[last_idx] - a[last_idx - 1])
    if min_diff > cur_diff:
        min_diff = cur_diff
        min_last_idx = last_idx

Из min_last_idx вы можете вычислить min_first_idx. Я использую диапазон для сохранения порядка idx. Если это python 2.7, это займет линейно больше ОЗУ. Это тот же самый алгоритм, который вы используете, но немного более эффективный (меньший по сложности), поскольку он меньше суммирует все.

Ответ 4

itertools для спасения?

from itertools import combinations

def closest_elements(iterable, K):
    N = set(iterable)
    assert(2 <= K <= len(N) <= 10**5)

    combs = lambda it, k: combinations(it, k)
    _abs = lambda it: abs(it[0] - it[1])
    d = {}
    v = 0

    for x in combs(N, K):
        for y in combs(x, 2):
            v += _abs(y)

        d[x] = v
        v = 0

    return min(d, key=d.get)

>>> a = [10,100,300,200,1000,20,30]
>>> b = [1,2,3,4,10,20,30,40,100,200]
>>> print closest_elements(a, 3); closest_elements(b, 4)
(10, 20, 30) (1, 2, 3, 4)

Ответ 5

После сортировки мы можем быть уверены, что если x1, x2,... xk являются решением, то x1, x2,... xk являются смежными элементами, right?

Итак,

  • взять интервалы между цифрами
  • суммируйте эти интервалы, чтобы получить интервалы между k числами
  • Выберите наименьшее из них

Ответ 6

Мое первоначальное решение состояло в том, чтобы просмотреть все окна элементов K и умножить каждый элемент на m и взять сумму в этом диапазоне, где m инициализируется - (K-1) и увеличивается на 2 на каждом шаге и принимает минимальная сумма из всего списка. Таким образом, для окна размером 3, m равно -2, а значения для диапазона будут -2 0 2. Это связано с тем, что я заметил свойство, что каждый элемент в окне K добавляет определенный вес к сумме. Например, если элементы являются [10 20 30], сумма равна (30-10) + (30-20) + (20-10). Итак, если разбить выражение, то получим 2 * 30 + 0 * 20 + (-2) * 10. Это может быть достигнуто в O (n) времени, и вся операция будет в O (NK) времени. Однако оказывается, что это решение не является оптимальным, и есть некоторые случаи краев, когда этот алгоритм терпит неудачу. Мне еще предстоит выяснить эти случаи, но поделился решением в любом случае, если кто-то может понять что-то полезное от него.

for(i = 0 ;i <= n - k;++i)
{
    diff = 0;
    l = -(k-1);
    for(j = i;j < i + k;++j)
    {
        diff += a[j]*l;
        if(min < diff)
            break;
        l += 2;
    }
    if(j == i + k && diff > 0)
    min = diff;
}