<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Just do IT.log</title>
        <link>https://velog.io/</link>
        <description>일단 하자.</description>
        <lastBuildDate>Fri, 17 Apr 2026 12:16:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Just do IT.log</title>
            <url>https://velog.velcdn.com/images/from-minju/profile/a3dc365f-8879-4ebb-b402-0dca24008a2e/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Just do IT.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/from-minju" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[프로그래머스/Python] 숫자의 표현 (완전탐색/투포인터) Lv2]]></title>
            <link>https://velog.io/@from-minju/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%EC%88%AB%EC%9E%90%EC%9D%98-%ED%91%9C%ED%98%84-%EC%99%84%EC%A0%84%ED%83%90%EC%83%89%ED%88%AC%ED%8F%AC%EC%9D%B8%ED%84%B0-Lv2</link>
            <guid>https://velog.io/@from-minju/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%EC%88%AB%EC%9E%90%EC%9D%98-%ED%91%9C%ED%98%84-%EC%99%84%EC%A0%84%ED%83%90%EC%83%89%ED%88%AC%ED%8F%AC%EC%9D%B8%ED%84%B0-Lv2</guid>
            <pubDate>Fri, 17 Apr 2026 12:16:39 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p>문제: 자연수 $n$이 주어질 때, 이를 연속된 자연수들의 합으로 표현하는 방법의 수를 구하라.
예시: $n = 15$일 때,
$1 + 2 + 3 + 4 + 5 = 15$
$4 + 5 + 6 = 15$
$7 + 8 = 15$
$15 = 15$
총 4가지 방법 존재.</p>
<h1 id="풀이">풀이</h1>
<p>완전탐색을 이용한 풀이, 투포인터를 사용한 풀이가 가능하다. </p>
<h1 id="1️⃣--span-stylecolor12b886완전-탐색-brute-forcespan">1️⃣  <span style="color:#12B886">완전 탐색 (Brute Force)</span></h1>
<p>가장 직관적인 방법은 $1$부터 시작해서 $n$까지 각 숫자를 &#39;시작점&#39;으로 잡고, 숫자를 하나씩 더해가며 $n$이 되는지 확인하는 것이다.</p>
<ul>
<li>1을 시작으로 하는 연속된 자연수들</li>
<li>2를 시작으로 하는 연속된 자연수들</li>
<li>...</li>
<li>$n$을 시작으로 하는 연속된 자연수들</li>
</ul>
<h3 id="풀이-로직">풀이 로직</h3>
<ol>
<li>$i$를 $1$부터 $n$까지 반복합니다 (연속된 수의 시작점).</li>
<li>$i$부터 시작해서 $i + 1, i + 2, \dots$를 차례대로 더합니다.</li>
<li>합이 $n$과 같아지면 카운트를 1 증가시키고 다음 시작점($i+1$)으로 넘어갑니다.</li>
<li>합이 $n$을 초과하면 더 이상 더할 필요가 없으므로 중단하고 다음 시작점으로 넘어갑니다.</li>
</ol>
<h2 id="💻-풀이-코드-완전-탐색">💻 풀이 코드 (완전 탐색)</h2>
<pre><code class="language-python">def solution(n):
    answer = 0
    for i in range(1, n + 1):
        total = 0
        for j in range(i, n + 1):
            total += j
            if total == n:
                answer += 1
                break
            if total &gt; n:
                break
    return answer</code></pre>
<hr>
<h1 id="2️⃣-span-stylecolor12b886투-포인터two-pointersspan">2️⃣ <span style="color:#12B886">투 포인터(Two Pointers)</span></h1>
<p>투 포인터는 주로 <strong>정렬된 배열</strong>에서 <strong>연속된 구간의 합</strong>을 구할 때 사용하는 알고리즘이다. 이 문제에서도 $1$부터 $n$까지의 자연수가 순서대로 나열되어 있다고 가정하고 적용할 수 있다.</p>
<h2 id="투-포인터-접근법">투 포인터 접근법</h2>
<p><code>left</code>, <code>right</code> 두 포인터로 구간 <code>[left, right]</code>의 합을 관리한다.</p>
<ul>
<li><code>left</code>, <code>right</code>를 모두 $1$로 설정하고 시작한다.</li>
<li><strong>종료 조건</strong> : <code>left</code>가 <code>n</code>을 넘어가면 종료한다.</li>
</ul>
<table>
<thead>
<tr>
<th>조건</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td><code>total == n</code></td>
<td>경우의 수 +1, <code>right</code> 이동</td>
</tr>
<tr>
<td><code>total &lt; n</code></td>
<td><code>right</code> 이동 (합을 늘림)</td>
</tr>
<tr>
<td><code>total &gt; n</code></td>
<td><code>left</code> 이동 (합을 줄임)</td>
</tr>
</tbody></table>
<h2 id="💻-풀이-코드-투포인터">💻 풀이 코드 (투포인터)</h2>
<pre><code class="language-python">def solution(n):
    count = 0
    left, right = 1, 1
    total = 1  # 초기 구간 [1, 1]의 합

    while left &lt;= n:
        if total == n:
            count += 1
            right += 1
            total += right   # right 확장
        elif total &lt; n:
            right += 1
            total += right   # right 확장
        else:  # total &gt; n
            total -= left    # left 제거
            left += 1

    return count</code></pre>
<h3 id="동작-흐름-n--15">동작 흐름 (n = 15)</h3>
<pre><code>[1,1]  total=1  → right 이동
[1,2]  total=3  → right 이동
[1,3]  total=6  → right 이동
[1,4]  total=10 → right 이동
[1,5]  total=15 → ✅ count=1, right 이동
[1,6]  total=21 → left 이동
[2,6]  total=20 → left 이동
[3,6]  total=18 → left 이동
[4,6]  total=15 → ✅ count=2, right 이동
[4,7]  total=22 → left 이동
[5,7]  total=19 → left 이동
[6,7]  total=13 → right 이동
[6,8]  total=21 → left 이동
[7,8]  total=15 → ✅ count=3, right 이동
[7,9]  total=24 → left 이동
...
[15,15] total=15 → ✅ count=4</code></pre><h3 id="핵심-포인트">핵심 포인트</h3>
<ul>
<li><strong>시간복잡도</strong>: <code>O(n)</code> — left, right 각각 최대 n번 이동</li>
<li><strong>종료 조건</strong>: <code>left &gt; n</code> 이 되면 더 이상 유효한 구간 없음</li>
<li><code>right += 1</code> 후 <code>total += right</code> 순서에 주의 (먼저 포인터를 옮긴 뒤 합산)<br>

</li>
</ul>
<h2 id="🤔-total--n-일때-왜-right만-이동하지">🤔 <code>total == n</code> 일때, 왜 right만 이동하지?</h2>
<p>right, left둘다 이동시키거나 left만 이동시키는 방식은 안되는걸까?
결론부터 말하면 셋 다 가능하다. 다만 각각 놓치는 케이스가 없도록 처리를 다르게 해야 한다.</p>
<h3 id="right만-이동하면"><strong>right만 이동하면?</strong></h3>
<p><code>total == n</code>을 찾은 순간, <code>left</code>를 고정한 채 <code>right</code>를 늘리면 다음 상태는 <code>total &gt; n</code>이 되어 자연스럽게 <code>left</code>가 이동한다.</p>
<pre><code>[4,5,6] = 15 ✅  → right 이동
[4,5,6,7] = 22  → left 이동
[5,6,7] = 18    → left 이동
[6,7] = 13      → right 이동
[6,7,8] = 21    → left 이동
[7,8] = 15 ✅</code></pre><p>흐름이 자연스럽게 이어져서 코드가 단순해지는 장점이 있다.</p>
<h3 id="left만-이동하면"><strong>left만 이동하면?</strong></h3>
<pre><code class="language-python">if total == n:
    count += 1
    total -= left  # left 제거
    left += 1</code></pre>
<p>이것도 동작한다. left를 줄이면 total &lt; n이 되어 right가 자연스럽게 이동한다.</p>
<pre><code>[4,5,6] = 15 ✅  → left 이동
[5,6] = 11       → right 이동
[5,6,7] = 18     → left 이동
[6,7] = 13       → right 이동
[6,7,8] = 21     → left 이동
[7,8] = 15 ✅</code></pre><h3 id="left-right-둘-다-이동하면"><strong>left, right 둘 다 이동하면?</strong></h3>
<pre><code class="language-python">if total == n:
    count += 1
    total -= left  # left 제거 후
    left += 1
    right += 1     # right도 추가
    total += right</code></pre>
<p>이것도 가능하다. 단, 반드시 두 줄을 같이 써야 하고, 순서도 지켜야 해서 실수하기 쉽다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준/Python] 3273. 두 수의 합 (투포인터 설명) 🥈3️⃣]]></title>
            <link>https://velog.io/@from-minju/%EB%B0%B1%EC%A4%80Python-3273.-%EB%91%90-%EC%88%98%EC%9D%98-%ED%95%A9-%ED%88%AC%ED%8F%AC%EC%9D%B8%ED%84%B0-3</link>
            <guid>https://velog.io/@from-minju/%EB%B0%B1%EC%A4%80Python-3273.-%EB%91%90-%EC%88%98%EC%9D%98-%ED%95%A9-%ED%88%AC%ED%8F%AC%EC%9D%B8%ED%84%B0-3</guid>
            <pubDate>Wed, 15 Apr 2026 17:33:32 GMT</pubDate>
            <description><![CDATA[<h1 id="백준python-3273-두-수의-합-투포인터-🥈3️⃣">[백준/Python] 3273. 두 수의 합 (투포인터) 🥈3️⃣</h1>
<hr>
<h1 id="문제">문제</h1>
<p>n개의 서로 다른 양의 정수 a1, a2, ..., an으로 이루어진 수열이 있다. ai의 값은 1보다 크거나 같고, 1000000보다 작거나 같은 자연수이다. 자연수 x가 주어졌을 때, ai + aj = x (1 ≤ i &lt; j ≤ n)을 만족하는 (ai, aj)쌍의 수를 구하는 프로그램을 작성하시오.</p>
<h3 id="입력">입력</h3>
<p>첫째 줄에 수열의 크기 n이 주어진다. 다음 줄에는 수열에 포함되는 수가 주어진다. 셋째 줄에는 x가 주어진다. (1 ≤ n ≤ 100000, 1 ≤ x ≤ 2000000)</p>
<h3 id="출력">출력</h3>
<p>문제의 조건을 만족하는 쌍의 개수를 출력한다.</p>
<h3 id="예제-입력">예제 입력</h3>
<pre><code>9
5 12 7 10 9 1 2 3 11
13</code></pre><h3 id="예제-출력">예제 출력</h3>
<pre><code>3</code></pre><hr>
<h1 id="풀이">풀이</h1>
<h2 id="1-문제의-핵심-이해하기">1. 문제의 핵심 이해하기</h2>
<p>이 문제는 정수 배열에서 두 수를 골라서 합이 $x$가 되는 쌍의 개수를 구하는 문제이다.</p>
<p>처음 떠올리기 쉬운 방법은 모든 두 수를 다 확인하는 것이다.
구현하기엔 쉽겠지만, 시간복잡도가 O(N²) 이라서 비효율적이다.
따라서, 정렬 + <strong>투포인터</strong>를 사용해 풀어야 한다.</p>
<h2 id="2-투포인터two-pointers란">2. 투포인터(Two Pointers)란?</h2>
<p>배열을 정렬한 뒤, 왼쪽 끝을 <code>left</code>, 오른쪽 끝을 <code>right</code>라고 두 개의 손가락(포인터)을 둔다. 양 끝 지점에서 가운데로 한쪽 포인터를 조건에 따라 이동시키며 탐색 범위를 줄여가는 방식이다.</p>
<p>준비물: 정렬된 숫자 배열 (중요! <strong>반드시 오름차순 정렬이 되어 있어야</strong> 한다.)
시작 위치: </p>
<ul>
<li>왼쪽 포인터 ($left$): 배열의 맨 첫 번째 (가장 작은 값)</li>
<li>오른쪽 포인터 ($right$): 배열의 맨 마지막 (가장 큰 값)</li>
</ul>
</br>

<h2 id="💡-투-포인터를-쉽게-이해하기">💡 투 포인터를 쉽게 이해하기</h2>
<p>배열을 정렬한 뒤, 
왼쪽 끝을 <code>left</code>, 오른쪽 끝을 <code>right</code> 라고 두고 시작한다.</p>
<p>예를 들어 정렬된 배열이
<code>[1, 2, 3, 4, 5, 7]</code> 이고, 목표 합이 <code>8</code>이라면</p>
<ul>
<li><code>left = 0</code> → 1</li>
<li><code>right = 5</code> → 7</li>
</ul>
</br>

<p><strong>1️⃣ 1단계</strong>
<code>1 + 7 = 8</code> 찾았다. 개수 1 증가.</p>
<p>그다음엔 같은 쌍을 또 세면 안 되니까
<code>left += 1</code>, <code>right -= 1</code>
</br></p>
<p><strong>2️⃣ 2단계</strong>
이제 <code>2 + 5 = 7</code>. 목표보다 작다.</p>
<p>그러면 <strong>더 큰 수가 필요하니까 왼쪽 값을 키워야 한다.</strong>
즉, <code>left += 1</code>
</br></p>
<p><strong>3️⃣ 3단계</strong>
<code>3 + 5 = 8</code>. 또 찾았다. 개수 1 증가.</p>
<p>다시 <code>left += 1</code>, <code>right -= 1</code>
이제 <code>left &gt;= right</code>가 되면 종료.</p>
<br>

<h3 id="왜-이렇게-움직이는걸까">왜 이렇게 움직이는걸까?</h3>
<p><strong>현재 합이 x보다 작다</strong>는 건, 더 큰 수가 필요하다는 뜻이다.</p>
<p>정렬되어 있으니까</p>
<ul>
<li>오른쪽은 이미 큰 값</li>
<li>왼쪽을 오른쪽으로 옮기면 값이 커짐</li>
</ul>
<p>그래서 <strong>left를 증가</strong>시켜야 한다.</p>
</br>

<p><strong>현재 합이 x보다 크다</strong>는 건, 더 작은 수가 필요하다는 뜻이다.</p>
<p>정렬되어 있으니까</p>
<ul>
<li>오른쪽을 왼쪽으로 옮기면 값이 작아짐</li>
</ul>
<p>그래서 <strong>right를 감소</strong>시켜야 한다.</p>
<br>

<p>이제 이 내용을 바탕으로 알고리즘을 정리해보면 다음과 같다.</p>
<h2 id="🧠-최종-알고리즘">🧠 최종 알고리즘</h2>
<pre><code>1. 수열을 입력받는다
2. 정렬한다
3. left = 0, right = n - 1로 시작한다
4. left &lt; right 동안 반복한다
    5. 두 수의 합을 보고
    6. x와 같으면 count 증가 후 양쪽 이동
    7. x보다 작으면 left 증가
    8. x보다 크면 right 감소</code></pre><br>

<hr>
<h1 id="코드">코드</h1>
<p>코드로 구현한다면 아래와 같다.</p>
<pre><code class="language-python">n = int(input())
arr = list(map(int, input().split()))
x = int(input())

arr.sort()

left = 0
right = n - 1
count = 0

while left &lt; right:
    total = arr[left] + arr[right]

    if total == x:
        count += 1
        left += 1
        right -= 1
    elif total &lt; x:
        left += 1
    else:
        right -= 1

print(count)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준/Python] 1459. 걷기 (그리디) 🥈3️⃣]]></title>
            <link>https://velog.io/@from-minju/%EB%B0%B1%EC%A4%80Python-1459.-%EA%B1%B7%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EB%94%94-3</link>
            <guid>https://velog.io/@from-minju/%EB%B0%B1%EC%A4%80Python-1459.-%EA%B1%B7%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EB%94%94-3</guid>
            <pubDate>Thu, 09 Apr 2026 15:55:53 GMT</pubDate>
            <description><![CDATA[<h1 id="백준python-1459-걷기-그리디-🥈3️⃣"><a href="https://www.acmicpc.net/problem/1459">[백준/Python] 1459. 걷기 (그리디) 🥈3️⃣</a></h1>
<hr>
<h1 id="문제">문제</h1>
<p>좌표 <code>(0,0)</code>에서 <code>(x,y)</code>까지 이동할 때,
직선 이동(상하좌우)과 대각선 이동을 사용할 수 있다.</p>
<ul>
<li>직선 이동 비용: <code>w</code></li>
<li>대각선 이동 비용: <code>s</code></li>
</ul>
<p>최소 비용으로 목적지까지 이동하는 시간을 구하는 문제이다.</p>
<hr>
<h1 id="💻-정석-코드">💻 정석 코드</h1>
<pre><code class="language-python">x, y, w, s = map(int, input().split())

case1 = (x + y) * w
case2 = min(x, y) * s + abs(x - y) * w

if (x + y) % 2 == 0:
    case3 = max(x, y) * s
else:
    case3 = (max(x, y) - 1) * s + w

print(min(case1, case2, case3))</code></pre>
<hr>
<h2 id="코드-풀이">코드 풀이</h2>
<p>(0, 0)에서 (x, y)로 평행선과 대각선을 사용하여 이동할 때 걸리는 시간 3가지 경우로 나눌 수 있다. </p>
<p><strong>1) 전부 직선 이동
2) 대각선 최대한 + 남은거리는 직선으로만
3) 가능한 한 대각선만 사용</strong></p>
<p>이 세 가지 경우중에 최소값이 곧 결과가 된다.</p>
</br>


<h3 id="1-전부-직선-이동">1) 전부 직선 이동</h3>
<pre><code class="language-python">(x + y) * w</code></pre>
<h3 id="2-대각선-최대한--남은거리는-직선으로만">2) 대각선 최대한 + 남은거리는 직선으로만</h3>
<pre><code class="language-python">min(x, y) * s + abs(x - y) * w</code></pre>
<h3 id="3-가능한-한-대각선만-사용">3) 가능한 한 대각선만 사용</h3>
<p>평행한 일직선의 거리도 대각선이동으로 가능하다. </p>
<p>예를 들어 <code>(0,0)</code>에서 <code>(2,0)</code> 로 이동하는 경우, x축으로 두 번 평행이동하는 방법도 있겠지만,
<code>(0,0)</code> - <code>(1,1)</code> - <code>(2,0)</code> 이렇게 대각선으로 두 번 이동하는 것도 가능하다. </p>
<p>단, 거리가 2의 배수인 경우는 위의 예시처럼 모두 대각선이동으로 가능하지만,
예를들어 <code>(0,0)</code>에서 <code>(1,0)</code>으로 이동하는 경우처럼 거리가 홀수인 경우는 대각선이동과 한번의 직선이동이 필요하다. <code>(0,0)</code> - <code>(1,1)</code> - <code>(1,0)</code></p>
<pre><code class="language-python">if (x + y) % 2 == 0:
    max(x, y) * s
else:
    (max(x, y) - 1) * s + w</code></pre>
<hr>
<h1 id="🔥-나의-시행착오">🔥 나의 시행착오</h1>
<p>위의 풀이가 정석풀이라면, 지금부터는 그 전까지 내가 시행착오했던 풀이들이다.</p>
<h3 id="첫-번째-시도--실패">첫 번째 시도 : 실패</h3>
<p>처음에는 다음과 같은 아이디어로 접근했다.</p>
<ul>
<li>가로1 + 세로1 이동은 대각선이동 하나로 대체가능하다.</li>
<li>s &lt; 2w : 대각선 이동이 유리함</li>
<li>s &gt; 2w : 평행이동이 유리함.</li>
</ul>
<blockquote>
<p><code>s &lt; 2w</code>이면 대각선이 유리하므로 최대한 대각선으로 이동하고,
나머지는 직선으로 이동한다.</p>
</blockquote>
<p>이를 코드로 구현하면 다음과 같다.</p>
<pre><code class="language-python">x, y, w, s = map(int, input().split())

max_s = min(x, y)
s_cnt = 0

if s &lt; 2 * w:
    s_cnt = min(x, y)

print(((x - s_cnt) + (y - s_cnt)) * w + s*s_cnt)</code></pre>
<p><strong>1) 틀린 이유</strong></p>
<p>이 코드는 <strong>남은 거리를 무조건 직선으로 처리</strong>한다고 가정했다.
하지만 실제로는 남은 거리도 대각선으로 처리하는 것이 더 빠를 수 있다.</p>
</br>

<p><strong>2) 반례</strong></p>
<pre><code>x = 2, y = 0
w = 12, s = 10</code></pre><ul>
<li>내 코드 결과: <code>24</code></li>
<li>실제 정답: <code>20</code></li>
</ul>
<p>이유:</p>
<ul>
<li><code>(0,0) → (1,1) → (2,0)</code></li>
<li>대각선 2번으로 이동 가능</li>
</ul>
<p>즉, <strong>한 축만 남았다고 해서 직선만 써야 하는 것은 아니다.</strong></p>
<br>

<h3 id="💻-나의-풀이-개선-코드">💻 나의 풀이 (개선 코드)</h3>
<pre><code class="language-python">x, y, w, s = map(int, input().split())

if s &gt;= 2 * w:
    answer = (x + y) * w
else:
    diag = min(x, y) #대각선(diagonal)으로 이동하는 횟수
    diff = abs(x - y) #대각선으로 최대한 이동하고 남은 거리

    answer = diag * s

    if diff % 2 == 0:
        answer += min(diff * w, diff * s)
    else:
        answer += min(diff * w, (diff - 1) * s + w)

print(answer)</code></pre>
<p><strong>1) 핵심 로직</strong></p>
<ul>
<li><code>min(x,y)</code> 만큼은 대각선 이동</li>
<li>남은 거리 <code>diff = abs(x-y)</code> 처리</li>
</ul>
<p><strong>2) 남은 거리 처리</strong></p>
<ol>
<li><p>짝수
→ 대각선만으로 정확히 도달 가능
<code>diff * s</code></p>
</li>
<li><p>홀수
→ 마지막 1칸은 직선 필요
<code>(diff - 1) * s + w</code></p>
</li>
</ol>
<p><strong>3) 직선 vs 대각선 비교</strong></p>
<pre><code class="language-python">min(diff * w, 대각선 방식)</code></pre>
<br>


<p>지금 이 풀이에서는 <code>s &gt;= 2 * w</code>에 따라 조건을 나눴지만, 
이렇게 할 필요없이 정석코드에서처럼 case1, 2, 3을 구하고 이 중에 최소값을 결과로 출력하는 편이 훨씬 보기에도 편하고 간단하다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스/Python] 콜라 문제 Lv1]]></title>
            <link>https://velog.io/@from-minju/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%EC%BD%9C%EB%9D%BC-%EB%AC%B8%EC%A0%9C-Lv1</link>
            <guid>https://velog.io/@from-minju/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%EC%BD%9C%EB%9D%BC-%EB%AC%B8%EC%A0%9C-Lv1</guid>
            <pubDate>Thu, 09 Apr 2026 12:46:20 GMT</pubDate>
            <description><![CDATA[<h1 id="프로그래머스python-콜라-문제-lv1"><a href="https://school.programmers.co.kr/learn/courses/30/lessons/132267">[프로그래머스/Python] 콜라 문제 Lv1</a></h1>
<hr>
<h1 id="문제">문제</h1>
<p>빈 병 <code>a</code>개를 가져가면 콜라 <code>b</code>병을 받을 수 있다.
현재 가지고 있는 빈 병이 <code>n</code>개일 때, 최대로 받을 수 있는 콜라 병 수를 구하는 문제이다.</p>
<hr>
<h1 id="요구사항-분석">요구사항 분석</h1>
<p><strong>1) 교환 조건</strong></p>
<ul>
<li>빈 병이 <code>a</code>개 이상 있어야 교환 가능하다.</li>
<li>한 번에 여러 번 교환 가능하다. (예: 10개면 5번 교환)</li>
</ul>
<br>

<p><strong>2) 반복 구조</strong></p>
<ul>
<li><p>교환할 때마다</p>
<ul>
<li>새로 받은 병을 더하고</li>
<li>남은 병을 다음 턴으로 넘긴다.</li>
</ul>
</li>
</ul>
<br>

<p><strong>3) 종료 조건</strong></p>
<ul>
<li>더 이상 교환할 수 없을 때 (<code>n &lt; a</code>) 반복 종료</li>
</ul>
<hr>
<h1 id="나의-풀이-과정">나의 풀이 과정</h1>
<h3 id="첫-번째-시도--통과">첫 번째 시도 : 통과</h3>
<p>통과되긴하지만 더 간단하게 작성할 수 있다.</p>
<pre><code class="language-python">def solution(a, b, n):
    answer = 0
    bottles = n

    while bottles &gt;=  a:
        x = (bottles // a) * a
        bottles -= x

        received = (x // a) * b
        answer += received
        bottles += received 

    return answer</code></pre>
<hr>
<h1 id="💻-정석-코드">💻 정석 코드</h1>
<pre><code class="language-python">def solution(a, b, n):
    answer = 0

    while n &gt;= a:
        new_cola = (n // a) * b
        answer += new_cola
        n = (n % a) + new_cola

    return answer</code></pre>
<hr>
<h1 id="배운점">배운점</h1>
<ul>
<li><code>x</code>와 같이 모호한 변수명은 사용하지 말자. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스/Python] 명예의 전당(1) (구현) Lv1]]></title>
            <link>https://velog.io/@from-minju/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%EB%AA%85%EC%98%88%EC%9D%98-%EC%A0%84%EB%8B%B91-%EC%A0%95%EB%A0%AC%EA%B5%AC%ED%98%84-Lv1</link>
            <guid>https://velog.io/@from-minju/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4Python-%EB%AA%85%EC%98%88%EC%9D%98-%EC%A0%84%EB%8B%B91-%EC%A0%95%EB%A0%AC%EA%B5%AC%ED%98%84-Lv1</guid>
            <pubDate>Thu, 09 Apr 2026 08:58:13 GMT</pubDate>
            <description><![CDATA[<h1 id="프로그래머스python-명예의-전당1-정렬구현-lv1"><a href="https://school.programmers.co.kr/learn/courses/30/lessons/138477">[프로그래머스/Python] 명예의 전당(1) (정렬/구현) Lv1</a></h1>
<hr>
<h1 id="문제-간단-요약-및-설명">문제 간단 요약 및 설명</h1>
<p>매일 점수가 하나씩 발표된다.
지금까지 나온 점수 중 <strong>상위 k개</strong>를 “명예의 전당”에 유지한다.</p>
<p>매일 명예의 전당에 포함된 점수들 중
<strong>가장 낮은 점수</strong>를 기록하여 반환하는 문제이다.</p>
<hr>
<h1 id="🔥-나의-풀이-및-시행착오">🔥 나의 풀이 및 시행착오</h1>
<h3 id="1️⃣-첫-번째-시도--실패">1️⃣ 첫 번째 시도 : 실패</h3>
<p>처음에는 문제를 다음과 같이 <code>k</code>를 기준으로 나누어 접근했다.</p>
<p><strong>1) 2일차부터 k일까지</strong>
<strong>2) k일 이후부터</strong></p>
<p>단, k 보다 score 길이가 짧은 경우까지 고려하지 못해 실패했다. </p>
<pre><code class="language-python">def solution(k, score):
    answer = [score[0]]
    honor = [score[0]]
    days = len(score)

    for i in range(2, k+1):
        honor.append(score[i-1])
        answer.append(min(honor))

    for j in range(k+1, days + 1):
        if answer[-1] &lt; score[j-1]:
            honor.sort(reverse=True)
            honor[-1] = score[j-1]
            answer.append(min(honor))
        else:
            answer.append(answer[-1])

    return answer</code></pre>
<h3 id="2️⃣-두-번째-시도--성공">2️⃣ 두 번째 시도 : 성공</h3>
<p>🔍 <strong>실패 원인</strong> : k보다 score 길이가 짧은 경우를 생각해보았는가?
🚀 <strong>해결 방법</strong> : k보다 score 길이가 짧은 경우를 조건으로 나누어 따로 처리</p>
<p>이 방식으로도 풀 수는 있다. <strong>다만 좋은 풀이는 아니다.</strong></p>
<pre><code class="language-python">def solution(k, score):
    answer = [score[0]]
    honor = [score[0]]
    days = len(score)

    if k &gt; days:
        for i in range(2, days+1):
            honor.append(score[i-1])
            answer.append(min(honor))
        return answer


    for i in range(2, k+1):
        honor.append(score[i-1])
        answer.append(min(honor))

    for j in range(k+1, days + 1):
        if answer[-1] &lt; score[j-1]:
            honor.sort(reverse=True)
            honor[-1] = score[j-1]
            answer.append(min(honor))
        else:
            answer.append(answer[-1])

    return answer</code></pre>
<hr>
<h1 id="💬-gpt의-피드백">💬 GPT의 피드백</h1>
<blockquote>
<p>네 코드는 <strong>“구간을 나눠서 처리”</strong>하고 있어</p>
</blockquote>
<p>이 코드는 문제를 이렇게 보고 있는 거야.</p>
<ul>
<li>첫째 날은 따로 처리</li>
<li>2일차 ~ k일차도 따로 처리</li>
<li>k+1일차부터 또 따로 처리</li>
<li>k가 전체 날짜보다 큰 경우도 따로 처리</li>
</ul>
<p>즉, 상황별로 나눠서 계산하고 있어.</p>
<p>이 방식의 단점은
문제를 풀다 보면 <strong>“예외 처리”</strong>가 계속 늘어난다는 거야.</p>
<p>예를 들면 이런 생각이 계속 생겨:</p>
<ul>
<li>첫날은 이미 넣었으니까 2일부터 시작해야 하나?</li>
<li>k가 days보다 크면?</li>
<li>k일까지는 그냥 넣기만 하면 되나?</li>
<li>그 다음부터는 교체만 하면 되나?</li>
</ul>
<p><span style="color:green"><strong>🔥 이런 식으로 코드가 점점 “문제 자체”보다 “예외를 다루는 코드”가 돼버리기 쉬워.</strong></span></p>
<h2 id="그렇다면-어떻게-해야하나">그렇다면 어떻게 해야하나?</h2>
<blockquote>
<p>정석 코드는 <strong>“매일 같은 규칙만 반복”</strong>해</p>
</blockquote>
<h1 id="💻-정석-코드">💻 정석 코드</h1>
<p>이 문제는 정답 리스트를 한 번에 만드는 문제가 아니라,
<strong>하루씩 보면서 명예의 전당 상태를 갱신하는 문제</strong>야.</p>
<pre><code class="language-python">def solution(k, score):
    answer = []
    honor = []

    for s in score:
        honor.append(s)

        if len(honor) &gt; k:
            honor.remove(min(honor))

        answer.append(min(honor))

    return answer</code></pre>
<p>즉 핵심은:</p>
<ol>
<li>오늘 점수가 들어온다</li>
<li>명예의 전당 후보에 반영한다</li>
<li>상위 k개만 남긴다</li>
<li>그중 최하위 점수를 answer에 넣는다</li>
</ol>
<p>이걸 매일 반복하면 끝이야.</p>
<p>문제를 이렇게 보면
“처음 k일”, “그 이후”로 나눌 필요가 없어져.
즉, 첫날도 똑같고 둘째 날도 똑같고 마지막 날도 똑같아.</p>
<p>이게 정석 풀이가 자연스러운 가장 큰 이유야.
<strong>문제를 매일 반복되는 하나의 규칙으로 바꿨기 때문이야.</strong></p>
<br>

<h2 id="🤔-앞으로-비슷한-문제에서-어떻게-생각하면-좋을까">🤔 앞으로 비슷한 문제에서 어떻게 생각하면 좋을까?</h2>
<p><strong>1) 상태를 유지하는 방식으로 접근해야 한다</strong></p>
<p>이 문제의 핵심은 “정답을 계산”하는 것이 아니라
👉 <strong>매 순간 명예의 전당 상태를 유지하는 것</strong>이다.</p>
<br>

<p><strong>2) 구간을 나누지 말고 동일한 규칙을 반복하자</strong></p>
<ul>
<li>넣는다</li>
<li>k 초과면 최소값 제거</li>
<li>최솟값 기록</li>
</ul>
<p>이 규칙 하나로 모든 케이스를 처리할 수 있다.</p>
<br>

<p><strong>3) 결과 배열이 아니라 상태 배열을 기준으로 판단하자</strong></p>
<ul>
<li><code>answer</code> → 출력용</li>
<li><code>honor</code> → 상태 관리</li>
</ul>
<p>이 둘을 분리하면 로직이 훨씬 명확해진다.</p>
<br>

<p><strong>4) 문제를 이렇게 바라보면 쉬워진다</strong></p>
<ul>
<li>유지해야 하는 상태: 상위 k개</li>
<li>새로운 값이 들어왔을 때: 넣고, 넘치면 제거</li>
<li>출력: 현재 상태의 최솟값</li>
</ul>
<p>이 3단계로 생각하면 대부분의 구현 문제가 훨씬 단순해진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Baekjoon/Python] 2468. 안전 영역 (DFS/BFS)]]></title>
            <link>https://velog.io/@from-minju/BaekjoonPython-2468.-%EC%95%88%EC%A0%84-%EC%98%81%EC%97%AD-DFSBFS-ulxwqkjn</link>
            <guid>https://velog.io/@from-minju/BaekjoonPython-2468.-%EC%95%88%EC%A0%84-%EC%98%81%EC%97%AD-DFSBFS-ulxwqkjn</guid>
            <pubDate>Tue, 31 Mar 2026 06:09:51 GMT</pubDate>
            <description><![CDATA[<h1 id="baekjoonpython-2468-안전-영역-bfs"><a href="https://www.acmicpc.net/problem/2468">[Baekjoon/Python] 2468. 안전 영역 (BFS)</a></h1>
<hr>
<h1 id="문제">문제</h1>
<p>N×N 크기의 지역이 주어지고, 각 칸에는 높이가 있다.
비가 일정 높이만큼 내리면 그 높이 이하의 모든 지역은 물에 잠긴다.</p>
<p>물에 잠기지 않은 칸들끼리 상하좌우로 연결되어 있다면 하나의 안전 영역으로 본다.
비의 높이를 바꿔가며 <strong>안전 영역의 최대 개수</strong>를 구하는 문제이다.</p>
<hr>
<h1 id="📚-문제-분석">📚 문제 분석</h1>
<p><strong>1. 핵심은 “여러 번 탐색해야 하는 문제”이다</strong></p>
<p>비의 높이가 하나로 고정되어 있지 않다.
따라서 한 번 BFS/DFS로 끝나는 문제가 아니라,
<strong>비의 높이를 바꿔가며 매번 안전 영역 개수를 다시 계산해야 한다.</strong></p>
<br>

<p><strong>2. 안전 영역은 연결 요소 개수 문제이다</strong></p>
<p>잠기지 않은 칸들을 기준으로 보면,
결국 <strong>상하좌우로 연결된 덩어리 개수</strong>를 세는 문제이다.</p>
<p>→ <strong>BFS</strong> 또는 <strong>DFS</strong>로 해결 가능</p>
<br>

<p><strong>3. 탐색 기준</strong></p>
<ul>
<li><code>graph[i][j] &gt; rain</code> → 잠기지 않은 칸 (안전 영역)</li>
<li>이 조건을 만족하는 칸들끼리 연결된 영역 개수 세기</li>
</ul>
<br>

<p><strong>4. 비의 범위</strong></p>
<p>비는 <code>0 ~ (최대 높이-1)</code>까지 확인하면 충분하다.</p>
<ul>
<li><code>rain = 0</code> → 비가 안 온 경우</li>
<li><code>rain &gt;= max_height</code> → 전부 잠김 → 의미 없음</li>
</ul>
<hr>
<h1 id="💻-나의-최종-코드-정석-코드">💻 나의 최종 코드 (정석 코드)</h1>
<pre><code class="language-python">from collections import deque

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

maximum = max(max(row) for row in graph)

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

def get_secure_zone(rain):
    secure_num = 0
    visited = [[False] * N for _ in range(N)]

    for i in range(N):
        for j in range(N):
            if visited[i][j] or graph[i][j] &lt;= rain:
                continue

            queue = deque([(i, j)])
            visited[i][j] = True

            while queue:
                x, y = queue.popleft()

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

                    if 0 &lt;= nx &lt; N and 0 &lt;= ny &lt; N:
                        if not visited[nx][ny] and graph[nx][ny] &gt; rain:
                            visited[nx][ny] = True
                            queue.append((nx, ny))

            secure_num += 1

    return secure_num

answer = 0

for rain in range(maximum):
    answer = max(answer, get_secure_zone(rain))

print(answer)</code></pre>
<hr>
<h1 id="🔥-시행착오했던-풀이">🔥 시행착오했던 풀이</h1>
<p><strong>1. 그래프를 직접 변경하는 방식으로 접근</strong>
처음에는 매번 그래프를 복사해서 BFS 중 방문한 칸을 0으로 바꾸는 방식으로 구현했다.</p>
<pre><code class="language-python">tgraph[tx][ty] = 0</code></pre>
<p>그래서 매 강수량마다 그래프를 복사해서 사용하려고 했다.</p>
<pre><code class="language-python">get_secure_zone(copy.deepcopy(graph), rain)</code></pre>
<p>이 방식 자체는 동작은 가능하다. 
다만, 매번 깊은복사를 해야하기때문에 무거워질수 있어 visited 배열로 따로 방문처리하는 편이 낫다. 이 방법은 3번에서 다시 다루겠다. </p>
<br>

<p><strong>2. 2차원 리스트 복사 문제 발생</strong></p>
<p>처음에는 <code>deepcopy</code> 대신 아래처럼도 시도했다가 실패했다.</p>
<pre><code class="language-python">graph[:]</code></pre>
<p>1차원 리스트였다면 깊은 복사가 되었겠지만, 2차원 리스트의 경우 깊은복사가 안된다.</p>
<ul>
<li>바깥 리스트만 복사됨</li>
<li>내부 리스트는 그대로 공유됨</li>
</ul>
<p>그래서 BFS 중에 값을 바꾸면
<strong>원본 graph까지 같이 바뀌는 문제</strong>가 발생했다.</p>
<p>이 때문에 다음 강수량 계산에서
이미 변경된 그래프를 사용하게 되어 오답이 발생했다.</p>
<br>

<p><strong>3. 해결 방법</strong></p>
<ul>
<li><code>copy.deepcopy(graph)</code> 사용 → 완전한 깊은 복사</li>
<li>또는 <code>[row[:] for row in graph]</code> 사용</li>
</ul>
<p>하지만 이 문제에서는 더 좋은 방법이 있다.</p>
<p>→ <strong>그래프를 건드리지 않고 <code>visited</code> 배열을 사용하는 것</strong></p>
<p>이렇게 하면 복사 자체가 필요 없어지고,
코드도 훨씬 깔끔해진다.</p>
<ul>
<li><code>graph[i][j]</code>는 높이 정보</li>
<li><code>visited[i][j]</code>는 방문 여부
이렇게 역할이 나뉘어 있어서 코드가 더 명확하다.</li>
</ul>
<p>한번만 검사할때는 visited 없이 그래프를 직접 바꾸는게 더 간단하겠지만,
여러 조건을 반복해서 검사해야 할 때는 그래프를 직접 바꾸는 방식은 불편해질 수 있다.</p>
<br>

<p><strong>4. 비가 안 오는 경우를 따로 처리했던 점</strong></p>
<ul>
<li><code>0&lt;= rain &lt; min</code> : 안전지대 항상 1개</li>
<li><code>min &lt;= rain &lt; max</code> : 안전지대 1개 이상 </li>
</ul>
<p>처음에는 위의 조건대로 <code>0&lt;= rain &lt; min</code> 범위에서는 안전지대가 항상 1개이기 때문에 이 범위에 대해서 탐색할 필요가 없다고 생각했다. 
그래서 result 리스트에 강수량별 안전지대 개수를 저장해놓고, 마지막에 max(result)를 정답으로 출력할 생각이었다.</p>
<pre><code class="language-python">result = [1]</code></pre>
<p>로 <code>0&lt;= rain &lt; min</code>범위의 안전지대 값(1)으로 초기화해두고, 
<code>min &lt;= rain &lt; max</code>에 대해 안전지대 개수를 구해 result에 추가하는 방식이다. </p>
<p>하지만, 이 문제의 시간복잡도는 <code>O(높이 범위 × N²)</code> 으로,
<code>0&lt;= rain &lt; min</code>를 포함하나 안하나 시간복잡도에 큰 영향을 주지 않는다.
최악의 경우, <code>100 × (100 × 100) = 1,000,000</code>번 정도 이다. </p>
<p>물론 조금이라도 시간을 줄이고싶다면, 최적화하는게 좋겠지만, 세세하게까지 다루다 실수해버릴수도 있기 때문에 휴먼에러와 코드단순화를 생각한다면 좀 더 단순하게 가는게 좋아보인다.</p>
<hr>
<h1 id="🤔-느낀점">🤔 느낀점</h1>
<p>문제의 큰 틀, 즉 핵심 풀이 아이디어는는 잘 파악했으나, 이를 구현하는 과정에서 오래걸린 것이 아쉽다.
사실 큰틀만 놓고보면 <a href="https://velog.io/@from-minju/BaekjoonPython-2667.-%EB%8B%A8%EC%A7%80%EB%B2%88%ED%98%B8%EB%B6%99%EC%9D%B4%EA%B8%B0-BFSDFS">&#39;단지 번호 붙이기&#39;</a> 같은 연결요소개수 구하기를 * n번 반복하는 것 뿐이다. 근데 왜이렇게 오래걸렸을까... 
너무 복잡하게, 사소한것까지 최적화하려고 해서 그런것 같다. 시간복잡도를 고려해서 필요없는 최적화는 버리고 단순하게 가는것도 중요하겠다.</p>
<hr>
<h1 id="✏️-배운점">✏️ 배운점</h1>
<p><strong>1. 연결 요소 문제로 변환하는 사고가 중요하다</strong></p>
<p>처음에는 단순히 “잠기는 영역”에 집중했지만,
실제 문제는 <strong>잠기지 않은 영역의 개수</strong>를 구하는 문제였다.</p>
<p>→ BFS/DFS 문제로 바꿔 생각하는 것이 핵심</p>
<br>

<p><strong>2. 그래프를 직접 바꿀지, visited를 쓸지 먼저 결정해야 한다</strong></p>
<ul>
<li>그래프 직접 변경 → 구현은 쉽지만 복사 문제 발생</li>
<li>visited 사용 → 조금 더 구조적이고 안정적</li>
</ul>
<p>특히 이 문제처럼 여러 번 탐색해야 할 때는 visited 방식이 훨씬 유리하다. 
상황에 따라 판단하자. </p>
<br>

<p><strong>3. 2차원 리스트 복사 개념은 반드시 정확히 알아야 한다</strong></p>
<ul>
<li><code>graph[:]</code> → 얕은 복사 (실수 포인트)</li>
<li><code>[row[:] for row in graph]</code> → 2차원 복사 가능</li>
<li><code>copy.deepcopy(graph)</code> → 완전한 깊은 복사</li>
</ul>
<p>이 차이를 모르고 BFS/DFS 문제를 풀면
디버깅이 매우 어려워진다.</p>
<br>

<p><strong>4. 모든 경우를 하나의 흐름으로 처리하는 것이 더 안전하다</strong></p>
<p>예외를 따로 두기보다</p>
<pre><code class="language-python">for rain in range(maximum):</code></pre>
<p>처럼 통일된 구조로 처리하면 코드가 단순해지고 실수도 줄어든다.(시간복잡도에 큰 영향을 미치지 않는 경우)</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준/Python] 5014. 스타트링크 (BFS)]]></title>
            <link>https://velog.io/@from-minju/%EB%B0%B1%EC%A4%80Python-5014.-%EC%8A%A4%ED%83%80%ED%8A%B8%EB%A7%81%ED%81%AC-BFS</link>
            <guid>https://velog.io/@from-minju/%EB%B0%B1%EC%A4%80Python-5014.-%EC%8A%A4%ED%83%80%ED%8A%B8%EB%A7%81%ED%81%AC-BFS</guid>
            <pubDate>Mon, 30 Mar 2026 15:32:52 GMT</pubDate>
            <description><![CDATA[<h1 id="baekjoonpython-5014-스타트링크-bfs"><a href="https://www.acmicpc.net/problem/5014">[Baekjoon/Python] 5014. 스타트링크 (BFS)</a></h1>
<hr>
<h1 id="문제">문제</h1>
<p>건물에 <code>F</code>층이 있고, 현재 <code>S</code>층에서 <code>G</code>층으로 이동하려고 한다.
엘리베이터 버튼은 두 가지:</p>
<ul>
<li>위로 <code>U</code>층 이동</li>
<li>아래로 <code>D</code>층 이동</li>
</ul>
<p>목표는 <strong>최소 버튼 횟수로 G층에 도달하는 것</strong>이다.
도달할 수 없다면 <code>&quot;use the stairs&quot;</code>를 출력한다.</p>
<hr>
<h1 id="풀이-과정">풀이 과정</h1>
<p><strong>1. BFS로 접근해야 하는 이유</strong></p>
<p>이 문제는 버튼을 한 번 누를 때마다 이동 횟수가 항상 1이다.
즉, 모든 이동 비용이 동일하므로 <strong>BFS로 최단 횟수를 구할 수 있다.</strong></p>
<br>

<p><strong>2. BFS 핵심 개념</strong></p>
<p>BFS는 이동 횟수 기준으로 다음과 같이 탐색한다.</p>
<ul>
<li>1번 이동</li>
<li>2번 이동</li>
<li>3번 이동</li>
</ul>
<p>이 순서로 탐색하기 때문에
<strong>처음 방문한 순간이 곧 최소 이동 횟수</strong>가 된다.</p>
<hr>
<h1 id="나의-시행착오">나의 시행착오</h1>
<p><strong>1. 이미 방문한 층도 다시 갱신하려고 했던 접근</strong></p>
<p>처음에는 아래처럼 생각했다.</p>
<blockquote>
<p>“이미 방문한 층이라도, 더 적은 횟수로 도달할 수 있지 않을까? 더 짧으면 갱신해야지” </p>
</blockquote>
<p>그래서 다음과 같이 코드를 작성했다.</p>
<pre><code class="language-python">if apt[moved] == 0:
    apt[moved] = apt[current] + 1
else:
    apt[moved] = min(apt[current] + 1, apt[moved])</code></pre>
<p>즉, 더 작은 값이 나오면 갱신하려는 방식이었다.</p>
<p>하지만 이 접근은 BFS에서는 필요 없는 로직이었다.</p>
<p>이 문제는 버튼 한 번 누를 때마다 비용이 전부 같다. 위로 가기 1번, 아래로 가기 1번.
<strong>즉 모든 간선의 가중치가 1인 그래프</strong>이다.</p>
<ul>
<li>BFS는 애초에 <strong>최소 횟수부터 탐색</strong> -&gt; 따라서 나중에 더 작은 값이 나올 일이 없음</li>
<li><strong>BFS: 간선 가중치가 모두 같을 때 최단거리 보장</strong>
그래서 처음 방문 = 최소 횟수
이미 방문한 곳을 다시 더 짧게 갱신할 일은 없음</li>
</ul>
<p>반대로 이런 경우는 다르다.만약 가중치가 다른 상황이면(예를들어 엘리베이터 위로 가면 10초, 아래로 가면 1초)
BFS 안됨. 나중에 더 빠른 경로가 생길 수 있다. (이게 다익스트라)</p>
<ul>
<li>이동 1번, 점프 1번, 버튼 1번처럼 모든 행동 비용이 동일하다 → BFS로 최단거리</li>
<li>이동마다 비용이 다르다 → 다익스트라 같은 다른 최단경로 알고리즘 고려</li>
</ul>
<br>


<p><strong>2. 방문 체크 없이 큐에 계속 추가한 문제</strong></p>
<p>또 하나의 문제는 다음 코드였다.</p>
<pre><code class="language-python">queue.append(moved)</code></pre>
<p>방문 여부를 확인하지 않고 무조건 큐에 넣었기 때문에:</p>
<ul>
<li>같은 층이 계속 반복해서 큐에 들어감</li>
<li>A → B → A → B 무한 반복 구조 발생</li>
<li>결국 <strong>SIGTERM (강제 종료)</strong> 발생</li>
</ul>
<br>

<p><strong>3. dist 배열 초기값 문제</strong></p>
<p>처음에는 <code>0</code>으로 초기화했는데:</p>
<ul>
<li>시작점도 <code>0</code></li>
<li>방문 안 한 곳도 <code>0</code></li>
</ul>
<p>이 둘이 구분되지 않아 로직이 꼬였다.</p>
<p>그래서 <code>-1</code>로 초기화하여 명확히 구분하도록 수정했다.</p>
<hr>
<h1 id="최종-알고리즘">최종 알고리즘</h1>
<p><strong>1. dist 배열을 -1로 초기화 (미방문 표시)</strong></p>
<p><strong>2. 시작점 S를 큐에 넣고 dist[S] = 0</strong></p>
<p><strong>3. BFS 수행</strong></p>
<ul>
<li><p>현재 층에서</p>
<ul>
<li>위로 이동</li>
<li>아래로 이동</li>
</ul>
</li>
<li><p>범위 안이고 아직 방문하지 않은 경우만 큐에 추가</p>
</li>
</ul>
<p><strong>4. 목표 G에 도달하면 종료</strong></p>
<p><strong>5. dist[G]가 -1이면 도달 불가</strong></p>
<hr>
<h1 id="최종-코드">최종 코드</h1>
<pre><code class="language-python">from collections import deque

F, S, G, U, D = map(int, input().split())

dist = [-1] * (F + 1)
dist[S] = 0

queue = deque([S])

while queue:
    current = queue.popleft()

    if current == G:
        break

    for moved in (current + U, current - D):
        if 1 &lt;= moved &lt;= F and dist[moved] == -1:
            dist[moved] = dist[current] + 1
            queue.append(moved)

if dist[G] == -1:
    print(&quot;use the stairs&quot;)
else:
    print(dist[G])</code></pre>
<hr>
<h1 id="배운-점-및-코테-팁">배운 점 및 코테 팁</h1>
<p><strong>1. BFS에서는 재방문 갱신이 필요 없다</strong></p>
<ul>
<li>처음 방문이 최단 거리</li>
<li><code>min()</code> 갱신 로직 불필요</li>
</ul>
<p><strong>2. 방문 체크는 큐에 넣기 전에 한다</strong></p>
<ul>
<li>안 하면 무한 반복</li>
<li>시간 초과 / SIGTERM 발생</li>
</ul>
<p><strong>3. 거리 배열은 -1로 초기화하는 것이 안전하다</strong></p>
<ul>
<li>0 초기화는 시작점과 구분이 안 됨</li>
</ul>
<p><strong>4. 판단 기준 정리</strong></p>
<ul>
<li>최소 횟수 → BFS</li>
<li>이동 비용 동일 → BFS</li>
<li>상태 전이 명확 → BFS</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준/Python] 7569. 토마토 (BFS) 🥇5️⃣]]></title>
            <link>https://velog.io/@from-minju/%EB%B0%B1%EC%A4%80Python-7569.-%ED%86%A0%EB%A7%88%ED%86%A0-BFS</link>
            <guid>https://velog.io/@from-minju/%EB%B0%B1%EC%A4%80Python-7569.-%ED%86%A0%EB%A7%88%ED%86%A0-BFS</guid>
            <pubDate>Fri, 27 Mar 2026 17:09:43 GMT</pubDate>
            <description><![CDATA[<h1 id="baekjoonpython-7569-토마토-bfs"><a href="https://www.acmicpc.net/problem/7569">[Baekjoon/Python] 7569. 토마토 (BFS)</a></h1>
<hr>
<h1 id="🍅-문제">🍅 문제</h1>
<p>3차원 상자에 토마토가 저장되어 있다.</p>
<ul>
<li>익은 토마토: 1</li>
<li>익지 않은 토마토: 0</li>
<li>빈 칸: -1</li>
</ul>
<p>익은 토마토는 하루가 지나면 상하좌우뿐만 아니라 위아래 방향까지 포함한 총 6방향으로 인접한 토마토를 익게 만든다.</p>
<p>모든 토마토가 익는데 걸리는 최소 일수를 구해야 하며,
모든 토마토가 익지 못하는 경우에는 -1을 출력한다.</p>
<hr>
<h1 id="풀이-과정">풀이 과정</h1>
<p><strong>1) 문제 접근</strong>
이 문제는 단순 탐색 문제가 아니라 “최소 일수”를 구하는 문제이다.
또한 토마토가 동시에 퍼지기 때문에 순차 탐색이 아닌 BFS를 사용해야 한다.</p>
<p>특히 처음부터 익은 토마토가 여러 개 존재하므로, 모든 시작점을 큐에 넣는 <strong>다중 시작점 BFS</strong>가 필요하다.</p>
<br>

<p><strong>2) BFS를 사용해야 하는 이유</strong>
DFS는 한 방향으로 깊게 탐색하기 때문에 최소 거리를 보장하지 못한다.
반면 BFS는 레벨 단위로 확장되므로, 각 토마토가 익는 시점이 곧 최소 일수가 된다.</p>
<br>

<p><strong>3) 구현 핵심</strong></p>
<ul>
<li>처음 입력을 받을 때 익은 토마토(1)의 위치를 모두 큐에 넣는다.</li>
<li>BFS를 진행하면서 인접한 토마토를 익힌다.</li>
<li>이때, 별도의 날짜 배열 없이 graph에 값을 누적한다.</li>
</ul>
<hr>
<h1 id="🔥-나의-시행착오">🔥 나의 시행착오</h1>
<p><strong>1) 모든 좌표를 순회하며 처리하려 했던 문제</strong></p>
<p>처음에는 3차원 배열을 모두 순회하면서 주변을 익히는 방식으로 접근하려 했다.</p>
<p>하지만 이 방법은 동시에 퍼지는 개념을 반영하지 못하고,
이미 같은 날 익어야 하는 토마토까지 순차적으로 처리되는 문제가 발생한다.</p>
<p>이 문제의 핵심은 “전파 + 최소 시간”이다.
이 조합은 <strong>BFS</strong>로 해결해야 한다.</p>
<br>

<p><strong>2) days 변수로 최적화하려던 시도</strong></p>
<p>전체 최댓값을 구하는 과정이 비효율적일 것 같아 days 변수를 따로 두고 graph의 값을 업데이트 할때마다 max()로 최댓값을 저장하려 했다.</p>
<p>하지만 실제로는 마지막에 한 번 전체를 순회하는 것은 시간복잡도에 큰 영향을 주지 않으며,
오히려 코드 복잡도와 실수 가능성만 증가시키는 요소였다.</p>
<hr>
<h1 id="최종-알고리즘">최종 알고리즘</h1>
<p><strong>1) 입력을 받으며 초기 설정</strong>
익은 토마토의 위치를 모두 큐에 넣는다</p>
<p><strong>2) BFS 수행</strong></p>
<ul>
<li>큐에서 하나씩 꺼낸다</li>
<li>6방향 탐색을 수행한다</li>
<li>익지 않은 토마토(0)를 만나면
→ 현재 값 + 1로 갱신
→ 큐에 추가</li>
</ul>
<p><strong>3) 결과 처리</strong></p>
<ul>
<li>BFS 이후에도 0이 존재하면 → -1</li>
<li>그렇지 않으면 → 전체 최댓값 - 1 출력</li>
</ul>
<hr>
<h1 id="💻-최종-코드">💻 최종 코드</h1>
<pre><code class="language-python">from collections import deque
import sys

input = sys.stdin.readline

M, N, H = map(int, input().split())

graph = []
queue = deque()

for h in range(H):
    box = []
    for n in range(N):
        row = list(map(int, input().split()))
        box.append(row)

        for m in range(M):
            if row[m] == 1:
                queue.append((h, n, m))
    graph.append(box)

# 6방향 (상, 하, 좌, 우, 위, 아래)
dh = [0, 0, 0, 0, -1, 1]
dn = [-1, 1, 0, 0, 0, 0]
dm = [0, 0, -1, 1, 0, 0]

while queue:
    h, n, m = queue.popleft()

    for i in range(6):
        nh = h + dh[i]
        nn = n + dn[i]
        nm = m + dm[i]

        if 0 &lt;= nh &lt; H and 0 &lt;= nn &lt; N and 0 &lt;= nm &lt; M:
            if graph[nh][nn][nm] == 0:
                graph[nh][nn][nm] = graph[h][n][m] + 1
                queue.append((nh, nn, nm))

answer = 0

for h in range(H):
    for n in range(N):
        for m in range(M):
            if graph[h][n][m] == 0:
                print(-1)
                sys.exit()
            answer = max(answer, graph[h][n][m])

print(answer - 1)</code></pre>
<hr>
<h1 id="📚-배운점">📚 배운점</h1>
<p><strong>1) 전파 + 최소 시간 → BFS로 접근한다</strong> 
이 문제처럼 “퍼진다” + “최소 일수”가 같이 나오면 BFS를 먼저 떠올리는 것이 중요하다.</p>
<p><strong>2) 다중 시작점 BFS를 기억하자</strong> 
시작점이 여러 개인 경우, 처음부터 큐에 모두 넣고 시작해야 한다.</p>
<p><strong>3) graph를 거리 배열로 활용할 수 있다</strong> 
별도의 distance 배열 없이 값 자체를 증가시키는 방식으로 해결 가능하다.</p>
<p><strong>4) 불필요한 최적화는 오히려 독이 된다</strong>
전체를 한 번 더 순회하는 비용보다, 코드 안정성과 정확성이 더 중요하다.
전체를 한 번 더 순회하는 것은 시간복잡도에 큰 영향을 미치지 않는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준/Python] 2644. 촌수 계산 (DFS/BFS)]]></title>
            <link>https://velog.io/@from-minju/%EB%B0%B1%EC%A4%80Python-2644.-%EC%B4%8C%EC%88%98-%EA%B3%84%EC%82%B0-DFSBFS</link>
            <guid>https://velog.io/@from-minju/%EB%B0%B1%EC%A4%80Python-2644.-%EC%B4%8C%EC%88%98-%EA%B3%84%EC%82%B0-DFSBFS</guid>
            <pubDate>Fri, 27 Mar 2026 09:05:07 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p>두 사람의 관계가 주어졌을 때,
이 둘 사이의 촌수(거리)를 계산하는 문제이다.</p>
<p>부모-자식 관계가 주어짐
두 사람 사이의 촌수를 구하기
연결이 안 되어 있으면 -1</p>
<h1 id="💡-문제-접근--dfs인가-bfs인가">💡 문제 접근 : DFS인가 BFS인가?</h1>
<p>👉 <strong>사람 간 관계 = 그래프</strong>
👉 부모-자식 관계 → <strong>양방향 간선</strong></p>
<p><strong>👉 “촌수” = 두 사람 사이의 거리 (간선 개수)</strong>
👉 두 노드 사이 거리 찾기 문제</p>
<p>이 문제를 처음봤을때 “최소 거리(촌수)를 찾아야 하니까 BFS?”라고 생각했다.
DFS를 사용한다 하더라도 모든 경로 탐색 + 최솟값 갱신 필요하다고 생각했다.</p>
<p>하지만 실제로는 BFS, DFS로도 충분히 해결 가능하다.
이 문제의 관계는 단순한 그래프가 아니라 <strong>트리(Tree) 구조</strong>로 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/from-minju/post/3cc3f12c-ba02-45a9-9055-cbf05da9eda8/image.png" alt="">
<strong>부모 - 자식 관계는 트리 구조</strong>이다.
즉, *<em>start → end로 가는 경로가 딱 하나인 것이다. *</em></p>
<blockquote>
<p><strong>트리의 특징</strong></p>
</blockquote>
<ul>
<li>사이클이 없음</li>
<li>두 노드 사이 경로는 항상 하나</li>
</ul>
<p>따라서 이 문제는 BFS, DFS 모두 가능하며, DFS로 푼다고 하더라도 굳이 최솟값 갱신하는 과정이 필요없다.</p>
<hr>
<h1 id="bfs-풀이">BFS 풀이</h1>
<pre><code class="language-python">from collections import deque

n = int(input())
start, end = map(int, input().split())
m = int(input())

graph = [[] for _ in range(n+1)]
visited = [0] * (n+1) 

for _ in range(m):
    a, b = map(int, input().split())
    graph[a].append(b)
    graph[b].append(a)

def bfs(v):
    queue = deque()
    queue.append(v)
    visited[v] += 1

    while queue:
        now = queue.popleft()
        if now == end:
            return visited[now] - 1
        for next in graph[now]:
            if not visited[next] :
                visited[next] = visited[now] + 1
                queue.append(next)

    return -1

print(bfs(start))</code></pre>
<hr>
<h1 id="dfs-풀이">DFS 풀이</h1>
<pre><code class="language-python">from collections import deque

n = int(input())
start, end = map(int, input().split())
m = int(input())

graph = [[] for _ in range(n+1)]
visited = [False] * (n+1) 
result = -1

for _ in range(m):
    a, b = map(int, input().split())
    graph[a].append(b)
    graph[b].append(a)

def dfs(v, depth):
    global result

    if v == end:
        result = depth
        return 

    visited[v] = True

    for next in graph[v]:
        if not visited[next]:
            dfs(next, depth + 1)

dfs(start, 0)

print(result)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Baekjoon/Python] 2667. 단지번호붙이기 (BFS/DFS)]]></title>
            <link>https://velog.io/@from-minju/BaekjoonPython-2667.-%EB%8B%A8%EC%A7%80%EB%B2%88%ED%98%B8%EB%B6%99%EC%9D%B4%EA%B8%B0-BFSDFS</link>
            <guid>https://velog.io/@from-minju/BaekjoonPython-2667.-%EB%8B%A8%EC%A7%80%EB%B2%88%ED%98%B8%EB%B6%99%EC%9D%B4%EA%B8%B0-BFSDFS</guid>
            <pubDate>Wed, 25 Mar 2026 21:08:21 GMT</pubDate>
            <description><![CDATA[<h1 id="baekjoonpython-2667-단지번호붙이기-bfsdfs">[Baekjoon/Python] 2667. 단지번호붙이기 (BFS/DFS)</h1>
<hr>
<h1 id="문제">문제</h1>
<p>NxN 크기의 지도에서 <code>1</code>은 집이 있는 곳, <code>0</code>은 집이 없는 곳을 의미한다.
상하좌우로 연결된 집들을 하나의 단지로 정의할 때,</p>
<ul>
<li>총 단지 수</li>
<li>각 단지의 집 개수 (오름차순)</li>
</ul>
<p>를 구하는 문제이다.</p>
<hr>
<h1 id="요구사항-분석">요구사항 분석</h1>
<p><strong>1. 그래프 탐색 문제로 해석</strong></p>
<p>2차원 배열은 그래프로 볼 수 있으며, 각 칸은 노드가 된다.
상하좌우 이동은 인접 노드 탐색에 해당한다.</p>
<p><strong>2. 연결 요소 문제</strong></p>
<p>연결된 <code>1</code>들의 묶음은 하나의 단지이다.
즉, 이 문제는 “연결 요소의 개수와 크기”를 구하는 문제이다.</p>
<hr>
<h1 id="나의-풀이">나의 풀이</h1>
<p><strong>1. BFS를 이용한 단지 탐색</strong></p>
<ul>
<li><code>1</code>을 발견하면 BFS 시작</li>
<li>연결된 모든 <code>1</code>을 탐색하며 <code>0</code>으로 변경 (방문 처리)</li>
<li>탐색하면서 집 개수를 세어 단지 크기로 저장</li>
</ul>
<pre><code class="language-python">from collections import deque

N = int(input())

graph = []
houses = []

for i in range(N):
    graph.append(list(map(int, input())))

def bfs(a, b):
    queue = deque()
    queue.append((a, b))
    graph[a][b] = 0
    house = 1

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

    while queue:
        x, y = queue.pop()

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

            if 0 &lt;= tx &lt; N and 0 &lt;= ty &lt; N and graph[tx][ty] == 1:
                queue.append((tx, ty))
                graph[tx][ty] = 0
                house += 1

    return house


for i in range(N):
    for j in range(N):
        if graph[i][j] == 1:
            houses.append(bfs(i, j))

print(len(houses))
houses.sort()
for house in houses:
    print(house)</code></pre>
<hr>
<h1 id="코드-피드백">코드 피드백</h1>
<p><strong>1. BFS에서 pop() 사용</strong></p>
<pre><code class="language-python">x, y = queue.pop()</code></pre>
<p>이 방식은 DFS처럼 동작한다.
BFS를 정확하게 구현하려면 다음과 같이 작성해야 한다.</p>
<pre><code class="language-python">x, y = queue.popleft()</code></pre>
<p>문제는 풀리지만, 탐색 방식의 의미를 맞추는 것이 중요하다.</p>
<hr>
<h1 id="정석-코드-bfs--dfs">정석 코드 (BFS + DFS)</h1>
<p><strong>1. BFS 풀이</strong></p>
<pre><code class="language-python">from collections import deque

N = int(input())
graph = [list(map(int, input().strip())) for _ in range(N)]

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

def bfs(a, b):
    queue = deque()
    queue.append((a, b))
    graph[a][b] = 0
    count = 1

    while queue:
        x, y = queue.popleft()

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

            if 0 &lt;= nx &lt; N and 0 &lt;= ny &lt; N and graph[nx][ny] == 1:
                queue.append((nx, ny))
                graph[nx][ny] = 0
                count += 1

    return count</code></pre>
<p><strong>2. DFS 풀이</strong></p>
<pre><code class="language-python">def dfs(x, y):
    graph[x][y] = 0
    count = 1

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

        if 0 &lt;= nx &lt; N and 0 &lt;= ny &lt; N and graph[nx][ny] == 1:
            count += dfs(nx, ny)

    return count</code></pre>
<p><strong>3. 실행 코드</strong></p>
<pre><code class="language-python">houses = []

for i in range(N):
    for j in range(N):
        if graph[i][j] == 1:
            houses.append(bfs(i, j))  # dfs(i, j)로 변경 가능

houses.sort()

print(len(houses))
for house in houses:
    print(house)</code></pre>
<hr>
<h1 id="풀이-팁-및-정리">풀이 팁 및 정리</h1>
<p><strong>1. 연결 요소 문제 패턴</strong></p>
<ul>
<li><code>1</code> 발견 → 탐색 시작</li>
<li>연결된 모든 <code>1</code> 방문 처리</li>
<li>개수 카운트</li>
</ul>
<p>이 패턴은 매우 자주 등장하므로 익혀두는 것이 중요하다.
</br></p>
<p><strong>2. 방문 처리 방법</strong></p>
<ul>
<li>visited 배열 사용</li>
<li>graph 값 직접 변경</li>
</ul>
<p>이 문제에서는 <code>1 → 0</code>으로 변경하는 방식이 더 간단하다.
</br></p>
<p><strong>3. BFS vs DFS 차이</strong></p>
<ul>
<li>BFS → <code>queue.popleft()</code></li>
<li>DFS → 재귀 또는 stack</li>
</ul>
<p>이 문제는 둘 다 가능하지만 구현 방식은 구분해야 한다.
</br></p>
<p><strong>4. map 함수 활용</strong></p>
<pre><code class="language-python">list(map(int, input()))</code></pre>
<p>문자열을 한 글자씩 나누어 정수로 변환한다.</p>
<p>예: &quot;10101&quot; → [1, 0, 1, 0, 1]</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>이 문제는 그래프 탐색의 기본인 연결 요소 개념을 연습하기에 적합한 문제이다.
BFS와 DFS 구조, 방문 처리 방식을 확실히 이해하고 넘어가는 것이 중요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CloudFront는 동적 데이터를 어떻게 처리하는가?]]></title>
            <link>https://velog.io/@from-minju/CloudFront%EB%8A%94-%EB%8F%99%EC%A0%81-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@from-minju/CloudFront%EB%8A%94-%EB%8F%99%EC%A0%81-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Tue, 10 Feb 2026 16:27:43 GMT</pubDate>
            <description><![CDATA[<h2 id="아니-cdn은-캐싱인데-동적인-걸-어떻게-처리해"><strong>“아니 CDN은 캐싱인데, 동적인 걸 어떻게 처리해?”</strong></h2>
<p>CloudFront는 정적데이터 동적데이터 모두 처리 가능하다.
CDN이면 &#39;캐시서버&#39;아닌가? 그럼 정적 파일만 해당하는거 아닌가?</p>
<p>로그인 API, 게시글 조회 같은 동적 요청은 캐싱도 못 하는데
그럼 CloudFront를 왜 쓰는 거지?</p>
<p>결론부터 말하자면,</p>
<blockquote>
<p><strong>CloudFront는 동적 데이터를 캐싱하지 않는다.
대신 동적 데이터를 가장 빠른 네트워크 경로로 전달해준다.</strong></p>
</blockquote>
<hr>
<h2 id="cloudfront의-두-가지-역할">CloudFront의 두 가지 역할</h2>
<p>CloudFront는 단순한 “캐시 서버”가 아니다.
실제로는 두 가지 모드로 동작한다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Cache 모드</strong></td>
<td>정적 파일을 Edge Location에 저장</td>
</tr>
<tr>
<td><strong>Proxy 모드</strong></td>
<td>동적 요청을 가장 빠른 경로로 Origin까지 전달</td>
</tr>
</tbody></table>
<p>👉 <strong>동적 콘텐츠는 Cache 모드가 아니라 Proxy 모드로 처리된다.</strong></p>
<hr>
<h2 id="동적-요청은-실제로-어떻게-흐를까">동적 요청은 실제로 어떻게 흐를까?</h2>
<p>예를 들어, 사용자가 아래 API를 호출한다고 해보자.</p>
<pre><code>https://www.example.com/api/posts</code></pre><h3 id="cloudfront-없이-요청할-경우">CloudFront 없이 요청할 경우</h3>
<pre><code>한국 → 미국 리전 ALB → EC2 → DB → 다시 한국</code></pre><ul>
<li>요청과 응답이 공용 인터넷을 왕복</li>
<li>물리적 거리 + 네트워크 혼잡</li>
<li>지연 시간(latency) 증가, 변동성 큼</li>
</ul>
<h3 id="cloudfront를-사용하는-경우-동적-요청">CloudFront를 사용하는 경우 (동적 요청)</h3>
<pre><code>사용자
 → 가장 가까운 CloudFront Edge
 → AWS 전용 글로벌 네트워크
 → Origin(ALB)</code></pre><p>사용자와 먼 서버 사이를 이미 뚫려있는 전용 고속도로를 이용해 빠르게 접근할 수 있다. </p>
<p>📌 이 방식을 <strong>Dynamic Content Acceleration</strong>이라고 부른다.</p>
<hr>
<h2 id="dynamic-content-acceleration-의-핵심-원리">Dynamic Content Acceleration 의 핵심 원리</h2>
<h3 id="1️⃣-tcpip-핸드셰이크-최적화--edge에서-연결을-끝낸다-edge-connection-termination">1️⃣ TCP/IP 핸드셰이크 최적화 : Edge에서 연결을 끝낸다 (Edge Connection Termination)</h3>
<p>가장 큰 차이는 <strong>TCP/IP 핸드셰이크가 어디서 일어나느냐</strong>다.</p>
<ul>
<li><p><strong>CloudFront 미사용</strong></p>
<blockquote>
<p>사용자 ↔ 멀리 있는 원본 서버
TCP 핸드셰이크 + TLS 설정을 <strong>먼 거리에서 수행</strong></p>
</blockquote>
<p>사용자가 전 세계 어디에 있든 멀리 떨어진 원본 서버(서울, 미국 등)와 직접 &quot;연결할 준비가 됐니?&quot;(TCP Handshake)라며 서너 번의 신호를 주고받아야 합니다. 거리가 멀수록 이 과정에서 시간이 많이 걸립니다.</p>
</li>
<li><p><strong>CloudFront 사용</strong></p>
<blockquote>
<p>사용자 ↔ <strong>가장 가까운 Edge Location</strong>
 물리적 거리가 짧아 초기 연결이 매우 빠름</p>
</blockquote>
<p>사용자는 가장 가까운 곳에 있는 CloudFront 엣지 로케이션과 즉시 연결을 맺습니다. 엣지까지의 거리는 매우 가깝기 때문에 초기 연결 속도가 압도적으로 빠릅니다. 그 이후 엣지와 원본 서버 사이는 이미 연결이 뚫려 있는 &#39;고속도로&#39;를 이용하게 됩니다.</p>
</li>
</ul>
<p>👉 <strong>즉, 사용자는 가까운 엣지 로케이션과 핸드셰이크(연결)하면된다. 
엣지 로케이션은 멀리떨어진 원본서버와 미리 연결되어있는 상태다. (이미 뚫려있는 ‘고속도로’)</strong></p>
<hr>
<h3 id="2️⃣-edge와-origin은-이미-연결돼-있다-keep-alive--connection-pooling">2️⃣ Edge와 Origin은 이미 연결돼 있다 (Keep-Alive &amp; Connection Pooling)</h3>
<p>이 부분이 <strong>성능 향상의 숨은 주역</strong>이다.</p>
<ul>
<li><p><strong>일반적인 서버 통신</strong></p>
<blockquote>
<p>요청마다 연결 생성 → 종료 반복</p>
</blockquote>
<p>일반적으로 서버와 통신할 때는 데이터를 주고받을 때마다 연결을 맺고 끊는 과정이 반복됩니다.</p>
</li>
<li><p><strong>CloudFront</strong></p>
<blockquote>
<p>Edge ↔ Origin(ALB/EC2) 사이에
  <strong>이미 수많은 연결을 미리 유지</strong></p>
</blockquote>
<p>하지만 CloudFront 엣지 서버는 원본 서버(ALB/EC2)와 <strong>이미 수많은 연결을 미리 맺어놓고 유지(Connection Pooling)</strong>하고 있습니다.
사용자의 요청이 들어오면, 새로 연결을 만드는 시간을 낭비하지 않고 <strong>이미 열려 있는 통로에 데이터를 바로 실어 보냅니다.</strong> 이로 인해 왕복 시간(RTT)이 획기적으로 줄어듭니다.</p>
</li>
</ul>
<p>👉 <strong>새로 연결을 만드는 시간이 0이다. 이미 뚫려 있는 통로에 요청을 바로 실어 보낸다</strong></p>
<hr>
<h3 id="3️⃣-aws-전용-글로벌-네트워크를-탄다">3️⃣ AWS 전용 글로벌 네트워크를 탄다</h3>
<p>데이터가 이동하는 <strong>“길” 자체가 다르다.</strong></p>
<ul>
<li><p>공용 인터넷</p>
<ul>
<li>ISP 여러 곳을 경유</li>
<li>병목, 우회, 품질 변동 발생</li>
</ul>
<p>수많은 통신사(ISP)를 거치며 복잡하게 얽혀 있습니다. 트래픽이 몰리는 구간에서 병목 현상이 발생하거나 경로가 길어질 수 있습니다.</p>
</li>
<li><p>AWS 전용 네트워크</p>
<ul>
<li>CloudFront Edge → Origin까지
<strong>AWS 글로벌 광섬유 백본망</strong></li>
</ul>
<p>CloudFront 엣지에서 원본 서버까지는 전 세계에 깔린 AWS 전용 광섬유 네트워크를 통해 이동합니다. 전용 고속도로를 타는 것과 같아서 지연 시간(Latency)이 일정하고, 공용 인터넷보다 훨씬 안전하고 빠릅니다.</p>
</li>
</ul>
<p>👉 전용 고속도로를 타는 것과 같다.</p>
<hr>
<h3 id="4️⃣-실시간-네트워크-경로-최적화">4️⃣ 실시간 네트워크 경로 최적화</h3>
<p>CloudFront는 네트워크 상태를 <strong>실시간으로 감시</strong>한다.</p>
<ul>
<li>특정 구간이 느려지면</li>
<li>자동으로 더 빠른 우회 경로 선택</li>
</ul>
<p>개발자가 직접 제어하기 어려운 영역을
AWS가 <strong>인프라 레벨에서 대신 처리</strong>해준다.</p>
<hr>
<h2 id="그래서-동적-데이터에-왜-유리할까">그래서 동적 데이터에 왜 유리할까?</h2>
<p>동적 데이터는 <strong>반드시 Origin까지 가야 한다.</strong>
캐싱이 안 되기 때문이다.</p>
<p>그럼에도 CloudFront가 효과적인 이유는 명확하다.</p>
<h3 id="✔-연결-시간-자체를-줄인다">✔ 연결 시간 자체를 줄인다</h3>
<ul>
<li>TCP 3-way handshake</li>
<li>TLS handshake
→ 이 왕복 과정을 <strong>Edge에서 끝내버린다</strong></li>
</ul>
<h3 id="✔-이미-워밍업된-연결을-재사용한다">✔ 이미 워밍업된 연결을 재사용한다</h3>
<ul>
<li>TCP Slow Start 문제 없음</li>
<li>처음부터 높은 전송 속도 유지</li>
</ul>
<h3 id="✔-결과">✔ 결과</h3>
<blockquote>
<p><strong>“동적 데이터라도
체감 응답 속도는 확실히 빨라진다.”</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[EBS, EFS, Snapshot, Instance Store, AMI]]></title>
            <link>https://velog.io/@from-minju/EBS-Instance-Store</link>
            <guid>https://velog.io/@from-minju/EBS-Instance-Store</guid>
            <pubDate>Sun, 11 Jan 2026 12:01:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>💫 <strong>참고</strong></p>
</blockquote>
<ul>
<li><a href="https://velog.io/@gagaeun/AWS-EC2-Instance-Storage-Section">https://velog.io/@gagaeun/AWS-EC2-Instance-Storage-Section</a></li>
<li><a href="https://velog.io/@gagaeun/AWS-EC2-Instance-Storage-Section">Udemy Ultimate AWS Certified Solutions Architect Associate (SAA)</a></li>
</ul>
<hr>
<h1 id="ebs">EBS</h1>
<blockquote>
<p>💡 <strong>Volume 볼륨</strong>
EC2 인스턴스에 연결되어 데이터를 저장하는 블록스토리지 
= EC2가 사용하는 하드디스크</p>
</blockquote>
<blockquote>
<p>💡 <strong>EBS (Elastic Block Store)</strong>
네트워크로 연결된 볼륨</p>
</blockquote>
<ul>
<li>인스턴스와 EBS가 서로 같은 가용영역(AZ)에 있어야 연결가능하다.<ul>
<li>us-east-1a에서 생성된 경우 us-east-1b에는 연결이 불가능하다.</li>
</ul>
</li>
<li>인스턴스가 종료된 후에도 데이터를 지속적으로 유지할 수 있도록 하기위함이다.</li>
<li>CCP 레벨에서 <strong>한 번에 하나의 인스턴스에만 마운트될 수 있다</strong>.<ul>
<li>CCP 레벨: 하나의 EBS는 하나의 EC2 인스턴스에만 마운트 가능 (하나의 EBS에 여러 인스턴스 불가능)</li>
<li>Associate 레벨: 일부 EBS 다중 연결 (e.g. 하나의 EBS에 2개의 인스턴스 마운트 가능)</li>
</ul>
</li>
<li>네트워크 USB스틱이라고 생각하면 쉽다. 물리적으로 연결되는건 아니지만 네트워크를 통해 연결된다.</li>
<li>미리 용량(size), IOPS 등을 결정해야 한다.</li>
<li>인스턴스에 연결하지 않은 상태로 그냥 두어도 된다.</li>
<li>인스턴스 종료 시 자동삭제 여부를 설정할 수 있다. (디폴트 : 루트볼륨은 자동삭제, 다른 볼륨은 삭제되지 않음.)</li>
</ul>
<blockquote>
<p>💡 <strong>루트 EBS 볼륨</strong>
인스턴스를 생성하면 기본적으로 생성되는 볼륨으로, 인스턴스가 부팅할 때 사용하는 운영체제가 설치된 EBS 볼륨이다. 
= EC2의 C드라이브</p>
</blockquote>
<ul>
<li>인스턴스 종료시 루트볼륨은 자동으로 삭제되는것이 디폴트이다.</li>
<li>하나의 인스턴스에 루트볼륨 외에 다른 볼륨도 부착할 수 있다.</li>
</ul>
<hr>
<h1 id="ebs-snapshot">EBS Snapshot</h1>
<blockquote>
<p>💡 <strong>EBS Snapshot</strong>
EBS 볼륨의 특정시점에 대한 백업이다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/from-minju/post/48a5bf3b-511a-4304-b6f6-dea15a8673dd/image.png" alt=""></p>
<ul>
<li>EBS볼륨과 다르게 서로 다른 AZ, 리전에도 복사가 가능하다.</li>
</ul>
<h2 id="ebs-sanpshots-features"><strong>EBS Sanpshots Features</strong></h2>
<ul>
<li><strong>EBS Snapshot Archive</strong><ul>
<li>아카이브 티어로 백업을 옮긴다.</li>
<li>즉시 복원되지 않는다. 24-72시간이 걸리지만 75% 더 저렴하다.</li>
<li>적합한 경우 : 장기보관목적으로 사용된다.</li>
</ul>
</li>
<li><strong>Recycle Bin for EBS Snapshots</strong><ul>
<li>스냅샷을 삭제하면 바로 영구삭제되지 않고 휴지통에 넣어진다. 복구 가능</li>
<li>디폴트는 바로 삭제되지만, 휴지통기능을 따로 설정해주면 실수로 삭제하는 경우를 대비할 수 있다.</li>
<li>삭제 후 유지할 스냅샷 보존 기간은 1일부터 1년까지 지정 가능.</li>
</ul>
</li>
<li><strong>Fast Snapshot Restore (FSR)</strong><ul>
<li>스냅샷 → 볼륨으로 복원하는 과정을 빠르게 진행한다.</li>
<li>다만, 비용이 많이 든다.</li>
</ul>
</li>
</ul>
<hr>
<h1 id="ami">AMI</h1>
<blockquote>
<p>💡 <strong>AMI (Amazon Machine Image)</strong>
EC2 인스턴스를 만드는데 필요한 템플릿 이미지
즉, EC2를 만들 때 OS, 애플리케이션, 설정 등을 <strong>복제해서 똑같이 다시 만들 수 있는 스냅샷</strong>.</p>
</blockquote>
<ul>
<li>인스턴스 생성할 때 Amazon Linux, Ubuntu 등 선택하는걸 말함. (또는 내가 직접 커스텀한 이미지 선택가능)</li>
<li>AMI에다 원하는 소프트웨어 또는 설정 파일을 추가하거나 별도의 운영 체제를 설치할 수도 있고 모니터링 툴을 추가할 수도 있다.</li>
<li>AMI를 따로 구성하면(커스터마이징), <strong>부팅 및 설정에 드는 시간을 줄일 수 있다.</strong> (EC2 인스턴스에 설치하고자 하는 모든 소프트웨어를 AMI가 미리 패키징해주니까)</li>
<li>AMI의 종류<ul>
<li><strong>Public AMI</strong> : AWS에서 제공하는</li>
<li><strong>Your own AMI</strong> : 내가 직접 구성한</li>
<li><strong>An AWS Marketplace AMI</strong> : 다른사람이 만든 AMI 가져다</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>AMI</th>
<th>Docker Image</th>
</tr>
</thead>
<tbody><tr>
<td>범위</td>
<td><strong>서버 전체</strong></td>
<td><strong>애플리케이션 단위</strong></td>
</tr>
<tr>
<td>포함 내용</td>
<td>OS + 미들웨어 + 앱</td>
<td>앱 + 런타임</td>
</tr>
<tr>
<td>실행 단위</td>
<td>EC2 인스턴스</td>
<td>컨테이너</td>
</tr>
<tr>
<td>무게</td>
<td>무거움</td>
<td>가벼움</td>
</tr>
<tr>
<td>용도</td>
<td>인프라 복제</td>
<td>앱 배포/이식</td>
</tr>
</tbody></table>
<h3 id="ami-생성-과정">AMI 생성 과정</h3>
<ol>
<li>EC2 인스턴스를 띄움</li>
<li>필요한 설정/애플리케이션 설치</li>
<li>인스턴스 상태가 완성되면</li>
<li>&quot;Create Image&quot; 클릭 or AWS CLI로 AMI 생성</li>
<li>EC2 상태를 기반으로 AMI(스냅샷) 생성됨</li>
<li>나중에 이 AMI로 EC2를 다시 만들면 → <strong>똑같은 상태로 복제됨!</strong></li>
</ol>
<hr>
<h1 id="ec2-인스턴스-스토어">EC2 인스턴스 스토어</h1>
<blockquote>
<p>💡 <strong>EC2 Instance Store</strong>
EC2 인스턴스 물리서버에 물리적으로 직접 연결된 하드웨어 드라이브를 가리킨다.</p>
</blockquote>
<ul>
<li>물리적으로 연결되어있다보니 속도가 빠르다.</li>
<li>I/O 성능향상을 위해 활용된다.</li>
<li>인스턴스 중지, 종료하면 스토리지가 손실된다. (그래서 임시 스토어라고함)</li>
<li>장기적으로 보관할만한 곳이 아님</li>
<li>적합한 경우 : 버퍼,캐시, 임시콘텐츠 사용하는 경우에 적합. (장기는 ebs가 적합)</li>
<li>기본서버에 장애발생하면 여기에도 장애발생하므로 데이터 백업, 복제해둬야함.</li>
</ul>
<hr>
<h1 id="ebs-볼륨-유형">EBS 볼륨 유형</h1>
<p><strong>EBS 볼륨을 정의하는 요소</strong> : 크기, 처리량, IOPS(I/O Ops Per Sec)</p>
<h3 id="general-purpose-ssd"><strong>General Purpose SSD</strong></h3>
<blockquote>
<p><strong>💿 gp2 / gp3 (SSD)</strong></p>
</blockquote>
<ul>
<li><p>범용 SSD 볼륨</p>
</li>
<li><p><strong>효율적인 비용</strong>, <strong>low-latency</strong> 낮은 지연시간</p>
</li>
<li><p>gp2보다 gp3가 최신세대 볼륨</p>
</li>
<li><p>gp2 : 볼륨크기에 따라 IOPS가 비례해서 증가한다. (i.e. 볼륨의 GB수를 늘리면 IOP도 늘어남)</p>
</li>
<li><p>gp3 : 최신 표준, IOPS와 처리량을 독립적으로 설정할 수 있다. 대부분의 워크로드에 추천.</p>
</li>
</ul>
<h3 id="provisioned-iops-piops-ssd"><strong>Provisioned IOPS (PIOPS) SSD</strong></h3>
<blockquote>
<p><strong>💿 io1 / io2 (SSD)
가장 높은 성능</strong>의 SSD 볼륨, <strong>IOPS</strong>를 매우 높고 안정적으로 보장한다.</p>
</blockquote>
<ul>
<li>적합한 곳 : 미션 크리티컬, 저지연, 고처리량 작업, 금융, 대규모 트랜잭션에 사용</li>
<li>IOP 16,000 이상 필요한 경우에 적합</li>
</ul>
<h3 id="hard-disk-drives-hdd"><strong>Hard Disk Drives (HDD)</strong></h3>
<ul>
<li><p><strong>Throughput Optimized HDD (처리량 중심 HDD)</strong></p>
<blockquote>
<p><strong>💿 st1 (HDD)</strong> 
  저비용 HDD 볼륨. <strong>자주 액세스되는 처리량 중심 작업</strong>을 위해 설계됨.</p>
</blockquote>
<ul>
<li>적합한 곳 : 로그 처리, 빅데이터, 스트리밍 워크로드</li>
</ul>
</li>
<li><p><strong>Cold HDD (저비용 HDD)</strong></p>
<blockquote>
<p><strong>💿 sc1 (HDD) 
  가장 저렴한</strong> HDD 볼륨. <strong>접근빈도가 낮은 작업</strong>을 위해 설계됨.</p>
</blockquote>
<ul>
<li>적합한 곳 : 백업 데이터, 장기 보관용 데이터</li>
<li>Cold = 자주 안 쓰는 데이터 = 접근빈도 낮으므로 느려도 된다. = 싼 게 최고(저비용)</li>
</ul>
</li>
<li><p>부팅볼륨으로 사용할 수 없다. (OS의 루트가 실행되는 위치)</p>
</li>
</ul>
<table>
<thead>
<tr>
<th>이름</th>
<th>풀네임</th>
<th>키워드</th>
<th>용도</th>
<th>약자</th>
</tr>
</thead>
<tbody><tr>
<td><code>st1</code></td>
<td>Throughput Optimized HDD</td>
<td>처리량</td>
<td>로그, 빅데이터</td>
<td>Sequential Throughput 1</td>
</tr>
<tr>
<td><code>sc1</code></td>
<td>Cold HDD</td>
<td>저비용</td>
<td>백업, 보관</td>
<td>Sequential Cold 1</td>
</tr>
</tbody></table>
</br>

<h3 id="✏️-시험-대비용-한-줄-요약">✏️ 시험 대비용 한 줄 요약</h3>
<blockquote>
<p><strong>데이터베이스</strong>가 필요한 경우 <strong>범용 SSD와 프로비저닝된 IOP SSD</strong>이고,
<strong>높은 처리량과 가장 낮은 비용</strong>이 필요하다면 <strong>ST1 또는 SC1</strong>이다.</p>
</blockquote>
<ul>
<li><strong>범용 → gp3</strong></li>
<li><strong>DB 고성능(32,000 이상의 IOP) → io2</strong> (또는 io1)</li>
<li><strong>로그/빅데이터 → st1</strong></li>
<li><strong>백업/저장 → sc1</strong></li>
</ul>
<hr>
<h1 id="ebs-다중-연결">EBS 다중 연결</h1>
<blockquote>
<p><strong>💡 EBS Multi-Attach (다중연결)</strong>
동일한 EBS 볼륨을 동일한 AZ에 있는 다수의 EC2 인스턴스에 연결</p>
</blockquote>
<ul>
<li>io1 / io2 family</li>
<li>애플리케이션이 동시 쓰기작업할때 사용한다.</li>
<li>한번에 16개 인스턴스까지 연결가능하다.</li>
<li>클러스터 인식 파일시스템을 사용해야한다.</li>
</ul>
<h1 id="ebs-암호화">EBS 암호화</h1>
<ul>
<li>백그라운드에서 처리된다.</li>
<li>지연시간에는 거의 영향이 없다.</li>
</ul>
<h3 id="encrypt-an-unencrypted-ebs-volume"><strong>Encrypt an unencrypted EBS volume</strong></h3>
<blockquote>
<p><strong>암호화되지않은 볼륨 암호화하기</strong>
→ 스냅샷을 복사해 암호화한 후, 암호화된 스냅샷을 통해 볼륨을 생성하면 된다. (자동으로 볼륨도 암호화됨)</p>
</blockquote>
<ol>
<li>볼륨의 EBS Snapshot 생성</li>
<li>복사기능을 통해 EBS Snapshot 암호화한다. </li>
<li>스냅샷을 이용해 새 EBS 볼륨을 생성하면 해당 볼륨도 암호화된다.</li>
<li>암호화된 볼륨을 인스턴스 원본에 연결한다. </li>
</ol>
<ul>
<li>암호화되지 않은 EBS 볼륨에서 생성한 스냅샷은 암호화되지 않는다.</li>
<li>Actions &gt; Copy snapshot (encrypt 옵션체크하기) &gt; 스냅샷A 완성.</li>
<li>스냅샷A에서 create volume from snapshot &gt; 자동으로 encryption 체크되어있음.</li>
<li>암호화되지않은 스냅샷에서 바로 암호화 활성화 체크해서 볼륨생성도 가능하다.</li>
</ul>
<hr>
<h1 id="amazon-efs">Amazon EFS</h1>
<blockquote>
<p>💡 EFS (Elastic File System)
여러 EC2가 동시에 접근가능한 네트워크 파일 시스템(NFS)으로 EC2 인스턴스가 서로 다른 AZ에 있어도 가능하다.</p>
</blockquote>
<ul>
<li>확장성 O, 가용성 O</li>
<li>단, 윈도우 안됨. 리눅스 기반 AMI와만 호환된다.</li>
<li>그만큼 비싸다. (EBS gp2의 3배)</li>
<li>사용량에 따라 비용을지불하므로 미리 용량에 대해 프로비저닝할 필요가 없다. (용량 미리 계획할 필요 없다는 뜻) 자동으로 확장되며, GB 사용량에 따라 비용을 지불한다.</li>
<li>보안그룹 설정 필요</li>
</ul>
<hr>
<p><strong>Performance Classes</strong></p>
<ul>
<li>EFS Scale</li>
</ul>
<p><strong>Storage Classes</strong></p>
<ul>
<li>Performance Mode<ul>
<li>General Purpose : 고성능, low-latency</li>
<li>Max I/O</li>
</ul>
</li>
<li>Throughput Mode<ul>
<li>Bursting</li>
<li>Provisioned</li>
<li>Elastic : 작업 부하에 따라 처리량을 자동으로 조정</li>
</ul>
</li>
</ul>
<p><strong>Storage Classes</strong> → 최대 90% 비용 절감 가능 </p>
<ul>
<li>Storage Tiers<ul>
<li>Standard</li>
<li>Infrequent access(EFS-IA)</li>
<li>Archive</li>
<li>Implement lifecycle policies</li>
</ul>
</li>
<li>Availability and durability<ul>
<li>Standard</li>
<li>One Zone : 하나의 AZ, 비용 절감</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[IP, EC2 배치그룹, ENI, Hibernate]]></title>
            <link>https://velog.io/@from-minju/IP-EC2-%EB%B0%B0%EC%B9%98%EA%B7%B8%EB%A3%B9-ENI-Hibernate</link>
            <guid>https://velog.io/@from-minju/IP-EC2-%EB%B0%B0%EC%B9%98%EA%B7%B8%EB%A3%B9-ENI-Hibernate</guid>
            <pubDate>Thu, 08 Jan 2026 11:41:16 GMT</pubDate>
            <description><![CDATA[<h1 id="private-vs-public-vs-elastic-ip">Private vs Public vs Elastic IP</h1>
<blockquote>
<p>💡 <strong>Private IP</strong> 
AWS VPC등 특정 네트워크 안에서 서로를 인식하기 위한 IP</p>
</blockquote>
<p>예를들어 A회사와 B회사의 네트워크안에는 동일한 사설IP가 존재가능하다. 그 회사 네트워크 안에서만 유일하면 됨.</p>
<blockquote>
<p><strong>💡 Public IP</strong> 
외부 인터넷 세상에서, 전 세계적으로 고유하게 식별하기위한 IP</p>
</blockquote>
<blockquote>
<p><strong>💡 Elastic IP (탄력적 IP)</strong>
AWS가 제공하는 <strong>고정된 Public IP 주소</strong></p>
</blockquote>
<table>
<thead>
<tr>
<th>항목</th>
<th>Private IP</th>
<th>Public IP (기본)</th>
<th>Elastic IP</th>
</tr>
</thead>
<tbody><tr>
<td><strong>EC2 중단(Stop)</strong></td>
<td>❌ <strong>안 바뀜</strong></td>
<td>✅ <strong>바뀜</strong></td>
<td>❌ 안 바뀜</td>
</tr>
<tr>
<td><strong>EC2 종료(Terminate)</strong></td>
<td>✅ 바뀜 (삭제됨)</td>
<td>✅ 바뀜</td>
<td>❌ 유지 가능 (다른 인스턴스에 할당 가능)</td>
</tr>
</tbody></table>
<p></br></br></p>
<hr>
<h1 id="ec2-배치그룹">EC2 배치그룹</h1>
<blockquote>
<p>💡 <strong>배치그룹</strong>
EC2 인스턴스들을 물리적으로 어떻게 배치할지 지정하는 기능</p>
</blockquote>
<h3 id="클러스터-배치그룹-cluster">클러스터 배치그룹 (Cluster)</h3>
<blockquote>
<p>💡 <strong>클러스터 배치그룹</strong>
모든 EC2 인스턴스들을 같은 AZ안에 두는 방식.</p>
</blockquote>
<ul>
<li>장점<ul>
<li>EC2간 거리가 가까우니 latency 짧고, 네트워크 처리량 향상된다.</li>
</ul>
</li>
<li>단점<ul>
<li>가용영역에 장애가 생기면 모든 인스턴스들에 장애가 발생한다.</li>
</ul>
</li>
<li>적합한 경우<ul>
<li>매우 빠른 네트워킹, 많은 네트워크 처리량 필요한 경우</li>
</ul>
</li>
</ul>
<h3 id="분산-배치그룹-spread">분산 배치그룹 (Spread)</h3>
<blockquote>
<p>💡 <strong>분산 배치그룹</strong>
모든 EC2 인스턴스가 각각 다른 하드웨어에 위치하여 실패 위험을 최소화한 구조 (최대 고가용성)</p>
</blockquote>
<ul>
<li>장점<ul>
<li>여러 가용영역에 걸쳐 있으므로 실패위험 감소한다.</li>
<li>장애가 생겨도 인스턴스 간에 서로 영향을 끼치지 않는다. (3가지 종류의 배치그룹 중 최대 가용성)</li>
</ul>
</li>
<li>단점<ul>
<li>가용영역당 최대 7개의 인스턴스가 가능하다.</li>
<li>따라서 규모가 제한적. 대규모 서비스에는 부적합.</li>
</ul>
</li>
<li>적합한 경우<ul>
<li>인스턴스 오류를 격리해야하는 경우 적합, 매우 중요한 크리티컬 작업에 적합</li>
</ul>
</li>
</ul>
<h3 id="분할-배치그룹-partition">분할 배치그룹 (Partition)</h3>
<blockquote>
<p>💡 <strong>분할 배치그룹</strong>
EC2들을 파티션 단위로 나눈 구조(각 파티션은 서로 다른 하드웨어에 위치)</p>
</blockquote>
<p>각 파티션은 AWS의 랙(서버들을 적층한 선반구조물)을 의미한다. 파티션이 많으면 인스턴스가 여러 하드웨어 랙에 분산되어 랙 실패로부터 안전하다.</p>
<p>각 파티션에는 수백개 이상의 EC2가 가능하다.</p>
<ul>
<li><p>장점</p>
<ul>
<li>파티션이 많을수록 인스턴스들이 서로 다른 랙 하드웨어에 배치된다는 의미이므로 실패 위험이 감소한다. (분산 배치그룹의 장점처럼)</li>
<li>하나의 파티션당 수백대의 EC2들이 가능(분산배치그룹의 규모제한 단점을 이겨냄)하면서도</li>
<li>클러스터 배치그룹 보다 EC2간 서로 실패영향력이 적다.</li>
</ul>
</li>
<li><p>단점</p>
<p>  관리 복잡도 (파티션 지정 필요)</p>
</li>
</ul>
<p></br></br></p>
<h3 id="배치그룹-총-정리비교">배치그룹 총 정리/비교</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th><strong>클러스터 배치 그룹 (Cluster)</strong></th>
<th><strong>분산 배치 그룹 (Spread)</strong></th>
<th><strong>분할 배치 그룹 (Partition)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>목적</strong></td>
<td><strong>고성능 네트워킹</strong></td>
<td>고가용성 확보</td>
<td>장애 도메인 분리 + 확장성</td>
</tr>
<tr>
<td><strong>인스턴스 배치 방식</strong></td>
<td><strong>모든 EC2가 같은 AZ 안에 있다. 물리적으로 가까운 위치</strong>에 집중 배치</td>
<td>인스턴스별로 서로 다른 하드웨어에 배치</td>
<td>파티션 단위로 격리된 하드웨어에 배치 (각 파티션은 서로 다른 하드웨어에)</td>
</tr>
<tr>
<td><strong>하드웨어 격리 수준</strong></td>
<td>없음 (같은 랙/하드웨어)</td>
<td>인스턴스 단위</td>
<td>파티션 단위</td>
</tr>
<tr>
<td><strong>최대 인스턴스 수</strong></td>
<td>제한 있지만 상대적으로 <strong>많음</strong></td>
<td>AZ당 최대 7개</td>
<td>수백~수천 개 가능 (파티션은 AZ당 최대 7개, 하나의 파티션에 수백개 이상 가능)</td>
</tr>
<tr>
<td><strong>적합한 워크로드</strong></td>
<td><strong>고성능 컴퓨팅, HPC, 빅데이터 분석</strong></td>
<td>중요 소규모 서비스 (ex. DB, 웹서버)</td>
<td>분산 시스템 (ex. Hadoop, Kafka)</td>
</tr>
<tr>
<td><strong>장점</strong></td>
<td>초저지연 네트워크, 빠른 성능</td>
<td>장애 발생 시 영향 최소화</td>
<td>대규모에서도 고가용성 유지 가능</td>
</tr>
<tr>
<td><strong>단점</strong></td>
<td>한 인스턴스 장애 시 영향 클 수 있음</td>
<td>확장성 제한, 인스턴스 수 제한</td>
<td>관리 복잡도 (파티션 지정 필요)</td>
</tr>
<tr>
<td><strong>예시 비유</strong></td>
<td>“모두 한 교실에 몰아넣기”</td>
<td>“학생을 전부 다른 교실에 나눠 앉히기”</td>
<td>“팀별로 교실에 나눠 앉히기”</td>
</tr>
<tr>
<td><strong>장애 발생 시</strong></td>
<td><strong>같은 하드웨어면 같이 영향 받을 수 있음</strong></td>
<td>하나 죽어도 나머지 OK</td>
<td>한 파티션 죽어도 나머지 OK</td>
</tr>
</tbody></table>
<p></br></br></p>
<hr>
<h1 id="eni-탄력적-네트워크-인터페이스">ENI 탄력적 네트워크 인터페이스</h1>
<blockquote>
<p>💡 <strong>ENI (Elastic Network Interfaces)</strong>
EC2 인스턴스에 꽂는 가상 네트워크 카드 (IP주소, 보안그룹 등의 네트워크 속성을 담고있다.)</p>
</blockquote>
<ul>
<li>하나의 EC2에 여러개 ENI를 부착할수 있다.</li>
<li>A 인스턴스에서 장애가 나면 ENI를 B로 이동시키면 된다. → <strong>IP주소를 쉽게 옮기게 해주는 역할</strong>, 장애 해결! 고가용성 확보</li>
<li>특정 가용영역에 바인딩된다. = 특정 AZ에서 ENI를 생성하면 해당 AZ에만 바인딩 가능. = 다른 AZ에 있는 EC2와는 연결 할 수 없다.</li>
</ul>
<p></br></br></p>
<hr>
<h1 id="ec2-hibernate-최대-절전-모드">EC2 Hibernate (최대 절전 모드)</h1>
<blockquote>
<p>💡 <strong>EC2 Hibernate</strong>
인스턴스를 잠시 중단했다가 다시 켜도 RAM의 상태를 보존시켜주는 모드이다.</p>
</blockquote>
<ul>
<li>RAM 상태를 암호화된 EBS 루트 볼륨에 저장하고, 다시 킬때 이를 복원시킴으로서 더 빠르게 부팅되도록 돕는다.<ul>
<li>일반적(stop) : ec2를 껐다 켜면 → 메모리 초기화</li>
<li>hibernate : ram 내용을 ebs에 저장하고, 다시 시작할때 그 상태 복원</li>
</ul>
</li>
<li>중지한 적이 없는 것처럼 운영체제가 인식하게 하는 것이 절전모드의 목적이다.</li>
<li>RAM에 있는 메모리상태는 그대로 보존됨 → <strong>부팅 더 빨라진다는 뜻</strong></li>
<li>인스턴스 RAM 최대 크기는 150 GB</li>
<li>EBS에만 저장 가능</li>
<li>루트 EBS 볼륨이 암호화되어있어야 한다.</li>
<li>최대 60일까지 절전모드 가능</li>
<li>인스턴스 종류(온디맨드, 예약…)에 상관없이 가능</li>
<li>루트볼륨에 RAM을 저장할 수 있는 충분한 공간이 있어야 한다. (e.g. RAM이 1GB인 t2.micro인스턴스의 경우, 8GB의 볼륨은 충분하다.)</li>
</ul>
<table>
<thead>
<tr>
<th>상태</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Stop</strong></td>
<td>EC2 인스턴스를 <strong>끄는 것</strong>. RAM 상태는 <strong>사라짐</strong></td>
</tr>
<tr>
<td><strong>Hibernate</strong></td>
<td>EC2 인스턴스를 <strong>끄는 건 같지만</strong>, RAM 내용을 <strong>EBS에 저장</strong>해서 나중에 <strong>그대로 복원</strong></td>
</tr>
<tr>
<td><strong>Terminate</strong></td>
<td>EC2 인스턴스를 <strong>완전히 삭제</strong>. 모든 상태, EBS 볼륨도 사라질 수 있음 (디폴트 설정 시)</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[EC2 Basics (인스턴스 유형, 요금, 보안그룹)]]></title>
            <link>https://velog.io/@from-minju/EC2-Basics</link>
            <guid>https://velog.io/@from-minju/EC2-Basics</guid>
            <pubDate>Tue, 06 Jan 2026 13:27:54 GMT</pubDate>
            <description><![CDATA[<h1 id="ec2">EC2</h1>
<blockquote>
<p><strong>💡 EC2</strong>
AWS에서 임대하는 가상 서버다.</p>
</blockquote>
<p>OS, CPU, RAM, 스토리지, Network, 보안그룹(방화벽 규칙)등을 선택 가능하다.</p>
<blockquote>
<p><strong>💡 부트스트래핑 Bootstrapping</strong></p>
<p>EC2 인스턴스가 처음 실행될때 자동으로 실행되는 스크립트를 의미한다. </p>
</blockquote>
<p>EC2 생성할 때 ‘User Data’에 스크립트를 넣어주면 된다. 패키지 설치, 설정 파일 작성, 애플리케이션 실행 등을 자동으로 처리할 수 있다. </p>
<h2 id="ec2-인스턴스-생성하는-방법">EC2 인스턴스 생성하는 방법</h2>
<ol>
<li>AMI 선택</li>
<li>인스턴스 유형 선택</li>
<li>키페어 설정(SSH 사용하기 위해)</li>
<li>네트워크 설정</li>
<li>스토리지 설정</li>
<li>User Data</li>
<li>등등</li>
</ol>
<blockquote>
<p>💡 <strong>AMI (Amazon Machine Image)</strong>
EC2의 이미지. 운영체제+설정 을 의미한다. </p>
</blockquote>
<blockquote>
<p>💡 <strong>Instance Type</strong> 인스턴스 유형
EC2의 하드웨어 스펙(CPU, Ram 등)을 의미한다. t2.micro, m5.2xlarge ... 등 </p>
</blockquote>
<table>
<thead>
<tr>
<th>항목</th>
<th>AMI (Amazon Machine Image)</th>
<th>인스턴스 유형 (Instance Type)</th>
</tr>
</thead>
<tbody><tr>
<td>의미</td>
<td>EC2의 <strong>소프트웨어 이미지</strong> (운영체제 + 설정)</td>
<td>EC2의 <strong>하드웨어 스펙 종류</strong></td>
</tr>
<tr>
<td>비유</td>
<td><strong>컴퓨터에 깔린 프로그램/OS</strong></td>
<td><strong>컴퓨터의 CPU, 메모리 성능</strong></td>
</tr>
<tr>
<td>선택 시</td>
<td>Ubuntu, Amazon Linux, Windows 등 OS &amp; 설정</td>
<td>t2.micro, t3.large, m5.xlarge 등</td>
</tr>
<tr>
<td>변경 가능성</td>
<td>EC2 생성 후 변경 ❌ (새로 만들어야 함)</td>
<td>재시작 없이 업/다운그레이드 가능 ✅</td>
</tr>
<tr>
<td>역할</td>
<td><strong>무엇을 실행할지</strong> 결정</td>
<td><strong>얼마나 빠르게 실행할지</strong> 결정</td>
</tr>
</tbody></table>
<p></br></br></p>
<h2 id="ec2-인스턴스-유형-예제">EC2 인스턴스 유형 예제</h2>
<p><code>m5.2xlarge</code></p>
<ul>
<li>m : 인스턴스 클래스</li>
<li>5 : generation</li>
<li>2xlarge : 인스턴스 크기</li>
</ul>
<h3 id="1-범용">1. 범용</h3>
<p>e.g. M5 인스턴스</p>
<h3 id="2-컴퓨팅-최적화">2. 컴퓨팅 최적화</h3>
<p>e.g. C5 인스턴스</p>
<h3 id="3-메모리-최적화">3. 메모리 최적화</h3>
<p>e.g. R6g 인스턴스 (R로 시작, Ram을 의미), X1, Z1</p>
<h3 id="4-가속-컴퓨팅">4. 가속 컴퓨팅</h3>
<p>e.g. P4 인스턴스</p>
<h3 id="5-스토리지-최적화">5. 스토리지 최적화</h3>
<p>e.g. D2 인스턴스, I, D, H1 </p>
<p></br></br></p>
<h2 id="ec2-요금">EC2 요금</h2>
<h3 id="1-aws-프리티어">1. AWS 프리티어</h3>
<p>t2.micro 달에 750시간 까지 무료 (인스턴스 여러개에서 합산가능)</p>
<h3 id="2-온디맨드-인스턴스">2. 온디맨드 인스턴스</h3>
<p>사용한 만큼 지불하는 요금제 (초 or 시간 단위로 과금)</p>
<ul>
<li>사용량을 예측할 수 없고, 중단되면 안되는 워크로드에 적합</li>
<li>Linux/Ubuntu : 최초 1분 이후 초단위로 요금 결제</li>
<li>Window/Mac : 시간 단위로 요금 결제</li>
</ul>
<h3 id="3-스팟-인스턴스">3. 스팟 인스턴스</h3>
<p>최대 90%까지 할인. 단, AWS가 필요할때 언제든 회수해 갈 수 있다.</p>
<ul>
<li>중요한 작업에는 적합하지 않다.</li>
<li>짧은 워크로드, 배치작업 등 시작/종료 시간을 유연하게 설정가능한 경우에 적합하다.</li>
</ul>
<h3 id="4-예약-인스턴스">4. 예약 인스턴스</h3>
<h3 id="5-saving-plans">5. Saving Plans</h3>
<h3 id="예약-인스턴스-vs-saving-plans">예약 인스턴스 Vs. Saving Plans</h3>
<ul>
<li>공통점<ul>
<li>1~3년간 약정</li>
<li>온디맨드에 비해 72-5% 절감 가능</li>
<li>장기 워크로드를 위함.</li>
</ul>
</li>
<li>예약 인스턴스 : 인스턴스 타입 변경 불가능!</li>
<li>Saving Plans : 인스턴스 타입, 리전 중간에 변경 가능. (유연함)</li>
</ul>
<table>
<thead>
<tr>
<th>항목</th>
<th><strong>Reserved Instances (RI)</strong></th>
<th><strong>Savings Plans</strong></th>
</tr>
</thead>
<tbody><tr>
<td>할인 적용 대상</td>
<td>EC2 인스턴스만</td>
<td>EC2, Fargate, Lambda 등</td>
</tr>
<tr>
<td>유연성</td>
<td><strong>낮음</strong> (정해진 인스턴스 유형에만 적용)</td>
<td><strong>높음</strong> (인스턴스 종류, 리전 변경 가능)</td>
</tr>
<tr>
<td>결제 방식</td>
<td>동일</td>
<td>선결제 없음 / 일부 / 전액 선결제 선택 가능</td>
</tr>
<tr>
<td>관리 복잡도</td>
<td>상대적으로 복잡</td>
<td>간단</td>
</tr>
<tr>
<td>변경 가능 여부</td>
<td>리전, 인스턴스 유형 바꾸려면 복잡함</td>
<td>자동 적용</td>
</tr>
<tr>
<td>시험에서 포인트</td>
<td><strong>구버전 개념, 특정 유형에 딱 맞춤</strong></td>
<td><strong>더 유연한 최신 방식</strong></td>
</tr>
<tr>
<td>비유</td>
<td>딱 그 자리에 앉아야함. 변경불가.</td>
<td>휴대폰 요금제, 기기 바뀌어도 상관 노</td>
</tr>
</tbody></table>
<h3 id="6-전용-호스트--전용-인스턴스">6. 전용 호스트 / 전용 인스턴스</h3>
<p>다른 고객과 h/w를 공유하지 않는다. 가장 비싼 옵션. </p>
<ul>
<li>전용 호스트 : 서버 통째로 독점 (호텔 건물 통째로 임대)</li>
<li>전용 인스턴스 : 서버는 공유, 내 인스턴스는 독점 (호텔방만 임대)</li>
</ul>
<p></br></br></p>
<h2 id="보안-그룹">보안 그룹</h2>
<ul>
<li><p>ec2의 방화벽으로 , 포트에 대해 액세스를 규제한다.</p>
</li>
<li><p>IP주소 기준으로 규칙을 설정</p>
</li>
<li><p>보안그룹과 인스턴스 사이에는 1:1 관계가 없다.</p>
</li>
<li><p><strong>하나의 인스턴스에는 여러 보안그룹이 가능하다.</strong></p>
</li>
<li><p>ssh 액세스를 위해 보안그룹을 분리하는 편이 낫다. (개발자를 위한 조언)</p>
</li>
<li><p><strong>인바운드 트래픽</strong> (www → ec2) : 디폴트는 모두 거부</p>
</li>
<li><p><strong>아웃바운드 트래픽</strong> (ec2 → www) : 디폴트는 모든 트래픽 나가는거 허용</p>
</li>
</ul>
</br>

<h3 id="classic-ports-to-know"><strong>Classic Ports to know</strong></h3>
<ul>
<li>22번 포트 : SSH, Secure Shell</li>
<li>22번 포트 : SFTP Secure File Transfer Protocol (SSH를 이용해 파일 업로드)</li>
<li>21번 포트 : FTP</li>
<li>80번 포트 : HTTP 
access unsecured websites → 웹브라우저에서 ip 입력해서 aws접근가능했던게 이 덕분. (80포트 0.0.0.0/0 인바운드로 설정 → 이 룰 삭제하면 웹 브라우저에서 접근 불가능하다. 보통 타임아웃 뜨면 100% 보안그룹 문제다.)</li>
<li>443번 포트 : HTTPS</li>
<li>3389번 포트 : RDP, 원격 데스크톱 프로토콜</li>
</ul>
</br>

<h3 id="다른-보안그룹의-보안그룹을-참조하는-방법">다른 보안그룹의 보안그룹을 참조하는 방법</h3>
<p><img src="https://velog.velcdn.com/images/from-minju/post/b1fe2170-0ee6-4f0e-b60b-315375a84be8/image.png" alt=""></p>
<p></br></br></p>
<blockquote>
<p><strong>참고</strong>
<a href="https://www.udemy.com/course/best-aws-certified-solutions-architect-associate/">https://www.udemy.com/course/best-aws-certified-solutions-architect-associate/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[IAM 및 AWS CLI]]></title>
            <link>https://velog.io/@from-minju/IAM-%EB%B0%8F-AWS-CLI</link>
            <guid>https://velog.io/@from-minju/IAM-%EB%B0%8F-AWS-CLI</guid>
            <pubDate>Tue, 06 Jan 2026 10:16:03 GMT</pubDate>
            <description><![CDATA[<h1 id="iam">IAM</h1>
<blockquote>
<p>💡 <strong>IAM</strong> (Identity Access Management)
AWS의 보안 인증 서비스로, AWS 계정 및 리소스에 대한 보안을 관리하고 권한을 제어한다.</p>
</blockquote>
<ul>
<li>IAM은 글로벌 서비스다. (선택할 리전이 없다는 뜻)</li>
<li>실제 물리적 사용자와 매핑한다.</li>
</ul>
<h2 id="root-account"><strong>Root Account</strong></h2>
<ul>
<li>처음 계정을 생성하면 기본적으로 만들어진다.</li>
<li>루트 계정은 처음 계정생성했을때만 사용하고, 이후에는 IAM으로 Users를 만들어 사용하는 것이 권장된다.(보안 강화를 위해)</li>
</ul>
<h2 id="iam-users"><strong>IAM Users</strong></h2>
<p>: 사용자</p>
<ul>
<li>하나의 사용자는 여러 그룹에 속할 수 있다.</li>
<li>반드시 사용자가 그룹에 속해야하는건 아니다.</li>
<li>IAM 정책은 IAM 사용자에게 직접 지정, 연결할 수 있다. (그룹을 통해 정책 지정하는 것이 아닌 사용자 개별마다 직접 지정가능)</li>
</ul>
<h2 id="iam-groups"><strong>IAM Groups</strong></h2>
<p>: 사용자들을 묶어 그룹으로 관리한다.</p>
<ul>
<li>그룹안에 그룹을 포함시킬 수 없다.</li>
</ul>
<p></br></br></p>
<h2 id="방어-매커니즘">방어 매커니즘</h2>
<h3 id="1-비밀번호-강화">1. 비밀번호 강화</h3>
<h3 id="2-mfa-multi-factor-authentication">2. MFA (Multi-Factor Authentication)</h3>
<p>비밀번호 + 보안기기를 사용하여 이중으로 보안을 강화한다.</p>
<p>MFA의 이점은 앨리스가 비밀번호를 도난당하거나 해킹당하여 비밀번호를 잊어버린 경우에도 해커가 앨리스의 물리적 장치도 확보해야 하므로 계정이 손상되지 않는다는 것입니다</p>
<ul>
<li><p><strong>Virtual MFA</strong></p>
<p>  실제 장비 없이도 보안을 강화해준다. 예시로 스마트폰에 인증앱을 설치하는 방법이 있다. </p>
<p>  로그인할때 비밀번호 + 스마트폰 twilio 인증앱에서 생성된 인증코드를 웹사이트에 그대로 입력</p>
</li>
<li><p><strong>물리적 장치</strong> (e.g. 보안토큰)</p>
</li>
</ul>
<p></br></br></p>
<h2 id="permissions">Permissions</h2>
<ul>
<li>IAM 정책을 JSON 문서로 지정</li>
</ul>
<blockquote>
<p>💡 <strong>최소 권한의 원칙</strong> (least privilege principle)
: 사용자 또는 시스템이 업무 수행에 꼭 필요한 권한만 부여받아야 한다.</p>
</blockquote>
<h2 id="policies-structure">Policies Structure</h2>
<pre><code class="language-jsx">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;PublicReadGetObject&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
              &quot;AWS&quot;: &quot;arn:aws:iam::123456789012:user/Alice&quot;
            },
      &quot;Action&quot;: &quot;s3:GetObject&quot;,
      &quot;Resource&quot;: &quot;arn:aws:s3:::my-public-bucket/*&quot;
    }
  ]
}</code></pre>
<h3 id="consists-of">Consists of</h3>
<ul>
<li><strong>Version</strong>:</li>
<li><strong>Id</strong>:</li>
<li><strong>Statement</strong>: 한 개 이상의 statements (required)</li>
</ul>
<h3 id="statements-consist-of">Statements consist of</h3>
<ul>
<li><strong>Sid(시드)</strong>: Statement ID (선택사항)</li>
<li><strong>Effect(효과)</strong>: Allow / Deny</li>
<li><strong>Principal(원칙/주체)</strong>: 누가 액세스하는지</li>
<li><strong>Action(조치)</strong>: 어떤 API를 허용/거부할지</li>
<li><strong>Resource(리소스)</strong>: 어떤 리소스에 적용할지</li>
<li><strong>Condition(조건)</strong>:</li>
</ul>
<p></br></br></p>
<h2 id="iam-roles-역할">IAM Roles 역할</h2>
<p>→ 임시된 권한 부여</p>
<p>다른 AWS 리소스나 서비스에 임시로 액세스하기 위해서 특정사용자나 서비스가 임시적으로 권한을 얻게하는 방법이다.</p>
<p>⛑️ 모자로 생각하면 이해하기 쉽다. 
Developer가 DA모자를 쓰면 잠시 DA업무를 수행가능하게된다. 단, 기존의 Developer의 권한은 없어진다. </p>
<h2 id="iam-보안도구">IAM 보안도구</h2>
<p>→ 계정의 권한을 감시하기 위함이다.</p>
<p><strong>1. IAM Credential Reports 자격증명보고서</strong>
csv파일로 생성됨. 계정을 생성한 시기, pw를 바꾼 시기, access key를 만든 시기… 등에 대한 정보를 파악가능.</p>
<p><strong>2. IAM Access Advisor 액세스 관리자</strong>
언제 어떤 서비스에 접근했는지를 보여준다. </p>
<p></br></br></p>
<h2 id="how-can-users-access-aws"><strong>How can users access AWS?</strong></h2>
<h3 id="1-management-console">1. Management Console</h3>
<p>AWS 관리를 위한 웹 기반 인터페이스 (aws 웹에서 접속하는 방법)</p>
<h3 id="2-aws-cli">2. AWS CLI</h3>
<p>Command Line Interface
AWS를 CLI로 접근하는 방법, access key가 필요하다. </p>
<h3 id="3-aws-sdk">3. AWS SDK</h3>
<p>Software Development Kit
코드 내에서 API를 호출하기 위한 방법, access key가 필요하다.</p>
</br>

<hr>
<h1 id="iam-section--summary">IAM Section – Summary</h1>
<ul>
<li><strong>Users</strong>: mapped to a physical user, has a password for AWS Console</li>
<li><strong>Groups</strong>: contains users only</li>
<li><strong>Policies</strong>: JSON document that outlines permissions for users or groups</li>
<li><strong>Roles</strong>: for EC2 instances or AWS services</li>
<li><strong>Security</strong>: MFA + Password Policy</li>
<li><strong>AWS CLI</strong>: manage your AWS services using the command-line</li>
<li><strong>AWS SDK</strong>: manage your AWS services using a programming language</li>
<li><strong>Access Keys</strong>: access AWS using the CLI or SDK</li>
<li><strong>Audit</strong>: IAM Credential Reports &amp; IAM Access Advisor</li>
</ul>
<p></br></br></p>
<blockquote>
<p><strong>참고</strong>
<a href="https://www.udemy.com/course/best-aws-certified-solutions-architect-associate/">https://www.udemy.com/course/best-aws-certified-solutions-architect-associate/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로젝트 세팅] NVM설치하고 node 18 사용하기]]></title>
            <link>https://velog.io/@from-minju/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%B8%ED%8C%85-NVM%EC%84%A4%EC%B9%98%ED%95%98%EA%B3%A0-node-18-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@from-minju/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%84%B8%ED%8C%85-NVM%EC%84%A4%EC%B9%98%ED%95%98%EA%B3%A0-node-18-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 08 Dec 2025 11:07:17 GMT</pubDate>
            <description><![CDATA[<h1 id="현재-상황">현재 상황</h1>
<pre><code class="language-bash">node -v</code></pre>
<p>노드 버전 확인결과, <code>v20.14.0</code> 으로 확인되었다. </p>
<p>현재 프로젝트에서는 18버전으로 진행해야하므로 버전을 바꿔보자.</p>
<p>이를 위해 <strong>nvm (Node Version Manager)</strong> 설치하면 편하다!</p>
<h2 id="1단계-nvm-설치-확인">1단계: nvm 설치 확인</h2>
<pre><code class="language-bash">nvm -v</code></pre>
<p>확인해서 버전이 나오면 OK</p>
<p>버전값이 안 나오면 설치해야 한다.</p>
<h2 id="2단계-nvm-설치-맥-기준">2단계: nvm 설치 (맥 기준)</h2>
<blockquote>
<h3 id="💡-nvm이-무엇인가">💡 <strong>nvm이 무엇인가?</strong></h3>
</blockquote>
<ul>
<li><strong>N</strong>ode <strong>V</strong>ersion <strong>M</strong>anager</li>
<li>말 그대로 <strong>Node.js 버전을 여러 개 깔아놓고, 필요할 때마다 바꿔 쓰게 해주는 도구</strong>야.</li>
<li>예: 프로젝트 A는 18, 프로젝트 B는 20 써야 할 때 <code>nvm use 18</code>, <code>nvm use 20</code> 이런 식으로 전환 가능.</li>
</ul>
<h3 id="1-내가-zsh인지-bash인지-먼저-확인"><strong>1. 내가 zsh인지 bash인지 먼저 확인</strong></h3>
<p>맥은 기본이 보통 <code>zsh</code>야.</p>
<pre><code class="language-bash">echo $SHELL</code></pre>
<ul>
<li><code>/bin/zsh</code> → zsh 사용 중 → <strong>.zshrc</strong> 설정</li>
<li><code>/bin/bash</code> → bash 사용 중 → <strong>.bashrc</strong> 설정</li>
</ul>
<p>대부분 요즘 맥은 <code>/bin/zsh</code> 나올 거야.</p>
<h3 id="2-nvm-설치-homebrew-사용-맥-기준"><strong>2. nvm 설치 (Homebrew 사용, 맥 기준)</strong></h3>
<p>터미널에서 아래 명령어 실행</p>
<pre><code class="language-bash">brew install nvm</code></pre>
<p>설치가 완료되면 brew가 이런 식으로 메시지를 줄 수도 있는데 그게 바로 <code>.zshrc</code> / <code>.bashrc</code>에 넣으라는 내용이야.</p>
<blockquote>
<p>Add the following to your profile: ...</p>
</blockquote>
<pre><code class="language-bash">You should create NVM&#39;s working directory if it doesn&#39;t exist:
  mkdir ~/.nvm

Add the following to your shell profile e.g. ~/.profile or ~/.zshrc:
  export NVM_DIR=&quot;$HOME/.nvm&quot;
  [ -s &quot;/opt/homebrew/opt/nvm/nvm.sh&quot; ] &amp;&amp; \. &quot;/opt/homebrew/opt/nvm/nvm.sh&quot;  # This loads nvm
  [ -s &quot;/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm&quot; ] &amp;&amp; \. &quot;/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm&quot;  # This loads nvm bash_completion</code></pre>
<h3 id="3-nvm-폴더-만들기"><strong>3. ~/.nvm 폴더 만들기</strong></h3>
<pre><code class="language-bash">mkdir -p ~/.nvm</code></pre>
<ul>
<li><code>p</code>는 “이미 있어도 에러 내지 말고 그냥 넘어가” 옵션.</li>
<li>nvm이 내부적으로 Node 버전들을 저장하는 디렉토리라고 생각하면 돼.</li>
</ul>
<h3 id="4-zshrc--bashrc를-왜-수정하냐"><strong>4. .zshrc / .bashrc를 왜 수정하냐?</strong></h3>
<blockquote>
<h3 id="💡zshrc--bashrc가-하는-일">💡.zshrc / .bashrc가 하는 일</h3>
</blockquote>
<ul>
<li>둘 다 “<strong>쉘 시작할 때 자동으로 읽는 설정 파일</strong>”이야.</li>
<li>터미널을 새로 열면:<ul>
<li>zsh 쓰는 경우 → <code>~/.zshrc</code> 내용 자동 실행</li>
<li>bash 쓰는 경우 → <code>~/.bashrc</code> 내용 자동 실행</li>
</ul>
</li>
<li>보통 여기에<ul>
<li><code>PATH</code> 설정</li>
<li>alias (예: <code>alias ll=&#39;ls -al&#39;</code>)</li>
<li>각종 툴 초기화 코드 (nvm, pyenv, rbenv 등) 를 넣어.</br>
👉 <strong>즉, “터미널 켤 때마다 매번 손으로 치기 귀찮은 설정들을 모아두는 곳”이라고 생각하면 됨.</strong></li>
</ul>
</li>
</ul>
<p>zsh를 쓰는 경우 (<code>~/.zshrc</code> 수정)</p>
<pre><code class="language-bash">vim ~/.zshrc</code></pre>
<p>파일 맨 아래에 아래 내용 추가:</p>
<pre><code class="language-bash">export NVM_DIR=&quot;$HOME/.nvm&quot;
source $(brew --prefix nvm)/nvm.sh</code></pre>
<ul>
<li><p><code>NVM_DIR</code></p>
<p>  → nvm이 설치된/설치될 디렉토리를 환경 변수로 알려주는 역할</p>
</li>
<li><p><code>source $(brew --prefix nvm)/nvm.sh</code></p>
<p>  → “nvm 기능을 현재 쉘에서 사용할 수 있게 스크립트를 불러온다”는 뜻</p>
<p>  → 이 줄이 있어야 <code>nvm</code> 명령이 인식돼.</p>
</li>
</ul>
<p>수정 끝나면 <code>esc</code> → <code>:wq!</code>로 저장 &amp; 종료.</p>
<p>그리고 변경사항을 지금 쉘에 반영:</p>
<pre><code class="language-bash">source ~/.zshrc</code></pre>
<h3 id="5-nvm-잘-깔렸는지-확인"><strong>5. nvm 잘 깔렸는지 확인</strong></h3>
<pre><code class="language-bash">nvm -v</code></pre>
<p>예를 들어:</p>
<pre><code class="language-bash">0.40.3</code></pre>
<p>이런 식으로 버전이 나오면 성공 🎉</p>
<p>이제 Node 18 설치 가능!</p>
<h2 id="3단계-node-18-설치하기">3단계: Node 18 설치하기</h2>
<pre><code class="language-bash">nvm install 18
nvm use 18
node -v   # v18.x.x 나오면 OK</code></pre>
<hr>
<h2 id="🔍-정리-왜-zshrc--bashrc를-만드는수정하는가">🔍 정리: 왜 .zshrc / .bashrc를 만드는/수정하는가?</h2>
<p>한 줄로 말하면:</p>
<blockquote>
<p>터미널이 켜질 때마다 nvm을 자동으로 사용할 수 있게 “초기 설정을 등록하는 파일”이라서.</p>
</blockquote>
<p>조금 길게 말하면:</p>
<ul>
<li>nvm은 그 자체로 하나의 “스크립트”</li>
<li>그 스크립트를 매번 손으로 <code>source</code> 하기 귀찮으니까</li>
<li><code>~/.zshrc</code> 또는 <code>~/.bashrc</code>에 “이 스크립트 불러와!”라고 써두는 것</li>
<li>그러면 터미널 새로 열 때마다 자동으로 실행 → <code>nvm</code> 명령이 항상 준비 완료 상태가 됨</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Baekjoon/Python] 12865. 평범한 배낭 (DP, 0/1 Knapsack) 🥇5️⃣]]></title>
            <link>https://velog.io/@from-minju/BaekjoonPython-12865.-%ED%8F%89%EB%B2%94%ED%95%9C-%EB%B0%B0%EB%82%AD-DP-01-Knapsack-5</link>
            <guid>https://velog.io/@from-minju/BaekjoonPython-12865.-%ED%8F%89%EB%B2%94%ED%95%9C-%EB%B0%B0%EB%82%AD-DP-01-Knapsack-5</guid>
            <pubDate>Mon, 01 Dec 2025 02:19:18 GMT</pubDate>
            <description><![CDATA[<h1 id="풀이-참고-사이트-알고리즘-요약"><a href="https://huiyu.tistory.com/entry/DP-01-Knapsack%EB%B0%B0%EB%82%AD-%EB%AC%B8%EC%A0%9C">풀이 참고 사이트</a> 알고리즘 요약</h1>
<table>
<thead>
<tr>
<th>가방 크기</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
<th>7</th>
<th>8</th>
<th>9</th>
<th>10</th>
<th>11</th>
<th>12</th>
</tr>
</thead>
<tbody><tr>
<td>루비</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>6</td>
</tr>
<tr>
<td>다이아</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>9</td>
<td>9</td>
<td>9</td>
<td>9</td>
<td>9</td>
<td>9</td>
</tr>
<tr>
<td>사파이어</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>6</td>
<td>9</td>
<td>11</td>
<td>11</td>
<td>11</td>
<td>11</td>
<td>11</td>
</tr>
</tbody></table>
<ul>
<li>행 ($i$): $i$번째 물건까지 고려했을 때</li>
<li>열 ($w$): 배낭의 현재 용량이 $w$일 때값 </li>
<li>$DP[i][w]$: $i$번째 물건까지 고려하고 배낭 용량이 $w$일 때 가질 수 있는 최대 가치</li>
</ul>
</br>

<p>💎 <strong>루비</strong>를 선택하는 경우(<code>dp[루비]</code>)</p>
<p>💎 <strong>다이아</strong>를 선택하는 경우
(<code>dp[다이아]</code> = 각 무게별로 <code>[루비, 다이아]</code>에서 만들어낼 수 있는 최대 가치합)</p>
<p>💎 <strong>사파이어</strong>를 선택하는 경우
(<code>dp[사파이어]</code> = 각 무게별로 <code>[루비, 다이아, 사파이어]</code>에서 만들어낼 수 있는 최대 가치합)</p>
<ul>
<li>가방의 크기가 7인 경우,<ul>
<li><strong>사파이어를 챙길 경우</strong>
남은 공간 2, 남은 공간이 2일 경우 루비, 다이아 두가지 보석 모두를 이용해도 더 넣을 수가 없으므로 가치는 5,</li>
<li><strong>사파이어를 챙기지 않은 경우</strong>
이전에 구해둔 9가 최대이기 때문에 최대이윤은 9가됩니다.</li>
</ul>
</li>
<li>가방의 크기가 8인 경우<ul>
<li>사파이어를 챙길 경우, 남은공간은 3, 이미 테이블을 통해 가방의 크기가 3일 때 루비/다이아를 이용해 구할 수 있는 최대 이윤은 6임을 구했습니다.
사파이어 가치 5 + ~다이아까지 넣을 떄 최대이윤 6 = 11, 즉 최대이윤이 11이 됩니다.</li>
</ul>
</li>
</ul>
<br>

<p>현재 내가 구하려고 하는 셀의 값은
<code>MAX(현재 보석의 가치+남은가방크기만큼 나머지 보석을 넣을 때 최대 가치, 이전까지 구해둔 보석의 가치)</code> </p>
<p>즉,
<strong><code>ans[i][j] = max(profit[i] + ans[i-1][j-weight[i], ans[i-1][j]]</code></strong></p>
<hr>
<h1 id="최종-코드">최종 코드</h1>
<pre><code class="language-python">n, k = map(int, input().split())

weight = [0]
value = [0]

for i in range(n):
    w, v = map(int, input().split())
    weight.append(w)
    value.append(v)

dp = [[0] * (k+1) for i in range(n+1)]

# dp 리스트 채우기
for i in range(n+1):
    for j in range(1, k+1):
        if weight[i] &lt;= j:
            dp[i][j] = max(value[i] + dp[i-1][j-weight[i]], dp[i-1][j])
        else:
            dp[i][j] = dp[i-1][j]

# 최종 답
answer = max(dp[n-1])

print(answer)</code></pre>
<ul>
<li>DP의 행과 열을 1-based로 한 이유 (DP가 N+1행, K+1열인 이유)
N=1인 경우, DP[i-1][j]값이 존재해야 한다.
즉, 아무것도 담지 않은 빈 배낭의 상태를 초기화 해놓기 위해 0행, 0열이 필요하다.</li>
</ul>
<hr>
<h1 id="배운-점-정리">배운 점 정리</h1>
<p>도무지 모르겠어서 <a href="https://huiyu.tistory.com/entry/DP-01-Knapsack%EB%B0%B0%EB%82%AD-%EB%AC%B8%EC%A0%9C">풀이 참고 사이트</a> 를 참고했다.</p>
<p><code>dp[i] = i요소를 반드시 포함하는 경우의 최대 가치의 합</code> 까지는 알고리즘을 떠올려봤는데, <code>[루비] → [루비, 다이아] → [루비, 다이아, 사파이어]</code> 이런식으로 요소들을 누적시켜서 계산할 생각은 못했다. (그게 바로 DP의 핵심인데 왜 못떠올렸을까...)</p>
<p>처음엔 n행은 0-based로, k열은 1-based로 설정하였다. 그래서 dp[0][]을 초기화한 후 dp[1][]부터 순회하도록 구현했다. 그러나 행또한 1-based로 하면 굳이 초기화 코드가 없어도 된다는걸 깨닫고 행과 열 모두 1-based로 수정했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Baekjoon/Python] 11055. 가장 큰 증가하는 부분 수열 (DP, LIS) 🥈2️⃣]]></title>
            <link>https://velog.io/@from-minju/BaekjoonPython-11055.-%EA%B0%80%EC%9E%A5-%ED%81%B0-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-DP-2</link>
            <guid>https://velog.io/@from-minju/BaekjoonPython-11055.-%EA%B0%80%EC%9E%A5-%ED%81%B0-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-DP-2</guid>
            <pubDate>Fri, 28 Nov 2025 06:25:29 GMT</pubDate>
            <description><![CDATA[<p>이 문제는 <strong>증가하는 부분 수열 중에서 ‘합이 가장 큰 경우’를 찾는 DP 문제</strong>이다.
LIS(가장 긴 증가 부분 수열)와 비슷하지만, 길이가 아닌 “부분 수열 값들의 총합”을 기준으로 한다는 점이 핵심이다.</p>
<p><strong>이전에 풀었던 비슷한 유형의 문제 :</strong> 
<a href="https://velog.io/@from-minju/BaekjoonPython-11053.-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-DP-%EB%B6%80%EB%B6%84%ED%95%A9-%EC%B9%B4%EB%8D%B0%EC%9D%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-2">11053. 가장 긴 증가하는 부분 수열 (DP) 🥈2️⃣</a></p>
<hr>
<h1 id="🧩-문제">🧩 문제</h1>
<p>수열이 주어졌을 때,
<strong>증가하는 부분 수열 중 합이 가장 큰 값을 구하기</strong>.</p>
<p>예를 들어
<code>10 30 10 20 20 50</code> 이라면,</p>
<p>가능한 증가 부분 수열 중 합이 가장 큰 건
<code>10 + 20 + 50 = 80</code> 또는 <code>10 + 30 + 50 = 90</code>
→ 답은 <strong>90</strong></p>
<hr>
<h1 id="✏️-나의-알고리즘">✏️ 나의 알고리즘</h1>
<p><a href="https://velog.io/@from-minju/BaekjoonPython-11053.-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-DP-%EB%B6%80%EB%B6%84%ED%95%A9-%EC%B9%B4%EB%8D%B0%EC%9D%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-2">이전에 풀었던 LIS문제</a>와 다르게
이 문제는 길이가 아니라 <strong>합</strong>을 기준으로 해야 한다.</p>
<h3 id="1-dp-배열-정의">1. DP 배열 정의</h3>
<p>먼저, 수열을 <code>seq[0] ~ seq[n-1]</code> 라고 할 때,</p>
<blockquote>
<p>*<em>💡 DP 리스트의 정의 *</em>
<code>dp[i]</code> = <code>seq[i]</code>를 마지막 원소로 가지는 “증가 부분 수열”들 중, 합이 가장 큰 값</p>
</blockquote>
<h3 id="2-초기값-설정">2. 초기값 설정</h3>
<p>처음에는 각 원소 하나만 선택한 경우를 생각하면 되기 때문에,</p>
<pre><code class="language-python">dp[i] = seq[i]</code></pre>
<p>를 만족하도록 초기화할 수 있다.</p>
<p>파이썬에서는 아래처럼 한 줄로 처리:</p>
<pre><code class="language-python">dp = seq[:]   # 원본과 독립된 새 리스트 (깊은 복사 느낌)</code></pre>
<p>나중에 반복문을 돌면서 “앞에 다른 수를 붙일 수 있는 경우들”을 고려해 <code>dp[i]</code>를 점점 키워 나간다.</p>
<h3 id="3-점화식-세우기">3. 점화식 세우기</h3>
<p>i번째 위치에 <code>seq[i]</code>가 있을 때,
그 앞에 올 수 있는 인덱스 j는 다음 조건을 만족해야 한다.</p>
<ul>
<li>항상 앞에 있어야 하니까: <code>0 ≤ j &lt; i</code></li>
<li>증가 수열이어야 하니까: <code>seq[j] &lt; seq[i]</code></li>
</ul>
<p>이 조건을 만족하는 모든 <code>j</code>에 대해,</p>
<blockquote>
<p><code>dp[i] = max(dp[i], dp[j] + seq[i])</code></p>
</blockquote>
<p>라고 갱신해 주면 된다.</p>
<p>즉,</p>
<ul>
<li>“나보다 작은 값들 중에서”</li>
<li>“그 값으로 끝나는 증가 부분 수열의 합(<code>dp[j]</code>)이 가장 큰 것”을 골라</li>
<li>거기에 나(<code>seq[i]</code>)를 붙여서 새로운 합을 만드는 과정이다.</li>
</ul>
<h3 id="시간-복잡도">시간 복잡도</h3>
<ul>
<li>이중 for문 (<code>i</code>에 대해 <code>0 ~ n-1</code>, <code>j</code>에 대해 <code>0 ~ i-1</code>)
→ 최악의 경우 약 <code>n * (n-1) / 2</code> 번 연산</li>
<li><strong>시간 복잡도: O(n²)</strong></li>
</ul>
<p>n의 범위가 크지 않기 때문에, 이 정도 복잡도로 충분히 통과 가능하다.</p>
<hr>
<h1 id="✅-최종-코드">✅ 최종 코드</h1>
<pre><code class="language-python">n = int(input())
seq = list(map(int, input().split()))
dp = seq[:]

for i in range(1, n):
    for j in range(i):
        if seq[i] &gt; seq[j]:
            dp[i] = max(dp[i], dp[j] + seq[i])

answer = max(dp)
print(answer)
</code></pre>
<hr>
<h1 id="📚-파이썬에서-얕은-복사-vs-깊은-복사-정리">📚 파이썬에서 얕은 복사 vs 깊은 복사 정리</h1>
<p>또 하나의 시행착오는 <strong>리스트 복사 문제</strong>였다.
처음에 아래처럼 썼는데:</p>
<pre><code class="language-python">dp = seq</code></pre>
<p>이렇게 하면 <strong>동일한 리스트를 가리키는 얕은 복사</strong>라서
<code>dp[i]</code>를 변경하면 <code>seq[i]</code>도 바뀌어버린다.
그래서 계산 과정이 꼬일 위험이 있었다.</p>
<p>이 문제에서는 원본을 건드리면 안 되기 때문에
아래처럼 반드시 <strong>깊은 복사 형태</strong>가 필요하다:</p>
<pre><code class="language-python">dp = seq[:]  # 안전한 방법!</code></pre>
<h2 id="✔-얕은-복사-shallow-copy">✔ 얕은 복사 (Shallow Copy)</h2>
<p><strong>같은 리스트 객체를 바라본다.</strong></p>
<pre><code class="language-python">seq = [1,2,3]
dp = seq
dp[0] = 100
print(seq)  # [100, 2, 3] ← 원본도 바뀜</code></pre>
<h2 id="✔-깊은-복사-deep-copy">✔ 깊은 복사 (Deep Copy)</h2>
<p><strong>새로운 리스트를 만든다(원본 영향 없음).</strong></p>
<h3 id="🔸-방법-1-슬라이싱">🔸 방법 1: 슬라이싱</h3>
<pre><code class="language-python">dp = seq[:]</code></pre>
<h3 id="🔸-방법-2-list-생성자">🔸 방법 2: list() 생성자</h3>
<pre><code class="language-python">dp = list(seq)</code></pre>
<h3 id="🔸-방법-3-copy-모듈">🔸 방법 3: copy 모듈</h3>
<p>(2차원 리스트 같은 복잡한 구조에서 유용)</p>
<pre><code class="language-python">import copy
dp = copy.deepcopy(seq)</code></pre>
<p>이번 문제는 1차원 리스트이기 때문에
<code>seq[:]</code> 만으로 충분했다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Baekjoon/Python] 11053. 가장 긴 증가하는 부분 수열 (DP, LIS) 🥈2️⃣]]></title>
            <link>https://velog.io/@from-minju/BaekjoonPython-11053.-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-DP-%EB%B6%80%EB%B6%84%ED%95%A9-%EC%B9%B4%EB%8D%B0%EC%9D%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-2</link>
            <guid>https://velog.io/@from-minju/BaekjoonPython-11053.-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-DP-%EB%B6%80%EB%B6%84%ED%95%A9-%EC%B9%B4%EB%8D%B0%EC%9D%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-2</guid>
            <pubDate>Thu, 27 Nov 2025 07:53:55 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-문제-요약">📌 문제 요약</h1>
<ul>
<li>길이가 N인 수열이 주어진다.</li>
<li>“증가하는 부분 수열(increasing subsequence)” 중
<strong>가장 길이가 긴 것의 길이</strong>를 구하는 문제.</li>
<li><em>부분 수열</em>이기 때문에 연속일 필요는 없다.
└ 다만 순서는 유지해야 함.</li>
</ul>
<hr>
<h1 id="🧠-내가-세운-접근-방식">🧠 내가 세운 접근 방식</h1>
<p>처음엔 “수열을 앞에서부터 보면서, 각 위치에서 끝나는 LIS의 길이”를 저장하면 되지 않을까?
라고 생각했다.</p>
<p>이 문제는 “구간”이 아니라 “부분 수열”이기 때문에
어떤 수 <code>seq[i]</code>를 선택하려면,
그 이전의 원소 <code>seq[j]</code> (j &lt; i) 중에서</p>
<pre><code>seq[j] &lt; seq[i]</code></pre><p>이 조건을 만족하는 것들만 고려해야 한다.</p>
<p>그래서 DP 테이블을 다음처럼 정의했다.</p>
<blockquote>
<p><strong>dp[i] = i번째 원소를 ‘마지막 원소’로 갖는 LIS의 길이</strong>
<span style="color:grey">LIS는 Longest Increasing Subsequence의 약자로, 가장 긴 증가하는 부분 수열을 의미한다.</span></p>
</blockquote>
<p>이렇게 정의해두면 자연스럽게 점화식도 나온다.</p>
<hr>
<h1 id="🔍-점화식-형태">🔍 점화식 형태</h1>
<p>i번째 위치에서 고려할 선택지는 두 가지뿐이다:</p>
<ol>
<li>증가 조건을 만족하는 이전 인덱스 j를 골라,
<code>dp[j] + 1</code> 로 이어붙이기</li>
<li>이전에 자신보다 작은 값이 하나도 없다면
→ 자기 자신만 쓰는 길이 <code>1</code></li>
</ol>
<p>즉,</p>
<pre><code>dp[i] = max(dp[j] + 1)   (단, j &lt; i 이고 seq[j] &lt; seq[i])
dp[i] = 1                (만약 조건을 만족하는 j가 없다면)</code></pre><hr>
<h1 id="🧪-코드">🧪 코드</h1>
<p>아래 코드는 위 아이디어대로 구현한 <strong>O(N²) 정석 풀이</strong>다.</p>
<pre><code class="language-python">n = int(input())
seq = list(map(int, input().split()))
dp = [0] * n

dp[0] = 1

for i in range(1, n):
    candidates = []
    for j in range(i):
        if seq[i] &gt; seq[j]:
            candidates.append(dp[j] + 1)

    if candidates:
        dp[i] = max(candidates)
    else:
        dp[i] = 1

print(max(dp))</code></pre>
<hr>
<h1 id="⏱-시간복잡도는-괜찮을까">⏱ 시간복잡도는? 괜찮을까?</h1>
<p>이 코드의 시간복잡도는</p>
<pre><code>바깥 for → N번
안쪽 for → 평균 N/2 정도
총 ≈ N²/2 → 대략 50만번 정도 (N = 1000일 때)</code></pre><p>파이썬에서 <strong>50만 연산</strong>은 아주 가벼운 수준이라서
백준 기준 1초 안에 충분히 통과한다.</p>
<h3 id="✔-코딩-테스트에서-시간복잡도-감각을-잡는-팁">✔ 코딩 테스트에서 시간복잡도 감각을 잡는 팁</h3>
<table>
<thead>
<tr>
<th>N 크기</th>
<th>안전한 시간복잡도 (Python 기준)</th>
</tr>
</thead>
<tbody><tr>
<td>N ≤ 1,000</td>
<td>O(N²) OK</td>
</tr>
<tr>
<td>N ≈ 5,000</td>
<td>O(N²)는 경계, O(N log N) 추천</td>
</tr>
<tr>
<td>N ≈ 100,000</td>
<td>O(N log N) 또는 O(N)만 가능</td>
</tr>
<tr>
<td>N ≥ 1,000,000</td>
<td>사실상 O(N) 이하</td>
</tr>
</tbody></table>
<p>→ 이 문제는 N ≤ 1000이므로 O(N²) 풀이가 <strong>정석</strong>.</p>
<hr>
<h1 id="🧭-배운-점-정리">🧭 배운 점 정리</h1>
<ul>
<li>LIS 문제의 핵심은
<strong>“i에서 끝나는 가장 긴 증가하는 부분 수열의 길이”</strong> 를 dp에 저장하는 것.</li>
<li>부분 수열이기 때문에
<strong>연속 여부는 상관없고, 순서만 지키면 된다.</strong></li>
<li>“이전 값 중 현재 값보다 작은 것들만” 확인한다는 점이 중요하다.</li>
<li>시간복잡도 O(N²) DP는 N이 작을 때(1000 이하) 매우 효과적.</li>
<li>이후에 N이 더 큰 LIS 문제(예: 12015, N ≤ 100만)를 만나면
이분 탐색을 이용한 O(N log N) 풀이가 필요해진다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Baekjoon/Python] 1912. 연속합 (DP, 부분합, 카데인 알고리즘) 🥈2️⃣]]></title>
            <link>https://velog.io/@from-minju/BaekjoonPython-1912.-%EC%97%B0%EC%86%8D%ED%95%A9-DP</link>
            <guid>https://velog.io/@from-minju/BaekjoonPython-1912.-%EC%97%B0%EC%86%8D%ED%95%A9-DP</guid>
            <pubDate>Wed, 26 Nov 2025 15:38:23 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-요약">문제 요약</h1>
<p>길이 N인 수열이 주어진다.
연속된 몇 개의 수를 선택해서 만들 수 있는 합 중 최대값을 구하는 문제.
수는 양수, 음수 모두 나올 수 있다.</p>
<p>“수열에서 연속된 구간 하나를 골랐을 때, 그 합이 최대가 되는 값을 구하라”</p>
<h1 id="풀이-과정">풀이 과정</h1>
<h2 id="1️⃣-첫-시도-오답-접근">1️⃣ 첫 시도 (오답 접근)</h2>
<p>처음 내가 세운 전략은 이랬다.</p>
<p>“음수는 구간 합을 감소시키는 요소니까
양수들만 더해서 그중 최대합을 고르면 되지 않을까?”</p>
<p>하지만 이는 틀린 전략이다. 반례를 살펴보자.</p>
<pre><code>1 2 -1 10</code></pre><p>내 로직대로 하면 <code>1 + 2 = 3</code> → <code>-1</code>에서 끊고 3 저장.
하지만, 실제 최대 연속합은 음수를 포함한다. <code>1 + 2 - 1 + 10 = 12</code></p>
<p>음수가 포함되어 있더라도, 뒤에 나오는 큰 양수들이 그 음수를 덮고도 남으면
음수를 포함한 긴 구간 전체가 최대합이 될 수 있다.</p>
<p>이 지점을 간과해서 틀렸다.</p>
<h2 id="2️⃣-정답-풀이--dpkadane-알고리즘으로-해결하기">2️⃣ 정답 풀이 — DP(kadane 알고리즘)으로 해결하기</h2>
<p><strong><code>dp[i]</code></strong> = i번째 원소로 끝나는 연속 부분 수열의 최대 합
즉, “i에서 끝난다고 가정했을 때, 최선의 합” 만을 관리하는 것.</p>
<p>이때 i에서 할 수 있는 선택은 딱 두 가지다.</p>
<ol>
<li><p>이전까지의 연속합에 현재 값을 이어붙인다
→ dp[i-1] + seq[i]</p>
</li>
<li><p>현재 값 하나로 새 구간을 시작한다
→ seq[i]</p>
</li>
</ol>
<p>그럼 점화식은 자연스럽게 이렇게 나온다.</p>
<blockquote>
<pre><code class="language-python">dp[i] = max(dp[i-1] + seq[i], seq[i])</code></pre>
</blockquote>
<pre><code>
최종 정답은?
→ 전체 i 중에서 **dp[i]의 최댓값**이 바로 우리가 원하는 “연속합 최댓값”


## ✅ 최종 코드
내가 작성한 정답 코드는 아래와 같다.
```python
n = int(input())
seq = list(map(int, input().split()))
dp = [0] * n

dp[0] = seq[0]

for i in range(1, n):
    dp[i] = max(dp[i-1] + seq[i], seq[i])

print(max(dp))</code></pre><h3 id="코드-한-줄씩-해석해보기">코드 한 줄씩 해석해보기</h3>
<ul>
<li><p><code>dp[0] = seq[0]</code></p>
<p>  첫 번째 원소로 끝나는 최대 연속합은 당연히 그 원소 하나뿐.</p>
</li>
<li><p><code>for i in range(1, n):</code></p>
<ul>
<li><p><code>dp[i-1] + seq[i]</code></p>
<p>  → 이전까지 이어오던 구간에 지금 값을 붙였을 때의 합</p>
</li>
<li><p><code>seq[i]</code></p>
<p>  → “아, 그냥 여기서부터 새로 시작하자”라고 마음먹었을 때의 합</p>
</li>
<li><p>둘 중 더 큰 걸 <code>dp[i]</code>에 저장.</p>
</li>
</ul>
</li>
<li><p>마지막에 <code>max(dp)</code></p>
<p>  → “각 위치에서 끝나는 최대합들” 중 가장 큰 값을 정답으로 출력.</p>
</li>
</ul>
</br>

<hr>
<h1 id="💬-gpt의-이런-알고리즘을-떠올리는-팁-정리">💬 GPT의 이런 알고리즘을 떠올리는 팁 정리</h1>
<p>이번 문제 풀면서 “어떻게 이런 점화식을 생각해낼까?” 스스로도 궁금했는데,</p>
<p>정리해보니까 이런 사고 흐름을 타면 훨씬 수월해진다.</p>
<h3 id="1-이전-결과가-항상-좋은-건-아니다를-인정하기">1. “이전 결과가 항상 좋은 건 아니다”를 인정하기</h3>
<p>연속합 문제라 해서</p>
<blockquote>
<p>“앞에서 이어붙이는 게 무조건 이득이겠지?”</p>
<p>라고 생각하면 함정에 빠진다.</p>
</blockquote>
<p>예시:</p>
<pre><code>-999 100
</code></pre><ul>
<li><code>999 + 100 = -899</code></li>
<li><code>100</code> 혼자 = 100</li>
</ul>
<p>이런 예시를 떠올리면</p>
<blockquote>
<p>“아, 이전까지의 합이 오히려 발목을 잡을 수도 있구나”</p>
<p>라는 생각이 자연스럽게 든다.</p>
</blockquote>
<p>그 순간,</p>
<blockquote>
<p>“그럼 어떤 지점에서는 과감히 새로 시작해야겠다”</p>
<p>는 아이디어가 나온다.</p>
</blockquote>
<hr>
<h3 id="2-현재-위치에서-선택지는-딱-두-개뿐이라고-생각하기">2. “현재 위치에서 선택지는 딱 두 개뿐”이라고 생각하기</h3>
<p>DP 점화식 만들 때 제일 큰 힌트:</p>
<blockquote>
<p>“i번째 칸에서 내가 할 수 있는 선택이 뭔지 다 써보자”</p>
</blockquote>
<p>이 문제에서 i번째 위치에서 할 수 있는 선택:</p>
<ol>
<li><p>이전까지의 구간에 <strong>이어붙이기</strong></p>
<p> → <code>dp[i-1] + seq[i]</code></p>
</li>
<li><p><strong>새로 시작하기</strong></p>
<p> → <code>seq[i]</code></p>
</li>
</ol>
<p>딱 둘뿐이다.</p>
<p>선택지를 정리하면 점화식도 훨씬 쉽게 보인다.</p>
<hr>
<h3 id="3-전체-답이-아니라-한-칸-한-칸-최선의-선택에-집중하기">3. “전체 답”이 아니라 “한 칸 한 칸 최선의 선택”에 집중하기</h3>
<p>우리는 자꾸 이런 생각을 한다.</p>
<blockquote>
<p>“최대 연속합 구간이 어디부터 어디까지일까…”</p>
</blockquote>
<p>근데 DP의 관점에서는</p>
<blockquote>
<p>“i에서 끝나는 최대 연속합만 잘 관리하면,</p>
<p>나중에 전체 최댓값은 <code>max(dp)</code>로 한 번에 구할 수 있다.”</p>
</blockquote>
<p>즉,</p>
<p><strong>완성된 구간을 한 번에 찾는 게 아니라, 각 위치에서 최선의 결과를 쌓아가는 느낌</strong>을 익히는 게 중요하다.</p>
<hr>
<h3 id="4-언제-구간을-끊고-새로-시작할지-기준-세우기">4. “언제 구간을 끊고 새로 시작할지” 기준 세우기</h3>
<p>이 문제에서도 구간을 끊는 기준은 명확하다.</p>
<blockquote>
<p>“이전까지의 합에 현재 값을 더한 것” vs “현재 값 하나만”</p>
</blockquote>
<p>둘을 비교해봤을 때</p>
<p>현재 값 하나로 시작하는 게 더 크면 과감히 이전 구간을 버린다.</p>
<p>이 패턴은 다른 DP/그리디류 문제에서도 꽤 자주 등장하는 사고방식이다.</p>
<hr>
<h1 id="🧠-배운-점-정리">🧠 배운 점 정리</h1>
<ul>
<li><p><strong>음수라고 해서 무조건 버리면 안 된다.</strong></p>
<p>  → 뒤에 나오는 큰 양수가 그 음수를 덮을 수 있다.</p>
</li>
<li><p><strong><code>dp[i]를 어떻게 정의할지</code>가 문제의 절반</strong></p>
<p>  → 이 문제에서는 <code>i에서 끝나는 최대 연속합</code>으로 정의하면서 길이감이 확 줄어든다.</p>
</li>
<li><p><strong>현재 위치에서 할 수 있는 선택을 “두 가지” 정도로 좁혀서 생각해보기</strong></p>
<p>  → 이어붙일지 / 새로 시작할지</p>
</li>
<li><p>최종 답은 <code>dp</code> 배열 전체를 보고 <code>max(dp)</code>로 고를 수 있다는 감각 익히기.</p>
</li>
</ul>
<p>이제 1912번은 그냥 “연속합 = 카데인 알고리즘”으로 기억해두고,</p>
<p>비슷한 DP 문제(최대 부분 수열, 누적합 + DP 등)에서 이 패턴을 계속 써먹으면 감각이 훨씬 빨리 늘어날 것 같다.</p>
<hr>
<h1 id="💡-카데인-알고리즘이란">💡 카데인 알고리즘이란?</h1>
<p>“연속된 부분 배열(subarray)의 최대 합”을 가장 빠르게 구하는 알고리즘이다.</p>
<blockquote>
<p>현재 위치에서 끝나는 최대 연속합을 매 순간 업데이트하면서,
그 중 가장 큰 값을 고르는 알고리즘.</p>
</blockquote>
<p>카데인의 강점은 한 번의 반복(O(N))만으로 정답을 구할 수 있다는 점이다.</p>
<h3 id="카데인-알고리즘의-핵심-포인트-요약">카데인 알고리즘의 핵심 포인트 요약</h3>
<p><strong>1) 전체를 한 번에 보지 않고, “i에서 끝나는 최댓값”만 본다</strong>
복잡한 문제를 작은 부분 문제로 쪼개는 DP 사고의 정석.</p>
<p><strong>2) 과거의 합이 미래에도 유리하리란 보장이 없다</strong>
과거에 아무리 많이 쌓아왔더라도
지금 값 하나가 더 크면 그냥 리셋하는 게 낫다.</p>
<p><strong>3) 음수를 만나도 끊지 않는다</strong>
음수를 건너뛰는 게 아니라, 음수를 포함한 전체가 뒤에서 이득이 될 수 있기 때문.</p>
<hr>
]]></description>
        </item>
    </channel>
</rss>