Блог "Школы программной инженерии"
Алгоритмы

Как решать задачи на программирование

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

Универсальный подход к решению задач

Этот метод изложен в книге «Как решать задачу» Дьёрдя Пойа. Первое издание вышло еще в 1945 году, было продано больше миллиона экземпляров. (На русском языке книга публиковалась как пособие для учителей еще в 1959 году. — Прим. перев.).
Метод Пойа используют многие программисты, от профессоров информатики (см. курс «Intro to CS» на Udacity, который ведет профессор Дэвид Эванс) до преподавателей современной веб-разработки вроде Кольта Стила.
Давайте пройдемся по решению простой задачи на программирование с применением метода Пойа. Это позволит увидеть работу метода на практике и в результате лучше разобраться в нем. Для примера будем использовать язык JavaScript.
Итак, задача:
Создайте функцию, которая будет складывать два числа и возвращать их сумму.
Вот четыре шага по решению любой задачи:
  1. Понять задачу.
  2. Разработать план.
  3. Осуществить план.
  4. Оглянуться назад.
«Во-первых, мы должны понять задачу; мы должны ясно видеть, что в ней является искомым. Во-вторых, мы должны усмотреть, как связаны друг с другом различные элементы задачи, как неизвестное связано с данными. Это необходимо, чтобы получить представление о решении, чтобы составить план. В-третьих, мы осуществляем наш план. В-четвертых, оглядываясь назад на полученное решение, мы вновь изучаем и анализируем его», — «Как решать задачу», 1959 г.
Давайте сделаем первый шаг.

Шаг 1: понять задачу

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

Каковы здесь входящие данные?

Какого рода input-ы следует ожидать? В нашем примере это аргументы, принимаемые функцией.
Читая текст задачи, мы понимаем, что входящими данными будут числа. Если подходить к делу более скрупулезно, мы можем спросить:
  • всегда ли будет только два числа?
  • что будет, если функция получит в качестве входящих данных три числа?
Эти вопросы мы можем задать интервьюеру или поискать ответ в описании задачи. (К задаче может прилагаться примечание типа «Исходите из того, что в функцию всегда будут передаваться только два числа». Конечно, это сразу все прояснит).
Можно задаться и другими вопросами. Всегда ли входящими данными будут числа? Что должна делать функция, если получит в качестве аргументов «a» и «b»? Уточните, всегда ли input будет числовым.
В качестве варианта — можно записать вероятные входящие данные в комментарии к коду, чтобы иметь представление о том, как они должны выглядеть.
// inputs: 2, 4
Далее следует спросить себя:

Каковы должны быть результаты?

Что функция будет возвращать? В нашем случае это должно быть оно число — сумма двух чисел, переданных в качестве аргументов. Убедитесь, что вы хорошо понимаете, каким должен быть output.

Придумайте простые примеры

Разобравшись в сути задачи и зная вероятные input-ы и output-ы, можно начать работать над конкретными примерами.
Примеры также могут использоваться для тестирования вашего решения. На техническом собеседовании или при подготовке к нему на сайтах вроде Codewars или HackerRank используются специальные редакторы. Большинство из них содержат уже готовые примеры или test cases. Несмотря на это, написание собственных примеров может помочь вам упрочить понимание задачи.
Начните с написания одного-двух простых примеров.
Давайте вернемся к нашей складывающей функции. Назовем ее «add».
Каким может быть input? Ну, допустим, таким:
// add(2, 3)
Каким будет результат при таких входящих данных? Записать это можно так:
// add(2, 3) ---> 5
Это показывает, что наша функция принимает в качестве input 2 и 3, а как output возвращает 5.

Придумайте сложные примеры

Продумывая более сложные примеры, вы можете уделить время поиску крайних случаев, которые стоит учесть.
Например, что будет, если наши входящие данные будут не числами, а строками? Что, если мы получим в качестве аргументов две строки, например, add(‘a’, ‘b’)?
Ваш интервьюер может сказать вам вернуть сообщение об ошибке, если входящих данных не будет или если они будут не того типа. В таком случае вы можете добавить соответствующий комментарий — в качестве напоминания себе.
// вернуть error, если input-ы - не числа.
Как вариант, интервьюер может сказать, что входящие данные всегда будут числами. В таком случае вам не понадобится писать никакой дополнительный код для обработки этого крайнего случая.
Если вы не на собеседовании, а просто решаете задачу, в ней может быть обозначено, что должно происходить при вводе невалидных данных.
Например, в задаче может говориться «При отсутствии input-а верните undefined». В таком случае можно написать комментарий:
// Проверить, если ли входящие данные.
// Если входящих данных нет, вернуть undefined.
В нашем случае мы будем исходить из того, что input всегда будет числовым. Но в общем всегда лучше подумать о крайних случаях.
Профессор информатики Эванс советует писать т. н. безопасный код. Всегда думайте о том, что может пойти не по плану и как защитить свой код от возможных ошибок.
Прежде чем перейти ко второму шагу, давайте кратко повторим, что нужно сделать на первом шаге:
  • прочитать задачу
  • выяснить, какими будут входящие данные
  • выяснить, каким должен быть результат
  • создать простые примеры, затем — более сложные.

Шаг 2: разработать план решения задачи

Далее нужно составить план того, как вы, собственно, собираетесь решать задачу. Придумав план, запишите его в виде псевдокода.
Псевдокод — это изложенные простым языком шаги алгоритма. Иными словами, это пошаговый план решения задачи.
Опишите каждый этап решения. Если задача сложная, этапов может быть много. Если говорить о нашей задаче, мы можем написать:
// Создать переменную sum.
// Сложить первый input со вторым, используя оператор сложения.
// Сохранить значение суммы input-ов в переменной sum.
// Вернуть переменную sum в качестве output.
Теперь у вас есть четырехэтапный план решения задачи.
Для более сложных задач профессор Эванс советует: «Полагайтесь на то, как задачи решаются людьми». То есть, забудьте на мгновение о том, как задачу может решить код, и подумайте о том, как ее решали бы вы — человек. Это может помочь вам более четко увидеть нужные шаги.

3. Шаг 3: осуществить план (решить задачу!)

Следующий шаг — собственно решение задачи. Руководствуясь псевдокодом, напишите настоящий код.
Профессор Эванс рекомендует сфокусироваться на простом, механическом решении. Чем проще и легче ваше решение, тем вероятнее, что вы напишете код правильно.
Если взять наш псевдокод, мы можем написать следующее:

function add(a, b) {

   const sum = a + b;

   return sum;

}

Помните, что не следует преждевременно оптимизировать код. Написав решение, вы можете подумать, что ваш код неэффективный, и захотеть его улучшить. Но для начала создайте простое, механическое решение.
Что, если вы не можете решить задачу полностью? Например, если вы так и не нашли решения для какой-то ее части? Кольт Стил советует в таких ситуациях игнорировать эту сложную часть. Сконцентрируйтесь на всем остальном — том, что вы уже можете написать. Когда напишете, возвращайтесь к части, которая вызвала у вас затруднения.

Шаг 4: оглянуться назад

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

function add(a, b) {

   return a + b;

}

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

Итоги

В этой статье мы рассмотрели четырехэтапную стратегию решения задач на программирование. Давайте повторим:
  • Шаг 1: понять задачу
  • Шаг2: создать пошаговый план решения
  • Шаг 3: реализовать план и написать код решения
  • Шаг 4: оглянуться назад и по возможности улучшить решение.
Применение этого подхода очень помогло мне при прохождении технических собеседований, да и вообще в моей работе.
Если вы не чувствуете себя уверенно, решая задачи, помните, что умение решать задачи — это навык. Чем больше практики, тем лучше вам это будет удаваться.
Источник