코딩테스트/프로그래머스

[Java] 프로그래머스 Level 2 : 하노이의 탑

이덩우 2023. 6. 26. 12:59

- 문제설명 & 제한사항

- 예시

- 해결과정

  • 작은 문제에서 큰 문제를 해결해나가도록 재귀함수를 호출하자.
    1. n개의 원반을 목적지까지 옮기기 위해서는 n-1개의 원반을 먼저 경유지로 옮긴다.
    2. 남아있는 가장 큰 원반을 목적지로 옮긴다.
    3. 경유지에 있는 n-1개의 원반을 목적지로 옮긴다.
  • 재귀 호출의 흐름을 파악하려고 하면 이 문제는 상당히 복잡해진다. 큰 흐름만 살펴보자.
  • 1) 원반이 1개인 경우, 즉 가장 작은 상황부터, 이동을 마치고 2개, 3개, 4개, 5개로 확장해서 생각해보자.
    2) 1개의 원반만 있으므로 바로 목적지로 옮길 수 있다. 현재 재귀호출을 종료하고 상위 재귀호출(원반2개)로 돌아간다.
    3) 이전 재귀호출의 입장(원반 1개 이동)에서는 목적지로 원반을 옮긴 것이지만, 현재 재귀호출(원반2개) 입장에서는 n-1개의 원반을 경유지로 옮긴것이다. 이후 남아있는 가장 큰 원반을 목적지로 옮기고, 경유지에 있는 1개의 원반을 목적지로 옮기게 된다.
    4) 2개를 모두 목적지까지 옮기고나서 상위 재귀호출(원반3개)로 넘어가게 되면, 2개(n-1개)를 옮긴것은 경유지로 옮긴것이다. 그럼 현재 재귀호출 입장에서는 남아있는 가장 큰 원반을 목적지로 옮기고 다시 경유지에 있는 2개의 원반을 목적지로 옮기게 되고 현재 재귀호출을 종료하게 된다.
    5) 또 다시 상위 재귀호출(원반4개)로 돌아가보면, 현재까지 목적지로 옮긴 3개의 원반은 경유지로 옮긴것이다.
    6) 반복
  • 작은 원반 위에 큰 원반이 오지 않도록 부분 문제들을 설계했기에, 해당 재귀를 거치면 원하는 결과를 얻을 수 있다.  

- 솔루션

import java.util.*;

class Solution {
    
    ArrayList<int[]> list = new ArrayList<>();
    
    public int[][] solution(int n) { 
        move(n, 1, 3, 2);
        return list.toArray(new int[list.size()][2]);
    }
    
    private void move(int n, int start, int end, int except) {
        if(n == 1) {
           list.add(new int[]{start, end});
           return;     // 남아있는 가장 큰 원반 목적지로 옮기기
        }
        else {
            move(n-1, start, except, end);  // n-1개 경유지로 옮기기

            move(1, start, end, except);   // 바로 위 재귀가 끝나면 상위 재귀 입장에서는 가장 큰 원반이
						// 남아있으므로 해당 원반을 목적지로 이동시킨다.
                        
            move(n-1, except, end, start); // 경유지에 있는 원반을 목적지로 이동시킨다.
        }
    }
    
}

- 배운점

  • 핵심은 재귀 함수의 흐름을 추적하며 코드를 작성하는 것이 아닙니다.
  • 그저 명확한 종료조건, 수행해야 할 동작을 오류 없이 설계하는 것이 중요합니다.