Навеяно комментарием под постом Фибоначчи на собеседовании. Пользователь pavellyzhin упомянул следующую задачу на собеседовании (комментарий):
Лучшее с чего можно начать — просмотреть глазами первые N чисел Фибоначчи и попытаться обнаружить закономерность в расположении чётных чисел.
В последовательности отмечены чётные числа, как можно заметить каждое 3-е число Фибоначчи — чётное и, наверное, все чётные числа стоят на позициях кратных 3. Это и будет наша догадка, теперь нам нужно её подтвердить и выработать алгоритм вычисления.
Лучшее доказательство — простое, поэтому спасибо пользователю AllexIn за простую идею, которую я первоначально упустил. Каждое последующее число Фибоначчи представляет собой сумму двух предыдущих, если два предыдущих числа — нечётные, то следующее будет чётным, если в двух предыдущих числах одно нечётное, а другое чётное, то следующее будет нечётным. В принципе одной этой идеи уже достаточно, чтобы «интуитивно нащупать» доказываемый факт. Доказательство по индукции очевидное, но не могу удержаться, чтобы не привести его
Мы доказываем, что «каждое третье число Фибоначчи — чётное, а два предшествующих каждому такому числу — нечётные».
Вот так можно доказать нашу догадку даже не прибегая к математическим выкладкам. Можно формализовать эту идею, заодно получив формулу для вычисления каждого третьего числа Фибоначчи
из
и
. Используя рекурентное соотношение
получим:
Таким образом, если
— чётное, то
также чётное в силу этой формулы. Учитывая, что
, то каждое третье число Фибоначчи действительно чётное, что подтверждает часть нашей догадки. Тут необходимо убедиться, что мы не пропускаем других чётных чисел, т.е. что они все имеют номера кратные 3. Спасибо пользователю janatem за его простую идею, ведь из приведенной формулы для
также следует, что если
— нечётное, то
также нечётное, таким образом получаем, что числа с номерами
— нечётные (по индукции, начинаем с
— нечётных), а
— чётные, что покрывает все числа Фибоначчи, а значит мы действительно покрываем всё чётные числа.
Есть ещё способ, как можно было бы показать, что других чётных чисел нет. Предположим, что существует число
— чётное и
, тогда
или
, где
— какое-то натуральное.
Обратимся к матричному представлению чисел Фибоначчи из оригинального поста
Вычисляя определитель обеих частей, получим
Отсюда следует, что делители чисел
и
совпадают с делителями
, т.е. соседние числа Фибоначчи взаимнопросты. Это же означает, что взаимнопросты и числа
и
, т.е.
и
. Но по предположению
— чётное, а
— чётное по доказанному ранее. Таким образом других чётных чисел, кроме
, где
в последовательности Фибоначчи не существует. А также мы установили интересный факт, что соседние числа Фибоначчи взаимнопросты.
Напоследок покажем как минимум ещё один способ доказать нашу догадку — воспользоваться теоремой Люка.
Теорема Люка. Целое число делит
, и
, тогда и только тогда, когда оно является делителем
, где
, в частности
Тут
— наибольший общий делитель. Если положить
, то
. Если
— чётное, то левая часть равна 2, тогда чтобы правая часть тоже равнялась 2 необходимо, чтобы
делилось на 3 и в то же время верно и обратное. Таким образом получаем именно то, что мы и хотели доказать.
Итак, каждое третье число Фибоначчи — чётное, а каждое чётное имеет номер кратный трём. Мы тщательно доказали это и никто не смеет в этом теперь усомниться.
А теперь осталось придумать алгоритм. Можно конечно поступить, как изначально сделал pavellyzhin, но вместо того чтобы проверять
, мы можем проверить
, вот это поворот! Правда это никак не влияет на число итераций алгоритма, ведь мы просто изменили фильтр последовательности. Лучше сразу генерировать подпоследовательность чисел Фибоначчи с нужным нам свойством (чётность), поэтому другой очевидный способ это воспользоваться формулой Бине
Тут имеются свои сложности с эффективностью вычислений, подробней об этом сказано в оригинальном посте. Поэтому предлагаю лучший, на мой взгляд, способ — итеративное вычисление
, это можно сделать, как мы ранее показали, по формуле
. Чтобы построить итеративный переход в алгоритме, нам понадобится вычислять
, тут всё так же просто
Кстати, вообще говоря несложно доказать, что
Тогда формально алгоритм записывается следующим образом (текущее чётное число Фибоначчи
, следующее за ним число Фибоначчи
,
— верхняя граница для чисел, заданная в условии задачи):
Алгоритм достаточно тривиален — мы просто перепрыгиваем на каждое третье число Фибоначчи и выводим его (если оно не больше
). Сложность алгоритма всё так же линейна, но число повторений шага 2 в точности равняется числу чётных чисел Фибоначчи в диапазоне
, для сравнения простой алгоритм с фильтрацией требует в 3 раза больше итераций (на каждое найденное будет приходиться 2 отброшенных).
Алгоритм существует на бумаге, на чём там было собеседование, PHP? Чтож расчехляем напоследок PHP значит
Примечание: Этот способ всегда можно улучшить, как, например, показал пользователь hunroll. Идея в том, что для вычислений нам не нужно ничего лишнего, кроме уже частично полученного результата, т.е. мы можем вычислять чётные числа используя только предыдущие чётные числа Фибоначчи.
Мы упомянули тут теорему Люка, которая гласит, что
. Как следствие из неё мы можем получить, что число Фибоначчи
кратно числу
, тогда и только тогда, когда его номер
кратен номеру
. Т.е. каждое 4-е число Фибоначчи делится на 3, каждое 5-е на 5, каждое 6-е на 8 и т.д.
Тогда несложным образом получаем алгоритм вычисления подпоследовательности Фибоначчи, в которой элементы кратны какому-то заданному числу
. Используя тот факт, что
Предыдущий алгоритм превращается в
Где
можно задать как константы.
Обобщение примечания. Как справедливо заметил пользователь ignorance, в обобщенном случае также можно построить алгоритм, который использует только числа из частичного результата.
Докажем, это методом математической индукции, при t = 1 и t = 2 выполнение очевидно. Пусть выполненно вплоть до некоторого t, тогда
Задачка конечно полностью синтетическая, количество итераций очень мало (для
ответ содержит всего 6 чисел, т.е. выполнено было 6 итераций, а первоначальному алгоритму «в лоб» потребовалось бы 18), но таким образом юзернейм, который открыл для себя тут в этом что-то новое сможет теперь показать чуть более глубокое математическое знание в числах Фибоначчи на собеседовании.
Edit: Спасибо пользователям janatem и AllexIn за простые доказательства, я включил их в «Теоретизируем», а также hunroll за алгоритм использующий в вычислениях только уже полученные чётные числа и пользователю ignorance за его обобщение.
Больше года назад откликнулся на вакансию «php-программист», прислали ТЗ и там было задание с Фибоначчи: выбрать все четные числа Фибоначчи в диапазоне от 1 до 10000. Решил с помощью цикла(for). Еще там нужно было SQL-запрос составить на выборку ближайших дней рождений пользователей, что-то сверстать, точно не помню и какую-то функцию написать. Все сделал, отправил. Прислали ответ: «по итогам тестового задания Вы не приняты». Что конкретно им не понравилось так и не написали. Вот сейчас сижу и думаю, наверное все-таки из-за Фибоначчи пролетел… :)В данном посте я собираюсь показать как можно было решить эту задачу эффектно, а может даже и эффективно, но это не точно. Заодно продемонстрирую парочку из тысяч доказанных про числа Фибоначчи фактов.
Теоретизируем
Лучшее с чего можно начать — просмотреть глазами первые N чисел Фибоначчи и попытаться обнаружить закономерность в расположении чётных чисел.
В последовательности отмечены чётные числа, как можно заметить каждое 3-е число Фибоначчи — чётное и, наверное, все чётные числа стоят на позициях кратных 3. Это и будет наша догадка, теперь нам нужно её подтвердить и выработать алгоритм вычисления.
Лучшее доказательство — простое, поэтому спасибо пользователю AllexIn за простую идею, которую я первоначально упустил. Каждое последующее число Фибоначчи представляет собой сумму двух предыдущих, если два предыдущих числа — нечётные, то следующее будет чётным, если в двух предыдущих числах одно нечётное, а другое чётное, то следующее будет нечётным. В принципе одной этой идеи уже достаточно, чтобы «интуитивно нащупать» доказываемый факт. Доказательство по индукции очевидное, но не могу удержаться, чтобы не привести его
Мы доказываем, что «каждое третье число Фибоначчи — чётное, а два предшествующих каждому такому числу — нечётные».
- База индукции. Первые два числа Фиббоначи
— нечётные, третье число
— чётное
- Гипотеза. Пусть вплоть до некоторого кратного по номеру 3 числа Фибоначчи выполнено, что каждое третье — чётное, а два предшествующих ему — нечётные.
- Шаг индукции. Следующее за последним чётным из гипотезы числом является нечётным, т.к. оно получается из суммы нечётного с чётным, следующее за уже этим числом также нечётное, т.к. оно получается из суммы чётного с нечётным, а уже следующее за ним — чётное, т.к. только что полученные два предыдущих для него — нечётные, по построению его номер кратен 3, оно чётное, а два предшествующих ему — нечётные.
Вот так можно доказать нашу догадку даже не прибегая к математическим выкладкам. Можно формализовать эту идею, заодно получив формулу для вычисления каждого третьего числа Фибоначчи
Таким образом, если
Есть ещё способ, как можно было бы показать, что других чётных чисел нет. Предположим, что существует число
Обратимся к матричному представлению чисел Фибоначчи из оригинального поста
Вычисляя определитель обеих частей, получим
Отсюда следует, что делители чисел
Напоследок покажем как минимум ещё один способ доказать нашу догадку — воспользоваться теоремой Люка.
Теорема Люка. Целое число делит
Тут
Итак, каждое третье число Фибоначчи — чётное, а каждое чётное имеет номер кратный трём. Мы тщательно доказали это и никто не смеет в этом теперь усомниться.
Алгоритм
А теперь осталось придумать алгоритм. Можно конечно поступить, как изначально сделал pavellyzhin, но вместо того чтобы проверять
Тут имеются свои сложности с эффективностью вычислений, подробней об этом сказано в оригинальном посте. Поэтому предлагаю лучший, на мой взгляд, способ — итеративное вычисление
Кстати, вообще говоря несложно доказать, что
Тогда формально алгоритм записывается следующим образом (текущее чётное число Фибоначчи
- Если
, то закончить, иначе добавить в результат
, перейти на шаг 2.
Алгоритм достаточно тривиален — мы просто перепрыгиваем на каждое третье число Фибоначчи и выводим его (если оно не больше
Алгоритм существует на бумаге, на чём там было собеседование, PHP? Чтож расчехляем напоследок PHP значит
function evenfibs($ubound) {
$result = [];
[$evenf, $nextf] = [2, 3];
while($evenf <= $ubound) {
array_push($result, $evenf);
[$nextf, $evenf] = [
3 * $nextf + 2 * $evenf,
2 * $nextf + $evenf];
}
return $result;
}
Примечание: Этот способ всегда можно улучшить, как, например, показал пользователь hunroll. Идея в том, что для вычислений нам не нужно ничего лишнего, кроме уже частично полученного результата, т.е. мы можем вычислять чётные числа используя только предыдущие чётные числа Фибоначчи.
function evenfibs($ubound) {
if($ubound < 2) return [];
$evens = [2];
$next = 8;
for($i = 1; $next <= $ubound; $i++) {
$evens[$i] = $next;
$next = $evens[$i]*4 + $evens[$i-1];
}
return $evens;
}
Обобщение
Мы упомянули тут теорему Люка, которая гласит, что
Тогда несложным образом получаем алгоритм вычисления подпоследовательности Фибоначчи, в которой элементы кратны какому-то заданному числу
Предыдущий алгоритм превращается в
- Если
, то закончить, иначе добавить в результат
, перейти на шаг 2.
Где
Обобщение примечания. Как справедливо заметил пользователь ignorance, в обобщенном случае также можно построить алгоритм, который использует только числа из частичного результата.
Докажем, это методом математической индукции, при t = 1 и t = 2 выполнение очевидно. Пусть выполненно вплоть до некоторого t, тогда
Что-то вроде итога
Задачка конечно полностью синтетическая, количество итераций очень мало (для
Edit: Спасибо пользователям janatem и AllexIn за простые доказательства, я включил их в «Теоретизируем», а также hunroll за алгоритм использующий в вычислениях только уже полученные чётные числа и пользователю ignorance за его обобщение.