Algorithm - fillingBlocks(n)

2 분 소요

Problem

  • n * 4 의 직사각형을 2 * 1 혹은 1 * 2의 직사각형으로 채워야 할때, 채울 수 있는 방법의 수는 총 몇 가지 인지를 리턴하는 함수입니다.
    • if n==1: 1 * 4 인 직사각형은 2 * 1을 가로로 두 번 채우는 것 밖에 방법이 없음 ==> 1
    • elif n==2:
      • if 왼쪽 위를 가로로 채웠을 경우 => 왼쪽 아래도 무조건 가로로 채워져야 함
        • if 오른쪽 위를 가로로 채웠을 경우 => 1
        • elif 오른쪽 첫번째를 세로로 채웠을 경우 => 1
      • elif 왼쪽을 세로로 채웠을 경우 =>
        • if 다시 세로로 하나 더 채울 경우 => 오른쪽 2 * 2가 남으며 이 경우는 2가지
        • else 가로로 위를 하나 채우는 경우 => 1
    • elif n==3: …
  • 이런 식으로 반복되도록 프로그래밍할 수 있습니다.

solution

  • 따라서, 아래처럼 간단하게 recursive하게 코드를 만들 수 있다.
  • if n> 3일 때는 다음처럼 케이스를 나누었는데
    • event row: 왼쪽 위에 가로로 하나 채울 경우,
    • event col: 왼쪽을 세로로 하나 채울 경우, 중요한 것은 not event row 일 경우 event col이라는 것이다. 왼쪽 위를 채울 수 있는 방법은 가로로 채우거나, 세로로 채우거나 일 방법밖에 없음
      • event col col row : 왼쪽을 세로로 두 개 가로로 하나 채우는 경우
      • event col row col : 세로로 하나 가로로 하나 세로로 하나 채우는 경우
      • event col col col col : 세로로 네 개를 연속으로 채우는 경우
      • 위 세 가지 event의 합집합은 event col이 된다.
  • dynamic programming에서 중요한 것은 서로 exclusive한 event를 찾는 것이고, 개별 event들이 촘촘하게 엮여서, 빠지는 경우의 수가 없도록 잘 정리하는 것이다.
def fillingBlocks(n):
    if n ==0:
        return 0
    elif n ==1:
        return 1
    elif n==2:
        return 5
    else:
        row = fillingBlocks0011(n-1) # 왼쪽 위에 가로로 넣었을 때, 
        col_col_row = fillingBlocks0011(n-2) # 왼쪽부터 세로로 두 개, 가로로 하나 넣었을 경우, 
        col_row_col = fillingBlocks0110(n-2)
        # 왼쪽부터 세로로 하나, 가로로 하나, 세로로 하나 넣었을 때, 뿔처럼 위에 두 칸만 비는 경우 
        col_col_col_col = fillingBlocks(n-2) # 세로로 제일 위 두 줄을 채웠을 때 
        return row + col_col_row + col_row_col + col_col_col_col
  • 아무튼 아래 함수에서 빠지는 경우의 수가 없도록 잘 정리했으니 이제 각각의 경우를 표현한 함수를 정리해보장
  • fillingBlocks0110(n)_--_ 의 형태를 채울 수 있는 가지 수를 의미한다.
    • case1) 튀어나와 있는 부분을 가로로 채우는 경우 ==> fillingBlocks(n)
    • case2) 튀어나와 있는 부부을 세로로 채우는 경우 ==> fillingBlocks0110(n-2)
def fillingBlocks0110(n):
    if n==0:
        return 1
    elif n==1:
        return 1
    else:
        return fillingBlocks(n)+fillingBlocks0110(n-2)
  • fillingBlocks0011(n)__-- 의 형태를 채울 수 있는 가지 수를 의미한다.
    • case1) 튀어나와 있는 부분을 가로로 채우는 경우 ==> fillingBlocks(n)
    • case2) 튀어나와 있는 부부을 세로로 채우는 경우 ==> fillingBlocks0011(n-1)
def fillingBlocks0011(n):
    if n==0:
        return 1
    else:
        return fillingBlocks(n)+fillingBlocks0011(n-1)

make it iterative

  • 지난 번에 말한 것처럼 recursive하게 만들었을 경우 계산 시간이 오래 걸린다.
    • if n==17, recursive는 4.5초, iterative는 0.0001
  • 따라서, 값이 새로 생성되면 이를 dictionary에 저장을 하여, 함수 콜을 하지 않고 바로 값을 가져오도록 처리하도록 하자.
def fillingBlocks(n):
    b0011_d = {0:1}
    b0110_d = {0:1, 1:1}
    b_d = {0:0, 1:1, 2:5}
    for i in range(1, n+1):
        if i==1:
            b_d[i]=1
        elif i==2:
            b_d[i]=5
        else:
            if i-2 in b0011_d.keys():
                a = b0011_d[i-2]
            else:
                b0011_d[i-2] = b_d[i-2] + b0011_d[i-3]
                a = b0011_d[i-2]
            if i-1 in b0011_d.keys():
                b = b0011_d[i-1]
            else:
                b0011_d[i-1] = b_d[i-1] + b0011_d[i-2]
                b = b0011_d[i-1]
            if i-2 in b0110_d.keys():
                c = b0110_d[i-2]
            else:
                b0110_d[i-2] = b_d[i-2] + b0110_d[i-4]
                c = b0110_d[i-2]
            if i-2 in b_d.keys():
                d = b_d[i-2]
            else:
                b_d[i-2] = b0011_d[i-4]+b0011_d[i-3] + b0110_d[i-3]+b_d[i-4]
                d = b_d[i-2]
            b_d[i] = a+b+c+d
    return b_d[n]

댓글남기기