Подход
Вот способ сделать это с помощью хэша, который отображает символы в индексы.Для строки s
предположим, что символы в подстроке s[j..j+n-1]
являются уникальными, и поэтому подстрока является кандидатом на самую длинную уникальную подстроку.Поэтому следующим элементом является e = s[j+n]
Мы хотим определить, включает ли s[j..j+n-1]
e
.Если этого не произойдет, мы можем добавить e
к подстроке, сохранив ее уникальность.
Если s[j..j+n-1]
включает e
, мы определяем, будет ли n
(размер подстроки) больше, чемдлина ранее известной подстроки и обновите наши записи, если это так.Чтобы определить, включает ли s[j..j+n-1]
e
, мы могли бы выполнить линейный поиск подстроки, но быстрее поддерживать хеш c_to_i
, чьи пары ключ-значение s[i]=>i
, i = j..j_n-1
.То есть c_to_i
сопоставляет символы в подстроке с их индексами в полной строке s
.Таким образом, мы можем просто оценить c_to_i.key?(e)
, чтобы увидеть, содержит ли подстрока e
.Если подстрока включает e
, мы используем c_to_i
, чтобы определить ее индекс в s
и добавляем один: j = c_to_i[e] + 1
.Поэтому новая подстрока имеет значение s[j..j+n-1]
с новым значением j
.Обратите внимание, что на этом шаге можно пропустить несколько символов s
.
Независимо от того, содержала ли подстрока e
, теперь мы должны добавить e
к (возможно обновленной) подстроке, чтобы онастановится s[j..j+n]
.
Код
def longest_no_repeats(str)
c_to_i = {}
longest = { length: 0, end: nil }
str.each_char.with_index do |c,i|
j = c_to_i[c]
if j
longest = { length: c_to_i.size, end: i-1 } if
c_to_i.size > longest[:length]
c_to_i.reject! { |_,k| k <= j }
end
c_to_i[c] = i
end
c_to_i.size > longest[:length] ? { length: c_to_i.size, end: str.size-1 } :
longest
end
Пример
a = ('a'..'z').to_a
#=> ["a", "b",..., "z"]
str = 60.times.map { a.sample }.join
#=> "ekgdaxxzlwbxixhlfbpziswcoelplhobivoygmupdaexssbuuawxmhprkfms"
longest = longest_no_repeats(str)
#=> {:length=>14, :end=>44}
str[0..longest[:end]]
#=> "ekgdaxxzlwbxixhlfbpziswcoelplhobivoygmupdaexs"
str[longest[:end]-longest[:length]+1,longest[:length]]
#=> "bivoygmupdaexs"
Эффективность
Вот сравнительное сравнение с кодом @ mechnicov:
require 'benchmark/ips'
a = ('a'..'z').to_a
arr = 50.times.map { 1000.times.map { a.sample }.join }
Benchmark.ips do |x|
x.report("mechnicov") { arr.sum { |s| max_non_repeated(s)[:length] } }
x.report("cary") { arr.sum { |s| longest_no_repeats(s)[:length] } }
x.compare!
end
отображает:
Comparison:
cary: 35.8 i/s
mechnicov: 0.0 i/s - 1198.21x slower