Ежедневная часть (e) C++ # 44, Общая проблема интервью C++: качающееся движение

Сегодня мы рассмотрим распространенный вопрос на собеседовании по C++: раскачивающееся движение.

Учитывая начальную позицию и пункт назначения (положительные целые координаты), определите, можно ли достичь пункта назначения с помощью качающегося движения. То есть мы можем перейти из позиции {x,y} либо в {x+y,y}, либо в {x,x+y}.

Прежде чем продолжить чтение решения, я рекомендую вам попробовать решить его самостоятельно. Вот ссылка на Compiler Explorer с парой тестов: https://compiler-explorer.com/z/TTshG4Ph3.

Решение

Основное наблюдение для этой проблемы состоит в том, что операция «качания» полностью обратима. Координата назначения {x,y} может быть достигнута только из {x-y,y} или {x,y-x}. Кроме того, поскольку наши координаты — положительные целые числа, возможно только одно из двух направлений.

Из-за этой обратимости для любого пункта назначения существует только один возможный обратный путь, ведущий от (к) этому пункту назначения. Поэтому мы можем проследить этот путь, проверяя, достигаем ли мы в какой-либо точке нашей исходной координаты.

Этот подход, однако, имеет неприятную проблему, связанную с линейностью (рассмотрите случай {1,1}→{MAX_INT, 1}).

К счастью, есть операция по модулю, которую мы можем использовать вместо многократного применения минусовой операции.

Однако нам нужно быть осторожными. Рассмотрим случай: {7,15}→{7,29}. Если мы прямо применим операцию по модулю, мы можем пропустить наш пункт назначения, перейдя от {7,29} прямо к {7,1}. Примечательно, что это происходит только тогда, когда одна из координат уже верна.

struct Coord {
    unsigned x;
    unsigned y;
    friend auto operator <=>(const Coord&, const Coord&) = default;
};

bool is_reachable(Coord src, Coord dst) {
    // We overshot, meaning that the destination is not reachable.
    if (src.x > dst.x || src.y > dst.y)
        return false;
    // We hit the source, the destination is reachable.
    if (src == dst)
        return true;
    if (dst.x > dst.y) {
        if (src.y != dst.y)
            // recurse
            return is_reachable(src, {dst.x % dst.y, dst.y});
        else
            // make sure we don't skip over src.x
            return (dst.x % dst.y) == (src.x % dst.y);
    } else {
        if (src.x != dst.x)
            // recurse
            return is_reachable(src, {dst.x, dst.y % dst.x});
        else
            // make sure we don't skip over src.y
            return (dst.y % dst.x) == (src.y % dst.x);
    }
}

Откройте решение в Compiler Explorer.