Также известно, что при рекурсивном обходе узла за узлом у нас будет доступ только к конкретному узлу и к следующему за ним. Это признак общего паттерна, применяемого при работе с рекурсивными функциями – нужно уметь ориентироваться в более широком контексте.
Для этого во многих рекурсивных функциях предусмотрены дополнительные аргументы, действующие в качестве флагов в тех ситуациях, когда на предыдущем или последующем уровне выполняются определённые условия.
В контексте поставленной задачи давайте продумаем каждую ситуацию, в которой может оказаться узел, и как она соотносится с фактом потенциального дублирования конкретного значения:
•Недублирующийся узел указывает на дублирующийся узел
•Дублирующийся узел указывает на дублирующийся узел
•Недублирующийся узел указывает на дублирующийся узел
•Дублирующийся узел указывает на недублирующийся узел
На первый взгляд кажется, что эта задача легко решаема при помощи рекурсивной функции – нужно только обеспечить, чтобы она дошла прямым ходом до конца списка, не пропуская никакого состояния на вышестоящие уровни. Таким образом, первый из вышеперечисленных случаев пропускаем. Во втором случае перезаписываем указатель актуального узла. Третий случай вновь пропускаем. В четвёртом случае ещё раз перезаписываем.
Но задумайтесь, что случится, если вы достигнете конечного (nil), имея строку дублей. Допустим, мы работаем с 2 -> 3 -> 3 -> nil, давайте разберём вышеприведённые случаи узел за узлом:
2 -> 3
2 не равно 3, поэтому здесь мы никаких изменений в узел вносить не будем и вновь вызовем функцию, на этот раз передав ей в качестве аргумента узел '3'.
3 -> 3
a b
Здесь значения равны, и нам нужно убрать дубли. К счастью, поскольку в связных списках содержатся указатели, мы можем изменить исходный список, даже не обращаясь напрямую к головному узлу.
В данном случае мы можем перезаписать указатель 'a', так, чтобы он стал 'b' – в сущности, удалив 'a'. Теперь снова вызываем функцию, на этот раз передав ей узел 'b'.
3 -> nil
b
Как только мы достигнем
nil
, имея дубль, нам придётся попытаться перезаписать указатель, но мы не сможем — Go выдаст панику, поскольку нельзя присвоить указателю nil. Поэтому нам понадобится реализовать определённую логику, которая позволяла бы перехватывать ситуацию до возникновения паники, и просто возвращать nil, а не перезаписывать с заменой на nil. Но, если мы вернём nil, то всплывём на уровень вверх. Так мы не только потеряем контекст, позволявший судить, является ли данное значение дублем, но и не сможем изменять значение выше по рекурсивной цепочке, поскольку мы не меняли указатель напрямую.Вот что можно сделать для решения этой проблемы: мы можем не только полагаться на флаг, сигнализирующий о дубле и посылаемый вниз по пути с рекурсивным изменением. Вдобавок давайте создадим флаг, сигнализирующий, не всплыли ли мы вверх с того уровня, где было дублирующееся значение, смежное с
nil
. Таким образом, у нас сохранится уловка, позволяющая добраться до nil
, и вместе с тем мы сможем безопасно перезаписывать указатели. А если бы мы достигли уровня, имея этот флаг со значением true, мы переключаем узлы и устанавливаем его в false – так сообщается, что дубля в конце у нас уже нет.📌Решение
@golang_interview