문제 설명
하노이 탑(Tower of Hanoi)은 퍼즐의 일종입니다. 세 개의 기둥과 이 기동에 꽂을 수 있는 크기가 다양한 원판들이 있고, 퍼즐을 시작하기 전에는 한 기둥에 원판들이 작은 것이 위에 있도록 순서대로 쌓여 있습니다. 게임의 목적은 다음 두 가지 조건을 만족시키면서, 한 기둥에 꽂힌 원판들을 그 순서 그대로 다른 기둥으로 옮겨서 다시 쌓는 것입니다.
한 번에 하나의 원판만 옮길 수 있습니다.
큰 원판이 작은 원판 위에 있어서는 안됩니다.
하노이 탑의 세 개의 기둥을 왼쪽 부터 1번, 2번, 3번이라고 하겠습니다. 1번에는 n개의 원판이 있고 이 n개의 원판을 3번 원판으로 최소 횟수로 옮기려고 합니다.
1번 기둥에 있는 원판의 개수 n이 매개변수로 주어질 때, n개의 원판을 3번 원판으로 최소로 옮기는 방법을 return하는 solution를 완성해주세요.
https://school.programmers.co.kr/learn/courses/30/lessons/12946
제한 사항
- n은 15이하의 자연수 입니다.
풀이
유명한 문제이니 풀이법을 대충은 알고 있었다.
우선 문제를 간단히 만들어보면 다음과 같다.
- n-1개의 원판을 1→2로 보낸다.
- 가장 큰 원판을 1→3으로 보낸다.
- 2에 있던 n-1개의 원판들을 2→3으로 보낸다.
그렇게 때문에 바로 이전 단계의 움직임만 알고 있다면 이번 단계를 쉽게 구할 수 있다.
전체 코드
#include <string>
#include <vector>
using namespace std;
enum
{
ONE_TWO,
ONE_THREE,
TWO_ONE,
TWO_THREE,
THREE_ONE,
THREE_TWO
};
vector<vector<int>> solution(int n) {
vector<vector<int>> answer;
vector<vector<vector<vector<int>>>> moved(n+1, vector<vector<vector<int>>>(6));
moved[1][ONE_TWO].push_back({1,2});
moved[1][ONE_THREE].push_back({1,3});
moved[1][TWO_ONE].push_back({2,1});
moved[1][TWO_THREE].push_back({2,3});
moved[1][THREE_ONE].push_back({3,1});
moved[1][THREE_TWO].push_back({3,2});
for(int i = 2; i <= n; i++)
{
//ONE_TWO
vector<vector<int>> oneTwo;
oneTwo = moved[i-1][ONE_THREE];
oneTwo.push_back({1,2});
oneTwo.insert(oneTwo.end(), moved[i-1][THREE_TWO].begin(), moved[i-1][THREE_TWO].end());
moved[i][ONE_TWO] = oneTwo;
//ONE_THREE
vector<vector<int>> oneThree;
oneThree = moved[i-1][ONE_TWO];
oneThree.push_back({1,3});
oneThree.insert(oneThree.end(), moved[i-1][TWO_THREE].begin(), moved[i-1][TWO_THREE].end());
moved[i][ONE_THREE] = oneThree;
//TWO_ONE
vector<vector<int>> twoOne;
twoOne = moved[i-1][TWO_THREE];
twoOne.push_back({2,1});
twoOne.insert(twoOne.end(), moved[i-1][THREE_ONE].begin(), moved[i-1][THREE_ONE].end());
moved[i][TWO_ONE] = twoOne;
//TWO_THREE
vector<vector<int>> twoThree;
twoThree = moved[i-1][TWO_ONE];
twoThree.push_back({2,3});
twoThree.insert(twoThree.end(), moved[i-1][ONE_THREE].begin(), moved[i-1][ONE_THREE].end());
moved[i][TWO_THREE] = twoThree;
//THREE_ONE
vector<vector<int>> threeOne;
threeOne = moved[i-1][THREE_TWO];
threeOne.push_back({3,1});
threeOne.insert(threeOne.end(), moved[i-1][TWO_ONE].begin(), moved[i-1][TWO_ONE].end());
moved[i][THREE_ONE] = threeOne;
//THREE_TWO
vector<vector<int>> threeTwo;
threeTwo = moved[i-1][THREE_ONE];
threeTwo.push_back({3,2});
threeTwo.insert(threeTwo.end(), moved[i-1][ONE_TWO].begin(), moved[i-1][ONE_TWO].end());
moved[i][THREE_TWO] = threeTwo;
}
answer = moved[n][ONE_THREE];
return answer;
}
또 다른 풀이
위와 같이 문제를 풀긴 했지만 시간과 메모리가 마음에 들지 않았다.
다른 풀이가 분명 존재한다고 생각하여 찾아봤다.
재귀를 이용하여 문제를 풀 수 있는 것을 알아냈다.
접근법은 비슷하다.
하지만 나는 모든 움직임을 기록하였지만 재귀를 이용하여 필요한 경우만 구해낼 수 있었다.
이때, 움직임은 총 세 가지 경우이다.
- 출발지에서 도착지로 움직임
- 출발지에서 나머지 기둥으로 움직임
- 나머지 기둥에서 도착지로 움직임
이때, 나머지 기둥은 6-start-dest로 구할 수 있다.
1, 2, 3을 모두 더하면 6이고 출발지와 도착지를 빼면 남은 기둥이 되기 때문이다.
또 다른 풀이 전체 코드
#include <string>
#include <vector>
using namespace std;
void HanoiTower(vector<vector<int>>& answer, int n, int start, int dest) {
if (n == 1) {
answer.push_back({ start, dest });
return;
}
HanoiTower(answer, n - 1, start, 6 - start - dest);
answer.push_back({ start, dest });
HanoiTower(answer, n - 1, 6 - start - dest, dest);
}
vector<vector<int>> solution(int n) {
vector<vector<int>> answer;
HanoiTower(answer, n, 1, 3);
return answer;
}