no image
[프로그래머스] 파괴되지 않은 건물 (파이썬)
문제 출처: 프로그래머스 프로그래머스 코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요. programmers.co.kr 문제 설명 🔎[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.] N x M 크기의 행렬 모양의 게임 맵이 있습니다. 이 맵에는 내구도를 가진 건물이 각 칸마다 하나씩 있습니다. 적은 이 건물들을 공격하여 파괴하려고 합니다. 건물은 적의 공격을 받으면 내구도가 감소하고 내구도가 0이하가 되면 파괴됩니다. 반대로, 아군은 회복 스킬을 사용하여 건물들의 내구도를 높이려고 합니다. 적의 공격과 아군의 회복 스킬은 항상 직사각형 모양입니다. 예를 들어, 아래 사진은 크기가 4 ..
2023.02.04
no image
[백준] 1748 수 이어쓰기 1 (파이썬)
문제 출처: 백준 온라인 저지 1748번: 수 이어 쓰기 1 첫째 줄에 N(1 ≤ N ≤ 100,000,000)이 주어진다. www.acmicpc.net 문제 설명 🔎1부터 N까지의 수를 이어서 쓰면 다음과 같이 새로운 하나의 수를 얻을 수 있다. (1 ≤ N ≤ 100,000,000) 1234567891011121314151617181920212223... 이렇게 만들어진 새로운 수는 몇 자리 수일까? 이 수의 자릿수를 구하는 프로그램을 작성하시오. (제한 시간: 0.15초, 파이썬은 0.5초) 해당 문제의 분류가 브루트포스로 되어있어서 설마 진짜 다 작성하고 len으로 구하는건가? 생각이 들어 작성해보니 역시나 시간초과가 떴다. 다른 방법을 생각했으나 해당 방법이 브루트포스인지는 명확하지 않다. n ..
2023.02.03
no image
[프로그래머스] 롤 케이크 자르기 (파이썬)
문제 출처: 프로그래머스 프로그래머스 코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요. programmers.co.kr 문제 설명 🔎철수는 롤케이크를 두 조각으로 잘라서 동생과 한 조각씩 나눠 먹으려고 합니다. 이 롤케이크에는 여러가지 토핑들이 일렬로 올려져 있습니다. 철수와 동생은 롤케이크를 공평하게 나눠먹으려 하는데, 그들은 롤케이크의 크기보다 롤케이크 위에 올려진 토핑들의 종류에 더 관심이 많습니다. 그래서 잘린 조각들의 크기와 올려진 토핑의 개수에 상관없이 각 조각에 동일한 가짓수의 토핑이 올라가면 공평하게 롤케이크가 나누어진 것으로 생각합니다. 예를 들어, 롤케이크에 4가지 종류의 토핑이 올려..
2023.01.31
no image
[프로그래머스] 스티커 모으기2
문제 출처: 프로그래머스 프로그래머스 코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요. programmers.co.kr 문제 설명 🔎 N개의 스티커가 원형으로 연결되어 있습니다. 다음 그림은 N = 8인 경우의 예시입니다. 원형으로 연결된 스티커에서 몇 장의 스티커를 뜯어내어 뜯어낸 스티커에 적힌 숫자의 합이 최대가 되도록 하고 싶습니다. 단 스티커 한 장을 뜯어내면 양쪽으로 인접해있는 스티커는 찢어져서 사용할 수 없게 됩니다. 예를 들어 위 그림에서 14가 적힌 스티커를 뜯으면 인접해있는 10, 6이 적힌 스티커는 사용할 수 없습니다. 스티커에 적힌 숫자가 배열 형태로 주어질 때, 스티커를 뜯어내어 ..
2023.01.29
no image
[백준] 3085 사탕 게임
문제 출처: 백준 온라인 저지 3085번: 사탕 게임 예제 3의 경우 4번 행의 Y와 C를 바꾸면 사탕 네 개를 먹을 수 있다. www.acmicpc.net 문제 설명 🔎상근이는 어렸을 적에 "봄보니 (Bomboni)" 게임을 즐겨했다. 가장 처음에 N×N크기에 사탕을 채워 놓는다. 사탕의 색은 모두 같지 않을 수도 있다. 상근이는 사탕의 색이 다른 인접한 두 칸을 고른다. 그 다음 고른 칸에 들어있는 사탕을 서로 교환한다. 이제, 모두 같은 색으로 이루어져 있는 가장 긴 연속 부분(행 또는 열)을 고른 다음 그 사탕을 모두 먹는다. 사탕이 채워진 상태가 주어졌을 때, 상근이가 먹을 수 있는 사탕의 최대 개수를 구하는 프로그램을 작성하시오. 첫째 줄에 보드의 크기 N이 주어진다. (3 ≤ N ≤ 50) ..
2023.01.28
[백준] 2309 일곱 난쟁이
문제 출처: 백준 온라인 저지 2309번: 일곱 난쟁이 아홉 개의 줄에 걸쳐 난쟁이들의 키가 주어진다. 주어지는 키는 100을 넘지 않는 자연수이며, 아홉 난쟁이의 키는 모두 다르며, 가능한 정답이 여러 가지인 경우에는 아무거나 출력한다. www.acmicpc.net 문제 설명 🔎왕비를 피해 일곱 난쟁이들과 함께 평화롭게 생활하고 있던 백설공주에게 위기가 찾아왔다. 일과를 마치고 돌아온 난쟁이가 일곱 명이 아닌 아홉 명이었던 것이다. 아홉 명의 난쟁이는 모두 자신이 "백설 공주와 일곱 난쟁이"의 주인공이라고 주장했다. 뛰어난 수학적 직관력을 가지고 있던 백설공주는, 다행스럽게도 일곱 난쟁이의 키의 합이 100이 됨을 기억해 냈다. 아홉 난쟁이의 키가 주어졌을 때, 백설공주를 도와 일곱 난쟁이를 찾는 프로..
2023.01.28
no image
[프로그래머스] 삼각 달팽이 (Python)
문제 출처: 프로그래머스 프로그래머스 코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요. programmers.co.kr 문제 설명 🔎정수 n이 매개변수로 주어집니다. 다음 그림과 같이 밑변의 길이와 높이가 n인 삼각형에서 맨 위 꼭짓점부터 반시계 방향으로 달팽이 채우기를 진행한 후, 첫 행부터 마지막 행까지 모두 순서대로 합친 새로운 배열을 return 하도록 solution 함수를 완성해주세요. 달팽이가 삼각형을 그리면서 한칸씩 이동할 때 발자취를 남기는 문제다. 해당 문제는 어떤 특별한 자료구조를 사용하기 보다 어떻게 구현할 것이냐가 중요한 문제였다. 처음에는 새로운 삼각형 시작의 상관성을 찾아서 ..
2023.01.28
no image
제품 시장 적합성(Product Market Fit, PMF)
고등학생 시절, ‘허니팁스’라는 온라인 거래 플랫폼을 제작하여 창업 동아리를 운영해보기도 하고, ‘머리 눌림 방지 헤드폰’ 아이디어를 공모하여 발명 대회에 출품하기도 하였다. 이러한 아이디어는 언제나 일상 생활 속에서 나에게 필요했던 것 으로 부터 시작되었다. 사람들은 제품을 출시하기 전에 ‘시장 조사’ 확실하게 수행하는 것이 중요하다고 한다. 해당 제품이 나에게만 필요한게 아닌 실제로 사람들이 필요로 하는 것인가, 필요로 하는 사람의 수는 얼마나 되는가 등을 조사해서 적절한 수익이 발생한다는 판단이 있어야 제품 생산 및 운영이 지속가능하기 때문이다. 여기에서의 ‘시장 조사’가 바로 **제품-시장 적합성(PMF)**이다. 제품-시장 적합성은 주로 린 스타트업에서 사용되는 개념으로, 제품 출시 이전 미리 ..
2023.01.28

문제 출처: 프로그래머스

 

프로그래머스

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

programmers.co.kr

 

문제 설명

🔎[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.]
N x M 크기의 행렬 모양의 게임 맵이 있습니다.
이 맵에는 내구도를 가진 건물이 각 칸마다 하나씩 있습니다.
적은 이 건물들을 공격하여 파괴하려고 합니다.
건물은 적의 공격을 받으면 내구도가 감소하고 내구도가 0이하가 되면 파괴됩니다.
반대로, 아군은 회복 스킬을 사용하여 건물들의 내구도를 높이려고 합니다.
적의 공격과 아군의 회복 스킬은 항상 직사각형 모양입니다.

예를 들어, 아래 사진은 크기가 4 x 5인 맵에 내구도가 5인 건물들이 있는 상태입니다.

출처: 프로그래머스

 

첫 번째로 적이 맵의 (0,0)부터 (3,4)까지 공격하여 4만큼 건물의 내구도를 낮추면 아래와 같은 상태가 됩니다.

출처: 프로그래머스

 

두 번째로 적이 맵의 (2,0)부터 (2,3)까지 공격하여 2만큼 건물의 내구도를 낮추면 아래와 같이 4개의 건물이 파괴되는 상태가 됩니다.

출처: 프로그래머스

 

세 번째로 아군이 맵의 (1,0)부터 (3,1)까지 회복하여 2만큼 건물의 내구도를 높이면 아래와 같이 2개의 건물이 파괴되었다가 복구되고 2개의 건물만 파괴되어있는 상태가 됩니다.

출처: 프로그래머스

 

마지막으로 적이 맵의 (0,1)부터 (3,3)까지 공격하여 1만큼 건물의 내구도를 낮추면 아래와 같이 8개의 건물이 더 파괴되어 총 10개의 건물이 파괴된 상태가 됩니다. (내구도가 0 이하가 된 이미 파괴된 건물도, 공격을 받으면 계속해서 내구도가 하락하는 것에 유의해주세요.)

출처: 프로그래머스

최종적으로 총 10개의 건물이 파괴되지 않았습니다.


이 문제는 22년도 카카오 블라인드 채용 당시 직접 경험했던 문제다.

당시에는 갑작스러운 취업 준비로 인해 1주일 정도 밖에 공부하지 못했었고, 그래프 탐색, 최단경로, 누적합 등의 개념을 몰랐기에 전체 배열을 탐색하는 방법으로 밖에 시도해보지 못했다.

 

그리고 채용과정에 불합격한 후에 꾸준하게 코딩 테스트를 준비하다가 다시 만나게 되었다.

 

처음에는 이 전과 같이 2차원 배열을 skill 하나씩 탐색하며 board를 탐색하는 브루트포스 코드를 작성했었다.

(그리고 마치 맞춘것 마냥 이 문제가 level 3이 맞나 생각했었다.)

 

아주 순조롭게 정확성 테스트 케이스를 전부 통과하고..

효율성 테스트 케이스를 전부 시간초과 해버렸다..

 

<틀린 코드 보기>

더보기
def solution(board, skill):
    answer = len(board) * len(board[0])

    for s in skill:
        if s[0] == 1:
            for i in range(s[1], s[3]+1):
                for j in range(s[2], s[4]+1):
                    flag = board[i][j] > 0 #원래는 정상적인 건물
                    board[i][j] -= s[5]
                    if flag and board[i][j] <= 0: # 공격받고 파괴되었는가 검사
                        answer -= 1
        else:
            for i in range(s[1], s[3]+1):
                for j in range(s[2], s[4]+1):
                    flag = board[i][j] <= 0 #원래 파괴된 건물
                    board[i][j] += s[5]
                    if flag and board[i][j] > 0: #회복받고 복구되었는가 검사
                        answer += 1
        
    
    return answer

제한 조건 중에 

  • 1 ≤ board의 원소 (각 건물의 내구도) ≤ 1,000

라는 조건이 있었기에 board 전체를 한번 더 탐색하지 않고 flag를 통해 접근하려는 앙큼한 생각이었지만, 문제는 skill 안에 있었다.

 

극단적인 예시로 board가 1,000*1,000일 때 skill의 길이가 최대인 250,000이고, 모든 skill의 원소가 항상 board 전체 범위를 타격할 때, 1,000*1,000의 범위를 250,000번이나 반복해서 연산하게 되므로 시간초과가 발생했다.

(시간 복잡도 O(N * M * K))

 

사실 처음에는 해결 방법을 찾지 못하고 열심히 서핑하였다.

그러다 카카오 팀에서 직접 밝힌 접근법에서 O(K + N * M)의 시간에 문제를 풀 수 있다고 했고, 그 방법의 중심에는 '누적합'의 개념이 있었다.

(카카오 팀의 접근법 링크)

(누적합 개념 링크)

 

이 전까지는 한번도 사용해보지 않았던 알고리즘이라 생소하기도 하고 설명을 보아도 무슨 말인지 정확하게 이해 못하고 이게 왜 더 빠르지? 라는 의문도 가졌었다.

 

하지만 직접 작성해보고 테스트를 진행해보니 점차 이해가 갔고 결국에는 정답을 맞출 수 있었다.

 

<정답 코드>

def solution(board, skill):
    answer = 0
    tmp = [[0] * (len(board[0]) + 1) for _ in range(len(board) + 1)]
    
    for type, r1, c1, r2, c2, degree in skill:
        tmp[r1][c1] += degree if type == 2 else -degree
        tmp[r1][c2 + 1] += -degree if type == 2 else degree
        tmp[r2 + 1][c1] += -degree if type == 2 else degree
        tmp[r2 + 1][c2 + 1] += degree if type == 2 else -degree

    for x in range(len(tmp) - 1):
        for y in range(len(tmp[0]) - 1):
            tmp[x][y + 1] += tmp[x][y]

    for y in range(len(tmp[0]) - 1):
        for x in range(len(tmp) - 1):
            tmp[x + 1][y] += tmp[x][y]

    for x in range(len(board)):
        for y in range(len(board[0])):
            board[x][y] += tmp[x][y]
            if board[x][y] > 0: 
                answer += 1
                
    return answer

작성된 코드에 반복문이 너무 많이 보여 지저분하고 더 복잡해 보일 수 있지만, 해당 코드를 통해 문제를 접근하게 되면 항상 board 전체를 2번만 순회하게 되면 정답을 도출할 수 있다.

 

접근 방법은 아래와 같다.

  1. board보다 행,열로 한 칸씩 더 큰 2차원 누적합 기록 배열을 생성한다.
  2. skill을 순회하면서 하나의 skill이 나타내는 사각형의 꼭짓점(우측과 하단으론 한칸씩 더 증가시킨다)에 degree를 기록한다. (자세한 것은 누적합의 개념을 숙지해야 이해할 수 있다.)
  3. 기록된 누적합 배열의 행, 열 기준으로 누적합을 계산한다.
  4. 누적합 배열과 기존 board를 더해주면서 파괴되지 않은 건물의 수를 계산한다. 

문제를 푸는 것에만 집중하면 쉬워보일 수 있는 문제였으나, 효율성을 고려하게 되면서 굉장히 어려워졌던 문제이다.

 

그리고 누적합의 설명은 보통 1차원 배열을 예시로 들기 때문에 개념을 이해하는데 어려움이 있었다.

 

그래도 고생 끝에 문제를 해결했기 때문에 오래 기억에 남을 것 같다.

 


내가 이해한 2차원 누적합 개념 간단 설명

기존 배열이 5*5라고 가정하고 진행한다.

 

그렇게 되면 누적합 배열은 6*6이 되고 최초의 모습은 아래 사진과 같다.

 

이 상태에서 [0, 0]과 [1, 1]의 사각형이 들어왔다고 하자. 그렇다면 우리는 아래와 같이 기록해야한다.

우리가 원하는 것은 [0, 0]부터 [1, 1]까지 전부 1이 되는 그림이기 때문에 이렇게 기록해야 한다.

 

바로 누적합을 계산해서 결과를 확인해보자.

 

처음에는 행을 기준으로 누적합을 진행한다.

행을 기준으로 j+1번째 칸의 값이 현재 값에 j번째 칸의 값을 더한 값이 되므로 위와 같이 진행되었고, [0, 2]가 -1일 때 [0, 1]의 1과 더해지면서 0이 된 후는 전부 0+0이 된다.

(편의를 위해 0이 아닌 칸에 대해서만 진행했다.)

최종적으로 진행된 결과 값이 두 번째 사진처럼 된다.

 

이러한 원리로 또 열을 기준으로 합을 진행해보면,

최종적으로 이와 같이 나타나게 된다.

이렇게 되면 우리가 원했던 [0, 0], [1, 1]을 꼭짓점으로 가지는 사각형 만큼 값을 삽입하게 된 것이다.

 

이렇게 계산된 누적합 배열을 기존 board 배열에 더하게 되면 우리가 원하는 값이 도출될 것이다.

 

이렇게 문제를 푸는 것이 항상 빠르게 작용하는 것은 아니나, 브루트포스로 풀었을 때 예시를 든 것 같은 경우에는 이것이 훨씬 빨리 작동할 수 있다.

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

 

1748번: 수 이어 쓰기 1

첫째 줄에 N(1 ≤ N ≤ 100,000,000)이 주어진다.

www.acmicpc.net

 

문제 설명

🔎1부터 N까지의 수를 이어서 쓰면 다음과 같이 새로운 하나의 수를 얻을 수 있다. (1 ≤ N ≤ 100,000,000)
1234567891011121314151617181920212223...
이렇게 만들어진 새로운 수는 몇 자리 수일까? 이 수의 자릿수를 구하는 프로그램을 작성하시오.
(제한 시간: 0.15초, 파이썬은 0.5초)

해당 문제의 분류가 브루트포스로 되어있어서 설마 진짜 다 작성하고 len으로 구하는건가? 생각이 들어 작성해보니 역시나 시간초과가 떴다.

다른 방법을 생각했으나 해당 방법이 브루트포스인지는 명확하지 않다.

 

<정답 코드>

n = input()
cur = 1
answer = 0
while cur < len(n):
    answer += cur * (10**cur - 10**(cur-1))
    cur += 1

answer += cur * (int(n) - 10**(cur-1) + 1)
print(answer)

접근은 아래와 같다.

  1. 한 자리 수 부터 시작해 입력 받은 수의 자릿수 -1까지 (자릿수 * 해당 자릿수에 존재하는 숫자 개수)만큼을 더한다.
  2. 입력받은 수의 자릿수에 대해서는 (자릿수 * (입력받은 수 - 입력받은 수의 자릿 수의 첫번째 수))를 더한다.

한자리수일 때는 1~9, 9개가 존재하고, 두자리수일 때는 10~99, 90개, 세자리수는 100~999, 900개가 존재한다. 이와 같은 방법으로 계속 더 해나가는 것이다.

 

예를 들어 n = 1001이면 9+90+900+(4*2) = 1007이 되는 것이다.

내가 작성한 코드도 정답인 코드지만, 조금 더 직관적으로 작성하려면 while문 안에서 cur을 이용하기 보다 9로 시작해 9*10**(cur-1)로 진행해도 될 것 같다.

 

모든 숫자를 고려해야한다는 것은 사실이나, 기존 브루트포스 문제들과 같이 진짜 모든 경우를 방문하지 않는다는 점에서 브루트포스 문제인지 헷갈렸다.

 

문제 출처: 프로그래머스

 

프로그래머스

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

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]으로 잘라도 공평하게 나뉩니다.

어떤 경우에는 롤케이크를 공평하게 나누지 못할 수도 있습니다.

제한사항
- 1 ≤ topping의 길이 ≤ 1,000,000
- 1 ≤ topping의 원소 ≤ 10,000

본 문제는 아주 간단한 문제이지만 시간 복잡도를 어떻게 개선하느냐에 관한 문제였다.

 

topping을 1번 순회하는데만 1,000,000번의 연산이 수행되므로, 파이썬에서는 O(n)으로 구현해야 1초 안에 문제가 해결된다고 볼 수 있다.

 

첫 번째 시도는 set과 stack을 이용한 풀이였다.

<틀린 코드 보기>

더보기
def solution(topping):
    answer = 0
    t2 = set()
    for i in range(len(topping)):
        t2.add(topping.pop())
        if len(t2) == len(set(topping)):
            answer += 1
    
    return answer

결과는..

20개의 테스트 케이스 중에서 2개만 맞고 전부 시간초과 하였다.

일반적으로 가장 수월한 1번 테스트 케이스조차 1초가 넘는 시간이 걸린 것으로 보아, 시간 복잡도 개선에 실패한 것이 분명했다.

 

원인은 리스트를 set으로 변환하는 set(topping)의 시간복잡도가 O(n)이기에 반복문 안에 존재하면 O(n) << t < O(n²) 정도의 시간이 발생하기 때문이었다.

 

따라서 for문 안에서 타입 변환이 일어나지 않게 하는 코드를 작성하였다.

 

<정답 코드>

from collections import Counter
def solution(topping):
    answer = 0
    kinds = Counter(topping)
    t2 = set()
    for i in topping:
        t2.add(i)
        kinds[i] = kinds[i]-1
        if kinds[i] == 0:
            del kinds[i]
            
        if len(t2) == len(kinds.keys()):
            answer += 1
        
    
    return answer

 

Counter은 해당 iterator 안에서 unique한 원소가 몇개씩 존재하는지 딕셔너리 형태로 반환해주는 클래스이다.

시간복잡도는 iterator의 길이 n에 따른 O(n)이다.

 

접근 방법은 아래와 같다.

1. topping 안에 존재하는 각 원소들의 개수를 Counter을 이용해 구한다. (=> kinds)

2. 철수가 먹을 토핑을 set으로 정의한다. (=>t2)

3. topping을 순회하면서 t2에 토핑을 하나씩 추가하고, kinds에서 해당 토핑의 개수를 -1 해준다. 

    3-1. kinds에서 해당 토핑이 0개가 되면 삭제해 key값에 포함되지 않게 한다.

4. t2의 길이와 kinds의 key 길이가 같아지면 answer을 +1 해준다.

 

t2가 set이므로 같은 토핑을 반복해서 집어넣어도 set의 길이가 늘어나지 않는다는 점을 이용해 진행한 풀이다.

 

Counter의 존재를 몰라도, topping을 한번 순회하는 것으로 직접 구현할 수 있으므로 (몇번 틀리긴 했지만)생각보다 간단한 문제였다.

문제 출처: 프로그래머스

 

프로그래머스

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

programmers.co.kr

 

문제 설명

출처: 프로그래머스

🔎 N개의 스티커가 원형으로 연결되어 있습니다. 다음 그림은 N = 8인 경우의 예시입니다.

원형으로 연결된 스티커에서 몇 장의 스티커를 뜯어내어 뜯어낸 스티커에 적힌 숫자의 합이 최대가 되도록 하고 싶습니다. 단 스티커 한 장을 뜯어내면 양쪽으로 인접해있는 스티커는 찢어져서 사용할 수 없게 됩니다.

예를 들어 위 그림에서 14가 적힌 스티커를 뜯으면 인접해있는 10, 6이 적힌 스티커는 사용할 수 없습니다. 스티커에 적힌 숫자가 배열 형태로 주어질 때, 스티커를 뜯어내어 얻을 수 있는 숫자의 합의 최댓값을 return 하는 solution 함수를 완성해 주세요. 원형의 스티커 모양을 위해 배열의 첫 번째 원소와 마지막 원소가 서로 연결되어 있다고 간주합니다.
- sticker는 원형으로 연결된 스티커의 각 칸에 적힌 숫자가 순서대로 들어있는 배열로, 길이(N)는 1 이상 100,000 이하입니다.
- sticker의 각 원소는 스티커의 각 칸에 적힌 숫자이며, 각 칸에 적힌 숫자는 1 이상 100 이하의 자연수입니다.
- 원형의 스티커 모양을 위해 sticker 배열의 첫 번째 원소와 마지막 원소가 서로 연결되어있다고 간주합니다.

스티커 모으기1 문제는 풀어보지 않았지만, 이와 비슷한 문제가 백준에서도 존재한다.

문제 유형 자체는 전형적인 DP 문제로 백준의 건물 색칠하기 문제와 유사하다.

 

문제에서 스티커가 원형이 아니었으면 하나의 DP배열을 이용해서 간단히 해결할 수 있지만, 원형이기 때문에 고려해야 할 사항이 추가된 셈이다.

 

<정답 코드>

def solution(sticker):
    n = len(sticker)
    if n == 1:
        return sticker[0]
    if n == 2:
        return max(sticker)
    
    dp1, dp2 = [0] * n, [0] * n
    dp1[0] = sticker[0]
    dp1[1] = max(sticker[0], sticker[1])

    dp2[1] = sticker[1]
    dp2[2] = max(sticker[1], sticker[2])

    for i in range(2, n):
        dp1[i] = max(dp1[i - 1], dp1[i - 2] + sticker[i]) if i < n-1 else dp1[i]
        dp2[i] = max(dp2[i - 1], dp2[i - 2] + sticker[i]) if 2 < i else dp2[i]

    return max(max(dp1), max(dp2))

접근 방법은 아래와 같다.

 

1. 0번 index의 스티커를 사용하는 배열 dp1, 사용하지 않는 배열 dp2를 생성한다.

    1-1. dp1배열은 마지막 스티커를 제외한 n-1개의 스티커를 사용하고, dp2는 0번 스티커를 제외한 n-1개의 스티커를                   사용한다.

2. 각 dp배열에서 처음 사용하게 되는 스티커는 sticker[i]로 초기화 해주고, 두번째로 사용하게 되는 스티커는 첫번째 스티       커와 비교해 더 큰 값으로 지정한다.

3. 앞의 스티커를 사용했을 경우, 바로 다음 스티커는 이용할 수 없으므로 dp[i-1]과 dp[i-2] + sticker[i]를 비교해 max값을      dp[i]에 삽입한다. (1-1을 어기지 않도록 작성한다.)

4. 두 배열 전체에서 가장 큰 값을 리턴한다.

 

3번의 부연 설명

더보기

dp[i]에서는 해당 스티커를 사용했을수도 사용하지 않았을 수도 있다.

- 사용했을 경우는 i+1번째 스티커를 사용할 수 없게 되므로 dp[i+1]은 dp[i-1]이 된다.

- 사용하지 않았을 경우는 dp[i-1]과 dp[i]가 같은 값이므로 dp[i-1]+sticker[i+1]이 dp[i-1]보다 클 수 밖에 없다. (조건에서 스티커의 value는 반드시 양수임을 보장하기 때문에)

 

비슷한 유형의 문제들을 많이 풀고 나니 이와 같은 문제들을 빠른 시간 안에 풀 수 있게 된 것 같다.

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

 

3085번: 사탕 게임

예제 3의 경우 4번 행의 Y와 C를 바꾸면 사탕 네 개를 먹을 수 있다.

www.acmicpc.net

 

문제 설명

🔎상근이는 어렸을 적에 "봄보니 (Bomboni)" 게임을 즐겨했다.
가장 처음에 N×N크기에 사탕을 채워 놓는다. 사탕의 색은 모두 같지 않을 수도 있다. 상근이는 사탕의 색이 다른 인접한 두 칸을 고른다. 그 다음 고른 칸에 들어있는 사탕을 서로 교환한다. 이제, 모두 같은 색으로 이루어져 있는 가장 긴 연속 부분(행 또는 열)을 고른 다음 그 사탕을 모두 먹는다.

사탕이 채워진 상태가 주어졌을 때, 상근이가 먹을 수 있는 사탕의 최대 개수를 구하는 프로그램을 작성하시오.
첫째 줄에 보드의 크기 N이 주어진다. (3 ≤ N ≤ 50)
다음 N개 줄에는 보드에 채워져 있는 사탕의 색상이 주어진다. 빨간색은 C, 파란색은 P, 초록색은 Z, 노란색은 Y로 주어진다.
사탕의 색이 다른 인접한 두 칸이 존재하는 입력만 주어진다.

캔디크러쉬를 구현하라는 문제였다.

사진같은 게임판이 주어졌을 때, 딱 한번 인접한 칸 끼리 교체할 수 있을 때 같은 문자가 나타나는 열 또는 행이 최대가 되는 방법을 도출하라는 것이다.

 

구현을 바탕으로 한 문제지만 문제 분류도 그렇고 결국은 브루트 포스로 접근해야하는 문제였다.

 

<정답 코드>

def candy_count(x, y):
    global n
    ea_x, ea_y = 1, 1
    cur = candy[x][y]

    for ny in range(y+1, n):
        if candy[x][ny] == cur:
            ea_y += 1
        else:
            break

    for py in range(y-1, -1, -1):
        if candy[x][py] == cur:
            ea_y += 1
        else:
            break

    for nx in range(x+1, n):
        if candy[nx][y] == cur:
            ea_x += 1
        else:
            break

    for px in range(x-1, -1, -1):
        if candy[px][y] == cur:
            ea_x += 1
        else:
            break

    return max(ea_x, ea_y)



n = int(input())
candy = [list(input()) for _ in range(n)]
dx, dy = [-1, 1, 0, 0], [0, 0, -1, 1]
ans = 0

for x in range(n):
    for y in range(n):
        ans = max(ans, candy_count(x,y))
        for i in range(4):
            nx = x+dx[i]
            ny = y+dy[i]
            if (0 <= nx < n) and (0 <= ny < n) and candy[x][y] != candy[nx][ny]:
                candy[x][y], candy[nx][ny] = candy[nx][ny], candy[x][y]
                ans = max(ans, candy_count(x, y))
                candy[x][y], candy[nx][ny] = candy[nx][ny], candy[x][y]

print(ans)

브루트포스 문제의 특성인건지 아니면 내가 정말 코드를 너무 못짜서 그런건지, 지저분하게 답이 나왔다.

 

접근 방법은 아래와 같다.

 

1. 입력을 2차원 리스트를 통해 게임보드처럼 생성한다.

2. 반복문을 통해 모든 칸을 순회한다.

3. 각 칸을 순회할 때 주변의 사탕 수를 count한다. 

    3-1. count는 candy_count함수를 통해 이루어진다.

    3-2. candy_count함수는 상하좌우를 모두 순회해 사탕의 행, 열 중 더 많은 사탕의 수를 반환한다.

4. 각 칸에서 인접한 모든 칸에 대해 이동할 수 있고, 원래 칸과 다른 사탕일 때만 스왑한다.

5. 스왑한 후에 candy_count를 한번 더 수행한다.

 

candy_count함수의 경우는 투포인터나 재귀를 통해 좀 더 간략하게 구현할 수 있었을 것 같다.

 

 

브루트포스 문제는 보통 각오로 덤빌 수 있는 문제는 아닌 것 같다. 시간 복잡도도 고려해야하고, 구현 자체도 복잡하고, 코드도 길어져서 한번 틀리면 디버깅하는게 힘든 것 같다.

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

 

2309번: 일곱 난쟁이

아홉 개의 줄에 걸쳐 난쟁이들의 키가 주어진다. 주어지는 키는 100을 넘지 않는 자연수이며, 아홉 난쟁이의 키는 모두 다르며, 가능한 정답이 여러 가지인 경우에는 아무거나 출력한다.

www.acmicpc.net

 

문제 설명

🔎왕비를 피해 일곱 난쟁이들과 함께 평화롭게 생활하고 있던 백설공주에게 위기가 찾아왔다. 일과를 마치고 돌아온 난쟁이가 일곱 명이 아닌 아홉 명이었던 것이다.
아홉 명의 난쟁이는 모두 자신이 "백설 공주와 일곱 난쟁이"의 주인공이라고 주장했다. 뛰어난 수학적 직관력을 가지고 있던 백설공주는, 다행스럽게도 일곱 난쟁이의 키의 합이 100이 됨을 기억해 냈다.
아홉 난쟁이의 키가 주어졌을 때, 백설공주를 도와 일곱 난쟁이를 찾는 프로그램을 작성하시오.
아홉 개의 줄에 걸쳐 난쟁이들의 키가 주어진다. 주어지는 키는 100을 넘지 않는 자연수이며, 아홉 난쟁이의 키는 모두 다르며, 가능한 정답이 여러 가지인 경우에는 아무거나 출력한다.

리스트의 길이가 9로 고정된 비교적 간단한 브루트포스 문제였다. 7명의 키의 합이 100이 되는 경우 중 한가지만 출력하면 된다.

 

7명을 선택하는 방법은 $$ _{9}\mathrm{C}_{7} $$ 로 36가지만 검토하면 되므로 Combination(조합)을 이용해서 풀어도 시간 제약에 걸리지 않고 통과할 것이다.

 

코딩 테스트 도중이라면 위 방법이 먹힘을 확인하고 바로 넘어갔겠지만, 통과하지 못하는 시간 제약이 있을 수도 있어 stack을 이용한 풀이까지 해서 총 2가지 방법으로 풀어보았다.

 

<정답 코드1>

import sys
from itertools import combinations
inp = sys.stdin.readline

heights = []
for _ in range(9):
    heights.append(int(inp()))

heights.sort()

kinds = list(combinations(heights, 7))

for i in kinds:
    if sum(i) == 100:
        print(*i, sep='\n')
        break

 

우선은 조합을 이용한 풀이다.

itertools에서 제공하는 combinations를 활용해 가능한 모든 조합을 도출하고, 조합 안에서 합이 100인 리스트가 존재하면 출력하고 종료하는 것으로 매우 간단한 코드다.

 

<정답 코드2>

import sys
inp = sys.stdin.readline

heights = []
for _ in range(9):
    heights.append(int(inp()))

heights.sort()

for i in range(9):
    for j in range(i, 9):
        if sum(heights) - (heights[i]+heights[j]) == 100:
            heights.pop(j)
            heights.pop(i)
            print(*heights, sep='\n')
            exit()

반복문을 통해 조합을 구현하되, 7명이 아닌 2명을 선택하고, 최악의 경우는 모든 조합을 검토하지만 최선의 경우에는 한번만 점검해도 답이 도출된다는 점에서 combinations보다 빠를 것으로 기대된다.

 

접근방법은 아래와 같다.

 

1. 출력이 오름차순으로 되어야하므로 미리 정렬시켜놓는다.

2. 이중 반복문을 통해 2명을 선택할 수 있는 모든 경우의 수를 순회한다.

3. 순회 도중 합이 100임을 만족하는 선택지가 있으면 해당 2명을 pop한 후 프로그램을 종료한다.

 

문제만 차분히 읽는다면 충분히 빠른 시간 안에 해결할 수 있는 문제였다.

문제 출처: 프로그래머스

 

프로그래머스

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

programmers.co.kr

 

문제 설명

🔎정수 n이 매개변수로 주어집니다. 다음 그림과 같이 밑변의 길이와 높이가 n인 삼각형에서 맨 위 꼭짓점부터 반시계 방향으로 달팽이 채우기를 진행한 후, 첫 행부터 마지막 행까지 모두 순서대로 합친 새로운 배열을 return 하도록 solution 함수를 완성해주세요.

출처: 프로그래머스 삼각 달팽이

 

달팽이가 삼각형을 그리면서 한칸씩 이동할 때 발자취를 남기는 문제다.

해당 문제는 어떤 특별한 자료구조를 사용하기 보다 어떻게 구현할 것이냐가 중요한 문제였다.

 

처음에는 새로운 삼각형 시작의 상관성을 찾아서 문제를 쉽게 해볼려고 노력했었다. (ex. n=4일 때, 다음 삼각형은 10에서 시작, n=5일 때는 13일 때 시작..)

 

하지만 늘 그렇듯 이렇게 생각하는게 오히려 더 복잡하게 돌아가는 방법이었고, 그냥 단순무식하게 코딩을 하는게 더 빠르게 답을 도출할 것 같다 생각이 들었고, 실제로 그랬다.

 

<정답 코드>

def solution(n):
    answer = [[0]*i for i in range(1, n+1)]
    dx = 0
    x, y = 0, 0
    cur = 1
    
    while n:
        if dx == 0:
            for i in range(n):
                answer[x][y] = cur
                cur += 1
                x += 1
            x, y = x-1, y+1
            dx = 1
            
        elif dx == 1:
            for i in range(n):
                answer[x][y] = cur
                cur += 1
                y += 1
            y -= 1
            dx = 2
        
        else:
            for i in range(n):
                x, y = x-1, y-1
                answer[x][y] = cur
                cur += 1
            x += 1
            dx = 0
        
        n -= 1
            
    ans = []
    for i in answer:
        ans.extend(i)
        
    return ans

결과적으로 코드가 아주 너저분하게 됐지만, 통과는 하였다.

접근 방법은 아래와 같다.

접근 방법

1. 피라미드 형식의 2차원 리스트를 생성한다.

2. 달팽이의 진행 방향을 결정하는 dx와 칸을 이동할 때 마다 증가하는 cur 변수를 통해 기록을 진행한다.

3. 달팽이가 한 방향으로 진행할 때 마다 전진 횟수가 줄어드는데 이는 n을 통해 기록한다.

4. 달팽이를 진행시키면서 기록한다.

5. 결과로 나온 2차원 리스트를 1차원 형식으로 변경한다.

 

달팽이는 사진처럼 이동한다.

dx = 0일 때 좌하향, dx = 1일 때 우향, dx = 2일 때 좌상향한다.

한 방향으로 진행이 끝나면 다음 방향으로 진행 횟수가 1 감소한다. (최초 진행횟수는 피라미드의 높이인 n과 같다.)

for문을 통해 해당 사항을 구현하면 최후의 좌표가 도착해야할 좌표보다 1 커지기 때문에 해당 진행 방향으로 1 감소시켜주는게 중요하다.

 

코딩 테스트는 참 컨디션에 영향을 많이 받는 것 같다. 어떤 날은 복잡한 문제가 더 잘 풀리고, 어떤 날은 간단한 문제가 잘 풀리고.. 기복이 없이 조절하는 것이 앞으로의 과제인 것 같다.

고등학생 시절, ‘허니팁스’라는 온라인 거래 플랫폼을 제작하여 창업 동아리를 운영해보기도 하고, ‘머리 눌림 방지 헤드폰’ 아이디어를 공모하여 발명 대회에 출품하기도 하였다. 이러한 아이디어는 언제나 일상 생활 속에서 나에게 필요했던 것 으로 부터 시작되었다.

 

사람들은 제품을 출시하기 전에 ‘시장 조사’ 확실하게 수행하는 것이 중요하다고 한다. 해당 제품이 나에게만 필요한게 아닌 실제로 사람들이 필요로 하는 것인가, 필요로 하는 사람의 수는 얼마나 되는가 등을 조사해서 적절한 수익이 발생한다는 판단이 있어야 제품 생산 및 운영이 지속가능하기 때문이다.

 

여기에서의 ‘시장 조사’가 바로 **제품-시장 적합성(PMF)**이다.

 

제품-시장 적합성은 주로 린 스타트업에서 사용되는 개념으로, 제품 출시 이전 미리 조사해둔다면 아래와 같은 상황을 방지할 수 있게 해준다.

💡 제작자는 아주 좋은 아이디어가 떠올라 열심히 기획을 하고 제품을 생산하고 발전시켜나갔다. 하지만 실제 시장에서는 판매실적이 좋지 않았다.

제작자의 기준에서는 완벽한 아이디어였기에 제품이 잘 팔리지 않는 것에 대한 원인을 분석하지 못해 난처해하였다.

사실 알고보니 제작자의 제품이 필요한 상황이 실제 생활에서 빈번하게 일어나지 않는 일이었다는데 문제가 있었던 것이다.

본인에게 필요한 아이템이라 생각하여 제품을 생산하였으나 사실은 본인에게만 필요한 문제였을 수도 있고, 너무 소수만을 위한 문제였을 수도 있다. 또 일회적인 문제일수도 있기 때문에 제품-시장 적합성에 대한 고려는 상시로 이루어져야 한다.

 

제품-시장 적합성을 판단하는 대표적인 지표로 리텐션(Retention), 전환율(Conversion Rate), 순수 추천 지수(NPS, Net Promoter Score) 3가지를 든다. 리텐션은 해당 제품을 이용하는 고객이 얼마나 꾸준히 남아있는가를 측정하고, 전환율은 제품 사용자의 단계별 전환 비율을 나타낸다(ex. 앱 다운 → 회원가입 → 서비스 구매). 순수 추천 지수는 해당 제품을 얼마나 추천하는지 측정하는 지표이다.


리텐션(Retention)

리텐션은 한글로 유지율, 잔존율이라고 하며, 말 그대로 제품에 대한 고객 유지율을 의미한다.

시장에서 필요로 하는 제품에 대한 리텐션은 초기에 감소하다가 일정 수준이 되면 유지되는 형태로 나타나는 것이 일반적이다.

지속적으로 감소하는 것은 마케팅 효과 등으로 흥미를 지닌 고객들이 초기에 많이 유입되었다가 실제로 제품이 필요하지 않아 이탈하는 비율이 높다고 해석할 수 있다.

하지만 제품의 특성에 따라 일회용인 제품일 경우 리텐션은 꾸준히 감소하여도 이득이 되기도 하는 등 제품의 특성에 따라 다르기도 해 절대적인 기준이 존재하는 것은 아니다.

이는 제품-시장 적합성을 판단하는 기준이 되기도 하고, AARRR에서 등장하기도 하는 개념이기에, 이후 AARRR을 설명할 때 자세하게 설명하기로 한다.

 

전환율(Conversion Rate)

제품에 흥미를 보이기 부터 구매할 때 까지의 과정에 대한 사용자 비율을 나타내면 위와 같은 역 피라미드 형태로 나타나는데, 이를 구매 전환 퍼널이라고 한다.

 

이 역시도 리텐션과 마찬가지로 제품의 특성에 따라 다른 형태를 띄기도 하고, 제품 자체의 성능 보다 UI, UX 등의 영향을 받기도 하지만 일반적으로는 위와 같은 형태를 지닌다.

 

설명한 바와 같이 전환율의 결정 요인들은 많기 때문에, 절대적인 기준을 가지고 목표로 두기보다, 전환율을 지속적으로 관찰하면서 변화를 파악하는 것이 중요하다.

 

순수 추천 지수 (Net Promoter Score, NPS)

웹이나 앱에서 제품을 사용하다보면, 서비스에 만족하고 계신가요? 후기를 남겨주세요! 와 같은 멘트와 함께 별점 팝업이 뜨는 것을 자주 목격할 수 있다.

 

NPS는 위와 같은 간단한 방법을 통해 측정할 수 있는데, 0~10점 까지의 리커트 척도(Likert Scale)로 답변을 받아 9~10점을 준 고객을 적극적 추천 그룹으로 분류하고, 나머지를 비추천 그룹으로 분류해

$$ (적극적 추천 그룹 - 비추천 그룹) \over 전체 응답자 $$

위 공식을 활용해 점수를 측정한다.

 

NPS의 범위는 위 공식에 의해 [-1, 1]을 가지며, 일반적으로 양수가 나오면 양호하다고 판단한다. 적극적 추천 그룹은 서비스에 매우 만족하여 별도의 보상 없이도 제품을 홍보하는 등, 바이럴 마케팅(Viral Marketing)에 활용할 수 있고, 지속적인 앱 사용이 예상되어 이들을 확보하는 것이 중요하다.


여기까지 대표적인 제품-시장 적합성 평가 지표에 대해 알아보았다.

위 지표들이 절대적인 기준인 것은 아니나, 제품을 출시하는 과정에서는 고려해서 손해 볼 것 없기도 하고 실제로 많은 기업에서 이를 마케팅에 활용하고 있다.

그렇다고 해서 지나치게 해당 지표에 집착하여 필요한 다른 지표들을 무시하는 일은 없도록 주의해야할 것이다.

'데이터 분석 > 그로스 해킹' 카테고리의 다른 글

그로스해킹  (0) 2023.01.10