Я пытаюсь написать программу в sml, которая принимает длину списка, максимальное число, которое будет отображаться в списке, и, конечно же, список. Затем он вычисляет длину наименьшего «подсписка», содержащего все числа.
Я попытался использовать подход скользящего окна с двумя индексами, передним и хвостовым. Передняя часть сначала сканирует, и когда находит номер, записывает на карту, сколько раз он уже видел этот номер. Если программа находит все числа, то она вызывает хвост. Хвост просматривает список и, если обнаруживает, что число встречалось больше одного раза, удаляет его.
Код, который я пробовал до сих пор, следующий:
structure Key=
struct
type ord_key=int
val compare=Int.compare
end
fun min x y = if x>y then y else x;
structure mymap = BinaryMapFn ( Key );
fun smallest_sub(n,t,listall,map)=
let
val k=0
val front=0
val tail=0
val minimum= n;
val list1=listall;
val list2=listall;
fun increase(list1,front,k,ourmap)=
let
val number= hd list1
val elem=mymap.find(ourmap,number)
val per=getOpt(elem,0)+1
fun decrease(list2,tail,k,ourmap,minimum)=
let
val number=hd list2
val elem=mymap.find(ourmap,number)
val per=getOpt(elem,0)-1
val per1=getOpt(elem,0)
in
if k>t then
if (per1=1) then decrease(tl list2,tail+1,k-1,mymap.insert(ourmap,number,per),min minimum (front-tail))
else decrease(tl list2,tail+1,k,mymap.insert(ourmap,number,per),min minimum (front-tail))
else increase (list1, front,k,ourmap)
end
in
if t>k then
if (elem<>NONE) then increase (tl list1,front+1,k,mymap.insert(ourmap,number,per))
else increase(tl list1,front+1,k+1,mymap.insert(ourmap,number,per))
else (if (n>front) then decrease(list2,tail,k,ourmap,minimum) else minimum)
end
in
increase(list1,front,k,map)
end
fun solve (n,t,acc)= smallest_sub(n,t,acc,mymap.empty)
Но когда я вызываю его с этим наименьшим_под(10,3,[1,3,1,3,1,3,3,2,2,1]); это не работает. Что я сделал не так??
Пример: если введено 1,3,1,3,1,3,3,2,2,1, программа должна распознать, что часть списка, которая содержит все числа и является наименьшей, равна 1,3,3,2. и 3,2,2,1, поэтому на выходе должно быть 4
Эта проблема «наименьшего подсписка, содержащего все значения», по-видимому, повторяется в новые вопросы без успешного ответа. Это потому, что это не минимальный, полный и проверяемый пример.
Поскольку вы используете подход «скользящего окна», индексируя переднюю часть а также задней вашего ввода, список, требующий На) времени для индексации элементов, не идеален. Ты действительно хочу использовать массивы здесь. Если ваша функция ввода должна иметь список, вы может преобразовать его в массив для целей алгоритма.
Я хотел бы выполнить очистку кода перед ответом, потому что запуск ваш текущий код вручную немного сложен, потому что он такой сжатый. Вот пример того, как вы можете абстрагироваться от учета того, является ли данный подсписок содержит по крайней мере одну копию каждого элемента в исходном списке:
Редактировать: Я изменил приведенный ниже код после его публикации.
structure CountMap = struct
structure IntMap = BinaryMapFn(struct
type ord_key = int
val compare = Int.compare
end)
fun count (m, x) =
Option.getOpt (IntMap.find (m, x), 0)
fun increment (m, x) =
IntMap.insert (m, x, count (m, x) + 1)
fun decrement (m, x) =
let val c' = count (m, x)
in if c' <= 1
then NONE
else SOME (IntMap.insert (m, x, c' - 1))
end
fun flip f (x, y) = f (y, x)
val fromList = List.foldl (flip increment) IntMap.empty
end
То есть CountMap
— это int IntMap.map
, где Int
представляет
фиксированный ключевой тип карты, int
, и параметр int
перед ним
представляет тип значения карты, будучи считать того, сколько раз это
произошло значение.
При построении initialCountMap
ниже вы используете CountMap.increment
и
когда вы используете подход «скользящего окна», вы используете CountMap.decrement
для
создайте новый countMap
, который вы можете рекурсивно протестировать.
Если вы уменьшите вхождение ниже 1, вы просматриваете подсписок, который
не содержит каждый элемент хотя бы один раз; мы исключаем любое решение
позволить CountMap.decrement
вернуться NONE
.
Когда весь этот механизм абстрагирован, сам алгоритм становится намного сложнее. проще выразить. Во-первых, я хотел бы преобразовать список в массив, чтобы indexing становится О(1), потому что мы будем много индексировать.
fun smallest_sublist_length [] = 0
| smallest_sublist_length (xs : int list) =
let val arr = Array.fromList xs
val initialCountMap = CountMap.fromList xs
fun go countMap i j =
let val xi = Array.sub (arr, i)
val xj = Array.sub (arr, j)
val decrementLeft = CountMap.decrement (countMap, xi)
val decrementRight = CountMap.decrement (countMap, xj)
in
case (decrementLeft, decrementRight) of
(SOME leftCountMap, SOME rightCountMap) =>
Int.min (
go leftCountMap (i+1) j,
go rightCountMap i (j-1)
)
| (SOME leftCountMap, NONE) => go leftCountMap (i+1) j
| (NONE, SOME rightCountMap) => go rightCountMap i (j-1)
| (NONE, NONE) => j - i + 1
end
in
go initialCountMap 0 (Array.length arr - 1)
end
Кажется, это работает, но...
Выполнение Int.min (go left..., go right...)
требует затрат в стеке О (п ^ 2)
памяти (в случае, если вы не можете исключить, что они оптимальны). Это
хороший вариант использования для динамического программирования, потому что ваши рекурсивные подзадачи имеют
общая подструктура, т.е.
go initialCountMap 0 10
|- go leftCountMap 1 10
| |- ...
| `- go rightCountMap 1 9 <-.
`- go rightCountMap 0 9 | possibly same sub-problem!
|- go leftCountMap 1 9 <-'
`- ...
Так что, возможно, есть способ сохранить рекурсивную подзадачу в массиве памяти, а не выполните рекурсивный поиск, если вы знаете результат этой подзадачи. Как делать мемоизацию в SML — хороший вопрос сам по себе. Как сделать чисто функциональная мемоизация на неленивом языке еще лучше.
Еще одна оптимизация, которую вы могли бы сделать, заключается в том, что если вы когда-нибудь найдете подсписок,
размер количества уникальных элементов, вам не нужно искать дальше. Это число
кстати, количество элементов в initialCountMap
, а IntMap
вероятно, есть функция для его поиска.
@ maverick98: Поскольку вы сказали, что исправили проблему, я расширил свое предложение до полного, но неэффективного решения и выделил проблему производительности, связанную с отказом от использования мемоизации.
Спасибо и извините за множество вопросов, нужно время, чтобы привыкнуть к SML.
Я фактически решил проблему, разделив увеличение и уменьшение и вызвав их из функции, которая зацикливается. Ваш код кажется намного чище, так что я посмотрю на него. Спасибо за уделенное время!!