<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>DHL</title>
        <link>https://velog.io/</link>
        <description>말로만 열심히 살기 전문</description>
        <lastBuildDate>Mon, 07 Apr 2025 13:13:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>DHL</title>
            <url>https://velog.velcdn.com/images/kk_0128_/profile/6645f7c2-2d55-4047-a57c-d4f13bf76612/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. DHL. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kk_0128_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[🔐 코딩테스트 연습 - 과일 장수 | 프로그래머스 스쿨]]></title>
            <link>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%EA%B3%BC%EC%9D%BC-%EC%9E%A5%EC%88%98-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8-g3allun4</link>
            <guid>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%EA%B3%BC%EC%9D%BC-%EC%9E%A5%EC%88%98-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8-g3allun4</guid>
            <pubDate>Mon, 07 Apr 2025 13:13:03 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-설명">문제 설명</h3>
<p>과일 장수가 사과 상자를 포장하고 있습니다. 사과는 상태에 따라 1점부터 k점까지의 점수로 분류하며, k점이 최상품의 사과이고 1점이 최하품의 사과입니다. 사과 한 상자의 가격은 다음과 같이 결정됩니다.</p>
<ul>
<li>한 상자에 사과를 m개씩 담아 포장합니다.</li>
<li>상자에 담긴 사과 중 가장 낮은 점수가 p (1 ≤ p ≤ k)점인 경우, 사과 한 상자의 가격은 p * m 입니다.</li>
</ul>
<p>과일 장수가 가능한 많은 사과를 팔았을 때, 얻을 수 있는 최대 이익을 계산하고자 합니다.(사과는 상자 단위로만 판매하며, 남는 사과는 버립니다)</p>
<p>예를 들어, <code>k</code> = 3, <code>m</code> = 4, 사과 7개의 점수가 [1, 2, 3, 1, 2, 3, 1]이라면, 다음과 같이 [2, 3, 2, 3]으로 구성된 사과 상자 1개를 만들어 판매하여 최대 이익을 얻을 수 있습니다.</p>
<ul>
<li>(최저 사과 점수) x (한 상자에 담긴 사과 개수) x (상자의 개수) = 2 x 4 x 1 = 8</li>
</ul>
<p>사과의 최대 점수 <code>k</code>, 한 상자에 들어가는 사과의 수 <code>m</code>, 사과들의 점수 <code>score</code>가 주어졌을 때, 과일 장수가 얻을 수 있는 최대 이익을 return하는 solution 함수를 완성해주세요.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>3 ≤ <code>k</code> ≤ 9</li>
<li>3 ≤ <code>m</code> ≤ 10</li>
<li>7 ≤ <code>score</code>의 길이 ≤ 1,000,000<ul>
<li>1 ≤ <code>score[i]</code> ≤ k</li>
</ul>
</li>
<li>이익이 발생하지 않는 경우에는 0을 return 해주세요.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>k</th>
<th>m</th>
<th>score</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>3</td>
<td>4</td>
<td>[1, 2, 3, 1, 2, 3, 1]</td>
<td>8</td>
</tr>
<tr>
<td>4</td>
<td>3</td>
<td>[4, 1, 2, 2, 4, 4, 4, 4, 1, 2, 4, 2]</td>
<td>33</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p><strong>입출력 예 #1</strong></p>
<ul>
<li>문제의 예시와 같습니다.</li>
</ul>
<p><strong>입출력 예 #2</strong></p>
<ul>
<li>다음과 같이 사과 상자를 포장하여 모두 팔면 최대 이익을 낼 수 있습니다.</li>
</ul>
<table>
<thead>
<tr>
<th>사과 상자</th>
<th>가격</th>
</tr>
</thead>
<tbody><tr>
<td>[1, 1, 2]</td>
<td>1 x 3 = 3</td>
</tr>
<tr>
<td>[2, 2, 2]</td>
<td>2 x 3 = 6</td>
</tr>
<tr>
<td>[4, 4, 4]</td>
<td>4 x 3 = 12</td>
</tr>
<tr>
<td>[4, 4, 4]</td>
<td>4 x 3 = 12</td>
</tr>
</tbody></table>
<p>따라서 (1 x 3 x 1) + (2 x 3 x 1) + (4 x 3 x 2) = 33을 return합니다.</p>
<ul>
<li><a href="https://school.programmers.co.kr/learn/courses/30/lessons/135808#">solution.c</a></li>
</ul>
<p>1</p>
<pre><code>#include &lt;stdio.h&gt;</code></pre><p>2</p>
<pre><code>#include &lt;stdbool.h&gt;</code></pre><p>3</p>
<pre><code>#include &lt;stdlib.h&gt;</code></pre><p>4</p>
<p>5</p>
<pre><code>// score_len은 배열 score의 길이입니다.</code></pre><p>6</p>
<pre><code>int solution(int k, int m, int score[], size_t score_len) {</code></pre><p>7</p>
<pre><code>    int answer = 0;</code></pre><p>8</p>
<pre><code>    return answer;</code></pre><pre><code class="language-python">def solution(k, m, score):
    sortdBox = sorted(score, reverse=True) # 전체 사과를 내림차순으로 정렬
    sum = 0

    for i in range(m-1, len(score), m): # 정렬된 사과들을 한 상자에 담을 수 있는 사과의 수 간격으로 카운트
        sum += sortdBox[i] * m # 가장 작은 가격의 사과 값 * 상자 내 사과 갯수

    return sum</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔐 코딩테스트 연습 - 혼자 놀기의 달인 | 프로그래머스 스쿨]]></title>
            <link>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%ED%98%BC%EC%9E%90-%EB%86%80%EA%B8%B0%EC%9D%98-%EB%8B%AC%EC%9D%B8-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8-kkn01u04</link>
            <guid>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%ED%98%BC%EC%9E%90-%EB%86%80%EA%B8%B0%EC%9D%98-%EB%8B%AC%EC%9D%B8-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8-kkn01u04</guid>
            <pubDate>Mon, 07 Apr 2025 13:12:07 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-설명">문제 설명</h3>
<p>혼자서도 잘 노는 범희는 어느 날 방구석에 있는 숫자 카드 더미를 보더니 혼자 할 수 있는 재미있는 게임을 생각해냈습니다.</p>
<p>숫자 카드 더미에는 카드가 총 100장 있으며, 각 카드에는 1부터 100까지 숫자가 하나씩 적혀있습니다. 2 이상 100 이하의 자연수를 하나 정해 그 수보다 작거나 같은 숫자 카드들을 준비하고, 준비한 카드의 수만큼 작은 상자를 준비하면 게임을 시작할 수 있으며 게임 방법은 다음과 같습니다.</p>
<p>준비된 상자에 카드를 한 장씩 넣고, 상자를 무작위로 섞어 일렬로 나열합니다. 상자가 일렬로 나열되면 상자가 나열된 순서에 따라 1번부터 순차적으로 증가하는 번호를 붙입니다.</p>
<p>그 다음 임의의 상자를 하나 선택하여 선택한 상자 안의 숫자 카드를 확인합니다. 다음으로 확인한 카드에 적힌 번호에 해당하는 상자를 열어 안에 담긴 카드에 적힌 숫자를 확인합니다. 마찬가지로 숫자에 해당하는 번호를 가진 상자를 계속해서 열어가며, 열어야 하는 상자가 이미 열려있을 때까지 반복합니다.</p>
<p>이렇게 연 상자들은 1번 상자 그룹입니다. 이제 1번 상자 그룹을 다른 상자들과 섞이지 않도록 따로 둡니다. 만약 1번 상자 그룹을 제외하고 남는 상자가 없으면 그대로 게임이 종료되며, 이때 획득하는 점수는 0점입니다.</p>
<p>그렇지 않다면 남은 상자 중 다시 임의의 상자 하나를 골라 같은 방식으로 이미 열려있는 상자를 만날 때까지 상자를 엽니다. 이렇게 연 상자들은 2번 상자 그룹입니다.</p>
<p>1번 상자 그룹에 속한 상자의 수와 2번 상자 그룹에 속한 상자의 수를 곱한 값이 게임의 점수입니다.</p>
<p>상자 안에 들어있는 카드 번호가 순서대로 담긴 배열 <code>cards</code>가 매개변수로 주어질 때, 범희가 이 게임에서 얻을 수 있는 최고 점수를 구해서 return 하도록 solution 함수를 완성해주세요.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li><code>2</code> ≤ <code>cards</code>의 길이 ≤ <code>100</code></li>
<li><code>cards</code>의 원소는 <code>cards</code>의 길이 이하인 임의의 자연수입니다.</li>
<li><code>cards</code>에는 중복되는 원소가 존재하지 않습니다.</li>
<li><code>cards[i]</code>는 i + 1번 상자에 담긴 카드에 적힌 숫자를 의미합니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>cards</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>[8,6,3,7,2,5,1,4]</td>
<td>12</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p>1, 4, 7, 8이 속하는 상자의 그룹과 2, 5, 6이 속하는 상자의 그룹과 3이 속하는 상자의 그룹이 존재합니다. 따라서 3번 상자를 고르지 않았을 때, 두 번의 시행에서 3과 4를 기록하며 최고 점수 12를 얻을 수 있습니다.</p>
<pre><code class="language-python">def solution(cards):
    answer = 0
    visited = [False for _ in range(len(cards))]  # 각 상자가 열렸는지 여부를 기록하는 배열
    lst = []  # 각 그룹의 크기를 저장할 리스트

    for i in range(len(cards)):  # 모든 상자를 순회하며 그룹을 형성
        if visited[i]:  # 이미 열렸다면 건너뜀
            continue
        visited[i] = True  # 현재 상자를 열었다고 표시
        cur, linked = i, 1  # 현재 상자의 인덱스와 그룹 크기를 초기화
        while cards[cur] - 1 != i:  # 다음 상자로 이동하며 그룹 형성
            cur = cards[cur] - 1  # 카드에 적힌 숫자를 인덱스로 사용하여 이동
            visited[cur] = True  # 해당 상자를 열었다고 표시
            linked += 1  # 그룹 크기를 증가시킴
        lst.append(linked)  # 그룹 크기를 리스트에 추가

    if len(lst) &lt; 2:  # 그룹이 2개 미만이면 점수는 0
        answer = 0
    else:
        lst.sort(reverse=True)  # 그룹 크기를 내림차순으로 정렬
        answer = lst[0] * lst[1]  # 가장 큰 두 그룹의 크기를 곱함

    return answer  # 최종 점수를 반환</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔐 코딩테스트 연습 - 소수 찾기 | 프로그래머스 스쿨]]></title>
            <link>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%EC%86%8C%EC%88%98-%EC%B0%BE%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8</link>
            <guid>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%EC%86%8C%EC%88%98-%EC%B0%BE%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8</guid>
            <pubDate>Mon, 07 Apr 2025 13:09:35 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-설명">문제 설명</h3>
<p>1부터 입력받은 숫자 n 사이에 있는 소수의 개수를 반환하는 함수, solution을 만들어 보세요.</p>
<p>소수는 1과 자기 자신으로만 나누어지는 수를 의미합니다.</p>
<p>(1은 소수가 아닙니다.)</p>
<h3 id="제한-조건">제한 조건</h3>
<ul>
<li>n은 2이상 1000000이하의 자연수입니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>n</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>10</td>
<td>4</td>
</tr>
<tr>
<td>5</td>
<td>3</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p>입출력 예 #1</p>
<p>1부터 10 사이의 소수는 [2,3,5,7] 4개가 존재하므로 4를 반환</p>
<p>입출력 예 #2</p>
<p>1부터 5 사이의 소수는 [2,3,5] 3개가 존재하므로 3를 반환</p>
<pre><code class="language-python">import math

def solution(n):
    result = []
    tmp = [True for i in range(n+1)]

    for i in range(2, int(math.sqrt(n))+1):
        if tmp[i] == True:
            j = 2
            while i * j &lt;= n:
                tmp[i*j] = False
                j+=1

    for j in range(2,n+1):
        if tmp[j]:
            result.append(j)

    return len(result)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔐 코딩테스트 연습 - 크기가 작은 부분 문자열 | 프로그래머스 스쿨]]></title>
            <link>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%ED%81%AC%EA%B8%B0%EA%B0%80-%EC%9E%91%EC%9D%80-%EB%B6%80%EB%B6%84-%EB%AC%B8%EC%9E%90%EC%97%B4-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8-gzuatjoo</link>
            <guid>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%ED%81%AC%EA%B8%B0%EA%B0%80-%EC%9E%91%EC%9D%80-%EB%B6%80%EB%B6%84-%EB%AC%B8%EC%9E%90%EC%97%B4-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8-gzuatjoo</guid>
            <pubDate>Mon, 07 Apr 2025 13:08:19 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-설명">문제 설명</h3>
<p>숫자로 이루어진 문자열 <code>t</code>와 <code>p</code>가 주어질 때, <code>t</code>에서 <code>p</code>와 길이가 같은 부분문자열 중에서, 이 부분문자열이 나타내는 수가 <code>p</code>가 나타내는 수보다 작거나 같은 것이 나오는 횟수를 return하는 함수 solution을 완성하세요.</p>
<p>예를 들어, <code>t</code>=&quot;3141592&quot;이고 <code>p</code>=&quot;271&quot; 인 경우, <code>t</code>의 길이가 3인 부분 문자열은 314, 141, 415, 159, 592입니다. 이 문자열이 나타내는 수 중 271보다 작거나 같은 수는 141, 159 2개 입니다.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>1 ≤ <code>p</code>의 길이 ≤ 18</li>
<li><code>p</code>의 길이 ≤ <code>t</code>의 길이 ≤ 10,000</li>
<li><code>t</code>와 <code>p</code>는 숫자로만 이루어진 문자열이며, 0으로 시작하지 않습니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>t</th>
<th>p</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>&quot;3141592&quot;</td>
<td>&quot;271&quot;</td>
<td>2</td>
</tr>
<tr>
<td>&quot;500220839878&quot;</td>
<td>&quot;7&quot;</td>
<td>8</td>
</tr>
<tr>
<td>&quot;10203&quot;</td>
<td>&quot;15&quot;</td>
<td>3</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p>입출력 예 #1</p>
<p>본문과 같습니다.</p>
<p>입출력 예 #2</p>
<p><code>p</code>의 길이가 1이므로 <code>t</code>의 부분문자열은 &quot;5&quot;, &quot;0&quot;, 0&quot;, &quot;2&quot;, &quot;2&quot;, &quot;0&quot;, &quot;8&quot;, &quot;3&quot;, &quot;9&quot;, &quot;8&quot;, &quot;7&quot;, &quot;8&quot;이며 이중 7보다 작거나 같은 숫자는 &quot;5&quot;, &quot;0&quot;, &quot;0&quot;, &quot;2&quot;, &quot;2&quot;, &quot;0&quot;, &quot;3&quot;, &quot;7&quot; 이렇게 8개가 있습니다.</p>
<p>입출력 예 #3</p>
<p><code>p</code>의 길이가 2이므로 <code>t</code>의 부분문자열은 &quot;10&quot;, &quot;02&quot;, &quot;20&quot;, &quot;03&quot;이며, 이중 15보다 작거나 같은 숫자는 &quot;10&quot;, &quot;02&quot;, &quot;03&quot; 이렇게 3개입니다. &quot;02&quot;와 &quot;03&quot;은 각각 2, 3에 해당한다는 점에 주의하세요</p>
<ul>
<li><a href="https://school.programmers.co.kr/learn/courses/30/lessons/147355#">solution.c</a></li>
</ul>
<p>1</p>
<pre><code>#include &lt;stdio.h&gt;</code></pre><p>2</p>
<pre><code>#include &lt;stdbool.h&gt;</code></pre><p>3</p>
<pre><code>#include &lt;stdlib.h&gt;</code></pre><p>4</p>
<p>5</p>
<pre><code>// 파라미터로 주어지는 문자열은 const로 주어집니다. 변경하려면 문자열을 복사해서 사용하세요.</code></pre><p>6</p>
<pre><code>int solution(const char* t, const char* p) {</code></pre><p>7</p>
<pre><code>    int answer = 0;</code></pre><p>8</p>
<pre><code>    return answer;</code></pre><pre><code class="language-python">def solution(t, p):
    max = len(t) - 1
    i = 0
    j = 0
    lenP = len(p)
    answer = 0

    while (j &lt;= max): 
        if (int(t[i:lenP + j]) &lt;= int(p)):
            answer += 1     
        i += 1
        j += 1
        if (i == len(t)- len(p) + 1):
            return answer</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔐 코딩테스트 연습 - 신고 결과 받기 | 프로그래머스 스쿨]]></title>
            <link>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%EC%8B%A0%EA%B3%A0-%EA%B2%B0%EA%B3%BC-%EB%B0%9B%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8</link>
            <guid>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%EC%8B%A0%EA%B3%A0-%EA%B2%B0%EA%B3%BC-%EB%B0%9B%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8</guid>
            <pubDate>Mon, 07 Apr 2025 13:06:18 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-설명">문제 설명</h3>
<p>신입사원 무지는 게시판 불량 이용자를 신고하고 처리 결과를 메일로 발송하는 시스템을 개발하려 합니다. 무지가 개발하려는 시스템은 다음과 같습니다.</p>
<ul>
<li>각 유저는 한 번에 한 명의 유저를 신고할 수 있습니다.<ul>
<li>신고 횟수에 제한은 없습니다. 서로 다른 유저를 계속해서 신고할 수 있습니다.</li>
<li>한 유저를 여러 번 신고할 수도 있지만, 동일한 유저에 대한 신고 횟수는 1회로 처리됩니다.</li>
</ul>
</li>
<li>k번 이상 신고된 유저는 게시판 이용이 정지되며, 해당 유저를 신고한 모든 유저에게 정지 사실을 메일로 발송합니다.<ul>
<li>유저가 신고한 모든 내용을 취합하여 마지막에 한꺼번에 게시판 이용 정지를 시키면서 정지 메일을 발송합니다.</li>
</ul>
</li>
</ul>
<p>다음은 전체 유저 목록이 [&quot;muzi&quot;, &quot;frodo&quot;, &quot;apeach&quot;, &quot;neo&quot;]이고, k = 2(즉, 2번 이상 신고당하면 이용 정지)인 경우의 예시입니다.</p>
<table>
<thead>
<tr>
<th>유저 ID</th>
<th>유저가 신고한 ID</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>&quot;muzi&quot;</td>
<td>&quot;frodo&quot;</td>
<td>&quot;muzi&quot;가 &quot;frodo&quot;를 신고했습니다.</td>
</tr>
<tr>
<td>&quot;apeach&quot;</td>
<td>&quot;frodo&quot;</td>
<td>&quot;apeach&quot;가 &quot;frodo&quot;를 신고했습니다.</td>
</tr>
<tr>
<td>&quot;frodo&quot;</td>
<td>&quot;neo&quot;</td>
<td>&quot;frodo&quot;가 &quot;neo&quot;를 신고했습니다.</td>
</tr>
<tr>
<td>&quot;muzi&quot;</td>
<td>&quot;neo&quot;</td>
<td>&quot;muzi&quot;가 &quot;neo&quot;를 신고했습니다.</td>
</tr>
<tr>
<td>&quot;apeach&quot;</td>
<td>&quot;muzi&quot;</td>
<td>&quot;apeach&quot;가 &quot;muzi&quot;를 신고했습니다.</td>
</tr>
</tbody></table>
<p>각 유저별로 신고당한 횟수는 다음과 같습니다.</p>
<table>
<thead>
<tr>
<th>유저 ID</th>
<th>신고당한 횟수</th>
</tr>
</thead>
<tbody><tr>
<td>&quot;muzi&quot;</td>
<td>1</td>
</tr>
<tr>
<td>&quot;frodo&quot;</td>
<td>2</td>
</tr>
<tr>
<td>&quot;apeach&quot;</td>
<td>0</td>
</tr>
<tr>
<td>&quot;neo&quot;</td>
<td>2</td>
</tr>
</tbody></table>
<p>위 예시에서는 2번 이상 신고당한 &quot;frodo&quot;와 &quot;neo&quot;의 게시판 이용이 정지됩니다. 이때, 각 유저별로 신고한 아이디와 정지된 아이디를 정리하면 다음과 같습니다.</p>
<table>
<thead>
<tr>
<th>유저 ID</th>
<th>유저가 신고한 ID</th>
<th>정지된 ID</th>
</tr>
</thead>
<tbody><tr>
<td>&quot;muzi&quot;</td>
<td>[&quot;frodo&quot;, &quot;neo&quot;]</td>
<td>[&quot;frodo&quot;, &quot;neo&quot;]</td>
</tr>
<tr>
<td>&quot;frodo&quot;</td>
<td>[&quot;neo&quot;]</td>
<td>[&quot;neo&quot;]</td>
</tr>
<tr>
<td>&quot;apeach&quot;</td>
<td>[&quot;muzi&quot;, &quot;frodo&quot;]</td>
<td>[&quot;frodo&quot;]</td>
</tr>
<tr>
<td>&quot;neo&quot;</td>
<td>없음</td>
<td>없음</td>
</tr>
</tbody></table>
<p>따라서 &quot;muzi&quot;는 처리 결과 메일을 2회, &quot;frodo&quot;와 &quot;apeach&quot;는 각각 처리 결과 메일을 1회 받게 됩니다.</p>
<p>이용자의 ID가 담긴 문자열 배열 <code>id_list</code>, 각 이용자가 신고한 이용자의 ID 정보가 담긴 문자열 배열 <code>report</code>, 정지 기준이 되는 신고 횟수 <code>k</code>가 매개변수로 주어질 때, 각 유저별로 처리 결과 메일을 받은 횟수를 배열에 담아 return 하도록 solution 함수를 완성해주세요.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>2 ≤ <code>id_list</code>의 길이 ≤ 1,000<ul>
<li>1 ≤ <code>id_list</code>의 원소 길이 ≤ 10</li>
<li><code>id_list</code>의 원소는 이용자의 id를 나타내는 문자열이며 알파벳 소문자로만 이루어져 있습니다.</li>
<li><code>id_list</code>에는 같은 아이디가 중복해서 들어있지 않습니다.</li>
</ul>
</li>
<li>1 ≤ <code>report</code>의 길이 ≤ 200,000<ul>
<li>3 ≤ <code>report</code>의 원소 길이 ≤ 21</li>
<li><code>report</code>의 원소는 &quot;이용자id 신고한id&quot;형태의 문자열입니다.</li>
<li>예를 들어 &quot;muzi frodo&quot;의 경우 &quot;muzi&quot;가 &quot;frodo&quot;를 신고했다는 의미입니다.</li>
<li>id는 알파벳 소문자로만 이루어져 있습니다.</li>
<li>이용자id와 신고한id는 공백(스페이스)하나로 구분되어 있습니다.</li>
<li>자기 자신을 신고하는 경우는 없습니다.</li>
</ul>
</li>
<li>1 ≤ <code>k</code> ≤ 200, <code>k</code>는 자연수입니다.</li>
<li>return 하는 배열은 <code>id_list</code>에 담긴 id 순서대로 각 유저가 받은 결과 메일 수를 담으면 됩니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>id_list</th>
<th>report</th>
<th>k</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td><code>[&quot;muzi&quot;, &quot;frodo&quot;, &quot;apeach&quot;, &quot;neo&quot;]</code></td>
<td><code>[&quot;muzi frodo&quot;,&quot;apeach frodo&quot;,&quot;frodo neo&quot;,&quot;muzi neo&quot;,&quot;apeach muzi&quot;]</code></td>
<td>2</td>
<td>[2,1,1,0]</td>
</tr>
<tr>
<td><code>[&quot;con&quot;, &quot;ryan&quot;]</code></td>
<td><code>[&quot;ryan con&quot;, &quot;ryan con&quot;, &quot;ryan con&quot;, &quot;ryan con&quot;]</code></td>
<td>3</td>
<td>[0,0]</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p><strong>입출력 예 #1</strong></p>
<p>문제의 예시와 같습니다.</p>
<p><strong>입출력 예 #2</strong></p>
<p>&quot;ryan&quot;이 &quot;con&quot;을 4번 신고했으나, 주어진 조건에 따라 한 유저가 같은 유저를 여러 번 신고한 경우는 신고 횟수 1회로 처리합니다. 따라서 &quot;con&quot;은 1회 신고당했습니다. 3번 이상 신고당한 이용자는 없으며, &quot;con&quot;과 &quot;ryan&quot;은 결과 메일을 받지 않습니다. 따라서 [0, 0]을 return 합니다.</p>
<h3 id="제한시간-안내">제한시간 안내</h3>
<ul>
<li>정확성 테스트 : 10초</li>
</ul>
<pre><code class="language-python">def solution(id_list, report, k):
    warning = {}
    result = [0] * len(id_list)  # 각 사용자가 받을 메일 수를 저장할 리스트
    report = set(report)  # 중복 신고를 제거

    # 신고 정보 처리
    for i in report:
        tmp = i.split()  # 공백을 기준으로 신고자와 대상자를 분리
        reporter, target = tmp[0], tmp[1]

        # 타겟에 대한 신고 정보가 warning에 없으면 초기화
        if target not in warning:
            warning[target] = [0, []]  # [신고 횟수, 신고한 사용자 리스트]

        # 신고자가 중복되지 않으면 추가
        if reporter not in warning[target][1]:
            warning[target][0] += 1
            warning[target][1].append(reporter)

    # 신고 횟수가 k번 이상인 경우 메일 발송
    for target, info in warning.items():
        if info[0] &gt;= k:  # 신고 횟수가 k 이상인 경우
            for reporter in info[1]:  # 해당 사용자를 신고한 사람들에게 메일 발송
                result[id_list.index(reporter)] += 1

    return result</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔐 코딩테스트 연습 - 숫자 짝꿍 | 프로그래머스 스쿨]]></title>
            <link>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%EC%88%AB%EC%9E%90-%EC%A7%9D%EA%BF%8D-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8-dtgn5eih</link>
            <guid>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%EC%88%AB%EC%9E%90-%EC%A7%9D%EA%BF%8D-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8-dtgn5eih</guid>
            <pubDate>Mon, 07 Apr 2025 13:05:16 GMT</pubDate>
            <description><![CDATA[<p>문제 설명</p>
<p>두 정수 <code>X</code>, <code>Y</code>의 임의의 자리에서 공통으로 나타나는 정수 k(0 ≤ k ≤ 9)들을 이용하여 만들 수 있는 가장 큰 정수를 두 수의 짝꿍이라 합니다(단, 공통으로 나타나는 정수 중 서로 짝지을 수 있는 숫자만 사용합니다). <code>X</code>, <code>Y</code>의 짝꿍이 존재하지 않으면, 짝꿍은 -1입니다. <code>X</code>, <code>Y</code>의 짝꿍이 0으로만 구성되어 있다면, 짝꿍은 0입니다.</p>
<p>예를 들어, <code>X</code> = 3403이고 <code>Y</code> = 13203이라면, <code>X</code>와 <code>Y</code>의 짝꿍은 <code>X</code>와 <code>Y</code>에서 공통으로 나타나는 3, 0, 3으로 만들 수 있는 가장 큰 정수인 330입니다. 다른 예시로 <code>X</code> = 5525이고 <code>Y</code> = 1255이면 <code>X</code>와 <code>Y</code>의 짝꿍은 <code>X</code>와 <code>Y</code>에서 공통으로 나타나는 2, 5, 5로 만들 수 있는 가장 큰 정수인 552입니다(<code>X</code>에는 5가 3개, <code>Y</code>에는 5가 2개 나타나므로 남는 5 한 개는 짝 지을 수 없습니다.)</p>
<p>두 정수 <code>X</code>, <code>Y</code>가 주어졌을 때, <code>X</code>, <code>Y</code>의 짝꿍을 return하는 solution 함수를 완성해주세요.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>3 ≤ <code>X</code>, <code>Y</code>의 길이(자릿수) ≤ 3,000,000입니다.</li>
<li><code>X</code>, <code>Y</code>는 0으로 시작하지 않습니다.</li>
<li><code>X</code>, <code>Y</code>의 짝꿍은 상당히 큰 정수일 수 있으므로, 문자열로 반환합니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>X</th>
<th>Y</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>&quot;100&quot;</td>
<td>&quot;2345&quot;</td>
<td>&quot;-1&quot;</td>
</tr>
<tr>
<td>&quot;100&quot;</td>
<td>&quot;203045&quot;</td>
<td>&quot;0&quot;</td>
</tr>
<tr>
<td>&quot;100&quot;</td>
<td>&quot;123450&quot;</td>
<td>&quot;10&quot;</td>
</tr>
<tr>
<td>&quot;12321&quot;</td>
<td>&quot;42531&quot;</td>
<td>&quot;321&quot;</td>
</tr>
<tr>
<td>&quot;5525&quot;</td>
<td>&quot;1255&quot;</td>
<td>&quot;552&quot;</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p><strong>입출력 예 #1</strong></p>
<ul>
<li><code>X</code>, <code>Y</code>의 짝꿍은 존재하지 않습니다. 따라서 &quot;-1&quot;을 return합니다.</li>
</ul>
<p><strong>입출력 예 #2</strong></p>
<ul>
<li><code>X</code>, <code>Y</code>의 공통된 숫자는 0으로만 구성되어 있기 때문에, 두 수의 짝꿍은 정수 0입니다. 따라서 &quot;0&quot;을 return합니다.</li>
</ul>
<p><strong>입출력 예 #3</strong></p>
<ul>
<li><code>X</code>, <code>Y</code>의 짝꿍은 10이므로, &quot;10&quot;을 return합니다.</li>
</ul>
<p><strong>입출력 예 #4</strong></p>
<ul>
<li><code>X</code>, <code>Y</code>의 짝꿍은 321입니다. 따라서 &quot;321&quot;을 return합니다.</li>
</ul>
<p><strong>입출력 예 #5</strong></p>
<ul>
<li>지문에 설명된 예시와 같습니다.</li>
<li><a href="https://school.programmers.co.kr/learn/courses/30/lessons/131128#">solution.c</a></li>
</ul>
<p>1</p>
<pre><code>#include &lt;stdio.h&gt;</code></pre><p>2</p>
<pre><code>#include &lt;stdbool.h&gt;</code></pre><p>3</p>
<pre><code>#include &lt;stdlib.h&gt;</code></pre><p>4</p>
<p>5</p>
<pre><code>// 파라미터로 주어지는 문자열은 const로 주어집니다. 변경하려면 문자열을 복사해서 사용하세요.</code></pre><p>6</p>
<pre><code>char* solution(const char* X, const char* Y) {</code></pre><p>7</p>
<pre><code>    // return 값은 malloc 등 동적 할당을 사용해주세요. 할당 길이는 상황에 맞게 변경해주세요.</code></pre><p>8</p>
<pre><code>    char* answer = (char*)malloc(1);</code></pre><p>9</p>
<pre><code>    return answer;</code></pre><pre><code class="language-python">def solution(X, Y):
    answer = &#39;&#39;

    for i in range(9,-1,-1) :
        answer += (str(i) * min(X.count(str(i)), Y.count(str(i))))

    if answer == &#39;&#39; :
        return &#39;-1&#39;
    elif len(answer) == answer.count(&#39;0&#39;):
        return &#39;0&#39;
    else :
        return answer</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔐 코딩테스트 연습 - 달리기 경주 | 프로그래머스 스쿨]]></title>
            <link>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%EB%8B%AC%EB%A6%AC%EA%B8%B0-%EA%B2%BD%EC%A3%BC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8</link>
            <guid>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-%EB%8B%AC%EB%A6%AC%EA%B8%B0-%EA%B2%BD%EC%A3%BC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8</guid>
            <pubDate>Mon, 07 Apr 2025 13:03:17 GMT</pubDate>
            <description><![CDATA[<p>문제 설명</p>
<p>얀에서는 매년 달리기 경주가 열립니다. 해설진들은 선수들이 자기 바로 앞의 선수를 추월할 때 추월한 선수의 이름을 부릅니다. 예를 들어 1등부터 3등까지 &quot;mumu&quot;, &quot;soe&quot;, &quot;poe&quot; 선수들이 순서대로 달리고 있을 때, 해설진이 &quot;soe&quot;선수를 불렀다면 2등인 &quot;soe&quot; 선수가 1등인 &quot;mumu&quot; 선수를 추월했다는 것입니다. 즉 &quot;soe&quot; 선수가 1등, &quot;mumu&quot; 선수가 2등으로 바뀝니다.</p>
<p>선수들의 이름이 1등부터 현재 등수 순서대로 담긴 문자열 배열 <code>players</code>와 해설진이 부른 이름을 담은 문자열 배열 <code>callings</code>가 매개변수로 주어질 때, 경주가 끝났을 때 선수들의 이름을 1등부터 등수 순서대로 배열에 담아 return 하는 solution 함수를 완성해주세요.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>5 ≤ <code>players</code>의 길이 ≤ 50,000<ul>
<li><code>players[i]</code>는 i번째 선수의 이름을 의미합니다.</li>
<li><code>players</code>의 원소들은 알파벳 소문자로만 이루어져 있습니다.</li>
<li><code>players</code>에는 중복된 값이 들어가 있지 않습니다.</li>
<li>3 ≤ <code>players[i]</code>의 길이 ≤ 10</li>
</ul>
</li>
<li>2 ≤ <code>callings</code>의 길이 ≤ 1,000,000<ul>
<li><code>callings</code>는 <code>players</code>의 원소들로만 이루어져 있습니다.</li>
<li>경주 진행중 1등인 선수의 이름은 불리지 않습니다.</li>
</ul>
</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>players</th>
<th>callings</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>[&quot;mumu&quot;, &quot;soe&quot;, &quot;poe&quot;, &quot;kai&quot;, &quot;mine&quot;]</td>
<td>[&quot;kai&quot;, &quot;kai&quot;, &quot;mine&quot;, &quot;mine&quot;]</td>
<td>[&quot;mumu&quot;, &quot;kai&quot;, &quot;mine&quot;, &quot;soe&quot;, &quot;poe&quot;]</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p>입출력 예 #1</p>
<p>4등인 &quot;kai&quot; 선수가 2번 추월하여 2등이 되고 앞서 3등, 2등인 &quot;poe&quot;, &quot;soe&quot; 선수는 4등, 3등이 됩니다. 5등인 &quot;mine&quot; 선수가 2번 추월하여 4등, 3등인 &quot;poe&quot;, &quot;soe&quot; 선수가 5등, 4등이 되고 경주가 끝납니다. 1등부터 배열에 담으면 [&quot;mumu&quot;, &quot;kai&quot;, &quot;mine&quot;, &quot;soe&quot;, &quot;poe&quot;]이 됩니다.</p>
<pre><code class="language-python">def solution(players, callings):
    result = {player: i for i, player in enumerate(players)} # 선수: 등수
    for name in callings:
        idxTmp = result[name] # 현재 등수
        result[name] -= 1 # 등수 변경
        result[players[idxTmp-1]] += 1 # 등수 Idx 뒤로 변경
        players[idxTmp-1], players[idxTmp] = players[idxTmp], players[idxTmp-1] # 위치 교환
    return players</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔐 코딩테스트 연습 - [PCCP 기출문제] 1번 / 붕대 감기 | 프로그래머스 스쿨]]></title>
            <link>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-PCCP-%EA%B8%B0%EC%B6%9C%EB%AC%B8%EC%A0%9C-1%EB%B2%88-%EB%B6%95%EB%8C%80-%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8</link>
            <guid>https://velog.io/@kk_0128_/%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%8A%B5-PCCP-%EA%B8%B0%EC%B6%9C%EB%AC%B8%EC%A0%9C-1%EB%B2%88-%EB%B6%95%EB%8C%80-%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%8A%A4%EC%BF%A8</guid>
            <pubDate>Mon, 07 Apr 2025 12:59:51 GMT</pubDate>
            <description><![CDATA[<p>문제 설명</p>
<p>어떤 게임에는 <code>붕대 감기</code>라는 기술이 있습니다.</p>
<p><code>붕대 감기</code>는 <code>t</code>초 동안 붕대를 감으면서 1초마다 <code>x</code>만큼의 체력을 회복합니다. <code>t</code>초 연속으로 붕대를 감는 데 성공한다면 <code>y</code>만큼의 체력을 추가로 회복합니다. 게임 캐릭터에는 최대 체력이 존재해 현재 체력이 최대 체력보다 커지는 것은 불가능합니다.</p>
<p>기술을 쓰는 도중 몬스터에게 공격을 당하면 기술이 취소되고, 공격을 당하는 순간에는 체력을 회복할 수 없습니다. 몬스터에게 공격당해 기술이 취소당하거나 기술이 끝나면 그 즉시 <code>붕대 감기</code>를 다시 사용하며, 연속 성공 시간이 0으로 초기화됩니다.</p>
<p>몬스터의 공격을 받으면 정해진 피해량만큼 현재 체력이 줄어듭니다. 이때, 현재 체력이 0 이하가 되면 캐릭터가 죽으며 더 이상 체력을 회복할 수 없습니다.</p>
<p>당신은 <code>붕대감기</code> 기술의 정보, 캐릭터가 가진 최대 체력과 몬스터의 공격 패턴이 주어질 때 캐릭터가 끝까지 생존할 수 있는지 궁금합니다.</p>
<p><code>붕대 감기</code> 기술의 시전 시간, 1초당 회복량, 추가 회복량을 담은 1차원 정수 배열 <code>bandage</code>와 최대 체력을 의미하는 정수 <code>health</code>, 몬스터의 공격 시간과 피해량을 담은 2차원 정수 배열 <code>attacks</code>가 매개변수로 주어집니다. 모든 공격이 끝난 직후 남은 체력을 return 하도록 solution 함수를 완성해 주세요. <strong>만약 몬스터의 공격을 받고 캐릭터의 체력이 0 이하가 되어 죽는다면 -1을 return 해주세요.</strong></p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li><code>bandage</code>는 [<code>시전 시간</code>, <code>초당 회복량</code>, <code>추가 회복량</code>] 형태의 길이가 3인 정수 배열입니다.<ul>
<li>1 ≤ <code>시전 시간</code> = <code>t</code> ≤ 50</li>
<li>1 ≤ <code>초당 회복량</code> = <code>x</code> ≤ 100</li>
<li>1 ≤ <code>추가 회복량</code> = <code>y</code> ≤ 100</li>
</ul>
</li>
<li>1 ≤ <code>health</code> ≤ 1,000</li>
<li>1 ≤ <code>attacks</code>의 길이 ≤ 100<ul>
<li><code>attacks[i]</code>는 [<code>공격 시간</code>, <code>피해량</code>] 형태의 길이가 2인 정수 배열입니다.</li>
<li><code>attacks</code>는 <code>공격 시간</code>을 기준으로 오름차순 정렬된 상태입니다.</li>
<li><code>attacks</code>의 <code>공격 시간</code>은 모두 다릅니다.</li>
<li>1 ≤ <code>공격 시간</code> ≤ 1,000</li>
<li>1 ≤ <code>피해량</code> ≤ 100</li>
</ul>
</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>bandage</th>
<th>health</th>
<th>attacks</th>
<th>result</th>
</tr>
</thead>
<tbody><tr>
<td>[5, 1, 5]</td>
<td>30</td>
<td>[[2, 10], [9, 15], [10, 5], [11, 5]]</td>
<td>5</td>
</tr>
<tr>
<td>[3, 2, 7]</td>
<td>20</td>
<td>[[1, 15], [5, 16], [8, 6]]</td>
<td>-1</td>
</tr>
<tr>
<td>[4, 2, 7]</td>
<td>20</td>
<td>[[1, 15], [5, 16], [8, 6]]</td>
<td>-1</td>
</tr>
<tr>
<td>[1, 1, 1]</td>
<td>5</td>
<td>[[1, 2], [3, 2]]</td>
<td>3</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p><strong>입출력 예 #1</strong></p>
<p>몬스터의 마지막 공격은 11초에 이루어집니다. 0초부터 11초까지 캐릭터의 상태는 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th>시간</th>
<th>현재 체력(변화량)</th>
<th>연속 성공</th>
<th>공격</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>30</td>
<td>0</td>
<td>X</td>
<td>초기 상태</td>
</tr>
<tr>
<td>1</td>
<td>30(+0)</td>
<td>1</td>
<td>X</td>
<td>최대 체력 이상의 체력을 가질 수 없습니다.</td>
</tr>
<tr>
<td>2</td>
<td>20(-10)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 공격으로 연속 성공이 초기화됩니다.</td>
</tr>
<tr>
<td>3</td>
<td>21(+1)</td>
<td>1</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>4</td>
<td>22(+1)</td>
<td>2</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>5</td>
<td>23(+1)</td>
<td>3</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>6</td>
<td>24(+1)</td>
<td>4</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>7</td>
<td>30(+6)</td>
<td>5 → 0</td>
<td>X</td>
<td>5초 연속 성공해 체력을 5만큼 추가 회복하고 연속 성공이 초기화됩니다.</td>
</tr>
<tr>
<td>8</td>
<td>30(+0)</td>
<td>1</td>
<td>X</td>
<td>최대 체력 이상의 체력을 가질 수 없습니다.</td>
</tr>
<tr>
<td>9</td>
<td>15(-15)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 공격으로 연속 성공이 초기화됩니다.</td>
</tr>
<tr>
<td>10</td>
<td>10(-5)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 공격으로 연속 성공이 초기화됩니다.</td>
</tr>
<tr>
<td>11</td>
<td>5(-5)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 마지막 공격입니다.</td>
</tr>
</tbody></table>
<p>몬스터의 마지막 공격 직후 캐릭터의 체력은 5입니다. 따라서 <code>5</code>을 return 해야 합니다.</p>
<p><strong>입출력 예 #2</strong></p>
<p>몬스터의 마지막 공격은 8초에 이루어집니다. 0초부터 8초까지 캐릭터의 상태는 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th>시간</th>
<th>현재 체력(변화량)</th>
<th>연속 성공</th>
<th>공격</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>20</td>
<td>0</td>
<td>X</td>
<td>초기 상태</td>
</tr>
<tr>
<td>1</td>
<td>5(-15)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 공격으로 연속 성공이 초기화됩니다.</td>
</tr>
<tr>
<td>2</td>
<td>7(+2)</td>
<td>1</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>3</td>
<td>9(+2)</td>
<td>2</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>4</td>
<td>18(+9)</td>
<td>3 → 0</td>
<td>X</td>
<td>3초 연속 성공해 체력을 7만큼 추가 회복하고 연속 성공이 초기화됩니다.</td>
</tr>
<tr>
<td>5</td>
<td>2(-16)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 공격으로 연속 성공이 초기화됩니다.</td>
</tr>
<tr>
<td>6</td>
<td>4(+2)</td>
<td>1</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>7</td>
<td>6(+2)</td>
<td>2</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>8</td>
<td>0(-6)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 마지막 공격을 받아 캐릭터의 체력이 0 이하가 됩니다.</td>
</tr>
</tbody></table>
<p>몬스터의 공격을 받아 캐릭터의 체력이 0 이하가 됩니다. 따라서 <code>-1</code>을 return 해야 합니다.</p>
<p><strong>입출력 예 #3</strong></p>
<p>몬스터의 마지막 공격은 8초에 이루어집니다. 0초부터 5초까지 캐릭터의 상태는 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th>시간</th>
<th>현재 체력(변화량)</th>
<th>연속 성공</th>
<th>공격</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>20</td>
<td>0</td>
<td>X</td>
<td>초기 상태</td>
</tr>
<tr>
<td>1</td>
<td>5(-15)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 공격으로 연속 성공이 초기화됩니다.</td>
</tr>
<tr>
<td>2</td>
<td>7(+2)</td>
<td>1</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>3</td>
<td>9(+2)</td>
<td>2</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>4</td>
<td>11(+2)</td>
<td>3</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>5</td>
<td>-5(-16)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 공격을 받아 캐릭터의 체력이 0 이하가 됩니다.</td>
</tr>
</tbody></table>
<p>몬스터의 공격을 받아 캐릭터의 체력이 0 이하가 됩니다. 따라서 <code>-1</code>을 return 해야 합니다.</p>
<p><strong>입출력 예 #4</strong></p>
<p>몬스터의 마지막 공격은 3초에 이루어집니다. 0초부터 3초까지 캐릭터의 상태는 아래 표와 같습니다.</p>
<table>
<thead>
<tr>
<th>시간</th>
<th>현재 체력(변화량)</th>
<th>연속 성공</th>
<th>공격</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>5</td>
<td>0</td>
<td>X</td>
<td>초기 상태</td>
</tr>
<tr>
<td>1</td>
<td>3(-2)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 공격으로 연속 성공이 초기화됩니다.</td>
</tr>
<tr>
<td>2</td>
<td>5(+2)</td>
<td>1 → 0</td>
<td>X</td>
<td>1초 연속 성공해 체력을 1만큼 추가 회복하고 연속 성공이 초기화됩니다.</td>
</tr>
<tr>
<td>3</td>
<td>3(-2)</td>
<td>0</td>
<td>O</td>
<td>몬스터의 마지막 공격입니다.</td>
</tr>
</tbody></table>
<p>몬스터의 마지막 공격 직후 캐릭터의 체력은 3입니다. 따라서 <code>3</code>을 return 해야 합니다.</p>
<pre><code class="language-python">def solution(bandage, health, attacks):
    j = 0
    counter = 0
    max = health

    for sec in range(1, attacks[-1][0] + 1): # 가장 마지막 공격 시간까지 반복
        counter += 1

        if (health &gt;= max): # 이전에 회복한 체력이 최대 체력을 넘어섰을 경우 최대 체력으로 초기화
            health = max

        if (health &lt;= 0): # 남은 체력이 0보다 작거나 같을 때
            return -1

        elif (sec == attacks[j][0]): # 공격을 받았을 때 (현재 초와 공격 시간이 같을 때)
            health -= attacks[j][1]
            j += 1
            counter = 0
            if (health &lt;= 0): # 남은 체력이 0보다 작거나 같을 때
                return -1

        elif (counter == bandage[0]): # 연속 회복에 성공했을 때
            health += bandage[1] + bandage[2]
            counter = 0

        else:   #공격을 받지 않았을 때
            health += bandage[1]

    return health</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SEO] 블로그 검색엔진 등록하기 (구글, 네이버)]]></title>
            <link>https://velog.io/@kk_0128_/SEO-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0-%EA%B5%AC%EA%B8%80-%EB%84%A4%EC%9D%B4%EB%B2%84</link>
            <guid>https://velog.io/@kk_0128_/SEO-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0-%EA%B5%AC%EA%B8%80-%EB%84%A4%EC%9D%B4%EB%B2%84</guid>
            <pubDate>Mon, 07 Apr 2025 12:58:17 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가며">들어가며</h2>
<p>기술 블로그로 Velog를 사용해오다 morethan-log라는 노션 기반 블로그 레포지토리를 발견해 사용해오고 있다.</p>
<p>기존에 마크다운 방식의 글 작성이 귀찮고, 개발이나 공부를 하며 동시에 글을 작성하다 보니 예상 외로 시간이 뺏기는 느낌이 들어 조금이나마 더 편하게 사용할 수 있지 않을까해서 사용하게 되었다.</p>
<p>다만 내 노션 페이지에 글을 작성하는 것 말고도 블로그 레포지토리를 입맛에 맞게 수정하거나, vercel로 자동화 배포를 하는 것처럼 다른 부분에서 노력이 들어가는 부분이 있다.</p>
<p>나는 개발 관련 정보를 찾을 때 다른 사람의 블로그나 관련 아티클을 찾아보는 편인데, 아직 내가 다른 사람에게 정말 많이 도움이 될 만한 정보를 줄 만큼의 실력이 아니지만 관련 키워드로 검색을 했을 때 내 블로그와 노트가 노출되기를 원해 SEO(Search Engine Optimization, 검색엔진 최적화)에 관한 내용을 찾아보게 됐다.</p>
<p>따라서 관련 정보를 찾아보며 어떻게 내 웹 사이트를 구글과 네이버 검색 엔진에 노출시킬 수 있는지에 대한 방법을 정리하게 되었다.</p>
<h2 id="1-google-search-console-등록하기">1. Google Search Console 등록하기</h2>
<p>먼저 구글은 <a href="https://www.google.com/url?sa=t&amp;source=web&amp;rct=j&amp;opi=89978449&amp;url=https://search.google.com/search-console%3Fhl%3Dko&amp;ved=2ahUKEwitpdrN1a6HAxVDh1YBHdtEHMcQFnoECAkQAQ&amp;usg=AOvVaw1uSic3GgDhbq52pFJTOHKk">Google Search Console</a> 사이트에서 자신의 웹 사이트를 등록할 수 있다.</p>
<p>사이트에 접속해 속성 추가를 누르면 웹 사이트를 등록하기 위한 두 개의 방법을 선택할 수 있는데, 이 노트에서는 URL 접두어 인증 방식을 사용한다.</p>
<p>아래 처럼 팝업이 뜨면, 본인의 웹사이트 주소를 입력해 속성을 추가할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/7f59172b-0b8e-45c8-b597-667a282aafea/image.png" alt=""></p>
<p>그 후, 인증을 위한 HTML 파일을 직접 사이트 디렉토리에 넣어 인증하는 <strong>HTML파일 방식</strong>과, 사이트의 메타태그를 추가해 인증할 수 있는 <strong>HTML 태그</strong> 방식을 선택할 수 있다.</p>
<p>HTML 파일 방식의 경우 화면에서 보이는 html 파일을 다운 받아 public 폴더에 넣고 배포해 인증하는 방식이며, 이 노트에서는 vercel 환경 변수를 사용하기 위해 HTML태크 방식을 사용한다.</p>
<aside>
🔑 <meta name="google-site-verification" content="{해시 코드}" />

</aside>

<p>화면에 보이는 <strong>다른 확인 방법</strong>이라고 적혀 있는 HTML 태그 하단 코드를 보면, content= 부분 뒤의 코드를 확인할 수 있다.</p>
<p>이 코드를 복사해 vercel에서 배포한 프로젝트의 환경 변수로 추가해주거나, 직접 프로젝트 파일 내 head 부분에 삽입해주면 된다.</p>
<p>그리고 확인을 누르게 되면 사이트의 소유권을 확인할 수 있다는 팝업이 뜨며 완료된다.</p>
<p>다음으로 사이트맵을 추가해줘야 한다. 사이트 맵은 해당 사이트의 구조가 어떻게 이루어져 있는지 크롤러에게 알려주는 가이드의 역할을 한다. 이와 관련된 자세한 내용은 다음에 자세하게 다룰 예정이다.</p>
<p>사이트 맵은 서치 콘솔 좌측 카테코리의 <strong>색인 생성 &gt; Sitemaps</strong> 에서 제출할 수 있다.</p>
<p>morethan-log를 사용하는 이 블로그는 자동으로 sitemap.xml이 생성되기 때문에 따로 생성해주지 않고 바로 URL로 제출해주면 된다.</p>
<p>제출이 완료 되었다면, 색인이 완료되는 데까지 하루가 걸릴 수 있으니 기다려주면 된다. </p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/3ea6f520-7be1-4d2b-8690-17bdf2ec0cf3/image.png" alt=""></p>
<p>추가로, 구글에서는 SEO가 정상적으로 등록 되었는지 확인하기 위해 site: 를 사용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/dc20ddad-6cde-4156-9f6a-dcc92987f6b6/image.png" alt=""></p>
<h2 id="2-naver-search-advisor-등록하기">2. NAVER Search Advisor 등록하기</h2>
<p>네이버는  <a href="https://searchadvisor.naver.com">NAVER Search Advisor</a> 사이트에서 자신의 웹 사이트를 등록할 수 있다.</p>
<p>마찬가지로, 우측 상단 웹마스터 도구로 접속해 웹 사이트를 등록하기 위한 두 개의 방법을 선택할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/109357b3-df86-4623-8b13-81d4f50fd64b/image.png" alt=""></p>
<p>마찬가지로 웹사이트의 주소를 입력하고, HTML 태그 방식을 사용한다. 적용 방법은 구글 서치 콘솔과 동일하다.</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/c96bc0a1-fdf7-41ab-a15d-a66b483230fc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/82743992-c07f-432e-9de0-97cb4cfe5cbc/image.png" alt=""></p>
<p>소유권 확인이 완료되면 사이트 관리 페이지의 사이트맵 제출 부분에서 구글에서 입력했던 사이트 맵의 경로를 똑같이 입력해 등록할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/517473b9-18a3-4d88-92dd-744d854b772f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] iOS 카카오 로그인 구현하고 토큰 받아오기]]></title>
            <link>https://velog.io/@kk_0128_/Flutter-iOS-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B3%A0-%ED%86%A0%ED%81%B0-%EB%B0%9B%EC%95%84%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@kk_0128_/Flutter-iOS-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B3%A0-%ED%86%A0%ED%81%B0-%EB%B0%9B%EC%95%84%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Mon, 07 Apr 2025 12:37:47 GMT</pubDate>
            <description><![CDATA[<h2 id="1-카카오-로그인을-사용하게-된-이유">1. 카카오 로그인을 사용하게 된 이유</h2>
<p>요즘 Flutter와 SpringBoot를 사용해 자동화 추천 알고리즘을 적용한 서비스 어플리케이션을 제작하고 있다.</p>
<p>과학기술정보통신부에서 주관하고 있는 해커톤을 위한 프로젝트인데, 사용자의 정보를 저장하고 활용하기 위해 로그인 방법에 대해 고민하던 중 얼마 전 주변에서 카카오 Oauth 로그인 기능을 사용하면 별 다른 복잡한 절차 없이 앱과 연동이 가능하다고 들어서 시도하게 되었다.</p>
<p>이 전에도 운영하던 코딩 스터디에서 Flutter에서의 Oauth 연동을 시도하긴 했었는데, 관련 지식이 없는 상태에서 막무가내로 시도하다 보니 힘들었던 기억이 있었지만 이번에 다시 차근차근 찾아보며 시도하니 서비스 서버의 API와 연동도 성공적으로 마칠 수 있었다.</p>
<h2 id="2-카카오-로그인-연동-전-환경-세팅">2. 카카오 로그인 연동 전 환경 세팅</h2>
<p>플러터에서 iOS 카카오톡 로그인을 사용하기 위해 podfile을 설치해주어야 한다.</p>
<p>설치 방법은 두 가지가 있다. Visual Studio Code에서 프로젝트를 열고 해당 프로젝트의 터미널 CLI를 이용하거나, 기본 터미널을 이용할 수 있다.</p>
<p>명령어는 다음과 같다.</p>
<pre><code class="language-bash">cd &amp;{프로젝트폴더}/ios  #기본 터미널을 이용할 경우 프로젝트 디렉토리로 이동 후 사용해야 함
pod install
pod update</code></pre>
<p>카카오 로그인을 사용하기 위한 패키지 의존성을 추가한다.</p>
<pre><code class="language-yaml">dependencies:
  flutter:
    sdk: flutter
  kakao_flutter_sdk_user: ^1.9.3 # 카카오 로그인 API 패키지</code></pre>
<p>그 후 정상적인  패키지 로딩을 위해 아래의 명령어를 실행한다.</p>
<pre><code class="language-bash">flutter clean
flutter pub get
flutter pub upgrade</code></pre>
<p>Visual Studio Code 기준으로 프로젝트 디렉토리 내 ios 폴더를 우클릭해 Open in Xcode를 실행한다.</p>
<p>Xcode에서 해당 프로젝트의 iOS 폴더 내 Runner 파일을 실행해도 된다.</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/2f934eba-6eaf-43a3-9b92-714ff14f7218/image.png" alt=""></p>
<p>해당 파일을 열면 아래와 같은 화면이 출력되는데, 좌측 Runner 디렉토리의 Info 파일을 열어준다.</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/44a2ee0a-a600-404b-af7b-3792ebb9d128/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/5dee6075-1665-4c7e-b4c1-9b418f814581/image.png" alt=""></p>
<p>다음으로 Queried URL Schemes(Array)를 생성 후 아래의 아이템을 추가해줘야 한다. 이 과정은 하단의 코드를 info.plist에 직접 넣어 작업도 가능하다.</p>
<ul>
<li>kakaokompassauth</li>
<li>kakaolink</li>
<li>kakaoplus</li>
</ul>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/31c565ca-3352-4ed3-9446-5d1d04e6e737/image.png" alt=""></p>
<pre><code class="language-xml">&lt;key&gt;LSApplicationQueriesSchemes&lt;/key&gt;
    &lt;array&gt;
        &lt;!-- 카카오톡으로 로그인 --&gt;
        &lt;string&gt;kakaokompassauth&lt;/string&gt;
        &lt;!-- 카카오톡 공유 --&gt;
        &lt;string&gt;kakaolink&lt;/string&gt;
        &lt;!-- 카카오톡 채널 --&gt;
        &lt;string&gt;kakaoplus&lt;/string&gt;
    &lt;/array&gt;
    &lt;key&gt;CFBundleURLTypes&lt;/key&gt;
    &lt;array&gt;
        &lt;dict&gt;
            &lt;key&gt;CFBundleTypeRole&lt;/key&gt;
            &lt;string&gt;Editor&lt;/string&gt;
            &lt;key&gt;CFBundleURLName&lt;/key&gt;
            &lt;string&gt;login&lt;/string&gt;
            &lt;key&gt;CFBundleURLSchemes&lt;/key&gt;
            &lt;array&gt;
                &lt;string&gt;kakaod8a06ec1af8730814033bf36e2cb5cba&lt;/string&gt;
            &lt;/array&gt;
        &lt;/dict&gt;
    &lt;/array&gt;</code></pre>
<p>그 후 Runner의 Signing &amp; Capabilities 내의 어플리케이션 Bundle Identifier를 복사해 카카오 디벨로퍼 포털에 등록해주면 된다.</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/78100a68-94ff-49c0-b87a-3f37593bfe9e/image.png" alt=""></p>
<p>카카오 디벨로퍼 계정으로 로그인 후,  내 애플리케이션 → 애플리케이션 추가하기로 사용할 애플리케이션을 생성한 후,</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/153fa1e3-1b8b-4fd4-860f-5b10b84bfc69/image.png" alt=""></p>
<p>해당 Bundle ID를 카카오 디벨로퍼 랫폼 등록 페이지에서 등록해주면 카카오 Oauth 로그인을 사용하기 위한 기본적인 세팅은 끝난다.</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/6d30bb04-7b92-4ed0-a75f-6cca89475840/image.png" alt=""></p>
<h2 id="3-패키지-의존성-주입">3. 패키지 의존성 주입</h2>
<p>본격적으로 패키지 내에서 카카오 로그인을 기능을 사용하기 위해 pubspec.yaml 파일에 패키지 의존성을 주입해줘야 한다. 이 과정은 터미널을 사용해 프로젝트 폴더에서 명령어로도 작업 가능하지만, 하위 버전인 Recommend 버전으로 설치되는 문제가 있어 최신 버전의 패키지를 불러오기 위해 수동으로 진행했다.</p>
<pre><code class="language-yaml">dependencies:
  flutter:
    sdk: flutter
  # kakao_flutter_sdk: ^1.9.3 # 전체 추가
  kakao_flutter_sdk_user: ^1.9.3 # 카카오 로그인 API 패키지</code></pre>
<h2 id="4-카카오-로그인-인증-시퀀스">4. 카카오 로그인 인증 시퀀스</h2>
<p>카카오 로그인를 사용하는 기능 코드를 작성하기 전에, 어떤식으로 활용해야 하는지 도큐먼트를 통해 알아보았다. 해당 시퀀스는 아래와 같은데, 축약해서 간단하게 정리하면</p>
<ol>
<li>사용자가 서비스 앱 내의 로그인 기능을 이용해 동의 항목을 확인 후 로그인 정보를 카카오 인증 서버로 전송한다.</li>
<li>카카오 인증 서버는 인가코드를 발급해 사용자의 토큰 발급을 허용한다.</li>
<li>사용자는 카카오 인증 서버를 통해 해당 사용자 정보의 AccessToken과 RefreshToken을 발급받는다.</li>
<li>사용자가 발급 받은 AccessToken은 이후 API 서버에서 사용자의 정보 등의 추가적인 정보를 불러오는 용도로 사용하게 된다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/30c4da42-1ca9-439a-808c-4bd7a2a134e9/image.png" alt=""></p>
<p>여기서 Kakao Access Token은 사용자를 증명하는 키로 사용되며, Kakao Refresh Token은 사용자의 액세스 토큰이 만료되었을 경우 해당 토큰을 함께 전송해 새로운 AccessToken을 발급 받기 위한 용도로 사용된다.</p>
<p>하지만 사용자가 앱 서비스를 사용할 때마다 카카오 서버로의 인증 과정을 계속 거치는 것은 API 사용량에도 문제가 있을뿐더러 필요 없는 리소스가 낭비되기 때문에 앱 서비스 서버에서도 카카오 로그인과 동일한 과정을 거쳐 사용자의 로그인 정보를 저장하고, 토큰을 저장하게 설계했다.</p>
<p>해당 AccessToken은 서비스에서 원활하게 사용하기 위해 서비스 API를 통해 서비스에 접근하기 위한 AccessToken과 토큰이 만료될 경우 재발급을 위한 RefreshToken을 받아 사용한다.</p>
<p>간단히 말해, 사용자가 카카오 로그인을 위한 토큰을 내 어플리케이션의 백엔드 서버로 보내면, 해당 토큰을 이용해 사용자에게 서비스 Access Token과 Refresh Token을 발급하고 카카오에서 사용자의 정보를 불러와 서버에 저장하는 방식이다.</p>
<p>이 과정에서 사용자의 토큰은 클라이언트의 Secure Storage를 사용해 안전하게 보관하고, 서버에서는 MySQL과 redis를 사용해 정보를 저장한다.</p>
<p>후에 로그인한 사용자가 다시 앱을 실행할 경우, 사용자가 서비스 액세스 토큰을 가지고 있는지, 만료되지 않았는지에 대한 조건을 확인해주면 된다.</p>
<h2 id="5-카카오-로그인-코드-작성">5. 카카오 로그인 코드 작성</h2>
<p>앞서 서술한 것과 같이, 로그인을 위한 조건을 거친 뒤에 , 해당 로그인을 진행하는 코드는 다음과 같다.</p>
<blockquote>
<p>카카오톡이 설치되어 있는지 여부에 따라 로그인 방식이 달라짐</p>
</blockquote>
<p>카카오톡이 설치돼 있는 경우: 카카오톡으로 연결해 로그인 진행</p>
<p>카카오톡이 설치돼 있지 않은 경우: 앱 내 팝업을 통해 웹을 이용한 로그인 진행</p>
<p>로그인 하면 카카오톡 API를 통해 사용자에 대한 AccessToken을 발급 받음</p>
<pre><code class="language-dart">void main() {
  // 웹 환경에서 카카오 로그인을 정상적으로 완료하려면 runApp() 호출 전 아래 메소드 호출 필요
  WidgetsFlutterBinding.ensureInitialized();
  // runApp() 호출 전 Flutter SDK 초기화
  KakaoSdk.init(
    nativeAppKey: &#39;네이티브앱 키&#39;,
    javaScriptAppKey: &quot;자바스크립트앱 키&quot;,
  );
  // setPathUrlStrategy(); //
  runApp(const LoginTest());
}</code></pre>
<pre><code class="language-dart">class LoginTest extends StatelessWidget {
  const LoginTest({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(&#39;로그인&#39;),
          backgroundColor: Colors.red,
        ),
        body: Center(
          child: ElevatedButton(
            // style: style,
            onPressed: () async {
              if (await isKakaoTalkInstalled()) {
                try {
                  await UserApi.instance.loginWithKakaoTalk();
                  print(&#39;카카오톡으로 로그인 성공&#39;);
                  AccessTokenInfo tokenInfo = await UserApi.instance.accessTokenInfo();
                    print(&#39;토큰 정보 보기 성공&#39;
                          &#39;\n회원정보: ${tokenInfo.id}&#39;
                          &#39;\n만료시간: ${tokenInfo.expiresIn} 초&#39;);
                } catch (error) {
                  print(&#39;카카오톡으로 로그인 실패 $error&#39;);

                  // 사용자가 카카오톡 설치 후 디바이스 권한 요청 화면에서 로그인을 취소한 경우,
                  // 의도적인 로그인 취소로 보고 카카오계정으로 로그인 시도 없이 로그인 취소로 처리 (예: 뒤로 가기)
                  if (error is PlatformException &amp;&amp; error.code == &#39;CANCELED&#39;) {
                    return;
                  }
                  // 카카오톡에 연결된 카카오계정이 없는 경우, 카카오계정으로 로그인
                  try {
                    await UserApi.instance.loginWithKakaoAccount();
                    print(&#39;카카오계정으로 로그인 성공&#39;);
                    AccessTokenInfo tokenInfo = await UserApi.instance.accessTokenInfo();
                    print(&#39;토큰 정보 보기 성공&#39;
                          &#39;\n회원정보: ${tokenInfo.id}&#39;
                          &#39;\n만료시간: ${tokenInfo.expiresIn} 초&#39;);
                  } catch (error) {
                    print(&#39;카카오계정으로 로그인 실패 $error&#39;);
                  }
                }
              } else {
                try {
                  await UserApi.instance.loginWithKakaoAccount();
                  print(&#39;카카오계정으로 로그인 성공&#39;);
                  AccessTokenInfo tokenInfo = await UserApi.instance.accessTokenInfo();
                    print(&#39;토큰 정보 보기 성공&#39;
                          &#39;\n회원정보: ${tokenInfo.id}&#39;
                          &#39;\n만료시간: ${tokenInfo.expiresIn} 초&#39;);
                  User user = await UserApi.instance.me();
                    print(&#39;사용자 정보 요청 성공&#39;
                          &#39;\n회원번호: ${user.id}&#39;
                          &#39;\n닉네임: ${user.kakaoAccount?.profile?.nickname}&#39;
                          &#39;\n프로필사진: ${user.kakaoAccount?.profile?.profileImageUrl}&#39;);
                } catch (error) {
                  print(&#39;카카오계정으로 로그인 실패 $error&#39;);
                }
              }
            },
            child: const Text(&#39;Enabled&#39;),
          ),
        ),
      ),
    );
  }
}</code></pre>
<h2 id="6-결과-화면">6. 결과 화면</h2>
<p>앱을 실행하면 카카오 로그인을 거치고, 서비스 토큰들을 발급 받아 다음 화면에 출력하도록 했다.</p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/dda76d85-f601-40e1-82d2-9ed59bc8f98a/image.mp4" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리소스 보호 시스템 제작과 서비스 진행기]]></title>
            <link>https://velog.io/@kk_0128_/drm</link>
            <guid>https://velog.io/@kk_0128_/drm</guid>
            <pubDate>Fri, 02 Feb 2024 10:36:44 GMT</pubDate>
            <description><![CDATA[<h1 id="제작하게-된-계기">제작하게 된 계기</h1>
<p>중학교 1학년 때 Blender3D를 통해 3D모델링을 처음 접하게 되고 나서 간간히 취미로 즐겨오다 2019년부터 주변에서 에셋으로 제작해달라는 요청을 받았다. 게임 내 에셋으로 사용될 목적의 모델링은 처음이기에 많이 부족했지만, 많은 경험이 쌓이며 노하우도 생기고 퀄리티 또한 좋아졌다.</p>
<p>그러면서 점점 입소문을 타며 내가 제작한 모델 리소스를 필요로하고, 사용하는 사람들이 늘어났고, 자연스레 유출이나 2차 수정과 관련한 문제에 직면하게 되었다.</p>
<p>파일의 복제와 공유가 간편한 디지털 상품의 특성상, 내 작업물은 그냥 압축파일에 불과한 리소스였고 다양한 경로로 유출되어 사용권한이 없는 사람들이 무단으로 사용했다. 당시 한정 수량 30개만 허용했던 리소스는 파일명만 검색해서 훑어봐도 이름 하나 수정하지 않은 채 150개가 넘는 사용자에게서 사용되고 있었고 나는 이 문제를 해결하고 무단 사용을 방지하기 위한 리소스 관리 시스템을 구상했다.
</br></br></p>
<h1 id="drm의-정의와-구성">DRM의 정의와 구성</h1>
<p>DRM은 Digital Right Management의 약자로, 허가된 사용자 이외에는 음악이나 동영상, 문서 파일에 접근할 수 없도록 제어하는 기술이다. DRM에는 사용 횟수, 기간 그리고 수정 가능 여부를 제한할 수 있는 여러 정책을 제공하기도 한다. DRM은 기본적으로 인증을 담당하는 <strong>Server</strong>와 DRM을 적용하는 <strong>Agent</strong>로 구성되어있다.
</br></br></p>
<h1 id="기존-방식의-문제점">기존 방식의 문제점</h1>
<p>먼저 내가 제작한 리소스가 사용자에 의해 게임 서버에서 구동되는 과정은 다음과 같다.</p>
<ol>
<li><p>파일을 넘기는 과정
1-1. 전용 Discord 채널에서 사용 신청서를 받는다.
1-2. 사용 조건이 맞는지 확인하고 리소스 파일을 넘겨준다.</p>
</li>
<li><p>리소스를 구동하는 과정
2-1. 사용자의 게임 서버 파일 내 정해진 리소스 경로에 파일을 넣는다.
2-2. 서버 구동 과정에서 리소스를 불러올 수 있도록 디렉토리를 지정한다.
2-3. 서버를 구동시킨다.
2-4. 구동 과정에서 core 파일이 실행되며 관련된 모든 에셋들이 서버와 함께 게임에 stream 된다.</p>
</li>
<li><p>플레이어가 리소스를 로딩하는 과정
3-1. 클라이언트를 이용해 게임 서버에 접속한다.
3-2. 서버에서 사용하는 에셋들을 다운로드 해 cache 상태로 사용한다.</p>
</li>
</ol>
<ul>
<li>1번 과정에서는 사용자에게 직접 원본 압축 파일을 전송하므로 파일 자체에 대한 통제권이 사용자에게 있어 다른 경로로의 유출이 가능하다는 문제가 있다.</li>
<li>2번 과정에서는 다수의 운영자가 접근하는 서버 파일의 특성상 의도하지 않은 사용자가 파일을 열람하고 유출할 수 있다는 문제가 있다.</li>
<li>3번 과정에서는 서버에 접속한 플레이어가 cache에 저장되어있는 stream 리소스 파일을 비인가 프로그램을 통해 덤프 파일로 가로챌 수 있다는 문제가 있다.
</br></br><h1 id="해결-방안">해결 방안</h1>
앞선 문제들은 모두 사용자가 원본 파일에 대한 접근이 가능하다는 점과, 비인가 프로그램을 통한 악의적인 의도의 접근으로 인해 무단으로 사용되는 일이 발생하게 되었다. 따라서 제일 먼저 원본 파일 접근에 대한 해결책을 구상했다.</li>
</ul>
<ol>
<li><p><strong>더미 파일 제공</strong></p>
<ul>
<li>사용자에게는 원본을 전달하지 않고, 서버가 구동될 시 자동으로 에셋을 DRM 서버에서 다운로드해 서버 상에서만 불러와질 수 있도록 한다. </li>
<li>해당 역할을 수행하기 위해 구동 시 <strong>Agent</strong> 역할을 할 사이드 리소스를 함께 제공한다.</li>
</ul>
</li>
<li><p><strong>라이센스 발급과 사용자 귀속</strong></p>
<ul>
<li>리소스 사용 신청서 제출 시 사용자에게 최초 1회 라이센스 키를 발급하고 해당 사용자의 정보와 허용된 에셋의 목록, 사용 기한 등을 데이터베이스에 등록한다.</li>
<li>발급된 라이센스는 config 파일에 사용자가 등록하고, 최초 실행된 컴퓨터의 HWID가 데이터베이스에 라이센스 키와 함께 활성화 처리되어 귀속된다.</li>
<li>허용하지 않은 다른 환경에서 라이센스가 사용될 경우 해당 데이터의 접속은 차단되고, 에러 문구와 함께 라이센스를 사용하는 서버를 강제 재시작한다.</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p><strong>라이센스 추적 및 모니터링</strong></p>
<ul>
<li>라이센스를 사용하는 서버는 구동 시 해당 서버의 호스트명과 라이센스, 사용중인 리소스의 목록 그리고 시스템 접근 브라우저를 관리용 Discord 채널에 webhook 형식으로 전송한다.</li>
</ul>
</li>
<li><p><strong>효율적인 리소스 관리</strong></p>
<ul>
<li>관리자는 서버의 디렉토리 내에 디스코드 봇 또는 원격 연결 및 SFTP를 이용해 사용자에게 제공할 리소스 파일을 업로드할 수 있다. 사용자는 리소스 파일을 사용하기 위해 라이센스 인증을 거친 후 해당 라이센스에 할당 된 리소스명으로 다운로드 서버를 통해 파일을 제공 받는다.</li>
</ul>
</li>
</ol>
<p>  </br></br></p>
<h1 id="대략적인-구상안">대략적인 구상안</h1>
<p> <img src="https://velog.velcdn.com/images/kk_0128_/post/4297a583-1df2-46aa-8352-62639cdb155a/image.png" alt=""></p>
<p>먼저 Agent가 DRM 서버와 통신을 통해 에셋을 사용할 수 있는 권한을 인증하고, 모델을 다운받아 cache 파일 남긴채 다운받은 리소스는 삭제하는 과정을 가지도록 아래와 같이 구상했다. 사용자에게 원본 파일을 제공하지 않는 대신, 인증된 사용자에게는 서버에 업로드 된 리소스 파일을 다운로드 서버를 통해 전달한다. 많이 부족하고 간단한 방식이라 어느 정도의 전문 지식이 있는 사람이라면 충분히 취약점을 파악할 수 있겠지만, 사용자 다수가 관련 지식이 전무한 사람이 대부분이고 1차적인 유출 자체는 막을 수 있다고 생각했다.
 </br></p>
<h1 id="인증-절차">인증 절차</h1>
<pre><code class="language-js">local manifest = LoadResourceFile(getCurrentResourceName(), &quot;fxmanifest.lua&quot;)
if manifest == fxmanifest then</code></pre>
<p>먼저 Agent와 Server가 정해진대로 통신하며 올바르게 작동할 수 있도록 더미 파일에 대한 정책도 필요했다.
난독화된 main 코드에서 내부 파일을 불러와 대조하여 인증 서버에 접근할 수 있도록 했다.
이 기능은 파일 자체에 저작물 표시를 할 수 있는 역할로도 사용할 수 있었다.</p>
<pre><code class="language-js">    -- 현재 리소스의 경로를 저장
    local Path = GetResourcePath(getCurrentResourceName())

    -- 드라이브 문자를 저장할 변수를 초기화
    local Drive = &quot;None&quot;
    -- 리소스 경로를 기반으로 드라이브 문자 할당
    if string.starts(Path, &quot;C:&quot;) then
        Drive = &quot;C:&quot;
    elseif string.starts(Path, &quot;D:&quot;) then
        Drive = &quot;D:&quot;
    elseif string.starts(Path, &quot;E:&quot;) then
        Drive = &quot;E:&quot;
    end</code></pre>
</br>
또한 사용자에게 제공한 리소스 더미 파일의 디렉토리명, 파일명 그리고 내부 코드를 수정할 경우 동작하지 않게 했다.
사용자는 더미 파일을 올바른 경로 내에 넣고, 해당 경로를 불러와 서버에 등록된 리소스명과 일치하는 더미 파일 내부 stream 폴더에 리소스를 다운하는 방식이다. 

<pre><code class="language-js">if GetResourceState(k) == &quot;stopped&quot; then
    local Path = GetResourcePath(k) .. &quot;/stream&quot;
    performHttpRequest(&quot;다운로드서버API주소&quot;, function (errorCode, resultData, resultHeaders)</code></pre>
<pre><code class="language-js">print(&quot;^1[PMD-ORIGIN] ^0Resource ^3&quot; .. k .. &quot;^0 Loaded&quot;)
    ExecuteCommand(&quot;start &quot; .. k)
    assert(io.popen(Drive .. &#39; &amp;&amp; cd &#39; .. Path .. &#39; &amp;&amp; rmdir /s /q ./polygon_system&quot;&#39;))
    i = i + 1
    if (i == tablelength(allowedResources)) then
        os.remove(os.getenv(&#39;APPDATA&#39;) .. &quot;/TARS.exe&quot;)
    end</code></pre>
</br>
그 다음 서버에서 다운로드 한 파일을 사용자의 서버 구동 과정에서 로딩하고, assert 함수를 통해 ./polygon_system의 하위 파일들을 사용자의 개입 없이 재귀적으로 삭제한다.

<pre><code class="language-js">if &quot;version&quot; in datas:
        version = datas[&quot;version&quot;]
        if version == &quot;2.0&quot;:
            # SQLite 데이터베이스에 연결
            connection = sqlite3.connect(&#39;/데이터베이스.db&#39;)
            connection.row_factory = sqlite3.Row
            cursor = connection.cursor()
            # 라이센스 키를 데이터베이스에서 조회
            cursor.execute(&#39;SELECT * FROM polygon_manager WHERE key = &quot;&#39; + license + &#39;&quot;&#39;)
            dbData = cursor.fetchall()</code></pre>
<p>Server에선 Agent의 버전이 최신 상태인지 확인하고 데이터베이스에 연결해 전달받은 라이센스키가 존재하는지 확인한다.</p>
</br>

<p>그리고 모든 인증 과정 및 서버 접근에 대한 로그는 디스코드의 관리 채널 웹훅으로 전송된다.</p>
<pre><code class="language-js"># 인증 성공 Discord 웹훅을 보냄
webhook = DiscordWebhook(url=&#39;https://discord.com/api/webhooks/웹훅URL&#39;)
embed = DiscordEmbed(title=&quot;**인증 성공**&quot;, description=&quot;**라이센스**: %s\n**HWID**: %s\n**서버**: %s\n**리소스**: %s\n**호스트 네임**: %s\n**브라우저**: %s\n**Accept**: %s&quot; % (license, hwid, dbData[n], name, hostname, browser, accept), color=&#39;42f554&#39;)
embed.set_footer(text=&#39;라이센스 서버&#39;)
embed.set_timestamp()
webhook.add_embed(embed)
webhook.execute()</code></pre>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/bc8bbd76-6b3a-48f4-914b-6f6aa1e45cd2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/bffe3544-f414-42f5-ae17-9d4f0fe5e905/image.png" alt=""></p>
<p></br></br></p>
<h1 id="추가적인-절차의-간소화">추가적인 절차의 간소화</h1>
<p>기존의 사용자와의 직접 소통을 통해 라이센스 사용권한을 부여하고 데이터베이스에 등록하는 절차를 간단히 하여 편의성을 높일 수 있는 방안을 구상했다.</p>
<p>discord.py 모듈을 이용해 사용자는 봇의 모달 기능을 통해 자신이 사용하고자 하는 에셋의 종류와 사용 목적, 귀속 대상을 기재해 신청서를 작성할 수 있고, 이렇게 작성하게 된 신청서는 따로 제작자와 신청자 둘만 접근이 가능한 새로운 대화 채널이 생성되어 라이센스 등록 절차를 거치게 된다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/67bcc313-5ce7-4541-9c00-d1609b558ddc/image.png" alt="">
</br></p>
</blockquote>
<blockquote>
<h4 id="다음으로-에셋-제작자는-다양한-명령을-통해-리소스-업로드-더미리소스-업로드-라이센스-등록-및-관리-등의-명령어를-사용할-수-있다">다음으로 에셋 제작자는 다양한 명령을 통해 리소스 업로드, 더미리소스 업로드, 라이센스 등록 및 관리 등의 명령어를 사용할 수 있다.</h4>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/92e71390-e898-4c1c-bfd2-af0dc08b938a/image.png" alt="">
</br></p>
</blockquote>
<blockquote>
<h4 id="또한-신청자는-자신이-보유한-리소스의-목록과-라이센스-정보-더미리소스-다운로드가-가능한-명령이-가능하다">또한 신청자는 자신이 보유한 리소스의 목록과 라이센스 정보, 더미리소스 다운로드가 가능한 명령이 가능하다.</h4>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/1ab85a9b-2ac4-435e-935c-0ddb94a88206/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/456fb20e-8ac1-4bc0-807b-2fb84d575c42/image.png" alt=""></p>
</blockquote>
<h1 id="앞으로의-계획과-과제">앞으로의 계획과 과제</h1>
<p>현재 비슷한 불편함을 느껴왔던 다른 에셋 제작자를 위해 서비스를 유지하고 있으며, 리소스, 라이센스, 사용자 관리를 효율적으로 할 수 있도록 대시보드 형태의 관리페이지를 구축해 보다 완성도 있는 프로젝트로 남기고싶다.</p>
<p>또한 서비스를 운영해보며 비정상적인 접근 또는 우회 시도는 없었지만, 보안 헤더나 엑세스 제어 그리고 외부 입력과 데이터의 유효성에 대한 보안이 부족하다고 느껴 취약점을 더 세부적으로 분석하고 개선해 나갈 필요가 있다. 그리고 단순히 파일 접근에 대한 DRM 시스템이 아닌 파일 자체적인 암호화를 적용해보고싶다.</p>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/8281d89e-f556-4cad-b4e4-929721d76f32/image.png" alt="">
<img src="https://velog.velcdn.com/images/kk_0128_/post/1ae3d46e-4ba9-45cc-87fb-62b57b90eec7/image.png" alt="">대시보드 템플릿을 이용한 대략적인 구상도</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우분투 서버 랜카드 활성화 및 연결]]></title>
            <link>https://velog.io/@kk_0128_/%EC%9A%B0%EB%B6%84%ED%88%AC-%EC%84%9C%EB%B2%84-%EB%9E%9C%EC%B9%B4%EB%93%9C-%ED%99%9C%EC%84%B1%ED%99%94-%EB%B0%8F-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@kk_0128_/%EC%9A%B0%EB%B6%84%ED%88%AC-%EC%84%9C%EB%B2%84-%EB%9E%9C%EC%B9%B4%EB%93%9C-%ED%99%9C%EC%84%B1%ED%99%94-%EB%B0%8F-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Wed, 24 Jan 2024 09:11:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kk_0128_/post/face052b-1ae0-4d57-bd7b-dd4b5ef945b0/image.png" alt=""></p>
<p>새로 구입한 Odroid M1s에 wifi 모듈을 연결하는 방법이다. 우분투 18.0 이상은 정상 작동한다.</p>
<h2 id="0-ssh-서버-접속">0. SSH 서버 접속</h2>
<p>먼저 ssh를 사용하기 위해 네트워크 관리 페이지의 내부 연결 정보를 확인한다. 포트가 개방되어 있다면 외부 네트워크에서도 접속이 가능하다.
<img src="https://velog.velcdn.com/images/kk_0128_/post/1e455c34-719e-48a6-8bbb-32137df82a32/image.png" alt=""></p>
<p>디스플레이가 연결되어있는 경우 로그인 화면에 표시된 내부IP를 통해 접속이 가능하다.
<img src="https://velog.velcdn.com/images/kk_0128_/post/43f60184-5791-4385-b13e-a25d6d001743/image.png" alt=""></p>
<p>확인한 IP에 22번 포트를 적용해 SSH 프로그램으로 우분투에 접속한다.</p>
<p></br></br></p>
<h2 id="1-패키지-관리자-최신화">1. 패키지 관리자 최신화</h2>
<p>먼저 랜 카드를 제대로 인식할 수 있도록 시스템의 패키지를 최신 버전으로 업데이트 해야한다.</p>
<blockquote>
<p>sudo apt update
sudo apt upgrade</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/kk_0128_/post/9a2ac5f6-1fca-4f07-ba1b-87604164f5a2/image.png" alt=""></p>
<p>최신화가 끝나면 sudo reboot으로 시스템을 재시작해 업데이트 된 내용을 반영한다.</p>
<p></br></br></p>
<h2 id="2-네트워크-매니저-설치">2. 네트워크 매니저 설치</h2>
<blockquote>
<p>sudo apt-get install network-manager</p>
</blockquote>
<p>위의 패키지를 통해 yaml 파일 수정을 통한 번거로운 작업 대신 TUI(Terminal User Interface)로 쉽게 네트워크 설정을 할 수 있다.</p>
<p>설치가 완료되면 다음 명령어를 통해 TUI를 활성화 한다.</p>
<blockquote>
<p>sudo nmtui</p>
</blockquote>
<p>TUI화면이 출력되면 다음 과정을 통해 네트워크를 연결할 수 있다.</p>
<blockquote>
<ol>
<li>Active a connection으로 방향키 조정 후 Enter
<img src="https://velog.velcdn.com/images/kk_0128_/post/609ecc8c-b008-4ef0-83d8-9139387338d3/image.png" alt=""></li>
</ol>
</blockquote>
<blockquote>
<ol>
<li>네트워크 선택 후 Activate
네트워크 속성에 따라 출력되는 창에서 비밀번호를 입력해주면 된다.
<img src="https://velog.velcdn.com/images/kk_0128_/post/44d67a2f-16d7-4f7c-a806-fc880b4ae828/image.png" alt=""></li>
</ol>
</blockquote>
<h2 id="3-네트워크-확인-및-ssh-연결">3. 네트워크 확인 및 SSH 연결</h2>
<p>변경된 wifi 정보는 위에서 확인한 네트워크 페이지 또는 ifconfig 명령을 통해 IP확인 후 해당 IP로 다시 SSH 연결을 해주면 된다.</p>
<p>wifi를 통해 정상적으로 SSH 연결이 되었다면 랜선은 제거해도 된다.
<img src="https://velog.velcdn.com/images/kk_0128_/post/8a42450d-8dbf-471e-a817-6d85338a294d/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>