no image
[백준] 13023 ABCDE
문제 출처: 백준 온라인 저지 13023 ABCDE 13023번: ABCDE 문제의 조건에 맞는 A, B, C, D, E가 존재하면 1을 없으면 0을 출력한다. www.acmicpc.net 문제 설명 🔎BOJ 알고리즘 캠프에는 총 N명이 참가하고 있다. 사람들은 0번부터 N-1번으로 번호가 매겨져 있고, 일부 사람들은 친구이다. 오늘은 다음과 같은 친구 관계를 가진 사람 A, B, C, D, E가 존재하는지 구해보려고 한다. A는 B와 친구다. B는 C와 친구다. C는 D와 친구다. D는 E와 친구다. 위와 같은 친구 관계가 존재하는지 안하는지 구하는 프로그램을 작성하시오. (문제의 조건에 맞는 A, B, C, D, E가 존재하면 1을 없으면 0을 출력한다.) 위 그림에서 나타난 것 처럼, 주어진 여러 ..
2023.01.26
no image
[프로그래머스] 가장 먼 노드
문제 출처: 프로그래머스 ( https://school.programmers.co.kr/learn/courses/30/lessons/49189 ) 문제 설명 🔎n개의 노드가 있는 그래프가 있습니다. 각 노드는 1부터 n까지 번호가 적혀있습니다. 1번 노드에서 가장 멀리 떨어진 노드의 갯수를 구하려고 합니다. 가장 멀리 떨어진 노드란 최단경로로 이동했을 때 간선의 개수가 가장 많은 노드들을 의미합니다. 노드의 개수 n, 간선에 대한 정보가 담긴 2차원 배열 vertex가 매개변수로 주어질 때, 1번 노드로부터 가장 멀리 떨어진 노드가 몇 개인지를 return 하도록 solution 함수를 작성해주세요. 제한사항 1. 노드의 개수 n은 2 이상 20,000 이하입니다. 2. 간선은 양방향이며 총 1개 이상 ..
2023.01.20
no image
[프로그래머스] 섬 연결하기
문제 출처: 프로그래머스 (https://school.programmers.co.kr/learn/courses/30/lessons/42861) 문제 설명 🔎n개의 섬 사이에 다리를 건설하는 비용(costs)이 주어질 때, 최소의 비용으로 모든 섬이 서로 통행 가능하도록 만들 때 필요한 최소 비용을 return 하도록 solution을 완성하세요. 다리를 여러 번 건너더라도, 도달할 수만 있으면 통행 가능하다고 봅니다. 예를 들어 A 섬과 B 섬 사이에 다리가 있고, B 섬과 C 섬 사이에 다리가 있으면 A 섬과 C 섬은 서로 통행 가능합니다. 제한사항 1. 섬의 개수 n은 1 이상 100 이하입니다. 2. costs의 길이는 ((n-1) * n) / 2이하입니다. 3. 임의의 i에 대해, costs[i]..
2023.01.20
no image
[백준] 1932 정수 삼각형
문제 출처: 백준 온라인 저지 (https://www.acmicpc.net/problem/1932) 문제 설명 🔎위 그림은 크기가 5인 정수 삼각형의 한 모습이다. 맨 위층 7부터 시작해서 아래에 있는 수 중 하나를 선택하여 아래층으로 내려올 때, 이제까지 선택된 수의 합이 최대가 되는 경로를 구하는 프로그램을 작성하라. 아래층에 있는 수는 현재 층에서 선택된 수의 대각선 왼쪽 또는 대각선 오른쪽에 있는 것 중에서만 선택할 수 있다. 삼각형의 크기는 1 이상 500 이하이다. 삼각형을 이루고 있는 각 수는 모두 정수이며, 범위는 0 이상 9999 이하이다. 처음 문제 예시만 봤을 땐 포화 이진트리나 힙을 생각하게 만드는 문제였다. 하지만 문제를 잘 읽어보면 이진트리 방식으로 수를 선택하면서 내려왔을 때 ..
2023.01.16
[프로그래머스] 단속카메라
문제 출처: 프로그래머스 (https://school.programmers.co.kr/learn/courses/30/lessons/42884#) 문제 설명 🔎고속도로를 이동하는 모든 차량이 고속도로를 이용하면서 단속용 카메라를 한 번은 만나도록 카메라를 설치하려고 합니다. 고속도로를 이동하는 차량의 경로 routes가 매개변수로 주어질 때, 모든 차량이 한 번은 단속용 카메라를 만나도록 하려면 최소 몇 대의 카메라를 설치해야 하는지를 return 하도록 solution 함수를 완성하세요. 처음 문제를 접했을 때는 완전탐색인가?라고 생각했었다. (아마 DP문제를 계속 풀다와서 그런 것 같다.) 첫 시도로 defaultdict로 모든 경로에 대해 방문하는 차량의 대수를 기록하여 문제 풀이를 시도했다. 더보기..
2023.01.13
[프로그래머스] n진수 게임
문제 출처: 프로그래머스 (https://school.programmers.co.kr/learn/courses/30/lessons/17687) 문제 설명 🔎튜브가 활동하는 코딩 동아리에서는 전통적으로 해오는 게임이 있다. 이 게임은 여러 사람이 둥글게 앉아서 숫자를 하나씩 차례대로 말하는 게임인데, 규칙은 다음과 같다. 숫자를 0부터 시작해서 차례대로 말한다. 첫 번째 사람은 0, 두 번째 사람은 1, … 열 번째 사람은 9를 말한다. 10 이상의 숫자부터는 한 자리씩 끊어서 말한다. 즉 열한 번째 사람은 10의 첫 자리인 1, 열두 번째 사람은 둘째 자리인 0을 말한다. 이렇게 게임을 진행할 경우,0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4,..
2023.01.13
no image
[프로그래머스] 등굣길
문제 출처 : 프로그래머스 (https://school.programmers.co.kr/learn/courses/30/lessons/42898) 문제 설명 🔎계속되는 폭우로 일부 지역이 물에 잠겼습니다. 물에 잠기지 않은 지역을 통해 학교를 가려고 합니다. 집에서 학교까지 가는 길은 m x n 크기의 격자모양으로 나타낼 수 있습니다. 격자의 크기 m, n과 물이 잠긴 지역의 좌표를 담은 2차원 배열 puddles이 매개변수로 주어집니다. 오른쪽과 아래쪽으로만 움직여 집에서 학교까지 갈 수 있는 최단경로의 개수를 1,000,000,007로 나눈 나머지를 return 하도록 solution 함수를 작성해주세요. 해당 문제는 언듯 보면 BFS 문제인 것 같다는 생각이 바로 들게 만들어 놓은 문제였다. 하지만,..
2023.01.13
no image
그로스해킹
(본 게시글은 양승화 저자의 ‘그로스해킹’ 도서를 참고하여 제작하였습니다.) 학부생 때 데이터 분석 프로젝트를 하면 대부분 공공 데이터를 활용하거나, GCP에서 제공하는 공개 데이터를 사용했다. 인턴을 하지 않으면 기업 데이터를 다뤄볼 기회가 없었고, SQL 테스트로 사내 데이터를 엿볼 기회가 생겨도 어떻게 활용할 지 감이 안왔다. 또한, 나는 데이터 분석가와 사이언티스트에 대한 구별이 확실하지 않았던터라 데이터를 정제하고 그래프로 현황을 파악한 다음 머신러닝을 통한 예측을 하는 것이 데이터를 활용하는 최적의 방안이라 생각했다. 하지만 대기업, 스타트업 등 많은 기업을 지원해보고 각 회사별로 데이터 분석가에게 요구하는 역량에 대해 자세히 분석하다 보니 데이터 분석가와 데이터 사이언티스트의 차이점을 이해하..
2023.01.10

문제 출처: 백준 온라인 저지 13023 ABCDE

 

13023번: ABCDE

문제의 조건에 맞는 A, B, C, D, E가 존재하면 1을 없으면 0을 출력한다.

www.acmicpc.net

 

문제 설명

🔎BOJ 알고리즘 캠프에는 총 N명이 참가하고 있다. 사람들은 0번부터 N-1번으로 번호가 매겨져 있고, 일부 사람들은 친구이다.
오늘은 다음과 같은 친구 관계를 가진 사람 A, B, C, D, E가 존재하는지 구해보려고 한다.

A는 B와 친구다.
B는 C와 친구다.
C는 D와 친구다.
D는 E와 친구다.

위와 같은 친구 관계가 존재하는지 안하는지 구하는 프로그램을 작성하시오.

(문제의 조건에 맞는 A, B, C, D, E가 존재하면 1을 없으면 0을 출력한다.)

위 그림에서 나타난 것 처럼, 주어진 여러 관계 속에서 A➡B➡C➡D➡E 로 이어지는 관계가 존재하는지 묻는 문제이다.

해당 관계가 몇번이 나타나는지는 상관없이 하나라도 존재하면 1, 하나도 존재하지 않는다면 0을 출력한다.

 

문제를 읽기만 했을 때는 그래프 문제라고 파악하기 힘들지만, 주어진 예제들을 통해 그래프 탐색을 통해 문제를 해결해야하는 것을 확인할 수 있다.

입력 예제 출력 예제
5   4
0   1
1   2
2   3
3   4
1

 

<정답 코드>

def dfs(cur, depth=0):
    if depth == 4:
        return True

    visit[cur] = True

    for nx in relation[cur]:
        if not visit[nx]:
            flag = dfs(nx, depth+1)
            if flag:
                return True

    visit[cur] = False
    return False


n, m = map(int, input().split())
relation = [[] for _ in range(n)]
ans = 0
for _ in range(m):
    v, w = map(int, input().split())
    relation[v].append(w)
    relation[w].append(v)


for i in range(n):
    visit = [False] * n
    ans = dfs(i)
    if ans:
        break

print(int(ans))

한번만 관계를 확인하면 되기에 flag와 DFS를 활용해 문제를 풀이하였다.

접근은 아래와 같다.

 

1. 입력을 2차원 리스트에 그래프형태로 저장한다.

2. 그래프를 dfs를 통해 순회한다. dfs를 수행할 때는 depth 인자를 주어 depth가 4가 되면 탈출하게 작성하였다.

3. depth가 4일 때만 True를 반환하여 flag를 변경해주었고, flag가 True가 되면 True를 반환하며 탈출한다.

 

dfs의 특성을 통해 깊이 = 관계의 깊이로 생각하여 문제를 풀었다.

주의해야할 점은 탐색에 실패하였을 때 visit[cur]False로 바꾸어 다음 진행에 차질이 생기지 않도록 하는 것이다.

문제 출처: 프로그래머스 ( https://school.programmers.co.kr/learn/courses/30/lessons/49189 )

 

문제 설명

🔎n개의 노드가 있는 그래프가 있습니다. 각 노드는 1부터 n까지 번호가 적혀있습니다. 1번 노드에서 가장 멀리 떨어진 노드의 갯수를 구하려고 합니다. 가장 멀리 떨어진 노드란 최단경로로 이동했을 때 간선의 개수가 가장 많은 노드들을 의미합니다.

노드의 개수 n, 간선에 대한 정보가 담긴 2차원 배열 vertex가 매개변수로 주어질 때, 1번 노드로부터 가장 멀리 떨어진 노드가 몇 개인지를 return 하도록 solution 함수를 작성해주세요.
제한사항
1. 노드의 개수 n은 2 이상 20,000 이하입니다.
2. 간선은 양방향이며 총 1개 이상 50,000개 이하의 간선이 있습니다.
3. vertex 배열 각 행 [a, b]는 a번 노드와 b번 노드 사이에 간선이 있다는 의미입니다.

해당 문제는 전형적인 그래프 최단거리를 구하는 문제로, bfs를 사용할 줄 알면 간단하게 풀 수 있는 문제이다.

다만, 문제의 설명에 연결되지 않은 1번 노드에 연결되지 않은 노드가 존재할 수 있다는 문구가 있었으면 조금 더 좋았을 것 같다.

 

<정답 코드>

from collections import deque
def solution(n, edge):
    answer = 0
    graph = [[] for _ in range(n+1)]
    for start, end in edge:
        graph[start].append(end)
        graph[end].append(start)
    
    dist = [float('inf')] * (n+1)
    visited = [False] * (n+1)
    
    dist[1] = 0
    q = deque()
    q.append(1)
    while q:
        cur = q.popleft()
        if visited[cur]:
            continue
        
        visited[cur] = True
        
        for x in graph[cur]:
            if dist[x] > dist[cur] + 1:
                dist[x] = dist[cur] + 1
                q.append(x)
                

    target = 0
    for i in dist[1:]:
        if i != float('inf') and target < i:
            target = i
    
    answer = dist.count(target)
    
    
    return answer

접근 방법은 아래와 같다.

 

1. 주어진 파라미터를 통해 그래프를 생성한다.

2. 1번 노드를 출발점으로 지정하여, 그래프를 탐색한다. 탐색한 깊이에 따라 거리를 업데이트 해준다.

3. 거리 리스트에서 inf가 아닌 최대 값을 도출하고 이것을 count한다.

 

처음에는 max(dist)를 target으로 설정하고 진행하였다. 하지만 테스트 2번 예시를 통해 앞서 언급한 방문 불가 노드가 있다는 사실을 깨닫고 위와 같이 수정하였다.

 

<테스트 2>

입력값 4, [[1, 2], [1, 3]]
기댓값 2

 

문제 출처: 프로그래머스 (https://school.programmers.co.kr/learn/courses/30/lessons/42861)

 

문제 설명

🔎n개의 섬 사이에 다리를 건설하는 비용(costs)이 주어질 때, 최소의 비용으로 모든 섬이 서로 통행 가능하도록 만들 때 필요한 최소 비용을 return 하도록 solution을 완성하세요.
다리를 여러 번 건너더라도, 도달할 수만 있으면 통행 가능하다고 봅니다. 예를 들어 A 섬과 B 섬 사이에 다리가 있고, B 섬과 C 섬 사이에 다리가 있으면 A 섬과 C 섬은 서로 통행 가능합니다.
제한사항
1. 섬의 개수 n은 1 이상 100 이하입니다.
2. costs의 길이는 ((n-1) * n) / 2이하입니다.
3. 임의의 i에 대해, costs[i][0] 와 costs[i] [1]에는 다리가 연결되는 두 섬의 번호가 들어있고, costs[i] [2]에는 이 두 섬을 연결하는 다리를 건설할 때 드는 비용입니다.
4.같은 연결은 두 번 주어지지 않습니다. 또한 순서가 바뀌더라도 같은 연결로 봅니다. 즉 0과 1 사이를 연결하는 비용이 주어졌을 때, 1과 0의 비용이 주어지지 않습니다.
5. 모든 섬 사이의 다리 건설 비용이 주어지지 않습니다. 이 경우, 두 섬 사이의 건설이 불가능한 것으로 봅니다.
6. 연결할 수 없는 섬은 주어지지 않습니다.

해당 문제는 프로그래머스의 코딩 테스트 고득점 kit에 있는 문제다.

 

섬, 연결, 최소 비용이라는 단어를 보자마자 bfs, 다익스트라 등을 생각했고 실제 바로 그리디한 방법이 떠오르지 않아 다익스트라로 먼저 시도했었다.

 

<틀린 코드 보기>

더보기
from queue import PriorityQueue
# 다익스트라 x
def solution(n, costs):
    answer = 0
    island = [[] for _ in range(n)]

    for e1, e2, w in costs:
        island[e1].append((e2, w))
        island[e2].append((e1, w))

    visited = [False] * (n+1)
    dist = [float('inf')] * (n+1)
    q = PriorityQueue()
    q.put((0, 0)) # 최단거리, 노드번호
    dist[0] = 0
    bridge = [0] * (n+1)
    while q.qsize()>0:
        cur_node = q.get()[1]
        if visited[cur_node]: continue

        visited[cur_node] = True

        for nx, val in island[cur_node]:
            if dist[nx] > dist[cur_node] + val:
                dist[nx] = dist[cur_node] + val
                bridge[nx] = val
                q.put((dist[nx], nx))

    print(dist)
    print(bridge)

다익스트라를 통한 구현의 문제점은 아래와 같다.

1. 결국은 모든 costs에 대한 점검을 해야한다.

2. 다익스트라로 구현된 dist 리스트는 시작점부터 해당 노드까지의 비용으로 산정되기 때문에 sum으로 답을 구할 수 없다.

 

제한 사항에서 섬의 개수(n)은 100이하라고 했고, costs의 길이는 ((n-1) * n) / 2 이라 규정하였으므로, 실제 costs를 전체 도는데만 해도 O(n²) 만큼의 시간이 들기 때문에 1번 문제점은 꽤나 크게 작용하였다.

 

이후 이 문제가 그리디로 분류되었다는 사실을 다시 한번 깨닫고 아래와 같은 코드를 작성하였다.

<정답 코드>

from collections import deque
def solution(n, costs):
    answer = 0
    costs = sorted(costs, key=lambda x: x[2])
    isl = [[] for _ in range(n)]

    
    for start, end, cost in costs:
        q = deque()
        flag = False
        q.append(start)
        visit = [False] * n
        visit[start] = True
        while q:
            a = q.popleft()
            if a == end:
                visit[a] = True
                flag = True
                break
            
            visit[a] = True
            for i in isl[a]:
                if not visit[i]:
                    q.append(i)
        
        if sum(visit) == n:
            break
        
        if not flag:
            isl[start].append(end)
            isl[end].append(start)
            answer += cost
    
    return answer

접근 방법은 아래와 같다.

 

1. 가장 낮은 비용의 다리부터 우선적으로 고려한다. (비용을 기준으로 costs를 정렬한다.)

2. 비용을 기준으로 정렬된 costs를 순회하면서, 연결되지 않은 섬에 대해서만 다리를 건설한다.

3. 모든 섬을 방문할 수 있게 되었으면 반복문을 탈출하게 한다.

 

연결되지 않은 섬을 판단하는 알고리즘으로는 bfs를 사용하였다. 

bfs를 채택할 수 있었던 것은, 다리를 하나씩 건설하기 때문에 bfs를 통한 연산 횟수가 0에서 부터 지수적으로 상승한다는 사실 때문이다.

 

해당 섬과 연결이 되었는가에 대한 판단은 flag를 통해 진행하였고, 만약 모든 섬을 방문할 수 있게 되었다면 반복문을 탈출하는 것으로 마무리 지었다. 이 부분이 다익스트라에서 모든 costs를 점검해야한다는 부분을 해소할 수 있게 해주었다.

 

사실 이 문제는 수개월 전에 먼저 도전했다가 실패한 문제이다. 최근에는 그래프나 트리 문제를 많이 풀지 않고 DP와 브루트포스 문제를 많이 풀어 해당 문제를 푸는데 도움이 되었던 것 같다.

'알고리즘 > 그리디' 카테고리의 다른 글

[프로그래머스] 단속카메라  (0) 2023.01.13

문제 출처: 백준 온라인 저지 (https://www.acmicpc.net/problem/1932)

 

문제 설명

🔎위 그림은 크기가 5인 정수 삼각형의 한 모습이다.

맨 위층 7부터 시작해서 아래에 있는 수 중 하나를 선택하여 아래층으로 내려올 때, 이제까지 선택된 수의 합이 최대가 되는 경로를 구하는 프로그램을 작성하라. 아래층에 있는 수는 현재 층에서 선택된 수의 대각선 왼쪽 또는 대각선 오른쪽에 있는 것 중에서만 선택할 수 있다.

삼각형의 크기는 1 이상 500 이하이다. 삼각형을 이루고 있는 각 수는 모두 정수이며, 범위는 0 이상 9999 이하이다.

처음 문제 예시만 봤을 땐 포화 이진트리나 힙을 생각하게 만드는 문제였다.

 

하지만 문제를 잘 읽어보면 이진트리 방식으로 수를 선택하면서 내려왔을 때 최대가 되는 수를 출력하는 다이나믹 프로그래밍 문제였다.

 

<정답코드>

import sys
input = sys.stdin.readline

n = int(input())
tree = []

for i in range(n):
    inp = list(map(int, input().split()))
    if i == 0:
        tree.append(inp)
        continue
    elif i == 1:
        tree.append(list(map(lambda x: x+tree[0][0], inp)))
        continue

    inp[0] += tree[-1][0]
    inp[-1] += tree[-1][-1]

    for j in range(1, len(inp)-1):
        inp[j] += max(tree[-1][j-1], tree[-1][j])

    tree.append(inp)

print(max(tree[-1]))

접근은 아래와 같다.

1. 리스트로 입력을 받아 2차원 리스트 형태로 저장한다.

2. 리스트에 저장하기 전, 각 원소에 대한 부모 노드 중 더 큰 값을 더하여 저장한다.

3. 0번째 리스트는 원래 값 그대로 저장하고, 1번째 리스트는 0번째 리스트의 값만 부모로 갖는다.

 

트리 구조에 대한 이해만 있다면 쉽게 풀 수 있었던 문제였던 것 같다.

문제 출처: 프로그래머스 (https://school.programmers.co.kr/learn/courses/30/lessons/42884#)

 

문제 설명

🔎고속도로를 이동하는 모든 차량이 고속도로를 이용하면서 단속용 카메라를 한 번은 만나도록 카메라를 설치하려고 합니다. 고속도로를 이동하는 차량의 경로 routes가 매개변수로 주어질 때, 모든 차량이 한 번은 단속용 카메라를 만나도록 하려면 최소 몇 대의 카메라를 설치해야 하는지를 return 하도록 solution 함수를 완성하세요.

처음 문제를 접했을 때는 완전탐색인가?라고 생각했었다. (아마 DP문제를 계속 풀다와서 그런 것 같다.)

 

첫 시도로 defaultdict로 모든 경로에 대해 방문하는 차량의 대수를 기록하여 문제 풀이를 시도했다.

<틀린코드 보기>

더보기
from collections import defaultdict
def solution(routes):
    answer = 0
    routes.sort()
    road = defaultdict(int)
    
    for enter, out in routes:
    	for i in range(enter, out+1):
        	road[i] += 1
    
    start = list(road.keys())[0]
    end = list(road.keys())[-1]
    cur = start
    rec = 0
    
    while cur < end:
    	answer += 1
    	rec = road[cur]
        while rec == road[cur]:
        	cur += 1
            
    return answer

다시 살펴보니 틀린 코드는 여기 올리기 민망할 정도로 논리가 안맞는 수준이었다. 점검을 위해 돌렸을 때 대부분의 케이스에서 시간 초과가 발생하였고, 제대로 돌아간 3개 조차 실패로 나왔다.

 

패착은 이렇다.

1. 방문한 전체 길이의 경로를 탐색하므로 인해 연산이 지나치게 많이 발생하였다.

2. 방문과 탐색을 따로 진행함으로 인해 연산이 두배정도 증가하였다.

3. 해당 도로의 방문 차량 숫자가 달라지는 것과 CCTV 수는 항상 관련이 있는 것은 아니다.

 

나의 잘못된 버릇이 항상 그렇듯 문제를 너무 꼬아 생각하게 되는 것을 하루 빨리 고쳐야 실력 향상에 도움이 될 것 같다..

 

정답은 다음과 같다.

<정답 코드>

from collections import deque
def solution(routes):
    answer = 0
    routes.sort(key=lambda x: x[1])
    q = deque(routes)
    
    while q:
        enter, out = q.popleft()
        answer += 1
        while q and q[0][0] <= out:
            q.popleft()

    return answer

이전에 짰던 코드보다 훨씬 간결하게 답안을 작성할 수 있는 것이었다.

 

접근은 아래와 같다.

 

1. 주어진 route에 대해, 먼저 나가는 차량들을 기준으로 정렬한다. 

2. 정렬된 리스트를 queue형식으로 변경한다.

3. queue에서 차량경로를 하나씩 꺼낸 후, 해당 경로 상 임의의 위치에 CCTV 하나를 배치한다 가정하고, 해당 경로 안에서 출발을 하는 차량들을 모두 pop해준다.

 

처음엔 나가는 차량을 기준으로 정렬을 하는게 아닌, 출입하는 차량을 기준으로 정렬을 해야한다고 생각했다.

 

이와 같이 생각한 이유는, 먼저 출발한 차량이 탈출하기 전에 있었던 모든 차량들을 pop해야한다고 생각했기 때문이다.

 

하지만 이런 접근이 틀린 이유는 가장 먼저 출발한 차량이 가장 나중에 탈출하는 경우가 있다면, CCTV 대수는 1대로 출력되기 때문이다. (그리고 가장 먼저 탈출하는 차량을 기준으로 정렬해야한다는 사실을 3시간 만에 떠올렸다...)

 

그리디 문제를 풀 때 가끔씩 드는 생각이지만 해당 코드가 항상 정답을 배출하는 코드일까?라는 의문이 아직까지 남아있지만, 그렇다할 반례를 아직 못찾았다. 반례를 찾게 된다면 추가로 업로드 해야겠다.

'알고리즘 > 그리디' 카테고리의 다른 글

[프로그래머스] 섬 연결하기  (0) 2023.01.20

문제 출처: 프로그래머스 (https://school.programmers.co.kr/learn/courses/30/lessons/17687)

 

문제 설명

🔎튜브가 활동하는 코딩 동아리에서는 전통적으로 해오는 게임이 있다. 이 게임은 여러 사람이 둥글게 앉아서 숫자를 하나씩 차례대로 말하는 게임인데, 규칙은 다음과 같다.

숫자를 0부터 시작해서 차례대로 말한다.
첫 번째 사람은 0, 두 번째 사람은 1, … 열 번째 사람은 9를 말한다.
10 이상의 숫자부터는 한 자리씩 끊어서 말한다. 즉 열한 번째 사람은 10의 첫 자리인 1, 열두 번째 사람은 둘째 자리인 0을 말한다.
이렇게 게임을 진행할 경우,0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, …순으로 숫자를 말하면 된다.

한편 코딩 동아리 일원들은 컴퓨터를 다루는 사람답게 이진수로 이 게임을 진행하기도 하는데, 이 경우에는0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, …순으로 숫자를 말하면 된다.

이진수로 진행하는 게임에 익숙해져 질려가던 사람들은 좀 더 난이도를 높이기 위해 이진법에서 십육진법까지 모든 진법으로 게임을 진행해보기로 했다. 숫자 게임이 익숙하지 않은 튜브는 게임에 져서 벌칙을 받는 굴욕을 피하기 위해, 자신이 말해야 하는 숫자를 스마트폰에 미리 출력해주는 프로그램을 만들려고 한다. 튜브의 프로그램을 구현하라.

(별 희한한 동아리가 다 있다.)

나는 진법 변환을 남들보다 어렵게 푸는 경향이 있어 어렵게 느껴졌던 문제였다.

 

처음 접근 방식은 전체 사람 수 만큼 돌 때 진행되는 길이에서 p번째 수를 추출하는 방법이었다.

이런 접근은 진법이 고정되어있으면 해볼만한 시도였지만, n에 따라 전체 사람 수 만큼 돌 때 진행되는 수(10진수)가 달랐기 때문에 구현을 제대로 해보지도 못했다.

 

정답률이 56%나 되고, 질문도 별로 없었던 문제인데 고집 때문에 차마 구글에 검색하지는 못하고 그나마 있는 질문들에서 정보를 캐내 다음과 같은 풀이 방법으로 문제를 해결할 수 있었다.

 

<정답코드>

# 진법 변환기
def change(n: int, num: int) -> str:
    result = ''
    while num:
        num, mod = num//n, num%n
        if 9 < mod:
            mod = chr(mod+ord('A')-10)
        result += str(mod)
    
    return result[-1::-1]

def solution(n, t, m, p):
    total = t*m
    cur = '0'
    nx = 1
    
    while len(cur) < total:
        cur += change(n, nx)
        nx += 1
    
    return cur[p-1:(t-1)*m+p:m]

접근은 처음 생각했던 것과 크게 다르진 않다.

1. 숫자를 말하는 전체 횟수는 t*m이다.

2. 따라서, 현재 진행되고 있는 숫자를 문자열로 치환한 것을 현재까지 얘기한 숫자(문자형)에 더한다.

3. 해당 숫자(문자형)의 길이가 t*m이 되면 반복문을 탈출한다.

4. 발표 순서와 순환 수에 따라 문자열 slicing을 통해 정답을 도출한다.

 

처음 생각한 것과 가장 큰 차이점은 매번 p번째를 도출할 필요가 없어, 매번 문자열을 잘라 생각할 필요가 없다는 것이다. (해당 문제는 문자열 slicing을 통해 해결이 가능했다.)

 

그리고 while을 탈출할 기준을 진법에 따라 정할 필요가 없고 길이로 보면 간단한 것을 너무 어렵게 생각했던게 이번 문제에 고전했던 이유였던 것 같다.

문제 출처 : 프로그래머스 (https://school.programmers.co.kr/learn/courses/30/lessons/42898)

 

문제 설명

출처: 프로그래머스

🔎계속되는 폭우로 일부 지역이 물에 잠겼습니다. 물에 잠기지 않은 지역을 통해 학교를 가려고 합니다. 집에서 학교까지 가는 길은 m x n 크기의 격자모양으로 나타낼 수 있습니다.

격자의 크기 m, n과 물이 잠긴 지역의 좌표를 담은 2차원 배열 puddles이 매개변수로 주어집니다. 오른쪽과 아래쪽으로만 움직여 집에서 학교까지 갈 수 있는 최단경로의 개수를 1,000,000,007로 나눈 나머지를 return 하도록 solution 함수를 작성해주세요.

 

해당 문제는 언듯 보면 BFS 문제인 것 같다는 생각이 바로 들게 만들어 놓은 문제였다. 하지만, 문제를 자세히 읽어보면 최단경로를 도출하는 것이 아니라 최단경로의 개수를 도출하는 문제였다.

 

나는 거짓말처럼 바로 BFS를 통해 문제 해결을 시도했고.. 처음에는 최단경로 문제인 줄 알고 일반적인 BFS를 구현했지만, 최단경로의 개수라는 것을 인지하고 바꾸어 시도해봤다.

<틀린 코드 보기>

더보기
from collections import deque
def solution(m, n, puddles):
    dx, dy = [0, 1], [1, 0]
    maps = [[0] * m for _ in range(n)]
    maps[0][0] = 1
	
    # x, y가 뒤바뀌어서 주어짐;;;
    for x, y in puddles:
        maps[y][x] = -1

    q = deque()
    q.append((0, 0))

    visit = set()
    visit.add((0, 0))

    while True:
        x, y = q.popleft()
        if x == n - 1 and y == m - 1:
            break

        visit.add((x, y))

        for i in range(2):
            nx, ny = x + dx[i], y + dy[i]
            if nx < n and ny < m and maps[nx][ny] != -1:
                q.append((nx, ny))
                maps[nx][ny] = (maps[nx][ny]+1) % (10**9+7)

#    print(*maps, sep='\n')

    return maps[-1][-1]

하지만 결과는 시간초과였다..

 

문제는 BFS를 통한 방문을 계산하게 되면 같은 좌표에 대한 연산을 방문할 수 있을 때 마다 하는 것이었다.

더보기

BFS를 통해 문제를 풀었을 경우:

BFS는 현재 좌표에서 이동가능한 경로들을 queue에 삽입하여 다음 loop에 꺼내 활용한다. 일반적으로는 visit을 표시하여 재방문을 하지 않지만, 해당 문제의 경우 (우, 하향) 으로 밖에 움직이지 않기 때문에 해당 좌표에 대한 모든 방문 경로가 최단거리임이 보장되었기에 visit할 때 마다 1씩 증가시키는 것으로 시도하였다.

 

하지만 문제에서 10**9 + 7을 나눈 몫으로 계산하라는 것에서 암시할 수 있듯, 위와 같은 방식으로 하면 한 좌표에 대한 방문 연산이 10**9 + 7 보다 더 크게 나올 수도 있는 것이었다.

따라서, 해당 문제의 분류인 다이나믹 프로그래밍으로 접근하여 문제를 해결하였다.

 

<정답 코드>

from collections import deque
limit = 10**9+7

def solution(m, n, puddles):
    maps = [[0]*m for _ in range(n)]
        
    for x, y in puddles:
        maps[y-1][x-1] = -1
    
    maps[0][0] = 1
    
    for i in range(1, n):
        if maps[i][0] != -1:
            maps[i][0] = maps[i-1][0]
    
    for i in range(1, m):
        if maps[0][i] != -1:
            maps[0][i] = maps[0][i-1]
    
    for i in range(1,n):
        for j in range(1,m):
            a, b = maps[i-1][j], maps[i][j-1]
            if maps[i][j] == -1:
                continue
            
            # 이 부분이 좀 지저분
            if a==-1:
                a=0
            if b==-1:
                b=0
            maps[i][j] = (a+b) % limit
    
    return maps[-1][-1]

 

접근은 다음과 같다.

문제에선 (우, 하향) 으로 밖에 움직이지 않기 때문에 특정 좌표에 대한 모든 방문 경로가 최단거리임이 보장된다.

 

1. 1행 또는 1열에 대해서는 방문할 수 있는 경우의 수가 1 밖에 없다. 

2. 1행 또는 1열에 존재하는 웅덩이들은 제외한다.

3. 2행 2열부터 n행m열까지 모든 경로(x, y)에 대해 (x-1, y), (x, y-1)의 값을 더해준다.

 

이렇게 점화식처럼 이전에 밟을 수 있었던 좌표들 간의 연산을 통해 진행된다는 점이 다이나믹 프로그래밍 스러운 문제였다.

 

조금 해매기도하고, 출제자의 실수(인지 의도인지 모르겠지만)가 있었기에 풀이하는데 꽤나 오랜 시간이 걸렸다.

하지만, 해당 문제처럼 방문 경로가 반드시 최단거리임이 보장이 될 때 DP로 문제를 해결하는 방법에 대해 배울 수 있어서 좋았다.

(본 게시글은 양승화 저자의 ‘그로스해킹’ 도서를 참고하여 제작하였습니다.)

학부생 때 데이터 분석 프로젝트를 하면 대부분 공공 데이터를 활용하거나, GCP에서 제공하는 공개 데이터를 사용했다. 인턴을 하지 않으면 기업 데이터를 다뤄볼 기회가 없었고, SQL 테스트로 사내 데이터를 엿볼 기회가 생겨도 어떻게 활용할 지 감이 안왔다.

 

또한, 나는 데이터 분석가와 사이언티스트에 대한 구별이 확실하지 않았던터라 데이터를 정제하고 그래프로 현황을 파악한 다음 머신러닝을 통한 예측을 하는 것이 데이터를 활용하는 최적의 방안이라 생각했다.

 

하지만 대기업, 스타트업 등 많은 기업을 지원해보고 각 회사별로 데이터 분석가에게 요구하는 역량에 대해 자세히 분석하다 보니 데이터 분석가와 데이터 사이언티스트의 차이점을 이해하게 되었고 데이터 분석가가 사이언티스트와 구분될 수 있는 기술인 ‘그로스 해킹’에 대해 알게 되었다.

 

아래 내용은 그로스 해킹에 대해 공부한 내용을 기록한 것이다.


그로스 해킹

그로스해킹이란?

그로스 해킹을 간단하게 말하면 말 그대로 기업의 성장을 관리하는 기법이라고 이해할 수 있다.

과거와 달리 소비자들 개인의 기준이 뚜렷해지고, 요구사항이 많아짐에 따라 일회적인 성공으로 경영을 지속하기는 힘들어졌다. 제품을 출시하고, 불편사항을 개선하고, 새로운 기능을 추가하고.. 이런 일련의 과정을 유지해나가면서 목표한 수익을 거두는 것이 성공에 가까워졌다. 그로스해킹은 이런 과정들을 데이터를 통해 살펴보고 성장 방향을 제시하는데 목적을 두고 있다.

💡 그로스해킹이 마케팅의 일부분이라거나, 그로스해커 집단에 의해 진행되는 업무라는 시각이 존재하기도 하는데, 그로스해킹은 그로스해커라는 한 사람에 의해 실현되는게 아니라 PM, 마케터, 데이터분석가 등 다양한 사람들과 부서 간의 협업을 통해 이루어진다.

기업에 따라 차이가 있지만, 그로스해킹 업무를 수행하는 부서 내부에서 PM, 엔지니어, 마케터, 디자이너, 데이터분석가로 구성되어 업무를 진행한다. 데이터 분석가가 현황에 대한 지표를 시각화해서 넘겨주면 각 부서는 KPI와 OMTM 등을 고려해 앞으로 취해야할 액션을 도출한다. 마케팅 부서에서는 결과를 바탕으로 마케팅 전략을 수립할 것이고, 엔지니어 부서에서는 데이터 적재 방식을 변경한다거나, 버그 수정에 힘을 쓸 것이다. 이렇게 모두의 노력이 뒷받침이 되어야 그로스해킹이라는 업무가 수행될 수 있는 것이다.

 

그로스 해킹 어떻게 하는건데..

그로스 해킹에서 가장 중점적으로 보는 것은 사용자 행동 패턴에 대한 지표들이다. 제품 또는 서비스에 대해 소비자들이 얼마나 지속적으로 이용하는가, 다음 서비스로 넘어가는 비율은 어떻게 되는가 등 다양한 지표를 살피고, 이를 단계적인 프레임워크를 통해 관리하며 KPI, OMTM 등의 목표를 설정하여 나아간다.

 

지표?

데이터를 통해 살펴 볼 수 있는 모든 것이 지표가 될 수 있다. 게임 광고에서 자주 등장하는 누적 설치량, 온라인 쇼핑몰에서 보이는 누적 가입자 수 등.. 지표 자체는 아주 다양하고, 각 지표가 보여줄 수 있는 인사이트 또한 가지각색이지만 단순 지표가 보여주는 함정에 빠져 잘못된 선택을 하는 경우들이 많다. 그래서 그로스해킹에서는 AARRR이라 불리는 프레임워크를 구성하고 각 단계를 설명하는 지표들을 선정하여 이들을 관리한다. (다른 방법을 통해 관리하기도 하지만, 현재까지 공부한 내용으론 AARRR이 가장 활성화 되어있다고 한다.)

 

이 모든 것을 설명하기에는 스크롤의 압박이 너무 심해지므로, 관련된 용어를 잠깐 설명하고 다음 내용은 다음 장으로 넘어가도록 한다.

 

<그로스해킹 관련 용어>

PMF(Product-Market Fit) 용어 그대로 제품의 시장 적합성을 의미한다. 실제 제품이 시중에서 원하고 있는 제품인지 판단하여, 기업 입장에서 성장관리를 할 가치가 있는지 판단하는 기준이 된다.
리텐션(Retention) 한글로는 유지율. 해당 서비스를 이용하는 고객이 유지되는 비율을 의미한다.
전환율(Conversion) 앱 접속 → 회원가입 → 상품 구매 등 다음 단계로 이어지는 과정에서 유지되는 고객의 비율을 의미한다.
NPS(Net Promoter Score) 순수 추천 지수, 해당 서비스를 이용하는 고객이 이를 타인에게 소개해주고 싶어하는 정도를 점수화 시킨 것이다.

 

AARRR  사용자의 서비스 이용 흐름에 따라 고객유치(Acquisition), 활성화(Activation), 리텐션(Retention), 수익화(Revenue), 추천(Referral)의 카테고리로 구성된 지표 관리 프레임워크.
오가닉 (Organic) 유료 마케팅을 통해 유입된 고객이 아닌, 타인의 추천이나 자사 SNS를 통해 유입된 고객
CAC(Customer Acquisition Cost) 고객 유치 비용, 한명의 사용자를 데려오기 위해 드는 평균 비용. 전체 마케팅 비용을 가입자수로 나누는 방법이 가장 대표적이며 마케팅 도구별로 구분하여 계산하기도 한다.
어트리뷰션 (Attribution) 모바일 사용자가 해당 앱을 설치하는데 까지 기여한 채널을 식별하는데 활용하는 도구.
퍼널(Funnel) 사용자가 서비스에 진입한 후 핵심기능을 사용할 때 까지의 과정을 도표로 나타낸 것.
코호트(Cohort) 공통적인 특성에 따라 세부 그룹으로 나뉜 사용자 집단.
DAU(Daily Active User) 일 평균 서비스 이용자 수.
MAU(Monthly Active User) 월 평균 서비스 이용자 수.
ARPU(Average Revenue Per User) 인당 평균 매출, 사용자 한명이 발생시키는 매출의 평균. 전체 고객을 대상으로 사용자 당 평균 매출로, ARPDAU, ARPMAU 등으로 고쳐 판단하기도 한다.
ARPPU(Average Revenue Per Paying User) 해당 서비스 내에서 유료 결제를 진행한 사용자들을 한정으로 한 사용자당 평균 매출.
LTV(LifeTime Value) 고객 생애 가치, 사용자 한명이 서비스를 이용하기 시작한 시점부터 이탈할 때 까지 누적해서 발생시킨 수익.
LTR(LifeTime Revenue) 사용자 한명이 서비스를 이용하기 시작한 시점부터 이탈할 때 까지 누적해서 발생시킨 매출. LTV와 유사한 개념이나, 수익이 아닌 매출을 의미.
바이럴 계수(Viral Coefficient) 추천 엔진이 얼마나 효율적으로 작동하는지 평가하는 지표.