문제링크

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

 

16918번: 봄버맨

첫째 줄에 R, C, N (1 ≤ R, C, N ≤ 200)이 주어진다. 둘째 줄부터 R개의 줄에 격자판의 초기 상태가 주어진다. 빈 칸은 '.'로, 폭탄은 'O'로 주어진다.

www.acmicpc.net

문제설명

문제

봄버맨은 크기가 R×C인 직사각형 격자판 위에서 살고 있다. 격자의 각 칸은 비어있거나 폭탄이 들어있다.

폭탄이 있는 칸은 3초가 지난 후에 폭발하고, 폭탄이 폭발한 이후에는 폭탄이 있던 칸이 파괴되어 빈 칸이 되며, 인접한 네 칸도 함께 파괴된다. 즉, 폭탄이 있던 칸이 (i, j)인 경우에 (i+1, j), (i-1, j), (i, j+1), (i, j-1)도 함께 파괴된다. 만약, 폭탄이 폭발했을 때, 인접한 칸에 폭탄이 있는 경우에는 인접한 폭탄은 폭발 없이 파괴된다. 따라서, 연쇄 반응은 없다.

봄버맨은 폭탄에 면역력을 가지고 있어서, 격자판의 모든 칸을 자유롭게 이동할 수 있다. 봄버맨은 다음과 같이 행동한다.

  • 가장 처음에 봄버맨은 일부 칸에 폭탄을 설치해 놓는다. 모든 폭탄이 설치된 시간은 같다.
  • 다음 1초 동안 봄버맨은 아무것도 하지 않는다.
  • 다음 1초 동안 폭탄이 설치되어 있지 않은 모든 칸에 폭탄을 설치한다. 즉, 모든 칸은 폭탄을 가지고 있게 된다. 폭탄은 모두 동시에 설치했다고 가정한다.
  • 1초가 지난 후에 3초 전에 설치된 폭탄이 모두 폭발한다.
  • 3과 4를 반복한다.

폭탄을 설치해놓은 초기 상태가 주어졌을 때, N초가 흐른 후의 격자판 상태를 구하려고 한다.

예를 들어, 초기 상태가 아래와 같은 경우를 보자.

...
.O.
...

1초가 지난 후에는 아무 일도 벌어지지 않기 때문에, 위와 같다고 볼 수 있다. 1초가 더 흐른 후에 격자판의 상태는 아래와 같아진다.

OOO
OOO
OOO

1초가 지난 후엔 가운데에 있는 폭탄이 폭발해 가운데 칸과 인접한 네 칸이 빈 칸이 된다.

O.O
...
O.O

입력

첫째 줄에 R, C, N (1 ≤ R, C, N ≤ 200)이 주어진다. 둘째 줄부터 R개의 줄에 격자판의 초기 상태가 주어진다. 빈 칸은 '.'로, 폭탄은 'O'로 주어진다.

출력

총 R개의 줄에 N초가 지난 후의 격자판 상태를 출력한다.

 

문제풀이

생각보다 어렵게 생각하고 헷갈려 시간이 오래걸린 문제였다.

 

폭탄이 있는 좌표를 큐에 삽입하고,

폭탄이 폭발하는 로직에서 하나씩 pop시켜 해당 위치와 상하좌우 위치좌표까지 상태를 변경하는건 기존에 풀었던 BFS와 매우 흡사하다고 생각했다.

 

반복문을 통해서 해당 (3),(4) 문을 순차적으로 하나씩 실행한다.

 

(1) (초기) 봄버맨이 폭탄 'O' 셋팅

(2) 아무것도 안함

(3) 폭탄 설치 안된 나머지 칸에 폭탄 전부 설치

(4) 3초 전에 설치된 폭탄 폭발.

 

그냥 순차적으로 격자판 상태를 변경하며 진행해도 예제로 주어진 테스트케이스는 통과한다.

하지만 제출을 하면 실패한다.

 

핵심은 

- 3초 전에 설치된 폭탄 폭발.

- 3과 4를 반복

시간이 흐르고 시간내에 반복하는 로직이 있기 때문에

이미지의 2번과 4번에 해당한 격자판 상태,

즉, 3번 로직들을 실행하기 전의 격자판 상태를 기억하는 로직을 만들자!

 

3번 과정 전에 기억한 폭탄들의 좌표를 큐에 삽입하는 것이 핵심이었다.

이제 이 큐를 4번과정인 폭발로직에 넘겨 활용하면 된다.

 

코드
import sys
from collections import deque

input = sys.stdin.readline

R, C, N = map(int, input().split())
graph = [[0 for _ in range(C)] for _ in range(R)]
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
queue = deque()

# 입력값 받기 (격자판 초기상태)
for i in range(R):
    graph[i] = list(input().rstrip())
    
# (1) 봄버맨이 일부칸에 폭탄 설치. (이미 입력값으로 받음)
# 폭탄('O')이 있는 좌표들 큐에 삽입하기

def memoBomb():
    for i in range(R):
        for j in range(C):
            if graph[i][j] == 'O':
                queue.append((i, j))
    return queue

# (2) 폭탄 모든칸에 설치
def plantBombs():
    global R, C
    for i in range(R):
        for j in range(C):
            graph[i][j] = 'O'

# (3) 3초 전에 설치된 폭탄이 모두 폭발하는 로직
def explode(bombQueue):
    global R, C
    while bombQueue:
        curX, curY = queue.popleft()
        graph[curX][curY] = '.' # 폭탄 폭발한 상태.
        for i in range(4):
            nx = curX + dx[i]
            ny = curY + dy[i]

            if 0 <= nx < R and 0 <= ny < C and graph[nx][ny] == 'O':
                graph[nx][ny] = '.'

# N초 동안 1초마다 해당 로직 실행.
def solution():
    global N
    # (1) 1초동안 아무것도 안하는 상태
    N -= 1
    while N:
        # 폭탄 채우기 바로 전 상황 기억 (폭탄 좌표 큐에 삽입).
        bombQueue = memoBomb()

        # (2) 폭탄 없는 칸에 전부 폭탄 채우기
        plantBombs()
        N -= 1
        # 시간 다 흘렀으면 반복문 종료
        if N <= 0:
            break

        # (3) 폭탄 터뜨리기
        explode(bombQueue)
        N -= 1

    # N초 흐른 후에 격자판 상태 출력
    for row in graph:
        print("".join(row))

if __name__ == "__main__":
    solution()
문제링크

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

 

7576번: 토마토

첫 줄에는 상자의 크기를 나타내는 두 정수 M,N이 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M,N ≤ 1,000 이다. 둘째 줄부터는 하나의 상자에 저장된 토마토

www.acmicpc.net

 

문제설명

문제

철수의 토마토 농장에서는 토마토를 보관하는 큰 창고를 가지고 있다. 토마토는 아래의 그림과 같이 격자 모양 상자의 칸에 하나씩 넣어서 창고에 보관한다. 

 

창고에 보관되는 토마토들 중에는 잘 익은 것도 있지만, 아직 익지 않은 토마토들도 있을 수 있다. 보관 후 하루가 지나면, 익은 토마토들의 인접한 곳에 있는 익지 않은 토마토들은 익은 토마토의 영향을 받아 익게 된다. 하나의 토마토의 인접한 곳은 왼쪽, 오른쪽, 앞, 뒤 네 방향에 있는 토마토를 의미한다. 대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다. 철수는 창고에 보관된 토마토들이 며칠이 지나면 다 익게 되는지, 그 최소 일수를 알고 싶어 한다.

토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라. 단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.

입력

첫 줄에는 상자의 크기를 나타내는 두 정수 M,N이 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M,N ≤ 1,000 이다. 둘째 줄부터는 하나의 상자에 저장된 토마토들의 정보가 주어진다. 즉, 둘째 줄부터 N개의 줄에는 상자에 담긴 토마토의 정보가 주어진다. 하나의 줄에는 상자 가로줄에 들어있는 토마토의 상태가 M개의 정수로 주어진다. 정수 1은 익은 토마토, 정수 0은 익지 않은 토마토, 정수 -1은 토마토가 들어있지 않은 칸을 나타낸다.

토마토가 하나 이상 있는 경우만 입력으로 주어진다.

출력

여러분은 토마토가 모두 익을 때까지의 최소 날짜를 출력해야 한다. 만약, 저장될 때부터 모든 토마토가 익어있는 상태이면 0을 출력해야 하고, 토마토가 모두 익지는 못하는 상황이면 -1을 출력해야 한다.

예제 입력 1 

6 4
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 1

예제 출력 1 

8

예제 입력 2 

6 4
0 -1 0 0 0 0
-1 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 1

예제 출력 2 

-1

 

문제분석

미로찾기와 본질은 매우 비슷한 문제였다.

익은 토마토 주변에서부터 익기 시작하는것이므로 BFS로 활용하는 것이 첫번째이고,

그 시작점은 1이 있는 좌표를 골라 탐색해야하므로, 1이 있는 좌표값을 전부 큐에 삽입.

그 후에 BFS 실행이 끝나면 이차원배열에 저장된 값중 최댓값을 골라 1을 빼면 최소 일 수를 구할 수 있는 문제였다.

 

코드
import sys
from collections import deque

def BFS():
    dx = [1, -1, 0, 0]
    dy = [0, 0, -1, 1]

    while queue:
        curX, curY = queue.popleft()
        for i in range(4):
            nx = curX + dx[i]
            ny = curY + dy[i]

            if 0 <= nx < N and 0 <= ny < M and graph[nx][ny] == 0:
                graph[nx][ny] = graph[curX][curY] + 1
                queue.append((nx, ny))

if __name__ == "__main__":
    input = sys.stdin.readline
    M, N = map(int, input().split())
    graph = [[0 for _ in range(M)] for _ in range(N)]

    # 입력값 저장
    for i in range(N):
        graph[i] = list(map(int, input().split()))
    
    queue = deque()

    # 처음에 1이 적혀있는 좌표중 하나를 골라 탐색해야할듯.
    # 익은 토마토의 인접한 곳이 익게 되는 것이 조건이므로 
    # 1이 있는 좌표 전부 다 큐에 삽입.
    for i in range(N):
        for j in range(M):
            if graph[i][j] == 1:
                queue.append((i, j))   

    # BFS 실행 (토마토 익히기)
    BFS()

    # 최소 경과일 수는 과일이 다 익었을 시점의
    # 좌표 인덱스에 저장된 값 출력하면 되니까 배열의 최댓값.
    answer = 0
    for row in graph:
        for item in row:
            # item이 0 인게 하나라도 있으면 모두 익지 못하는 상황.
            if item == 0:
                print(-1)
                exit()
        # 0이 있는지 검사 끝났으면, sub배열의 최댓값 업데이트.
        answer = max(answer, max(row))

    # 1에서부터 카운트 시작했으므로 
    # 경과 일수를 출력하려면 1빼야함
    print(answer - 1)
문제링크

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

 

2178번: 미로 탐색

첫째 줄에 두 정수 N, M(2 ≤ N, M ≤ 100)이 주어진다. 다음 N개의 줄에는 M개의 정수로 미로가 주어진다. 각각의 수들은 붙어서 입력으로 주어진다.

www.acmicpc.net

문제설명

문제

N×M크기의 배열로 표현되는 미로가 있다.

1 0 1 1 1 1
1 0 1 0 1 0
1 0 1 0 1 1
1 1 1 0 1 1

미로에서 1은 이동할 수 있는 칸을 나타내고, 0은 이동할 수 없는 칸을 나타낸다. 이러한 미로가 주어졌을 때, (1, 1)에서 출발하여 (N, M)의 위치로 이동할 때 지나야 하는 최소의 칸 수를 구하는 프로그램을 작성하시오. 한 칸에서 다른 칸으로 이동할 때, 서로 인접한 칸으로만 이동할 수 있다.

위의 예에서는 15칸을 지나야 (N, M)의 위치로 이동할 수 있다. 칸을 셀 때에는 시작 위치와 도착 위치도 포함한다.

입력

첫째 줄에 두 정수 N, M(2 ≤ N, M ≤ 100)이 주어진다. 다음 N개의 줄에는 M개의 정수로 미로가 주어진다. 각각의 수들은 붙어서 입력으로 주어진다.

출력

첫째 줄에 지나야 하는 최소의 칸 수를 출력한다. 항상 도착위치로 이동할 수 있는 경우만 입력으로 주어진다.

예제 입력 

4 6
101111
101010
101011
111011

예제 출력 

15

 

문제분석

핵심

BFS 활용.

가는 경로에 1을 만난다면 큐에 저장하고 거리값 업데이트.

BFS로 탐색하기 때문에 graph[nx][ny] == 1 조건만 검사해주면 최소값으로 찍힘.

먼 경로에서 탐색할 경우에는 이미 1이 아닌 다른값으로 업데이트 되었을 것이기 때문.

 

코드
import sys
from collections import deque

input = sys.stdin.readline

N, M = map(int, input().split())
graph = [[0 for _ in range(M)] for _ in range(N)]

for i in range(N):
    graph[i] = list(input().rstrip())
    graph[i] = list(map(int, graph[i]))

def BFS(startX, startY):
    queue = deque()
    queue.append((startX, startY))

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

    while queue:
        curX, curY = queue.popleft()
        for i in range(4):
            nx = curX + dx[i]
            ny = curY + dy[i]

            if 0 <= nx < N and 0 <= ny < M and graph[nx][ny] == 1:
                graph[nx][ny] = graph[curX][curY] + 1
                queue.append((nx, ny))

BFS(0, 0)
print(graph[N-1][M-1])

# 그래프의 거리를 저장하여, 비교하는 로직 필요
# 하지만, BFS로 이미 동서남북 전부 살피고 가므로
# 최소거리였다면 graph[nx][ny]가 이미 계산되어 저장되었을 것.
# 그리고 더 먼 거리에서 들어온다면 이미 1이 아닌 상태로 저장되어 지장X
핵심
1. 함수선언문과 함수표현식의 차이
2. 상수(constant), 리터럴(Litereal)
3. '식' 과 '문'의 차이

 

1.  함수선언문 vs 함수 표현식

우선 함수는 함수객체를 가리키는 식별자로 호출된다는 것을 알아두자!
함수명으로는 함수 외부에서 함수를 참조할 수 없다.

cf. 함수명은 함수 몸체 내에서만 참조가능한 식별자

 

함수선언문

function foo() {
	console.log("Hello!");
}

foo();

함수 선언문의 경우엔 JS엔진이 함수 객체를 가리키는 식별자를 암묵적으로 생성한다. (함수명과 동일한 이름의 식별자)

그리고 거기에 함수객체를 할당한다.

⇒ 그래서 겉보기엔 이름으로 호출이 가능한 것처럼 보이는 것이다.

 

함수표현식 (함수 리터럴)

- 자바스크립트에서 함수는 일급객체라는 특성을 이용하여 함수 리터럴 방식으로 함수를 정의.

- 변수에 할당 가능.
- 기명보다는 익명함수 즉, 함수명을 생략하는게 일반적임.

(function foo() {
	console.log("foo");
});

foo();  // Reference Error

함수명으로는 외부에서 함수를 참조할 수 없으므로 Reference Error가 나오는것을 확인할 수 있었다.

 

var foo = function() {
	return 10;
};

console.log(foo())

그럼 위의 코드에서 호출할때 쓰이는 foo는 무엇이냐?

=> 함수명이 아니라 식별자로, 이는 할당된 함수를 가리키는 참조값을 저장한다.

 

2. 상수와 리터럴

상수(constant)

상수는 변하지 않는 변수를 뜻한다. 즉, 메모리 공간을 의미하며, 메모리 '값'을 변경할 수 없다.

자바스크립트에서 보통 const 로 선언한다.

 

상수에 넣는 데이터로 숫자나 문자열만 오는게 아닌, 객체도 들어올 수 있다.

변하지 않는다는 말 때문에, 객체 안의 프로퍼티 값까지 변할 수 없다고 생각할 수 있다.

 

하지만 이는 원시값에만 해당되는 말이고, 객체는 참조타입 변수이다.

이 참조타입 변수의 메모리 주소값이 변할 수 없는 것이지, 주소가 가리키는 데이터는 변할 수 있다.

전에 let, const, var의 차이를 다룬 글에서 다시 가져와 봤다.

const obj = {
    name: "bae",
    gender: "male",
    age: 27,
    height: 178,
}

//수정 (주소가 가리키는 데이터는 변할 수 있다)
obj.age = 25

//재할당 (객체 자체의 참조를 변경하는 것은 안된다!)
obj = {
    name: "bae",
    gender: "male",
    age: 25,
    score: 100,
}

 

리터럴(Literal)

그에 반해 리터럴은 데이터(value) 그 자체를 뜻한다.

e.g. 정수 리터럴 - 100

       함수 리터럴 - function(){}

       객체 리터럴 - { name: 'Bae' }

 

cf. 리터럴 표기법이란?

 변수를 선언함과 동시에 값을 지정해주는 표기법

 

3. 식, 문

식 : 하나의 value를 반환하면 '식'이다.    e.g. 함수 표현식

문 : 값을 반환하지 않는다.   e.g. 함수 선언문

목차
1. 변수 생성 과정
2. var, let, const 비교
3. 호이스팅

 

 

1. 변수 생성 과정

먼저 변수의 생성 과정에 대해 살펴보려고 한다.

 

1. 선언 단계

실행 컨텍스트의 변수객체에 등록한다. 즉, 변수명(식별자)을 등록하여 스코프가 참조할 대상을 만든다.

2. 초기화 단계

변수저장을 위해 메모리에 공간을 확보한다. undefined가 할당

3. 할당 단계

undefined로 초기화된 변수에 실제 값을 할당한다.

 

2. var, let, const 비교

var

1. 함수 레벨 스코프

  • 함수 스코프를 따르기 때문에 함수 내에서 접근하기만 한다면, 다른 모든 블록스코프에서 접근이 가능하므로 의도치 않게 변수가 오염될 수 있다.
  • 즉, 함수 외부에서 생성한 변수는 모두 전역변수인셈.

2. 중복 선언 가능

이미선언한 변수를 의도치 않게 같은 이름으로 선언해도 오류발생X.  알려주지 않으니 문제.

선언단계와 초기화 단계가 동시에 진행.

 

3. 선언단계와 초기화단계가 동시에 진행된다.

그 뜻은, 변수 선언문 전에 변수에 접근해도 에러가 발생하지 않고 undefined가 출력된다.

(메모리에 공간까지 확보했으니까)

cf. 변수 호이스팅이 가능해서 가능한 일.

console.log(bae);	// undefined	
// 호이스팅이 일어나기 때문에 변수가 선언된 것으로 적용된다.
// => "여기 해당 블록스코프에서 변수 사용할게!" (선언단계)
// => 메모리에 변수 공간 확보 undefined로 초기화(초기화단계)

var bae;
console.log(bae);	// undefined

bae = 1;
console.log(bae);	// 1
// 할당문에 도달해야 할당단계가 실행된다!

var로 선언된 변수의 생명주기

 

 

 

let 

1. 블록 레벨 스코프를 따른다.

2. 중복 선언 허용 안함.

3. 선언 단계와 초기화 단계 분리되어 진행.

  var과 달리 let으로 선언된 변수를 변수선언문 이전에 참조하면 referenceError가 발생한다.

  왜?

  let과 const는 TDZ의 영향을 받음.

 

TDZ(Temporal Dead Zone: 일시적 사각지대)

스코프의 시작 지점부터 초기화 시작 지점까지의 구간.

let으로 선언된 변수의 생명주기

 

let bae = 1;

if (1) {
	console.log(bae);	// 1이 출력될 것 같지만 => Reference에러가 뜬다.
	let bae = 2;
}

=> 왜냐하면 bae라는 변수는 이미 전역변수로 선언되었지만, 블록 스코프 안에서 같은이름으로 지역변수가 선언되었기 때문에 블록스코프 시작지점에서 ~~~ 초기화 구간까지 TDZ에 빠진다!

 

cf. let이나 const로 선언한 변수는 전역 객체의 프로퍼티가 아니다. ( var로 선언한 변수를 전역변수로 쓰면 전역 객체의 프로퍼티가 됨.)

전역변수로 let bae = 2; 선언해도 전역 스코프에 바인딩되어 전역변수가 되는것이지 전역객체에 바인딩이 되지 않는다고 함.

=> 일반적으로 Browser-side에서의 전역객체는 window객체를 의미함. (server-side즉 node.js에서는 global객체)

 

const

1. 선언과 초기화를 동시에 진행해야 한다. (재선언, 재할당 불가능)

2. 재할당은 불가능하나, 객체의 내용을 수정할 수는 있다.

  (객체의 프로퍼티는 보호되지 않음.)

 

cf. 재할당과 수정의 차이

 - 재할당 : 변수에 할당할 값의 메모리 주소를 새롭게 부여.

 - 수정 : 메모리 주소를 참조해 요소를 바꾸는거.

let obj = {
    name: "bae",
    gender: "male",
    age: 27,
    height: 178,
}

//수정
obj.age = 25

//재할당
obj = {
    name: "bae",
    gender: "male",
    age: 25,
    score: 100,
}

 

★3. 호이스팅

1. 정의

변수, 함수 선언이 스코프 최상단으로 옮긴 것처럼 동작하는 자바스크립트의 특성.

 

2. 설명

var, const, let 중 어떤 키워드로 변수를 선언하느냐에 따라 호이스팅의 방식이 조금 다르다.

 

이 내용은 위에서도 다뤘지만 다시 정리하자면,

JS에서 변수 생성은 선언 -> 초기화 -> 할당 단계로 이루어짐.

 

var - 변수 선언문 이전에 참조하면 undefined가 발생. (호이스팅 특성 + var는 선언단계와 초기화단계가 동시 진행하므로)

let 과 const - 변수는 hoisting되지만, 값은 초기화되지 않는다. (선언단계와 초기화단계가 나눠져 있으므로)

따라서 const와 let으로 선언한 변수를 변수 선언 전에 사용하려고 하면 Reference Error나온다.

이 스코프의 시작지점에서 변수의 선언문까지 구간을 TDZ(Temporal Dead Zone)라고 한다.

 

 

결론

기본적으로 변수의 스코프는 최대한 좁게 만드는 것을 권장하므로, var보다는 let과 const를 사용하고,

상수라면 const를 활용하는 것이 바람직하다.

  재할당 재선언 스코프
var O O 함수레벨
let O X 블록레벨
const X X 블록레벨

 

 

 

 

 

참고 : https://poiemaweb.com/es6-block-scope

문제링크

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

 

1256번: 사전

동호와 규완이는 212호에서 문자열에 대해 공부하고 있다. 김진영 조교는 동호와 규완이에게 특별 과제를 주었다. 특별 과제는 특별한 문자열로 이루어 진 사전을 만드는 것이다. 사전에 수록되

www.acmicpc.net

문제설명

문제

동호와 규완이는 212호에서 문자열에 대해 공부하고 있다. 김진영 조교는 동호와 규완이에게 특별 과제를 주었다. 특별 과제는 특별한 문자열로 이루어 진 사전을 만드는 것이다. 사전에 수록되어 있는 모든 문자열은 N개의 "a"와 M개의 "z"로 이루어져 있다. 그리고 다른 문자는 없다. 사전에는 알파벳 순서대로 수록되어 있다.

규완이는 사전을 완성했지만, 동호는 사전을 완성하지 못했다. 동호는 자신의 과제를 끝내기 위해서 규완이의 사전을 몰래 참조하기로 했다. 동호는 규완이가 자리를 비운 사이에 몰래 사전을 보려고 하기 때문에, 문자열 하나만 찾을 여유밖에 없다.

N과 M이 주어졌을 때, 규완이의 사전에서 K번째 문자열이 무엇인지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 세 정수 N, M, K가 순서대로 주어진다.

출력

첫째 줄에 규완이의 사전에서 K번째 문자열을 출력한다. 만약 규완이의 사전에 수록되어 있는 문자열의 개수가 K보다 작으면 -1을 출력한다.

제한

  • 1 ≤ N, M ≤ 100
  • 1 ≤ K ≤ 1,000,000,000
 
문제풀이

문제 분석

- K번째 문자열 구해야한다.

- 주어진 입력값 K가,  만들 수 있는 문자열 조합 개수보다 크다면 -1 출력하자.

- N개의 a와 M개의 z로 만들 수 있는 문자열 조합 경우의 수를 구하자.

 

핵심 아이디어

D[N][M] = N개의 a와 M개의 z로 만들 수 있는 문자열 조합 개수

 

예를들어, N = 2, M = 2, K = 2 라면 가능한 문자열 조합은 다음과 같다.

[ aazz, azaz, azza, zaaz, zaza, zzaa ]

 

그렇다면,

D[N-1][M] 은 무엇을 의미할까?

문자열의 맨앞을 a로 고정한다는 의미.

 

왜?

D[1][2] 일 경우 가능한 조합은 [ azz, zaz, zaa ] ( a가 1개, z가 2개이니까) 로

모두 맨앞에 a를 이어 D[2][2]에 포함될 후보군인 셈이다.

([aazz, azaz, azza] 가 될 예정.)


근데 주어진 K(여기선 2로 가정) 가 저 조합 개수에 포함되므로,

앞자리는 a로 고정.

그리고 N은 1감소하여 N -= 1

 

이런식으로 N, M, K을 조금씩 감소 시키며 문자열의 첫자리부터 알파벳을 하나씩 고정해나간다는 아이디어로 구현해보았다.

 

 

코드
import sys

input = sys.stdin.readline
N, M, K = map(int, input().split())
D = [[0 for _ in range(M+1)] for _ in range(N+1)]
answer = ''

#a와 z중 하나로만 만들 수 있는 문자열 조합 경우의수 전부 1로 초기화
for i in range(N+1):
    D[i][0] = 1
for j in range(M+1):
    D[0][j] = 1 

# 점화식 기반으로 경우의수 계산
for i in range(1, N+1):
    for j in range(1, M+1):
        D[i][j] = D[i-1][j] + D[i][j-1]

maxArr = list(map(max, D))
maxVal = max(maxArr)

if maxVal < K:
    print(-1)
else:
    while N > 0 and M > 0:
        tmp = D[N-1][M] # 현재 타겟 인덱스의 알파벳을 a로 고정했을때, 후보군 경우의수

        if K <= tmp:  # 현재 알파벳을 a로 고정한 인덱스 범위 안에 K가 속해있을때,
            N -= 1  # 그 자리 a로 확정
            answer += 'a'
        else: # K가 더 크다는 말은, a를 고정한 인덱스들의 범위안에 없으므로 z가 되어야한다.
            M -= 1
            K -= tmp
            answer += 'z'

    # N, M 중 하나가 0이 되어 반복문 빠져나오면, 나머지 하나 마저 이어주기
    answer += ('a' * N + 'z' * M)
    print(answer)​
문제링크

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

 

1722번: 순열의 순서

첫째 줄에 N(1 ≤ N ≤ 20)이 주어진다. 둘째 줄의 첫 번째 수는 소문제 번호이다. 1인 경우 k(1 ≤ k ≤ N!)를 입력받고, 2인 경우 임의의 순열을 나타내는 N개의 수를 입력받는다. N개의 수에는 1부터 N

www.acmicpc.net

문제설명

 

문제

1부터 N까지의 수를 임의로 배열한 순열은 총 N! = N×(N-1)×…×2×1 가지가 있다.

임의의 순열은 정렬을 할 수 있다. 예를 들어  N=3인 경우 {1, 2, 3}, {1, 3, 2}, {2, 1, 3}, {2, 3, 1}, {3, 1, 2}, {3, 2, 1}의 순서로 생각할 수 있다. 첫 번째 수가 작은 것이 순서상에서 앞서며, 첫 번째 수가 같으면 두 번째 수가 작은 것이, 두 번째 수도 같으면 세 번째 수가 작은 것이….

N이 주어지면, 아래의 두 소문제 중에 하나를 풀어야 한다. k가 주어지면 k번째 순열을 구하고, 임의의 순열이 주어지면 이 순열이 몇 번째 순열인지를 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 N(1 ≤ N ≤ 20)이 주어진다. 둘째 줄의 첫 번째 수는 소문제 번호이다. 1인 경우 k(1 ≤ k ≤ N!)를 입력받고, 2인 경우 임의의 순열을 나타내는 N개의 수를 입력받는다. N개의 수에는 1부터 N까지의 정수가 한 번씩만 나타난다.

출력

k번째 수열을 나타내는 N개의 수를 출력하거나, 몇 번째 수열인지를 출력하면 된다.

예제 입력 1 복사

4
1 3

예제 출력 1 복사

1 3 2 4

 

문제풀이

이해하는데 한참 걸린 문제다.

 

코드
import sys
from math import factorial

input = sys.stdin.readline
N = int(input())
  
# 자리별로 만들수 있는 순열의 경우의수 저장(팩토리얼 형태임).
Fac = [factorial(i) for i in range(N-1, -1, -1)]
visited = [False] * 21  # 숫자 사용 여부 저장
inputArr = list(map(int,(input().split())))

if inputArr[0] == 1:
    K = inputArr[1] - 1
    numbers = list(range(1, N+1)) #1부터 N까지 배열 생성
    answer = []

    for i in range(N):
        index = K // Fac[i] # K를 자리의 경우의 수로 나눈 몫(정수)
                            # 몫이 0이면 그자리에 그 수를 써도 된다는 얘기
        answer.append(numbers[index])
        numbers.pop(index)
        K %= Fac[i]
    print(*answer)


else:
    numSeq = inputArr[1:]
    K = 1

    for i in range(N):
        dNum = numSeq[i] - 1
        for j in range(i):
            if numSeq[j] < numSeq[i]:
                dNum -= 1
        K += dNum * Fac[i]
    print(K)

스코프

const와 let을 사용해서 함수 외부에서 사용된 변수를 함수 내부에서도 사용하게 되면 어떻게 될까?

블록 내부의 feedback 변수는 서로 다른 스코프를 가지고 있기 때문에 서로 다른 변수로 여겨진다

let name = 'Son';

function f1() {
	let name = 'Kim';
	f2();
}

function f2() {
	console.log(name);
}

f1();  // ? => Son

다음 코드를 살펴보자.

let x = 10;

function foo() {
	x = 100;
	console.log(x);
}

foo();  // 100
console.log(x); // ? => 100

이와 같은 경우는 중복선언이 아니라 글로벌 영역에서 선언된 변수 x를,

함수 foo에서 변수에 값을 재할당 하는 것이므로 x값이 변경된다.

 

 

실행 컨텍스트 관점에서 설명해보자면,

위 코드에서는 총 1. 전체스크립트 렉시컬 environment 2. 함수 렉시컬 environment , 이렇게 두가지가 만들어진다.

 

foo() 함수를 실행하는 순간 함수 렉시컬 환경이 생성되는데

이 렉시컬 환경은 환경 레코드와 외부 렉시컬 환경으로 구성되어 있음.

 

환경 레코드에는 선언된 변수,함수, this 값 등이 저장되고,

외부렉시컬 environment에는 부모환경에 접근 할 수 있는 참조값을 가지고 있다.

 

함수가 선언된 시점 기준으로 그 외부 환경을 참조하므로 외부환경인 글로벌 렉시컬 env의 환경레코드에 있는 변수 x를 참조 할 수 있다.

 

⇒ 요약 : foo 함수내에서 글로벌 영역의 환경 레코드에 있는 변수 x를 참조할 수 있다. 변수값이 변경됨.

“렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정된다.

이 말이 방금 설명한 내용 그대로다.

 

 

암묵적 전역

console.log(x);  // undefined
console.log(y);  // Reference Error

let x = 10;

function foo() {
	y = 100;
	console.log(x + y);
}

foo();  // ? => 110

y가 선언된 스코프를 함수 스코프와 글로벌 스코프에서도 찾을 수 없기 때문에

foo함수를 실행하면 참조에러가 발생해야 할 것 같으나,

자바스크립트 엔진은 y = 20을 window.y = 20으로 해석하여 프로퍼티를 동적 생성한다.

 

결국 y는 전역 객체의 프로퍼티가 되어 마치 전역 변수처럼 동작한다. 이러한 현상을 **암묵적 전역(implicit global)**이라 한다. 하지만 winodw객체의 프로퍼티일 뿐, 변수가 아니므로 호이스팅은 발생안함!

JavaScript의 특징

 - 웹 브라우저에서 동작하는 유일한 프로그래밍 언어

 - 인터프리터 언어

 - 크롬 V8엔진 같은 모던 JS엔진은 인터프리터 + 컴파일러 장점 결합 => 처리속도 느리다는 인터프리터의 단점 극복.

 

 

ES6 주요 특징

1. const, let (Block Level Scope)

before es6.

var

- 함수 레벨 스코프 

- 변수의 선언과 초기화가 동시에 일어남

=> 선언 이전에 참조시, undefined 출력 (에러가 안뜸)

- 할당 전에 변수를 참조해도 undefined가 출력.

  (호이스팅이 일어나기때문에)

console.log(name) // undefined

var name = 'bae'

 

after es6.

let, const

- 블록 레벨 스코프

- 선언과 초기화 단계가 나누어짐. 스코프에 변수를 등록은 하지만, 초기화 단계는 변수 선언문에 도달해야 이루어지므로

=> 선언 이전에 참조시, TDZ에 빠져 reference Error 발생

console.log(name) // Reference Error

let name = 'bae'

2. Template Literals

문자열 중간에 변수나 함수호출 등 삽입할때 + '' 조합 사용 안해도 됨.

let name = 'bae';

// Before es6
console.log("Hi, I am" + name + '.');

// After es6
console.log(`Hi, I am ${name}.`);

3. Arrow Functions

화살표 함수가 등장하면서 코드 양을 줄일 수 있게됨

 

규칙1. 화살표함수가 전달받는 인자가 1개가 아닌, 2개 이상인 경우 괄호 사용

규칙2. 화살표함수 내용에 한줄이상의 코드를 작성하려면 => 중괄호와 return 사용

 

4. Spread Operator

- Spread 연산자는 배열이나 객체의 속성을 펼쳐서 사용할 수 있게 해준다.

- ★ES6에서 객체나 배열의 깊은 복사를 위해 사용되는 대표적인 방법이다. 

 

cf. 깊은복사 vs 얕은 복사

얕은복사는 참조값을 복사 (즉 복사본을 만드는 것이 아니라 참조값을 통해 객체를 공유)

깊은복사는 값을 복사 (독립적인 메모리에 값 자체를 할당하여 생성, 즉 객체의 복사본을 만든다.) 

 

ES5 에서는 객체 프로퍼티를 다른 객체에 복사하는 방법은

   [1] Object.assgin() 활용

   [2] 수동으로 단일로 접근하여 복사

하는 방법이 있다.

 

 

- ES5의 Object.assgin()(객체), apply() (배열)를 대체한다.

// ES5
var person = {
    name: 'bae',
    age: 28
};

// ES5 - 1. Object.assgin() 활용 
var updatedPerson = Object.assign({}, person, {
    age: 25,
    gender: 'male'
});

// ES5 - 2. 수동으로 복사
var updatedPerson = {
    name: person.name,
    age: 25, // 새로운 값으로 업데이트
    gender: 'male'
};

console.log(person); // { name: 'bae', age: 28 }
console.log(updatedPerson); // { name: 'bae', age: 25, gender: 'male' }


// ES6 - Spread 연산자
const person = {
    name: 'bae',
    age: 28,
}

const updatedPerson = {
    ...person,
    age: 25,
    gender: male;
}

console.log(person) // { name: 'bae', age: 28 }
console.log(updatedPerson) // {name: 'bae', age: 25, gender: male }

5. Rest Parameter

spread 연산자와 똑같이 생겼지만, 반대의 역할을 한다.

=> 즉, 개별 요소를 하나의 배열로 만들어 주는 역할

 

주로 함수의 파라미터를 동적으로 처리할때 유용하다고한다.

함수에서 개수가 정해지지 않은 여러 인자들을 배열로 처리하게 해줌

function sum(...numbers) {
    return numbers.reduce((acc, curr) => acc + curr, 0);
}


console.log(sum(1, 5, 8)); // 14
console.log(sum(1, 2, 3, 4, 5)); // 15

 

 

6. Destruturing Assginment

'구조분해할당'

객체와 배열로부터 프로퍼티를 쉽게 꺼낼 수 있는 문법

// 이렇게 객체가 있다고 가정
const person1 = {
	name: 'bae',
    age: 28,
    gender: male,
    phoneNumber: '010-0000-0000',
}

// ES5
var name = person.name;
var age: person.age;
var gender: person.gender;


// ES6
let { name, age, gender, phonNumber } = person;

=> 객체 안의 프로퍼티를 가져오는 코드가 확실히 간결해지는것을 볼 수 있다.

7. Promise

자바스크립트의 비동기 콜백지옥을 해결하기 위해 나온 객체.

내용은 실행되었으나 결과를 아직 반환하지 않은 객체이다.

pending상태, 이행상태, reject상태 이렇게 3가지 상가 있다.

 

cf. 콜백지옥: 콜백함수를 전달하는 과정이 반복되어 가독성이 떨어질 정도로 깊어지는 현상. 주로 이벤트 처리나 서버 통신과 같은 비동기 작업 수행을 위해 콜백지옥이 자주 등장했다고 한다.

 

8. Class

자바스크립트는 프로토타입 기반 객체지향 언어이다.

ES5에서는 생성자 함수, 프로토타입, 클로저를 사용해 상속 및 캡슐화의 개념을 구현할 수 있었다.

 

ES6부터는 다른 객체지향 언어처럼 class 키워드를 사용하여 클래스를 정의할 수 있다.

getter, setter를 활용한 필드값 조회 및 할당, extends 키워드를 사용하여 상속기능 등이 가능 

 

9. Module

처음으로 모듈에 대한 표준이 도입되었고, 그 이전에는 CommonJS 등의 방식의 모듈시스템 사용.

- export, import등을 활용해 JS파일 자체가 모듈이 되어, 다른 모듈에서 가져오거나 내보낼수있다.

- 생성한 js모듈을 브라우저가 인식할수있어야 하므로 script 태그에 type="module"을 지정한다.

 

=> 모듈을 사용할 수 있게 되면서부터, 재사용 가능 코드를 캡슐화 할 수 있게 되었다.

=> 파일단위의 모듈 스코프를 외부에서 사용 가능하게 되었다.

ES8

Async, Await 

Callback과 Promise 처럼 비동기 처리에 쓰이는 문법이다.

Callback과 Promise보다 직관적이고, 이전 문법에서 나왔던 꼬리에 꼬리를 무는 코드가 나오는 단점을 해결하였다.

 

하지만 Async/ Await 문법은 Promise와는 달리 에러 헨들링 기능이 없기 때문에 Try~ catch 문을 활용해 에러 핸들링을 해야한다고 한다.

문제링크

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

 

14501번: 퇴사

첫째 줄에 백준이가 얻을 수 있는 최대 이익을 출력한다.

www.acmicpc.net

문제설명

 

문제

상담원으로 일하고 있는 백준이는 퇴사를 하려고 한다.

오늘부터 N+1일째 되는 날 퇴사를 하기 위해서, 남은 N일 동안 최대한 많은 상담을 하려고 한다.

백준이는 비서에게 최대한 많은 상담을 잡으라고 부탁을 했고, 비서는 하루에 하나씩 서로 다른 사람의 상담을 잡아놓았다.

각각의 상담은 상담을 완료하는데 걸리는 기간 Ti와 상담을 했을 때 받을 수 있는 금액 Pi로 이루어져 있다.

N = 7인 경우에 다음과 같은 상담 일정표를 보자.

  1일 2일 3일 4일 5일 6일 7일
Ti 3 5 1 1 2 4 2
Pi 10 20 10 20 15 40 200

1일에 잡혀있는 상담은 총 3일이 걸리며, 상담했을 때 받을 수 있는 금액은 10이다. 5일에 잡혀있는 상담은 총 2일이 걸리며, 받을 수 있는 금액은 15이다.

상담을 하는데 필요한 기간은 1일보다 클 수 있기 때문에, 모든 상담을 할 수는 없다. 예를 들어서 1일에 상담을 하게 되면, 2일, 3일에 있는 상담은 할 수 없게 된다. 2일에 있는 상담을 하게 되면, 3, 4, 5, 6일에 잡혀있는 상담은 할 수 없다.

또한, N+1일째에는 회사에 없기 때문에, 6, 7일에 있는 상담을 할 수 없다.

퇴사 전에 할 수 있는 상담의 최대 이익은 1일, 4일, 5일에 있는 상담을 하는 것이며, 이때의 이익은 10+20+15=45이다.

상담을 적절히 했을 때, 백준이가 얻을 수 있는 최대 수익을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N (1 ≤ N ≤ 15)이 주어진다.

둘째 줄부터 N개의 줄에 Ti와 Pi가 공백으로 구분되어서 주어지며, 1일부터 N일까지 순서대로 주어진다. (1 ≤ Ti ≤ 5, 1 ≤ Pi ≤ 1,000)

출력

첫째 줄에 백준이가 얻을 수 있는 최대 이익을 출력한다.

예제 입력 1 

7
3 10
5 20
1 10
1 20
2 15
4 40
2 200

예제 출력 1 

45
문제풀이

 

코드
N = int(input())

T = [0] * (N+1)
P = [0] * (N+1)
D = [0] * (N+2)

for i in range(1, N+1):
    T[i], P[i] = map(int, input().split())

for i in range(N, 0, -1):
    if i+T[i] > N+1:
        D[i] = D[i+1]
    else:
        D[i] = max(D[i+1], D[i+T[i]] + P[i])

print(D[1])

+ Recent posts