Recebi o seguinte problema durante uma entrevista:
Dá uma string que contém uma mistura de parênteses (não colchetes ou chaves - somente parênteses) com outros caracteres alfanuméricos, identifica todas as parênteses que não têm parênteses correspondentes.
Por exemplo, na cadeia ") (ab))", os índices 0 e 5 contêm parênteses que não têm parênteses correspondentes.
Eu apresentei uma solução O (n) em funcionamento usando memória O (n), usando uma pilha e passando pela string depois de adicionar parens à pilha e removê-los da pilha sempre que encontrava um parêntese de fechamento e o topo da pilha continha um parêntese de abertura.
Posteriormente, o entrevistador observou que o problema poderia ser resolvido em tempo linear com memória constante (como em, nenhum uso adicional de memória além do que é absorvido pela entrada).
Perguntei como e ela disse algo sobre atravessar a corda uma vez da esquerda identificando todas as parênteses abertas, e depois uma segunda vez da direita identificando todas as parênteses próximas ... ou talvez fosse o contrário. Eu realmente não entendi e não queria pedir que ela me segurasse com ela.
Alguém pode esclarecer a solução que ela sugeriu?
fonte
Respostas:
Como isso vem de um background de programação e não de um exercício teórico de ciência da computação, suponho que seja necessária uma memóriaO ( 1 ) para armazenar um índice na string. Na ciência da computação teórica, isso significaria usar o modelo de RAM; com máquinas de Turing você não poderia fazer isso e você precisa de Θ ( log( N ) ) de memória para armazenar um índice para uma cadeia de comprimento n .
Você pode manter o princípio básico do algoritmo usado. Você perdeu uma oportunidade de otimização de memória.
Então, o que essa pilha contém? Ele nunca vai conter
()
(um parêntese de abertura seguido de um parêntese de fechamento), pois sempre que a)
aparência você aparece, em(
vez de pressionar a tecla)
. Portanto, a pilha sempre tem a forma)…)(…(
- um monte de parênteses de fechamento seguido por um monte de parênteses de abertura.Você não precisa de uma pilha para representar isso. Lembre-se do número de parênteses de fechamento e do número de parênteses de abertura.
Se você processar a sequência da esquerda para a direita, usando esses dois contadores, o que você tem no final é o número de parênteses de fechamento incompatíveis e o número de parênteses de abertura incompatíveis.
Se você deseja relatar as posições dos parênteses incompatíveis no final, precisará lembrar a posição de cada parêntese. Isso exigiriaΘ ( n ) memória no pior caso. Mas você não precisa esperar até o final para produzir a saída. Assim que você encontrar um parêntese de fechamento incompatível, você sabe que ele é incompatível, portanto, imprima-o agora. E você não usará o número de parênteses de fechamento incompatíveis para nada, portanto, mantenha um contador de parênteses de abertura incomparáveis.
Em resumo: processe a sequência da esquerda para a direita. Mantenha um contador de parênteses de abertura incomparáveis. Se você vir um parêntese de abertura, aumente o contador. Se você vir um parêntese de fechamento e o contador for diferente de zero, diminua o contador. Se você vir um parêntese de fechamento e o contador for zero, insira o índice atual como um parêntese de fechamento incompatível.
O valor final do contador é o número de parênteses de abertura incompatíveis, mas isso não indica a posição deles. Observe que o problema é simétrico. Para listar as posições dos parênteses de abertura incompatíveis, basta executar o algoritmo na direção oposta.
Exercício 1: escreva isso em uma notação formal (matemática, pseudocódigo ou sua linguagem de programação favorita).
Exercício 2: convença-se de que esse é o mesmo algoritmo do Apass.Jack , apenas explicado de maneira diferente.
fonte
(()
.Como podemos simplesmente ignorar todos os caracteres alfanuméricos, assumiremos que a sequência contém apenas parênteses a partir de agora. Como na pergunta, existe apenas um tipo de parêntese, "()".
Se continuarmos removendo parênteses balanceados até que nenhum parêntese mais balanceado possa ser removido, todos os parênteses restantes deverão se parecer com ")) ...) ((… (", que são todos parênteses desequilibrados. Esta observação sugere que devemos encontrar primeiro esse ponto de virada) , antes do qual temos apenas parênteses de fechamento desequilibrados e depois dos quais temos apenas parênteses de abertura desequilibrados.
Aqui está o algoritmo. Em poucas palavras, ele calcula o ponto de viragem primeiro. Em seguida, ele gera parênteses de fechamento extra, varrendo a corda do início para a direita até o ponto de virada. Simetricamente, ele gera parênteses de abertura extra, digitalizando do final para a esquerda até o ponto de virada.
str
Inicialize
turning_point=0, maximum_count=0, count=0
. Para cadai
a partir0
den-1
fazer o seguinte.str[i] = ')'
, adicione 1 acount
; caso contrário, subtraia 1.count > maximum_count
, definaturning_point=i
emaximum_count=count
.Agora
turning_point
é o índice do ponto de virada.Reset
maximum_count=0, count=0
. Para cadai
a partir0
deturning_point
fazer o seguinte.str[i] = ')'
, adicione 1 acount
; caso contrário, subtraia 1.count > maximum_count
definirmaximum_count = count
. Saídai
como o índice de um parêntese de fechamento desequilibrado.Reset
maximum_count=0, count=0
. Para cada umi
den-1
paraturning_point+1
baixo, faça o seguinte.str[j] = '('
, adicione 1 acount
; caso contrário, subtraia 1.count > maximum_count
definirmaximum_count = count
. Saídai
como o índice de um parêntese de abertura desequilibrada.Se analisarmos o algoritmo acima, veremos que, de fato, não precisamos encontrar nem usar o ponto de virada. A boa observação de que todos os parênteses de fechamento desequilibrados ocorre antes que todos os parênteses de abertura desequilibrados possam ser ignorados, embora interessantes.
Aqui está o código em Python .
Basta clicar em "executar" para ver vários resultados do teste.
Exercício 1. Mostre que o algoritmo acima produzirá um conjunto de parênteses com a menor cardinalidade, de forma que os parênteses restantes sejam balanceados.
Problema 1. Podemos generalizar o algoritmo para o caso em que a cadeia contém dois tipos de parênteses, como "() []"? Temos que determinar como reconhecer e tratar a nova situação, o caso intercalado, "([)]".
fonte