Algoritmo O (nlogn) - Encontre três espaçados uniformemente na cadeia binária


173

Ontem fiz essa pergunta em um teste de algoritmos e não consigo descobrir a resposta. Isso está me deixando absolutamente louco, porque valeu cerca de 40 pontos. Eu acho que a maioria da turma não a resolveu corretamente, porque não encontrei uma solução nas últimas 24 horas.

Dada uma sequência binária arbitrária de comprimento n, encontre três espaçadas igualmente dentro da sequência, se existirem. Escreva um algoritmo que resolva isso em O (n * log (n)).

Portanto, seqüências de caracteres como essas têm três que são "espaçadas igualmente": 11100000, 0100100100

edit: como é um número aleatório, deve poder trabalhar para qualquer número. Os exemplos que dei foram para ilustrar a propriedade "uniformemente espaçada". Então 1001011 é um número válido. Com 1, 4 e 7 sendo os que são espaçados igualmente.


4
É possível o seguinte: 10011010000? Tem três 1s (primeiro, segundo, quarto) espaçados igualmente, mas também existem 1s adicionais.
Anna

5
Robert, você precisa pedir ao seu professor a resposta e postar aqui. Esse problema está me levando até a parede. Eu posso descobrir como fazê-lo em n ^ 2, mas não n * log (n).
21411 James McMahon

3
Hmm, passei muito tempo tentando descobrir isso também, ainda não encontrei uma boa resposta. Talvez você tenha entendido mal a pergunta? Por exemplo, se a pergunta for feita, encontre um algoritmo executado em O (n log n) que determine a posição de uma sequência igualmente espaçada do espaçamento k, em uma sequência muito maior, isso pode ser feito facilmente usando a transformação rápida de Fourier.
Ldog 14/10/09

2
se o seu professor fornecer uma solução, poste-a como resposta.
Ldog 14/10/09

5
Considerando o fato de Klaus Roth receber uma Medalha Fields 1958 por (entre outras coisas) provar que para cada densidade d> 0 existe um número natural N tal que cada subconjunto de {1, ..., N} com pelo menos d * N elementos contém uma progressão aritmética de comprimento 3, não estou surpreso que até agora ninguém encontrasse um algoritmo convincente para o problema ainda. Veja também en.wikipedia.org/wiki/Szemer%C3%A9di%27s_theorem
jp

Respostas:


128

Finalmente! Seguindo os leads na resposta do sdcvvc , temos: o algoritmo O (n log n) para o problema! Também é simples, depois que você entende. Quem adivinhou a FFT estava certo.

O problema: recebemos uma sequência binária Sde comprimento n e queremos encontrar três 1s com espaçamento uniforme. Por exemplo, Spode ser 110110010, onde n = 9. Ele espaçou 1s uniformemente nas posições 2, 5 e 8.

  1. Digitalize da Sesquerda para a direita e faça uma lista Lde posições de 1. Para o S=110110010exposto, temos a lista L = [1, 2, 4, 5, 8]. Este passo é O (n). O problema é agora encontrar uma progressão aritmética de comprimento 3 em L, ou seja, para encontrar distinta a, b, c em Ltais que bA = CB , ou de forma equivalente a + c = 2b . Para o exemplo acima, queremos encontrar a progressão (2, 5, 8).

  2. Faça um polinômio p com os termos x k para cada k pol L. Para o exemplo acima, criamos o polinômio p (x) = (x + x 2 + x 4 + x 5 + x 8 ) . Este passo é O (n).

  3. Encontre o polinômio q= p 2 , usando a Transformada rápida de Fourier . Para o exemplo acima, obtemos o polinômio q (x) = x 16 + 2x 13 + 2x 12 + 3x 10 + 4x 9 + x 8 + 2x 7 + 4x 6 + 2x 5 + x 4 + 2x 3 + x 2 . Esta etapa é O (n log n).

  4. Ignore todos os termos, exceto aqueles correspondentes a x 2k por alguns k in L. Para o exemplo acima, obtemos os termos x 16 , 3x 10 , x 8 , x 4 , x 2 . Esta etapa é O (n), se você optar por fazê-lo.

Aqui está o ponto crucial: o coeficiente de qualquer x 2b para b in Lé precisamente o número de pares (a, c) de Lmodo que a + c = 2b . [CLRS, Ex. 30.1-7] Um desses pares é (b, b) sempre (portanto, o coeficiente é pelo menos 1), mas se existe algum outro par (a, c) , então o coeficiente é pelo menos 3, de (a, c ) e (c, a) . Para o exemplo acima, temos o coeficiente de x 10 para ser 3 precisamente por causa do PA (2,5,8). (Esses coeficientes x 2bsempre serão números ímpares, pelas razões acima. E todos os outros coeficientes em q sempre serão pares.)

Portanto, o algoritmo deve examinar os coeficientes desses termos x 2b e verificar se algum deles é maior que 1. Se não houver nenhum, não haverá 1s espaçados uniformemente. Se não é uma b em Lpara os quais o coeficiente de x 2b é maior do que 1, então sabe que há alguns par (a, c) - diferente (B, b) - para que a + c = 2b . Para encontrar o par real, simplesmente tentamos cada a em L(o c correspondente seria 2b-a ) e verificamos se existe um 1 na posição 2b-a em S. Este passo é O (n).

Isso é tudo, pessoal.


Alguém pode perguntar: precisamos usar a FFT? Muitas respostas, como beta , flybywire e rsp , sugerem que a abordagem que verifica cada par de 1s e vê se existe um 1 na "terceira posição", pode funcionar em O (n log n), com base na intuição que, se houver 1s demais, encontraríamos um triplo com facilidade e, se houver 1s muito baixo, verificar todos os pares leva pouco tempo. Infelizmente, embora essa intuição esteja correta e a abordagem simples seja melhor que O (n 2 ), ela não é significativamente melhor. Como na resposta do sdcvvc , podemos usar o "conjunto Cantor-like" de strings de comprimento n = 3 k, com 1s nas posições cuja representação ternária possui apenas 0s e 2s (sem 1s). Essa string possui 2 k = n (log 2) / (log 3) ≈ n 0,63 e não 1s espaçados de maneira uniforme; portanto, a verificação de todos os pares seria da ordem do quadrado do número de 1s nela: 4 k ≈ n 1,26 que infelizmente é assintoticamente muito maior que (n log n). De fato, o pior caso é ainda pior: Leo Moser, em 1953, construiu (efetivamente) aquelas cordas que possuem n 1-c / √ (log n) 1s nelas, mas não 1s espaçadas uniformemente, o que significa que nessas cordas, o simples abordagem levaria Θ (n 2-2c / √ (log n) )- apenas uma pequena pouco melhor do que Θ (n 2 ) , surpreendentemente!


Sobre o número máximo de 1s em uma sequência de comprimento n sem 3 espaçados uniformemente (que vimos acima era pelo menos n 0,63 da construção fácil do tipo Cantor e pelo menos n 1-c / √ (log n) com Moser) - este é o OEIS A003002 . Também pode ser calculado diretamente do OEIS A065825 como k, de modo que A065825 (k) ≤ n <A065825 (k + 1). Eu escrevi um programa para encontrá-los, e acontece que o algoritmo ganancioso não fornece a maior seqüência de caracteres. Por exemplo, para n = 9, podemos obter 5 1s (110100011), mas o ganancioso fornece apenas 4 (110110000), para n= 26, podemos obter 11 1s (11001010001000010110001101), mas o ganancioso fornece apenas 8 (110110000110110000000000000000), e para n = 74, podemos obter 22 1s (11000010110001000001010100000000000000000000010001011010000010001101000000000000000000000000000000000010000100001000100010001000100010001000100010001) Eles concordam em alguns lugares até 50 (por exemplo, de 38 a 50). Como as referências da OEIS dizem, parece que Jaroslaw Wroblewski está interessado nessa questão, e ele mantém um site sobre esses conjuntos não médios . Os números exatos são conhecidos apenas até 194.


27
Muito agradável. Impressionante. Parece um pouco demais esperar que alguém invente isso em um teste.
21133 hughdbrown

4
Bem, a Etapa 1, traduzindo o problema para encontrar um AP, é simples. A etapa 3, de que os polinômios podem ser multiplicados no tempo O (n log n), é apenas um fato. O verdadeiro truque, e o que dificulta o problema, é a idéia de pensar no 11011 como o polinômio com coeficientes [1,1,0,1,1] etc. caminho de volta para Euler. [Veja o impressionante livro de Wilf " generatorfunctionology " para uma exposição moderna: math.upenn.edu/~wilf/DownldGF.html ] Portanto, depende se os alunos foram expostos ou não a gerar funções na memória recente. :-)
ShreevatsaR

2
Desculpe, meu cálculo foi completamente errado. Deve ser 110110010 ^ 2 = 12124214302200100. Mas a ideia permanece. Apenas observe a posição dos 3.
Guillermo Phillips

11
Muito impressionante. É muito legal ver esse tópico / pergunta se unir e encontrar uma solução. Eu estava começando a pensar que não era possível. Além disso, esse professor é mau.
22410 KingNestor

1
@RexE: Se p é do grau n-1 (possui n termos), q = p ^ 2 é do grau 2n-2 (possui no máximo 2n-1 termos). Como você conseguiu n ^ 2? (Além disso, multiplicar dois polinômios de grau n no tempo O (n log n) usando a FFT é uma operação bastante padrão; clique no link da resposta ou veja o artigo da Wikipedia .)
ShreevatsaR

35

Seu problema é chamado de MÉDIA neste documento (1999):

Um problema é 3SUM-difícil se houver uma redução sub-quadrática do problema 3SUM: Dado um conjunto A de n números inteiros, existem elementos a, b, c em A tal que a + b + c = 0? Não se sabe se MÉDIA é difícil 3SUM. No entanto, há uma redução simples no tempo linear de AVERAGE para 3SUM, cuja descrição omitimos.

Wikipedia :

Quando os números inteiros estão no intervalo [−u ... u], o 3SUM pode ser resolvido no tempo O (n + u lg u), representando S como um vetor de bits e realizando uma convolução usando FFT.

Isso é suficiente para resolver seu problema :).

O que é muito importante é que O (n log n) é complexidade em termos de número de zeros e uns, não a contagem de uns (que pode ser dada como uma matriz, como [1,5,9,15]). Verificar se um conjunto tem uma progressão aritmética, termos de número de 1's, é difícil e, de acordo com esse artigo, em 1999, nenhum algoritmo mais rápido que O (n 2 ) é conhecido e é conjecturado que ele não exista. Todo mundo que não leva isso em consideração está tentando resolver um problema em aberto.

Outras informações interessantes, principalmente irrelevantes:

Limite inferior:

Um limite inferior fácil é o conjunto do tipo Cantor (números 1..3 ^ n-1 que não contém 1 em sua expansão ternária) - sua densidade é n ^ (log_3 2) (cerca de 0,631). Portanto, qualquer verificação se o conjunto não é muito grande e a verificação de todos os pares não são suficientes para obter O (n log n). Você precisa investigar a sequência de maneira mais inteligente. Um limite inferior melhor é citado aqui - é n 1-c / (log (n)) ^ (1/2) . Isso significa que o conjunto Cantor não é o ideal.

Limite superior - meu antigo algoritmo:

Sabe-se que para n grande, um subconjunto de {1,2, ..., n} que não contém progressão aritmética tem no máximo n / (log n) ^ (1/20) elementos. O artigo Sobre triplos na progressão aritmética prova mais: o conjunto não pode conter mais do que n * 2 28 * (log log n / log n) 1/2 elementos. Assim, você pode verificar se esse limite é alcançado e, se não, verificar ingenuamente os pares. Esse é o algoritmo O (n 2 * log log n / log n), mais rápido que O (n 2 ). Infelizmente "On triples ..." está no Springer - mas a primeira página está disponível e a exposição de Ben Green está disponível aqui , página 28, teorema 24.

A propósito, os trabalhos são de 1999 - o mesmo ano que o primeiro que mencionei, provavelmente é por isso que o primeiro não menciona esse resultado.


2
Ótima resposta, a primeira que diz algo definitivo sobre esse problema. Portanto, o conjunto do tipo Cantor possui n ^ 0,63 1s, o que significa que o algoritmo "verificar todos os pares de 1s" é pelo menos n ^ 1,26 (log n log n) no pior caso. O limite inferior citado no artigo de Szemeredi (aliás, o artigo de Moser que ele cita está disponível aqui: books.google.com/books?id=Cvtwu5vVZF4C&pg=PA245 ) parece realmente implicar n ^ (2-o (1)), mas devemos tenha um pouco de cuidado, pois temos números extraídos de {1, ..., n}, mas aqui é a soma dos números na sequência que é n.
ShreevatsaR

Er, qual é exatamente a sequência binária "do tipo Cantor" que contém n ^ (log_3 2) 1s nela e não três 1s espaçados igualmente?
ShreevatsaR

Exemplo: 101000101000000000101000101. Seu comprimento é 3 ^ n e possui 2 ^ n um (portanto, densidade de n ^ 0,63). Se você escrever os lugares dos 1s em binário, será {0,2,20,22,200,202,220,222}. Outra maneira possível de pensar nisso é pegar uma sequência de unidades e remover continuamente as "intermediárias", como na construção normal do conjunto Cantor: 111111111 -> 111000111 -> 101000101. A razão pela qual não contém progressão aritmética é: se x , y, z formou um, então y = (x + z) / 2 e x e z diferem em algum local de expansão. Tome o mais significativo. Diga x tem 0 e z tem 2. Então y deve ter 1 lá. contradição.
Sdcvvc 18/10/09

3
Mais uma vez, ótima pesquisa! Acompanhei o documento 3SUM de 2008, que se referia ao exercício CLRS. 30.1-7, depois de olhar para a qual obtive a resposta - o algoritmo O (n log n) é realmente bastante simples! (Apenas esquadrando uma função polinomial / geradora.) Publiquei a resposta abaixo. (Agora me chutando por não ter pensado nisso antes ... soluções simples sempre provocar essa reação: p)
ShreevatsaR

Portanto, a resposta para a pergunta do exame era algo como: "Esse problema é redutível ao problema difícil 3-SUM e o 3-SUM rígido não tem solução sub-quadrática, portanto, esse problema não pode ser resolvido em O (n logn). " Sim?
hughdbrown 18/10/09

8

Esta não é uma solução, mas uma linha de pensamento semelhante ao que Olexiy estava pensando

Eu estava brincando com a criação de sequências com o número máximo de unidades, e todas são bastante interessantes, tenho até 125 dígitos e aqui estão os três primeiros números encontrados ao tentar inserir o maior número possível de bits '1':

  • 11011000011011000000000000001101100001101100000000000000000000000000000000000000000110110000110110000000000000011011000011011
  • 10110100010110100000000000010110100010110100000000000000000000000000000000000000000101101000101101000000000000101101000101101
  • 10011001010011001000000000010011001010011001000000000000000000000000000000000000010011001010011001000000000010011001010011001

Observe que todos eles são fractais (não muito surpreendentes, dadas as restrições). Pode haver algo em pensar de trás para frente, talvez se a sequência não for um fractal com uma característica, ela deve ter um padrão repetitivo?

Obrigado ao beta pelo melhor termo para descrever esses números.

Atualização: Infelizmente, parece que o padrão falha ao iniciar com uma sequência inicial grande o suficiente, como: 10000000000001:

100000000000011
10000000000001101
100000000000011011
10000000000001101100001
100000000000011011000011
10000000000001101100001101
100000000000011011000011010000000001
100000000000011011000011010000000001001
1000000000000110110000110100000000010011
1000000000000110110000110100000000010011001
10000000000001101100001101000000000100110010000000001
10000000000001101100001101000000000100110010000000001000001
1000000000000110110000110100000000010011001000000000100000100000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011
1000000000000110110000110100000000010011001000000000100000100000000000001101
100000000000011011000011010000000001001100100000000010000010000000000000110100001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001000000000000000000000010010000010000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001000000000000000000000000000000000000110010000000000000000000000100100000100000011
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001000000110000000000001

2
Santo * @ !!, estes são FRACTAIS! Se isso persistir, ele coloca um limite superior no número de 1s e é menor que O (n).
Beta

fractais, é um termo muito melhor para descrevê-los. Obrigado
z -

Interessante, esses padrões se parecem muito com o conjunto ternário de Cantor ( en.wikipedia.org/wiki/Cantor_set ). Se isto é assim, então a proporção de uns tem que tendem a zero ...
fly-by-wire

É óbvio que as seqüências com o número máximo de 1s sem triplos são diretamente relevantes para o pior caso de execução do algoritmo? É concebível que você possa ter seqüências com lotes de 1s, mas nas quais você só encontra os triplos muito tarde, pois esses 1s estão nas posições que são examinadas tardiamente pelo seu algoritmo.
ShreevatsaR

3
Minha análise do número de unidades nas seqüências de caracteres em comparação com o tamanho geral parece indicar que existe uma relação linear entre o número de unidades e o tamanho da sequência, levando-me a acreditar que não há um limite superior feliz que permita o número de unidades em uma string terá no máximo log (n) para uma determinada string. Portanto, as soluções que lidam apenas com as posições dessas e não com toda a cadeia em si também serão O (n ^ 2). Ou, mais precisamente, O (n + m ^ 2), em que m é o número de unidades na cadeia, n é o tamanho da cadeia e m é big-theta (n).
Welbog 16/10/09

6

Suspeito que uma abordagem simples que se pareça com O (n ^ 2) realmente produza algo melhor, como O (n ln (n)). As seqüências que demoram mais para serem testadas (para qualquer n) são as que não contêm trios, e isso impõe severas restrições ao número de 1s que podem estar na sequência.

Eu vim com alguns argumentos de acenar com a mão, mas não consegui encontrar uma prova clara. Vou dar uma facada no escuro: a resposta é uma idéia muito inteligente que o professor conhece há tanto tempo que parece óbvia, mas é muito difícil para os alunos. (Ou você dormiu durante a palestra que a abordou.)


2
lol, não, eu não dormi durante nenhuma aula. Conversei com alguns outros alunos e ninguém tinha uma idéia clara de como resolvê-lo. A maioria escreveu alguns BS sobre dividir e conquistar em um apelo para obter algum crédito parcial.
911 Robert Parker

3

Revisão: 17-10-2009 23:00

Eu executei isso em grandes números (tipo, cadeias de 20 milhões) e agora acredito que esse algoritmo não é O (n logn). Não obstante, é uma implementação bastante interessante e contém várias otimizações que a tornam muito rápida. Ele avalia todos os arranjos de cadeias binárias com 24 ou menos dígitos em menos de 25 segundos.

Atualizei o código para incluir a 0 <= L < M < U <= X-1observação de hoje mais cedo.


Original

Isso é, em conceito, semelhante a outra pergunta que respondi . Esse código também analisou três valores em uma série e determinou se um trigêmeo satisfazia uma condição. Aqui está o código C # adaptado disso:

using System;
using System.Collections.Generic;

namespace StackOverflow1560523
{
    class Program
    {
        public struct Pair<T>
        {
            public T Low, High;
        }
        static bool FindCandidate(int candidate, 
            List<int> arr, 
            List<int> pool, 
            Pair<int> pair, 
            ref int iterations)
        {
            int lower = pair.Low, upper = pair.High;
            while ((lower >= 0) && (upper < pool.Count))
            {
                int lowRange = candidate - arr[pool[lower]];
                int highRange = arr[pool[upper]] - candidate;
                iterations++;
                if (lowRange < highRange)
                    lower -= 1;
                else if (lowRange > highRange)
                    upper += 1;
                else
                    return true;
            }
            return false;
        }
        static List<int> BuildOnesArray(string s)
        {
            List<int> arr = new List<int>();
            for (int i = 0; i < s.Length; i++)
                if (s[i] == '1')
                    arr.Add(i);
            return arr;
        }
        static void BuildIndexes(List<int> arr, 
            ref List<int> even, ref List<int> odd, 
            ref List<Pair<int>> evenIndex, ref List<Pair<int>> oddIndex)
        {
            for (int i = 0; i < arr.Count; i++)
            {
                bool isEven = (arr[i] & 1) == 0;
                if (isEven)
                {
                    evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count+1});
                    oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count});
                    even.Add(i);
                }
                else
                {
                    oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count+1});
                    evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count});
                    odd.Add(i);
                }
            }
        }

        static int FindSpacedOnes(string s)
        {
            // List of indexes of 1s in the string
            List<int> arr = BuildOnesArray(s);
            //if (s.Length < 3)
            //    return 0;

            //  List of indexes to odd indexes in arr
            List<int> odd = new List<int>(), even = new List<int>();

            //  evenIndex has indexes into arr to bracket even numbers
            //  oddIndex has indexes into arr to bracket odd numbers
            List<Pair<int>> evenIndex = new List<Pair<int>>(), 
                oddIndex = new List<Pair<int>>(); 
            BuildIndexes(arr, 
                ref even, ref odd, 
                ref evenIndex, ref oddIndex);

            int iterations = 0;
            for (int i = 1; i < arr.Count-1; i++)
            {
                int target = arr[i];
                bool found = FindCandidate(target, arr, odd, oddIndex[i], ref iterations) || 
                    FindCandidate(target, arr, even, evenIndex[i], ref iterations);
                if (found)
                    return iterations;
            }
            return iterations;
        }
        static IEnumerable<string> PowerSet(int n)
        {
            for (long i = (1L << (n-1)); i < (1L << n); i++)
            {
                yield return Convert.ToString(i, 2).PadLeft(n, '0');
            }
        }
        static void Main(string[] args)
        {
            for (int i = 5; i < 64; i++)
            {
                int c = 0;
                string hardest_string = "";
                foreach (string s in PowerSet(i))
                {
                    int cost = find_spaced_ones(s);
                    if (cost > c)
                    {
                        hardest_string = s;
                        c = cost;
                        Console.Write("{0} {1} {2}\r", i, c, hardest_string);
                    }
                }
                Console.WriteLine("{0} {1} {2}", i, c, hardest_string);
            }
        }
    }
}

As principais diferenças são:

  1. Pesquisa exaustiva de soluções
    Este código gera um conjunto de dados poderoso para encontrar as entradas mais difíceis de resolver para esse algoritmo.
  2. Todas as soluções versus as mais difíceis de resolver
    O código da pergunta anterior gerou todas as soluções usando um gerador python. Este código apenas exibe o mais difícil para cada comprimento de padrão.
  3. Algoritmo de pontuação
    Este código verifica a distância entre o elemento do meio e as extremidades esquerda e direita. O código python testou se uma soma estava acima ou abaixo de 0.
  4. Convergência em um candidato
    O código atual trabalha do meio para a borda para encontrar um candidato. O código no problema anterior funcionava das bordas em direção ao meio. Essa última alteração fornece uma grande melhoria de desempenho.
  5. Uso de piscinas pares e ímpares
    Com base nas observações no final deste artigo, o código pesquisa pares de números pares de pares de números ímpares para encontrar L e U, mantendo M fixo. Isso reduz o número de pesquisas pré-computando informações. Assim, o código usa dois níveis de indireção no loop principal do FindCandidate e requer duas chamadas para o FindCandidate para cada elemento do meio: uma para números pares e outra para números ímpares.

A idéia geral é trabalhar em índices, não na representação bruta dos dados. O cálculo de uma matriz em que os 1s aparecem permite que o algoritmo seja executado no tempo proporcional ao número de 1s nos dados, e não no tempo proporcional ao comprimento dos dados. Essa é uma transformação padrão: crie uma estrutura de dados que permita uma operação mais rápida, mantendo o problema equivalente.

Os resultados estão desatualizados: removidos.


Edit: 2009-10-16 18:48

Nos dados de yx, que recebem alguma credibilidade nas outras respostas como representativas dos dados concretos para calcular, eu obtenho esses resultados ... Eu os removi. Eles estão desatualizados.

Eu apontaria que esses dados não são os mais difíceis para o meu algoritmo, então acho que a suposição de que os fractais de yx são os mais difíceis de resolver está equivocada. O pior caso para um algoritmo específico, espero, dependerá do próprio algoritmo e provavelmente não será consistente entre diferentes algoritmos.


Edit: 2009-10-17 13:30

Outras observações sobre isso.

Primeiro, converta a sequência de 0 e 1 em uma matriz de índices para cada posição do 1. Digamos que o comprimento dessa matriz A seja X. Então o objetivo é encontrar

0 <= L < M < U <= X-1

de tal modo que

A[M] - A[L] = A[U] - A[M]

ou

2*A[M] = A[L] + A[U]

Como A [L] e A [U] somam um número par, eles não podem ser (pares, ímpares) ou (ímpares, pares). A busca por uma correspondência pode ser aprimorada dividindo A [] em conjuntos ímpares e pares e procurando correspondências em A [M] nos conjuntos de candidatos pares e ímpares.

No entanto, isso é mais uma otimização de desempenho do que uma melhoria algorítmica, eu acho. O número de comparações deve cair, mas a ordem do algoritmo deve ser a mesma.


Edit 2009-10-18 00:45

Outra otimização me ocorre, na mesma linha que separa os candidatos em pares e ímpares. Como os três índices precisam ser adicionados a um múltiplo de 3 (a, a + x, a + 2x - mod 3 é 0, independentemente de aex), é possível separar L, M e U nos valores do mod 3 :

M  L  U
0  0  0
   1  2
   2  1
1  0  2
   1  1
   2  0
2  0  1
   1  0
   2  2

De fato, você pode combinar isso com a observação par / ímpar e separá-los em seus valores do mod 6:

M  L  U
0  0  0
   1  5
   2  4
   3  3
   4  2
   5  1

e assim por diante. Isso forneceria uma otimização de desempenho adicional, mas não uma aceleração algorítmica.


2

Ainda não conseguiu encontrar a solução :(, mas tenho algumas idéias.

E se começarmos de um problema inverso: construa uma sequência com o número máximo de 1s e SEM trios uniformemente espaçados. Se você puder provar que o número máximo de 1s é o (n), poderá melhorar sua estimativa iterando apenas através da lista de 1s.


Bem, o número de 1's é certamente delimitado acima por O (n). Não pode ser O (n ** 2), certo - o número de 1's cresce mais rápido que os dados? A questão importante é se o limite superior é menor que isso.
21139 hughdbrown

Eu usei o pequeno o, não o grande
Olexiy

2

Isso pode ajudar ....

Esse problema se reduz ao seguinte:

Dada uma sequência de números inteiros positivos, encontre uma subsequência contígua particionada em um prefixo e um sufixo, de modo que a soma do prefixo da subsequência seja igual à soma do sufixo da subsequência.

Por exemplo, dada uma sequência de [ 3, 5, 1, 3, 6, 5, 2, 2, 3, 5, 6, 4 ], encontraríamos uma subsequência de [ 3, 6, 5, 2, 2]com um prefixo de [ 3, 6 ]com prefixo soma de 9e um sufixo de [ 5, 2, 2 ]com sufixo soma de 9.

A redução é a seguinte:

Dada uma sequência de zeros e uns, e começando pelo mais à esquerda, continue se movendo para a direita. Cada vez que outro é encontrado, registre o número de movimentos desde que o anterior foi encontrado e anexe esse número à sequência resultante.

Por exemplo, dada uma sequência de [ 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 0 ], encontraríamos a redução de [ 1, 3, 4]. A partir dessa redução, calculamos a subsequência contígua de [ 1, 3, 4], o prefixo de [ 1, 3]com soma de 4e o sufixo de [ 4 ]com soma de 4.

Essa redução pode ser calculada em O(n).

Infelizmente, não sei para onde ir a partir daqui.


1
É uma notação mais compacta, mas não ajudará na complexidade do tempo. O conjunto de partições "prefixo" é isomórfico para uma pesquisa de todos os pares em todas as ocorrências de "1", que é O (n ^ 2).
P00ya

Aparentemente, existem algoritmos que lidam com somas de subsequência contígua. Infelizmente, todos parecem lidar com a descoberta da subsequência contígua com a soma máxima em O (n).
Yfeldblum 16/10/09

@ p00ya isso não está correto. Usando esse algoritmo, a coplexidade do tempo depende do limite superior do número de falsos, que por assupton na string gerada pelo Cantor é ((3/2) ^ (log (n) / log (3))) e a complexidade do espaço se torna essa, mas a complexidade do tempo se torna isso multiplicada por n. Veja minha segunda resposta. (não o negativo): D
Luka Rahne

@ralu: isso é sob sua suposição de que as seqüências geradas pelo Cantor são o pior caso, o que está errado. Para o registro, o número de pares é certamente O (n ^ 2); mas acho que estava realmente sugerindo que era o Omega grande (n ^ 2), o que é incorreto, considerando esses resultados (consulte o link NrootN particularmente), sugerindo um limite inferior nos pares de Omega grande (n ^ (2 / 1,52 )) por prova ou big-Omega (n ^ (4/3)) por conjectura.
P00ya 19/10/09

1

Para o tipo de problema simples (ou seja, você pesquisa três "1" com apenas (ou seja, zero ou mais) "0" entre eles), é bastante simples: você pode dividir a sequência a cada "1" e procurar duas subsequências adjacentes o mesmo comprimento (a segunda subsequência não é a última, é claro). Obviamente, isso pode ser feito em O (n) tempo.

Para a versão mais complexa (ou seja, você pesquisa um índice ie uma diferença g > 0 tal que s[i]==s[i+g]==s[i+2*g]=="1"), não tenho certeza, se existe uma solução O (n log n) , uma vez que existem possivelmente trigêmeos O (n²) tendo essa propriedade (pense em uma série de todas, existem aproximadamente n / 2 desses trigêmeos). Claro, você está procurando apenas um desses, mas atualmente não tenho idéia de como encontrá-lo ...


Sim, estamos discutindo a versão mais difícil do problema. Ainda assim, a solução n * log (n) pode ser possível.
Olexiy

1
Na verdade, existem n escolher 3, que é O (n ^ 3) possíveis triplos, eu acho que quando você disse que cerca de n ^ 2/2-lo onde o pensamento n escolher 2
ldog

@ gmatt: n escolha 2 é suficiente; se fixarmos dois 1s, a posição do terceiro é determinada e é tempo constante ver se há um 1 nessa posição ou não.
ShreevatsaR

@ShreevatsaR: sim, está certo, eu acho, eu estava pensando no caso irrestrito.
ldog

1
@ gmatt: na verdade, estamos procurando Tuplas (i, g), conforme definido acima, com as restrições de que 0 <= i <(n-3) e 0 <g <(ni-1) / 2, daí a estimativa de n ^
2/2

1

Uma pergunta divertida, mas depois que você percebe que o padrão real entre dois '1s não importa, o algoritmo se torna:

  • procurar um '1'
  • começando da próxima posição, procure outro '1' (até o final da matriz menos a distância do primeiro '1' atual ou o terceiro '1' estaria fora dos limites)
  • se na posição do 2º '1' mais a distância do primeiro 1 'um terço' 1 'for encontrada, teremos espaços iguais.

No código, da maneira JTest, (observe que este código não foi escrito para ser mais eficiente e eu adicionei alguns println's para ver o que acontece.)

import java.util.Random;

import junit.framework.TestCase;

public class AlgorithmTest extends TestCase {

 /**
  * Constructor for GetNumberTest.
  *
  * @param name The test's name.
  */
 public AlgorithmTest(String name) {
  super(name);
 }

 /**
  * @see TestCase#setUp()
  */
 protected void setUp() throws Exception {
  super.setUp();
 }

 /**
  * @see TestCase#tearDown()
  */
 protected void tearDown() throws Exception {
  super.tearDown();
 }

 /**
  * Tests the algorithm.
  */
 public void testEvenlySpacedOnes() {

  assertFalse(isEvenlySpaced(1));
  assertFalse(isEvenlySpaced(0x058003));
  assertTrue(isEvenlySpaced(0x07001));
  assertTrue(isEvenlySpaced(0x01007));
  assertTrue(isEvenlySpaced(0x101010));

  // some fun tests
  Random random = new Random();

  isEvenlySpaced(random.nextLong());
  isEvenlySpaced(random.nextLong());
  isEvenlySpaced(random.nextLong());
 }

 /**
  * @param testBits
  */
 private boolean isEvenlySpaced(long testBits) {
  String testString = Long.toBinaryString(testBits);
  char[] ones = testString.toCharArray();
  final char ONE = '1';

  for (int n = 0; n < ones.length - 1; n++) {

   if (ONE == ones[n]) {
    for (int m = n + 1; m < ones.length - m + n; m++) {

     if (ONE == ones[m] && ONE == ones[m + m - n]) {
      System.out.println(" IS evenly spaced: " + testBits + '=' + testString);
      System.out.println("               at: " + n + ", " + m + ", " + (m + m - n));
      return true;
     }
    }
   }
  }

  System.out.println("NOT evenly spaced: " + testBits + '=' + testString);
  return false;
 }
}

4
Se não me engano, esse é O (n²) porque o loop externo executa n vezes e o loop interno executa n / 2 vezes em média.
StriplingWarrior

O loop externo executa n vezes e o loop interno executa n / 4 em média, mas é iniciado apenas a partir de posições após um '1'. Para abordar um comportamento n ^ 2, o número de '1s deve ser alto, o que resulta em um resultado verdadeiro antecipadamente, interrompendo o processamento. Portanto, o comportamento n ^ 2 nunca ocorrerá. Como determinar um O com base nas propriedades conhecidas dos dados me escapa no momento.
rsp

Infelizmente, não se trata de tempo médio de execução na vida real, mas de tempo teórico de Big O. E sua abordagem é O (n²) (mesmo que o meu, porque a sua abordagem é a mesma que a minha)
DaClown

Eu não estava falando sobre comportamento médio, mas comportamento máximo. Eu não ficaria surpreso se for possível que a entropia máxima que falha no teste contenha log n '1s na string.
rsp

E se você atualizar o índice no loop externo com o do primeiro 1 encontrado no loop interno, isto é, se (ones [m] == ONE) {n = m}? Isso ajuda o grande O?
steamer25

1

Pensei em uma abordagem de dividir e conquistar que poderia funcionar.

Primeiro, no pré-processamento, você precisa inserir todos os números com menos da metade do seu tamanho de entrada ( n / 3) em uma lista.

Dada uma sequência: 0000010101000100(observe que este exemplo em particular é válido)

Insira todos os números primos (e 1) de 1 a (16/2) em uma lista: {1, 2, 3, 4, 5, 6, 7}

Depois divida ao meio:

100000101 01000100

Continue fazendo isso até chegar às cadeias de tamanho 1. Para todas as cadeias de tamanho um com um 1, adicione o índice da cadeia à lista de possibilidades; caso contrário, retorne -1 para falha.

Você também precisará retornar uma lista de distâncias de espaçamento ainda possíveis, associadas a cada índice inicial. (Comece com a lista que você criou acima e remova os números à medida que avança) Aqui, uma lista vazia significa que você está lidando apenas com 1 e, portanto, qualquer espaçamento é possível neste momento; caso contrário, a lista inclui espaçamentos que devem ser descartados.

Então, continuando com o exemplo acima:

1000 0101 0100 0100

10 00 01 01 01 00 01 00

1 0 0 0 0 1 0 1 0 1 0 0 0 1 0 0

No primeiro passo da combinação, temos oito conjuntos de dois agora. No primeiro, temos a possibilidade de um conjunto, mas aprendemos que o espaçamento de 1 é impossível por causa do outro zero estar lá. Portanto, retornamos 0 (para o índice) e {2,3,4,5,7} pelo fato de que o espaçamento por 1 é impossível. No segundo, não temos nada e, portanto, retornamos -1. No terceiro, temos uma partida sem espaçamento eliminada no índice 5, então retorne 5, {1,2,3,4,5,7}. No quarto par, retornamos 7 {1,2,3,4,5,7}. No quinto, retorne 9 {1,2,3,4,5,7}. No sexto, retorne -1. No sétimo, retorne 13, {1,2,3,4,5,7}. No oitavo, retorne -1.

Combinando novamente em quatro conjuntos de quatro, temos:

1000: Retorno (0, {4,5,6,7}) 0101: Retorno (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6 , 7}) 0100: Retorno (9, {3,4,5,6,7}) 0100: Retorno (13, {3,4,5,6,7})

Combinando em conjuntos de oito:

10000101: Retorno (0, {5,7}), (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6,7}) 01000100: Retorno (9, {4,7}), (13, {3,4,5,6,7})

Combinando um conjunto de dezesseis:

10000101 01000100

À medida que progredimos, continuamos verificando todas as possibilidades até o momento. Até essa etapa, deixamos coisas que iam além do final da string, mas agora podemos verificar todas as possibilidades.

Basicamente, verificamos o primeiro 1 com espaçamento de 5 e 7 e descobrimos que eles não estão alinhados com o número 1. (Observe que cada verificação é CONSTANTE, e não o tempo linear). Depois, verificamos a segunda (índice 5) com espaçamentos de 2, 3, 4, 5, 6 e 7 - ou o faria, mas podemos parar em 2 desde que realmente combina.

Ufa! Esse é um algoritmo bastante longo.

Eu não sei 100% se é O (n log n) por causa da última etapa, mas tudo até lá é definitivamente O (n log n) , tanto quanto eu posso dizer. Voltarei a isso mais tarde e tentarei refinar o último passo.

EDIT: Alterei minha resposta para refletir o comentário de Welbog. Desculpe pelo erro. Também escreverei algum pseudocódigo mais tarde, quando tiver um pouco mais de tempo para decifrar o que escrevi novamente. ;-)


Eu não siga o seu algoritmo, mas +1 para tentar um algoritmo que realmente tenta ser O (n log n)
ldog

Obrigado. Vou tentar explicar melhor quando tiver mais tempo (talvez escreva algum pseudocódigo ou algo assim).
Platinum Azure

Por que você está apenas olhando para as possibilidades de lacunas dos números primos? Como você proporia combinar uma string como 100010001? Se eu entendi sua abordagem corretamente, ela não será compatível porque a resposta correta (0,{4})não é possível calcular. Dado que você precisa de números não primos em sua lista, é fácil criar seqüências patológicas que aumentem as listas de possibilidades que você precisa verificar para maiores que O (n log (n)), eu acho.
27409 Welbog

jura Bem, originalmente eu ia fazer múltiplos, mas meio que mudei minha resposta no meio do caminho e não consegui mudar tudo. Desculpe. Será corrigido em breve
Platinum Azure

3
Eu não acho que é O (n log n). Na primeira etapa de combinação, você trata (n / 2) conjuntos, cada um dos quais possivelmente retorna um conjunto de O (n) espaçamentos possíveis. Isso por si só torna O (n ^ 2), infelizmente.
10133 MartinStettner

1

Vou dar um palpite aqui e deixar que aqueles que são melhores em calcular a complexidade me ajudem a saber como meu algoritmo se sai em termos de notação O

  1. dada sequência binária 0000010101000100 (como exemplo)
  2. cabeça de corte e cauda de zeros -> 00000 101010001 00
  3. obtemos 101010001 do cálculo anterior
  4. verifique se o bit do meio é 'um', se verdadeiro, encontrado válido três 'iguais' espaçados igualmente (somente se o número de bits for ímpar)
  5. correlativamente, se o número de bits cortados restantes for numerado, o cabeçalho e a cauda 'one' não poderão fazer parte do 'one', com espaçamento uniforme,
  6. usamos 1010100001 como exemplo (com um 'zero' extra para se tornar uma colheita numerada uniforme); nesse caso, precisamos recortar novamente e depois tornar-se -> 10101 00001
  7. obtemos 10101 do cálculo anterior e verificamos o bit do meio, e encontramos o bit uniformemente espaçado novamente

Não tenho idéia de como calcular a complexidade para isso, alguém pode ajudar?

edit: adicione algum código para ilustrar minha ideia

edit2: tentei compilar meu código e encontrei alguns erros importantes, corrigidos

char *binaryStr = "0000010101000100";

int main() {
   int head, tail, pos;
   head = 0;
   tail = strlen(binaryStr)-1;
   if( (pos = find3even(head, tail)) >=0 )
      printf("found it at position %d\n", pos);
   return 0;
}

int find3even(int head, int tail) {
   int pos = 0;
   if(head >= tail) return -1;
   while(binaryStr[head] == '0') 
      if(head<tail) head++;
   while(binaryStr[tail] == '0') 
      if(head<tail) tail--;
   if(head >= tail) return -1;
   if( (tail-head)%2 == 0 && //true if odd numbered
       (binaryStr[head + (tail-head)/2] == '1') ) { 
         return head;
   }else {
      if( (pos = find3even(head, tail-1)) >=0 )
         return pos;
      if( (pos = find3even(head+1, tail)) >=0 )
         return pos;
   }
   return -1;
}

@recursive eu acho que funcionará quando alcançou a chamada find3even (cabeça + 1, cauda), que cortará para se tornar 111 na cabeça = 4, você poderia verificar novamente por mim?
22615 Andycjw

@recursive por favor, verifique o código eu adicionei para explicar melhor o código pseudo eu fiz mais cedo, o que não é muito rigorosa e concisa
andycjw

Isso é nlogn - para n bits, esperamos aproximadamente iterações de logn verificando n * c bits em que C é uma constante.
21119 Ron Warholic

Sim, isso parece falhar nos 111001 e 100111 como os casos mais simples. Os 1s espaçados uniformemente não precisam se centrar na parte intermediária.
Dean J

Ele lida com esses casos corretamente, o 111001 possui um número par de bits e, portanto, é imediatamente dividido em 111 e 001. Como o 111 tem um número ímpar de bits e o bit do meio é aquele que retorna com êxito.
Ron Warholic 15/10/2009

1

Eu vim com algo assim:

def IsSymetric(number):
    number = number.strip('0')

    if len(number) < 3:
        return False
    if len(number) % 2 == 0:
        return IsSymetric(number[1:]) or IsSymetric(number[0:len(number)-2])
    else:
        if number[len(number)//2] == '1':
            return True
        return IsSymetric(number[:(len(number)//2)]) or IsSymetric(number[len(number)//2+1:])
    return False

Isso é inspirado no andycjw.

  1. Truncar os zeros.
  2. Se, mesmo assim, teste duas subcadeias 0 - (len-2) (pule o último caractere) e de 1 - (len-1) (pule o primeiro caractere)
  3. Se não for, mesmo que o caractere do meio seja um, teremos sucesso. Em seguida, divida a corda no meio sem o elemento médio e verifique as duas partes.

Quanto à complexidade, isso pode ser O (nlogn), pois em cada recursão estamos dividindo por dois.

Espero que ajude.


Parece que você está convertendo um problema com elementos N em 2 problemas com elementos N-1. Dividi-lo ao meio significaria convertê-lo em 2 problemas com elementos N / 2.
RHSeeger

Esse é apenas o caso de comprimentos uniformes. Portanto, se o len é 8, o algoritmo cria seqüências de comprimento: 7, 7, 3, 3, 3, 3. A altura da árvore de recursão é 3 e isso é igual a lg (8).
21415 Beku

1

Ok, vou dar outra facada no problema. Eu acho que posso provar um algoritmo O (n log (n)) que é semelhante aos já discutidos usando uma árvore binária balanceada para armazenar distâncias entre 1s. Essa abordagem foi inspirada na observação de Justice sobre como reduzir o problema a uma lista de distâncias entre os 1s.

Poderíamos fazer a varredura da sequência de entrada para construir uma árvore binária balanceada em torno da posição dos 1s, de modo que cada nó armazene a posição do 1 e cada borda seja rotulada com a distância do 1 adjacente para cada nó filho. Por exemplo:

10010001 gives the following tree

      3
     / \
  2 /   \ 3
   /     \
  0       7

Isso pode ser feito em O (n log (n)), pois, para uma sequência de tamanho n, cada inserção recebe O (log (n)) no pior caso.

Em seguida, o problema é procurar na árvore para descobrir se, em qualquer nó, existe um caminho desse nó através do filho esquerdo que tem a mesma distância que um caminho através do filho direito. Isso pode ser feito recursivamente em cada subárvore. Ao mesclar duas subárvores na pesquisa, devemos comparar as distâncias dos caminhos na subárvore esquerda com as distâncias dos caminhos à direita. Como o número de caminhos em uma subárvore será proporcional ao log (n) e o número de nós é n, acredito que isso possa ser feito no tempo O (n log (n)).

Eu perdi alguma coisa?


"Como o número de caminhos em uma subárvore será proporcional ao log (n)" Por que não n? Geralmente, essa é uma abordagem promissora.
Sdcvvc 16/10/09

@sdcwc: é proporcional ao log (n) e não n porque em uma árvore balanceada cada subárvore possui metade dos nós, e o número de caminhos para a raiz da subárvore é igual ao número de nós na subárvore (excluindo o raiz).
Jeremy Bourque

0

Parecia um problema divertido, então decidi tentar.

Estou assumindo que 111000001 encontraria os 3 primeiros e seria bem-sucedido. Essencialmente, o número de zeros após o 1 é importante, pois 0111000 é o mesmo que 111000, de acordo com sua definição. Depois de encontrar dois casos de 1, o próximo 1 encontrado completa a trilogia.

Aqui está em Python:

def find_three(bstring):
    print bstring
    dict = {}
    lastone = -1
    zerocount = 0
    for i in range(len(bstring)):
        if bstring[i] == '1':
            print i, ': 1'
            if lastone != -1:
                if(zerocount in dict):
                    dict[zerocount].append(lastone)
                    if len(dict[zerocount]) == 2:
                        dict[zerocount].append(i)
                        return True, dict
                else:
                    dict[zerocount] = [lastone]
            lastone = i
            zerocount = 0
        else:
            zerocount = zerocount + 1
    #this is really just book keeping, as we have failed at this point
    if lastone != -1:
        if(zerocount in dict):
            dict[zerocount].append(lastone)
        else:
            dict[zerocount] = [lastone]
    return False, dict

Esta é uma primeira tentativa, por isso tenho certeza de que isso pode ser escrito de uma maneira mais limpa. Liste os casos em que esse método falha abaixo.


@ recursivo, esses não são uniformemente espaçados.
21419 James McMahon

O que você quer dizer com espaçamento uniforme? Veja o índice 0, 3 e 6. Todos os e com dois separando cada um.
recursivo

Ah, entendo, os zeros foram incluídos apenas no espaçamento.
21711 James McMahon

A pergunta menciona "1001011", na qual isso não funciona. Houve uma resposta anterior (agora excluída), postada imediatamente após a pergunta, que resolveu o mesmo (outro) problema que esse. :-)
ShreevatsaR

Eu estava vendo isso no trabalho hoje e não entendi o que Rob quis dizer com sua edição. Eu editei a pergunta para maior clareza. Eu deveria saber que estava faltando alguma coisa quando tive um tempo fácil com ela.
21711 James McMahon

0

Presumo que o motivo é nlog (n) devido ao seguinte:

  • Para encontrar o 1 que é o início do trigêmeo, é necessário verificar os caracteres (n-2). Se você não o encontrou até esse ponto, não o encontrará (os caracteres n-1 en não podem iniciar um trigêmeo) (O (n))
  • Para encontrar o segundo 1 que faz parte do trigêmeo (iniciado pelo primeiro), é necessário marcar m / 2 (m = nx, onde x é o deslocamento dos primeiros 1) caracteres. Isso ocorre porque, se você não encontrou o segundo 1 quando estiver na metade do primeiro até o fim, não o encontrará ... já que o terceiro 1 deve estar exatamente na mesma distância após o segundo. (O (log (n)))
  • É O (1) para encontrar o último 1, pois você sabe o índice em que ele deve estar no momento em que encontra o primeiro e o segundo.

Então, você tem n, log (n) e 1 ... O (nlogn)

Edit: Opa, meu mal. Meu cérebro definiu que n / 2 era logn ... o que obviamente não é (dobrar o número de itens ainda dobra o número de iterações no loop interno). Isso ainda está em n ^ 2, não resolvendo o problema. Bem, pelo menos eu tenho que escrever algum código :)


Implementação em Tcl

proc get-triplet {input} {
    for {set first 0} {$first < [string length $input]-2} {incr first} {
        if {[string index $input $first] != 1} {
            continue
        }
        set start [expr {$first + 1}]
        set end [expr {1+ $first + (([string length $input] - $first) /2)}]
        for {set second $start} {$second < $end} {incr second} {
            if {[string index $input $second] != 1} {
                continue
            }
            set last [expr {($second - $first) + $second}]
            if {[string index $input $last] == 1} {
                return [list $first $second $last]
            }
        }
    }
    return {}
}

get-triplet 10101      ;# 0 2 4
get-triplet 10111      ;# 0 2 4
get-triplet 11100000   ;# 0 1 2
get-triplet 0100100100 ;# 1 4 7

0

Acho que encontrei uma maneira de resolver o problema, mas não consigo construir uma prova formal. A solução que eu fiz foi escrita em Java e usa um contador 'n' para contar quantos acessos à lista / matriz ele faz. Portanto, n deve ser menor ou igual a stringLength * log (stringLength) se estiver correto. Eu tentei para os números 0 a 2 ^ 22, e funciona.

Ele começa iterando sobre a sequência de entrada e fazendo uma lista de todos os índices que contêm uma. Este é apenas O (n).

Em seguida, na lista de índices, ele escolhe um firstIndex e um secondIndex que é maior que o primeiro. Esses dois índices devem conter um, porque estão na lista de índices. A partir daí, o thirdIndex pode ser calculado. Se o inputString [thirdIndex] for 1, ele será interrompido.

public static int testString(String input){
//n is the number of array/list accesses in the algorithm
int n=0;

//Put the indices of all the ones into a list, O(n)
ArrayList<Integer> ones = new ArrayList<Integer>();
for(int i=0;i<input.length();i++){
    if(input.charAt(i)=='1'){
        ones.add(i);
    }
}

//If less than three ones in list, just stop
if(ones.size()<3){
    return n;
}

int firstIndex, secondIndex, thirdIndex;
for(int x=0;x<ones.size()-2;x++){
    n++;
    firstIndex = ones.get(x);

    for(int y=x+1; y<ones.size()-1; y++){
        n++;
        secondIndex = ones.get(y);
        thirdIndex = secondIndex*2 - firstIndex;

        if(thirdIndex >= input.length()){
            break;
        }

        n++;
        if(input.charAt(thirdIndex) == '1'){
            //This case is satisfied if it has found three evenly spaced ones
            //System.out.println("This one => " + input);
            return n;
        }
    }
}

return n;

}

nota adicional: o contador n não é incrementado quando itera sobre a sequência de entrada para construir a lista de índices. Esta operação é O (n), portanto não afetará a complexidade do algoritmo.


Você ainda parecem ter duas voltas de O (n), aninhado, o que torna O (n ^ 2)
RHSeeger

A matriz de índices não é do mesmo tamanho que a sequência de entrada. Isso está tornando difícil para mim escrever uma prova real ou provar que ela está incorreta. Suspeito que exista alguma ideia matemática subjacente que faça isso funcionar.
Robert Parker

1
Eu acho que o truque para esse problema é que, apesar de seu algoritmo ser O (n ^ 2), o pior caso possível de uma string que você pode obter resultará apenas em iterações O (nlogn), caso contrário, você encontrará uma solução usando seu algoritmo.
1 -

2
testá-lo até 2 ^ 22 não testa sua complexidade. 2 ^ 22 possui apenas 22 bits, o que significa que seu N é 22. Experimente alguns valores em que N é alguns milhões.
Peter Recore

1
Experimente esse algoritmo com uma das seqüências "ruins" máximas fornecidas na resposta de yx e você descobrirá que esse é um O(n^2)algoritmo.
Welbog 16/10/09

0

Uma incursão no problema é pensar em fatores e mudanças.

Com a mudança, você compara a sequência de uns e zeros com uma versão deslocada de si mesma. Você pega os correspondentes. Tomemos este exemplo deslocado por dois:

1010101010
  1010101010
------------
001010101000

Os 1s resultantes (AND bit a bit) devem representar todos os 1s que são espaçados igualmente por dois. O mesmo exemplo mudou em três:

1010101010
   1010101010
-------------
0000000000000

Nesse caso, não há 1's que estão uniformemente espaçados três.

Então, o que isso lhe diz? Bem, você só precisa testar turnos que são números primos. Por exemplo, digamos que você tenha dois 1s, que são seis separados. Você só teria que testar 'dois' turnos e 'três' turnos (uma vez que estes dividem seis). Por exemplo:

10000010 
  10000010 (Shift by two)
    10000010
      10000010 (We have a match)

10000010
   10000010 (Shift by three)
      10000010 (We have a match)

Portanto, os únicos turnos que você precisa verificar são 2,3,5,7,11,13 etc. Até o primo mais próximo da raiz quadrada do tamanho da sequência de dígitos.

Quase resolvido?

Eu acho que estou mais perto de uma solução. Basicamente:

  1. Digitalize a string em busca de 1's. Para cada 1 nota, é restante depois de tomar um módulo de sua posição. O módulo varia de 1 a metade do tamanho da string. Isso ocorre porque o maior tamanho de separação possível é metade da cadeia. Isso é feito em O (n ^ 2). MAS. Somente os módulos primos precisam ser verificados para que O (n ^ 2 / log (n))
  2. Classifique a lista de módulos / remanescentes em ordem do módulo maior primeiro, isso pode ser feito em O (n * log (n)).
  3. Procure três módulos / remanescentes consecutivos iguais.
  4. De alguma forma, recupere a posição daqueles!

Penso que a maior pista para a resposta é que os algoritmos de classificação mais rápidos são O (n * log (n)).

ERRADO

O passo 1 está errado, conforme indicado por um colega. Se tivermos 1's nas posições 2,12 e 102. Então, tomando um módulo de 10, todos terão os mesmos remanescentes e, no entanto, não serão igualmente espaçados! Desculpe.


Essa é uma abordagem interessante, deixe-nos saber se você tiver uma solução completa.
21711 James McMahon

deslocamento por um número k O (n) vezes e, em seguida, verificações O (n) por turno produz um algoritmo O (n ^ 2), mesmo se você estivesse deslocando um número. Seu algoritmo teria que mudar em mais de um número.
Ldog 14/10/09

0

Aqui estão alguns pensamentos que, apesar de meus esforços, parecem não se envolver. Ainda assim, eles podem ser um ponto de partida útil para a análise de alguém.

Considere a solução proposta da seguinte forma, que é a abordagem sugerida por várias pessoas, inclusive eu em uma versão anterior desta resposta. :)

  1. Apare os zeros à esquerda e à direita.
  2. Digitalize a string procurando 1's.
  3. Quando um 1 é encontrado:
    1. Suponha que seja o 1 do meio da solução.
    2. Para cada 1 anterior, use sua posição salva para calcular a posição antecipada do 1 final.
    3. Se a posição calculada for após o final da sequência, ela não poderá fazer parte da solução, portanto, retire a posição da lista de candidatos.
    4. Verifique a solução.
  4. Se a solução não foi encontrada, adicione o 1 atual à lista de candidatos.
  5. Repita até que não sejam encontrados mais 1s.

Agora considere as seqüências de caracteres de entrada como as seguintes, que não terão uma solução:

101
101001
1010010001
101001000100001
101001000100001000001

Em geral, esta é a concatenação de k strings da forma j 0's, seguida de um 1 para j de zero a k-1.

k=2  101
k=3  101001
k=4  1010010001
k=5  101001000100001
k=6  101001000100001000001

Observe que os comprimentos das substrings são 1, 2, 3 etc. Portanto, o tamanho do problema n possui substrings de comprimentos 1 a k, de modo que n = k (k + 1) / 2.

k=2  n= 3  101
k=3  n= 6  101001
k=4  n=10  1010010001
k=5  n=15  101001000100001
k=6  n=21  101001000100001000001

Observe que k também rastreia o número de 1s que devemos considerar. Lembre-se de que toda vez que vemos um 1, precisamos considerar todos os 1 vistos até agora. Portanto, quando vemos o segundo 1, consideramos apenas o primeiro, quando vemos o terceiro 1, reconsideramos os dois primeiros, quando vemos o quarto 1, precisamos reconsiderar os três primeiros e assim por diante. No final do algoritmo, consideramos k (k-1) / 2 pares de 1's. Chame isso de p.

k=2  n= 3  p= 1  101
k=3  n= 6  p= 3  101001
k=4  n=10  p= 6  1010010001
k=5  n=15  p=10  101001000100001
k=6  n=21  p=15  101001000100001000001

A relação entre n e p é que n = p + k.

O processo de passar pela string leva O (n) tempo. Sempre que um 1 é encontrado, são feitas comparações no máximo (k-1). Como n = k (k + 1) / 2, n> k ** 2, então sqrt (n)> k. Isso nos dá O (n sqrt (n)) ou O (n ** 3/2). Observe, no entanto, que pode não ser um limite muito restrito, porque o número de comparações varia de 1 a um máximo de k, não é k o tempo todo. Mas não sei como explicar isso em matemática.

Ainda não é O (n log (n)). Além disso, não posso provar que esses dados são os piores casos, embora eu suspeite que sejam. Eu acho que um empacotamento mais denso de 1 para a frente resulta em um empacotamento ainda mais esparso no final.

Como alguém ainda pode achar útil, aqui está o meu código para essa solução no Perl:

#!/usr/bin/perl

# read input as first argument
my $s = $ARGV[0];

# validate the input
$s =~ /^[01]+$/ or die "invalid input string\n";

# strip leading and trailing 0's
$s =~ s/^0+//;
$s =~ s/0+$//;

# prime the position list with the first '1' at position 0
my @p = (0);

# start at position 1, which is the second character
my $i = 1;

print "the string is $s\n\n";

while ($i < length($s)) {
   if (substr($s, $i, 1) eq '1') {
      print "found '1' at position $i\n";
      my @t = ();
      # assuming this is the middle '1', go through the positions
      # of all the prior '1's and check whether there's another '1'
      # in the correct position after this '1' to make a solution
      while (scalar @p) {
         # $p is the position of the prior '1'
         my $p = shift @p;
         # $j is the corresponding position for the following '1'
         my $j = 2 * $i - $p;
         # if $j is off the end of the string then we don't need to
         # check $p anymore
         next if ($j >= length($s));
         print "checking positions $p, $i, $j\n";
         if (substr($s, $j, 1) eq '1') {
            print "\nsolution found at positions $p, $i, $j\n";
            exit 0;
         }
         # if $j isn't off the end of the string, keep $p for next time
         push @t, $p;
      }
      @p = @t;
      # add this '1' to the list of '1' positions
      push @p, $i;
   }
   $i++;
}

print "\nno solution found\n";

Sua sequência de "não solução" está incorreta; o índice de cada 1 é a sequência dos números triangulares 1, 3, 6, 10, 15 ... etc. e inclui os números 6, 36 e 66, que formam uma progressão aritmética.
21713 Jason S

0

Ao digitalizar 1s, adicione suas posições a uma Lista. Ao adicionar os segundos 1s e sucessivos, compare-os com cada posição da lista até agora. O espaçamento é igual a currentOne (centro) - previousOne (esquerda). O bit do lado direito é currentOne + espaçamento. Se é 1, o fim.

A lista de unidades cresce inversamente com o espaço entre elas. Em termos simples, se você tiver muitos 0s entre os 1s (na pior das hipóteses), sua lista de 1s conhecidos crescerá muito lentamente.

using System;
using System.Collections.Generic;

namespace spacedOnes
{
    class Program
    {
        static int[] _bits = new int[8] {128, 64, 32, 16, 8, 4, 2, 1};

        static void Main(string[] args)
        {
            var bytes = new byte[4];
            var r = new Random();
            r.NextBytes(bytes);
            foreach (var b in bytes) {
                Console.Write(getByteString(b));
            }
            Console.WriteLine();
            var bitCount = bytes.Length * 8;
            var done = false;
            var onePositions = new List<int>();
            for (var i = 0; i < bitCount; i++)
            {
                if (isOne(bytes, i)) {
                    if (onePositions.Count > 0) {
                        foreach (var knownOne in onePositions) {
                            var spacing = i - knownOne;
                            var k = i + spacing;
                            if (k < bitCount && isOne(bytes, k)) {
                                Console.WriteLine("^".PadLeft(knownOne + 1) + "^".PadLeft(spacing) + "^".PadLeft(spacing));
                                done = true;
                                break;
                            }
                        }
                    }
                    if (done) {
                        break;
                    }
                    onePositions.Add(i);
                }
            }
            Console.ReadKey();
        }

        static String getByteString(byte b) {
            var s = new char[8];
            for (var i=0; i<s.Length; i++) {
                s[i] = ((b & _bits[i]) > 0 ? '1' : '0');
            }
            return new String(s);
        }

        static bool isOne(byte[] bytes, int i)
        {
            var byteIndex = i / 8;
            var bitIndex = i % 8;
            return (bytes[byteIndex] & _bits[bitIndex]) > 0;
        }
    }
}

0

Pensei em adicionar um comentário antes de postar a 22ª solução ingênua para o problema. Para a solução ingênua, não precisamos mostrar que o número de 1s na string é no máximo O (log (n)), mas sim que é no máximo O (sqrt (n * log (n)).

Solver:

def solve(Str):
    indexes=[]
    #O(n) setup
    for i in range(len(Str)):
        if Str[i]=='1':
            indexes.append(i)

    #O((number of 1's)^2) processing
    for i in range(len(indexes)):
        for j in range(i+1, len(indexes)):
                            indexDiff = indexes[j] - indexes[i]
            k=indexes[j] + indexDiff
            if k<len(Str) and Str[k]=='1':
                return True
    return False

É basicamente um pouco parecido com a idéia e a implementação do flybywire, embora olhando para a frente em vez de para trás.

Construtor ganancioso de cordas:

#assumes final char hasn't been added, and would be a 1 
def lastCharMakesSolvable(Str):
    endIndex=len(Str)
    j=endIndex-1
    while j-(endIndex-j) >= 0:
        k=j-(endIndex-j)
        if k >= 0 and Str[k]=='1' and Str[j]=='1':
            return True
        j=j-1
    return False



def expandString(StartString=''):
    if lastCharMakesSolvable(StartString):
        return StartString + '0'
    return StartString + '1'

n=1
BaseStr=""
lastCount=0
while n<1000000:
    BaseStr=expandString(BaseStr)
    count=BaseStr.count('1')
    if count != lastCount:
        print(len(BaseStr), count)
    lastCount=count
    n=n+1

(Em minha defesa, ainda estou no estágio de entendimento 'aprender python')

Além disso, saída potencialmente útil da construção gananciosa de cordas, há um salto bastante consistente depois de atingir uma potência de 2 no número de 1's ... que eu não estava disposto a esperar para testemunhar atingindo 2096.

strlength   # of 1's
    1    1
    2    2
    4    3
    5    4
   10    5
   14    8
   28    9
   41    16
   82    17
  122    32
  244    33
  365    64
  730    65
 1094    128
 2188    129
 3281    256
 6562    257
 9842    512
19684    513
29525    1024

0

Vou tentar apresentar uma abordagem matemática. Isso é mais um começo do que um fim; portanto, qualquer ajuda, comentário ou mesmo contradição será profundamente apreciada. No entanto, se essa abordagem for comprovada - o algoritmo é uma pesquisa direta na string.

  1. Dado um número fixo de espaços ke uma string S, a busca por um trigêmeo com espaçamento k leva O(n)- Nós simplesmente testamos todos os 0<=i<=(n-2k)if S[i]==S[i+k]==S[i+2k]. O teste leva O(1)e fazemos n-kvezes que ké uma constante, por isso leva O(n-k)=O(n).

  2. Vamos supor que exista uma proporção inversa entre o número de 1's e o máximo de espaços que precisamos procurar. Ou seja, se existem muitos 1, deve haver um trigêmeo e deve ser bastante denso; Se houver apenas alguns 1, o trigêmeo (se houver) pode ser bastante escasso. Em outras palavras, posso provar que, se tenho o suficiente 1, esse trigêmeo deve existir - e quanto mais 1eu tenho, um trigêmeo mais denso deve ser encontrado. Isso pode ser explicado pelo princípio Pigeonhole - Espero elaborar mais adiante.

  3. Digamos que tenha um limite superior kno número possível de espaços que tenho que procurar. Agora, para cada 1localizado em S[i], precisamos fazer check- 1in S[i-1]e S[i+1], S[i-2]e S[i+2], ... S[i-k]e S[i+k]. Isso leva O((k^2-k)/2)=O(k^2)para cada 1em S- devido a Gauss' Formula Series Soma . Observe que isso difere da seção 1 - estou tendo kcomo limite superior o número de espaços, não como espaço constante.

Nós precisamos provar O(n*log(n)). Ou seja, precisamos mostrar que k*(number of 1's)é proporcional a log(n).

Se pudermos fazer isso, o algoritmo é trivial - para cada um 1em Scujo índice esteja i, basta procurar 1os de cada lado até a distância k. Se dois foram encontrados na mesma distância, retorne ie k. Novamente, a parte complicada seria encontrar ke provar a correção.

Eu realmente aprecio seus comentários aqui - eu tenho tentado encontrar a relação entre ke o número de 1's no meu quadro, até agora sem sucesso.


0

Suposição:

Apenas errado, falando sobre o número de log (n) do limite superior de unidades

EDITAR:

Agora, descobri que, usando os números do Cantor (se correto), a densidade no set é (2/3) ^ Log_3 (n) (que função estranha) e concordo que a densidade do log (n) / n é muito alta.

Se esse for o limite superior, existe um algoritmo que resolve esse problema em pelo menos O (n * (3/2) ^ (log (n) / log (3))) complexidade de tempo e O ((3/2) ^ ( log (n) / log (3))) complexidade do espaço. (verifique a resposta da Justiça para o algoritmo)

Isso ainda é muito melhor que O (n ^ 2)

Essa função ((3/2) ^ (log (n) / log (3))) realmente se parece com n * log (n) à primeira vista.

Como consegui essa fórmula?

Visualizando o número dos cantores na string.
Suponha que o comprimento da string seja 3 ^ p == n
Em cada etapa da geração da string Cantor, você mantém 2/3 do número anterior de unidades. Aplique este p vezes.

Isso significa que (n * ((2/3) ^ p)) -> (((3 ^ p)) * ((2/3) ^ p)) os restantes e após a simplificação 2 ^ p. Isso significa 2 ^ p ones em 3 ^ p string -> (3/2) ^ p ones. Substitua p = log (n) / log (3) e obtenha
((3/2) ^ (log (n) / log (3)))


Falso: o conjunto de cantores possui densidade n ^ log_3 (2).
Sdcvvc 16/10/09

0

Que tal uma solução O (n) simples, com espaço O (n ^ 2)? (Usa a suposição de que todos os operadores bit a bit funcionam em O (1).)

O algoritmo basicamente funciona em quatro estágios:

Etapa 1: para cada bit em seu número original, descubra a que distância estão os números, mas considere apenas uma direção. (Eu considerei todos os bits na direção do bit menos significativo.)

Etapa 2: Inverta a ordem dos bits na entrada;

Etapa 3: Execute novamente a etapa 1 na entrada reversa.

Etapa 4: Compare os resultados da Etapa 1 e da Etapa 3. Se algum bit estiver igualmente espaçado acima E abaixo, devemos obter um resultado.

Lembre-se de que nenhuma etapa do algoritmo acima leva mais tempo que O (n). ^ _ ^

Como um benefício adicional, este algoritmo encontrará TODOS os igualmente espaçados de CADA número. Por exemplo, se você obtiver um resultado de "0x0005", haverá espaços igualmente espaçados em ambas as unidades 1 e 3

Eu realmente não tentei otimizar o código abaixo, mas é um código C # compilável que parece funcionar.

using System;

namespace ThreeNumbers
{
    class Program
    {
        const int uint32Length = 32;

        static void Main(string[] args)
        {
            Console.Write("Please enter your integer: ");
            uint input = UInt32.Parse(Console.ReadLine());

            uint[] distancesLower = Distances(input);
            uint[] distancesHigher = Distances(Reverse(input));

            PrintHits(input, distancesLower, distancesHigher);
        }

        /// <summary>
        /// Returns an array showing how far the ones away from each bit in the input.  Only 
        /// considers ones at lower signifcant bits.  Index 0 represents the least significant bit 
        /// in the input.  Index 1 represents the second least significant bit in the input and so 
        /// on.  If a one is 3 away from the bit in question, then the third least significant bit 
        /// of the value will be sit.
        /// 
        /// As programed this algorithm needs: O(n) time, and O(n*log(n)) space.  
        /// (Where n is the number of bits in the input.)
        /// </summary>
        public static uint[] Distances(uint input)
        {
            uint[] distanceToOnes = new uint[uint32Length];
            uint result = 0;

            //Sets how far each bit is from other ones. Going in the direction of LSB to MSB
            for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex)
            {
                distanceToOnes[arrayIndex] = result;
                result <<= 1;

                if ((input & bitIndex) != 0)
                {
                    result |= 1;
                }
            }

            return distanceToOnes;
        }

        /// <summary>
        /// Reverses the bits in the input.
        /// 
        /// As programmed this algorithm needs O(n) time and O(n) space.  
        /// (Where n is the number of bits in the input.)
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static uint Reverse(uint input)
        {
            uint reversedInput = 0;
            for (uint bitIndex = 1; bitIndex != 0; bitIndex <<= 1)
            {
                reversedInput <<= 1;
                reversedInput |= (uint)((input & bitIndex) != 0 ? 1 : 0);
            }

            return reversedInput;
        }

        /// <summary>
        /// Goes through each bit in the input, to check if there are any bits equally far away in 
        /// the distancesLower and distancesHigher
        /// </summary>
        public static void PrintHits(uint input, uint[] distancesLower, uint[] distancesHigher)
        {
            const int offset = uint32Length - 1;

            for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex)
            {
                //hits checks if any bits are equally spaced away from our current value
                bool isBitSet = (input & bitIndex) != 0;
                uint hits = distancesLower[arrayIndex] & distancesHigher[offset - arrayIndex];

                if (isBitSet && (hits != 0))
                {
                    Console.WriteLine(String.Format("The {0}-th LSB has hits 0x{1:x4} away", arrayIndex + 1, hits));
                }
            }
        }
    }
}

Alguém provavelmente comentará que, para qualquer número suficientemente grande, as operações bit a bit não podem ser feitas em O (1). Você estaria certo. No entanto, eu suporia que toda solução que usa adição, subtração, multiplicação ou divisão (que não pode ser feita por deslocamento) também teria esse problema.


0

Abaixo está uma solução. Pode haver alguns pequenos erros aqui e ali, mas a idéia é sólida.

Edit: Não é n * log (n)

PSEUDO-CÓDIGO:

foreach character in the string
  if the character equals 1 {         
     if length cache > 0 { //we can skip the first one
        foreach location in the cache { //last in first out kind of order
           if ((currentlocation + (currentlocation - location)) < length string)
              if (string[(currentlocation + (currentlocation - location))] equals 1)
                 return found evenly spaced string
           else
              break;
        }
     }
     remember the location of this character in a some sort of cache.
  }

return didn't find evenly spaced string

Código c #:

public static Boolean FindThreeEvenlySpacedOnes(String str) {
    List<int> cache = new List<int>();

    for (var x = 0; x < str.Length; x++) {
        if (str[x] == '1') {
            if (cache.Count > 0) {
                for (var i = cache.Count - 1; i > 0; i--) {
                    if ((x + (x - cache[i])) >= str.Length)
                        break;

                    if (str[(x + (x - cache[i]))] == '1')
                        return true;                            
                }
            }
            cache.Add(x);                    
        }
    }

    return false;
}

Como funciona:

iteration 1:
x
|
101101001
// the location of this 1 is stored in the cache

iteration 2:
 x
 | 
101101001

iteration 3:
a x b 
| | | 
101101001
//we retrieve location a out of the cache and then based on a 
//we calculate b and check if te string contains a 1 on location b

//and of course we store x in the cache because it's a 1

iteration 4:
  axb  
  |||  
101101001

a  x  b  
|  |  |  
101101001


iteration 5:
    x  
    |  
101101001

iteration 6:
   a x b 
   | | | 
101101001

  a  x  b 
  |  |  | 
101101001
//return found evenly spaced string

1
Seu algoritmo funciona, mas você precisa provar que é menor que O (n ^ 2). A análise trivial leva você a O (n ^ 2). Para cada 1, você repassa todos os 1s anteriores. Fazendo a função de complexidade ser 1 + 2 + 3 + ... + (k / 2-1) = O (k ^ 2) [onde k é o número de 1s].
Anna

Eu verifiquei e na verdade o pior cenário sem solução é maior, em seguida, O (n * log (n))
Niek H.

0

Obviamente, precisamos pelo menos verificar grupos de trigêmeos ao mesmo tempo, portanto, precisamos comprimir os cheques de alguma forma. Eu tenho um algoritmo candidato, mas a análise da complexidade do tempo está além da minha capacidade * limite de tempo.

Crie uma árvore em que cada nó tenha três filhos e cada nó contenha o número total de 1s em suas folhas. Crie uma lista vinculada ao longo dos 1s também. Atribua a cada nó um custo permitido proporcional ao intervalo que ele cobre. Enquanto o tempo que gastamos em cada nó estiver dentro do orçamento, teremos um algoritmo O (n lg n).

-

Comece pela raiz. Se o quadrado do número total de 1s abaixo for menor que o custo permitido, aplique o algoritmo ingênuo. Caso contrário, recorrer a seus filhos.

Agora, retornamos dentro do orçamento ou sabemos que não há trigêmeos válidos totalmente contidos em uma das crianças. Portanto, devemos verificar os trigêmeos entre nós.

Agora as coisas ficam incrivelmente bagunçadas. Queremos, essencialmente, recuar sobre os possíveis conjuntos de filhos, limitando o alcance. Assim que o intervalo for restrito o suficiente para que o algoritmo ingênuo seja executado dentro do orçamento, você o fará. Desfrute de implementar isso, porque eu garanto que será tedioso. Há uma dúzia de casos.

-

A razão pela qual acho que o algoritmo funcionará é porque as seqüências sem trigêmeos válidos parecem alternar entre grupos de 1 e muitos 0. Ele efetivamente divide o espaço de pesquisa próximo e a árvore emula essa divisão.

O tempo de execução do algoritmo não é óbvio. Ele se baseia nas propriedades não triviais da sequência. Se os 1s são realmente escassos, o algoritmo ingênuo funcionará dentro do orçamento. Se os 1s são densos, uma correspondência deve ser encontrada imediatamente. Mas se a densidade é "perfeita" (por exemplo, perto de ~ n ^ 0,63, que você pode obter definindo todos os bits em posições sem o dígito '2' na base 3), não sei se funcionará. Você teria que provar que o efeito de divisão é forte o suficiente.


0

Nenhuma resposta teórica aqui, mas escrevi um programa Java rápido para explorar o comportamento em tempo de execução em função de k e n, em que n é o comprimento total de bits e k é o número de 1s. Estou com alguns dos respondentes que estão dizendo que o algoritmo "regular" que verifica todos os pares de posições de bits e procura o terceiro bit, mesmo que exija O (k ^ 2) na pior das hipóteses, em A realidade porque o pior caso precisa de cadeias de bits esparsas, é O (n ln n).

Enfim, aqui está o programa abaixo. É um programa no estilo Monte-Carlo que executa um grande número de tentativas NTRIALS para constante n e gera aleatoriamente conjuntos de bits para uma faixa de valores k usando processos de Bernoulli com densidade de unidades restrita entre limites que podem ser especificados e registra o tempo de execução de encontrar ou não encontrar um triplo de espaçados uniformemente, tempo medido nas etapas NÃO no tempo da CPU. Executei-o para n = 64, 256, 1024, 4096, 16384 * (ainda em execução), primeiro um teste com 500000 tentativas para ver quais valores de k levam mais tempo, depois outro teste com 5000000 tentativas com estreitas- densidade para ver como são esses valores. Os tempos de execução mais longos ocorrem com uma densidade muito esparsa (por exemplo, para n = 4096, os picos do tempo de execução estão na faixa k = 16-64, com um pico suave para o tempo de execução médio em 4212 etapas @ k = 31, tempo de execução máximo atingiu 5101 etapas @ k = 58). Parece que seriam necessários valores extremamente grandes de N para a pior etapa O (k ^ 2) se tornar maior que a etapa O (n) em que você varre a cadeia de bits para encontrar os índices de posição do 1.

package com.example.math;

import java.io.PrintStream;
import java.util.BitSet;
import java.util.Random;

public class EvenlySpacedOnesTest {
    static public class StatisticalSummary
    {
        private int n=0;
        private double min=Double.POSITIVE_INFINITY;
        private double max=Double.NEGATIVE_INFINITY;
        private double mean=0;
        private double S=0;

        public StatisticalSummary() {}
        public void add(double x) {
            min = Math.min(min, x);
            max = Math.max(max, x);
            ++n;
            double newMean = mean + (x-mean)/n;
            S += (x-newMean)*(x-mean);
            // this algorithm for mean,std dev based on Knuth TAOCP vol 2
            mean = newMean;
        }
        public double getMax() { return (n>0)?max:Double.NaN; }
        public double getMin() { return (n>0)?min:Double.NaN; }
        public int getCount() { return n; }
        public double getMean() { return (n>0)?mean:Double.NaN; }
        public double getStdDev() { return (n>0)?Math.sqrt(S/n):Double.NaN; } 
        // some may quibble and use n-1 for sample std dev vs population std dev    
        public static void printOut(PrintStream ps, StatisticalSummary[] statistics) {
            for (int i = 0; i < statistics.length; ++i)
            {
                StatisticalSummary summary = statistics[i];
                ps.printf("%d\t%d\t%.0f\t%.0f\t%.5f\t%.5f\n",
                        i,
                        summary.getCount(),
                        summary.getMin(),
                        summary.getMax(),
                        summary.getMean(),
                        summary.getStdDev());
            }
        }
    }

    public interface RandomBernoulliProcess // see http://en.wikipedia.org/wiki/Bernoulli_process
    {
        public void setProbability(double d);
        public boolean getNextBoolean();
    }

    static public class Bernoulli implements RandomBernoulliProcess
    {
        final private Random r = new Random();
        private double p = 0.5;
        public boolean getNextBoolean() { return r.nextDouble() < p; }
        public void setProbability(double d) { p = d; }
    }   
    static public class TestResult {
        final public int k;
        final public int nsteps;
        public TestResult(int k, int nsteps) { this.k=k; this.nsteps=nsteps; } 
    }

    ////////////
    final private int n;
    final private int ntrials;
    final private double pmin;
    final private double pmax;
    final private Random random = new Random();
    final private Bernoulli bernoulli = new Bernoulli();
    final private BitSet bits;
    public EvenlySpacedOnesTest(int n, int ntrials, double pmin, double pmax) {
        this.n=n; this.ntrials=ntrials; this.pmin=pmin; this.pmax=pmax;
        this.bits = new BitSet(n);
    }

    /*
     * generate random bit string
     */
    private int generateBits()
    {
        int k = 0; // # of 1's
        for (int i = 0; i < n; ++i)
        {
            boolean b = bernoulli.getNextBoolean();
            this.bits.set(i, b);
            if (b) ++k;
        }
        return k;
    }

    private int findEvenlySpacedOnes(int k, int[] pos) 
    {
        int[] bitPosition = new int[k];
        for (int i = 0, j = 0; i < n; ++i)
        {
            if (this.bits.get(i))
            {
                bitPosition[j++] = i;
            }
        }
        int nsteps = n; // first, it takes N operations to find the bit positions.
        boolean found = false;
        if (k >= 3) // don't bother doing anything if there are less than 3 ones. :(
        {       
            int lastBitSetPosition = bitPosition[k-1];
            for (int j1 = 0; !found && j1 < k; ++j1)
            {
                pos[0] = bitPosition[j1];
                for (int j2 = j1+1; !found && j2 < k; ++j2)
                {
                    pos[1] = bitPosition[j2];

                    ++nsteps;
                    pos[2] = 2*pos[1]-pos[0];
                    // calculate 3rd bit index that might be set;
                    // the other two indices point to bits that are set
                    if (pos[2] > lastBitSetPosition)
                        break;
                    // loop inner loop until we go out of bounds

                    found = this.bits.get(pos[2]);
                    // we're done if we find a third 1!
                }
            }
        }
        if (!found)
            pos[0]=-1;
        return nsteps;
    }

    /*
     * run an algorithm that finds evenly spaced ones and returns # of steps.
     */
    public TestResult run()
    {
        bernoulli.setProbability(pmin + (pmax-pmin)*random.nextDouble());
        // probability of bernoulli process is randomly distributed between pmin and pmax

        // generate bit string.
        int k = generateBits();
        int[] pos = new int[3];
        int nsteps = findEvenlySpacedOnes(k, pos);
        return new TestResult(k, nsteps); 
    }

    public static void main(String[] args)
    {
        int n;
        int ntrials;
        double pmin = 0, pmax = 1;
        try {
            n = Integer.parseInt(args[0]);
            ntrials = Integer.parseInt(args[1]);
            if (args.length >= 3)
                pmin = Double.parseDouble(args[2]);
            if (args.length >= 4)
                pmax = Double.parseDouble(args[3]);
        }
        catch (Exception e)
        {
            System.out.println("usage: EvenlySpacedOnesTest N NTRIALS [pmin [pmax]]");
            System.exit(0);
            return; // make the compiler happy
        }

        final StatisticalSummary[] statistics;
        statistics=new StatisticalSummary[n+1];
        for (int i = 0; i <= n; ++i)
        {
            statistics[i] = new StatisticalSummary();
        }

        EvenlySpacedOnesTest test = new EvenlySpacedOnesTest(n, ntrials, pmin, pmax);
        int printInterval=100000;
        int nextPrint = printInterval;
        for (int i = 0; i < ntrials; ++i)
        {
            TestResult result = test.run();
            statistics[result.k].add(result.nsteps);
            if (i == nextPrint)
            {
                System.err.println(i);
                nextPrint += printInterval;
            }
        }
        StatisticalSummary.printOut(System.out, statistics);
    }
}

0
# <algorithm>
def contains_evenly_spaced?(input)
  return false if input.size < 3
  one_indices = []
  input.each_with_index do |digit, index|
    next if digit == 0
    one_indices << index
  end
  return false if one_indices.size < 3
  previous_indexes = []
  one_indices.each do |index|
    if !previous_indexes.empty?
      previous_indexes.each do |previous_index|
        multiple = index - previous_index
        success_index = index + multiple
        return true if input[success_index] == 1
      end
    end
    previous_indexes << index
  end
  return false
end
# </algorithm>

def parse_input(input)
  input.chars.map { |c| c.to_i }
end

Estou tendo problemas com os piores cenários com milhões de dígitos. A difusão de /dev/urandomessencialmente fornece O (n), mas eu sei que o pior caso é pior que isso. Eu simplesmente não posso dizer o quanto pior. Para os pequenos n, é trivial encontrar entradas em torno de3*n*log(n) , mas é surpreendentemente difícil diferenciá-las de alguma outra ordem de crescimento para esse problema em particular.

Alguém que estava trabalhando nas entradas dos piores casos pode gerar uma string com comprimento maior que, digamos, cem mil?


Como apontei na minha resposta, é fácil gerar seqüências ruins (embora não sejam as piores) de qualquer número de dígitos: coloque 1s exatamente nas posições p que não contêm "1" s em sua representação ternária (ou seja, em posições 2, 6, 8, 18, 20, 24, 26, 54, 56, 60 ...: consulte as fórmulas em research.att.com/~njas/sequences/…). Para 3 ^ 13 × 1 milhão, isso tem 2 ^ 13 × 8000 1s. O tempo de execução em tais strings será ≈ n ^ (1,26) - o que ainda pode ser difícil de distinguir de O (n log n) para um número tão pequeno de n. Experimente e veja.
ShreevatsaR


-3

Isso poderia ser uma solução? Não tenho certeza se é O (nlogn), mas na minha opinião é melhor que O (n²) porque a única maneira de não encontrar um triplo seria uma distribuição de números primos.

Há espaço para melhorias, o segundo encontrado 1 pode ser o próximo primeiro 1. Também não há verificação de erros.

#include <iostream>

#include <string>

int findIt(std::string toCheck) {
    for (int i=0; i<toCheck.length(); i++) {
        if (toCheck[i]=='1') {
            std::cout << i << ": " << toCheck[i];
            for (int j = i+1; j<toCheck.length(); j++) {
                if (toCheck[j]=='1' && toCheck[(i+2*(j-i))] == '1') {
                    std::cout << ", " << j << ":" << toCheck[j] << ", " << (i+2*(j-i)) << ":" << toCheck[(i+2*(j-i))] << "    found" << std::endl;
                    return 0;
                }
            }
        }
    }
    return -1;
}

int main (int agrc, char* args[]) {
    std::string toCheck("1001011");
    findIt(toCheck);
    std::cin.get();
    return 0;
}

1
Tecnicamente, este é O (n ^ 2). Em média, o loop interno itera mais da metade de n cada vez que é executado. Assim, poderia ser escrita como O (n * (n / 2)), e que pode ser simplificada para O (n ^ 2)
Robert Parker

Hm, parece que você está certo. Este não é um problema simples, apenas para encontrar todas as 1 tomadas O (n), não há muito espaço para qualquer pesquisa / comparação adicional com a complexidade de O (logn).
DaClown 14/10/09

-3

Eu acho que esse algoritmo tem complexidade O (n log n) (C ++, DevStudio 2k5). Agora, não sei os detalhes de como analisar um algoritmo para determinar sua complexidade, por isso adicionei algumas informações de coleta de métricas ao código. O código conta o número de testes feitos na sequência de 1 e 0 para qualquer entrada (espero que eu não tenha feito as bolas do algoritmo). Podemos comparar o número real de testes com o valor O e ver se há uma correlação.

#include <iostream>
using namespace std;

bool HasEvenBits (string &sequence, int &num_compares)
{
  bool
    has_even_bits = false;

  num_compares = 0;

  for (unsigned i = 1 ; i <= (sequence.length () - 1) / 2 ; ++i)
  {
    for (unsigned j = 0 ; j < sequence.length () - 2 * i ; ++j)
    {
      ++num_compares;
      if (sequence [j] == '1' && sequence [j + i] == '1' && sequence [j + i * 2] == '1')
      {
        has_even_bits = true;
        // we could 'break' here, but I want to know the worst case scenario so keep going to the end
      }
    }
  }

  return has_even_bits;
}

int main ()
{
  int
    count;

  string
    input = "111";

  for (int i = 3 ; i < 32 ; ++i)
  {
    HasEvenBits (input, count);
    cout << i << ", " << count << endl;
    input += "0";
  }
}

Este programa gera o número de testes para cada comprimento de cadeia com até 32 caracteres. Aqui estão os resultados:

 n  Tests  n log (n)
=====================
 3     1     1.43
 4     2     2.41
 5     4     3.49
 6     6     4.67
 7     9     5.92
 8    12     7.22
 9    16     8.59
10    20    10.00
11    25    11.46
12    30    12.95
13    36    14.48
14    42    16.05
15    49    17.64
16    56    19.27
17    64    20.92
18    72    22.59
19    81    24.30
20    90    26.02
21   100    27.77
22   110    29.53
23   121    31.32
24   132    33.13
25   144    34.95
26   156    36.79
27   169    38.65
28   182    40.52
29   196    42.41
30   210    44.31
31   225    46.23

Eu adicionei os valores 'n log n' também. Plote-os usando sua ferramenta gráfica de escolha para ver uma correlação entre os dois resultados. Essa análise se estende a todos os valores de n? Eu não sei.


Não é uma correlação perfeita, eu concordo. No entanto, a curva está mais próxima de n log n do que para n ^ 2.
Skizz

3
Tente aumentar o tamanho da entrada em um milhão ou mais. Em entradas pequenas, a curva geralmente se parece com as curvas de algoritmos que são obviamente melhores quando o tamanho da entrada é aumentado.
22610 Nick Larsen

Um loop for duplo com o interno delimitado pelo externo cria uma forma triangular, que ainda tem O (n ^ 2) em complexidade. Pense em tudo (i, j) de modo que i em [0, n] ej em [0, n-2 * i], você tenha um triângulo e a área de um triângulo tenha uma tendência quadrática.
Matthieu M.

Para ser mais preciso, Testes = (n ^ 2-2n) / 4 para o mesmo n; obviamente quadrático.
Vovô
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.