Откройте длинные узоры

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

 1, 2, 3, 4, 7, 15, 27, 30, 31, 81

то подпоследовательность 1, 3, 7, 15, 31. В качестве альтернативы рассмотрим 1, 2, 5, 6, 11, 15, 23, 41, 47, имеющую подпоследовательность 5, 11, 23, 47 с a = 3 и k = 2.

Можно ли это решить в O (n 2) времени? Где n - длина списка.

Меня интересует как в общем случае, когда прогрессия разностей есть ak, ak 2 ak 3 и т.д., где оба a и k являются целыми числами, и в частном случае, когда a = 1, поэтому прогрессия разности равна k, k 2 k 3 и т.д.

Ответ 1

Обновление

Я сделал усовершенствование алгоритма, что он принимает среднее значение O (M + N ^ 2) и потребности в памяти O (M + N). В основном это то же, что описанный ниже протокол, но для вычисления возможных факторов A, K для ech diference D, я предварительно загружаю таблицу. Эта таблица занимает меньше секунды для построения M = 10 ^ 7.

Я сделал реализацию C, которая занимает менее 10 минут, чтобы решить N = 10 ^ 5 различных случайных целых элементов.

Вот исходный код в C: Для выполнения просто выполните: gcc -O3 -o findgeo findgeo.c

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory.h>
#include <time.h>

struct Factor {
    int a;
    int k;
    struct Factor *next;
};

struct Factor *factors = 0;
int factorsL=0;

void ConstructFactors(int R) {
    int a,k,C;
    int R2;
    struct Factor *f;
    float seconds;
    clock_t end;
    clock_t start = clock();

    if (factors) free(factors);
    factors = malloc (sizeof(struct Factor) *((R>>1) + 1));
    R2 = R>>1 ;
    for (a=0;a<=R2;a++) {
        factors[a].a= a;
        factors[a].k=1;
        factors[a].next=NULL;
    }
    factorsL=R2+1;
    R2 = floor(sqrt(R));
    for (k=2; k<=R2; k++) {
        a=1;
        C=a*k*(k+1);
        while (C<R) {
            C >>= 1;
            f=malloc(sizeof(struct Factor));
            *f=factors[C];
            factors[C].a=a;
            factors[C].k=k;
            factors[C].next=f;
            a++;
            C=a*k*(k+1);
        }
    }

    end = clock();
    seconds = (float)(end - start) / CLOCKS_PER_SEC;
    printf("Construct Table: %f\n",seconds);
}

void DestructFactors() {
    int i;
    struct Factor *f;
    for (i=0;i<factorsL;i++) {
        while (factors[i].next) {
            f=factors[i].next->next;
            free(factors[i].next);
            factors[i].next=f;
        }
    }
    free(factors);
    factors=NULL;
    factorsL=0;
}

int ipow(int base, int exp)
{
    int result = 1;
    while (exp)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        base *= base;
    }

    return result;
}

void findGeo(int **bestSolution, int *bestSolutionL,int *Arr, int L) {
    int i,j,D;
    int mustExistToBeBetter;
    int R=Arr[L-1]-Arr[0];
    int *possibleSolution;
    int possibleSolutionL=0;
    int exp;
    int NextVal;
    int idx;
    int kMax,aMax;
    float seconds;
    clock_t end;
    clock_t start = clock();


    kMax = floor(sqrt(R));
    aMax = floor(R/2);
    ConstructFactors(R);
    *bestSolutionL=2;
    *bestSolution=malloc(0);

    possibleSolution = malloc(sizeof(int)*(R+1));

    struct Factor *f;
    int *H=malloc(sizeof(int)*(R+1));
    memset(H,0, sizeof(int)*(R+1));
    for (i=0;i<L;i++) {
        H[ Arr[i]-Arr[0] ]=1;
    }
    for (i=0; i<L-2;i++) {
        for (j=i+2; j<L; j++) {
            D=Arr[j]-Arr[i];
            if (D & 1) continue;
            f = factors + (D >>1);
            while (f) {
                idx=Arr[i] + f->a * f->k  - Arr[0];
                if ((f->k <= kMax)&& (f->a<aMax)&&(idx<=R)&&H[idx]) {
                    if (f->k ==1) {
                        mustExistToBeBetter = Arr[i] + f->a * (*bestSolutionL);
                    } else {
                        mustExistToBeBetter = Arr[i] + f->a * f->k * (ipow(f->k,*bestSolutionL) - 1)/(f->k-1);
                    }
                    if (mustExistToBeBetter< Arr[L-1]+1) {
                        idx=  floor(mustExistToBeBetter - Arr[0]);
                    } else {
                        idx = R+1;
                    }
                    if ((idx<=R)&&H[idx]) {
                        possibleSolution[0]=Arr[i];
                        possibleSolution[1]=Arr[i] + f->a*f->k;
                        possibleSolution[2]=Arr[j];
                        possibleSolutionL=3;
                        exp = f->k * f->k * f->k;
                        NextVal = Arr[j] + f->a * exp;
                        idx=NextVal - Arr[0];
                        while ( (idx<=R) && H[idx]) {
                            possibleSolution[possibleSolutionL]=NextVal;
                            possibleSolutionL++;
                            exp = exp * f->k;
                            NextVal = NextVal + f->a * exp;
                            idx=NextVal - Arr[0];
                        }

                        if (possibleSolutionL > *bestSolutionL) {
                            free(*bestSolution);
                            *bestSolution = possibleSolution;
                            possibleSolution = malloc(sizeof(int)*(R+1));
                            *bestSolutionL=possibleSolutionL;
                            kMax= floor( pow (R, 1/ (*bestSolutionL) ));
                            aMax= floor(R /  (*bestSolutionL));
                        }
                    }
                }
                f=f->next;
            }
        }
    }

    if (*bestSolutionL == 2) {
        free(*bestSolution);
        possibleSolutionL=0;
        for (i=0; (i<2)&&(i<L); i++ ) {
            possibleSolution[possibleSolutionL]=Arr[i];
            possibleSolutionL++;
        }
        *bestSolution = possibleSolution;
        *bestSolutionL=possibleSolutionL;
    } else {
        free(possibleSolution);
    }
    DestructFactors();
    free(H);

    end = clock();
    seconds = (float)(end - start) / CLOCKS_PER_SEC;
    printf("findGeo: %f\n",seconds);
}

int compareInt (const void * a, const void * b)
{
    return *(int *)a - *(int *)b;
}

int main(void) {
    int N=100000;
    int R=10000000;
    int *A = malloc(sizeof(int)*N);
    int *Sol;
    int SolL;
    int i;


    int *S=malloc(sizeof(int)*R);
    for (i=0;i<R;i++) S[i]=i+1;

    for (i=0;i<N;i++) {
        int r = rand() % (R-i);
        A[i]=S[r];
        S[r]=S[R-i-1];
    }

    free(S);
    qsort(A,N,sizeof(int),compareInt);

/*
    int step = floor(R/N);
    A[0]=1;
    for (i=1;i<N;i++) {
        A[i]=A[i-1]+step;
    }
*/

    findGeo(&Sol,&SolL,A,N);

    printf("[");
    for (i=0;i<SolL;i++) {
        if (i>0) printf(",");
        printf("%d",Sol[i]);
    }
    printf("]\n");
    printf("Size: %d\n",SolL);

    free(Sol);
    free(A);
    return EXIT_SUCCESS;
}

Demostration

Я попытаюсь продемонстрировать, что предложенный мной алгоритм O(N`2+M) в среднем для равномерно распределенной случайной последовательности. Я не математик, и я не привык делать подобные демонстрации, поэтому, пожалуйста, заполните, чтобы исправить мне любую ошибку, которую вы можете видеть.

Существует 4 отступованных петель, два первых - коэффициент N ^ 2. M - для вычисления таблицы возможных факторов).

Третий цикл выполняется только один раз в среднем для каждой пары. Вы можете увидеть это, проверяя размер таблицы предварительно рассчитанных факторов. Его размер M, когда N- > inf. Таким образом, средние шаги для каждой пары составляют M/M = 1.

Итак, доказательство проверяет, что четвертый цикл. (Тот, который пересекает добрые последовательности, выполняется меньше или равен O (N ^ 2) для всех пар.

Чтобы продемонстрировать это, я рассмотрю два случая: один, где M → N и другие, где M ~ = N. Где M - максимальная разница исходного массива: M = S (n) -S (1).

В первом случае (M → N) вероятность найти совпадение равна p = N/M. Чтобы начать последовательность, она должна совпадать с вторым и элементом b + 1, где b - длина самой лучшей последовательности до сих пор. Таким образом, цикл будет вводить N^2*(N/M)^2 раз. И средняя длина этой серии (предполагая бесконечный ряд) равна p/(1-p) = N/(M-N). Таким образом, общее количество циклов, которые будет выполняться, будет N^2 * (N/M)^2 * N/(M-N). И это близко к 0, когда M → N. Проблема здесь в том, что M ~ = N.

Теперь рассмотрим этот случай, когда M ~ = N. Предположим, что b - лучшая длина последовательности до сих пор. Для случая A = k = 1 последовательность должна начинаться до N-b, поэтому число последовательностей будет N-b, а времена, которые будут проходить для цикла, будут максимальны (N-b) * b.

При A > 1 и k = 1 мы можем экстраполировать на (N-A*b/d)*b, где d - M/N (среднее расстояние между числами). Если мы добавим для всех As от 1 до dN/b, то мы увидим верхний предел:

\sum_{A=1}^{dN/b}\left ( N-\frac{Ab}{d} \right )b=\frac{N^2d}{2}

Для случаев, когда k >= 2, мы видим, что последовательность должна начинаться до N-A*k^b/d, поэтому цикл будет вводить среднее значение A*k^b/d)*b и добавление для всех As от 1 до dN/k ^ b, это дает предел

\sum_{A=1}^{dN/k^b}\left ( N-\frac{Ak^b}{d} \right )b=\frac{bN^2d}{2k^b}

Здесь худший случай, когда b минимален. Поскольку мы рассматриваем минимальные ряды, рассмотрим очень худший случай b = 2, поэтому число проходов для 4-го цикла для данного k будет меньше

\frac{dN^2}{k^2}.

И если мы добавим все ks от 2 до бесконечности, получим:

\sum_{k=2}^{\infty } \frac{dN^2}{k^2} = dN^2 \left ( \frac{\pi ^2}{6}  -1\right )

Итак, добавив все проходы для k = 1 и k >= 2, мы имеем максимум:

<Т411 >

Заметим, что d = M/N = 1/p.

Итак, у нас есть два предела: один, который переходит в бесконечность, когда d = 1/p = M/N переходит в 1, а другой - бесконечен, когда d переходит в бесконечность. Таким образом, наш предел - это минимум обоих, и худший случай - когда пересекаются оба круга. Поэтому, если мы решим уравнение:

N^2d\left ( \frac{\pi ^2}{6} - \frac{1}{2}\right ) = N^2\left ( \frac{N}{M} \right )^2\frac{N}{M-N} =N^2\left ( \frac{1}{d} \right )^2\frac{1}{d-1}

мы видим, что максимум равен d = 1.353

Таким образом, показано, что четвертые петли будут обрабатываться менее 1,55 Н ^ 2 раза.

Конечно, это для среднего случая. В худшем случае я не могу найти способ генерировать ряды, чей четвертый цикл выше O (N ^ 2), и я твердо верю, что они не существуют, но я не математик, чтобы это доказать.

Старый ответ

Вот решение в среднем O ((n ^ 2) * cube_root (M)), где M - разность между первым и последним элементами массива. И требования к памяти O (M + N).

1. Построить массив H длины M, так что M [i-S [0]] = true, если я существует в начальном массиве и false, если он не существует.

2.- Для каждой пары в массиве S [j] S [i] do:

2.1 Проверьте, может ли это быть первым и третьим элементом возможного решения. Для этого вычислите все возможные пары A, K, которые удовлетворяют уравнению S (i) = S (j) + AK + AK ^ 2. Проверьте этот вопрос SO, чтобы узнать, как решить эту проблему. И убедитесь, что существуют второй элемент: S [i] + A * K

2.2. Проверьте также, что существует элемент с одной позицией, что лучшее решение, которое у нас есть. Например, если наилучшее решение, которое мы имеем до сих пор, состоит из 4 элементов, то проверьте, существуют ли элементы A [j] + AK + AK ^ 2 + AK ^ 3 + AK ^ 4

2.3 Если значения 2.1 и 2.2 истинны, то итерации, сколько времени этот ряд и устанавливается как bestSolution до сих пор, длиннее последнего.

Вот код в javascript:

function getAKs(A) {
    if (A / 2 != Math.floor(A / 2)) return [];
    var solution = [];
    var i;
    var SR3 = Math.pow(A, 1 / 3);
    for (i = 1; i <= SR3; i++) {
        var B, C;
        C = i;
        B = A / (C * (C + 1));
        if (B == Math.floor(B)) {
            solution.push([B, C]);
        }

        B = i;
        C = (-1 + Math.sqrt(1 + 4 * A / B)) / 2;
        if (C == Math.floor(C)) {
            solution.push([B, C]);
        }
    }

    return solution;
}

function getBestGeometricSequence(S) {
    var i, j, k;

    var bestSolution = [];

    var H = Array(S[S.length-1]-S[0]);
    for (i = 0; i < S.length; i++) H[S[i] - S[0]] = true;

    for (i = 0; i < S.length; i++) {
        for (j = 0; j < i; j++) {
            var PossibleAKs = getAKs(S[i] - S[j]);
            for (k = 0; k < PossibleAKs.length; k++) {
                var A = PossibleAKs[k][0];
                var K = PossibleAKs[k][17];

                var mustExistToBeBetter;
                if (K==1) {
                    mustExistToBeBetter = S[j] + A * bestSolution.length;
                } else {
                    mustExistToBeBetter = S[j] + A * K * (Math.pow(K,bestSolution.length) - 1)/(K-1);
                }

                if ((H[S[j] + A * K - S[0]]) && (H[mustExistToBeBetter - S[0]])) {
                    var possibleSolution=[S[j],S[j] + A * K,S[i]];
                    exp = K * K * K;
                    var NextVal = S[i] + A * exp;
                    while (H[NextVal - S[0]] === true) {
                        possibleSolution.push(NextVal);
                        exp = exp * K;
                        NextVal = NextVal + A * exp;
                    }

                    if (possibleSolution.length > bestSolution.length) {
                        bestSolution = possibleSolution;
                    }
                }
            }
        }
    }
    return bestSolution;
}

//var A= [ 1, 2, 3,5,7, 15, 27, 30,31, 81];
var A=[];
for (i=1;i<=3000;i++) {
    A.push(i);
}
var sol=getBestGeometricSequence(A);

$("#result").html(JSON.stringify(sol));

Вы можете проверить код здесь: http://jsfiddle.net/6yHyR/1/

Я поддерживаю другое решение, потому что считаю, что лучше, когда M очень большой по сравнению с N.

Ответ 2

Чтобы начать с чего-то, здесь - это простое решение в JavaScript:

var input = [0.7, 1, 2, 3, 4, 7, 15, 27, 30, 31, 81], 
    output = [], indexes, values, i, index, value, i_max_length,
    i1, i2, i3, j1, j2, j3, difference12a, difference23a, difference12b, difference23b,
    scale_factor, common_ratio_a, common_ratio_b, common_ratio_c,
    error, EPSILON = 1e-9, common_ratio_is_integer,
    resultDiv = $("#result");

for (i1 = 0; i1 < input.length - 2; ++i1) {
    for (i2 = i1 + 1; i2 < input.length - 1; ++i2) {
        scale_factor = difference12a = input[i2] - input[i1];
        for (i3 = i2 + 1; i3 < input.length; ++i3) {
            difference23a = input[i3] - input[i2];
            common_ratio_1a = difference23a / difference12a;
            common_ratio_2a = Math.round(common_ratio_1a);
            error = Math.abs((common_ratio_2a - common_ratio_1a) / common_ratio_1a);
            common_ratio_is_integer = error < EPSILON;
            if (common_ratio_2a > 1 && common_ratio_is_integer) {
                indexes = [i1, i2, i3];
                j1 = i2;
                j2 = i3
                difference12b = difference23a;
                for (j3 = j2 + 1; j3 < input.length; ++j3) {
                    difference23b = input[j3] - input[j2];
                    common_ratio_1b = difference23b / difference12b;
                    common_ratio_2b = Math.round(common_ratio_1b);
                    error = Math.abs((common_ratio_2b - common_ratio_1b) / common_ratio_1b);
                    common_ratio_is_integer = error < EPSILON;
                    if (common_ratio_is_integer && common_ratio_2a === common_ratio_2b) {
                        indexes.push(j3);
                        j1 = j2;
                        j2 = j3
                        difference12b = difference23b;
                    }
                }
                values = [];
                for (i = 0; i < indexes.length; ++i) {
                    index = indexes[i];
                    value = input[index];
                    values.push(value);
                }
                output.push(values);
            }
        }
    }
}
if (output !== []) {
    i_max_length = 0;
    for (i = 1; i < output.length; ++i) {
        if (output[i_max_length].length < output[i].length)
            i_max_length = i;
    }
    for (i = 0; i < output.length; ++i) {
        if (output[i_max_length].length == output[i].length)
            resultDiv.append("<p>[" + output[i] + "]</p>");
    }
}

Вывод:

[1, 3, 7, 15, 31]

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

Ответ 3

Вот мое решение в Javascript. Он должен быть близок к O (n ^ 2), но может быть в некоторых патологических случаях.

function bsearch(Arr,Val, left,right) {
    if (left == right) return left;
    var m=Math.floor((left + right) /2);
    if (Val <= Arr[m]) {
        return bsearch(Arr,Val,left,m);
    } else {
        return bsearch(Arr,Val,m+1,right);
    }
}

function findLongestGeometricSequence(S) {
    var bestSolution=[];

    var i,j,k;
    var H={};
    for (i=0;i<S.length;i++) H[S[i]]=true;

    for (i=0;i<S.length;i++) {
        for (j=0;j<i;j++) {
            for (k=j+1;k<i;) {
                var possibleSolution=[S[j],S[k],S[i]];

                var K = (S[i] - S[k]) / (S[k] - S[j]);
                var A = (S[k] - S[j]) * (S[k] - S[j]) / (S[i] - S[k]); 

                if ((Math.floor(K) == K) && (Math.floor(A)==A)) {
                    exp= K*K*K;
                    var NextVal= S[i] + A * exp;
                    while (H[NextVal] === true) {
                        possibleSolution.push(NextVal);
                        exp = exp * K;
                        NextVal= NextVal + A * exp;                
                    }

                    if (possibleSolution.length > bestSolution.length)
                        bestSolution=possibleSolution;

                    K--;
                } else {
                    K=Math.floor(K);
                }

                if (K>0) {
                    var NextPossibleMidValue= (S[i] + K*S[j]) / (K +1); 
                    k++;
                    if (S[k]<NextPossibleMidValue) {
                        k=bsearch(S,NextPossibleMidValue, k+1, i);
                    }
                } else {
                    k=i;
                }
            }        
        }
    }
    return bestSolution;
}


function Run() {
    var MyS= [0.7, 1, 2, 3, 4, 5,6,7, 15, 27, 30,31, 81];

    var sol = findLongestGeometricSequence(MyS); 

    alert(JSON.stringify(sol));
}

Малое пояснение

Если мы возьмем 3 числа массива S (j) S (k) S (i), то вы можете вычислить a и k так, чтобы: S (k) = S (j) + a * k и S (i) = S (k) + a * k ^ 2 (2 уравнения и 2 инкогнита), Имея это в виду, вы можете проверить, существует ли в массиве число, которое является S (next) = S (i) + a * k ^ 3. Если это так, то продолжите проверку для S (next2) = S (next) + a * k ^ 4 и т.д.

Это было бы решение O (n ^ 3), но вы можете использовать преимущество h, чтобы k было целым, чтобы ограничить выбранные точки S (k).

В случае, если известно, что вы можете вычислить a (k), и вам нужно проверить только одно число в третьем цикле, поэтому этот случай будет явно O (n ^ 2).

Ответ 4

Я думаю, что эта задача связана с не так давно опубликованной Самая длинная однополярная подпоследовательность. Я немного изменил свой алгоритм в Python:

from math import sqrt

def add_precalc(precalc, end, (a, k), count, res, N):
    if end + a * k ** res[1]["count"] > N: return

    x = end + a * k ** count
    if x > N or x < 0: return

    if precalc[x] is None: return

    if (a, k) not in precalc[x]:
        precalc[x][(a, k)] = count

    return

def factors(n):
    res = []
    for x in range(1, int(sqrt(n)) + 1):
        if n % x == 0:
            y = n / x
            res.append((x, y))
            res.append((y, x))
    return res

def work(input):
    precalc = [None] * (max(input) + 1)
    for x in input: precalc[x] = {}

    N = max(input)

    res = ((0, 0), {"end":0, "count":0})
    for i, x in enumerate(input):
        for y in input[i::-1]:
            for a, k in factors(x - y):
                if (a, k) in precalc[x]: continue
                add_precalc(precalc, x, (a, k), 2, res, N)

        for step, count in precalc[x].iteritems():
            count += 1
            if count > res[1]["count"]: res = (step, {"end":x, "count":count})
            add_precalc(precalc, x, step, count, res, N)
        precalc[x] = None

    d = [res[1]["end"]]
    for x in range(res[1]["count"] - 1, 0, -1):
        d.append(d[-1] - res[0][0] * res[0][1] ** x)
    d.reverse()
    return d

объяснение

  • Перемещение массива
  • Для каждого предыдущего элемента массива вычисляют коэффициенты разности между текущим и принятым предыдущим элементом, а затем предварительно вычисляют следующий возможный элемент последовательности и сохраняя его для прекалирования массива.
  • Таким образом, при достижении элемента i в массиве precalc уже есть все возможные последовательности с элементом i, поэтому мы должны вычислить следующий возможный элемент и сохранить его для precalc.

В настоящее время существует одно место в алгоритме, которое может быть медленным - факторизация каждого предыдущего числа. Я думаю, что это можно сделать быстрее с двумя оптимизациями:

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

Ответ 5

Python:

def subseq(a):
    seq = []
    aset = set(a)
    for i, x in enumerate(a):
        # elements after x
        for j, x2 in enumerate(a[i+1:]):
            j += i + 1  # enumerate starts j at 0, we want a[j] = x2
            bk = x2 - x  # b*k (assuming k and k exponent start at 1)

            # given b*k, bruteforce values of k
            for k in range(1, bk + 1):
                items = [x, x2]  # our subsequence so far
                nextdist = bk * k # what x3 - x2 should look like

                while items[-1] + nextdist in aset:
                    items.append(items[-1] + nextdist)
                    nextdist *= k

                if len(items) > len(seq):
                    seq = items

    return seq

Время работы O(dn^3), где d - расстояние (среднее?) между двумя элементами, и n, конечно, len(a).

Ответ 6

На самом деле это точно такой же вопрос, как Самая длинная равноуровневая подпоследовательность, вам просто нужно рассмотреть логарифм ваших данных. Если последовательность a, ak, ak ^ 2, ak ^ 3, то логарифмическое значение равно ln (a), ln (a) + ln (k), ln (a) + 2ln (k), ln (a) + 3ln (k), поэтому он равномерно распределен. Конечно, наоборот. В вопросе выше есть много другого кода.

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