Автор оригинала: FreeCodeCamp Community Member.
Николой Отасевич
Несмотря на значительный опыт, наращивающие программные продукты, многие инженеры ощущают джитрия при мысли о том, чтобы пройти через кодирующее интервью, которое фокусируется на алгоритмах. Я опросил сотни инженеров в Refdash , Google, и при запуске я был частью, а некоторые из наиболее распространенных вопросов, которые делают инженеры, недобросовестные, являются теми, которые включают в себя динамическое программирование (DP).
Многие технические компании любят задавать вопросы DP в своих интервью. Хотя мы можем обсудить, эффективно ли они в оценке чьей-либо способности выполнять в инженерной роли, DP продолжает быть областью, которая поезжает инженеры на пути к поиску работы, которую они любят.
Динамическое программирование – предсказуемое и претенденнее
Одна из причин, по которой я лично считаю, что вопросы DP могут не быть лучшим способом тестирования инженерных способностей заключается в том, что они предсказуемыми и простыми в шаблон. Они позволяют нам фильтровать гораздо больше для готовности в отличие от инженерных способностей.
Эти вопросы обычно кажутся довольно сложными снаружи и могут создать впечатление, что человек, который решает их, очень хорош в алгоритмах. Точно так же люди, которые не могут быть в состоянии преодолеть некоторые концепции скручивания ума DP, могут показаться довольно слабыми в своих знаниях алгоритмов.
Реальность другая, и самый большой фактор в их производительности является готовность. Итак, давайте убедитесь, что все готовы к этому. Однажды и на всегда.
7 шагов, чтобы решить проблему динамической программирования
В остальной части этого поста я пойду над рецептом, который вы можете следовать, чтобы понять, если проблема является «проблемой DP», а также выяснить решение для такой проблемы. В частности, я пройду через следующие шаги:
- Как распознать проблему DP
- Определите переменные проблемы
- Четко выразить связь рецидива
- Определите базовые случаи
- Решить, хотите ли вы реализовать его итеративно или рекурсивно
- Добавить мемузацию
- Определить сложность времени
Образец задачи DP
С целью наличия примера для абстракций, которые я собираюсь сделать, позвольте мне представить образец задачи. В каждом из разделов я буду ссылаться на проблему, но вы также можете прочитать разделы независимо от проблемы.
Постановка задачи:
В этой проблеме мы на сумасшедшем прыжке, пытаясь остановиться, избегая шипов по пути.
Вот правила:
1) Вам дают плоскую взлетно-посадочную полосу с кучей шипов. ВПП представлена логическим массивом, который указывает, является ли особая (дискретная) пятно о шипах. Это верно для ясных и ложных для неясных.
Примерное представление массива:
2) Вам дают начальную скорость S. S – неотрицательное целое число в любой заданной точке, и она указывает на то, сколько вы будете двигаться вперед со следующим прыжком.
3) Каждый раз, когда вы приземлитесь на месте, вы можете настроить скорость до 1 единицы до следующего прыжка.
4) Вы хотите безопасно остановиться в любом месте вдоль взлетно-посадочной полосы (не нужно быть в конце массива). Вы останавливаетесь, когда скорость становится 0. Однако, если вы приземлитесь на шип в любой момент, ваш сумасшедший прыгающий мяч вспышки и это игра.
Выход вашей функции должен быть логическим, указывающим, можем ли мы безопасно остановиться в любом месте вдоль взлетно-посадочной полосы.
Шаг 1: Как распознать динамическую проблему программирования
Во-первых, давайте посмотрим, что DP по существу, просто метод оптимизации. DP – это способ решения проблем, нарушив их в коллекцию более простых субпроблем, решение каждого из этих подпроблем только один раз и хранение их решений. В следующий раз происходит одна и та же подполе, вместо того, чтобы пересматривать его решение, вы просто смотрите ранее вычисленное решение. Это экономит время вычисления за счет (надежда) скромных расходов в пространстве хранения.
Признавая, что проблема может быть решена с использованием DP, является первым и часто самым сложным шагом в ее решении. То, что вы хотите задать себе, является ли ваша проблема решения в качестве функции решений аналогичных меньших проблем.
В случае нашей примерной проблемы, учитывая точку на взлетно-посадочной полосе, скорости и взлетно-посадочной полосы, мы могли бы определить места, где мы могли бы потенциально прыгать дальше. Кроме того, кажется, что мы можем остановиться с текущей точки с текущей скоростью, зависит только от того, могли ли мы остановиться с точки зрения того, мы решили перейти к следующему.
Это отличное, потому что, двигаясь вперед, мы сокращаем взлетно-посадочную полосу вперед и сделаем нашу проблему меньше. Мы должны быть в состоянии повторить этот процесс до тех пор, пока не доберемся до точки, где очевидно, можем ли мы остановиться.
Шаг 2: Определите переменные задачи
Теперь мы установили, что существует некоторая рекурсивная структура между нашими подпроблемами. Затем нам нужно выразить проблему с точки зрения параметров функций и увидеть, какие из этих параметров меняются.
Как правило, в интервью у вас будет один или два изменяющих параметры, но технически это может быть любое число. Классическим примером однометражной задачи параметров является «определить номер N-го фибоначчи». Такой пример для двух изменяющихся задачи параметров является «вычислять расстояние между строками». Если вы не знакомы с этими проблемами, не беспокойтесь об этом.
Способ определения количества изменяющихся параметров состоит в том, чтобы перечислить примеры нескольких подпроблем и сравните параметры. Подсчет количества изменяющихся параметров ценен для определения количества подпортем, которые мы должны решить. Также важно само по себе, помогая нам укрепить понимание отношения рецидива от шага 1.
В нашем примере два параметра, которые могут измениться для каждой подзоли:
- Положение массива (P)
- Скорость (ы)
Можно сказать, что взлетно-посадочная полоса впереди также меняется, но это было бы избыточным, учитывая, что вся не изменяющая взлетно-посадочная полоса и позиция (P) несут эту информацию уже.
Теперь, с этими 2 изменяющими параметрами и другими статическими параметрами, у нас есть полное описание наших подпроставленных проблем.
Шаг 3: Четко выразить отношение рецидива
Это важный шаг, который многие бросаются, чтобы попасть в кодирование. Выражение отношения рецидива как можно четко укрепит вашу проблему понимания и сделать все остальное значительно проще.
После того, как вы выясните, что отношение рецидива существует, и вы указываете проблемы с точки зрения параметров, это должно прийти как натуральный шаг. Как проблемы относятся друг к другу? Другими словами, давайте предположим, что вы вычислили подобраны. Как бы вы вычислили основную проблему?
Вот как мы думаем об этом в нашей проблеме образца:
Поскольку вы можете настроить скорость до 1 до 1 до перехода на следующую позицию, существует только 3 возможных скорости, а следовательно, 3 пятна, в которых мы могли бы быть рядом.
Более формально, если наша скорость S, позиция P, мы могли бы пойти от (S, P) к:
- (S, P + S) ; # Если мы не изменим скорость
- (S – 1, P + S – 1) ; # Если мы изменим скорость на -1
- (S + 1, P + S + 1) ; # Если мы изменим скорость на +1
Если мы сможем найти способ остановиться в любой из подпроблем выше, то мы также можем остановиться от (S, P). Это потому, что мы можем перейти от (S, P) к любому из вышеперечисленных трех вариантов.
Обычно это тонкий уровень понимания проблемы (простое английское объяснение), но вы иногда можете также захотеть выразить отношение математически. Давайте назовем функцию, которую мы пытаемся вычислить CanStop. Потом:
Canstop (S, (S, P + S) || Canstop (S – 1, P + S – 1) || Canstop (S + 1, P + S + 1)
Woohoo, похоже, у нас есть наше рецидивовое отношение!
Шаг 4: Определите базовые случаи
Базовый чехол представляет собой подгруппу, которая не зависит от любой другой подпорблемы. Чтобы найти такие подпраты, вы обычно хотите попробовать несколько примеров, посмотрите, как ваша проблема упрощает в меньших подпроблемах и идентифицировать в какой момент его нельзя упростить дальше.
Причина, по которой проблема не может быть упрощена, заключается в том, что один из параметров станет значением, которое невозможно, чтобы Ограничения проблемы.
В нашей примерной проблеме у нас есть два изменения параметров, S и P. Давайте подумаем о том, какие возможные значения S и P не может быть законным:
- P должен быть в пределах данной взлетно-посадочной полосы
- P не может быть таким, что взлетно-посадочная полоса [P] неверна, потому что это будет означать, что мы стоим на шиповке
- S не может быть отрицательным, и A указывает, что мы закончили
Иногда это может быть немного сложным для преобразования утверждений, которые мы принимаем о параметрах в программируемые базовые случаи. Это связано с тем, что в дополнение к перечислению утверждений, если вы хотите заставить свой код выглядеть кратко и не проверять ненужные условия, вам также нужно подумать о том, какие из этих условий даже возможно.
В нашем примере:
- Р <0 P ВПП кажется правильной, чтобы сделать. Альтернативой может быть рассмотреть M Aking P
- взлетно-посадочная полоса базовый чехол. Тем не менее, возможно, что проблема расщепляется в подпроблему, которая выходит за пределы конца взлетно-посадочной полосы, поэтому нам действительно нужно проверить неравенство. Это кажется довольно очевидно. Мы можем просто проверить Если взлетно-посадочная полоса [P] неверна
- Отказ Подобно # 1, мы могли бы просто проверить S <0 и S. Тем не менее, здесь мы можем повести причину, чтобы быть невозможным быть <0, потому что уменьшается с помощью максимума 1, поэтому ему придется перейти через Scept… эпорова S – это достаточный базовый корпус для параметра S.
Шаг 5: Решите, хотите ли вы реализовать его итеративно или рекурсивно
То, как мы говорили о шагах до сих пор, могут привести вас к тому, что мы должны рекурсивно реализовать проблему. Тем не менее, все, о чем мы говорили до сих пор, совершенно агностики, чтобы вы решили реализовать проблему рекурсивно или итеративно. В обоих подходах вам придется определить соотношение рецидива и базовые случаи.
Решить, следует ли идти итеративно или рекурсивно, вы хотите тщательно подумать о компромистах .
Проблемы переполнения стека, как правило, выключатель сделки И причина, по которой вы не захотите иметь рекурсию в (Backend) производственной системе. Однако для целей интервью, если вы упоминаете компромиссы, вы обычно должны быть в порядке с любой из реализаций. Вы должны чувствовать себя комфортно, реализующие оба.
В нашей конкретной проблеме я реализовал оба версиях. Вот код Python для этого: Рекурсивное решение: (оригинальные фрагменты кода можно найти здесь )
Итеративное решение: (оригинальные фрагменты кода можно найти здесь )
Шаг 6: Добавить мемузацию
Мемузаризация Это техника, которая тесно связана с ДП. Он используется для хранения результатов дорогих вызовов функций и возврата кэшированного результата, когда те же происходят те же входы.
Почему мы добавляем мемузацию нашу рекурсию? Мы сталкиваемся с теми же подпругими, которые без воспоминания вычисляются неоднократно. Эти повторы очень часто приводят к экспоненциальным временным сложности.
В рекурсивных решениях добавление мемузаризации должно чувствовать себя простой. Посмотрим, почему. Помните, что воспоминания – это просто кэш результатов функций. Есть времена, когда вы хотите отклониться от этого определения, чтобы выжать некоторые незначительные оптимизации, но лечение памяти в качестве функционального кэша результата является наиболее интуитивным способом для его реализации.
Это означает, что вы должны:
- Храните свою функцию привести к вашей памяти перед каждым Возвращение утверждение
- Посмотрите на память для результата функции, прежде чем начать делать какие-либо другие вычисления
Вот код сверху с добавленной памятью (добавленные линии выделены): (Оригинальные фрагменты кода можно найти здесь )
Чтобы проиллюстрировать эффективность воспоминания и различных подходов, давайте сделаем несколько быстрых тестов. Я буду стремиться к тестированию всех трех методов, которые мы видели до сих пор. Вот настроек:
- Я создал взлетно-посадочную полосу длиной 1000 с шипами в случайных местах (я решил иметь вероятность того, что на наличии в любом месте было 20%)
- initspeed
- Я провел все функции 10 раз и измерял среднее время выполнения
Вот результаты (в считанные секунды):
Вы можете видеть, что чистый рекурсивный подход принимает около 500 раза больше времени, чем итеративный подход и около 1300 раз больше времени, чем рекурсивный подход с воспоминаниями. Обратите внимание, что это несоответствие быстро растут с длиной взлетно-посадочной полосы. Я призываю вас попробовать его самостоятельно.
Шаг 7: Определить сложность времени
Есть несколько простых правил, которые могут сделать вычислительную сложность динамической программирования проблемы намного проще. Вот два шага, которые вам нужно сделать:
- Подсчитайте количество состояний – это будет зависеть от количества изменяющихся параметров в вашей проблеме
- Подумайте о работе, выполненной за каждое государство. Другими словами, если все остальное, но одно государство было вычислено, сколько работы вы должны сделать, чтобы вычислить это последнее состояние?
В нашей примерной проблеме количество состояний является | P |. * | S |, куда
- P – это набор всех позиций (| p | указывает количество элементов в p)
- S – набор всех скоростей
Работа, выполненная за каждое государство, является O (1) в этой проблеме, потому что, учитывая все другие государства, нам просто нужно посмотреть на 3 подблемы для определения полученного состояния.
Как мы отмечали в коде ранее, | S | Ограничено длиной взлетно-посадочной полосы (| p |), поэтому мы могли бы сказать, что количество состояний – | p | ², потому что работа, выполняемая на каждое состояние, составляет O (1), то общая сложность времени является O (| P | ²).
Однако кажется, что | S | Может быть дополнительно ограничен, потому что если бы это было действительно | P |, очень ясно, что остановка не будет возможно, потому что вам придется прыгать на длину всей взлетно-посадочной полосы на первом ходу.
Итак, давайте посмотрим, как мы можем положить более жесткий границ | S |. Давайте позвоним максимальной скорости S. Предположим, что мы начиная с позиции 0. Как быстро мы могли остановиться, если мы пытались остановиться как можно скорее, и если мы игнорируем потенциальные шипы?
В первой итерации нам придется прийти хотя бы до такой степени (S – 1), путем регулировки нашей скорости на нулю на -1. Оттуда мы бы по минимальному проходу (S-2) шагов вперед и так далее.
Для взлетно-посадочной полосы Длина л следующее должно держать:
=> (S-1) + (S-2) + (S-3) + …. + 1 => S * (S-1)/2 => S 2 – S – 2L <0. Если вы найдете корни вышеуказанной функции, они будут: R1/2 + SQRT (1/4 + 2L) и/2 – SQRT (1/4 + 2L) Мы можем написать наше неравенство как: (S – R1) * (S – R2) < ; 0. Учитывая, что S – R2> 0 для любого S> 0 и L> 0, нам нужно следующее: S – 1/2 – SQRT (1/4 + 2L) < ; 0. => S <1/2 + SQRT (1/4 + 2L) Это максимальная скорость, которую мы могли бы иметь на взлетно-посадочной полосе длины Л. Если бы у нас была скорость выше, чем мы не смогли остановиться даже теоретически, независимо от позиции шипов. Это означает, что общая сложность времени зависит только от длины взлетно-посадочной полосы L в следующей форме: O (l * sqrt (l)), который лучше, чем o (l²) Потрясающе, вы пробили его!:) 7 шагов, которые мы проходили, должны предоставить вам рамки для систематически решения любых динамических проблем программирования. Я настоятельно рекомендую практиковать этот подход к еще нескольким проблемам, чтобы освободить ваш подход. Когда вы чувствуете, что вы покорили эти идеи, проверим Refdash Откуда вы интервью у старшего инженера и получаете подробную обратную связь о вашем кодировании, алгоритмам и дизайну системы. Первоначально опубликовано Refdash blog . REFDASH – это платформа для интервьюирования, которая помогает инженерам собетать анонимно с опытными инженерами из лучших компаний, таких как Google, Facebook, или Palantir, и получить подробный обратную связь. Refdash. Также помогает инженерам открывать удивительные возможности работы на основе их навыков и интересов.Вот несколько следующих шагов, которые вы можете взять
Оригинал: “https://www.freecodecamp.org/news/follow-these-steps-to-solve-any-dynamic-programming-interview-problem-cc98e508cd0e/”