문제링크

https://leetcode.com/problems/pacific-atlantic-water-flow/description/

 

Pacific Atlantic Water Flow - LeetCode

Can you solve this real interview question? Pacific Atlantic Water Flow - There is an m x n rectangular island that borders both the Pacific Ocean and Atlantic Ocean. The Pacific Ocean touches the island's left and top edges, and the Atlantic Ocean touches

leetcode.com

문제설명

 

There is an m x n rectangular island that borders both the Pacific Ocean and Atlantic Ocean. The Pacific Ocean touches the island's left and top edges, and the Atlantic Ocean touches the island's right and bottom edges.

The island is partitioned into a grid of square cells. You are given an m x n integer matrix heights where heights[r][c] represents the height above sea level of the cell at coordinate (r, c).

The island receives a lot of rain, and the rain water can flow to neighboring cells directly north, south, east, and west if the neighboring cell's height is less than or equal to the current cell's height. Water can flow from any cell adjacent to an ocean into the ocean.

Return a 2D list of grid coordinates result where result[i] = [ri, ci] denotes that rain water can flow from cell (ri, ci) to both the Pacific and Atlantic oceans.

 

Example 1:

Input: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
Output: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
Explanation: The following cells can flow to the Pacific and Atlantic oceans, as shown below:
[0,4]: [0,4] -> Pacific Ocean 
       [0,4] -> Atlantic Ocean
[1,3]: [1,3] -> [0,3] -> Pacific Ocean 
       [1,3] -> [1,4] -> Atlantic Ocean
[1,4]: [1,4] -> [1,3] -> [0,3] -> Pacific Ocean 
       [1,4] -> Atlantic Ocean
[2,2]: [2,2] -> [1,2] -> [0,2] -> Pacific Ocean 
       [2,2] -> [2,3] -> [2,4] -> Atlantic Ocean
[3,0]: [3,0] -> Pacific Ocean 
       [3,0] -> [4,0] -> Atlantic Ocean
[3,1]: [3,1] -> [3,0] -> Pacific Ocean 
       [3,1] -> [4,1] -> Atlantic Ocean
[4,0]: [4,0] -> Pacific Ocean 
       [4,0] -> Atlantic Ocean
Note that there are other possible paths for these cells to flow to the Pacific and Atlantic oceans.

Example 2:

Input: heights = [[1]]
Output: [[0,0]]
Explanation: The water can flow from the only cell to the Pacific and Atlantic oceans.

 

Constraints:

  • m == heights.length
  • n == heights[r].length
  • 1 <= m, n <= 200
  • 0 <= heights[r][c] <= 105
문제풀이

 

처음엔 여타 그래프 탐색 문제를 풀듯이 인접행렬로 BFS 돌려서 조건에 맞게 좌표를 산출하면 되겠거니 하는 안일한 생각이었다.

 

하지만 이중for문으로 모든 좌표에서 BFS를 시행하는 것이 과연 맞는 전략일까 라는 의문이 들었고,

또 우측, 하측에 있는 Atlantic 바다와 좌, 상측에 있는 Pacific 바다로

모두 흐르는 좌표를 체킹하는 접근법이 떠오르지 않아서 레퍼런스를 참고했다.

 

그 결과 다시 세운 핵심전략은,

1. 시작좌표를 각 바다와 인접한 좌표들로만 배열을 구성한다. (Pacific, Atlantic 나눠서)
2. 파이썬의 set() 을 사용
    - Pacific으로 흐를 수 있는 좌표들의 set(),  Atlantic으로 흐를 수 있는 좌표들의 set() 을 따로 구하기.
        => 따로 BFS시행해야겠지
    - BFS 내에서도 visited라는 set()을 활용. 바다와 인접한 스타트 좌표들을 전부삽입.
        그리고 BFS를 통해 조건에 부합하여 큐에 삽입될때마다(방문할 때마다) 삽입  
3. 구한 집합들의 교집합을 구하면 된다. (set문법 활용)

 

리트코드는 다른 알고리즘 플랫폼들과 달리 이렇게 본인이 푼 솔루션의 시간복잡도, 공간복잡도 랭킹(?)도 알 수 있다.

 

코드
from collections import deque

class Solution:
    def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
        d = [(0, 1), (0, -1), (1, 0), (-1, 0)]    #동, 서 남, 북
        m = len(heights)
        n = len(heights[0])
        pacific = set()		# pacific 바다로 흐를 수 있는 좌표들 집합
        atlantic = set()	# atlantic 으로 흐를 수 있는 좌표들 집합

        def bfs(starts):
            queue = deque(starts)
            visited = set(starts)

            while queue:
                curX, curY = queue.popleft()
                for dx, dy in d:
                    nx = curX + dx
                    ny = curY + dy

                    # 육지내에 속하고, 방문하지 않았고, 이전 셀의 높이보다 높아야함.
                    if 0 <= nx < m and 0 <= ny < n and (nx, ny) not in visited and heights[nx][ny] >= heights[curX][curY]:
                        queue.append((nx, ny))
                        visited.add((nx, ny))
            
            return visited

        # 각 바다와 인접한부분의 좌표를 각 바다의 스타트 배열로 만든다.
        pStart = [(0, i) for i in range(n)] + [(i, 0) for i in range(1, m)]
        aStart = [(m-1, i) for i in range(n)] + [(i, n-1) for i in range(m-1)]

        pacific = bfs(pStart)
        atlantic = bfs(aStart)
    
        return list(pacific & atlantic) # 교집합 구해서 리스트 변환.
        # 핵심 아이디어: BFS
    
        # for문 돌려서 각지점에서 BFS 전부 시행??
        # 아틀란틱 => 우, 하 / 패시픽 -> 좌, 상 인데 체크조건을 어떻게 잡아야할까??
문제링크

https://school.programmers.co.kr/learn/courses/30/lessons/42577?language=python3

 

프로그래머스

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

programmers.co.kr

문제설명

문제 설명

전화번호부에 적힌 전화번호 중, 한 번호가 다른 번호의 접두어인 경우가 있는지 확인하려 합니다.
전화번호가 다음과 같을 경우, 구조대 전화번호는 영석이의 전화번호의 접두사입니다.

구조대 : 119
박준영 : 97 674 223
지영석 : 11 9552 4421


전화번호부에 적힌 전화번호를 담은 배열 phone_book 이 solution 함수의 매개변수로 주어질 때, 어떤 번호가 다른 번호의 접두어인 경우가 있으면 false를 그렇지 않으면 true를 return 하도록 solution 함수를 작성해주세요.

제한 사항
phone_book의 길이는 1 이상 1,000,000 이하입니다.
각 전화번호의 길이는 1 이상 20 이하입니다.
같은 전화번호가 중복해서 들어있지 않습니다.

 

문제풀이

 

 나의 아이디어
 번호의 길이가 다른데, 짧은번호가 배열의 앞에와야 유리한데..
 0번째 인덱스값과 그 나머지값을 비교하는 방식으로 늘려나가면서 검사?


 1. 배열을 받아서 각 문자열 값을 숫자로 변환한후 오름차순 정렬

변환하는 과정중에 

for item in arr:

   item = int(item) 으로 하니 배열값이 갱신이 안되어

 

for i in range()를 사용했다.

근데 나중에보니 오름차순 정렬을 문자열 상태일 때 해도 된다는걸 잊고있었다....

 

결국 정렬 후 감이 잘 안와서, 해시테이블을 쓰기로 했다.

 

구현할 로직

1. 폰북 배열 속 원소를 key로 하는 myDict 키,값 생성.
2. 폰북 속을 탐색하며 item 마다 각 item의 문자를 부분문자열로 하는 부분문자열 생성.
3. 그리고 그 부분 문자열이 myDict의 key로 속해있는지 검사
4. + 자기자신이 아닌지 검사
5. 조건문 하나라도 충족하면 False값 반환

 

 

코드
def solution(phone_book):
    answer = True
    myDict =  {}
    
    for item in phone_book:
        myDict[item] = 1

    for item in phone_book:
        part = ""
        for num in item:
            part += num # 문자를 더해가며 부분문자열 생성
            if part in myDict and part != item:
                return False
    # 1. 폰북 배열 속 원소를 key로 하는 myDict 키,값 생성.
    # 2. 폰북 속을 탐색하며 item 마다 각 item의 문자를 부분문자열로 하는 부분문자열 생성.
    # 3. 그리고 그 부분 문자열이 myDict의 key로 속해있는지 검사
    # 4. 자기자신이 아닌지 검사
    # 5. 조건문 하나라도 충족하면 False값 반환
    return answer
문제링크

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

 

12891번: DNA 비밀번호

평소에 문자열을 가지고 노는 것을 좋아하는 민호는 DNA 문자열을 알게 되었다. DNA 문자열은 모든 문자열에 등장하는 문자가 {‘A’, ‘C’, ‘G’, ‘T’} 인 문자열을 말한다. 예를 들어 “ACKA”

www.acmicpc.net

문제설명

문제

평소에 문자열을 가지고 노는 것을 좋아하는 민호는 DNA 문자열을 알게 되었다. DNA 문자열은 모든 문자열에 등장하는 문자가 {‘A’, ‘C’, ‘G’, ‘T’} 인 문자열을 말한다. 예를 들어 “ACKA”는 DNA 문자열이 아니지만 “ACCA”는 DNA 문자열이다. 이런 신비한 문자열에 완전히 매료된 민호는 임의의 DNA 문자열을 만들고 만들어진 DNA 문자열의 부분문자열을 비밀번호로 사용하기로 마음먹었다.

하지만 민호는 이러한 방법에는 큰 문제가 있다는 것을 발견했다. 임의의 DNA 문자열의 부분문자열을 뽑았을 때 “AAAA”와 같이 보안에 취약한 비밀번호가 만들어 질 수 있기 때문이다. 그래서 민호는 부분문자열에서 등장하는 문자의 개수가 특정 개수 이상이여야 비밀번호로 사용할 수 있다는 규칙을 만들었다.

임의의 DNA문자열이 “AAACCTGCCAA” 이고 민호가 뽑을 부분문자열의 길이를 4라고 하자. 그리고 부분문자열에 ‘A’ 는 1개 이상, ‘C’는 1개 이상, ‘G’는 1개 이상, ‘T’는 0개 이상이 등장해야 비밀번호로 사용할 수 있다고 하자. 이때 “ACCT” 는 ‘G’ 가 1 개 이상 등장해야 한다는 조건을 만족하지 못해 비밀번호로 사용하지 못한다. 하지만 “GCCA” 은 모든 조건을 만족하기 때문에 비밀번호로 사용할 수 있다.

민호가 만든 임의의 DNA 문자열과 비밀번호로 사용할 부분분자열의 길이, 그리고 {‘A’, ‘C’, ‘G’, ‘T’} 가 각각 몇번 이상 등장해야 비밀번호로 사용할 수 있는지 순서대로 주어졌을 때 민호가 만들 수 있는 비밀번호의 종류의 수를 구하는 프로그램을 작성하자. 단 부분문자열이 등장하는 위치가 다르다면 부분문자열이 같다고 하더라도 다른 문자열로 취급한다.

입력

첫 번째 줄에 민호가 임의로 만든 DNA 문자열 길이 |S|와 비밀번호로 사용할 부분문자열의 길이 |P| 가 주어진다. (1 ≤ |P| ≤ |S| ≤ 1,000,000)

두번 째 줄에는 민호가 임의로 만든 DNA 문자열이 주어진다.

세번 째 줄에는 부분문자열에 포함되어야 할 {‘A’, ‘C’, ‘G’, ‘T’} 의 최소 개수가 공백을 구분으로 주어진다. 각각의 수는 |S| 보다 작거나 같은 음이 아닌 정수이며 총 합은 |S| 보다 작거나 같음이 보장된다.

출력

첫 번째 줄에 민호가 만들 수 있는 비밀번호의 종류의 수를 출력해라.

예제 입력 1 복사

9 8
CCTGGATTG
2 0 1 1

예제 출력 1 복사

0

예제 입력 2 복사

4 2
GATA
1 0 0 1

예제 출력 2 복사

2
문제풀이

 

핵심

- 문자열에서 특정 조건을 만족하는 크기가 고정된 부분 문자열을 탐색하기 위해 슬라이딩 윈도우 활용

- 딕셔너리를 사용하여 각 필요한 알파벳 충족개수를 비교하는 로직에 활용하였다.

 

시간복잡도를 줄이기 위해 활용하는 슬라이딩 윈도우의 본질을 잊지말자!

(이중반복문 X)

 

반복문 작성시에는 항상 인덱스를 초과하지 않도록 유념해야하는 것 같다.

해당 문제 같은 경우는, 오른쪽 포인터가 인덱스를 벗어나지 않도록 j += 1 문 이전에 

맨 오른쪽 원소를 추가하는 로직을 작성하였다.

 

시간복잡도는 O(N) 이다.

코드
s, p = map(int, input().split())
data = input()
acnt, ccnt, gcnt, tcnt = map(int, input().split())
answer = 0
# 두개의 포인터 지정
i = 0
j = p - 1

# 딕셔너리
dict = { 'A': 0, 'C': 0, 'G': 0, 'T': 0 }
targetStr = data[i:j]

for item in targetStr:
    dict[item] += 1

# 슬라이딩 윈도우
while j <= s-1:
    # 인덱스 초과 나니까 j+=1 하기전에, 오른쪽 원소 추가하는 로직을 먼저.
    dict[data[j]] += 1

    # 비밀번호 유효성 검사
    if dict['A'] >= acnt and dict['C'] >= ccnt and dict['G'] >= gcnt and dict['T'] >= tcnt:
        answer += 1

    dict[data[i]] -= 1 # 인덱스 맨 왼쪽 문자열 제거
    i += 1
    j += 1

print(answer)
문제링크

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

 

2644번: 촌수계산

사람들은 1, 2, 3, …, n (1 ≤ n ≤ 100)의 연속된 번호로 각각 표시된다. 입력 파일의 첫째 줄에는 전체 사람의 수 n이 주어지고, 둘째 줄에는 촌수를 계산해야 하는 서로 다른 두 사람의 번호가 주어

www.acmicpc.net

문제설명

문제

우리 나라는 가족 혹은 친척들 사이의 관계를 촌수라는 단위로 표현하는 독특한 문화를 가지고 있다. 이러한 촌수는 다음과 같은 방식으로 계산된다. 기본적으로 부모와 자식 사이를 1촌으로 정의하고 이로부터 사람들 간의 촌수를 계산한다. 예를 들면 나와 아버지, 아버지와 할아버지는 각각 1촌으로 나와 할아버지는 2촌이 되고, 아버지 형제들과 할아버지는 1촌, 나와 아버지 형제들과는 3촌이 된다.

여러 사람들에 대한 부모 자식들 간의 관계가 주어졌을 때, 주어진 두 사람의 촌수를 계산하는 프로그램을 작성하시오.

입력

사람들은 1, 2, 3, …, n (1 ≤ n ≤ 100)의 연속된 번호로 각각 표시된다. 입력 파일의 첫째 줄에는 전체 사람의 수 n이 주어지고, 둘째 줄에는 촌수를 계산해야 하는 서로 다른 두 사람의 번호가 주어진다. 그리고 셋째 줄에는 부모 자식들 간의 관계의 개수 m이 주어진다. 넷째 줄부터는 부모 자식간의 관계를 나타내는 두 번호 x,y가 각 줄에 나온다. 이때 앞에 나오는 번호 x는 뒤에 나오는 정수 y의 부모 번호를 나타낸다.

각 사람의 부모는 최대 한 명만 주어진다.

출력

입력에서 요구한 두 사람의 촌수를 나타내는 정수를 출력한다. 어떤 경우에는 두 사람의 친척 관계가 전혀 없어 촌수를 계산할 수 없을 때가 있다. 이때에는 -1을 출력해야 한다.

예제 입력 1 복사

9
7 3
7
1 2
1 3
2 7
2 8
2 9
4 5
4 6

예제 출력 1 복사

3

예제 입력 2 복사

9
8 6
7
1 2
1 3
2 7
2 8
문제풀이

 

주어진 친족관계들을 기반으로, 입력 받은 두 값 a, b의 촌수를 계산하는 문제였다.

 

핵심 아이디어

1. 인접리스트 형태로 저장.

2. ★그래프에 저장할때 양방향임을 고려 (주어진 입력값은 한 방향관계만을 나타냄)

3. a, b의 촌수를 구한다는건 => 그래프를 그려놓고 생각해보면 a부터 b까지의 최단거리

   따라서 BFS를 활용

4. 촌수 카운트를 위해 queue에 (노드번호, depth) 튜플형태로 삽입

5. BFS를 시행하다가 구하고자 하는 노드에 도달하면 depth 반환

6. BFS 끝났는데도 도달 안하면 -1 반환

 

코드
from collections import deque

n = int(input())
a, b = map(int, input().split())
m = int(input())
graph = [[] for _ in range(n+1)]
visited = [0] * (n+1)
queue = deque([])

# 양방향 그래프
for _ in range(m):
    s, e = map(int, input().split())
    graph[s].append(e)
    graph[e].append(s)

def BFS(a, b):
    queue.append([a, 0])
    visited[a] = 1

    while queue:
        cur, depth = queue.popleft()

        if cur == b:
            return depth

        for next in graph[cur]:
            if not visited[next]:
                queue.append([next, depth+1])
                visited[next] = 1

    return -1

print(BFS(a, b))
문제링크

https://swexpertacademy.com/main/code/problem/problemDetail.do?problemLevel=2&problemLevel=3&problemLevel=4&contestProbId=AV5PobmqAPoDFAUq&categoryId=AV5PobmqAPoDFAUq&categoryType=CODE&problemTitle=&orderBy=RECOMMEND_COUNT&selectCodeLang=ALL&select-1=4&pageSize=10&pageIndex=1

 

SW Expert Academy

SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!

swexpertacademy.com

문제설명

달팽이는 1부터 N*N까지의 숫자가 시계방향으로 이루어져 있다.

다음과 같이 정수 N을 입력 받아 N크기의 달팽이를 출력하시오.


[예제]

N이 3일 경우,
 


N이 4일 경우,
 

[제약사항]

달팽이의 크기 N은 1 이상 10 이하의 정수이다. (1 ≤ N ≤ 10)


[입력]

가장 첫 줄에는 테스트 케이스의 개수 T가 주어지고, 그 아래로 각 테스트 케이스가 주어진다.

각 테스트 케이스에는 N이 주어진다.


문제풀이

이차원 배열에서의 주어진 조건(여기서는 시계방향)대로 인덱스에 접근하기 위한 구현 문제다.

방향전환, 범위체크 등이 가장 핵심이었으며

이전에 많이 비슷한 문제를 많이 풀어봤었지만 응용하려면 또 생각이 더 필요한 문제였던 것 같다.

코드
T = int(input())

for tc in range(1, T+1):
    N = int(input())
    arr = [[0 for _ in range(N)] for _ in range(N)]

    dx = [0, 1, 0, -1] # (동, 남, 서, 북)
    dy = [1, 0, -1, 0]
    dir = 0
    r, c = 0, 0

    for num in range(1, N*N+1):
        # 숫자 삽입
        arr[r][c] = num
        # 이동
        r += dx[dir]
        c += dy[dir]

        # 해당 지역이 범위를 벗어났거나 이미 값이 있다면
        if r < 0 or r >= N or c < 0 or c >= N or arr[r][c]:
            # 이전상태로 다시이동 
            r -= dx[dir]
            c -= dy[dir]

            # 방향 바꾸기(동->남, 남->서, 서->북, 북 >동)
            dir = (dir + 1) % 4

            # 이동
            r += dx[dir]
            c += dy[dir]

    print('#' + str(tc))
    for row in arr:
        print(*row)
문제링크

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

 

14888번: 연산자 끼워넣기

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1인 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, 곱

www.acmicpc.net

문제설명

문제

N개의 수로 이루어진 수열 A1, A2, ..., AN이 주어진다. 또, 수와 수 사이에 끼워넣을 수 있는 N-1개의 연산자가 주어진다. 연산자는 덧셈(+), 뺄셈(-), 곱셈(×), 나눗셈(÷)으로만 이루어져 있다.

우리는 수와 수 사이에 연산자를 하나씩 넣어서, 수식을 하나 만들 수 있다. 이때, 주어진 수의 순서를 바꾸면 안 된다.

예를 들어, 6개의 수로 이루어진 수열이 1, 2, 3, 4, 5, 6이고, 주어진 연산자가 덧셈(+) 2개, 뺄셈(-) 1개, 곱셈(×) 1개, 나눗셈(÷) 1개인 경우에는 총 60가지의 식을 만들 수 있다. 예를 들어, 아래와 같은 식을 만들 수 있다.

  • 1+2+3-4×5÷6
  • 1÷2+3+4-5×6
  • 1+2÷3×4-5+6
  • 1÷2×3-4+5+6

식의 계산은 연산자 우선 순위를 무시하고 앞에서부터 진행해야 한다. 또, 나눗셈은 정수 나눗셈으로 몫만 취한다. 음수를 양수로 나눌 때는 C++14의 기준을 따른다. 즉, 양수로 바꾼 뒤 몫을 취하고, 그 몫을 음수로 바꾼 것과 같다. 이에 따라서, 위의 식 4개의 결과를 계산해보면 아래와 같다.

  • 1+2+3-4×5÷6 = 1
  • 1÷2+3+4-5×6 = 12
  • 1+2÷3×4-5+6 = 5
  • 1÷2×3-4+5+6 = 7

N개의 수와 N-1개의 연산자가 주어졌을 때, 만들 수 있는 식의 결과가 최대인 것과 최소인 것을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1인 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, 곱셈(×)의 개수, 나눗셈(÷)의 개수이다.

출력

첫째 줄에 만들 수 있는 식의 결과의 최댓값을, 둘째 줄에는 최솟값을 출력한다. 연산자를 어떻게 끼워넣어도 항상 -10억보다 크거나 같고, 10억보다 작거나 같은 결과가 나오는 입력만 주어진다. 또한, 앞에서부터 계산했을 때, 중간에 계산되는 식의 결과도 항상 -10억보다 크거나 같고, 10억보다 작거나 같다.

예제 입력 1 복사

2
5 6
0 0 1 0

예제 출력 1 복사

30
30

예제 입력 2 복사

3
3 4 5
1 0 1 0

예제 출력 2 복사

35
17
문제풀이

 

코드
N = int(input())
arr = list(map(int, input().split()))
add, sub, mul, div = map(int, input().split())
maxValue = - int(1e9)
minValue = int(1e9)

def DFS(add, sub, mul, div, total, i):
    global maxValue, minValue

    if i == N:
        maxValue = max(maxValue, total)
        minValue=  min(minValue, total)
        return
    # 연산자 삽입
    if add:
        DFS(add-1, sub, mul, div, total+arr[i], i+1)
    if sub:
        DFS(add, sub-1, mul, div, total-arr[i], i+1)
    if mul:
        DFS(add, sub, mul-1, div, total*arr[i], i+1)
    if div:
        DFS(add, sub, mul, div-1, int(total/arr[i]), i+1)
            

DFS(add, sub, mul, div, arr[0], 1)
print(maxValue)
print(minValue)
# 연산자 우선순위 무시
# 1 2 3 4 5 6
# + + - * /

# N개의 수와 N-1개의 연산자가 주어질때 만들수있는 결과가 최대인것과 최소인것 구하기
# 완전탐색
문제링크

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

 

프로그래머스

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

programmers.co.kr

문제설명

문제 설명

두 개의 단어 begin, target과 단어의 집합 words가 있습니다. 아래와 같은 규칙을 이용하여 begin에서 target으로 변환하는 가장 짧은 변환 과정을 찾으려고 합니다.

1. 한 번에 한 개의 알파벳만 바꿀 수 있습니다.
2. words에 있는 단어로만 변환할 수 있습니다.

예를 들어 begin이 "hit", target가 "cog", words가 ["hot","dot","dog","lot","log","cog"]라면 "hit" -> "hot" -> "dot" -> "dog" -> "cog"와 같이 4단계를 거쳐 변환할 수 있습니다.

두 개의 단어 begin, target과 단어의 집합 words가 매개변수로 주어질 때, 최소 몇 단계의 과정을 거쳐 begin을 target으로 변환할 수 있는지 return 하도록 solution 함수를 작성해주세요.

제한사항

  • 각 단어는 알파벳 소문자로만 이루어져 있습니다.
  • 각 단어의 길이는 3 이상 10 이하이며 모든 단어의 길이는 같습니다.
  • words에는 3개 이상 50개 이하의 단어가 있으며 중복되는 단어는 없습니다.
  • begin과 target은 같지 않습니다.
  • 변환할 수 없는 경우에는 0를 return 합니다.

입출력 예

begin              target                  words                                                                                                                           return

"hit" "cog" ["hot", "dot", "dog", "lot", "log", "cog"] 4
"hit" "cog" ["hot", "dot", "dog", "lot", "log"] 0

입출력 예 설명

예제 #1
문제에 나온 예와 같습니다.

예제 #2
target인 "cog"는 words 안에 없기 때문에 변환할 수 없습니다.

문제풀이

처음엔 DFS로 풀이에 접근하였으나, 역시 BFS가 더 익숙하고 쉽게 느껴진다.

문제를 풀때 요구사항에 맞는 알고리즘 및 자료구조를 최대한 빨리 캐치하는 능력이 중요한 것 같고,

문제를 많이 풀면서 향상 시킬 수 있도록 해야겠다.

 

핵심

단어를 target으로 만드는데에 필요한 최소횟수를 구하는것이므로 BFS활용이 좋아보인다.

1. 큐를 만들어 [시작단어, depth] 삽입

2. 한번에 한개의 알파벳만을 변환할 수 있으므로,

  words에 있는 후보 단어들과 큐에서 꺼낸 단어를 비교하여 한글자만 다르다면 큐에 삽입.

   그리고 depth 1증가

 

 

코드
from collections import deque

def solution(begin, target, words):
    answer = 0
    visited = [0] * (len(words))
    
    # BFS시행: 조건에 부합하는 변환과정을 위한 단어를 큐에 삽입해가기
    def BFS(data):
        queue = deque([[data, 0]]) # 단어, 변환횟수
        while queue:
            data, cnt = queue.popleft()
            
            if data == target:
                return cnt
          
            # words 배열의 단어중 한개만 변환하여 바꿀 수 있는 단어 탐색
            for i in range(len(words)):
                # 방문안한것중에서 탐색
                if not visited[i]:
                    check = 0
                    for j in range(len(words[i])):
                        # 다른 문자가 하나여야만 하므로
                        if data[j] != words[i][j]:
                            check += 1
                    if check == 1:
                        queue.append([words[i], cnt+1])
                        visited[i] = 1
        
        return 0
    
    answer = BFS(begin)
    return answer
문제링크

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

 

3190번: 뱀

'Dummy' 라는 도스게임이 있다. 이 게임에는 뱀이 나와서 기어다니는데, 사과를 먹으면 뱀 길이가 늘어난다. 뱀이 이리저리 기어다니다가 벽 또는 자기자신의 몸과 부딪히면 게임이 끝난다. 게임

www.acmicpc.net

문제설명

 

문제

'Dummy' 라는 도스게임이 있다. 이 게임에는 뱀이 나와서 기어다니는데, 사과를 먹으면 뱀 길이가 늘어난다. 뱀이 이리저리 기어다니다가 벽 또는 자기자신의 몸과 부딪히면 게임이 끝난다.

게임은 NxN 정사각 보드위에서 진행되고, 몇몇 칸에는 사과가 놓여져 있다. 보드의 상하좌우 끝에 벽이 있다. 게임이 시작할때 뱀은 맨위 맨좌측에 위치하고 뱀의 길이는 1 이다. 뱀은 처음에 오른쪽을 향한다.

뱀은 매 초마다 이동을 하는데 다음과 같은 규칙을 따른다.

  • 먼저 뱀은 몸길이를 늘려 머리를 다음칸에 위치시킨다.
  • 만약 벽이나 자기자신의 몸과 부딪히면 게임이 끝난다.
  • 만약 이동한 칸에 사과가 있다면, 그 칸에 있던 사과가 없어지고 꼬리는 움직이지 않는다.
  • 만약 이동한 칸에 사과가 없다면, 몸길이를 줄여서 꼬리가 위치한 칸을 비워준다. 즉, 몸길이는 변하지 않는다.

사과의 위치와 뱀의 이동경로가 주어질 때 이 게임이 몇 초에 끝나는지 계산하라.

입력

첫째 줄에 보드의 크기 N이 주어진다. (2 ≤ N ≤ 100) 다음 줄에 사과의 개수 K가 주어진다. (0 ≤ K ≤ 100)

다음 K개의 줄에는 사과의 위치가 주어지는데, 첫 번째 정수는 행, 두 번째 정수는 열 위치를 의미한다. 사과의 위치는 모두 다르며, 맨 위 맨 좌측 (1행 1열) 에는 사과가 없다.

다음 줄에는 뱀의 방향 변환 횟수 L 이 주어진다. (1 ≤ L ≤ 100)

다음 L개의 줄에는 뱀의 방향 변환 정보가 주어지는데, 정수 X와 문자 C로 이루어져 있으며. 게임 시작 시간으로부터 X초가 끝난 뒤에 왼쪽(C가 'L') 또는 오른쪽(C가 'D')로 90도 방향을 회전시킨다는 뜻이다. X는 10,000 이하의 양의 정수이며, 방향 전환 정보는 X가 증가하는 순으로 주어진다.

출력

첫째 줄에 게임이 몇 초에 끝나는지 출력한다.

예제 입력 1 

6
3
3 4
2 5
5 3
3
3 D
15 L
17 D

예제 출력 1 

9

 

문제풀이

시뮬레이션 문제로,

주어진 조건을 빠르고 정확하게 파악하는 것이 제일 중요한 것 같다.

이 부분은 앞으로도 남들보다 더 꾸준히 문제를 풀어 익숙해지려고 노력해야겠다.

 

핵심

1. 지도에서 움직이는 형태 구현. (상하좌우)

2. 방향 변환하는 함수 만들어서 활용

3. 자료구조 큐 활용

 

주의점

1. 이동후 현재좌표 갱신하기

 

BFS 문제중에 지도에서 움직이는 형태를 구현한 적이 많았는데 갱신된 좌표를 큐에 넣어서 해당좌표를 다시 꺼내 이용하여 문제가 없었지만,

지금은 직접 현재좌표를 갱신하는 추가코드가 필요하다!!

조금 안일하게 문제풀이에 임했지 않았나 싶다...

 

풀이 간략히 설명

- arr을 N * N 으로 만들고 사과가 있는 좌표에 1을 삽입 

- 방향 정보 저장은 딕셔너리를 활용

   direction[시간] = 문자정보  형태

- 뱀이 방문한 좌표는 arr[nx][ny] = 2. 

- 방문한 좌표마다 큐에 삽입하고, 방문한 좌표에 사과가 없다면

큐의 특성을 활용해 꼬리빼기(방문안함으로 처리)

- 이동할때마다 현재좌표 갱신!!

- 매초 마다 time을 증가시켜 direction의 키와 일치하는 시간이 되면 => 방향 변환 (함수로 구현)

 

코드
from collections import deque

N = int(input())
K = int(input())
arr = [[0 for _ in range(N)] for _ in range(N)]
dx = [0, -1, 0, 1]  # 각각 우 상 좌 하
dy = [1, 0, -1, 0]
directions = {}

# 입력값1) 사과 위치 정보 저장
for i in range(K):
    r, c = map(int, input().split())
    arr[r-1][c-1] = 1

L = int(input())
# 입력값2) 방향 변환 정보 저장
for i in range(L):
    x, d = input().split()
    directions[int(x)] = d

def rotateDir(index, str):  # 방향 나타내는 인덱스, 방향
    # 상3 하2 좌1 우0
    # L 왼쪽회전 => 우 -> 상(1) -> 좌(2) -> 하(3)
    # D 오른쪽회전 => 우(0) -> 하(3) -> 좌(2) -> 상(1)
    if str == 'L':
        result = (index + 1) % 4
    elif str == 'D':
        result = (index + 3) % 4
    return result

def start(x, y):
    dir = 0 # 처음은 오른쪽으로 향하니, 동을 나타내는 인덱스0 에 접근 위해 설정.
    queue = deque([(x, y)])
    arr[x][y] = 2
    time = 0
    
    while True:
        time += 1
        nx = x + dx[dir]
        ny = y + dy[dir]
        # 범위안에 있고 방문한 지역 아니라면
        if 0 <= nx < N and 0 <= ny < N and arr[nx][ny] != 2:
            queue.append((nx, ny))
            x, y = nx, ny
            # 사과가 있다면 꼬리 안뺌 그대로 늘림.
            if arr[nx][ny] == 1:
                arr[nx][ny] = 2   # 방문
            else:
                arr[nx][ny] = 2
                px, py = queue.popleft()
                arr[px][py] = 0
        else:
            break

        if time in directions.keys():
            dir = rotateDir(dir, directions[time])
    
    return time

print(start(0, 0))
문제링크

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

 

1759번: 암호 만들기

첫째 줄에 두 정수 L, C가 주어진다. (3 ≤ L ≤ C ≤ 15) 다음 줄에는 C개의 문자들이 공백으로 구분되어 주어진다. 주어지는 문자들은 알파벳 소문자이며, 중복되는 것은 없다.

www.acmicpc.net

문제설명

 

문제

바로 어제 최백준 조교가 방 열쇠를 주머니에 넣은 채 깜빡하고 서울로 가 버리는 황당한 상황에 직면한 조교들은, 702호에 새로운 보안 시스템을 설치하기로 하였다. 이 보안 시스템은 열쇠가 아닌 암호로 동작하게 되어 있는 시스템이다.

암호는 서로 다른 L개의 알파벳 소문자들로 구성되며 최소 한 개의 모음(a, e, i, o, u)과 최소 두 개의 자음으로 구성되어 있다고 알려져 있다. 또한 정렬된 문자열을 선호하는 조교들의 성향으로 미루어 보아 암호를 이루는 알파벳이 암호에서 증가하는 순서로 배열되었을 것이라고 추측된다. 즉, abc는 가능성이 있는 암호이지만 bac는 그렇지 않다.

새 보안 시스템에서 조교들이 암호로 사용했을 법한 문자의 종류는 C가지가 있다고 한다. 이 알파벳을 입수한 민식, 영식 형제는 조교들의 방에 침투하기 위해 암호를 추측해 보려고 한다. C개의 문자들이 모두 주어졌을 때, 가능성 있는 암호들을 모두 구하는 프로그램을 작성하시오.

입력

첫째 줄에 두 정수 L, C가 주어진다. (3 ≤ L ≤ C ≤ 15) 다음 줄에는 C개의 문자들이 공백으로 구분되어 주어진다. 주어지는 문자들은 알파벳 소문자이며, 중복되는 것은 없다.

출력

각 줄에 하나씩, 사전식으로 가능성 있는 암호를 모두 출력한다.

예제 입력 1 복사

4 6
a t c i s w

예제 출력 1 복사

acis
acit
aciw
acst
acsw
actw
aist
aisw
aitw
astw
cist
cisw
citw
istw
문제풀이

핵심

C개 문자 중  L개를 골라 암호를 만들어야하는데,

1. 최소 2개 자음과 1개 모음을 이용해야함.

2. 알파벳 순서로 나열된 암호여야함

 

풀이 과정

1. arr 오름차순 정렬

2. 정렬된 상태의 배열에서 반복문 시행. 각각 첫번째 원소부터 C-L 번째까지의 원소를 시작점으로 함수시행.

3. 후보리스트에 조건에 맞는 원소 삽입하고 재귀함수 호출

4. 후보리스트 길이가 원하는 길이라면, 암호조건에 부합하는지 체킹하고 리턴

5. 함수시행이 종료됐으므로, 백트래킹하여 다음 순번의 암호찾기.

코드
from itertools import combinations

L, C = map(int, input().split())
arr = list(input().split())
arr.sort()
# 입력예제1 => a, c, i, s, t, w

vowels = set(('a', 'e', 'i', 'o', 'u'))
# 최소 자음2개 모음1개 구성.
def check(arr):
    vcnt, ccnt = 0, 0
    for item in arr:
        if item in vowels:
            vcnt += 1
        else:
            ccnt += 1

    if vcnt >= 1 and ccnt >= 2:
        return True
    else:
        return False
    
def DFS(tmp):
    if len(tmp) == L:
        # 체킹
        if check(tmp):
            print(''.join(tmp))
            return
    
    # a c i s t w
    for i in range(len(tmp), C):
        if tmp[-1] < arr[i]:
            tmp.append(arr[i])
            DFS(tmp)
            tmp.pop()


for i in range(C-L+1):
    tmp = [arr[i]]
    DFS(tmp)

 

문제링크

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

 

1987번: 알파벳

세로 R칸, 가로 C칸으로 된 표 모양의 보드가 있다. 보드의 각 칸에는 대문자 알파벳이 하나씩 적혀 있고, 좌측 상단 칸 (1행 1열) 에는 말이 놓여 있다. 말은 상하좌우로 인접한 네 칸 중의 한 칸으

www.acmicpc.net

문제설명

 

문제

세로 R칸, 가로 C칸으로 된 표 모양의 보드가 있다. 보드의 각 칸에는 대문자 알파벳이 하나씩 적혀 있고, 좌측 상단 칸 (1행 1열) 에는 말이 놓여 있다.

말은 상하좌우로 인접한 네 칸 중의 한 칸으로 이동할 수 있는데, 새로 이동한 칸에 적혀 있는 알파벳은 지금까지 지나온 모든 칸에 적혀 있는 알파벳과는 달라야 한다. 즉, 같은 알파벳이 적힌 칸을 두 번 지날 수 없다.

좌측 상단에서 시작해서, 말이 최대한 몇 칸을 지날 수 있는지를 구하는 프로그램을 작성하시오. 말이 지나는 칸은 좌측 상단의 칸도 포함된다.

입력

첫째 줄에 R과 C가 빈칸을 사이에 두고 주어진다. (1 ≤ R,C ≤ 20) 둘째 줄부터 R개의 줄에 걸쳐서 보드에 적혀 있는 C개의 대문자 알파벳들이 빈칸 없이 주어진다.

출력

첫째 줄에 말이 지날 수 있는 최대의 칸 수를 출력한다.

예제 입력 1 

2 4
CAAB
ADCB

예제 출력 1 

3

예제 입력 2 

3 6
HFDFFB
AJHGDH
DGAGEH

예제 출력 2 

6
문제풀이

백트래킹

DFS의 특성대로 탐색하다가, 더이상 솔루션이 될 수 없다고 판단되면 포기하고 다른 후보를 탐색하는 기법이다.

 

실수한 부분

1. 처음 DFS시행 전에 시작지점 좌표 방문, 알파벳체크 처리 잊지말자!

2. 불필요한 반복검사 줄이기. (아스키코드 활용하여 알파벳 => 숫자 변환하는 로직을 초반에 따로 빼자.)

    -> 이것도 시간초과에 한몫한듯.

 

코드
R, C = map(int, input().split())
arr = [list(input()) for i in range(R)]
visited = [[0 for _ in range(C)] for _ in range(R)] 
checked = [0] * 27
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
answer = 0

# 아스키코드 활용하여 알파벳 => 숫자로 바꾸기
for i in range(R):
    for j in range(C):
        arr[i][j] = ord(arr[i][j]) - 64

def DFS(x, y, cnt):
    global answer
    answer = max(cnt, answer)

    for i in range(4):
        nx = x + dx[i]
        ny = y + dy[i]

        # 범위 내에 있고, 방문안한 노드인지
        # arr[nx][ny]가 기존 알파벳들과 다른 알파벳인지
        if 0 <= nx < R and 0 <= ny < C and not visited[nx][ny]:
            if not checked[arr[nx][ny]]:
                visited[nx][ny] = 1
                checked[arr[nx][ny]] = 1
                DFS(nx, ny, cnt+1)
                visited[nx][ny] = 0
                checked[arr[nx][ny]] = 0
            

visited[0][0] = 1
checked[arr[0][0]] = 1
DFS(0, 0, 1)

print(answer)

+ Recent posts