Все началось с того, что на сайте codeforces.ru в очередном Codeforces Round я увидел интересную задачку “Скобочная последовательность” и решать ее “неинтересным способом” никак не хотелось.
Вкратце условия задачи сводятся к нахождению в строке, состоящей только из символов «(», «)», «[» и «]», правильной cкобочной последовательности, содержащей как можно больше скобок «[».
Для начала превращаем нашу плоскую строчку в более удобное иерархическое представление:
with brackets as
(select '[[)]([][][][[()[()[()()]]]])]' b
from dual)
select substr(b,level,1) q,
level curr_pos,
level - 1 prev_pos
from brackets
connect by substr(b,level,1) is not null
В каждой строчке будет очередная скобка, позиция текущей скобки и позиция предыдущей скобки. С этим уже можно что-то сделать.
Осталось найти все правильные скобочные последовательности. Здесь начинается самое интересное.
Так как для определения правильности выражения у нас нет стека, в котором можно хранить скобочки, пришлось идти на “хитрость”.
Найдем все возможные последовательности, начинающиеся с «(» или «[» (остальные неправильные по умолчанию). Для каждой такой последовательности определим позицию первой и последней скобки и “уровень вложенности” последней скобки, т.е.:
«(» «1ый - уровень» «(» «2ой – уровень» .. ... .. .. «]» «2ой – уровень» .. ... .. .. .
- Уровень увеличивается, если: скобка открывается, а перед ней ничего не было, либо была любая открывающаяся;
- Уровень не изменяется, если скобка открывается, а перед ней была закрывающаяся, либо если скобка закрывается, а перед ней была открывающаяся;
- Уровень уменьшается, если скобка закрывается, а перед ней была закрывающаяся;
with brackets as
(select '[[)]([][][][[()[()[()()]]]])]' b
from dual) ,
all_brackets as
(select substr(b,level,1) q,
level curr_pos,
level-1 prev_pos
from brackets
connect by substr(b,level,1) is not null)
select replace(sys_connect_by_path(q,'-'), '-') str,
q,
connect_by_root(curr_pos) start_pos,
curr_pos end_pos,
sum( case
when (q = '(' or q = '[')
and (prior q is null)
then 1
when (q = '(' or q = '[')
and (prior q = '(' or prior q = '[')
then 1
when (q = '(' or q = '[')
and (prior q = ']' or prior q = ')')
then 0
when (q = ')' or q = ']')
and (prior q = '(' or prior q = '[')
then 0
when (q = ')' or q = ']')
and (prior q = ')' or prior q = ']')
then -1
end)
over (partition by connect_by_root(curr_pos)
order by connect_by_root(curr_pos), curr_pos) bracket_level
from all_brackets
connect by prior curr_pos = prev_pos
start with q = '(' or q = '['
С помощью этих данных мы можем определить, скобку “вредителя”, которая делает из правильной последовательности неправильную. Для этого просмотрим все полученные последовательности, и при “появлении” очередной скобки, найдем предыдущую скобку, находящуюся на одном уровне с текущей (Спасибо Oracle за аналитическую функцию lag). Неправильная последовательность получается, если на одном из уровней возникает одна из следующих ситуаций:
- закрывается закрытая скобка
- круглая скобка закрывает квадратную
- квадратная скобка закрывает круглую
Любая открывающаяся скобка на данной итерации не может “поломать” последовательность.
with brackets as
(select '[[)]([][][][[()[()[()()]]]])]' b
from dual) ,
all_brackets as
(select substr(b,level,1) q,
level curr_pos,
level - 1 prev_pos
from brackets
connect by substr(b,level,1) is not null),
brackets_comb as
(select replace(sys_connect_by_path(q,'-'), '-') str,
q,
connect_by_root(curr_pos) start_pos,
curr_pos end_pos,
sum( case
when (q = '(' or q = '[')
and (prior q is null)
then 1
when (q = '(' or q = '[')
and (prior q = '(' or prior q = '[')
then 1
when (q = '(' or q = '[')
and (prior q = ']' or prior q = ')')
then 0
when (q = ')' or q = ']')
and (prior q = '(' or prior q = '[')
then 0
when (q = ')' or q = ']')
and (prior q = ')' or prior q = ']')
then -1
end)
over (partition by connect_by_root(curr_pos)
order by connect_by_root(curr_pos), curr_pos) bracket_level
from all_brackets
connect by prior curr_pos = prev_pos
start with q = '(' or q = '[')
select start_pos,
end_pos,
str,
case
when q = ')'
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = '('
then 'ok'
when q = '('
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = ')'
then 'ok'
when q = '('
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = ']'
then 'ok'
when q = ']'
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = '['
then 'ok'
when q = '['
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = ']'
then 'ok'
when q = '['
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = ')'
then 'ok'
when lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) is null
and bracket_level > 0
then 'ok'
else 'not ok!'
end status
from brackets_comb bc
Таким образом, для каждого множества последовательностей, начинающихся с одной позиции (start_pos) и упорядоченных по количеству скобок, мы нашли первую неверную последовательность.
В каждом таком множестве последовательностей “выкинем” последовательности после “неправильной”, и для всех оставшихся проверим, что открывшиеся скобки закрылись (для этого достаточно посчитать количество открывшихся и закрывшихся скобок каждого вида).
В результате останется найти ту единственную, а может и не единственную, её (нет, не девушку своей мечты, как вы могли подумать), скобочную последовательность с максимальным количеством «[».
Весь запрос:
with brackets as
(select '[[]])[([[]][[(]])]' b
from dual) ,
all_brackets as
(select substr(b,level,1) q,
level curr_pos,
level - 1 prev_pos
from brackets
connect by substr(b,level,1) is not null),
brackets_comb as
(select replace(sys_connect_by_path(q,'-'), '-') str,
q,
connect_by_root(curr_pos) start_pos,
curr_pos end_pos,
sum( case
when (q = '(' or q = '[')
and (prior q is null)
then 1
when (q = '(' or q = '[')
and (prior q = '(' or prior q = '[')
then 1
when (q = '(' or q = '[')
and (prior q = ']' or prior q = ')')
then 0
when (q = ')' or q = ']')
and (prior q = '(' or prior q = '[')
then 0
when (q = ')' or q = ']')
and (prior q = ')' or prior q = ']')
then -1
end)
over (partition by connect_by_root(curr_pos)
order by connect_by_root(curr_pos), curr_pos) bracket_level
from all_brackets
connect by prior curr_pos = prev_pos
start with q = '(' or q = '['),
brackets_comb_status as
(select start_pos,
end_pos,
str,
case
when q = ')'
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = '('
then 'ok'
when q = '('
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = ')'
then 'ok'
when q = '('
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos ) = ']'
then 'ok'
when q = ']'
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = '['
then 'ok'
when q = '['
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = ']'
then 'ok'
when q = '['
and lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) = ')'
then 'ok'
when lag(q) over (partition by start_pos, bracket_level
order by start_pos, bracket_level, end_pos) is null
and bracket_level > 0
then 'ok'
else 'not ok!'
end status
from brackets_comb bc)
select str "последовательность",
cnt "колличество [",
start_pos "позиция с",
end_pos "позиция по"
from (
select str,
start_pos,
end_pos,
length(regexp_replace(str,'[)(]',''))/2 cnt,
max(length(regexp_replace(str,'[)(]',''))/2) over (order by null) best_cnt
from (
select str,
start_pos,
end_pos,
nvl(
lag(
case
when status = 'ok' then null
else status
end
ignore nulls)
over (partition by start_pos order by start_pos, end_pos),
status
) status
from brackets_comb_status
)
where status = 'ok'
and length(replace(str,'[','')) = length(replace(str,']',''))
and length(replace(str,'(','')) = length(replace(str,')',''))
) result
where best_cnt = cnt
Автор: beIive