Рубрики
Без рубрики

Развращение битовой динамического программирования

Автор оригинала: FreeCodeCapm Team.

Сачин Малхотра

Как пойти от опасающегося его покорения!

Ну, мастер прогусья, нет неуважения, совпадения не происходит, и я думаю, что они просто Божий путь оставшихся анонимных. Не только я, Альберт Эйнштейн так считает это?

111, это не совсем мой Удачный номер, так сказать.

Тем не менее, этот номер наполнил меня радостью и экстаз, когда я увидел его как мой ранг на странице рейтинга Лецкод Конкурс № 111.

Какое совпадение!

Это самый высокий рейтинг в еженедельном конкурсе, который я добился до этого далеко на платформе. Номер конкурса и рейтинг был явно чистым совпадением.

Конкурс обычно состоит из простых проблем, 2 проблем среднего уровня и сложная проблема.

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

Почему я говорю, что это было очень тяжело? Посмотрите на количество людей, которые смогли решить это во время конкурса.

Как предполагает название статьи, эта проблема должна была быть решена с использованием динамического программирования на основе битовой маскировки.

Динамическое программирование является одним из самых страшных алгоритмических доменов. Требуется много практики для разработки интуиции о динамическом программированном решении решения проблемы. Я всегда считал, что это улучшение рекурсивного решения проблемы. Основная идея динамического программирования в условиях Лейперсона:

Bitmasking Как тема в компьютерном программировании – это то, что у меня есть (и я уверен, что бесчисленные другие разработчики там имеются) боялись долгое время. Это одна из тех тем, которые наверняка возьмут у вас баланс в интервью и получают вам отклонение.

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

Оптимизации, связанные с битовыми манипуляциями, происходят в самых неожиданных местах. С какой-то практикой я смог преодолеть Страх, Так сказать, работа над проблемами по программированию на основе растраски.

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

Как и в случае любого нового, вы узнаете, очень трудно сохранить теоретические концепции, связанные с битовой микросхемой. Удержание является лучшим, когда речь идет через практику. Это главная цель этой статьи. Давайте быстро посмотрим на оглавление, прежде чем мы выбиваем основную статью.

Получите немного горячего шоколада и будьте готовы к …

0️⃣ 1️⃣ Что значит манипулированием? 0️⃣ 1️⃣.

Немного по сути – самая маленькая единица хранения в компьютере. Это единственная единица, которую компьютер понимает.

Единственная информация, которую немного может хранить, сформирована из двух разных состояний: 0️⃣ и 1️⃣. Любые вычисления, которые выпускает компьютер, в основном является некоторой формой битовой манипулирования.

Давайте посмотрим на Википедия Определение битовых манипуляций:

Реализация-мудрые, одна из самых полезных и эффективных оптимизаций низкоуровневых к алгоритму – манипуляциями.

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

Это одна страшная часть ☠ Код ☠.

Основы ✍️.

В основе битовых манипуляций являются бит-мудрыми операторами:

  • А также (&)
  • Или (|)
  • Не (~)
  • XOR (^)

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

Таблицы истины показывают результаты для этих операторов, когда они работают на 2 битах, представленных А и B Отказ Компьютер должен иметь дело с гораздо больше, чем только один бит данных.

Данные, обработанные системой, как правило, в байтах или килобайтах или более. Как эти операторы работают на операндах, представленных для E.G. 8 бит или 16 битов? В таком сценарии операции одинаковы, за исключением того, что они работают на каждом бите аргументов. Давайте рассмотрим простой пример, чтобы уточнить это.

A = 11101010B = 00110101
+-+-+-+-  AND  -+-+-+-+A & B = 00100000
+-+-+-+-  OR   -+-+-+-+A | B = 11111111
+-+-+-+-  NOT  -+-+-+-+~A    = 00010101
+-+-+-+-  XOR  -+-+-+-+A ^ B = 11011111

В дополнение к этим 4 основным операторам есть два других набора побитовых операторов, которые входят в очень удобные. Почти все проблемы, на которых мы рассмотрим в этой статье, используют их, чтобы дать огромное повышение вычислительной скорости. Это левая смена & l t; <и право Sh IFT >> Операторы.

Проще говоря, оператор левого смещения означает умножение числа на 2 и оператор правого смещения означает разделить число на 2.

Давайте посмотрим на простую анимацию, чтобы показать, почему эти операторы называются Левая смена и правый сдвиг соответственно.

Для демонстрации операции левой смены мы начинаем с десятичного числа 1 А потом несколько раз умножайте его с помощью 2. Как вы можете видеть в двоичном представлении полученных чисел, единственным 1 В представлении продолжается переключаться на один шаг за раз. Вот почему это называется левый Сдвиг операция.

Вдоль тех же строк, для демонстрации операции правильной смены, мы начинаем с десятичного числа 128 А потом неоднократно разделите его на 2. Как вы можете видеть в двоичном представлении полученных чисел, единственным 1 В представлении продолжается переключаться прямо на один шаг за раз. Вот почему это называется правильно Сдвиг операции.

Теперь, когда мы все разбираемся с основными двоичными операторами, давайте перейдем к некоторым простым случаям использования для этих операторов. Мы посмотрим на несколько примеров ниже. Однако это не программирование проблем сами по себе, однако они много используются как строительные блоки во многих алгоритмах.

Основные случаи использования?

Подсчет количества установленных битов

Одним из основных утилит для операторов, которые мы рассмотрели выше, составляют количество битов, установленных в заданном двоичном представлении.

Это может не казаться важным случаем использования, но мы будем получать более подробную информацию позже, а затем начнут казаться более значимым. На данный момент давайте просто подсчитаем количество битов настроек максимально эффективно.

Первый метод, на котором мы будем смотреть на это, это бит интуитивно понятный Это использует побитовое питание И оператор. Начиная с наименее значимого бита, мы просто проверяем, установлен ли блок на каждой позиции или нет и не увеличивает счетчик соответственно.

И оператор возвращает Правда IFF обе биты являются Правда Отказ Давайте посмотрим на код для этого.

Еще один способ сделать это по Инг Номер с Сама - 1 пока число не станет нулевым. Количество шагов, предпринятых для достижения 0, будет количеством установленных битов в исходном номере.

Причина, по которой это работает, что каждый раз, когда мы И Номер с Сама - 1 один бит удален из числа. Это продолжается до тех пор, пока число не станет нулевым.

Маскировка и разоблачение определенного бита

Предположим, мы хотим маска конкретный бит в двоичном представлении Отказ Это просто означает отключение бита или преобразование 1 → 0 Отказ На подобных линиях разоблачить Просто означает обратную работу на определенный бит.

Вам может быть интересно, почему это вообще полезно. Одним из наиболее важных случаев использования для маскировки (или разоблачения) немного является в заданном связанных операциях.

Мы можем представлять набор предметов как X-Bit Целое число, Х это количество предметов в наборе. Маскировка немного будет означать удаление этого элемента из набора. Для практического применения этого, будьте терпеливы и читать дальше?,

XOR – очень универсальный оператор, и он идеально подходит для задачи маскировки и разоблачения операций.

1 ^ ? → 00 ^ ? → 1

По сути, мы можем использовать то же самое маскировка Переменная для достижения операции набора/не установлена, соответствующая конкретному биту (целое число в том, называется переменной маскировки).

A ^ (1 << i)

Вышеуказанная операция приведет к маскировке бита на индекс Я Если изначально этот бит был Установить в А и та же операция приведет к разобщению бит при индексе Я Если изначально это было unset в А Отказ

Мы уже видели, как обнаружить, установлено ли немного или нет, когда мы смотрели способы подсчета количества установленных битов в заданном двоичном представлении.

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

То, что мы имеем в виду указатель выше, от правильно конец (наименее значимый бит в двоичном представлении имеет индекс 0).

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

? Отсутствует номер?

Есть несколько способов решения этой проблемы. Мы можем отсортировать данный список чисел, а затем итерации от 0..n и легко найти пропущенный номер. Это даст нам O (nlogn) решение.

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

Давайте посмотрим, как мы можем достичь O (1) пространство, O (n) Время решения как босс? используя битные манипулирования.

Мы сделаем использование Хор Собственность здесь, чтобы решить эту проблему. Как упоминалось ранее, Xor оценивает Правда Когда входные биты разные, и он оценивает Ложь Когда представлены теми же битами. Мы заинтересованы в более позднем сценарии. Как вы думаете, что следующее оценивает?

A ^ A

Косой Номер с собой дает нам 0. Это главная идея нашего подхода здесь.

Итак, что мы сделаем, мы будем XOR все номера в нашем списке. Давайте назовем ценность, которую мы получаем после этого, как А Отказ

Мы будем XOR все номера от 0..n все вместе. Давайте назовем это значение, B Отказ

Делая это, все номера, присутствующие в исходном массиве, получат Xored на своих коллег и оценит до 0. Единственное число, оставленное в конце, будет отсутствующий номер.

A ^ B = missing number

☝️? ✌️? Подсчет битов ?? ??

Это одна из тех проблем, где пишет ответ на разные тестовые случаи и соблюдение результатов для шаблонов, действительно помогает. Итак, мы сделаем именно это и используем шаблон, который мы находим, чтобы напрямую прибыть в финальный алгоритм.

Давайте посмотрим на число 1 в двоичном представлении первых 16 чисел.

0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  160  1  1  2  1  2  2  3  1  2   2   3   2   3   3   4   1

Выше сформированная выше картина следующая:

Это все, что есть, чтобы увидеть в результатах выше. Это алгоритм в полном объеме. Все, что нам нужно сделать, это итерации по номерам 0..n И используйте два правила выше, и мы решили проблему, как босс?

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

Мы используем результат для предыдущей подзоли (меньшего числа) для расчета ответа (количество установленных битов) для текущей подпроблемы.

На грани этого нам не нужны какие-либо битовые манипуляции как таковые, чтобы решить эту проблему. Это может быть решено простым Если-ж пункты и для петля.

Итак, это не Действительно Bitmasking + динамический программирующий вид проблемы.

Давайте посмотрим на простое решение, основанное на идеях выше.

Это совершенно хороший способ решить эту проблему. Для каждого индекса Я мы проверяем количество битов в количестве I/2 а также добавить 1 Если текущий номер нечетным.

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

  1. разделение на 2 и
  2. Проверка, если число равно или нет.

Мы уже видели использование правильного оператора сдвига, и г T;>, для разделения на 2.

Что касается второй операции, мы можем просто проверить, установлен ли наименее значимый или нет. Если вы заметите, все нечетные номера имеют свой наименее значительный набор битов. В то время как даже номера не.

Мы уже видели, как проверить, установлен ли конкретный бит или не использует И оператор. Давайте посмотрим на гике? Версия вышеуказанного кода.

Если вы посмотрите на runtimes как для программ, они почти одинаковы. Крупный кусок времени потребляется строительством производственного массива.

Битовые операции всегда оптимизированы и быстрее, чем другие конструкции программирования более высокого уровня.

⛩ Максимальный продукт длин слов ⛩

Есть хорошие новости и плохие новости. Хорошая новость заключается в том, что слегка оптимизированная версия алгоритма Brute Force получит ваш код, принятый на платформе.

Путь грубой силы – проверить все пары слов и для каждой пары, проверьте, есть ли общие символы между ними. Для всех таких пар, у которых нет общих персонажей, запишите максимальное значение Len (Word1) * Len (Word2) Отказ

Плохая новость заключается в том, что этот алгоритм чрезвычайно медленнее. Давайте посмотрим на процент решений на летекоде, который этот алгоритм грубой силы способен бить.

Позор на тебе, Сачин!

Это не та статистика, которую вы хотите увидеть для вашего представления. Если вы тот, кто просто хочет решить Проблема, то ваша работа здесь сделана. Больше ничего не делать. Но, если вы похожи на меня, и если вы хотите сделать эту собаку стоять?, Тогда читай!

Давайте посмотрим на момент сложности этого алгоритма грубой силы, прежде чем перейти на гораздо более оптимизированное решение, используя биты манипуляции.

Рассмотрим все возможные пары слов из данного массива. Учитывая, что есть N Слова в данном массиве, мы получим O (n²) сложность прямо с летучей мыши.

Кроме того, у нас есть словарь, содержащий наборы символов для каждого из слов в словаре. Учитывая размер алфавита до 26 (только строчные буквы алфавита), каждый набор может потенциально иметь размер 26 Отказ

Для каждой пара слов мы выполняем множество пересечений, чтобы проверить, имеют ли два слова общих символов или нет. Установить пересечение принимает линейное время и, следовательно, общая сложность этого алгоритма будет выходить O (26n²) что по существу O (n²) Отказ

Оказывается, мы не можем избавиться от части, где мы должны рассмотреть каждую пару слов из данного массива. Итак, мы не можем избавиться от O (n²) часть алгоритма.

Часть, которую мы можем избавиться от, однако, это часть, где мы сравниваем два слова и посмотрим, есть ли у них общие персонажи. Эта постоянная 26 Смедит алгоритм много.

Важно отметить здесь, что вопрос просто заботится об общих персонажах и не их частота или их порядок.

Здесь мы можем сделать здесь, состоит в том, чтобы иметь ратушку, состоящую из 26 битов, чтобы представлять персонажей, принадлежащие к конкретному слову. Давайте посмотрим на такое представление для нескольких слов, чтобы сделать вещи более понятными.

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

Если два слова будут иметь все общие символы, то соответствующие биты для этих символов будут установлены в растратах для обоих слов.

Следовательно, все, что нам нужно сделать, это делать побитовые И из растровых компаний, представляющих два слова и проверьте, получаем ли мы 0 или нет. Если мы в конечном итоге получаем 0, это будет подразумевать бы пересечения, и это именно то, что мы ищем.

For e.g. Let's consider two words "hello" and "jack" 
The bitmask corresponding to "hello" will have the bits for 'h', 'e', 'l', and 'o' set.
The bitmask corresponding to "jack" will have the bits for 'j', 'a', 'c', and 'k' set. 
Since these words don't have any character in common, the bitwise AND of their bitmasks will give a 0. 
Had there been any common characters between them, then both their bitmasks would have set bits at the same indexes (corresponding to the common letters) and hence we would get a non-zero bitwise AND.

Давайте посмотрим на код для этой модификации в алгоритме, который мы просто обсуждали.

Битовые операции очень быстро. Давайте посмотрим на производительность этого алгоритма на платформе LeetCode для подтверждения быстроты битовой манипулирования.

Неплохо, верно? Мы улучшили время выполнения для нашей программы от 1820 мс ранее до 712 мс сейчас Отказ Это огромное улучшение, не так ли? Оказывается, мы можем улучшить этот алгоритм еще дальше.

Для E.g. Привет и LLOHHEL Оба будут иметь такую же битмассу. Мы можем хранить Самое длинное слово для данной битмазки, так как все, что мы заботимся о том, это максимизировать продукт длин слов.

Давайте посмотрим на код после включения этого улучшения.

Делая эту очень оптимизацию, время выполнения доходит до 208 мс ???

Установите представление с использованием битмаски

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

В предыдущей статье мы рассмотрели набор из 26 алфавитов, и мы возмотали, что, используя ратушку, содержащую 26 бит. A 0 при определенном индексе в маске будет представлять установленные исключения, тогда как A 1 будет представлять установить включение.

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

Для E.g. Предположим, у нас есть множество номеров [4, 3, 6] Давайте посмотрим на все возможные подмножества этого массива. [] [4] [3] [6] [4, 3] [4, 6] [3, 6] [4, 3, 6] Для проблем с динамическими программированиями, где промежуточные состояния определяются этими подмутами, нам нужен эффективный способ выполнения кэширования.

Что мы делаем в таком сценарии?

Это где у нас появились битовые маски. Они являются эффективными памятью способами представления подмножеств элементов. Давайте посмотрим на то, как мы можем представлять подмножества выше, используя ратуши.

Поскольку у нас есть 3 элемента в нашем заданном массиве, [4, 3, 6] мы можем использовать 3-битное число для представления каждого из этих элементов. Пустое подмножество будет представлено 000. Давайте посмотрим на каждый из подмножеств вместе с их битовыми представлениями. 000 - >> [] 001 - >> [6] 010 - >> [3] 011 - >> [3, 6] 100 - >> [4] 101 - >> [4, 6] 110 - >> [4, 3] 111 - >> [4, 3, 6]

В зависимости от ограничений проблемы, мы можем использовать растровую маску. Для E.g. В предыдущей задаче набор алфавита был ограничен размером 26. Много проблем программирования, которые имеют небольшие размеры массива и включают динамическое программирование, которое требует от вашего хэш-подмножеств, обычно представляют собой индикацию подхода на основе радушителя.

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

Давайте посмотрим на другую проблему, основанную на этой идее, прежде чем мы наконец-то обсудим звездную проблему этой статьи.

Разделский массив на K равную сумму подмножества ??

Это только один из тех вопросов, которые умоляют вас использовать битмаски. Обратите внимание на размер массива. Это всего лишь 16 элементов. Если мы рассмотрим Bitmask, чтобы представлять элементы в массиве, у нас будет 16-битное целое число в наших руках.

Это означает, что представлять все возможные подмножества данного массива, у нас было бы 2¹⁶ возможных целых чисел. Нам не нужно действительный Подвеска. Все, что нам нужно, это растратная маска, рассказывая нам элементы, принадлежащие этому подмножеству. Исходя из этой идеи, давайте посмотрим на динамический подход на основе программирования для решения вышеуказанной проблемы.

Заявление о проблеме просит нас, возможно, возможна ли разделение распределения K-равной суммы или нет. Это не просит нас вернуть действительный разбиение. Это делает проблему в целом проще.

Нам не волнует, какой раздел элемент принадлежит до тех пор, пока общая сумма раздела – это то, что total_sum/k. .

Как мы упоминались в приведенных выше абзацах, мы будем использовать ратушную массу для представления элементов массива.

Мы будем использовать отдельные битовые состояния маски для выявления того, какие цифры уже присваивали раздел, и какие все еще остаются, чтобы быть назначенным. Мы можем назначить только номер разбиению, если после добавления его к этому разделу общая сумма остается <= total_sum. /k .

Если сумма, S из всех элементов массива делится k мы должны завершить к – 1. Перегородки, поскольку последний раздел автоматически упадет на это. В случае, если общая сумма не делится по k Тогда такая равная сумма разделения невозможна.

Почему динамическое программирование?

Прежде чем посмотреть на код для этой проблемы, основываясь на идеях, которые мы обсуждали выше, важно понимать, как динамическое программирование вписывается в картину. Мы уже видели, почему рабинка пригодится. Но где именно работает динамическое программирование?

Рекурсия – это естественная пригодная для этой проблемы, так как у нас есть набор N элементы, и нам нужно разделить их в k разные подмножества, которые все из которых имеют равную сумму.

Поскольку мы на самом деле не знаем, к чему должно принадлежать номер номер, мы должны попробовать все варианты. Это означает, что для данного раздела мы пытаемся добавлять все доступные номера рекурсивно (в пределах суммы раздела) и посмотреть, какой выбор приводит нас к ответу.

Рекурсия будет основана на следующих трех переменных:

  1. то Количество разделов осталось.
  2. то маска представляющий какие элементы в оригинальном массиве неназначен.
  3. и ток Сумма раздела .

Как мы все знаем, за любые проблемы будут классифицированы как динамическая проблема программирования, она должна иметь следующие два свойства:

  1. Оптимальная субструктура ~ Что просто означает, что оригинальная задача должна быть расположена в подпроблемах и оптимальные решения для подпроблем должны использоваться таким образом, чтобы найти оптимальное решение основной проблемы.
  2. Перекрывающиеся подпункты ~ Что означает, что есть несколько рекурсивных вызовов в те же подпроблемы и избежать повторных вычислений, мы можем кэш Результаты для наших подпрублем.

Давайте посмотрим на дерево рекурсиона для этой проблемы, чтобы понять, если у нас есть какие-либо переплетемы. У нас уже есть первая собственность удовлетворена.

В приведенном выше дереве рекурсии мы рассмотрим. Это означает, что нам нужно 2 перегородки каждой суммы 18 Отказ Так как мы никогда не достигаем 18 на дереве выше, K никогда не уменьшается. Это всего лишь часть дерева рекурсии и полная версия.

Мы можем четко видеть, как повторяются две рекурсии. Мы получаем ту же маску 0011 дважды. Мы можем просто сохранить результат один раз, а затем повторно использовать его позже.

Теперь давайте посмотрим на код для на основе Bitmasking + динамическое программирование.

Теперь мы наконец готовы посмотреть на главную проблему этой статьи. Проблема, которая не так проста, в его приложении Bitmasking в качестве текущего и предыдущего.

? Найти самый короткий сверхструйный?

Как вы думаете, самый простой способ формирования суперструйный является?

Вы просто принимаете любую перестановку данных слов, и все эти перестановки будут действительными суперструнтами.

Тем не менее, вопрос не просит нас формировать любой Superstring. Это просит нас сформировать Самое короткое сверхустойчивое покрытие всех слов.

Для этой проблемы мы рассмотрим идею о цепочка слова вместе. Интуитивно, вы можете рассмотреть идею цепочки в качестве установленного союза.

Давайте рассмотрим два разных элемента, а затем посмотрите на комбинированный набор, содержащий все свои элементы вместе I.e. Союзный набор.

Как мы видим выше, всякий раз, когда мы делаем профсоюз на два набора, общие элементы появляются только один раз. Мы примем аналогичную идею с цепочкой двух слов вместе.

По сути, при цепочке слов A а также B вместе мы будем рассматривать только общую часть между Суффикс A и префикс B ровно один раз. Давайте посмотрим на схематическое представление для цепочки двух слов.

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

Мы узнаем длину суффикса A это совпадает с префиксом B где А и Б. вещаются вместе. Мы сделаем это за все пары слов в нашем данном списке. В Python, учитывая строку S мы можем использовать S [I:] Чтобы получить все суффиксы и S [: я] Чтобы получить все префиксы. Мы используем это для нашей предварительной обработки ниже.

Теперь, когда идея цепочки ясна, мы можем перейти к следующей части проблемы, где мы объясняем, почему эта проблема может быть реюрвена рекурсивно.

Почему рекурсия?

Теперь, когда мы знакомы с концепцией цепочки, мы знаем, что всякий раз, когда мы цепим два слова вместе, новое сформированное слово может быть короче, чем комбинированная длина двух оригинальных слов.

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

Важный вопрос здесь, в каком порядке слова будут цепочны вместе?

Для трех слов aabc. , HJAA , а также Чуй порядок цепочки HJAA → AABC → Это лучше чем AABC → HJAA → Так как первое дает надзорность длины 9, в отличие от 12 в последнем порядке.

Таким образом, порядок цепи определяет общую длину образовавшегося сверхугольника.

Это кажется законным, но почему динамическое программирование? ?

Вместо того, чтобы полагаться на дерево рекурсиона, чтобы объяснить потребность в динамическом программировании, мы посмотрим на пример, который объяснит ту же идею.

Предположим, нам дают набор из 5 слов [A, B, C, D, E] И мы должны сформировать кратчайший сверхструйный, который содержит все слова.

Мы уже знаем, что мы рекурсивно решаем эту проблему. Предположим, в середине нашей рекурсии мы уже сделаны, решая порядок цепочки первых 3 слов. Допустим, это упорядочение было B → A → C Отказ Теперь нам нужно узнать лучший способ цепи D и E После C, чтобы общую длину надстройки минимизировать.

Скажем, что в нашей рекурсии мы обнаружили, что C → E → D был лучшим цепочным орденом. Мы еще не знаем, если сверхстройка образована через B → A → C → E → D самый короткий. Тем не менее, мы знаем, что C → E → D является лучшим цепочным орденом для трех слов C, D и E .

Предположим, теперь мы устроили первые три слова немного по-разному в нашей рекурсии (отдельный путь рекурсии), и теперь у нас есть A → B → C и мы должны резать очередной раз Чтобы узнать лучшее договоренность для И д Отказ Тем не менее, мы уже знаем сверху лучшее договоренность C → E → D Отказ Зачем рассчитать это снова? Это где динамическое программирование входит в картину.

Это оно? Продолжай. Объяснить еще немного.

Нах, давайте теперь посмотрим на код Python, приведя все эти идеи вместе.

Ох! Где, черт возьми, во всем этом?

Хорошо, хорошо, давайте доберемся до растраски?

Давайте перефразируем проблему немного по-другому, которая будет иметь требование к битмаски очень ясно.

Учитывая набор N Элементы, нам нужно найти подходящее расположение для них, что минимизирует определенную метрику. Так как мы полагаемся на динамическое программирование, мы будем кэширование Результаты для подзоров .

Наши подгружители будут представлены подмножеством этих N элементов. Как мы видели в этой статье и особенно в предыдущих двух проблемах, трудно кэшировать подмножества, как оно есть.

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

Давайте наконец доберемся до кода для поиска кратчайшего сверхстройка, учитывая набор n слов.

  • Линия 3 ~ Начальная маска состоит из всех 1s. Это подразумевает все слова доступны для цепочки. Если маска становится 0, это означает, что ни одно слово не осталось для цепочки. Итак, длина суперстругов будет 0. Это базовый случай.
  • Линия 7 ~ Если вы обратили внимание на пример, включающий 5 слов [A, b, c, d, e] от ранее, вы знаете, что возможность кэширования возникает, когда у нас есть два слова D и E быть цепочке вместе После C. Таким образом, нам нужно знать предыдущее слово в суперструн, так как прикрепить остальные слова. Следовательно, Prev_i , который представляет индекс в исходном массиве предыдущего слова в Supertring также используется для кэширования, кроме маски.
  • Линия 12, 15 ~ Мы всегда проверяем все данные набор слов и считаем только те, как Варианты текущего шага в нашей рекурсии которые ранее не использовались. Мы используем битмаска для этого. Чтобы увидеть, использовалось ли слово или нет, мы просто проверяем, если бит в соответствующем индексе в маске несомненно или нет.
  • Линия 18 ~ Использует предварительную обработку, которую мы сделали ранее. Для слова, которое будет привязано/прикреплено к слову при индексе prev_i. Мы знаем количество перекрытия между двумя. Таким образом, если принять решение о рассмотрении слова при индексе i Как следующее слово в нашей рекурсии, количество длины добавит к нашему суперструнгу, будет Len (A [I]) - перекрывается между словами A [Prev_i] и [I]

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

Я выклоняюсь от решения всей проблемы? Конечно нет!

Функция рекурс просто возвращает длина кратчайшего суперструнда, а не суперстроительства сама. Однако заявление о проблеме просит нас выяснить кратчайший сверхструйный.

Если вы внимательно посмотрите на код, вы заметите родитель Словарь. На каждом шаге в нашей рекурсии у нас есть несколько вариантов слов, которые могут быть прикованы к текущему суперструнту (тем самым расширяя его). Мы используем родительский словарь для хранения слова на каждом шаге, который дал Лучший Ответ (кратчайшая длина).

Для данной маски – которая говорит нам, какие слова уже были церованы вместе – и данное предыдущее слово ( prev_i. ), то родитель Словарь хранит следующее слово, которое в суперструи, которое дает оптимальный ответ.

Мы будем использовать этот словарь для возврата и формировать Самый короткий сверхструйный.

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

Заключение ??

  • Операции на битах очень эффективны, и они обычно выполняются параллельно через оптимизированные инструкции на уровне системы.
  • Это сложно, но не невозможно, писать чистый, понятный код, связанный с битовыми манипуляциями.
  • А ТАКЖЕ , ИЛИ ЖЕ , а также НЕТ Три фундаментальных битовой операторы.
  • То Левый Сдвиг & l t; D правый IFT >> Операторы используются для умножения или разделения на 2.
  • То Хорна Оператор является универсальным оператором, который можно использовать во многих различных проблемах программирования. Это возвращает Правда Только когда оба бита являются противоположности и Ложь иначе.
  • Динамические программированные решения включают в себя какую-то кэширование для результатов подпор. Субпроблемы представлены набором переменных. Эти переменные не обязательно являются примитивными типами данных.
  • Bitmasking приходит в очень удобные проблемы с динамическими проблемами программирования, когда мы должны иметь дело с подмутами, и размер списка/массива небольшой. Маска (имеющая все 0s или все 1s), может представлять элементы набора и настройки или резависимости, может означать включение и исключение от набора.
  • Если массив/список/набор размер, скажем, около 20, то у вас будет 2 ⁰ возможный Bitmasks, которые выделяются почти миллион из них. Это не всегда случай, когда вы столкнетесь все этих растровых мазков в тестовом случае. Однако это максимально возможное количество.
  • Мы должны быть осторожны, когда использовать битмаски. У нас не может быть 100-битная маска, так как это будет вычислительно неразрешена.
  • Для решения, который должен иметь право на динамическое программирование, он должен удовлетворить оптимальную подструктуру и свойства перекрывающихся подпруты. Обычно через некоторую практику вы можете начать определять, будет ли проблема под рукой, будет иметь решение на основе DP или нет.

Рекомендации ?

Это была длинная статья и то, что я абсолютно любил писать. Если вы прочитали это далеко, то вы, вероятно, нашли эту статью действительно полезную. Распространите некоторую любовь, делясь как можно больше и уничтожить эту кнопку Clap! ?

О, если вы хотите прочитать подробнее таких удивительных статей, все освещенные с анимацией и красивыми, пояснительными изображениями, проверив наш недавно открытый кухня .

Поверьте мне, у нас есть несколько рецептов чмоков для губ для вас и с праздничным сезоном, я уверен, что вы будете любить их.

Желаю вам всем счастливого Рождества и счастливого нового года!