Я предлагал решение в виде построения дерева где листья являются подстроками корневого узла, потом через dynamic programming для каждого узла найти все возможные комбинации листьев, которые при конкатенации совпадут со значением узла. Не уложился во время, интервьюер в итоге сказал, что так решить можно, но есть решение проще через dynamic programming без построения дерева.
2. Дан список последовательностей чисел
Найти самую часто встречающуюся непрерывную подпоследовательность и сколько раз она встречается.
Облегчённый вариант, с которым я также не справился: длина подпоследовательности задана, рассматривать только подпоследовательности этой длины.
Очевидное решение с Dictionary, где ключом является сама подпоследовательность не подходит по требуемой памяти, даже в "облегчённом" варианте. Предполагается, что список читается из файла и в память не влезет.
Здравствуйте, Chriso, Вы писали:
C>1. Дан список строк S[1..N], нужно вернуть список R всех возможных списков таких, что: C>R[i][j] ∈ S C>string.Join("", R[i]) ∈ S C>R[i].Count >= 2
C>Пример: C>вход [a, b, ab, cd, abcd, fgh, f, g, h] C>выход [[a, b], [a, b, cd], [ab, cd], [f, g, h]]
Извиняюсь за эзотерическую julia, но я сейчас на ней пытаюсь мыслить:
julia> s = ["a", "b", "ab", "cd", "abcd", "fgh", "f", "g", "h"]
9-element Array{String,1}:
"a"
"b"
"ab"
"cd"
⋮
"f"
"g"
"h"
julia> pairs = map(outer->outer=>filter(inner->inner != outer && occursin(inner, outer), s), s) |> x->filter(e->!isempty(last(e)),x)
3-element Array{Pair{String,Array{String,1}},1}:
"ab" => ["a", "b"]
"abcd" => ["a", "b", "ab", "cd"]
"fgh" => ["f", "g", "h"]
julia> function sbreak(outer::String, inner::Vector{String})::Vector{Vector{String}}
function sbreak2(outer::String, inner::Vector{String})::Vector{Vector{String}}
#println(outer, inner)if isempty(outer)
[[""]]
else
vcat([
let new_outer = outer[length(e)+1:end]; new_inner = filter(e2->e != e2, inner); new_sbreak = sbreak2(new_outer, new_inner)
map(x->[e;x], new_sbreak)
end
for e in inner if startswith(outer, e)
]...)
end
end
[e[1:end-1] for e in sbreak2(outer, inner)]
end
julia> sbreak("abcd",["a", "b", "ab", "cd"])
2-element Array{Array{String,1},1}:
["a", "b", "cd"]
["ab", "cd"]
julia> vcat(map(p->sbreak(first(p), last(p)), pairs)...)
4-element Array{Array{String,1},1}:
["a", "b"]
["a", "b", "cd"]
["ab", "cd"]
["f", "g", "h"]
C>2. Дан список последовательностей чисел C>Найти самую часто встречающуюся непрерывную подпоследовательность и сколько раз она встречается. C>Облегчённый вариант, с которым я также не справился: длина подпоследовательности задана, рассматривать только подпоследовательности этой длины. C>Очевидное решение с Dictionary, где ключом является сама подпоследовательность не подходит по требуемой памяти, даже в "облегчённом" варианте. Предполагается, что список читается из файла и в память не влезет.
C>Пример: C>вход [[1, 2, 3, 4], [4, 2, 3, 1]] C>выход [2, 3], 2
C>вход [[1, 2, 3, 4], [4, 2, 3, 2, 3]] C>выход [2, 3], 3
Если длина задача, то для каждой последовательности считаете вхождения пос-ти данной длины. Пусть длина 2, сначала 12, пргоняете по всем последовательном и считаете, потом 23, потом 34,42,23(возможно уже быда), 31 . По памяти О(1).
Вообще на ютубе полно видео разбора задач с интервью Амазонка, гугла и т.д.
Здравствуйте, Chriso, Вы писали:
C>Сегодня завалил (скорее всего) первое своё интервью в AWS. C>Было четыре раунда по часу, первые два достаточно простые, потом началась жесть.
C>Кому интересно, предлагаю подумать над следующими задачками. Я сам пока решений не знаю (решения простым перебором, понятно, не рассматриваются):
Ну так к интервью на кодинг надо готовиться. leetcode, hackerrank всякие... На ютубе тоже полно, целые каналы
Вторая задача вторая возможно на структуры данных (trie вместо dictionary с числом повторений в узлах не подойдет?)
Здравствуйте, Chriso, Вы писали:
C>Пример: C>вход [a, b, ab, cd, abcd, fgh, f, g, h] C>выход [[a, b], [a, b, cd], [ab, cd], [f, g, h]]
C>Я предлагал решение в виде построения дерева где листья являются подстроками корневого узла, потом через dynamic programming для каждого узла найти все возможные комбинации листьев, которые при конкатенации совпадут со значением узла. Не уложился во время, интервьюер в итоге сказал, что так решить можно, но есть решение проще через dynamic programming без построения дерева.
Для решения нам нужно найти такие строки, которые можно составить из других подстрок (из двух и более).
При этом, для составлении можем использовать подстроки несколько раз (это упрощает задачу).
Решение с помощью динамического программирования — запоминаются все разбиения хвостов слов на подстроки.
from functools import lru_cache
from typing import List
def find_solve(strings: List[str]) -> List[List[str]]:
@lru_cache
def split_word(word: str) -> List[List[str]]:
"""Находит все варианты разбиения слова word на подстроки из strings"""
result = []
for subword in strings:
if not word.startswith(subword):
continue
if word == subword:
result.append([subword])
continue
new_word = word[len(subword):]
for rest_combination in split_word(word=new_word):
result.append([subword, *rest_combination])
return result
result = []
for word in strings:
for combination in split_word(word):
if len(combination) >= 2:
result.append(combination)
return result
Сложность тоже интересно посчитать (не получилось сразу, не буду выкладывать)
Здравствуйте, Chriso, Вы писали:
C>2. Дан список последовательностей чисел C>Найти самую часто встречающуюся непрерывную подпоследовательность и сколько раз она встречается. C>Облегчённый вариант, с которым я также не справился: длина подпоследовательности задана, рассматривать только подпоследовательности этой длины. C>Очевидное решение с Dictionary, где ключом является сама подпоследовательность не подходит по требуемой памяти, даже в "облегчённом" варианте. Предполагается, что список читается из файла и в память не влезет.
C>Пример: C>вход [[1, 2, 3, 4], [4, 2, 3, 1]] C>выход [2, 3], 2
C>вход [[1, 2, 3, 4], [4, 2, 3, 2, 3]] C>выход [2, 3], 3
Думаю, так:
Надо объединить все входящие последовательности в одну большую, заменяя промежутки между последовательностями специальным символом ($).
Теперь у нас стоит задача поиска наиболее часто повторяющейся подстроки в строке.
Для этого нужно построить суффиксное дерево (что само по себе не просто).
При построении дерева нужно для каждого узла запоминать количество его вхождений в строку.
Потом найти в дереве наиболее глубокий узел по количеству символов, считая от корня. Это и будет ответ.
P.S. Да уж, задачи — жесть. В условиях малого времени сделать сложно
Здравствуйте, Буравчик, Вы писали:
Б>Здравствуйте, Chriso, Вы писали:
Б>Думаю, так:
Б>Надо объединить все входящие последовательности в одну большую, заменяя промежутки между последовательностями специальным символом ($). Б>Теперь у нас стоит задача поиска наиболее часто повторяющейся подстроки в строке.
Б>Для этого нужно построить суффиксное дерево (что само по себе не просто).
Так вроде ограничения по памяти O(1). Или дерево влезть? Мне казалось, что при заданной длине подпоследовательности простейшим перебором можно все решить.
Здравствуйте, Sharov, Вы писали:
S>Так вроде ограничения по памяти O(1). Или дерево влезть? Мне казалось, что при заданной длине подпоследовательности простейшим перебором можно все решить.
Дерево требует O(N) памяти, где N — количество символов.
За O(1) по памяти — сомневаюсь, что можно так решить.
Здравствуйте, Буравчик, Вы писали:
Б>Здравствуйте, Sharov, Вы писали:
S>>Так вроде ограничения по памяти O(1). Или дерево влезть? Мне казалось, что при заданной длине подпоследовательности простейшим перебором можно все решить.
Б>Дерево требует O(N) памяти, где N — количество символов.
Б>За O(1) по памяти — сомневаюсь, что можно так решить.
Постановки была с идеей, что списки читаются из файла (типа парсятся логи).
O(N) по памяти точно не подходит, но вероятно предполагалось, что можно сделать несколько проходов.
Здравствуйте, Sharov, Вы писали:
S>Если длина задача, то для каждой последовательности считаете вхождения пос-ти данной длины. Пусть длина 2, сначала 12, пргоняете по всем последовательном и считаете, потом 23, потом 34,42,23(возможно уже быда), 31 . По памяти О(1).
Как-то уж слишком брутфорсно получается, но ничего лучше я не могу тоже придумать.
Возможно, если задать отношение порядка для подпоследовательностей и искать вхождения подпоследовательностей от меньшей к большей, то можно избежать повторений.
Тогда будет выглядеть так:
За первый проход ищем минимальную подпоследовательность.
На следующих проходах считаем две величины: количество вхождений заданной подпоследовательности и следующую по порядку подпоследовательнось.
Но в пределе все равно квадрат, а для среднего что-то не могу вероятности сходу посчитать.
Здравствуйте, Sharov, Вы писали:
S>Если длина задача, то для каждой последовательности считаете вхождения пос-ти данной длины. Пусть длина 2, сначала 12, пргоняете по всем последовательном и считаете, потом 23, потом 34,42,23(возможно уже быда), 31 . По памяти О(1).
По памяти О(1), но по времени как минимум O(N^3) или даже O(N^4).
Кубическая сложность это очень долго даже на маленьких файлах, которые точно влезли бы в память (например, 1 млн байт). Не говоря уже про чтение с диска.
Здравствуйте, Буравчик, Вы писали:
Б>По памяти О(1), но по времени как минимум O(N^3) или даже O(N^4). Б>Кубическая сложность это очень долго даже на маленьких файлах, которые точно влезли бы в память (например, 1 млн байт). Не говоря уже про чтение с диска.
Откуда у тебя там куб и тем более четвертая степень наросла?
Здравствуйте, MaximVK, Вы писали:
MVK>Откуда у тебя там куб и тем более четвертая степень наросла?
Всего элементов — N. Длина рассматриваемой последовательности K (в нашем примере 2).
1. Выбор оригинальной последовательности — (N-K) вариантов
2. Поиск дубликата для это оригинальной последовательности — (N-K) вариантов
3. Сравнение двух последовательностей — K действий
СУММА (по K от 1 до N) K*(N-K)*(N-K)
Получится что-то от N^3 до N^4.
Б>Всего элементов — N. Длина рассматриваемой последовательности K (в нашем примере 2).
Б>1. Выбор оригинальной последовательности — (N-K) вариантов Б>2. Поиск дубликата для это оригинальной последовательности — (N-K) вариантов Б>3. Сравнение двух последовательностей — K действий
Б>СУММА (по K от 1 до N) K*(N-K)*(N-K) Б>Получится что-то от N^3 до N^4.
Хм, но если скобки раскроем, то получится N^2
Но вообще, в данном случае у тебя N — это количество символов в одной последовательности.
Количество символов в последовательности — это не то, что мы увеличиваем в этой задаче. Условно, я бы считал это за константу.
Так как в качестве входного параметра мы имеем количество последовательностей, то я бы отвечал на вопрос: "Как увеличится количество операций, при увеличении количества строк(последовательностей) в файле, при условии, что макс длинна последовательности остаётся неизменной".
В этом случае твои 1,2,3 можно ограничить константой сверху рассчитанной для максимальной последовательности.
Здравствуйте, Буравчик, Вы писали:
Б>Здравствуйте, MaximVK, Вы писали:
MVK>>Откуда у тебя там куб и тем более четвертая степень наросла?
Б>Всего элементов — N. Длина рассматриваемой последовательности K (в нашем примере 2).
Б>1. Выбор оригинальной последовательности — (N-K) вариантов Б>2. Поиск дубликата для это оригинальной последовательности — (N-K) вариантов Б>3. Сравнение двух последовательностей — K действий
Б>СУММА (по K от 1 до N) K*(N-K)*(N-K) Б>Получится что-то от N^3 до N^4.
Б>ДОБАВЛЕНО: Б>https://www.wolframalpha.com/input/?i=lim+%28SUM+k*%28n-k%29*%28n-k%29%2C+k%3D1+to+n%29+as+n+-%3E+infinity
Здравствуйте, MaximVK, Вы писали:
MVK>Хм, но если скобки раскроем, то получится N^2
Для K близких к N/2, получим N^3.
А когда просуммируем по всем К получим N^4
MVK>Но вообще, в данном случае у тебя N — это количество символов в одной последовательности.
N-количество символов во всем файле.
В файле может единственная очень длинная последовательность.
MVK>Количество символов в последовательности — это не то, что мы увеличиваем в этой задаче. Условно, я бы считал это за константу.
К — длина искомой последовательность. Я говорю про исходную задачу, когда K неизвестен.
Думаю, что собеседующий предложил рассмотреть константный К только как промежуточный этап решения полной задачи.
MVK>Так как в качестве входного параметра мы имеем количество последовательностей
Что такое "количество последовательностей"? Приведи свои расчеты
Здравствуйте, Буравчик, Вы писали:
Б>Думаю, так:
Б>Надо объединить все входящие последовательности в одну большую, заменяя промежутки между последовательностями специальным символом ($). Б>Теперь у нас стоит задача поиска наиболее часто повторяющейся подстроки в строке.
Б>Для этого нужно построить суффиксное дерево (что само по себе не просто). Б>При построении дерева нужно для каждого узла запоминать количество его вхождений в строку. Б>Потом найти в дереве наиболее глубокий узел по количеству символов, считая от корня. Это и будет ответ.
Б>P.S. Да уж, задачи — жесть. В условиях малого времени сделать сложно
Можно так сделать:
Начинаем с подпоследовательности длинной n=2:
1. Cтроим суффиксное дерево для подпоследовательность длинной n
2. Выбираем те подпоследовательности у которых макс. число вхождений
3. Ищем последовательности длинной n+1 которые содержат подпоследовательности выбранные на шаге 2, т.е. вида ?abc и abc?
4. Повторяем пока максимальное количество вхождений не начнет уменьшаться
В этом случае мы ограничим рост дерева, вот только сложность я не могу пока посчитать. Вечером подумаю.
Б>В файле может единственная очень длинная последовательность.
Это вырожденный случай. Тогда и про quicksort можно сказать, что сложность будет N^2.
Честно говоря, я думаю, что в оригинальной задаче было ограничение на длину одной строки.
Иначе задача в принципе не имеет решения, так как строка может просто не влезть в память.
Спросил у топикстартера.
Б>Думаю, что собеседующий предложил рассмотреть константный К только как промежуточный этап решения полной задачи.
Да, судя по всему так оно и есть. Но если мы решаем задачу для K, то потом можно решить для K+1,
сузив поиск для подпоследовательностей содержащих подпоследовательности найденные для K.
MVK>>Так как в качестве входного параметра мы имеем количество последовательностей Б>Что такое "количество последовательностей"? Приведи свои расчеты
"количество последовательностей" == количество строк
Я исхожу из того, что длинна строки (== последовательности) ограничена и влазит в нашу константную память.
В этом случае предложенное решение будет N^2, где N количество строк, так как обработка одной строки в этом случае становится константой.
Здравствуйте, Chriso, Вы писали:
C>2. Дан список последовательностей чисел C>Найти самую часто встречающуюся непрерывную подпоследовательность и сколько раз она встречается. C>Облегчённый вариант, с которым я также не справился: длина подпоследовательности задана, рассматривать только подпоследовательности этой длины. C>Очевидное решение с Dictionary, где ключом является сама подпоследовательность не подходит по требуемой памяти, даже в "облегчённом" варианте. Предполагается, что список читается из файла и в память не влезет.
Уточняющий вопрос по этой задаче.
А длинна одной последовательности ограничена?
Здравствуйте, Chriso, Вы писали:
C>2. Дан список последовательностей чисел C>Найти самую часто встречающуюся непрерывную подпоследовательность и сколько раз она встречается. C>Облегчённый вариант, с которым я также не справился: длина подпоследовательности задана, рассматривать только подпоследовательности этой длины. C>Очевидное решение с Dictionary, где ключом является сама подпоследовательность не подходит по требуемой памяти, даже в "облегчённом" варианте. Предполагается, что список читается из файла и в память не влезет.
Весь список списков(т.е. целый файл в память не влезает), а один список из входного списка влезает в память?
Здравствуйте, MaximVK, Вы писали:
MVK>Да, судя по всему так оно и есть. Но если мы решаем задачу для K, то потом можно решить для K+1, MVK>сузив поиск для подпоследовательностей содержащих подпоследовательности найденные для K.
Как мы запомним подпоследовательности из предыдущего К? Ведь память должна быть O(1)
MVK>Я исхожу из того, что длинна строки (== последовательности) ограничена и влазит в нашу константную память. MVK>В этом случае предложенное решение будет N^2, где N количество строк, так как обработка одной строки в этом случае становится константой.
Что-то я запутался.
Пусть P — длина строки, N — количество строк. Превратить длину строки P в константу мы всегда сможем, а сейчас предлагаю ее учитывать.
Как работает алгоритм и какова его сложность?
Здравствуйте, Буравчик, Вы писали:
Б>Как мы запомним подпоследовательности из предыдущего К? Ведь память должна быть O(1)
В этом случае надо будет несколько проходов.
Ты можешь отсортировать подпоследовательности и запихать столько в память, сколько влезет. Это позволит уменьшить кол-во чтений файла.
MVK>>Я исхожу из того, что длинна строки (== последовательности) ограничена и влазит в нашу константную память. MVK>>В этом случае предложенное решение будет N^2, где N количество строк, так как обработка одной строки в этом случае становится константой.
Б>Что-то я запутался. Б>Пусть P — длина строки, N — количество строк. Превратить длину строки P в константу мы всегда сможем, а сейчас предлагаю ее учитывать. Б>Как работает алгоритм и какова его сложность?
Сходу только в брутфорсном варианте когда мы для для каждой подстроки считаем количество вхождений сканируя весь файл (считаем, что поиск подстроки стоит нам O(P)):
для i in 2..P (i = длина подпоследовательности)
N * (N-1) * (P-i) * P
получаем
O(N^2*P^3)
В умном варианте надо подумать, сходу не скажу. Постараюсь вечером прикинуть.
C>2. Дан список последовательностей чисел C>Найти самую часто встречающуюся непрерывную подпоследовательность и сколько раз она встречается. C>Облегчённый вариант, с которым я также не справился: длина подпоследовательности задана, рассматривать только подпоследовательности этой длины. C>Очевидное решение с Dictionary, где ключом является сама подпоследовательность не подходит по требуемой памяти, даже в "облегчённом" варианте. Предполагается, что список читается из файла и в память не влезет.
Что-то здесь не так. Даже упрощенная задача "найти самое часто встречающееся число в потоке" требует хранения всех чисел со счетчиками в памяти
Здравствуйте, scf, Вы писали:
scf>Что-то здесь не так.
определенно, не хватает ограничений. или не сказали или предполагалось, что топикстартер должен их был спросить.
scf>Даже упрощенная задача "найти самое часто встречающееся число в потоке" требует хранения всех чисел со счетчиками в памяти
ну файл можно несколько раз зачитать
Здравствуйте, MaximVK, Вы писали:
scf>>Даже упрощенная задача "найти самое часто встречающееся число в потоке" требует хранения всех чисел со счетчиками в памяти MVK>ну файл можно несколько раз зачитать
Мне что-то не приходит в голову, как чтение файла несколько раз может снизить потребность в памяти.
Здравствуйте, scf, Вы писали:
scf>Мне что-то не приходит в голову, как чтение файла несколько раз может снизить потребность в памяти.
Ну в твоем примере, для каждого числа перечитываешь файл и считаешь количество вхождений.
В этом случае нужно помнить только число и кол.-во вхождений.
Здравствуйте, Chriso, Вы писали: C>2. Дан список последовательностей чисел C>Найти самую часто встречающуюся непрерывную подпоследовательность и сколько раз она встречается. C>Облегчённый вариант, с которым я также не справился: длина подпоследовательности задана, рассматривать только подпоследовательности этой длины. C>Очевидное решение с Dictionary, где ключом является сама подпоследовательность не подходит по требуемой памяти, даже в "облегчённом" варианте. Предполагается, что список читается из файла и в память не влезет.
Во-первых, нужно дополнительное условие вроде "среди всех самых часто встречающихся последовательностей найти самую длинную". Иначе допустимым ответом будет последовательность длины 1 из самого часто встречающегося числа.
Во-вторых, было бы неплохо указать остальные ограничения по памяти — влезает ли в память один список, влезает ли в память Dictionary со всеми числами из всех последовательностей, влезает ли в память ответ, и т.д.
Я пока приведу решение для одной последовательности и случая, когда в память влезает всё.
Пусть у нас есть ответ — подпоследовательность, встречающаяся C раз. Тогда для неё выполняются следующие утверждения:
1) Любое число, входящее в эту подпоследовательность, встречается в ней ровно один раз. (Если оно встречается несколько раз, то последовательность длины 1, состоящая из этого числа, встретится k*C раз, т.е. чаще, чем ответ).
2) Любое число, входящее в эту подпоследовательность, встречается в исходной последовательности только во вхождениях ответа. (Если оно встречается где-то ещё, то последовательность длины 1 из этого числа, встретится C+k раз — чаще чем ответ).
Следствием этих утверждений является то, что все последовательности-кандидаты на ответ состоят из непересекающихся множеств чисел.
Решение следующее. На первом проходе считаем вхождения всех чисел, и составляем набор чисел, имеющих максимальное количество вхождений. Ответ будет состоять только из них.
На втором проходе жадным алгоритмом строим последовательности, удовлетворяющие приведенным выше условиям, и выбираем самую длинную из них. Сложность решения — O(N*Log(N)), где N — длина входной последовательности. Хотя нет, нашёл ошибку и теперь там O(N*N) вылезает.
Код решения
#include <iostream>
#include <vector>
#include <deque>
#include <map>
using namespace std;
class SequenceSolver
{
public:
SequenceSolver(vector<int>& sequence);
int getCount() { return maxCount_; }
vector<int> getSequence();
private:
// находит все числа-кандидаты для построения последовательностейvoid buildNumbers(vector<int>& sequence);
// прекращает построение/проверку активной последовательностиvoid terminateCurrentSequence();
// делит последовательность на две по элементу с индексом position
size_t splitSequence(deque<int>& sequence, size_t position);
// начинает построение новой последовательностиvoid beginSequence(size_t& index, int number);
// начинает проверку существующей последовательностиvoid beginSequenceCheck(size_t index, size_t position);
private:
static const size_t NONE = (size_t)(-1);
// числа-кандидаты для построения последовательностей
// значение - (индекс последовательности, позиция в последовательности)
map<int, pair<size_t, size_t>> numbers_;
// последовательности-кандидаты
vector<deque<int>> sequences_;
// индекс и текущая позиция активной последовательности
// (которая строится или проверяется в данный момент)
size_t activeIndex_;
size_t activePosition_;
// true, если активная последовательность встретилась впервыеbool activeNew_;
// сколько раз встречается искомая последовательностьint maxCount_;
};
SequenceSolver::SequenceSolver(vector<int>& sequence) : maxCount_(0), activeIndex_(NONE)
{
buildNumbers(sequence);
for (size_t i = 0; i < sequence.size(); i++)
{
auto it = numbers_.find(sequence[i]);
if (it == numbers_.end())
{
terminateCurrentSequence();
}
else
{
if (activeIndex_ == NONE) // нет активной последовательности
{
if (it->second.first == NONE)
{
beginSequence(it->second.first, it->first);
}
else
{
beginSequenceCheck(it->second.first, it->second.second);
}
}
else if (activeNew_) // есть новая активная последовательность
{
if (it->second.first == NONE)
{
// продолжаем новую активную последовательность
it->second.first = activeIndex_;
it->second.second = activePosition_;
sequences_[activeIndex_].push_back(it->first);
activePosition_ += 1;
}
else
{
// прерываем новую активную последовательность
// и начинаем проверку старой активной последовательности
terminateCurrentSequence();
beginSequenceCheck(it->second.first, it->second.second);
}
}
else// есть старая активная последовательность
{
if (it->second.first == NONE)
{
// прерываем старую активную последовательность
// и начинаем новую активную последовательность
terminateCurrentSequence();
beginSequence(it->second.first, it->first);
}
else if (it->second.first == activeIndex_ && it->second.second == activePosition_)
{
// продолжаем проверку старой активной последовательности
activePosition_ += 1;
if (activePosition_ == sequences_[activeIndex_].size())
{
activeIndex_ = NONE;
}
}
else
{
// прерываем старую активную последовательность
// и начинаем проверку другой старой активной последовательности
terminateCurrentSequence();
// обработка случая, когда число находится в этой же последовательности но на более
// далёкой позиции, например: [ 1 2 3 4 5 ] и [ 1 2 5 ]if (numbers_.find(sequence[i]) == numbers_.end())
{
activeIndex_ = NONE;
}
else
{
beginSequenceCheck(it->second.first, it->second.second);
}
}
}
}
}
}
vector<int> SequenceSolver::getSequence()
{
size_t max = 0;
for (size_t i = 1; i < sequences_.size(); i++)
{
if (sequences_[i].size() > sequences_[max].size())
{
max = i;
}
}
return vector<int>(sequences_[max].begin(), sequences_[max].end());
}
void SequenceSolver::buildNumbers(vector<int>& sequence)
{
map<int, int> counts;
for (size_t i = 0; i < sequence.size(); i++)
{
counts[sequence[i]]++;
}
maxCount_ = 0;
for (auto it = counts.begin(); it != counts.end(); ++it)
{
maxCount_ = it->second > maxCount_ ? it->second : maxCount_;
}
for (auto it = counts.begin(); it != counts.end(); ++it)
{
if (it->second == maxCount_)
{
numbers_[it->first] = make_pair(NONE, 0);
}
}
}
void SequenceSolver::terminateCurrentSequence()
{
if (activeIndex_ != NONE && !activeNew_)
{
splitSequence(sequences_[activeIndex_], activePosition_);
}
activeIndex_ = NONE;
}
size_t SequenceSolver::splitSequence(deque<int>& sequence, size_t position)
{
deque<int> temp(sequence.begin() + position, sequence.end());
sequence.erase(sequence.begin() + position, sequence.end());
size_t index = NONE;
if (temp.size() > 0)
{
index = sequences_.size();
for (size_t i = 0; i < temp.size(); i++)
{
numbers_[temp[i]] = make_pair(index, i);
}
sequences_.push_back(temp);
}
return index;
}
void SequenceSolver::beginSequence(size_t& index, int number)
{
index = sequences_.size();
sequences_.push_back(deque<int>());
sequences_.back().push_back(number);
activeIndex_ = index;
activePosition_ = 1;
activeNew_ = true;
}
void SequenceSolver::beginSequenceCheck(size_t index, size_t position)
{
activeIndex_ = splitSequence(sequences_[index], position);
activePosition_ = 1;
activeNew_ = false;
if (activePosition_ == sequences_[activeIndex_].size())
{
activeIndex_ = NONE;
}
}
int main()
{
int arr[] = { 1, 2, 3, 4, 0, 4, 2, 3, 2, 3 };
SequenceSolver sol(vector<int>(arr, arr + sizeof(arr) / sizeof(int)));
vector<int> ans = sol.getSequence();
cout << "N = " << sol.getCount() << endl;
cout << ans[0];
for (size_t i = 1; i < ans.size(); i++)
{
cout << " " << ans[i];
}
cout << endl;
return 0;
}
Если в ответе за числом X идёт число Y, то в исходной последовательности будет то же самое — за числом X всегда будет идти число Y.
Поэтому для решения задачи нам нужно посчитать количество вхождений каждого числа, и выяснить, идёт ли за ним всегда одно и то же число, или могут идти разные (конец последовательности трактуется как "идут разные числа").
Далее рассматриваем только числа, имеющие максимальное количество вхождений. По сути у нас есть ориентированный граф, вершины которого — числа, а дуги строятся по правилу: если за числом X всегда идёт число Y, то есть дуга X->Y. Ответом исходной задачи является самый длинный путь в этом графе. Поскольку исходящих дуг у каждой вершины не более одной, и отсутствуют циклы, то поиск такого пути тривиален.
И сложность решения теперь O(N*Log(N)).
Код решения
#include <iostream>
#include <vector>
#include <stack>
#include <map>
using namespace std;
void solve(vector<int>& sequence)
{
// для каждого числа считаем, сколько раз оно встречается
// и одинаковые ли числа идут после него
// если одинаковые - запоминаем их во втором поле, если разные - записываем -1
map<int, pair<int, int>> numbers;
for (size_t i = 0; i < sequence.size(); i++)
{
int nextNumber = i + 1 < sequence.size() ? sequence[i + 1] : -1;
auto it = numbers.find(sequence[i]);
if (it == numbers.end())
{
it = numbers.insert(make_pair(sequence[i], make_pair(1, nextNumber))).first;
}
else
{
it->second.first++;
if (it->second.second != nextNumber)
{
it->second.second = -1;
}
}
}
int maxCount = 0;
for (auto it = numbers.begin(); it != numbers.end(); ++it)
{
maxCount = it->second.first > maxCount ? it->second.first : maxCount;
}
// удаляем все числа, встречающиеся менее максимального числа раз
// одновременно инициализируем поле счетчика для запоминания длины последовательностиauto it = numbers.begin();
while (it != numbers.end())
{
auto tmp = it++;
if (tmp->second.first != maxCount)
{
numbers.erase(tmp);
}
else
{
tmp->second.first = -1;
}
}
// поиск в глубину из каждого числа с кэшированием результата
stack<int> stk;
for (auto it = numbers.begin(); it != numbers.end(); ++it)
{
if (it->second.first == -1)
{
auto tmp = it;
while (tmp->second.first == -1 && tmp->second.second != -1)
{
stk.push(tmp->first);
tmp = numbers.find(tmp->second.second);
}
if (tmp->second.second == -1)
{
tmp->second.first = 0;
}
while (!stk.empty())
{
tmp = numbers.find(stk.top());
tmp->second.first = numbers.find(tmp->second.second)->second.first + 1;
stk.pop();
}
}
}
// ищем число, из которого начинается путь максимальной длиныint maxLength = 0;
int maxNumber = 0;
for (auto it = numbers.begin(); it != numbers.end(); ++it)
{
if (it->second.first > maxLength)
{
maxLength = it->second.first;
maxNumber = it->first;
}
}
cout << "N = " << maxCount << endl;
cout << maxNumber;
it = numbers.find(maxNumber);
while (it->second.second != -1)
{
it = numbers.find(it->second.second);
cout << " " << it->first;
}
cout << endl;
}
int main()
{
int arr[] = { 1, 2, 3, 4, 0, 4, 2, 3, 2, 3 };
solve(vector<int>(arr, arr + sizeof(arr) / sizeof(int)));
return 0;
}
Здравствуйте, Sergei MO, Вы писали:
SM>Придумал, как сделать ещё проще. SM>И сложность решения теперь O(N*Log(N)). SM>int arr[] = { 1, 2, 3, 4, 0, 4, 2, 3, 2, 3 };
Решение ломается, если в конец последовательности добавить 2, 4.
Б>Т.е. в качестве ответа предложена последовательность {2}, а правильный ответ {2, 3}
С чего-то ответ [2, 3] является правильным? Невооруженным взглядом видно, что в этом массиве эта непрерывная подпоследовательность встречается три раза. Хотя другая подпоследовательность ([2]), которая встречается четыре раза. Так что это сразу свидетельствует том, что [2, 3] не может быть самой частвовстречаемой.
Здравствуйте, watchmaker, Вы писали:
W>С чего-то ответ [2, 3] является правильным? Невооруженным взглядом видно, что в этом массиве эта непрерывная подпоследовательность встречается три раза. Хотя другая подпоследовательность ([2]), которая встречается четыре раза. Так что это сразу свидетельствует том, что [2, 3] не может быть самой частвовстречаемой.
Ну, ок. Я пытался привести пример, когда за двойкой будет следовать другой элемент (четверка вместо тройки), и алгоритм ошибется.
Вот для такого массива { 1, 2, 3, 4, 0, 4, 2, 3, 2, 3, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4 }
N = 9
2
Правильный ответ {2, 4} и N=6
ДОБАВЛЕНО:
Если уж что-то и реализовывать из достаточно простых и эффективных алгоритмов, то это алгоритм на суффиксных массивах (не дереве).
— формируем массив суффиксов (можно просто хранить начальный индекс суффикса)
— сортируем все суффиксы по алфавиту (лексикографически)
— пробегаем по списку и находим общие префиксы у суффиксов (сравнивая соседние элементы)
Выводим ответ — максимальный найденный префикс
Но почему он правильный?
Ведь эта подпоследовательность встречается только 6 раз. Хотя в этот массив входит и другая подпоследовательность (всё та же [2]), которая встречается целых 9 раз. Что явно чаще
Здравствуйте, watchmaker, Вы писали:
W>Ведь эта подпоследовательность встречается только 6 раз. Хотя в этот массив входит и другая подпоследовательность (всё та же [2]), которая встречается целых 9 раз. Что явно чаще
Здравствуйте, watchmaker, Вы писали:
W>Но почему он правильный? W>Ведь эта подпоследовательность встречается только 6 раз. Хотя в этот массив входит и другая подпоследовательность (всё та же [2]), которая встречается целых 9 раз. Что явно чаще
Имхо, задача с длиной последовательности равной единице — это совсем другая задач. Это задача про поиск наиболее часто встречающегося элемента в массиве.
А условие "рассматривать только подпоследовательности этой длины" (в облегченной задаче!) намекает нам, что наша задача не про подсчет элементов, а про поиск последовательности (т.е. хотя бы из двух элементов).
Здравствуйте, Буравчик, Вы писали:
Б>А условие "рассматривать только подпоследовательности этой длины" (в облегченной задаче!) намекает нам, что наша задача не про подсчет элементов, а про поиск последовательности (т.е. хотя бы из двух элементов).
В исходном сообщении в задаче опущены и другие условия, не только это, которые важны для того чтобы понять какое решение является подходящим, а какое нет. Так что многие додумывают как хотят
Гораздо печальнее, если это происходит не на форуме, а во время интервью кандидат не уточняет важные детали и решает не ту задачу, которую имел ввиду собеседующий.
То есть из исходного сообщения понятно из какой области спрашивали знания. В целом интересно. Как раз при подготовке к собеседованию ясно какие разделы нужно повторить. Но условия задач лучше искать в другом месте.
Здравствуйте, Chriso, Вы писали:
C>1. Дан список строк S[1..N], нужно вернуть список R всех возможных списков таких, что: C>R[i][j] ∈ S C>string.Join("", R[i]) ∈ S C>R[i].Count >= 2
C>Пример: C>вход [a, b, ab, cd, abcd, fgh, f, g, h] C>выход [[a, b], [a, b, cd], [ab, cd], [f, g, h]]
Перебираем все слова в input.
Отсекаем те, длина которы меньше 2-х символов, т.к. для них не будет решений, удоволетворяющих условию R[i].Count >= 2
Для каждого слова перебираем все возможные комбинации head и tail (скользящий splitIndex)
Если head найден в input, то рекурсивно ищем все возможные комбинации для tail.
Из найденных комбинаций убираем состоящие из одного элемента.
class Program
{
private static string[] input = new[] { "a", "b", "ab", "cd", "abcd", "fgh", "f", "g", "h" };
static void Main()
{
var output = input
.Where(term => term.Length >= 2) // Заранее отсекаем все слова, которые короче 2-х символов
.SelectMany(term => FindCombinations(term)) // Ищем все возможные комбинации для каждого слова
.Where(list => list.Count() >= 2); // Отсекаем комбинации, которые состоят из менее 2-х элементов
Console.WriteLine(JsonConvert.SerializeObject(output));
Console.ReadLine();
}
static IEnumerable<IEnumerable<string>> FindCombinations(string term)
{
var splitIndex = 1;
while (splitIndex <= term.Length)
{
var (head, tail) = (term.Substring(0, splitIndex), term.Substring(splitIndex++));
if (input.Contains(head))
{
if (tail.Length == 0)
yield return new[] { head };
else
foreach (var result in FindCombinations(tail))
yield return new[] { head }.Concat(result);
}
}
}
}
Здравствуйте, sergeya, Вы писали:
S>Пусть word можно составить как из subword1, так из subword2 + subword3. S>Приведенное условие имхо остановит перебор как только встретится subword1.
Нет, не остановит. Это часть цикла, в котором мы пробегаем по всем возможным подсловам. После выполнения этого условия мы перейдем к рассмотрению следующего возможного подслова (на операторе continue)
Можно переписать так (может легче будет воспринимать конечное условие рекурсии):
@lru_cache
def split_word(word: str) -> List[List[str]]:
"""Находит все варианты разбиения слова word на подстроки из strings"""if word == "":
return [[]]
result = []
for subword in strings:
if not word.startswith(subword):
continue
rest_word = word[len(subword):]
for rest_combination in split_word(word=rest_word):
result.append([subword, *rest_combination])
return result
Здравствуйте, Буравчик, Вы писали:
Б>Нет, не остановит. Это часть цикла, в котором мы пробегаем по всем возможным подсловам. После выполнения этого условия мы перейдем к рассмотрению следующего возможного подслова (на операторе continue)
Ты прав. Там же не выход из процедуры, а только переход к следующему слову.
Здравствуйте, scf, Вы писали:
scf>Что-то здесь не так. Даже упрощенная задача "найти самое часто встречающееся число в потоке" требует хранения всех чисел со счетчиками в памяти
ну если прямо вот так в память не влазит, то можно и на диск выгружать.
а может им примерно сойдет, то можно Count–min sketch прикрутить
Здравствуйте, Буравчик, Вы писали:
Б>Здравствуйте, baxton_ulf, Вы писали:
_>>ну раз ДП то добавь мемоизацию и посчитай сколько ключей там будет храниться
Б>Мемоизация там есть.