Наименьший подсписок, содержащий все числа

Я пытаюсь написать программу в 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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
222
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Эта проблема «наименьшего подсписка, содержащего все значения», по-видимому, повторяется в новые вопросы без успешного ответа. Это потому, что это не минимальный, полный и проверяемый пример.

Поскольку вы используете подход «скользящего окна», индексируя переднюю часть а также задней вашего ввода, список, требующий На) времени для индексации элементов, не идеален. Ты действительно хочу использовать массивы здесь. Если ваша функция ввода должна иметь список, вы может преобразовать его в массив для целей алгоритма.

Я хотел бы выполнить очистку кода перед ответом, потому что запуск ваш текущий код вручную немного сложен, потому что он такой сжатый. Вот пример того, как вы можете абстрагироваться от учета того, является ли данный подсписок содержит по крайней мере одну копию каждого элемента в исходном списке:

Редактировать: Я изменил приведенный ниже код после его публикации.

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 вероятно, есть функция для его поиска.

Я фактически решил проблему, разделив увеличение и уменьшение и вызвав их из функции, которая зацикливается. Ваш код кажется намного чище, так что я посмотрю на него. Спасибо за уделенное время!!

user7475343 05.04.2019 14:50

@ maverick98: Поскольку вы сказали, что исправили проблему, я расширил свое предложение до полного, но неэффективного решения и выделил проблему производительности, связанную с отказом от использования мемоизации.

sshine 05.04.2019 17:48

Спасибо и извините за множество вопросов, нужно время, чтобы привыкнуть к SML.

user7475343 06.04.2019 12:42

Другие вопросы по теме