Мне понадобилось какое то время, чтобы понять верный подход к решению, делюсь подробным разбором этой задачи.
Ставится она так: есть набор (массив) элементов, чаще всего чисел, требуется найти следующей по порядку возрастания набор этих элементов.
Например, есть массив [1,2,5,3], предполагается, что функция изменит исходный массив вот так: [1,3,2,5]. Следующий вызов функции для измененного массива выдаст [1,3,5,2] и так далее, пока мы не достигнем состояния [5,3,2,1]. После этого требуется, чтобы функция вернула минимальное состояние, а именно — [1,2,3,5].
Для начала стоит понаблюдать как изменяются состояния, чтобы выдвинуть гипотезы о том, как это работает. Начнем с минимального состояния:
1 2 3 4 5 6 7 8 |
1. [1,2,3,5] 2. [1,2,5,3] 3. [1,3,2,5] 4. [1,3,5,2] 5. [1,5,2,3] 6. [1,5,3,2] 7. [2,1,3,5] ... |
Просматривая каждый раз массив с конца, мы находим место где i-й элемент меньше i + 1. Я назвал это место «точкой роста».
В этой точке нужно i-й элемент заменить на следующий по порядку, выбирая из тех, что следуют за ним. Сам i-й элемент и оставшиеся далее в хвосте сортируются по порядку возрастания.
Например, в строке 4, точка роста — это элемент 3 (i = 1). Выбирая следующий по порядку (т.е. > 3) из хвоста [5, 2], мы имеем только один вариант — элемент 5. Этот элемент переходит на позицию i = 1, и у нас остается хвост из набора [3, 2]. Их мы сортируем, выстраивая по порядку [2, 3]. Так у нас получается следующая комбинация (шаг №5) — [1, 5, 2, 3].
Сортировка нужна, чтобы получить комбинацию чисел не просто больше предыдущей, а именно ближайшую следующую для заданной.
Если логика понятна, то перейдем к коду. Вариант на TS.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// функция меняет исходный массив, // потому ничего не должна возвращать function nextPermutation(nums: number[]): void { // сначала мы находим "точку роста" let i = nums.length - 2; while (i >= 0 && nums[i] >= nums[i + 1]) i--; // если точка не найдена, это значит, что // мы имеем макс. комбинацию и нужно // выполнить реверс массива, чтобы перейти к минимуму if (i < 0) { nums.reverse() return; } // ищем следующий по порядку элемент в хвосте после // точки роста let best = i + 1, minDelta = nums[best] - nums[i], next = best + 1; while (next < nums.length) { const newDelta = nums[next] - nums[i]; // кандидат должен быть больше, чем элемент точки роста, // но при этом меньше предыдущего кандидата. if (newDelta > 0 && minDelta > newDelta) { minDelta = newDelta best = next; } next ++; } // меняем местами элементы точки роста и // лучшего найденного кандидата [nums[i], nums[best]] = [nums[best], nums[i]]; // сортируем хвост const tail = nums.slice(i + 1).sort((a, b) => a - b); nums.splice(i + 1, Number.MAX_SAFE_INTEGER, ...tail); }; |