Largest Rectangle in Histogram
Um histograma é uma fileira de barras lado a lado, sem espaços entre elas, cada uma com uma unidade de largura: heights[i] é a altura da barra i. Um retângulo dentro dele cobre uma sequência de barras vizinhas e não pode ser mais alto que a barra mais baixa dessa sequência.
Retorne a maior área que esse retângulo pode ter.
Função
- heightsinteger-array
- a altura de cada barra, da esquerda para a direita
- Retornainteger
- a área do maior retângulo que cabe no histograma
Restrições
1 ≤ heights.length ≤ 2 × 1040 ≤ heights[i] ≤ 105- Cada barra tem uma unidade de largura, então um retângulo sobre as barras
iajtemj-i+1unidades de largura.
Exemplos
- Entrada
- heights = [2, 5, 6, 3, 4, 1]
- Saída
- 12
- Explicação
- As quatro barras 5, 6, 3 e 4 têm todas pelo menos 3 de altura, então um retângulo de altura 3 abrange todas elas: 3 × 4 = 12. As duas barras mais altas, 5 e 6, resultam em apenas 5 × 2 = 10.
- Entrada
- heights = [1, 8, 1, 1]
- Saída
- 8
- Explicação
- A barra de 8 sozinha forma 8 × 1 = 8. Qualquer retângulo mais largo inclui uma barra de 1, então mede no máximo 1 × 4 = 4.
- Entrada
- heights = [3, 3, 3, 3]
- Saída
- 12
- Explicação
- Todas as quatro barras têm altura 3, então o histograma inteiro é um retângulo: 3 × 4 = 12.
+17 testes ocultos ao enviar
Para ir além
Suponha que cada barra tenha sua própria largura, fornecida em um segundo array. O que muda na solução de pilha em uma única passagem?
Dicas
Abra uma de cada vez. Cada uma revela um pouco mais.
O maior retângulo toca o topo de pelo menos uma barra abaixo dele: se não tocasse, você poderia deixá-lo mais alto. Então, experimente cada barra como a barra que determina a altura. Qual pode ser a largura de um retângulo exatamente dessa altura?
Um retângulo da altura da barra
ise estende para a esquerda e para a direita até encontrar uma barra estritamente mais baixa de cada lado. Se você souber qual é a barra mais próxima e mais baixa de cada lado de todas as barras, cada barra fornece uma área candidata, e há apenasndelas.Mantenha uma pilha de índices cujas alturas aumentam da base para o topo. Quando chega uma barra que não é mais alta que a do topo, a barra do topo não pode alcançar mais à direita: retire-a da pilha, e seu retângulo cobre as barras estritamente entre o novo topo da pilha e a barra atual. Uma barra de altura 0 após o fim retira da pilha o que restar.
Solução
Um retângulo pode começar e terminar em qualquer barra, e sua altura depende da barra mais baixa que ele cobre, então tentar cada sequência de barras custa cerca de n²/2 etapas. A solução é inverter a pergunta: o melhor retângulo tem exatamente a altura de uma de suas barras, então cada barra só precisa saber até onde pode se estender antes que uma barra mais baixa a detenha. Uma pilha monótona encontra esses pontos de parada para cada barra, primeiro em duas passagens e depois em uma.
Tente cada execução mantendo um mínimo
Correta, mas não termina nos maiores testes
Intuição
Um retângulo cobre uma sequência de barras vizinhas de start a end, e sua altura é limitada pela barra mais baixa da sequência. Então, experimente todas as sequências. Fixe start, depois aumente end uma barra por vez e mantenha a menor altura encontrada até então. A melhor área de retângulo nessa sequência é lowest × (end-start+1).
Em [2, 5, 6, 3, 4, 1], comece no 5. As sequências dão 5 × 1 = 5, depois 5 × 2 = 10 com o 6, depois 3 × 3 = 9 quando o 3 se junta, 3 × 4 = 12 com o 4 e 1 × 5 = 5 com o 1. A resposta é 12. Atualizar lowest à medida que a sequência cresce mantém cada etapa em O(1), então você nunca precisa percorrer novamente uma sequência para encontrar seu mínimo.
Isso está correto porque todo retângulo fica sobre alguma sequência e, para uma sequência fixa, o retângulo mais alto que cabe tem exatamente a altura da barra mais baixa. É lento porque há n(n+1)/2 sequências: cerca de 2 × 10^8 para 2 × 10^4 barras, e essa quantidade não depende das alturas. A maioria dessas sequências é interrompida por uma barra baixa muito antes de chegar ao fim, mas a força bruta continua estendendo-as mesmo assim.
Algoritmo
- Defina
bestcomo 0. - Para cada
start, definalowestcomoheights[start]. - Para cada
enddestartaté a última barra, reduzalowestparaheights[end]se essa barra for mais baixa. - Atualize
bestcomlowest × (end-start+1). - Retorne
best.
def largestRectangleArea(heights):
n = len(heights)
best = 0
for start in range(n):
lowest = heights[start]
for end in range(start, n):
# The rectangle over start..end is as tall as the lowest bar in it.
lowest = min(lowest, heights[end])
best = max(best, lowest * (end - start + 1))
return bestBarra mais próxima e mais curta em cada lado
Intuição
Inverta a busca. No melhor retângulo, pelo menos uma barra sob ele tem exatamente a mesma altura do retângulo; caso contrário, você poderia aumentá-lo. Portanto, a resposta é o melhor, entre todas as barras i, de um retângulo com exatamente heights[i] de altura que se estende o máximo possível. Ele se estende até encontrar uma barra estritamente mais baixa de cada lado. Chame seus índices de left[i] e right[i], usando -1 e n quando não houver nenhuma. O retângulo cobre as barras estritamente entre elas: largura right[i]-left[i]-1. São n candidatos, em vez de n²/2.
Para encontrar left[i] para cada barra, percorra da esquerda para a direita com uma pilha de índices cujas alturas aumentam estritamente da base ao topo. Quando a barra i chega, remova todos os índices cujas barras são pelo menos tão altas quanto heights[i]. Essas barras nunca poderão ser a barra mais próxima e mais baixa para i nem para qualquer barra depois dela, porque i está mais perto e não é mais alta. O que restar no topo será a barra mais próxima e mais baixa à esquerda. Em seguida, empilhe i. A mesma passagem da direita para a esquerda fornece right[i].
Para [2, 5, 6, 3, 4, 1], as passagens fornecem left = [-1, 0, 1, 0, 3, -1] e right = [5, 3, 3, 5, 5, 6]. A barra de altura 3 no índice 3 é limitada pelo 2 no índice 0 e pelo 1 no índice 5, então seu retângulo mede 3 × (5-0-1) = 12. O 6 fica limitado pelos vizinhos e só resulta em 6 × 1.
Cada índice é empilhado uma vez e removido no máximo uma vez em cada passagem, portanto ambas as passagens são O(n), mesmo que uma barra possa remover muitas. O custo são dois arrays extras.
Algoritmo
- Percorra da esquerda para a direita com uma pilha vazia. Para cada
i, desempilhe enquanto a barra no topo for pelo menos tão alta quantoheights[i]; definaleft[i]como o topo ou como -1 se a pilha estiver vazia; empilhei. - Percorra da direita para a esquerda da mesma maneira para preencher
right[i], usandonquando a pilha estiver vazia. - Para cada
i, calculeheights[i] × (right[i]-left[i]-1). - Retorne a maior dessas áreas.
def largestRectangleArea(heights):
n = len(heights)
left = [-1] * n # index of the nearest shorter bar on the left, or -1
right = [n] * n # index of the nearest shorter bar on the right, or n
stack = [] # indices whose heights rise strictly from bottom to top
for i in range(n):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
if stack:
left[i] = stack[-1]
stack.append(i)
stack = []
for i in range(n - 1, -1, -1):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
if stack:
right[i] = stack[-1]
stack.append(i)
best = 0
for i in range(n):
# Bar i is the lowest bar of everything strictly between left[i] and right[i].
best = max(best, heights[i] * (right[i] - left[i] - 1))
return bestUma passagem com uma pilha monotônica
Intuição
A passagem da esquerda para a direita já encontra cada limite à direita; ela o descarta. Quando a barra i remove a barra t da pilha, heights[i] não é maior que heights[t], então i é onde o retângulo de t termina à direita. E o índice que fica sob t na pilha é onde ele termina à esquerda. Portanto, meça o retângulo no momento em que remover a barra: heights[t] × (i - below - 1), em que below é o novo topo da pilha, ou -1 se a pilha agora estiver vazia.
O invariante: as alturas na pilha aumentam estritamente da base ao topo, e o índice abaixo de cada entrada é a barra mais próxima à sua esquerda que é mais baixa do que ela. Todas as barras entre as duas foram removidas no caminho, seja pela própria entrada, seja por uma barra que a entrada removeu depois; portanto, nenhuma delas é mais baixa do que a entrada. As barras que nunca são removidas chegam até o fim, então, depois de processar a última barra, processe mais uma barra de altura 0. Ela é mais baixa do que todas e esvazia a pilha.
Percorra [2, 5, 6, 3, 4, 1]. Empilhe 2, 5 e 6: a pilha contém os índices [0, 1, 2]. O 3 no índice 3 remove o 6 (área 6 × (3-1-1) = 6) e o 5 (área 5 × (3-0-1) = 10), depois para no 2 e é empilhado. Empilhe o 4. O 1 no índice 5 remove o 4 (área 4), depois o 3, cujo retângulo vai do índice 1 ao 4: 3 × (5-0-1) = 12. Ele também remove o 2 (2 × 5 = 10; a pilha está vazia, então a largura é 5). O 0 de fechamento remove o 1 (1 × 6 = 6). O maior valor é 12.
Remover com >= significa que uma barra de mesma altura pode fazer uma barra parar antes. Isso é seguro: a barra de mesma altura ocupa o lugar dela na pilha, herda o mesmo limite à esquerda e, quando for removida depois, seu retângulo cobrirá toda a sequência. Em [3, 3, 3, 3], os três primeiros 3 registram larguras 1, 2 e 3, e o último é removido pelo 0 de fechamento com largura 4, resultando em 12.
Algoritmo
- Comece com uma pilha vazia de índices e
best = 0. - Para
ide 0 an, considere a altura atual comoheights[i]ou 0 quandoi = n. - Enquanto a barra no topo da pilha for pelo menos tão alta quanto a altura atual, remova-a como
t; a largura éi - below - 1, sendobelowo novo topo ou -1; atualizebestcomheights[t] × width. - Empilhe
i. - Retorne
best.
def largestRectangleArea(heights):
n = len(heights)
stack = [] # indices whose heights rise strictly from bottom to top
best = 0
for i in range(n + 1):
current = heights[i] if i < n else 0 # a bar of 0 past the end empties the stack
while stack and heights[stack[-1]] >= current:
# Bar i stops the popped bar on the right; the bar below it stops it on the left.
height = heights[stack.pop()]
left = stack[-1] if stack else -1
best = max(best, height * (i - left - 1))
stack.append(i)
return best
Armadilhas e casos extremos
O loop da pilha é curto, e quase todos os bugs estão na largura ou nas barras que sobraram no final.
- Esquecer as barras que ainda estão na pilha. Em um histograma crescente como
[1, 2, 3, 4, 5], nada é removido dentro do loop e, sem a barra final de altura 0, você retorna 0 em vez de 9. - Medir a largura a partir do próprio índice da barra removida. O retângulo dela começa logo depois da barra abaixo dela na pilha, não nela mesma: em
[2, 5, 6, 3, 4, 1], o 3 no índice 3 se estende pelos índices de 1 a 4. Usari - tdá 2 em vez de 4. - Usar a largura errada quando a pilha fica vazia após uma remoção. A barra removida é a mais baixa até então, então seu retângulo se estende até o índice 0 e a largura é
i. Em[2, 1, 2], o 1 se estende pelas três barras, formando uma área de 3. - Parar ao encontrar barras iguais dos dois lados na versão de duas passagens. Assim, em
[3, 3, 3, 3], cada barra vê uma largura de 1 e você retorna 3 em vez de 12. Remova usando>=para que os limites sejam barras estritamente mais baixas. - Supor que a barra mais alta ou o trecho mais largo será o maior. Em
[2, 5, 6, 3, 4, 1], nem o 6 nem a largura total de 6 barras dão a resposta; uma altura intermediária sobre uma largura intermediária dá. - Estouro. A área chega a
10^5 × 2 × 10^4 = 2 × 10^9aqui, o que ainda cabe em um inteiro com sinal de 32 bits; com limites maiores, faça a multiplicação em 64 bits.
Perguntas frequentes4
Qual é a complexidade de tempo de Largest Rectangle in Histogram?
A solução com pilha monotônica é executada em O(n) de tempo e usa O(n) de espaço extra. Cada índice é inserido na pilha uma vez e removido uma vez, e cada remoção exige uma quantidade constante de trabalho. Testar cada sequência de barras leva O(n²) de tempo, cerca de 2 × 10^8 passos para 2 × 10^4 barras.
Por que o retângulo de uma barra é medido quando ela é removida?
Uma barra é removida pela primeira barra à sua direita que não seja mais alta; é aí que seu retângulo termina à direita. O índice abaixo dela na pilha corresponde à barra mais próxima à sua esquerda que seja mais baixa; é aí que termina à esquerda. No momento da remoção, os dois extremos são conhecidos, e a área é height × (i - below - 1).
O problema do Maior Retângulo em um Histograma pode ser resolvido com divisão e conquista?
Sim. A barra mais baixa de todo o intervalo ou fica sob o melhor retângulo, que então tem área lowest × width, ou divide o intervalo em uma parte à esquerda e outra à direita, que você resolve separadamente. Com uma varredura linear para encontrar o mínimo, isso leva O(n log n) em uma entrada aleatória, mas O(n²) em uma entrada ordenada; uma árvore de segmentos para mínimos em intervalos faz com que sempre leve O(n log n). A pilha é mais simples e rápida.
Como o maior retângulo em um histograma é usado para encontrar o retângulo máximo em uma grade 0/1?
Percorra a grade linha por linha e mantenha, para cada coluna, quantos 1s consecutivos terminam na linha atual; um 0 zera essa contagem. As contagens de cada linha formam um histograma, e o maior retângulo de 1s que termina nessa linha é o maior retângulo desse histograma. Executar a pilha uma vez por linha resolve a grade em O(rows × cols) de tempo.
Problemas parecidos
Problemas que usam as mesmas ideias. Resolver dois ou três é o que fixa um padrão.
Python
def largestRectangleArea(heights):
# Escreva o código aquiCaso 1
Caso 2
Caso 3
Entrada
heights = [2, 5, 6, 3, 4, 1]
Esperado
12