Sad Puppy 3 '코딩테스트/문제 풀이 - Python' 카테고리의 글 목록 (2 Page) :: 개발자 아지트

https://www.acmicpc.net/problem/16918

 

문제 해결 방법

1. 시간이 1초씩 감에 따라서 봄버맨의 행동과 폭탄의 상태가 달라지니 시간을 변수로 둔다. 

2. 봄버맨의 움직임 패턴을 분석한다. 

종이에 쓰인 까만색깔 숫자는 시간 초를 의미한다. 

봄버맨은의 행동 패턴은 1초 단위로 다음과 같이 흐른다.

 

설치

->유지(현재 놓여진 폭탄에 대해서만 시간초를 매김=시간증가)

->설치와 동시에 현재 놓여진 폭탄도 시간초를매김 == 모든 좌표값을 +1 해준다.

->폭발

 

위 패턴을 폭탄 패턴이라 하겠다. 

 

그런데 이게 2초 부터는 폭탄이 설치 되어있지 않은 모든 칸에 또 폭탄을 설치하기 때문에, 2초부터 또다른 폭탄 패턴이 진행된다. 

 

따라서 0초부터 N초 까지는 직사각형 격자판에 2개의 폭탄 패턴이 돌고있다고 보면된다. 

 

이 2개의 서로 다른 초에 시작한 폭탄 패턴을 어떻게 패턴화 할까?

 

종이에 쓰인 폭탄 패턴을 0초, 1초는 제외해두고 2초부터 N초까지 보면,

 

증가와 설치, 폭발과 유지, 설치와 증가, 유지와 폭발 이 네가지 패턴이 반복됨을 알 수 있다. 

(유지와 폭발, 폭발과 유지는 폭발->유지로 친다. 왜냐하면 폭발이 우선순위이기 때문이다. )

 

이 네가지 패턴이 등장하는 숫자를 따로 모아보면, 

 

n%4 ==3, n%4 ==1, 이외의 경우로 정리할 수 있다. 

이렇게 정리된 경우를 잘 고려해 출력까지 잘 해주면 된다. 

 

코드 구현

틀린 코드

더보기
# 크기 RxC 격자 
# 폭탄칸 3초 지난후 폭발 => 폭탄 칸 파괴되어 빈칸됨 => 인접한 네 칸도 파괴됨
# 만약 폭탄 폭발시 인접 네칸에 폭탄이 있는 경우, 해당 폭탄은 폭발없이 파괴됨 = 연쇄반응X


# 1. 일부 칸에 폭탄 설치 = 모든 폭탄이 설치된 시간은 같음 0초
# 2. 다음 1초 동안 아무것도 하지 않음
# 3. 다음 1초 동안 폭탄이 설치되지 않은 모든 칸에 폭탄 설치=모든 칸은 폭탄을 가짐 =동시에 설치
# 4. 1초 지난 후, 3초 전에 설치된 폭탄이 모두 폭발
# 5. 3-4 반복
import sys

input= sys.stdin.readline # 뒤에 괄호 붙이지 않도록 조심.

dy=[0, 0, 1, -1]
dx=[1, -1, 0, 0]


r, c, n = map(int, input().split()) 

bombMap = []
for i in range(r):
    tmp = list(input().strip())
    bombMap.append(tmp)

print(bombMap)

time = 0
check=0
for i in range(r):
    for j in range(c):
        if bombMap[i][j] == '.':
            bombMap[i][j]=0
        else:
            bombMap[i][j]=1
print('초기상태')
print('check', check)
print('time: ', time)
for i in range(len(bombMap)):
    print('check', bombMap[i])
print()

realzero=1


while 1:
    
    time+=1
    check+=1
    
    if check==1:
        if realzero==1:
            realzero=0
            for i in range(r):
                for j in range(c):
                    if bombMap[i][j] > 0:
                        bombMap[i][j]+=1
        elif realzero ==0:
            for i in range(r):
                for j in range(c):
                    bombMap[i][j]+=1
    else:
        if check ==2 :
            for i in range(r):
                for j in range(c):
                    bombMap[i][j]+=1

            rem = [item[:] for item in bombMap]  
            if time >=5:
                for i in range(r):
                    for j in range(c):
                        if rem[i][j]==3:
                            bombMap[i][j]=0
                            for k in range(4):
                                ni = i + dy[k]
                                nj = j + dx[k] 
                                if  0<=ni<r and 0<=nj<c: 
                                    bombMap[ni][nj]=0
        elif check ==3:
            for i in range(r):
                for j in range(c):
                    if rem[i][j]==3:
                        bombMap[i][j]=0
                        for k in range(4):
                            ni = i + dy[k]
                            nj = j + dx[k] 
                            if  0<=ni<r and 0<=nj<c: 
                                bombMap[ni][nj]=0

    if time >= n:
        print('check', check)
        print('time: ', time)
        for i in range(len(bombMap)):
            print('hello', bombMap[i])
        print()
        for i in range(r):
            tmpS=''
            for j in range(c):
                if bombMap[i][j]>0:
                    s='O'
                    tmpS+=s
                else:
                    s='.'
                    tmpS+=s
            print(tmpS)
        break

    rem = [item[:] for item in bombMap]  
    
    print('check', check)
    print('time: ', time)
    for i in range(len(bombMap)):
        print('hello', bombMap[i])
    print()

    if check ==3:
        check=1

 

정답 코드

# 크기 RxC 격자 
# 폭탄칸 3초 지난후 폭발 => 폭탄 칸 파괴되어 빈칸됨 => 인접한 네 칸도 파괴됨
# 만약 폭탄 폭발시 인접 네칸에 폭탄이 있는 경우, 해당 폭탄은 폭발없이 파괴됨 = 연쇄반응X

# 1. 일부 칸에 폭탄 설치 = 모든 폭탄이 설치된 시간은 같음 0초
# 2. 다음 1초 동안 아무것도 하지 않음
# 3. 다음 1초 동안 폭탄이 설치되지 않은 모든 칸에 폭탄 설치=모든 칸은 폭탄을 가짐 =동시에 설치
# 4. 1초 지난 후, 3초 전에 설치된 폭탄이 모두 폭발
# 5. 3-4 반복
import sys
input= sys.stdin.readline # 뒤에 괄호 붙이지 않도록 조심.

dy=[0, 0, 1, -1]
dx=[1, -1, 0, 0]

r, c, n = map(int, input().split()) 

bombMap = []
for i in range(r):
    tmp = list(input().strip())
    bombMap.append(tmp)

time = 0
for i in range(r):
    for j in range(c):
        if bombMap[i][j] == '.':
            bombMap[i][j]=0
        else:
            bombMap[i][j]=1

# 유지 time ==1
time+=1
for i in range(r):
    for j in range(c):
        if bombMap[i][j] > 0:
            bombMap[i][j]+=1

if n==1:
    if time == n:
        for i in range(r):
            tmpS=''
            for j in range(c):
                if bombMap[i][j]>0:
                    s='O'
                    tmpS+=s
                else:
                    s='.'
                    tmpS+=s
            print(tmpS)

else:
    while 1:
        time+=1
        
        if time % 4==1:
            # 유지
            for i in range(r):
                for j in range(c):
                    if bombMap[i][j] > 0:
                        bombMap[i][j]+=1
            rem = [item[:] for item in bombMap]  
            # 폭발
            for i in range(r):
                for j in range(c):
                    if rem[i][j]==3:
                        bombMap[i][j]=0
                        for k in range(4):
                            ni = i + dy[k]
                            nj = j + dx[k] 
                            if  0<=ni<r and 0<=nj<c: 
                                bombMap[ni][nj]=0
        elif time % 4 == 3:
            # 폭발
            rem = [item[:] for item in bombMap]  
            for i in range(r):
                for j in range(c):
                    if rem[i][j]==3:
                        bombMap[i][j]=0
                        for k in range(4):
                            ni = i + dy[k]
                            nj = j + dx[k] 
                            if  0<=ni<r and 0<=nj<c: 
                                bombMap[ni][nj]=0
        else:
            # 설치, 증가
            for i in range(r):
                    for j in range(c):
                        bombMap[i][j]+=1


        if time == n:
            for i in range(r):
                tmpS=''
                for j in range(c):
                    if bombMap[i][j]>0:
                        s='O'
                        tmpS+=s
                    else:
                        s='.'
                        tmpS+=s
                print(tmpS)
            break

 

시간/공간 복잡도

 

시뮬레이션 문제 같아서 아예 시간복잡도 계산을 배제하고 문제를 풀었다. 

 

 

최적화 및 개선

  • 따로 하지않음

 

어려웠던 점

  • 이 문제는 문제를 풀 때 단순하게 생각하면 안된다. 
  • 시간을 보내는것과 보내는 중일때 할 일을 잘 구분해야한다. 
  • 처음에 폭탄 패턴이 두개 돌거란 생각을 하지 못했다. 따라서 문제 자체가 이해 안되고, 심지어 문제에 문제가 있는거 아닌가 하는 생각을 했었다. 항상 무조건 진행 과정에 대해서 헷깔리거나 파악이 안될때 종이에 해당 과정을 손으로 쓰면서 따라가보는게 중요한 것 같다. 
  • 규칙을 찾아야한다. 규칙을 찾는게 제일 중요한 것 같다. 

 

알게된 것

  • sys모듈의 sys.stdin.readLine는 코드로 작성할 때 괄호를 붙이면 안된다. 
  • sys.stdin.readLine을 사용할때, 뒤에 개행문자가 따라붙는다. 따라서 사용시 주의해줘야한다. 

 

개행문자를 제거하는 함수 사용시 참고할 부분이다. 

  • strip()이랑 rstrip() 무슨차이인가?
    • .strip()과 .rstrip()은 모두 문자열의 양 끝에서 공백 문자를 제거하는 메서드이지만, 제거하는 위치가 다름
      • .strip(): 문자열의 앞과 뒤 양쪽에 있는 모든 공백 문자를 제거한다.
      • .rstrip(): 문자열의 오른쪽(뒤쪽) 끝에 있는 공백 문자만 제거한다.

https://www.acmicpc.net/problem/20440

 

 

 

문제 해결 방법

 

1. 시간을 key로 모기수를 value로 한 딕셔너리를 만든다. 

2. key를 통해 딕셔너리를 정렬한 후, 순회하면서 현재 들어온 모기 수랑 그동안 순회하며 들어온 최대 모기 수를 갱신한다. 최대 모기 수를 갱신하는 중에는 flag변수를 통해 계속 인지하고있는다. 또한 최대 모기 수를 갱신하기 시작한 시간을 표시해준다. 

3. 최대 모기 수를 유지하다가 모기가 나가기 시작하면 최대 모기 수는 깨진다. 따라서 이때 flag변수를 통해 다시 이를 인지할 수 있도록 값을 바꿔주고, 그동안 최대 모기 수를 유지했던 시간을 표시해준다. 

4. 출력

 

코드 구현

시간 초과된 코드

더보기
import sys

n=int(sys.stdin.readline())
total=[]
minn=2100000000
maxx=0

for i in range(n):
    # 입장시간, 퇴장시간
    e, x = map(int, sys.stdin.readline().split()) 
    #print(e, x)
    if e<minn:
        minn=e
    
    if x>maxx:
        maxx=x

    total.append([e,x-1])

#print(minn, maxx)

total 

tmp=[]
for i in range(maxx):
    tmp.append(0)

for i in total:
    s, e = i
    for i in range(s, e+1):
        tmp[i]+=1

#print(tmp)

check = 0
start = -1
end = -1

result = []

cnt=0

for i, value in enumerate(tmp):
    if value == max(tmp):
        if start != -1:
            check=1
            continue
        else:
            #최초로 넣을때 
            check=1
            start = i
            result.append(i)
            cnt+=1
            start=-1
            

    else:
        if check==1:
            check=0
            end=i
            result.append(i)
            end = -1

#print(result)

print(max(tmp))


origin = result[0]
originEnd = 0
originStart=0
for i, value in enumerate(result):
    if i ==0:
        originStart=value
        continue

    if origin +1 == value:
        origin+=1
    else:
        originEnd=result[i-1]
        break

if originEnd==0:
    originEnd=result[-1]

    
print(originStart, originEnd)

정답 코드

 

(주석 있는 버전)

import sys
from collections import defaultdict
input = sys.stdin.readline

n = int(input())
d = defaultdict(int)
print('defaultdict', d)
for i in range(n):
    s, e = map(int, input().split())
    d[s] += 1
    d[e] -= 1




m, cnt = 0, 0 # cnt는 모기 마릿수 m은 최대갱신 모기 마릿수 
tem, txm = 0, 0# 최댓값 갱신시 시간과 갱신 해제시 시간
flag = False
for i in sorted(d.keys()):
    print(i)
    cnt += d[i]
    # 기존 최대 모기수 보다 현재 모기수가 더 클때
    # == 최고일때만 계산함 
    if cnt > m:
        m = cnt
        tem = i
        flag = True
    # 현재 모기수 cnt보다 최대 갱신 모기수가 더 크고
    # 만약, 현재 값이 모기가 나가는 타이밍일때
    # 현재 모기수에서 지금 시간의 모기를 빼면 음수값이라 더한 격이 되어
    # 지금 나간 모기를 더했을때의 값 = 최대값이 m인 경우이다. 
    # FLAG가 TRUE인 상태 
    # 마찬가지로 최고자리가 박탈 됐을때 계산함 
    elif cnt < m and cnt - d[i] == m and flag:
        txm = i
        flag = False
print(m)
print(tem, txm)

 

(주석 없는 버전) - 변수명 변경됨

import sys
from collections import defaultdict
input = sys.stdin.readline

n=int(input())
dict = defaultdict(int)

for i in range(n):
    # 입장시간, 퇴장시간
    e, x = map(int, input().split()) 

    dict[e] += 1
    dict[x] -= 1
    

maxx, nowMos = 0, 0
startTime, endTime = 0, 0
flag = False
for i in sorted(dict.keys()):
    nowMos += dict[i]

    if nowMos > maxx:
        maxx = nowMos
        startTime = i
        flag = True
    elif  nowMos < maxx and nowMos - dict[i] == maxx and flag:
        flag = False
        endTime = i

print(maxx)
print(startTime, endTime)

 

시간/공간 복잡도

 

입력값보다 입력값의 값의 범위가 워낙 커서 웬만하면 N으로 구현해야 했다. 

 

 

최적화 및 개선

 

  • 기존에는 입력값의 범위를 전부 순회하면서 최대 모기수를 체크해서 시간초과가 발생했었다. 
    • 이런 방식의 관점을 바꿨다. 
    • => 현재 방에 있는 모기의 수가 갱신이 되는 모기의 수일 경우에만 체크 

 

어려웠던 점

시간복잡도에 대한 개념은 알고있지만, 여전히 문제 풀기전에 시간복잡도에 대한 계산을 하는것이 어렵다. 

파이썬의 경우 1초에 최대 연산횟수가 logN인 알고리즘으로 계산해도 최대 연산 횟수가 10억번인데, 이 문제의 경우 값의 범위가 10억을 훌쩍넘어 20억쯤 된다. 이런 경우는 시간복잡도 계산을 어떻게 해야하는지 모르겠다. 

 

 

알게된 것

  • sys모듈의 sys.stdin.readLine() 함수를 input 변수로 저장 할 생각을 못하고 계속 수기로 작성해왔었는데 이제는 변수로 저장할 줄 알게됐다. 
  • 딕셔너리 자료구조가 이런경우에 유용함을 다시 상기시킬수 있었다. 

 

'코딩테스트 > 문제 풀이 - Python' 카테고리의 다른 글

[백준2110] 공유기 설치  (0) 2024.08.06
[백준16918] 봄버맨  (0) 2024.08.05
[백준18808] 스티커 붙이기  (0) 2024.08.01
[백준19637] IF문 좀 대신 써줘  (0) 2024.07.30
[백준12933] 오리  (0) 2024.07.24

https://www.acmicpc.net/problem/18808

 

 

링크 참고

 

문제 해결 방법

시간복잡도를 고려햐여 문제를 잘 읽고 그대로 구현하면 됨.

아래의 조건을 고려하여 코드를 작성하였음.

 

  • 노트북에 스티커를 붙이기전에 모눈종이의 크기만으로 노트북에 들어갈 수 있는지 확인한다. 
    • 모눈종이의 행이 노트북의 행의 크기를 넘거나 모눈종이의 열이 노트북의 열의 크기를 넘는 경우, 모눈종이의 행과 열을 뒤집었을때(90도 회전한다고 가정했을때)도 노트북의 범위를 벗어나는지 확인한다. 
      • 범위를 벗어나지 않는다면 90도 회전
      • 범위를 벗어난다면 다음 스티커로 넘어간다. 
  • 노트북에 모눈종이의 크기가 들어갈 수 있을때, 노트북의 빈곳에 스티커를 붙이는 시도를 한다. 
  • 노트북에 붙여진 스티커의 현황과 스티커는 겹쳐도 되지만, 붙이려는 스티커의 빈칸이 노트북에 붙여진 스티커 일부를 지워서는 안된다.(모눈종이 전체를 붙이려고 할 경우 주의)
  • 노트북에 스티커를 붙일때 회전을 먼저 해서는 안되고 일단 생긴 그대로 붙이려는 시도를 반드시 해야한다. 
    • 그 후 90도 회전을 3번 할 수 있다. (단, 90, 180, 270 순으로 진행해야함)
    • 3번의 회전이 끝나면 더이상의 회전은 의미가 없으므로, 스티커를 버린다.(다음 스티커로 넘어간다. )

 

코드 구현

 

시간초과난 코드 1

더보기
import sys, copy
# 스티커 각 칸은 상하좌우로 모두 연결되어있어야함 = 어디 끊기면 안됨
# 모눈종이에 스티커가 없는 불필요한 행이나 열이 있으면 정상아님

# 다른 스티커와 겹침X, 노트북 벗어나면X, 
# 노트북의 위쪽부터 채움(우선순위 = 1. 가장 위쪽의 위치
# , 위쪽에 여러곳일 경우2. 가장 왼쪽) 

# 만약 스티커 붙일 자리가 안보인다면, 스티커를 시계방향 90도 회전 후
# 붙일 자리 찾기

# 스티커를 0도, 90도, 180도, 270도 회전시켜 봤음에도 
# 스티커를 붙이지 못했다면 해당 스티커를 붙이지 않고 버린다.

# 다 붙인 후에 노트북에서 몇개의 칸이 채워졌는가?

#시간 복잡도
# 세로N(40) 가로M(40) 스티커의 개수K(100)

n, m, k = map(int, sys.stdin.readline().split())
stickerPocket = []
stickerPocketEntire=[]
for i in range(k):
    #모눈종이의 행, 열
    r, c = map(int, sys.stdin.readline().split()) 
    stickerPocketEntire.append([r,c])
    tmp2=[]
    for j in range(r):
        tmp1 = list(map(int, sys.stdin.readline().split()))
        tmp2.append(tmp1)
    stickerPocket.append(tmp2)

print(stickerPocket)

#스티커는 받았던것 부터 차례로 붙임 (최대한 붙여야하고 이런거 없음)
#전체에서 1번을 먼저 붙임. 
# 단, 붙일 수 없는 경우는 판별해줌 
# 붙일 수 없는 경우
#   1. 노트북을 벗어나는경우[조건문]
#   2. 회전을 시도해본다 (3회전) -> 그래도 벗어나면 버림[시계방향90도로 넘겨주는 함수]
# 붙일 수 있는 경우
#   ->맨위, 맨왼쪽에 붙인다. =>[전체에서 스티커 붙여졌는지 확인하는 배열]
#   ->다음 스티커로 넘어간다. 

# 두번째 스티커를 붙일때 부터는 스티커를 붙일 수 없는 자리를
# 피해서 스티커를 붙여야 한다. 

visited=[ [0] * m for i in range(n)]

print('visited', visited)

def rotation(cnt, i):
    
    r, l = stickerPocketEntire[i]
    # 3번만 돌림 
    
    #90도로 돌리기 
    tmp = stickerPocket[i]
    nn=len(tmp) 
    mm=len(tmp[0])
    tmpList =[]
    for qq in range(mm):
        tmpList2=[]
        for pp in range(nn-1, -1, -1):
            tmpList2.append(tmp[pp][qq])
        tmpList.append(tmpList2)
    stickerPocket[i]=tmpList
    stickerPocketEntire[i]=[mm, nn]
    print('checkcnt: ', cnt)
    cnt+=1
    print('cnt: ', cnt)
    return cnt

def backtrack(p, q, i, r, l, visited):
    rem = copy.deepcopy(visited)
    print('초기 rem', rem)
    # 이부분 백트래킹으로 구현해야할듯 
    for j in range(r):
        for jj in range(l):
            #왼쪽 위부터 조회
            nr = p+j
            nl = q+jj
            if 0<=nr<n and 0<=nl<m:
                print('i:', i, "j:", j, "jj", jj)
                if stickerPocket[i][j][jj]==1:
                    #두번째 스티커를 자리에 붙여본다
                    if visited[p+j][q+jj]!=1:
                        #겹치는지 확인후 붙여보기
                        visited[p+j][q+jj]=1
                    else:
                        #겹친다.
                        #스티커를 붙이기 전으로 원래대로 돌아가기 
                        visited=copy.deepcopy(rem)
                        check = 0
                        print('리턴 visited', visited)
                        return visited, check
                check = 1
            else:
                print("뭐지 nr:", nr, "nl:", nl)
                visited=copy.deepcopy(rem)
                check = 0
                print('리턴 visited', visited)
                return visited, check
                
    if check == 1:
        return visited, check

i=0
cnt=0 # 회전 횟수
check=1
while(1):
    r, l = stickerPocketEntire[i]
    if (n<r or m<l) or check ==0: 
        # 모눈종이의 크기가 노트북을 넘어가면
        # 90도 회전 적용
        if cnt <=3:
            cnt = rotation(cnt, i)
            r, l = stickerPocketEntire[i]
        else:
            i+=1
            cnt=0
                
    if i == 0:
        #항상 0,0에서 부터 시작해야함
        for j in range(r):
            for jj in range(l):
                if stickerPocket[0][j][jj]==1:
                    visited[j][jj]=1
        i+=1
        cnt=0
    else:
        for p in range(n):
            for q in range(m):
                if i ==k:
                    break
                
                print('hi')
                print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                visited, check = backtrack(p, q, i, r, l, visited)
                print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                print('hi end')
                if check==1 or cnt>3: #스티커를 제대로 붙였을 때
                    i+=1
                    cnt=0

                if check==0 and cnt>3:
                    break
            if check==0 and cnt>3:
                break

    if i ==k:
        break

print("?????????")
sum=0
for end in visited:
    for end2 in end:
        if end2==1:
            sum+=1
print(sum)

 

 

시간초과난 코드 2

더보기
import sys, copy
# 스티커 각 칸은 상하좌우로 모두 연결되어있어야함 = 어디 끊기면 안됨
# 모눈종이에 스티커가 없는 불필요한 행이나 열이 있으면 정상아님

# 다른 스티커와 겹침X, 노트북 벗어나면X, 
# 노트북의 위쪽부터 채움(우선순위 = 1. 가장 위쪽의 위치
# , 위쪽에 여러곳일 경우2. 가장 왼쪽) 

# 만약 스티커 붙일 자리가 안보인다면, 스티커를 시계방향 90도 회전 후
# 붙일 자리 찾기

# 스티커를 0도, 90도, 180도, 270도 회전시켜 봤음에도 
# 스티커를 붙이지 못했다면 해당 스티커를 붙이지 않고 버린다.

# 다 붙인 후에 노트북에서 몇개의 칸이 채워졌는가?

#시간 복잡도
# 세로N(40) 가로M(40) 스티커의 개수K(100)

n, m, k = map(int, sys.stdin.readline().split())
stickerPocket = []
stickerPocketEntire=[]
for i in range(k):
    #모눈종이의 행, 열
    r, c = map(int, sys.stdin.readline().split()) 
    stickerPocketEntire.append([r,c])
    tmp2=[]
    for j in range(r):
        tmp1 = list(map(int, sys.stdin.readline().split()))
        tmp2.append(tmp1)
    stickerPocket.append(tmp2)

print(stickerPocket)

#스티커는 받았던것 부터 차례로 붙임 (최대한 붙여야하고 이런거 없음)
#전체에서 1번을 먼저 붙임. 
# 단, 붙일 수 없는 경우는 판별해줌 
# 붙일 수 없는 경우
#   1. 노트북을 벗어나는경우[조건문]
#   2. 회전을 시도해본다 (3회전) -> 그래도 벗어나면 버림[시계방향90도로 넘겨주는 함수]
# 붙일 수 있는 경우
#   ->맨위, 맨왼쪽에 붙인다. =>[전체에서 스티커 붙여졌는지 확인하는 배열]
#   ->다음 스티커로 넘어간다. 

# 두번째 스티커를 붙일때 부터는 스티커를 붙일 수 없는 자리를
# 피해서 스티커를 붙여야 한다. 

visited=[ [0] * m for i in range(n)]

print('visited', visited)

def rotation(cnt, i):
    
    r, l = stickerPocketEntire[i]
    # 3번만 돌림 
    
    #90도로 돌리기 
    tmp = stickerPocket[i]
    nn=len(tmp) 
    mm=len(tmp[0])
    tmpList =[]
    for qq in range(mm):
        tmpList2=[]
        for pp in range(nn-1, -1, -1):
            tmpList2.append(tmp[pp][qq])
        tmpList.append(tmpList2)
    print('before',stickerPocket[i])
    stickerPocket[i]=tmpList
    
    stickerPocketEntire[i]=[mm, nn]
    print('after',tmpList)
    print('checkcnt: ', cnt)
    cnt+=1
    print('cnt: ', cnt)
    return cnt

def backtrack(p, q, i, r, l, visited):
    rem = copy.deepcopy(visited)
    print('초기 rem', rem)
    # 이부분 백트래킹으로 구현해야할듯 
    for j in range(r):
        for jj in range(l):
            #왼쪽 위부터 조회
            nr = p+j
            nl = q+jj
            if 0<=nr<n and 0<=nl<m:
                print('i:', i, "j:", j, "jj", jj)
                if stickerPocket[i][j][jj]==1:
                    #두번째 스티커를 자리에 붙여본다
                    if visited[p+j][q+jj]!=1:
                        #겹치는지 확인후 붙여보기
                        visited[p+j][q+jj]=1
                    else:
                        #겹친다.
                        #스티커를 붙이기 전으로 원래대로 돌아가기 
                        visited=copy.deepcopy(rem)
                        check = 0
                        print('리턴 visited', visited)
                        return visited, check
                check = 1
            else:
                print("뭐지 nr:", nr, "nl:", nl)
                visited=copy.deepcopy(rem)
                check = 0
                print('리턴 visited', visited)
                return visited, check
                
    if check == 1:
        return visited, check

i=0
cnt=0 # 회전 횟수
check=1
while(1):
    r, l = stickerPocketEntire[i]
    if (n<r or m<l) or check ==0: 
        if l<=n and r<=m:
        # 모눈종이의 크기가 노트북을 넘어가면
        # 90도 회전 적용
        # 회전의 조건: 모눈종이가 노트북에 딱 들어맞지 않는경우(단, 가로 세로는 뒤집혔을때 노트북에 들어갈 수 있어야 함)
            if cnt <3:
                cnt = rotation(cnt, i)
                r, l = stickerPocketEntire[i]
            else:
                i+=1
                cnt=0
        else:
            i+=1
    
    if i == 0:
        #항상 0,0에서 부터 시작해야함
        for j in range(r):
            for jj in range(l):
                if stickerPocket[0][j][jj]==1:
                    visited[j][jj]=1
        i+=1
        cnt=0
    else:
        for p in range(n):
            for q in range(m):
                if i ==k:
                    break
                r, l = stickerPocketEntire[i]
                print('hi')
                print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                visited, check = backtrack(p, q, i, r, l, visited)
                print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                print('hi end')
                if check==1 or cnt>3: #스티커를 제대로 붙였을 때
                    i+=1
                    cnt=0

                if check==0 and cnt>3:
                    break
            if check==0 and cnt>3:
                break

    if i ==k:
        break

print("?????????")
sum=0
for end in visited:
    for end2 in end:
        if end2==1:
            sum+=1
print(sum)

 

 

시간초과 해결후 틀린 코드 1

더보기
import sys, copy
# 스티커 각 칸은 상하좌우로 모두 연결되어있어야함 = 어디 끊기면 안됨
# 모눈종이에 스티커가 없는 불필요한 행이나 열이 있으면 정상아님

# 다른 스티커와 겹침X, 노트북 벗어나면X, 
# 노트북의 위쪽부터 채움(우선순위 = 1. 가장 위쪽의 위치
# , 위쪽에 여러곳일 경우2. 가장 왼쪽) 

# 만약 스티커 붙일 자리가 안보인다면, 스티커를 시계방향 90도 회전 후
# 붙일 자리 찾기

# 스티커를 0도, 90도, 180도, 270도 회전시켜 봤음에도 
# 스티커를 붙이지 못했다면 해당 스티커를 붙이지 않고 버린다.

# 다 붙인 후에 노트북에서 몇개의 칸이 채워졌는가?

#시간 복잡도
# 세로N(40) 가로M(40) 스티커의 개수K(100)

n, m, k = map(int, sys.stdin.readline().split())
stickerPocket = []
stickerPocketEntire=[]
for i in range(k):
    #모눈종이의 행, 열
    r, c = map(int, sys.stdin.readline().split()) 
    stickerPocketEntire.append([r,c])
    tmp2=[]
    for j in range(r):
        tmp1 = list(map(int, sys.stdin.readline().split()))
        tmp2.append(tmp1)
    stickerPocket.append(tmp2)

#print(stickerPocket)

#스티커는 받았던것 부터 차례로 붙임 (최대한 붙여야하고 이런거 없음)
#전체에서 1번을 먼저 붙임. 
# 단, 붙일 수 없는 경우는 판별해줌 
# 붙일 수 없는 경우
#   1. 노트북을 벗어나는경우[조건문]
#   2. 회전을 시도해본다 (3회전) -> 그래도 벗어나면 버림[시계방향90도로 넘겨주는 함수]
# 붙일 수 있는 경우
#   ->맨위, 맨왼쪽에 붙인다. =>[전체에서 스티커 붙여졌는지 확인하는 배열]
#   ->다음 스티커로 넘어간다. 

# 두번째 스티커를 붙일때 부터는 스티커를 붙일 수 없는 자리를
# 피해서 스티커를 붙여야 한다. 

visited=[[0] * m for i in range(n)]

#print('visited', visited)

def rotation(cnt, i):
    
    r, l = stickerPocketEntire[i]
    # 3번만 돌림 
    
    #90도로 돌리기 
    tmp = stickerPocket[i]
    nn=len(tmp) 
    mm=len(tmp[0])
    tmpList =[]
    for qq in range(mm):
        tmpList2=[]
        for pp in range(nn-1, -1, -1):
            tmpList2.append(tmp[pp][qq])
        tmpList.append(tmpList2)
    #print('before',stickerPocket[i])
    stickerPocket[i]=tmpList
    
    stickerPocketEntire[i]=[mm, nn]
    # print('after',tmpList)
    # print('checkcnt: ', cnt)
    cnt+=1
    # print('cnt: ', cnt)
    return cnt

def backtrack(p, q, i, r, l, visited):
    rem = copy.deepcopy(visited)
    # print('초기 rem', rem)
    # 이부분 백트래킹으로 구현해야할듯 
    for j in range(r):
        for jj in range(l):
            #왼쪽 위부터 조회
            nr = p+j
            nl = q+jj
            if 0<=nr<n and 0<=nl<m:
                # print('i:', i, "j:", j, "jj", jj)
                if stickerPocket[i][j][jj]==1:
                    #두번째 스티커를 자리에 붙여본다
                    if visited[p+j][q+jj]!=1:
                        #겹치는지 확인후 붙여보기
                        visited[p+j][q+jj]=1
                    else:
                        #겹친다.
                        #스티커를 붙이기 전으로 원래대로 돌아가기 
                        visited=copy.deepcopy(rem)
                        check = 0
                        # print('리턴 visited', visited)
                        return visited, check
                check = 1
            else:
                # print("뭐지 nr:", nr, "nl:", nl)
                visited=copy.deepcopy(rem)
                check = 0
                # print('리턴 visited', visited)
                return visited, check
                
    if check == 1:
        return visited, check

i=0
cnt=0 # 회전 횟수
check=1
while(1):
    r, l = stickerPocketEntire[i]
    if (n<r or m<l) or check ==0: 
        if l<=n and r<=m:
        # 모눈종이의 크기가 노트북을 넘어가면
        # 90도 회전 적용
        # 회전의 조건: 모눈종이가 노트북에 딱 들어맞지 않는경우(단, 가로 세로는 뒤집혔을때 노트북에 들어갈 수 있어야 함)
            if cnt <3:
                cnt = rotation(cnt, i)
                r, l = stickerPocketEntire[i]
            else:
                i+=1
                cnt=0
        else:
            i+=1
    
    if i == 0:
        #항상 0,0에서 부터 시작해야함
        for j in range(r):
            for jj in range(l):
                if stickerPocket[0][j][jj]==1:
                    visited[j][jj]=1
        i+=1
        cnt=0
    else:
        for p in range(n):
            for q in range(m):
                if i ==k:
                    break
                r, l = stickerPocketEntire[i]
                # print('hi')
                # print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                visited, check = backtrack(p, q, i, r, l, visited)
                # print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                # print('hi end')
                if check==1 or cnt>3: #스티커를 제대로 붙였을 때
                    i+=1
                    cnt=0

                if check==0 and cnt>3:
                    break
            if check==0 and cnt>3:
                break

    if i ==k:
        break

#print("?????????")
sum=0
for end in visited:
    for end2 in end:
        if end2==1:
            sum+=1
print(sum)

 

 

시간초과 해결후 틀린 코드 2

더보기
import sys
#import copy
# 스티커 각 칸은 상하좌우로 모두 연결되어있어야함 = 어디 끊기면 안됨
# 모눈종이에 스티커가 없는 불필요한 행이나 열이 있으면 정상아님

# 다른 스티커와 겹침X, 노트북 벗어나면X, 
# 노트북의 위쪽부터 채움(우선순위 = 1. 가장 위쪽의 위치
# , 위쪽에 여러곳일 경우2. 가장 왼쪽) 

# 만약 스티커 붙일 자리가 안보인다면, 스티커를 시계방향 90도 회전 후
# 붙일 자리 찾기

# 스티커를 0도, 90도, 180도, 270도 회전시켜 봤음에도 
# 스티커를 붙이지 못했다면 해당 스티커를 붙이지 않고 버린다.

# 다 붙인 후에 노트북에서 몇개의 칸이 채워졌는가?

#시간 복잡도
# 세로N(40) 가로M(40) 스티커의 개수K(100)

n, m, k = map(int, sys.stdin.readline().split())
stickerPocket = []
stickerPocketEntire=[]
for i in range(k):
    #모눈종이의 행, 열
    r, c = map(int, sys.stdin.readline().split()) 
    stickerPocketEntire.append([r,c])
    tmp2=[]
    for j in range(r):
        tmp1 = list(map(int, sys.stdin.readline().split()))
        tmp2.append(tmp1)
    stickerPocket.append(tmp2)

#print(stickerPocket)

#스티커는 받았던것 부터 차례로 붙임 (최대한 붙여야하고 이런거 없음)
#전체에서 1번을 먼저 붙임. 
# 단, 붙일 수 없는 경우는 판별해줌 
# 붙일 수 없는 경우
#   1. 노트북을 벗어나는경우[조건문]
#   2. 회전을 시도해본다 (3회전) -> 그래도 벗어나면 버림[시계방향90도로 넘겨주는 함수]
# 붙일 수 있는 경우
#   ->맨위, 맨왼쪽에 붙인다. =>[전체에서 스티커 붙여졌는지 확인하는 배열]
#   ->다음 스티커로 넘어간다. 

# 두번째 스티커를 붙일때 부터는 스티커를 붙일 수 없는 자리를
# 피해서 스티커를 붙여야 한다. 

visited=[[0] * m for i in range(n)]

#print('visited', visited)

def rotation(cnt, i):
    
    r, l = stickerPocketEntire[i]
    # 3번만 돌림 
    
    #90도로 돌리기 
    tmp = stickerPocket[i]
    nn=len(tmp) 
    mm=len(tmp[0])
    tmpList =[]
    for qq in range(mm):
        tmpList2=[]
        for pp in range(nn-1, -1, -1):
            tmpList2.append(tmp[pp][qq])
        tmpList.append(tmpList2)
    #print('before',stickerPocket[i])
    stickerPocket[i]=tmpList
    
    stickerPocketEntire[i]=[mm, nn]
    # print('after',tmpList)
    # print('checkcnt: ', cnt)
    cnt+=1
    # print('cnt: ', cnt)
    return cnt

def backtrack(p, q, i, r, l, visited):
    #rem = copy.deepcopy(visited)
    rem = [item[:] for item in visited]
    # print('초기 rem', rem)
    # 이부분 백트래킹으로 구현해야할듯 
    for j in range(r):
        for jj in range(l):
            #왼쪽 위부터 조회
            nr = p+j
            nl = q+jj
            if 0<=nr<n and 0<=nl<m:
                # print('i:', i, "j:", j, "jj", jj)
                if stickerPocket[i][j][jj]==1:
                    #두번째 스티커를 자리에 붙여본다
                    if visited[p+j][q+jj]!=1:
                        #겹치는지 확인후 붙여보기
                        visited[p+j][q+jj]=1
                    else:
                        #겹친다.
                        #스티커를 붙이기 전으로 원래대로 돌아가기 
                        #visited=copy.deepcopy(rem)
                        visited=[item[:] for item in rem]
                        check = 0
                        # print('리턴 visited', visited)
                        return visited, check
                check = 1
            else:
                # print("뭐지 nr:", nr, "nl:", nl)
                #visited=copy.deepcopy(rem)
                visited = [item[:] for item in rem]
                check = 0
                # print('리턴 visited', visited)
                return visited, check
                
    if check == 1:
        return visited, check

i=0
cnt=0 # 회전 횟수
check=1
while(1):
    r, l = stickerPocketEntire[i]
    if (n<r or m<l) or check ==0: 
        if l<=n and r<=m:
        # 모눈종이의 크기가 노트북을 넘어가면
        # 90도 회전 적용
        # 회전의 조건: 모눈종이가 노트북에 딱 들어맞지 않는경우(단, 가로 세로는 뒤집혔을때 노트북에 들어갈 수 있어야 함)
            if cnt <3:
                cnt = rotation(cnt, i)
                r, l = stickerPocketEntire[i]
            else:
                i+=1
                cnt=0
        else:
            i+=1
    
    if i == 0:
        #항상 0,0에서 부터 시작해야함
        for j in range(r):
            for jj in range(l):
                if stickerPocket[0][j][jj]==1:
                    visited[j][jj]=1
        i+=1
        cnt=0
    else:
        for p in range(n):
            for q in range(m):
                if i ==k:
                    break
                r, l = stickerPocketEntire[i]
                # print('hi')
                # print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                visited, check = backtrack(p, q, i, r, l, visited)
                # print(visited, 'check: ', check, ' i:', i, 'cnt:', cnt)
                # print('hi end')
                if check==1 or cnt>3: #스티커를 제대로 붙였을 때
                    i+=1
                    cnt=0

                if check==0 and cnt>3:
                    break
            if check==0 and cnt>3:
                break

    if i ==k:
        break

#print("?????????")
sum=0
for end in visited:
    for end2 in end:
        if end2==1:
            sum+=1
print(sum)


정답 코드

 

import sys

n, m, k = map(int, sys.stdin.readline().split())

# 스티커 받고, 스티커 함에 넣기 
stickerPocket = []     # 모눈종이 각각 map 모음
stickerPocketEntire=[] # 모눈종이 r, l 모아놓는 함
for i in range(k):
    #모눈종이의 행, 열
    r, c = map(int, sys.stdin.readline().split()) 
    stickerPocketEntire.append([r,c])
    sticker=[]
    for j in range(r):
        tmp = list(map(int, sys.stdin.readline().split()))
        sticker.append(tmp)
    stickerPocket.append(sticker)

# 붙여졌나요?
visited=[[0] * m for i in range(n)]

# 90도 회전 함수
def rotation(cnt, i): 
    r, l = stickerPocketEntire[i] 
    # (90, 180, 270)도에 해당하게끔 3번만 돌림 
    
    sticker = stickerPocket[i]
    nn=len(sticker) # 기존의 행을 열로 사용
    mm=len(sticker[0]) # 기존의 열을 행으로 사용
    newSticker =[]

    for qq in range(mm):
        tmp=[]
        for pp in range(nn-1, -1, -1):
            tmp.append(sticker[pp][qq])
        newSticker.append(tmp)
    stickerPocket[i]=newSticker
    stickerPocketEntire[i]=[mm, nn]
    # 회전카운트 증가시킴
    cnt+=1  
    return cnt

def backtrack(p, q, i, visited):
    # 스티커를 붙일 수 없을 경우, 붙이려는 스티커를 붙이기 전 상태로 돌아오기위한것 
    rem = [item[:] for item in visited]
    # 붙이려는 스티커의 행열 조회
    r, c = stickerPocketEntire[i] 
    for j in range(r): # 행
        for jj in range(c): # 열
            #왼쪽 위부터 조회
            nr = p+j # 행/ 기존 행:n
            nc = q+jj # 열/ 기존 열:m
            # 스티커가 노트북 범위 안에 들어오면
            if 0<=nr<n and 0<=nc<m: 
                # 모눈종이에서 붙여야 하는 지점인 경우 
                if stickerPocket[i][j][jj]==1:
                    # 미리 붙여진 스티커가 없다.
                    if visited[nr][nc]!=1:
                        # 스티커 붙이기 
                        check = 1
                        visited[p+j][q+jj]=1
                    # 미리 붙여진 스티커가 있다. 겹친다.
                    else:
                        # 스티커를 붙이기 전(원래대로)으로 돌아가기 
                        visited=[item[:] for item in rem]
                        # 스티커를 못붙였다는 표식
                        check = 0
                        return visited, check
                # 모눈종이에서 빈칸인 경우
                else:
                    continue
            # 스티커가 노트북 범위 안에 못을어오면
            else:
                # 노트북 원상복구
                visited = [item[:] for item in rem]
                # 스티커를 못붙였다는 표식
                check = 0
                return visited, check
    # 스티커를 정상적으로 붙인 경우
    if check == 1:
        return visited, check

# 시작 스티커 번호
i=0
# 붙일 스티커 번호가 증가한 후 한번도 스티커를 회전한적 없는경우를 가리기 위한 변수
first=1
# 회전 횟수
cnt=0 
# 스티커를 붙이지 않았다는 표식
check=0
needRotation=0
while(1):
    rotat=0
    
    # 붙일 스티커가 없는 경우 종료 
    if i == k:
        break

    # 스티커의 행 열
    r, c = stickerPocketEntire[i]

    # 스티커의 행이나 열중 하나가 노트북을 벗어나는 경우
    if n<r or m<c: 
        #스티커의 행을 열로, 열을 행으로 바꿨을때(회전했을 때) 노트북에 들어가는 경우
        if c<=n and r<=m: 
            if cnt <3: # 0-0, 90-1, 180-2, 270-3
                cnt = rotation(cnt, i)
                rotat = 1
            # 회전을 4번다 한 경우, 다음 스티커로 넘어감 
            else:
                i+=1
                cnt=0
                first=1
        # 스티커의 행을 열로, 열을 행으로 바꿨을때(회전했을 때) 노트북에 안들어가는 경우=붙일 수 없는 경우
        elif c>n and r>m:
            # 다음 스티커로 넘어감 
            first=1
            i+=1
            cnt=0

    # 스티커가 붙여지지 않았고 0도 일때 스티커 붙이는 시도를 다 해본경우 
    # 회전이 270까지 되지 않은 경우=> 회전
    if check == 0 and first==0 and rotat==0 and needRotation==1:
        if cnt<=3:
            cnt = rotation(cnt, i)
        
    for p in range(n):
        for q in range(m):
            # 한번 돌았으면 first=0
            rotat=0
            first=0
            
            # 붙일 스티커가 없는 경우 종료 
            if i == k:
                break

            # 붙일 스티커 붙여보기 
            visited, check = backtrack(p, q, i, visited)
            #스티커를 제대로 붙였으면 빠져나감
            if check==1: 
                break
        #다음으로 붙일 스티커로 넘어가고, 회전 횟수 초기화
        #스티커를 붙였으면 빠져나감
        if check==1:
            check = 0
            i+=1
            cnt=0
            first=1
            needRotation=0
            break
    if check == 0:
        needRotation=1

    if cnt==3:
        i+=1
        first=1
        cnt=0

    # 붙일 스티커가 없는 경우 종료 
    if i ==k:
        break

# 노트북에 붙여진 스티커 개수 세기
sum=0
for end in visited:
    for end2 in end:
        if end2==1:
            sum+=1
print(sum)

 

시간/공간 복잡도

 

N(40)*M(40)*K(100) = 16000이라  1초 기준으로 NlogN으로 풀어야 하는 문제이다. 근데 문제에서 2초를 주엇으니 사실상

2NlogN인건가? 

스터디할 때 물어볼 것 

 

 

최적화 및 개선

 

  • sys 모듈 sys.stdin.readline()을 사용하여 반복적으로 입력받을 때 input()에 비해 시간을 확 줄였음
  • copy 모듈의 deepcopy함수는 시간을 많이 잡아먹으니, 다른 방식으로 리스트 복사를 해야 시간초과가 나지 않는다. 

 

 

어려웠던 점

문제를 정확하게 이해해야 빈틈없이 구현할 수 있다. 

시뮬레이션 문제는 빈틈없이 구현하는게 가장 중요한것 같다. 

 

알게된 것

  • copy모듈의 deepcopy()함수는 시간을 많이 잡아먹는다. 따라서 다른 방법을 이용해야한다.(시간초과의 주범)

https://www.acmicpc.net/problem/19637

 

 

 

문제 해결 방법

시간복잡도를 고려햐여 이분탐색을 통해 문제를 풀이해야 함 

 

코드 구현

 

시간초과난 코드

더보기
# 전투력 10,000 이하의 캐릭터는 WEAK
# 10,000 초과 그리고 100,000 이하의 캐릭터는 NORMAL
# 100,000 초과 그리고 1,000,000 이하의 캐릭터는 STRONG 칭호
n, m = map(int, input().split())
check=[]
for i in range(n):
    k, value = input().split()
    check.append([k, int(value)])
#받은값 정렬해줌 
# check = sorted(check, key = lambda x: x[1])

tmp = []
# 값 받고
for i in range(m):
    value = int(input())
    tmp.append(value)

tmp.sort()

cnt = 0
for i in tmp:
    if i <= check[cnt][1]:
        print(check[cnt][0])
    else:
        while 1:
            cnt+=1
            if i <= check[cnt][1]:
                print(check[cnt][0])
                break

 

 

# 전투력 10,000 이하의 캐릭터는 WEAK
# 10,000 초과 그리고 100,000 이하의 캐릭터는 NORMAL
# 100,000 초과 그리고 1,000,000 이하의 캐릭터는 STRONG 칭호
n, m = map(int, input().split())
check=[]
for i in range(n):
    k, value = input().split()
    check.append([k, int(value)])
#받은값 정렬해줌 
# check = sorted(check, key = lambda x: x[1])

tmp = []
# 값 받고
for i in range(m):
    value = int(input())
    tmp.append(value)

tmp.sort()
############################
mid, high, low = (n-1)//2, n-1, 0

print('mid, high, low:', mid, high, low)

for i in range(m):
    target = tmp[i]
    print("target:", target)
    print("check:", check)
    while 1:
        if target <= check[mid][1]:
            print(check[mid][0])
            break
        elif target > check[mid][1]:
            low = mid+1
            mid = (low+high)//2
        elif target < check[mid][1]:
            high = mid-1
            mid = (low+high)//2

 

bisect 모듈을 통해 풀이한 코드

from bisect import bisect_left
import sys
# 전투력 10,000 이하의 캐릭터는 WEAK
# 10,000 초과 그리고 100,000 이하의 캐릭터는 NORMAL
# 100,000 초과 그리고 1,000,000 이하의 캐릭터는 STRONG 칭호
n, m = map(int, input().split())
checkChing=[]
checkValue=[]

for i in range(n):
    k, value = sys.stdin.readline().split()
    checkChing.append(k)
    checkValue.append(int(value))

# 값 받고
tmp = []
for i in range(m):
    value = int(sys.stdin.readline())
    tmp.append(value)

for i in range(m):
    target = tmp[i]
    tmpIndex=bisect_left(checkValue, target)
    print(check2[tmpIndex])

 

bisect 모듈 없이 풀이한 코드

 

import sys

# 전투력 10,000 이하의 캐릭터는 WEAK
# 10,000 초과 그리고 100,000 이하의 캐릭터는 NORMAL
# 100,000 초과 그리고 1,000,000 이하의 캐릭터는 STRONG 칭호
n, m = map(int, sys.stdin.readline().split())
check=[]
for i in range(n):
    k, value = sys.stdin.readline().split()
    check.append([k, int(value)])
#받은값 정렬해줌 
check = sorted(check, key = lambda x: x[1])

tmp = []
# 값 받고
for i in range(m):
    value = int(sys.stdin.readline().rstrip())
    tmp.append(value)

for i in range(m):
    high = len(check)
    low = 0
    result = 0
    target = tmp[i]
    while low <= high:
        mid = (low + high)//2
        if target <= check[mid][1]:
            high = mid-1
            result = mid
        elif target > check[mid][1]:
            low = mid+1
    print(check[result][0])

 

시간/공간 복잡도

 

모든 경우를 다 조회할 경우 N은 100만까지 가능함. 

이 경우, 시간 초과를 면하기 위해서는 O(NlogN)의 시간복잡도가 되게끔 코드를 작성해야 한다. 

 

(기존에 작성한 코드에서 사용한 이중 반복문으로 구현하면 안됨,,)

 

(백준한정)추가시간 없음 이라는 말은 파이썬은 기존에 1초에 2천만번 연산 가능함 원래는 이런말 없으면 1초에 1억번 연산가능함 

 

최적화 및 개선

 

sys 모듈 sys.stdin.readline()을 사용하여 반복적으로 입력받을 때 input()에 비해 시간을 확 줄였음

 

 

어려웠던 점

이분탐색 개념이 가물가물 했다. 

 

어이없는 실수: 받은 값을 정렬함으로써 아무리 알고리즘을 맞게 짜도 출력 자체가 이상하게 되도록 함; (스스로 재앙을 불러옴)

 

알게된 것

  • 이분탐색
  • bisect 모듈
    •  
  • sys 모듈 sys.stdin.readline: 반복적으로 입력받을 때 input()에 비해 시간을 확 줄여줌
    • 단, 반복적으로 받지만 하나의 값만을 받는 경우 rstrip()을 통해 받은 값의 오른쪽에 붙는 개행문자를 지워줘야함. 

https://www.acmicpc.net/problem/12933

 

문제설명

문제 해결 방법

1. quack이 끝나기전에 q가 나오면 다른 오리가 하나 더 있는 것으로 침 

2. 리스트에 오리가 발견될 때마다 0의 값을 추가해줌

3. quack을 문자마다 0, 1, 2, 3, 4로 표기함.  즉, 오리가 quack을 완성할수록 하나의 인덱스의 값은 0에서 4까지 1씩 증가함

4. 리스트에서 제일먼저 k까지 울은 오리의 index를 따로 다른 리스트를 만들어서 오리를 순서대로 줄세워둠 

5. 최근에 k까지 울은 오리를 먼저 뽑아서 (스택 스타일) q를 먼저 줌(해당 인덱스에 0으로 갱신) = 먼저 울게 해준다.

6. 정상적으로 울지 않는 것에 대해서는 예외처리를 통해 처리하고, -1을 출력함. 

 

코드 구현

# 울음 소리를 한 번 또는 그 이상 연속해서 내는 것

# quack이 끝나기전에 q가 나오면 다른 마리인 것.
# quack이 완벽하게 끝나고 또 quack이 완벽하게 끝나면 한마리로 침

tmp = []

s = input()

waitList=[]

check = 0

for i in s:
    if i == 'q':
        if waitList:
            where = waitList.pop()
            tmp[where] = 0
        else:
            tmp.append(0)
    elif i == 'u':
        try: 
            where = tmp.index(0)
        except ValueError: 
            check = 1
            break
        tmp[where]+=1
    elif i == 'a':
        try: 
            where = tmp.index(1)
        except ValueError: 
            check = 1
            break
        tmp[where]+=1
    elif i == 'c':
        try: 
            where = tmp.index(2)
        except ValueError: 
            check = 1
            break
        tmp[where]+=1
    elif i == 'k':
        try: 
            where = tmp.index(3)
        except ValueError: 
            check = 1
            break
        tmp[where]+=1
        waitList.append(where)
result = 0
if check ==1:
    print(-1)
else:
    for i in tmp:
        if i == 4:
            result +=1  
        else:
            print(-1)
            check = 1
            break
if check !=1:
    print(result)

 

시간/공간 복잡도

O(N)

 

 

최적화 및 개선

따로 하지 않음.

 

어려웠던 점

리스트에서 특정 값을 통해 index를 구할때, index()함수만 쓸 줄 알았다. 그리고 그 함수가 찾고자 하는 값을 못찾을때 -1을 반환하기 보다는 ValueError를 발생시킬줄 몰랐다. 찾아보니 find()함수가 문자열에서 찾고자 하는 값을 못찾을때 -1을 반환했다. 

 

하지만 find()함수를 사용하지 않았다. quack 우는 것을 숫자로 표기해서 문제푸는게 넘 편한데 다시 문자열로 변환할 필요를 못느꼈다. 

 

처음에 문제의 감을 못잡아서 문자열 앞에서부터 quack을 지워갈 생각을 했다. ㅎ; 

 

코딩테스트 문제 풀면서 예외처리문을 처음 사용해본것 같다. 사용법에 대해서 잘 숙지하고 있어야겠다. 

 

코드에 중복되는 부분이 많아서 함수로 표현을 하면 좋을것 같았다. 함수로 코드를 짜버릇 해야겠다. 

https://www.acmicpc.net/problem/22232

 

 

문제설명

 

 

가희는 jo_test 폴더에 들어와 있습니다. 가희는 jo_test에 있는 파일 N개를 아래 기준에 따라 정렬하려고 합니다.

  1. 파일명 (FILENAME) 사전순으로
  2. 파일명 (FILENAME)이 같다면 가희가 설치한 OS에서 인식하는 확장자가 붙은 것이 먼저 나오게
  3. 1과 2로도 순서를 결정할 수 없다면, 파일 확장자 (EXTENSION) 사전 순으로

파일 N개를 문제에서 설명하는 정렬 기준에 따라 정렬해 주세요. 사전순의 기준은 아스키 코드 순입니다.



 

문제 해결 방법

 

이름, 확장자 순으로 sorted 함수에서 lambda를 통해 키를 설정후 정렬했다. 

 

이 과정에서 이름이 같은 값 중에서 확장자가 없것에 대해서 (확장자 없는 이름, 확장자 있는 이름) 순일 경우에만 확장자 없는 이름을 확장자 있는 이름 뒤로 자리를 바꿔줬다. 이 조건이 아닐경우, 정렬이 흐트러질 수 있기때문이다. 

 

 


코드 구현

 

 

import sys
input = sys.stdin.readline

N, M = map(int, input().split())

name = [input().rstrip() for _ in range(N)]
extent = set(input().rstrip() for _ in range(M))

tmpValue = []
for value in name:
    tmpValue.append(value.split('.'))
#print(extent)


tmpValue = sorted(tmpValue, key = lambda x: (x[0], x[1]))
# sorted함수에 key를 lambda함수로 정해줬다. 

#print('tmpValue', tmpValue)

for i in range(N-1):
    if tmpValue[i][0] == tmpValue[i+1][0]:
        if tmpValue[i][1] not in extent and tmpValue[i+1][1] in extent:
            tmp = tmpValue[i]
            tmpValue[i] = tmpValue[i+1]
            tmpValue[i+1] = tmp
        
#print('tmpValue', tmpValue)


for i in tmpValue:
    #strr, extt = i
    print(i[0]+'.'+i[1])

 

시간/공간 복잡도

 

 

O(N)

 

 

최적화 및 개선

 

input을 통해 하나의 변수가 아닌 여러 변수를 연속적으로 받을때는 입력에 input함수를 사용하면 시간초과가 걸릴 수 있

다. 이를 개선하기 위해 sys.stdin.readline을 사용하면 시간을 줄일 수 있다.  

 

특정 값이 순서와 관계없이 시퀀스 자료에서 존재하는지 확인만 하고 싶을 경우, 해당 시퀀스 자료의 자료형을 set으로 설정하는 것이 list나 다른 자료형 보다 시간을 확 줄일 수 있다. 

 

 

근거는 다음과 같다. 

 

Set은 List보다 더 빠른 탐색 속도를 가진다.
그 이유는 내부적인 데이터 구조와 탐색 알고리즘의 차이 때문입니다.

1. 데이터 구조: 
- Set은 해시 테이블(Hash Table)로 구현되어 있습니다. 해시 테이블은 값에 대한 고유한 해시 코드를 계산하고, 해당 해시 코드를 기반으로 값을 저장하고 검색합니다. 이렇게 함으로써 Set은 중복된 값을 허용하지 않으며, 값에 상수 시간(O(1))으로 접근할 수 있습니다.
- List는 배열(Array)로 구현되어 있습니다. 배열은 각 요소가 인덱스로 직접 접근되므로 인덱스를 알면 상수 시간(O(1))에 해당 요소에 접근할 수 있습니다. 하지만 List에서 값을 검색하기 위해서는 전체 리스트를 순회해야 하므로 최악의 경우 선형 시간(O(N))이 걸릴 수 있습니다. 

2. 탐색 알고리즘: 
- Set에서 값의 존재 여부를 확인하는 연산은 내부적으로 해시 함수와 버킷(bucket)을 사용하여 상수 시간O(1) 안에 처리됩니다. 
- List에서 값의 존재 여부를 확인하기 위해서는 순차적인 비교 연산을 수행해야 합니다. 따라서 최악의 경우 리스트의 모든 요소를 순회해야 하므로 선형 시간O(n)이 걸립니다. 따라서 Set은 내부적인 데이터 구조와 탐색 알고리즘을 통해 중복을 제거하고 빠른 탐색 속도를 제공합니다. 

그러나 Set은 순서가 보장되지 않으며, 인덱스 기반 접근이 불가능하기 때문에 특정 위치의 요소에 바로 접근하는 용도보다는 멤버십(Membership) 확인(=존재유무확인) 등을 위한 용도로 주로 사용됩니다.

출처: https://05-archives.tistory.com/232 [05의 개발 계발:티스토리]

 

 

어려웠던 점

 

로직적으로 맞게 코드를 짜도 입출력 함수나 자료형 설정에 따라 시간 초과되는 코드가 정답처리가 되다니 놀랍다!!

이제 이런 부분도 열심히 챙기면서 문제 풀어야겠다. 

 

 

 

https://school.programmers.co.kr/learn/courses/30/lessons/132265

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

 

문제설명

 

철수는 롤케이크를 두 조각으로 잘라서 동생과 한 조각씩 나눠 먹으려고 합니다. 이 롤케이크에는 여러가지 토핑들이 일렬로 올려져 있습니다. 철수와 동생은 롤케이크를 공평하게 나눠먹으려 하는데, 그들은 롤케이크의 크기보다 롤케이크 위에 올려진 토핑들의 종류에 더 관심이 많습니다. 그래서 잘린 조각들의 크기와 올려진 토핑의 개수에 상관없이 각 조각에 동일한 가짓수의 토핑이 올라가면 공평하게 롤케이크가 나누어진 것으로 생각합니다.

 

예를 들어, 롤케이크에 4가지 종류의 토핑이 올려져 있다고 합시다. 토핑들을 1, 2, 3, 4와 같이 번호로 표시했을 때, 케이크 위에 토핑들이 [1, 2, 1, 3, 1, 4, 1, 2] 순서로 올려져 있습니다. 만약 세 번째 토핑(1)과 네 번째 토핑(3) 사이를 자르면 롤케이크의 토핑은 [1, 2, 1], [3, 1, 4, 1, 2]로 나뉘게 됩니다. 철수가 [1, 2, 1]이 놓인 조각을, 동생이 [3, 1, 4, 1, 2]가 놓인 조각을 먹게 되면 철수는 두 가지 토핑(1, 2)을 맛볼 수 있지만, 동생은 네 가지 토핑(1, 2, 3, 4)을 맛볼 수 있으므로, 이는 공평하게 나누어진 것이 아닙니다. 만약 롤케이크의 네 번째 토핑(3)과 다섯 번째 토핑(1) 사이를 자르면 [1, 2, 1, 3], [1, 4, 1, 2]로 나뉘게 됩니다. 이 경우 철수는 세 가지 토핑(1, 2, 3), 동생도 세 가지 토핑(1, 2, 4)을 맛볼 수 있으므로, 이는 공평하게 나누어진 것입니다. 공평하게 롤케이크를 자르는 방법은 여러가지 일 수 있습니다. 위의 롤케이크를 [1, 2, 1, 3, 1], [4, 1, 2]으로 잘라도 공평하게 나뉩니다. 어떤 경우에는 롤케이크를 공평하게 나누지 못할 수도 있습니다.

 

롤케이크에 올려진 토핑들의 번호를 저장한 정수 배열 topping이 매개변수로 주어질 때, 롤케이크를 공평하게 자르는 방법의 수를 return 하도록 solution 함수를 완성해주세요.

 

문제 해결 방법

 

topping을 딕셔너리를 통해 분류후 개수를 센다. 

집합 자료형을 하나 만들어서 topping을 순회하며 하나씩 담고, topping 딕셔너리에도 수정사항을 반영해준다. 

이때, 집합 자료형과, 만들어둔 딕셔너리의 크기를 비교해서 같으면 카운트 해준다. 

 

 

코드 구현

 

from collections import Counter
def solution(topping):
    answer = 0
    
    topping_counter = Counter(topping)
    # print(len(topping_counter)) 딕셔너리의 키 개수가 나옴
    
    half_topping_set = set()
    
    for t in topping:
        half_topping_set.add(t)
        topping_counter[t] -= 1
        
        if topping_counter[t] == 0:
            topping_counter.pop(t)
        
        if len(half_topping_set) == len(topping_counter):
            answer +=1
    
    
    return answer

 

 

 

시간/공간 복잡도

 

O(n)

 

최적화 및 개선

 

처음에는 슬라이스도 쓰고 이중반복문도 쓰고 해서 거의 O(N^3)의 시간복잡도로 코드를 작성했는데 시간초과가 나서 수정했다. 

 


어려웠던 점

 

딕셔너리를 저렇게 활용할 생각을 못했다. 

저렇게 구현하는 방법을 생각해내는 것이 어렵다.

딕셔너리의 개수를 세아리면 key의 개수가 나온다는 것을 알게됐고, 딕셔너리는 키값으로 pop()을 하면 해당 키값과 value값은 삭제되는 것을 알게됐다. set함수는 값을 추가할 때, add를 사용하는것을 완전히 알게됐다. 

 

그리고 리스트가 있을때, 딕셔너리의 형태로 정리하고싶으면 Counter함수를 사용하면 쉽게 정리가 된다는 것을 알게됐다. 

https://school.programmers.co.kr/learn/courses/30/lessons/62050

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

문제설명

 

N x N 크기인 정사각 격자 형태의 지형이 있습니다. 각 격자 칸은 1 x 1 크기이며, 숫자가 하나씩 적혀있습니다. 격자 칸에 적힌 숫자는 그 칸의 높이를 나타냅니다.

 

이 지형의 아무 칸에서나 출발해 모든 칸을 방문하는 탐험을 떠나려 합니다. 칸을 이동할 때는 상, , , 우로 한 칸씩 이동할 수 있는데, 현재 칸과 이동하려는 칸의 높이 차가 height 이하여야 합니다. 높이 차가 height 보다 많이 나는 경우에는 사다리를 설치해서 이동할 수 있습니다. 이때, 사다리를 설치하는데 두 격자 칸의 높이차만큼 비용이 듭니다. 따라서, 최대한 적은 비용이 들도록 사다리를 설치해서 모든 칸으로 이동 가능하도록 해야 합니다. 설치할 수 있는 사다리 개수에 제한은 없으며, 설치한 사다리는 철거하지 않습니다.

 

각 격자칸의 높이가 담긴 2차원 배열 land와 이동 가능한 최대 높이차 height가 매개변수로 주어질 때, 모든 칸을 방문하기 위해 필요한 사다리 설치 비용의 최솟값을 return 하도록 solution 함수를 완성해주세요.

 

제한사항

  • land는 N x N크기인 2차원 배열입니다.
  • land의 최소 크기는 4 x 4, 최대 크기는 300 x 300입니다.
  • land의 원소는 각 격자 칸의 높이를 나타냅니다.
  • 격자 칸의 높이는 1 이상 10,000 이하인 자연수입니다.
  • height는 1 이상 10,000 이하인 자연수입니다.

 

문제 해결 방법

 

BFS를 활용해서 문제를 풀었다. 그런데 BFS를 할 때, 고려해줘야 하는게 있었다. 

바로 사다리 설치비용이다. 문제에서 요구한대로 최소한의 사다리 설치 비용을 산출해내야 되기 때문에, 좌표에서 상, 하, 좌, 우 이동시 발생할 수 있는 사다리 설치 비용에 대해서 우선순위를 매겨야한다. 이때 heapq 우선순위 큐를 활용하여 문제를 해결했다. 

 

 

코드 구현

 

BFS, heapq 자료구조를 이용해 풀이한 코드

import heapq 

def solution(land, height):
    answer = 0
    # 좌표 움직임을 위한 배열
    dx = [0, 0, -1, 1]
    dy = [1, -1, 0, 0]
    
    #시작 좌표
    startX, startY = 0, 0
    
    # 방문 여부 확인을 위한 배열
    visited = [[False] * len(land) for _ in range(len(land[0]))]
    
    heap = []
    
    
    def bfs(stratX, startY):
        answer = 0
        heapq.heappush(heap, [0, startX, startY]) # 비용, startX, startY
        
        while len(heap) != 0:
            cost, x, y = heapq.heappop(heap)
            #print('cost', cost)
            
            
            if visited[y][x] == False:
                visited[y][x] = True
                
                answer += cost
                for i in range(4):
                    nx = x + dx[i]
                    ny = y + dy[i]
                    
                    # 새로운 좌표가 정사각 격자 형태의 지형을 벗어나는지 체크
                    if ny >= len(land) or nx >= len(land[0]) or ny < 0 or nx < 0:
                        continue
                    
                    if abs(land[y][x]-land[ny][nx]) <= height:
                        newcost = 0
                        
                    else:
                        newcost = abs(land[y][x]-land[ny][nx])
                        
                    heapq.heappush(heap, [newcost, nx, ny])
        return answer
        
        
    answer = bfs(startX, startY)          
    #print(answer)
    
    return answer

 

 

 

BFS구현 및 deque로 구현하려다가 실패한 코드

 

from collections import deque

def solution(land, height):
    answer = 0
    realAnswer = 0
    # 좌표 움직임을 위한 배열
    dx = [0, 0, -1, 1]
    dy = [1, -1, 0, 0]
    
    #시작 좌표
    startX, startY = 0, 0
    
    # 방문 여부 확인을 위한 배열
    visited = [[False] * len(land) for _ in range(len(land[0]))]
    
    check = [[10000] * len(land[0]) for _ in range(len(land))]
    
    print(visited)
    
    queue = deque()
    
    def bfs(stratX, startY):
        
        #시작하는 좌표를 큐에 넣고, 
        
        # 큐에 값이 있으면 큐에서 값을 꺼내어 방문 처리를 한다. 
        # 꺼낸 값에 인접한 값 중에서 방문하지 않은 좌표인지 확인한 후, 
        # 방문하지 않았으면 큐에 넣는다.
        
        queue.append([startX, startY])
        nx = 0
        ny = 0
        
        while len(queue) != 0:
            x, y = queue.popleft()
            visited[y][x] = True
            print('')
            print('x, y', x, y)
            for i in range(4):
                nx = x + dx[i]
                ny = y + dy[i]
                print('i', i, 'nx, ny:', nx, ny)
                # 새로운 좌표가 정사각 격자 형태의 지형을 벗어나는지 체크
                if ny >= len(land) or nx >= len(land[0]) or ny < 0 or nx < 0:
                    print('?1')
                    continue
                
                if visited[ny][nx] == True:
                    print(visited)
                    print('?2')
                    continue
                
                print('nx, ny', nx, ny, 'x', 'y', x, y)
                print(land[y][x], land[ny][nx])
                if abs(land[y][x]-land[ny][nx]) <= height:
                    check[ny][nx] = abs(land[y][x]-land[ny][nx])
                    print('input', nx, ny)
                    queue.append([nx, ny])
#                 else:
#                     if check[ny][nx] > abs(land[y][x]-land[ny][nx]):
#                         check[ny][nx] = abs(land[y][x]-land[ny][nx])
                    
#                     print('input', nx, ny)
#                     queue.append([nx, ny])
    
    
    bfs(startX, startY)
    check[0][0]=0    
    print('check', check)           
        
    
    
    
    
    
    
    return answer

 

 

시간/공간 복잡도

 

최악의 경우 O(N^2log(N^2))

 

최적화 및 개선 / 어려웠던 점

 

진짜 BFS에서 좌표 문제는 항상 헷갈리는 것 같다. 

이 문제를 풀면서 깨달은 것은 이차원배열에서 x, y 좌표의 값을 구하고 싶으면

 

이차원배열명[y][x]

이렇게 호출해야 한다는 점이다. 

 

그리고 heapq와 deque의 사용처에 대해서 분명히 알게됐다. 

heapq는 deque보다 자료의 우선순위를 매겨야 할 때 사용하는 것이 적절하다. 

반대로 자료의 우선순위를 매겨야할 때 deque를 사용하는 것은 적절치 않다. 

 

 

https://school.programmers.co.kr/learn/courses/30/lessons/42746

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

문제설명

0 또는 양의 정수가 주어졌을 때, 정수를 이어 붙여 만들 수 있는 가장 큰 수를 알아내 주세요.

 

예를 들어, 주어진 정수가 [6, 10, 2]라면 [6102, 6210, 1062, 1026, 2610, 2106]를 만들 수 있고, 이중 가장 큰 수는 6210입니다.

 

0 또는 양의 정수가 담긴 배열 numbers가 매개변수로 주어질 때, 순서를 재배치하여 만들 수 있는 가장 큰 수를 문자열로 바꾸어 return 하도록 solution 함수를 작성해주세요.

 

제한 사항

numbers의 길이는 1 이상 100,000 이하입니다.

numbers의 원소는 0 이상 1,000 이하입니다.

정답이 너무 클 수 있으니 문자열로 바꾸어 return 합니다.

입출력 예

numbers return

[6, 10, 2] "6210"

[3, 30, 34, 5, 9]    "9534330"

 

문제 해결 방법

 

주어진 값들의 대소비교를 원활하게 하기 위해서, 3배를 취해준뒤, 내림차순으로 정렬하였다. 

중복제거를 위해 집합을 사용했다. 

 

코드 구현

 

def solution(numbers):
    answer = ''
    
    sortedNum = []
    numbers = list(map(str, numbers))
    numbers.sort(reverse=True)
    
    numbers.sort(key=lambda x : x*3, reverse=True)
    numbers = list(map(int, numbers))
    
#     i = len(numbers)-1

#     while 1:
#         if i == 0:
#             break
#         if int(str(numbers[i-1]) + str(numbers[i])) >= int(str(numbers[i]) + str(numbers[i-1])):
#             i -=1
#             continue
#         else:
#             tmp = numbers[i]
#             numbers[i]=numbers[i-1]
#             numbers[i-1]=tmp
        
#         i = len(numbers)-1

    if max(numbers)==0:
        return '0'
    
    numbers = list(map(str, numbers))
            
    answer = ''.join(numbers)
    return answer

 

# 사실 주석처리 한 부분처럼 구현하려 했으나, 쓸모없어졌다. 

 

시간/공간 복잡도

 

O(n)

 

최적화 및 개선

 

하지않음 

 


어려웠던 점

 

진짜 볼때마다 아주 놀라운 문제다 싶다. 

숫자의 대소비교를 할때, 문자열로 형태를 변환한 뒤,  제한사항에서 주어진 값의 값의 범위를 참고하여 값을 임의로 부풀려 대소비교를 하는 방법을 잊지 말도록 하자. 

https://school.programmers.co.kr/learn/courses/30/lessons/12913#qna

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

문제설명

 

땅따먹기 게임을 하려고 합니다. 땅따먹기 게임의 땅(land)은 총 N 4열로 이루어져 있고, 모든 칸에는 점수가 쓰여 있습니다. 1행부터 땅을 밟으며 한 행씩 내려올 때, 각 행의 4칸 중 한 칸만 밟으면서 내려와야 합니다. , 땅따먹기 게임에는 한 행씩 내려올 때, 같은 열을 연속해서 밟을 수 없는 특수 규칙이 있습니다.

 

예를 들면,

 

| 1 | 2 | 3 | 5 |

 

| 5 | 6 | 7 | 8 |

 

| 4 | 3 | 2 | 1 |

 

로 땅이 주어졌다면, 1행에서 네번째 칸 (5)를 밟았으면, 2행의 네번째 칸 (8)은 밟을 수 없습니다.

 

마지막 행까지 모두 내려왔을 때, 얻을 수 있는 점수의 최대값을 return하는 solution 함수를 완성해 주세요. 위 예의 경우, 1행의 네번째 칸 (5), 2행의 세번째 칸 (7), 3행의 첫번째 칸 (4) 땅을 밟아 16점이 최고점이 되므로 16 return 하면 됩니다.

 

제한사항

  행의 개수 N : 100,000 이하의 자연수

  열의 개수는 4개이고, (land) 2차원 배열로 주어집니다.

  점수 : 100 이하의 자연수

 

문제 해결 방법

 

DP를 활용하여 문제를 풀었음.

두번째 행 기준에서 첫번째 행에서 어느 열부터 출발하냐에 따라 두번째 행에서 시작하는 점수가 달라진다. 

첫번째 행에서 밟을 수 있는 모든 열을 기준으로, 두번째 행에서 밟을 수 있는 열을 밟아서, 두번째 행에서 시작할 수 있는 모든 점수 중 가장 큰 점수를 각 열에 기록했다. 그 다음 행도 이런식으로 점수를 기록한 후, 맨 마지막 행에서 최대 점수를 찾아서 리턴했다. 

 

 

코드 구현

 

DP로 구현한 코드

import copy
def solution(land):
    answer = 0
    
    lenL = len(land)
    originLand = copy.deepcopy(land)
    
    for j in range(0, lenL):
        if j + 1 == lenL:
            break
        
        for i in range(4):
            for k in range(4):
                if i !=k:
                    if land[j+1][k] < originLand[j+1][k] + land[j][i]:
                        land[j+1][k] = originLand[j+1][k] + land[j][i]
                    #print(land)
    answer = (max(land[-1]))
    

    return answer

 

 

DFS(재귀)로 구현후, 간단한 테스트케이스는 통과가 되지만, 제출시 시간초과와 런타임에러로 도배됐던 코드 

def solution(land):
    answer = 0

    check = [False] * 4
    
    #print(land[0])
    
    summ = 0
    maxValue = 0
    maxCnt = len(land)
    cnt = 0
    result = []
    def dfs(check, summ, cnt, result, rem):
        if cnt == maxCnt:
            
            result.append(summ)
            return
        originRem = rem
        for idx, i in enumerate(land[cnt]):
            #print(check, 'rem', rem)
            if check[idx] == True:
                continue
            elif check[idx] == False:
                summ +=i
                check = [False] * 4
                check[idx] = True
                cnt +=1
                # print('cnt',cnt, 'idx', idx, 'i', i, check, 'rem', rem)
                # print()
                rem = idx
                dfs(check, summ, cnt, result, rem)
                check[idx] = False
                check[originRem] = True
                rem = originRem
                summ -=i
                cnt -=1
    
    rem = 0
    for idx, i in enumerate(land[0]):
        summ += i
        check[idx] = True
        cnt +=1
        #print('cnt',cnt, 'i', i)
        rem = idx
        dfs(check, summ, cnt, result, rem)
        check[idx] = False
        summ -=i
        cnt -= 1
            
#     print(result)
#     print(max(result))
            
    answer = max(result)
    return answer

 

 

시간/공간 복잡도

 

 

최악의 경우 O(NlogN)

 

 

최적화 및 개선 / 어려웠던 점

 

내 기준에서 진짜 어려운 문제다. 역대급 어려웠다. 

DP문제는 직관적으로는 이해가 가는데, 코드 구현을 하려고 하니 너무 헷깔리는 부분이 많았다. 

 

그리고 처음부터 이 문제는 문제의 제한사항을 안보고(시간 복잡도를 고려하지 않고) 문제를 풀었었는데, 어떤 문제라도 시간 복잡도를 잘 고려하고 문제에 접근하는 것의 중요성을 아주 많이 느꼈다. 

 

아예 시간복잡도 별 알고리즘과 최대 연산 횟수를 포스트잇에 써서 컴퓨터 본체에 붙여놨다 

 

문제가 어렵긴한데 풀고나니 해방감이 아주 좋고, 즐거움이 느껴진다. 

 

그리고 다른사람이 이 문제에 대해서 상세히 풀이해준 글을 봤는데 처음에 글을 보고도 이해가 잘 안갔다. 

그림으로 그려보기도 했는데 이해가 잘 안갔다. 

 

다시 글을 이해하려고 했을때는 그림을 아주 상세하게 그려서 펼쳐놓고 고민을 많이 하고 해설을 읽어보고를 반복했는데, 

이해가 됐다. 

 

이해가 안되는 문제에 대해서 그림을 상세하게 그리는게 시간이 많이 들진 몰라도, 확실히 그림을 대충그리는 것보다 상세히 그리는게 도움이 많이 되는것 같다. 

 

그리고 내가 작성한 코드에 대해서 시간복잡도를 계산하는 방법을 다시 공부해야할 것 같은 느낌이 든다.

 

 

+ Recent posts