<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>lee_yesol421.log</title>
        <link>https://velog.io/</link>
        <description>문서화를 좋아하는 개발자</description>
        <lastBuildDate>Fri, 08 May 2026 13:41:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>lee_yesol421.log</title>
            <url>https://images.velog.io/images/lee_yesol421/profile/f87aef0b-d0a8-40c7-80ae-2911f85279fb/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. lee_yesol421.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/lee_yesol421" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[20260508 오늘의 학습: 전 영역 워밍업 11문제]]></title>
            <link>https://velog.io/@lee_yesol421/20260508-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%A0%84-%EC%98%81%EC%97%AD-%EC%9B%8C%EB%B0%8D%EC%97%85-11%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@lee_yesol421/20260508-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%A0%84-%EC%98%81%EC%97%AD-%EC%9B%8C%EB%B0%8D%EC%97%85-11%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Fri, 08 May 2026 13:41:26 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<p>지난 세션(4/28)에서는 90도 회전 공식과 약수 함수 슬림화로 3문제 전부 1차 정답을 받았다. 8일 공백에도 <code>arr[n-1-j][i]</code> 공식을 정확히 떠올리며 정착 신호 확인. 함수 작성 4세션 연속 자력 정답으로 추세를 굳혔다.</p>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>시험까지 일주일 남은 시점에서 약 10일 만에 다시 책상 앞에 앉았다. 바로 90분 타이머 모의고사로 들어가는 건 무리라 판단해, <strong>전 영역을 한 문제씩 가볍게 노출</strong>하는 워밍업 트랙을 짰다. 총 11문제. 자료형/조건/반복 → 리스트/문자열/딕셔너리/집합 → 정렬/함수/예외 → 스택/이진탐색/수학/구현 순서로, 지금까지 배운 모든 개념을 자연스럽게 한 번씩 점검한다. 컨디션 회복이 1차 목표, 90분 타이머 진입 가능 여부 진단이 2차 목표.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-반복문--조건-sum_multiples_of_3">1. 반복문 + 조건 (sum_multiples_of_3)</h3>
<p>a부터 b까지(둘 다 포함) 정수 중 3의 배수의 합을 반환. 음수 입력도 처리해야 한다.</p>
<pre><code class="language-python">def sum_multiples_of_3(a, b):
    total = 0
    for i in range(a, b+1):
        if i % 3 == 0:
            total += i
    return total</code></pre>
<p><strong>한 줄 컴프리헨션 형태도 익혀두자</strong> — 시험 빈칸으로 자주 나오는 형태.</p>
<pre><code class="language-python">return sum(i for i in range(a, b+1) if i % 3 == 0)</code></pre>
<blockquote>
<p><strong>수업 중 질문</strong>: 테스트케이스 <code>(-5, 5)</code>의 기대값이 왜 <code>3</code>이지? <code>-3 + 0 + 3</code>이면 <code>0</code> 아닌가?</p>
<p>학습자가 첫 문제부터 출제자 검산을 시작했다. 정확한 지적이고 내 계산 실수였다. 정정: <code>-3 + 0 + 3 = 0</code>이 맞다. 참고로 Python에서 <code>-3 % 3 == 0</code>, <code>0 % 3 == 0</code> 둘 다 성립하므로 <code>n % 3 == 0</code> 조건 하나로 음수까지 잘 잡힌다. 검증 습관이 10일 공백에도 살아있다는 좋은 신호.</p>
</blockquote>
<hr>
<h3 id="2-리스트-슬라이싱--컴프리헨션-filter_reverse">2. 리스트 슬라이싱 + 컴프리헨션 (filter_reverse)</h3>
<p>리스트를 뒤집은 후 k 이상인 값만 필터링.</p>
<pre><code class="language-python">def filter_reverse(nums, k):
    return [i for i in nums if i &gt;= k][::-1]</code></pre>
<h4 id="인사이트-filter와-reverse는-교환-가능">인사이트: filter와 reverse는 교환 가능</h4>
<p>문제는 &quot;뒤집은 후 필터&quot;였는데 풀이는 &quot;필터한 후 뒤집기&quot;다. 결과는 같다 — <strong>둘 다 상대 순서를 보존하는 연산</strong>이라 교환 법칙이 성립하기 때문. 그리고 필터로 먼저 줄인 뒤 뒤집는 게 작업량이 더 적어서 살짝 효율적이기까지 하다.</p>
<pre><code class="language-python">[i for i in nums if i &gt;= k][::-1]   # 효율적
[i for i in nums[::-1] if i &gt;= k]   # 문제 그대로</code></pre>
<p>→ <strong>메모해둘 패턴</strong>: 두 연산의 순서를 바꿔도 결과가 같은지 자문하는 습관. 시험에서 빈칸 답이 두 형태 모두 정답인 경우가 종종 있다.</p>
<hr>
<h3 id="3-문자열-회문-판별-is_palindrome">3. 문자열 회문 판별 (is_palindrome)</h3>
<p>대소문자와 공백을 무시하고 회문 판별.</p>
<pre><code class="language-python">def is_palindrome(s):
    word = s.replace(&quot; &quot;, &quot;&quot;).lower()
    return word == word[::-1]</code></pre>
<p><strong>2단계 공식</strong>: (1) 정규화(공백 제거 + 소문자), (2) <code>word == word[::-1]</code> 비교 한 줄. 회문 패턴은 손에 박힌 상태.</p>
<blockquote>
<p><strong>사이드 노트</strong>: 만약 숫자나 구두점도 무시해야 하는 강한 회문 문제라면 <code>re.sub(r&#39;[^a-z0-9]&#39;, &#39;&#39;, word)</code> 패턴이 필요하다. 오늘은 워밍업이라 안 갔지만 응용 문제에서 떠올릴 수 있게 익혀둔다.</p>
</blockquote>
<hr>
<h3 id="4-딕셔너리--counter--가장-많이-등장한-글자-most_char">4. 딕셔너리 / Counter — 가장 많이 등장한 글자 (most_char)</h3>
<p>가장 많이 등장한 글자와 횟수를 튜플로. 동률이면 먼저 나온 글자 우선.</p>
<h4 id="풀이-a--counter-한-줄">풀이 A — Counter 한 줄</h4>
<pre><code class="language-python">from collections import Counter

def most_char(s):
    return Counter(s).most_common(1)[0]</code></pre>
<h4 id="동률-처리가-자동으로-되는-이유">동률 처리가 자동으로 되는 이유</h4>
<ul>
<li><code>Counter</code>는 <code>dict</code> 상속이라 <strong>삽입 순서를 보존</strong> (Python 3.7+)</li>
<li><code>most_common(n)</code>은 내부적으로 <code>heapq.nlargest</code>를 쓰는데, 이건 <strong>동률일 때 입력 순서를 유지</strong>하는 안정 정렬</li>
<li>그래서 &quot;먼저 등장한 글자&quot;가 자동으로 앞에 옴</li>
</ul>
<p>→ <code>most_common(1)[0]</code>은 거의 공식처럼 외워도 된다.</p>
<blockquote>
<p><strong>수업 중 질문</strong>: <code>dict.get()</code> 방식으로도 풀어보다가 막혔다. 이 코드는 왜 에러나?</p>
<pre><code class="language-python">tmp_dict = {}
for c in s:
    tmp_dict[c] = tmp_dict.get(c, 0) + 1
return max(tmp_dict, key=lambda x: x[1])   # IndexError 발생</code></pre>
<p>원인은 <strong><code>max(tmp_dict, ...)</code>가 딕셔너리에서 key만 꺼낸다</strong>는 것. 그래서 <code>lambda x: x[1]</code>의 <code>x</code>는 글자 한 개(<code>&#39;a&#39;</code>, <code>&#39;p&#39;</code> 등)가 되고, 한 글자짜리 문자열에 <code>x[1]</code>을 하면 IndexError가 난다.</p>
<p><strong>수정</strong>: <code>.items()</code>를 넘긴다.</p>
<pre><code class="language-python">return max(tmp_dict.items(), key=lambda x: x[1])</code></pre>
<p>이렇게 하면 <code>x</code>가 <code>(&#39;a&#39;, 1)</code> 같은 튜플이 되고 <code>x[1]</code>이 횟수가 된다. 그리고 동률 처리는 <code>Counter</code> 버전과 동일 — <code>dict.items()</code>가 삽입 순서로 순회하고 <code>max</code>는 먼저 본 max를 반환하므로 &quot;먼저 등장한 글자&quot;가 자동으로 우선된다.</p>
</blockquote>
<h4 id="자료구조와-순회-패턴-정리">자료구조와 순회 패턴 정리</h4>
<table>
<thead>
<tr>
<th>표현</th>
<th>순회 시 꺼내는 것</th>
</tr>
</thead>
<tbody><tr>
<td><code>for x in dict</code></td>
<td><strong>key만</strong></td>
</tr>
<tr>
<td><code>for x in dict.values()</code></td>
<td>value만</td>
</tr>
<tr>
<td><code>for x in dict.items()</code></td>
<td><strong>(key, value) 튜플</strong></td>
</tr>
</tbody></table>
<p><code>max</code> / <code>min</code> / <code>sorted</code> / <code>for</code> 모두 같은 규칙. 한 번 의식해두면 비슷한 에러 다시 안 난다.</p>
<hr>
<h3 id="5-집합set-교집합-common_chars">5. 집합(set) 교집합 (common_chars)</h3>
<p>두 문자열에 모두 등장하는 글자를 알파벳순으로.</p>
<pre><code class="language-python">def common_chars(a, b):
    return sorted(set(a) &amp; set(b))</code></pre>
<h4 id="집합-연산자-4가지-시험-빈출">집합 연산자 4가지 (시험 빈출)</h4>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
<th>예시 (<code>A={1,2,3}</code>, <code>B={2,3,4}</code>)</th>
</tr>
</thead>
<tbody><tr>
<td><code>A &amp; B</code></td>
<td>교집합 (intersection)</td>
<td><code>{2, 3}</code></td>
</tr>
<tr>
<td><code>A | B</code></td>
<td>합집합 (union)</td>
<td><code>{1, 2, 3, 4}</code></td>
</tr>
<tr>
<td><code>A - B</code></td>
<td>차집합 (difference)</td>
<td><code>{1}</code></td>
</tr>
<tr>
<td><code>A ^ B</code></td>
<td>대칭차 (symmetric difference)</td>
<td><code>{1, 4}</code></td>
</tr>
</tbody></table>
<h4 id="슬림화-체크--신규-약점-자연-노출">슬림화 체크 — 신규 약점 자연 노출</h4>
<p>처음 작성한 풀이는 <code>sorted(list(set(a) &amp; set(b)))</code> 였다. <strong><code>list()</code> 호출이 불필요</strong>하다. <code>sorted()</code>는 어떤 iterable이든 받아서 항상 list를 반환하기 때문.</p>
<p>이게 4/27에 새로 잡힌 약점인 <strong>&quot;불필요 변환/분기 추가&quot;</strong> 패턴이다. 자연 노출에서 1회 발생. 의식하면 다음번엔 안 나올 가능성 높음.</p>
<p>함수 작성 후 자기 점검 한 마디:</p>
<blockquote>
<p>&quot;이 변환/분기 정말 필요한가? 자료구조나 호출 결과를 그대로 넘기면 사라지지 않나?&quot;</p>
</blockquote>
<hr>
<h3 id="6-다중-정렬-sort_students">6. 다중 정렬 (sort_students)</h3>
<p><code>(이름, 점수)</code> 튜플 리스트를 점수 내림차순, 동점이면 이름 오름차순으로 정렬.</p>
<pre><code class="language-python">def sort_students(students):
    return sorted(students, key=lambda x: (-x[1], x[0]))</code></pre>
<h4 id="튜플-key의-동작-원리">튜플 key의 동작 원리</h4>
<pre><code>key=lambda x: (-x[1], x[0])
        ┌─────┐  ┌────┐
        점수    이름
        내림    오름</code></pre><ol>
<li>첫 원소 <code>-x[1]</code>로 먼저 비교 → 음수로 만들어서 <strong>큰 점수가 앞에</strong></li>
<li>첫 원소가 같으면 둘째 원소 <code>x[0]</code>(이름)으로 오름차순 비교</li>
<li><strong>튜플은 lexicographic 비교</strong>라 자동으로 1차 → 2차 정렬이 작동</li>
</ol>
<p>→ 음수 트릭으로 내림차순 + 두 번째 키로 동률 처리. <strong>COS Pro 빈출 1순위 패턴</strong>.</p>
<hr>
<h3 id="7-함수-기본값--예외-처리-safe_get">7. 함수 (기본값) + 예외 처리 (safe_get)</h3>
<p><code>nums[idx]</code>를 안전하게 반환. 범위를 벗어나면 default 반환.</p>
<pre><code class="language-python">def safe_get(nums, idx, default=None):
    try:
        return nums[idx]
    except IndexError:
        return default</code></pre>
<blockquote>
<p><strong>수업 중 질문</strong>: try/except 구문이 바로 안 떠올라서 옛날 자료를 찾아봤다. 나도 모르게 try-catch로 생각하고 있더라.</p>
<p>Java 출신이라 자연스러운 슬립이고, 7차 학습 이후 한 달 반 만의 자연 노출이라 더 그렇다. 로직은 완전히 손에 있는데 문법 키워드만 슬쩍 미끄러진 상태. 다시 굳히는 차원에서 매핑 정리.</p>
</blockquote>
<h4 id="java-→-python-tryexcept-매핑">Java → Python try/except 매핑</h4>
<pre><code class="language-python">try:
    risky()
except IndexError as e:    # catch → except, (X) → as
    handle(e)
except (KeyError, ValueError):   # 여러 개 잡기 (튜플)
    handle()
else:                       # try가 예외 없이 끝났을 때만 (Java엔 없음)
    print(&quot;성공&quot;)
finally:
    cleanup()</code></pre>
<table>
<thead>
<tr>
<th>Java</th>
<th>Python</th>
<th>메모</th>
</tr>
</thead>
<tbody><tr>
<td><code>try</code></td>
<td><code>try</code></td>
<td>동일</td>
</tr>
<tr>
<td><code>catch (Exception e)</code></td>
<td><strong><code>except Exception as e</code></strong></td>
<td>키워드/문법 다름</td>
</tr>
<tr>
<td><code>finally</code></td>
<td><code>finally</code></td>
<td>동일</td>
</tr>
<tr>
<td>❌ 없음</td>
<td><strong><code>else</code></strong></td>
<td>try가 성공했을 때만 실행 (보너스 절)</td>
</tr>
</tbody></table>
<p><strong>시험 함정 포인트</strong>:</p>
<ul>
<li><code>return</code>이 try에 있어도 <strong><code>finally</code>는 반드시 실행</strong>된다</li>
<li><code>else</code>는 <code>try</code>가 예외 없이 끝났을 때만, <strong><code>except</code>보다 먼저</strong> 위치</li>
</ul>
<h4 id="음수-인덱스가-자동-처리되는-점">음수 인덱스가 자동 처리되는 점</h4>
<p><code>nums[-1]</code>은 마지막 원소를 정상 반환하고, <strong>범위를 벗어난 음수</strong>(<code>nums[-10]</code> for <code>len=3</code>)에서만 <code>IndexError</code>를 던진다. C/Java라면 음수 인덱스 자체에서 별도 분기를 짜야 했을 텐데 Python은 한 줄에 처리됨.</p>
<hr>
<h3 id="8-스택--다종-괄호-매칭-is_balanced">8. 스택 — 다종 괄호 매칭 (is_balanced)</h3>
<p><code>( ) [ ] { }</code> 6가지 괄호의 짝과 순서를 모두 검사.</p>
<pre><code class="language-python">def is_balanced(s):
    pairs = {&quot;)&quot;: &quot;(&quot;, &quot;]&quot;: &quot;[&quot;, &quot;}&quot;: &quot;{&quot;}
    stack = []
    for c in s:
        if c in pairs.values():
            stack.append(c)
        elif c in pairs:
            if not stack or stack[-1] != pairs[c]:
                return False
            stack.pop()
    return not stack</code></pre>
<h4 id="정착-약점-두-개가-자연스럽게-등장">정착 약점 두 개가 자연스럽게 등장</h4>
<p>19차에서 학습한 두 가지가 다시 자연 재노출에서 안정적으로 나왔다.</p>
<ul>
<li>✅ <strong>무엇 저장</strong>: 여는 괄호를 스택에 쌓는다</li>
<li>✅ <strong>빈 스택 체크</strong>: <code>not stack</code> 단락평가로 <code>stack[-1]</code> 호출을 보호</li>
</ul>
<p>3가지 False 조건도 모두 처리:</p>
<ol>
<li>빈 스택에서 닫는 괄호 등장 (<code>not stack</code>)</li>
<li>짝이 안 맞음 (<code>stack[-1] != pairs[c]</code>)</li>
<li>끝났는데 스택에 남은 여는 괄호 (<code>return not stack</code>)</li>
</ol>
<h4 id="마이크로-최적화-시험엔-영향-없음">마이크로 최적화 (시험엔 영향 없음)</h4>
<p><code>c in pairs.values()</code>는 매번 values 순회라 O(n)이고, <code>c in pairs</code>는 dict 해시라 O(1). 문자열 상수로 꺼내두면 더 빠르고 시험 답안 같은 느낌:</p>
<pre><code class="language-python">opens = &quot;([{&quot;
pairs = {&quot;)&quot;: &quot;(&quot;, &quot;]&quot;: &quot;[&quot;, &quot;}&quot;: &quot;{&quot;}
for c in s:
    if c in opens:
        stack.append(c)
    elif c in pairs:
        if not stack or stack[-1] != pairs[c]:
            return False
        stack.pop()</code></pre>
<hr>
<h3 id="9-이진-탐색--기본-템플릿-binary_search">9. 이진 탐색 — 기본 템플릿 (binary_search)</h3>
<p>정렬된 리스트에서 target 인덱스 반환. 없으면 -1.</p>
<pre><code class="language-python">def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left &lt;= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] &lt; target:
            left = mid + 1
        else:
            right = mid - 1
    return -1</code></pre>
<h4 id="디테일-4가지--시험-빈출-함정-포인트">디테일 4가지 — 시험 빈출 함정 포인트</h4>
<ul>
<li><code>while left &lt;= right</code> (등호 포함, 원소 1개 케이스 처리)</li>
<li><code>(left + right) // 2</code> (정수 나눗셈, <code>/</code>면 float이 되어 인덱싱 에러)</li>
<li><code>mid + 1</code> / <code>mid - 1</code> (무한루프 방지)</li>
<li>빈 리스트 자동 처리 (<code>right = -1</code>이라 루프 안 들어감)</li>
</ul>
<p><strong><code>/</code> vs <code>//</code></strong> 디버깅 함정은 22차에서 직접 잡아본 문제. 그 경험이 자산이 됐다.</p>
<hr>
<h3 id="10-수학--소수-판별-√n-최적화-is_prime">10. 수학 — 소수 판별 √n 최적화 (is_prime)</h3>
<pre><code class="language-python">def is_prime(n):
    if n &lt;= 1:
        return False
    i = 2
    while i * i &lt;= n:
        if n % i == 0:
            return False
        i += 1
    return True</code></pre>
<blockquote>
<p><strong>수업 중 질문</strong>: <code>range(2, int(n**0.5) + 1)</code> 이 형태는 뭐지?</p>
<p>while 형태와 <strong>완전히 같은 범위를 도는 for 형태</strong>다. 메커니즘만 다르다.</p>
</blockquote>
<h4 id="두-형태-비교">두 형태 비교</h4>
<pre><code class="language-python"># 정수 연산만 사용 (안전)
i = 2
while i * i &lt;= n:
    ...
    i += 1

# 동등한 for 형태 (sqrt 직접 계산)
for i in range(2, int(n**0.5) + 1):
    ...</code></pre>
<p>조각조각 뜯어보면 (n=25 기준):</p>
<table>
<thead>
<tr>
<th>표현</th>
<th>값</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>n ** 0.5</code></td>
<td><code>5.0</code></td>
<td>√n 계산 (float 반환)</td>
</tr>
<tr>
<td><code>int(...)</code></td>
<td><code>5</code></td>
<td>소수점 버림 (양수에선 floor)</td>
</tr>
<tr>
<td><code>+ 1</code></td>
<td><code>6</code></td>
<td>range 끝값은 미포함이라 +1</td>
</tr>
<tr>
<td><code>range(2, 6)</code></td>
<td><code>2, 3, 4, 5</code></td>
<td>i가 2~5까지 순회</td>
</tr>
</tbody></table>
<h4 id="어느-쪽이-좋은가">어느 쪽이 좋은가</h4>
<table>
<thead>
<tr>
<th>형태</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><code>while i*i &lt;= n</code></td>
<td><strong>정수 연산만</strong> 사용 (안전)</td>
<td>for문 한 줄로 못 끝냄</td>
</tr>
<tr>
<td><code>range(2, int(n**0.5)+1)</code></td>
<td>for문 한 줄로 깔끔</td>
<td>float 거치므로 <strong>아주 큰 수</strong>에서 정밀도 함정 가능</td>
</tr>
</tbody></table>
<p>→ COS Pro 입력 크기에서는 둘 다 정답. 빈칸으로 어느 쪽이 나와도 헷갈리지 않게 둘 다 알아둔다.</p>
<hr>
<h3 id="11-구현--90도-시계방향-회전-rotate">11. 구현 — 90도 시계방향 회전 (rotate)</h3>
<p>N×N 행렬을 시계방향으로 90도 돌린 새 리스트를 반환.</p>
<pre><code class="language-python">def rotate(arr):
    n = len(arr)
    rotated = [[0] * n for _ in range(n)]
    for i in range(n):
        for j in range(n):
            rotated[j][n - 1 - i] = arr[i][j]
    return rotated</code></pre>
<h4 id="두-가지-매핑-스타일-둘-다-정답">두 가지 매핑 스타일 (둘 다 정답)</h4>
<pre><code class="language-python">rotated[j][n-1-i] = arr[i][j]   # 원본 → 회전 매핑 (이번 풀이)
rotated[i][j] = arr[n-1-j][i]   # 회전 → 원본 매핑 (4/28 학습)</code></pre>
<p>루프를 도는 관점이 다를 뿐, 결과는 동일.</p>
<blockquote>
<p><strong>수업 중 질문</strong>: 이건 시계방향이지? 만약 반대로(반시계) 돌린다면?</p>
</blockquote>
<h4 id="회전-4종-매핑-정리">회전 4종 매핑 정리</h4>
<table>
<thead>
<tr>
<th>방향</th>
<th>원본 → 회전 매핑</th>
<th>회전 → 원본 매핑</th>
<th>zip 한 줄형</th>
</tr>
</thead>
<tbody><tr>
<td><strong>CW (시계)</strong></td>
<td><code>rotated[j][n-1-i] = arr[i][j]</code></td>
<td><code>rotated[i][j] = arr[n-1-j][i]</code></td>
<td><code>zip(*arr[::-1])</code></td>
</tr>
<tr>
<td><strong>CCW (반시계)</strong></td>
<td><code>rotated[n-1-j][i] = arr[i][j]</code></td>
<td><code>rotated[i][j] = arr[j][n-1-i]</code></td>
<td><code>list(zip(*arr))[::-1]</code></td>
</tr>
</tbody></table>
<h4 id="시각적-기억법--외우는-것보다-안-까먹음">시각적 기억법 — 외우는 것보다 안 까먹음</h4>
<pre><code>    원본            CW (시계)       CCW (반시계)
   1 2 3            7 4 1           3 6 9
   4 5 6     →      8 5 2           2 5 8
   7 8 9            9 6 3           1 4 7</code></pre><ul>
<li><strong>CW</strong>: 첫 행(1,2,3)이 → <strong>마지막 열</strong>로 (왼쪽 위 → 오른쪽 위)</li>
<li><strong>CCW</strong>: 첫 행(1,2,3)이 → <strong>첫 열을 거꾸로</strong> (왼쪽 위 → 왼쪽 아래)</li>
</ul>
<h4 id="보너스--회전-패밀리-4종">보너스 — 회전 패밀리 4종</h4>
<ul>
<li><strong>시계 90도</strong>: <code>arr[n-1-j][i]</code></li>
<li><strong>반시계 90도</strong>: <code>arr[j][n-1-i]</code></li>
<li><strong>180도</strong>: <code>rotated[i][j] = arr[n-1-i][n-1-j]</code> (양쪽 다 뒤집힘)</li>
<li><strong>전치(transpose, 대각선 대칭)</strong>: <code>rotated[i][j] = arr[j][i]</code> 또는 <code>zip(*arr)</code></li>
</ul>
<p>CW 두 번 = 180도 = CCW 두 번. 회전 패밀리는 이 4가지가 전부다.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<p>11문제 1차 정답률 <strong>11/11 (100%)</strong> — 약 10일 공백 후 컨디션 회복용 워밍업 완료. 자연 노출 약점 1건(불필요한 <code>list()</code> 변환)만 발생, 의식 후 다음 노출 정착 판정 예정. <strong>90분 타이머 모의고사 진입 가능 신호</strong> 확인 — 다음 세션은 구름EDU 기출 4회차 풀타임 풀이.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260428 오늘의 학습: 2차원 회전 복습 + 약수 함수 슬림화]]></title>
            <link>https://velog.io/@lee_yesol421/20260428-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-2%EC%B0%A8%EC%9B%90-%ED%9A%8C%EC%A0%84-%EB%B3%B5%EC%8A%B5-%EC%95%BD%EC%88%98-%ED%95%A8%EC%88%98-%EC%8A%AC%EB%A6%BC%ED%99%94</link>
            <guid>https://velog.io/@lee_yesol421/20260428-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-2%EC%B0%A8%EC%9B%90-%ED%9A%8C%EC%A0%84-%EB%B3%B5%EC%8A%B5-%EC%95%BD%EC%88%98-%ED%95%A8%EC%88%98-%EC%8A%AC%EB%A6%BC%ED%99%94</guid>
            <pubDate>Tue, 28 Apr 2026 12:34:21 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<p>지난 세션(4/27)에서는 수학 파트(소수 판별, 약수, GCD, LCM)를 한 번에 정리하며 4문제 전부 1차 정답으로 끝냈다. 다만 함수 작성에서 <strong>같은 값을 가진 변수 2개</strong>, <strong>불필요한 엣지 분기</strong> 같은 비-슬림한 패턴이 나와서 &quot;코드 슬림화 체크리스트 3가지&quot;를 신규 약점으로 정리했다. 함수 작성 자력 정답은 21차 → 22차 → 23차로 3세션 연속 진행 중.</p>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>오늘은 가벼운 보강 세션. 두 가지 트랙으로 갔다. 먼저 4/20에 학습한 <strong>90도 회전 공식</strong>이 이번엔 8일 공백이라 망각 점검 — 인덱스 직접 / zip 패턴 두 형태로 빈칸과 디버깅 한 문제씩. 그다음 어제 신규 약점인 <strong>슬림화 의식</strong>을 보강하기 위해 약수 함수 작성 후 직접 작성한 풀이를 슬림화 체크리스트로 자기 점검.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-90도-시계방향-회전--인덱스-직접-매핑">1. 90도 시계방향 회전 — 인덱스 직접 매핑</h3>
<p>n × n 2차원 배열을 시계 방향으로 90도 돌리는 가장 직관적인 풀이. 핵심 공식 한 줄을 외우면 끝난다.</p>
<blockquote>
<p><strong><code>rotated[i][j] = arr[n-1-j][i]</code></strong></p>
</blockquote>
<h4 id="왜-이렇게-매핑되는가">왜 이렇게 매핑되는가?</h4>
<p>회전 후 i행은 <strong>원본 i열을 아래에서 위로 읽은 값</strong>으로 채워진다. 3×3 예시로 추적해보면 명확해진다.</p>
<p>원본:</p>
<pre><code>[1, 2, 3]
[4, 5, 6]
[7, 8, 9]</code></pre><p>시계 방향 90도 회전 후 (기대):</p>
<pre><code>[7, 4, 1]
[8, 5, 2]
[9, 6, 3]</code></pre><p>회전 후 0행 <code>[7, 4, 1]</code>은 <strong>원본의 0열 <code>[1, 4, 7]</code>을 거꾸로</strong> 읽은 값이다. 그래서:</p>
<table>
<thead>
<tr>
<th>위치 (i, j)</th>
<th>값</th>
<th>매핑</th>
</tr>
</thead>
<tbody><tr>
<td>rotated[0][0] = 7</td>
<td>arr[2][0]</td>
<td><code>arr[n-1-j][i]</code> = arr[2][0] ✓</td>
</tr>
<tr>
<td>rotated[0][1] = 4</td>
<td>arr[1][0]</td>
<td><code>arr[n-1-j][i]</code> = arr[1][0] ✓</td>
</tr>
<tr>
<td>rotated[0][2] = 1</td>
<td>arr[0][0]</td>
<td><code>arr[n-1-j][i]</code> = arr[0][0] ✓</td>
</tr>
</tbody></table>
<p>→ <strong>행 인덱스에는 <code>n-1-j</code>(거꾸로), 열 인덱스에는 <code>i</code>(원본 열)</strong> 가 들어간다.</p>
<h4 id="빈칸-풀이">빈칸 풀이</h4>
<pre><code class="language-python">def rotate_90(arr):
    n = len(arr)
    rotated = [[0] * n for _ in range(n)]
    for i in range(n):
        for j in range(n):
            rotated[i][j] = arr[n - 1 - j][i]   # ← 핵심
    return rotated</code></pre>
<blockquote>
<p>⚠️ <strong>함정 후보</strong>: <code>arr[n-1-i][j]</code>로 쓰면 <strong>반시계 방향</strong>이 된다. 시험 디버깅 문제로 충분히 등장 가능. i와 j 자리 바뀌는 거 항상 주의.</p>
</blockquote>
<hr>
<h3 id="2-90도-회전--zip-패턴-뒤집기--전치">2. 90도 회전 — zip 패턴 (뒤집기 + 전치)</h3>
<p>같은 회전을 Python 답게 한 줄로 쓰는 방법도 있다. 이게 시험 디버깅에 자주 등장.</p>
<pre><code class="language-python">[list(row) for row in zip(*arr[::-1])]</code></pre>
<p>이 한 줄을 <strong>두 단계로 분해</strong>해서 이해하면 외우기 쉬워진다.</p>
<h4 id="단계-1-arr-1--행-순서-뒤집기">단계 1: <code>arr[::-1]</code> — 행 순서 뒤집기</h4>
<pre><code>원본:           뒤집기 후:
[1, 2, 3]      [7, 8, 9]
[4, 5, 6]  →   [4, 5, 6]
[7, 8, 9]      [1, 2, 3]</code></pre><h4 id="단계-2-zip뒤집힌_배열--전치transpose">단계 2: <code>zip(*뒤집힌_배열)</code> — 전치(transpose)</h4>
<p><code>zip</code>은 <strong>여러 시퀀스에서 i번째 원소를 묶어주는</strong> 함수. <code>*</code> 언패킹으로 각 행을 인자로 펼쳐 넣으면 열 단위로 묶이는 효과가 난다.</p>
<pre><code class="language-python">zip([7,8,9], [4,5,6], [1,2,3])
→ (7,4,1), (8,5,2), (9,6,3)</code></pre>
<p>list로 감싸면 회전 결과 완성. 인덱스를 직접 쓰지 않고 끝낸다.</p>
<h4 id="디버깅-문제--어디가-버그">디버깅 문제 — 어디가 버그?</h4>
<pre><code class="language-python">def rotate_90(arr):
    flipped = arr[::]                              # ⓐ
    return [list(row) for row in zip(*flipped)]    # ⓑ</code></pre>
<p>ⓐ에 버그가 있다. <code>arr[::]</code>은 단순 복사(슬라이싱으로 새 리스트 만들기)일 뿐, <strong>순서를 뒤집지 않는다</strong>. 그래서 zip만 호출되면 그냥 전치(transpose)가 되어버려 회전이 아닌 전혀 다른 결과가 나온다.</p>
<p><strong>한 줄 수정</strong>:</p>
<pre><code class="language-python">flipped = arr[::-1]</code></pre>
<h4 id="디버깅-풀이-흐름-직접-설명">디버깅 풀이 흐름 (직접 설명)</h4>
<blockquote>
<p>&quot;zip 부분이 마지막 행에서부터 하나씩 뽑아야 되니 마지막 행이 처음 오도록 뒤집어야 함&quot;</p>
</blockquote>
<p>zip의 동작이 <strong>첫 행부터 차례로 열 단위로 묶는</strong> 거라는 점을 이해하면, 시계 방향 회전 결과의 0행이 &quot;원본 마지막 행의 0번째 값들&quot;이 되려면 마지막 행이 맨 앞에 와야 한다는 게 자연스럽게 따라온다. 4/20에 학습한 &quot;뒤집기 + 전치&quot; 분해가 도구로 자리잡은 형태.</p>
<hr>
<h3 id="3-약수-구하기--직접-작성--슬림화-비교">3. 약수 구하기 — 직접 작성 + 슬림화 비교</h3>
<p>어제(4/27)에 √n 최적화 패턴으로 약수 짝꿍 구조를 다뤘다면, 오늘은 그걸 <strong>함수 작성</strong>으로 응용하면서 슬림화 의식을 점검.</p>
<h4 id="문제">문제</h4>
<p>자연수 n의 모든 약수를 오름차순 리스트로 반환. 시간 복잡도 O(√n).</p>
<pre><code class="language-python">divisors(12)  # [1, 2, 3, 4, 6, 12]
divisors(36)  # [1, 2, 3, 4, 6, 9, 12, 18, 36]   ← 제곱수
divisors(7)   # [1, 7]                            ← 소수
divisors(1)   # [1]                               ← 엣지</code></pre>
<h4 id="직접-작성한-풀이">직접 작성한 풀이</h4>
<pre><code class="language-python">def divisors(n):
    small = []
    large = []
    i = 1
    while i * i &lt;= n:
        if n % i == 0:                  # 나누어떨어짐(약수)
            small.append(i)
            if n // i != i:              # i로 n 나눈 수가 i 아니면 두 번째도 추가
                large.append(n // i)
        i += 1
    return small + large[::-1]           # large는 큰 수부터 들어가있으니 역순</code></pre>
<p><strong>1차 정답</strong> — 5개 테스트 전부 통과.</p>
<p>이 풀이의 좋은 점:</p>
<ul>
<li>제곱수 함정(<code>if n // i != i</code>)을 정확히 처리</li>
<li><code>n == 1</code> 같은 안전망 엣지 분기를 <strong>추가하지 않음</strong> (메인 while 루프가 자연 처리)</li>
<li>같은 값 변수 중복 없음</li>
</ul>
<p>→ <strong>어제(23차) 신규 약점이던 &quot;안전망 분기 추가&quot; 패턴이 1회 노출에서 발생하지 않음</strong>. 어제 학습한 슬림화 체크리스트 3가지가 자기 점검으로 작동한 신호.</p>
<h4 id="슬림화-체크--한-발-더-짧게-가는-길">슬림화 체크 — 한 발 더 짧게 가는 길</h4>
<p>학습자 풀이는 <strong>시간 복잡도 측면에서 살짝 우위</strong>다 (reverse는 O(d), sorted는 O(d log d)). 의식적인 성능 선택이라면 그대로 둬도 좋다. 다만 <strong>코드 길이/가독성</strong> 관점에서 보면 더 짧은 형태가 있다.</p>
<p><strong>대안 1: 한 리스트 + sorted</strong></p>
<pre><code class="language-python">def divisors(n):
    result = []
    i = 1
    while i * i &lt;= n:
        if n % i == 0:
            result.append(i)
            if i != n // i:
                result.append(n // i)
        i += 1
    return sorted(result)</code></pre>
<p>리스트 분리 없이 한곳에 다 넣고 마지막에 정렬. 코드 짧아짐.</p>
<p><strong>대안 2: set + sorted (가장 슬림)</strong></p>
<pre><code class="language-python">def divisors(n):
    result = set()
    i = 1
    while i * i &lt;= n:
        if n % i == 0:
            result.add(i)
            result.add(n // i)
        i += 1
    return sorted(result)</code></pre>
<p>여기선 <strong>제곱수 분기(<code>if i != n // i</code>) 자체가 사라진다.</strong> set이 중복을 자동으로 제거해주니까. 메인 자료구조를 바꾸는 것만으로 분기 한 줄이 통째로 없어지는 패턴.</p>
<h4 id="메타-포인트--자료구조-선택을-통한-슬림화">메타 포인트 — 자료구조 선택을 통한 슬림화</h4>
<table>
<thead>
<tr>
<th>풀이</th>
<th>분기 줄</th>
<th>코드 길이</th>
<th>시간복잡도</th>
</tr>
</thead>
<tbody><tr>
<td>small/large 분리 + reverse</td>
<td>1줄 (제곱수 체크)</td>
<td>가장 김</td>
<td>O(√n + d)</td>
</tr>
<tr>
<td>한 리스트 + sorted</td>
<td>1줄 (제곱수 체크)</td>
<td>중간</td>
<td>O(√n + d log d)</td>
</tr>
<tr>
<td><strong>set + sorted</strong></td>
<td><strong>0줄</strong></td>
<td><strong>가장 짧음</strong></td>
<td>O(√n + d log d)</td>
</tr>
</tbody></table>
<p>세 가지 모두 정답이고 trade-off가 있다. 다만 어제 학습한 슬림화 체크리스트에 <strong>항목 하나가 추가</strong>된다.</p>
<blockquote>
<p>💡 <strong>슬림화 자기 점검 — 추가 메타 질문</strong>
4. <strong>이 분기 정말 필요한가? 자료구조 바꾸면 사라지지 않나?</strong></p>
<p>&quot;리스트 + 중복 체크 분기&quot; 자리에 <strong>&quot;set으로 자동 중복 제거&quot;</strong>가 거의 항상 한 번 더 짧게 만들어준다. 약수 문제뿐 아니라 경계 순회(20차 좌표 visited), 부분합 중복 등에도 동일.</p>
</blockquote>
<h4 id="java-vs-python--자료구조-선택의-무게">Java vs Python — 자료구조 선택의 무게</h4>
<table>
<thead>
<tr>
<th>측면</th>
<th>Java</th>
<th>Python</th>
</tr>
</thead>
<tbody><tr>
<td>HashSet 사용</td>
<td><code>Set&lt;Integer&gt; set = new HashSet&lt;&gt;();</code></td>
<td><code>result = set()</code></td>
</tr>
<tr>
<td>선언 무게</td>
<td>타입 명시 + 제네릭</td>
<td>한 단어</td>
</tr>
<tr>
<td>변환 비용</td>
<td><code>new ArrayList&lt;&gt;(set)</code> + sort</td>
<td><code>sorted(result)</code> 한 번에</td>
</tr>
</tbody></table>
<p>Java에서는 자료구조 바꾸는 게 선언/변환 비용 때문에 <strong>약간의 결심</strong>이 필요한 행동인데, Python은 그 비용이 거의 없다. 그래서 Python에선 <strong>&quot;분기를 자료구조로 흡수&quot;</strong>하는 게 더 자연스러운 선택지가 된다.</p>
<hr>
<h3 id="4-공식-재노출-주기-관리--8일-공백-결과">4. 공식 재노출 주기 관리 — 8일 공백 결과</h3>
<p>4/20(20차)에서 90도 회전 공식을 5일 공백으로 망각했던 적이 있어, 그 후 재노출 주기를 의식하고 있었다. 오늘은 4/20 이후 <strong>8일 공백</strong> 상태에서 첫 재노출.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td>인덱스 형태 (<code>arr[n-1-j][i]</code>)</td>
<td>정확히 떠올림 ✓</td>
</tr>
<tr>
<td>zip 패턴 (<code>arr[::-1]</code>)</td>
<td>디버깅 정답 + 자체 설명 ✓</td>
</tr>
</tbody></table>
<p><strong>8일 공백에도 두 형태 모두 안정</strong> → 4/20 망각 사례 이후 첫 재노출에서 정착 신호. 다음 자연 재노출은 5/12 전후 오답 총복습 기간으로 예정.</p>
<blockquote>
<p>📌 <strong>공백 주기 관리 메타</strong> — &quot;공식은 망각 사이클을 의식하고 다시 만지기&quot;라는 학습 운영이 한 번 검증됨. 새 공식을 외울 때마다 다음 재노출 시점을 머릿속에 한 번 박아두는 게 효과적.</p>
</blockquote>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<p>오늘 푼 문제는 3문제(빈칸 1 + 디버깅 1 + 함수 작성 1), <strong>1차 정답률 3/3 (100%)</strong>. 함수 작성 자력 정답이 21차 → 22차 → 23차 → 24차로 <strong>4세션 연속</strong>, 어제 신규 약점이던 슬림화 의식도 1회 노출에서 안전망 분기 추가 없이 통과. 다음 학습은 4/29(수)부터 BFS/DFS 시작 — 그래프 탐색 본격 진입.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260427 오늘의 학습: 수학 기초 (소수 판별, 약수, GCD, LCM)]]></title>
            <link>https://velog.io/@lee_yesol421/20260427-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%88%98%ED%95%99-%EA%B8%B0%EC%B4%88-%EC%86%8C%EC%88%98-%ED%8C%90%EB%B3%84-%EC%95%BD%EC%88%98-GCD-LCM</link>
            <guid>https://velog.io/@lee_yesol421/20260427-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%88%98%ED%95%99-%EA%B8%B0%EC%B4%88-%EC%86%8C%EC%88%98-%ED%8C%90%EB%B3%84-%EC%95%BD%EC%88%98-GCD-LCM</guid>
            <pubDate>Mon, 27 Apr 2026 11:38:16 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<p>지난 세션(4/24)에서는 이진 탐색을 처음 학습하며 빈칸/디버깅/함수 작성 4문제 전부 1차 정답으로 끝냈다. 코드 추적 습관이 강화돼 <code>print(f&quot;left=... mid=...&quot;)</code>로 실행을 시뮬레이션하며 풀이하는 흐름이 자리잡았다. bisect 라이브러리는 완성형에서만 쓰고 빈칸은 직접 구현이 정석이라는 실전 전략도 같이 정리.</p>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>시험 출제 범위 중 자주 등장하는 수학 파트를 한 번에 정리한다. 소수 판별의 √n 최적화부터 시작해서, 같은 원리로 풀리는 약수 구하기, 그리고 외우면 끝나는 공식 두 개(유클리드 호제법, LCM 공식)까지. 마지막은 GCD를 이용해 N개 수의 LCM을 구하는 함수 작성 문제로 마무리.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-소수-판별--√n-최적화">1. 소수 판별 — √n 최적화</h3>
<p>소수는 1과 자기 자신만으로 나누어떨어지는 자연수(1 제외). 가장 단순한 풀이는 2부터 n-1까지 다 나눠보는 것이다.</p>
<pre><code class="language-python">def is_prime(n):
    if n &lt; 2:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True</code></pre>
<p>문제는 n이 1,000,000 같은 큰 수면 백만 번 다 돌아야 한다는 점. 그래서 <strong>√n까지만 보면 충분</strong>하다는 최적화가 핵심이다.</p>
<h4 id="왜-√n까지면-충분한가-시각적-설명">왜 √n까지면 충분한가? (시각적 설명)</h4>
<p>36의 약수 쌍을 적어보면 √36 = 6을 기준으로 거울처럼 대칭이다.</p>
<pre><code>1 × 36 = 36
2 × 18 = 36
3 × 12 = 36
4 × 9  = 36
6 × 6  = 36   ← √36 (중간점)
9 × 4  = 36   ← 위와 대칭
12 × 3 = 36
...</code></pre><p>즉 √n 이하에서 약수를 못 찾으면 √n 초과에서도 못 찾는다(대칭이니까). 그래서 검사 범위를 √n까지로 줄이면 시간복잡도가 O(n) → O(√n)으로 떨어진다.</p>
<pre><code class="language-python">def is_prime(n):
    if n &lt; 2:
        return False
    i = 2
    while i * i &lt;= n:        # i*i &lt;= n 은 i &lt;= √n 과 같음
        if n % i == 0:
            return False
        i += 1
    return True</code></pre>
<blockquote>
<p>⚠️ <strong>포인트</strong>: <code>i * i &lt;= n</code>은 제곱근 함수(<code>math.sqrt</code>)를 쓰지 않고도 같은 효과를 낸다. 실수 오차도 없고 정수 연산이라 빠르다. 이 패턴은 시험에 그대로 등장한다.</p>
</blockquote>
<h4 id="java-vs-python--정수-연산-비교">Java vs Python — 정수 연산 비교</h4>
<table>
<thead>
<tr>
<th>연산</th>
<th>Java</th>
<th>Python</th>
</tr>
</thead>
<tbody><tr>
<td>정수 나눗셈</td>
<td><code>5 / 2 = 2</code> (자동)</td>
<td><code>5 / 2 = 2.5</code> (실수), <code>5 // 2 = 2</code></td>
</tr>
<tr>
<td>나머지</td>
<td><code>%</code></td>
<td><code>%</code></td>
</tr>
<tr>
<td>거듭제곱</td>
<td><code>Math.pow(a, b)</code> (실수 반환)</td>
<td><code>a ** b</code> (정수 반환)</td>
</tr>
</tbody></table>
<p>Java에서 <code>int / int = int</code> 로 자동 정수화되는 게, Python에서는 명시적으로 <code>//</code>로 적어줘야 한다. <strong>22차 이진 탐색에서도 이 함정이 디버깅 문제로 나왔는데</strong>, 오늘 LCM 공식에서도 같은 패턴이 또 등장. 정수 결과가 필요한 곳에선 무조건 <code>//</code> — 외우자.</p>
<hr>
<h3 id="2-약수-구하기--짝꿍-패턴">2. 약수 구하기 — 짝꿍 패턴</h3>
<p>약수 구하기도 √n 트릭을 그대로 쓴다. 다만 핵심 아이디어가 하나 더 추가된다.</p>
<blockquote>
<p><strong>i가 약수면 <code>n // i</code>도 자동으로 약수다.</strong> 두 개를 동시에 수집하면 √n까지만 돌면 된다.</p>
</blockquote>
<pre><code class="language-python">def get_divisors(n):
    small = []
    large = []
    i = 1
    while i * i &lt;= n:
        if n % i == 0:
            small.append(i)
            if i != n // i:           # 제곱수 함정 처리
                large.append(n // i)
        i += 1
    return small + large[::-1]</code></pre>
<h4 id="⚠️-제곱수-함정">⚠️ 제곱수 함정</h4>
<p>36의 경우 i=6일 때 짝꿍 <code>n//i = 6</code> — 같은 수다. 이 체크를 빼먹으면 6이 두 번 들어가 <code>[1,2,3,4,6,6,9,...]</code>이 된다. <strong>이게 시험 디버깅 문제의 단골 함정.</strong></p>
<h4 id="디버깅-문제">디버깅 문제</h4>
<pre><code class="language-python">def count_divisors(n):
    count = 0
    i = 1
    while i * i &lt;= n:
        if n % i == 0:
            count += 2          # 버그: 제곱수에서 2번 셈
        i += 1
    return count

# count_divisors(36) → 기대 9, 실제 10 (i=6에서 2번 셈)
# count_divisors(49) → 기대 3, 실제 4</code></pre>
<p><strong>한 줄 수정</strong>:</p>
<pre><code class="language-python">count += 1 if i == n // i else 2</code></pre>
<h4 id="수업-중-질문-i--n--i-이-조건-i--i--n-이렇게-써도-되지-않나-저렇게-쓴-이유가-있나해서">수업 중 질문: <code>i == n // i</code> 이 조건 <code>i * i == n</code> 이렇게 써도 되지 않나? 저렇게 쓴 이유가 있나해서</h4>
<p>좋은 질문이다. <strong>둘 다 정답</strong>이고 우리 맥락에선 결과가 같다. 다만 미묘한 차이가 있다.</p>
<table>
<thead>
<tr>
<th>표현</th>
<th>의미</th>
<th>안전성</th>
</tr>
</thead>
<tbody><tr>
<td><code>i == n // i</code></td>
<td>&quot;i와 짝꿍이 같은 값?&quot; (약수 쌍 관점)</td>
<td><code>n % i == 0</code> 조건 안에서만 안전</td>
</tr>
<tr>
<td><code>i * i == n</code></td>
<td>&quot;i가 정확히 √n?&quot; (제곱근 관점)</td>
<td><strong>항상 안전</strong> — 정수 곱셈, 오차 없음</td>
</tr>
</tbody></table>
<p><strong>왜 차이가 생기나?</strong> 만약 <code>if n % i == 0:</code> 조건이 없는 곳에서 비교한다고 하면:</p>
<pre><code class="language-python">n = 10, i = 3
i * i == n   →  9 == 10  →  False  ✅ (정확)
i == n // i  →  3 == 10 // 3 → 3 == 3 → True  ❌ (잘못된 판정!)</code></pre>
<p>정수 나눗셈 <code>//</code>은 나머지를 버려버리니까 i가 약수가 아닌데도 결과가 맞아떨어진 것처럼 보일 수 있다. 우리 코드는 <code>if n % i == 0:</code> 안에서 쓰니까 둘 다 안전하지만, <strong>습관적으로는 <code>i * i == n</code>이 robust한 디폴트.</strong> 의도 표현 면에서는 짝꿍 변수와 같이 쓸 때 <code>i == n // i</code>가 가독성이 좋다 — 상황에 맞게 쓰면 된다.</p>
<hr>
<h3 id="3-최대공약수-gcd-greatest-common-divisor--유클리드-호제법">3. 최대공약수 (GCD, Greatest Common Divisor) — 유클리드 호제법</h3>
<p>두 수의 공통 약수 중 가장 큰 수. 외우면 끝나는 공식 1개:</p>
<blockquote>
<p><strong><code>gcd(a, b) = gcd(b, a % b)</code> 를 반복. b가 0이 되면 a가 답.</strong></p>
</blockquote>
<h4 id="추적해보기">추적해보기</h4>
<pre><code>gcd(48, 18)
= gcd(18, 48 % 18) = gcd(18, 12)
= gcd(12, 18 % 12) = gcd(12, 6)
= gcd(6,  12 % 6)  = gcd(6, 0)
                              ↑ b=0 → a=6이 정답</code></pre><h4 id="코드-반복문--시험에-더-흔함">코드 (반복문 — 시험에 더 흔함)</h4>
<pre><code class="language-python">def gcd(a, b):
    while b != 0:
        a, b = b, a % b
    return a</code></pre>
<blockquote>
<p>🐍 <strong>Python 튜플 언패킹</strong>: <code>a, b = b, a % b</code>는 <strong>오른쪽 전체를 먼저 계산한 다음 왼쪽에 동시 대입</strong>한다. Java라면 임시변수가 필요한 패턴인데 Python은 한 줄에 끝.</p>
<pre><code class="language-java">// Java
int temp = a;
a = b;
b = temp % b;</code></pre>
<pre><code class="language-python"># Python
a, b = b, a % b</code></pre>
</blockquote>
<h4 id="수업-중-질문-a-b-파라미터-조건-있어-둘-중에-작은-수를-기준값으로-해야할-것-같은데-그런-설명이-없더라고">수업 중 질문: a, b 파라미터 조건 있어? 둘 중에 작은 수를 기준값으로 해야할 것 같은데 그런 설명이 없더라고</h4>
<p>핵심 질문이다. <strong><code>a &lt; b</code>여도 자동으로 작동한다.</strong> 추적해보면 보인다:</p>
<p><code>gcd(5, 12)</code> — 작은 수를 a에 넣어봤다.</p>
<table>
<thead>
<tr>
<th>반복</th>
<th>a</th>
<th>b</th>
<th>a % b</th>
<th>다음 (a, b)</th>
</tr>
</thead>
<tbody><tr>
<td>시작</td>
<td>5</td>
<td>12</td>
<td>5 % 12 = <strong>5</strong></td>
<td>(12, 5) ← <strong>자동 swap!</strong></td>
</tr>
<tr>
<td>1</td>
<td>12</td>
<td>5</td>
<td>2</td>
<td>(5, 2)</td>
</tr>
<tr>
<td>2</td>
<td>5</td>
<td>2</td>
<td>1</td>
<td>(2, 1)</td>
</tr>
<tr>
<td>3</td>
<td>2</td>
<td>1</td>
<td>0</td>
<td>(1, 0)</td>
</tr>
<tr>
<td>4</td>
<td>1</td>
<td>0</td>
<td>종료</td>
<td>return <strong>1</strong></td>
</tr>
</tbody></table>
<p><strong>왜 자동 swap이 되냐면</strong>, <code>a &lt; b</code>이면 <code>a % b = a</code>가 항상 성립한다(5를 12로 나눈 나머지는 5 그대로). 그래서 첫 줄 <code>a, b = b, a % b</code>가 → <code>a, b = b, a</code>로 변신. 한 번 헛돌고 큰 수가 자연스럽게 앞으로 온다.</p>
<p>→ <strong>매개변수 조건 따로 없다. 어느 쪽이 크든 알아서 됨.</strong> 그래서 시험에서 빈칸/디버깅 문제에 <code>if a &lt; b: a, b = b, a</code> 같은 줄이 있다면 그건 <strong>있어도 없어도 결과 같은 줄</strong>(함정 후보).</p>
<hr>
<h3 id="4-최소공배수-lcm-least-common-multiple--황금-공식">4. 최소공배수 (LCM, Least Common Multiple) — 황금 공식</h3>
<p>두 수의 공통 배수 중 가장 작은 수. <strong>공식 한 줄.</strong></p>
<p>$$
\text{lcm}(a, b) = \frac{a \times b}{\gcd(a, b)}
$$</p>
<pre><code class="language-python">lcm = (a * b) // gcd(a, b)
# 또는 (오버플로우 안전 버전)
lcm = a // gcd(a, b) * b</code></pre>
<p><strong>왜 성립?</strong> 직관적으로:</p>
<ul>
<li><code>a × b</code>는 공통 배수 중 하나(최소는 아닐 수 있음)</li>
<li>거기서 <strong>공통으로 겹치는 부분(GCD)만큼 한 번 빼주면</strong> 최소가 됨</li>
</ul>
<p>확인:</p>
<pre><code>lcm(4, 6) = (4 * 6) // gcd(4, 6) = 24 // 2 = 12 ✅
lcm(12, 18) = (12 * 18) // gcd(12, 18) = 216 // 6 = 36 ✅</code></pre><blockquote>
<p>⚠️ <strong><code>/</code> vs <code>//</code> 함정 재등장</strong>: 22차 이진 탐색에서도 나왔던 함정. 정수 결과 필요한 곳에선 무조건 <code>//</code>. <code>(4*6) / gcd(4,6)</code>은 <code>12.0</code>(실수)가 나와서 시험 답으로 오답 처리 가능.</p>
</blockquote>
<hr>
<h3 id="5-n개-수의-lcm--누적-패턴-함수-작성">5. N개 수의 LCM — 누적 패턴 함수 작성</h3>
<p>세 수 이상의 LCM은 어떻게? <strong><code>lcm(a, b, c) = lcm(lcm(a, b), c)</code></strong> — 둘씩 누적해서 합쳐가면 된다.</p>
<h4 id="수업-중-질문-빈-리스트가-들어올-수도-있어">수업 중 질문: 빈 리스트가 들어올 수도 있어?</h4>
<p>엣지케이스 사고력 좋은 질문. 이번 문제는 <strong><code>len(numbers) &gt;= 1</code></strong> 가정. COS Pro는 보통 입력 제약이 명시되고, 빈 리스트가 들어오는 경우는 드물다. 다만 <strong>실전 코딩테스트에서 입력 제약을 먼저 확인하는 습관</strong>은 함정 회피에 직접적으로 도움이 된다 — 계속 유지할 것.</p>
<h4 id="직접-작성한-풀이">직접 작성한 풀이</h4>
<pre><code class="language-python">def lcm_all(numbers):
    if len(numbers) == 1:
        return numbers[0]

    a = numbers[0]
    lcm = numbers[0]

    for i in range(1, len(numbers)):
        b = numbers[i]
        lcm = a // gcd(a, b) * b
        a = lcm

    return lcm</code></pre>
<p>테스트 5개 전부 통과. 1차 정답.</p>
<h4 id="피드백-코드-슬림화-체크리스트">피드백: 코드 슬림화 체크리스트</h4>
<p>잘 동작하지만 <strong><code>a</code>와 <code>lcm</code> 두 변수가 항상 같은 값</strong>이다. 추적해보면:</p>
<pre><code>i=1 직후:  a = lcm = 12
i=2 직후:  a = lcm = 24</code></pre><p>또 <code>if len == 1</code> 분기도 <strong>불필요</strong>하다 — 리스트가 1개면 for 루프가 한 번도 안 돌고 <code>lcm = numbers[0]</code> 그대로 반환된다.</p>
<p><strong>슬림화된 형태</strong>:</p>
<pre><code class="language-python">def lcm_all(numbers):
    lcm = numbers[0]
    for n in numbers[1:]:                    # 첫 번째 빼고 값 순회
        lcm = lcm // gcd(lcm, n) * n
    return lcm</code></pre>
<blockquote>
<p>💡 <strong>코드 슬림화 체크리스트</strong> (앞으로 함수 작성 후 한 번씩 자기 점검)</p>
<ol>
<li><strong>같은 값을 가진 변수 2개</strong>가 있나? → 하나로 합치기</li>
<li><strong>불필요한 엣지 분기</strong>가 있나? → 메인 로직이 자연스럽게 처리하면 제거</li>
<li><strong>인덱스가 꼭 필요한가?</strong> → 값 자체로 충분하면 <code>for x in arr</code></li>
</ol>
</blockquote>
<p><code>for n in numbers[1:]</code>는 <strong>첫 번째 원소 빼고 값으로 순회</strong>하는 Python 패턴이다. 인덱스 안 쓰고 값 자체로 도는 게 더 깔끔. 누적 패턴에선 슬라이싱 + 값 순회가 더 흔하다.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<p>오늘 푼 문제는 4문제(빈칸 2 + 디버깅 1 + 함수 작성 1), <strong>1차 정답률 4/4 (100%)</strong>. 함수 작성 자력 정답이 21차→22차→23차로 3세션 연속 이어지며 과거 약점이 강점으로 전환되는 추세. 다음 학습은 4/29(수)부터 BFS/DFS 시작.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260424 오늘의 학습: 이진 탐색]]></title>
            <link>https://velog.io/@lee_yesol421/20260424-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%9D%B4%EC%A7%84-%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@lee_yesol421/20260424-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%9D%B4%EC%A7%84-%ED%83%90%EC%83%89</guid>
            <pubDate>Fri, 24 Apr 2026 14:06:37 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<p>4/23 21차 세션에서는 20차 자기 진단에서 나온 두 약점(초기값 세팅 누락 / 인덱스+값 병렬 처리 미숙)을 겨냥해 <code>enumerate</code> → <code>zip</code> → 튜플 정렬 세 빈칸을 가볍게 풀었다. <code>zip</code> 문제에서 <code>top_score = scores[0]</code>, <code>top_name = names[0]</code> 초기값 세팅을 힌트 없이 스스로 써서 개선 신호를 확인했고, 부호 뒤집기 트릭의 경계 질문에서 stable sort 기반 2단계 정렬까지 확장했다. 세션 마지막에는 이미 풀어본 프린터 우선순위 문제를 매개로 <strong>그리디 알고리즘 개념</strong>을 명시적으로 정립했다.</p>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>오늘은 <strong>이진 탐색(Binary Search)</strong> 을 처음부터 배운다. 개념 설명 → 기본 템플릿 빈칸 → lower bound 변형 빈칸 → 디버깅 → 함수 작성(개수 세기 응용) 순서로 진행한다. COS Pro 1급에 빈출되는 유형이자 아직 안 배운 영역이라 기초를 탄탄히 쌓는 데 집중한다. 마지막에 학습자가 제기한 <strong>&quot;시험에서 bisect 써도 되나?&quot;</strong> 질문에서 완성형/부분형별 활용 전략까지 정리한다.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-이진-탐색--언제-왜-쓰는가">1) 이진 탐색 — 언제, 왜 쓰는가?</h3>
<p><strong>전제 조건</strong>: 리스트가 <strong>정렬되어 있어야</strong> 한다. 정렬되지 않은 데이터에는 못 쓴다.</p>
<p><strong>속도 비교</strong>:</p>
<table>
<thead>
<tr>
<th>탐색 방법</th>
<th>시간복잡도</th>
<th>크기 100만 리스트에서 최악 비교 횟수</th>
</tr>
</thead>
<tbody><tr>
<td>선형 탐색</td>
<td>O(n)</td>
<td>약 1,000,000번</td>
</tr>
<tr>
<td><strong>이진 탐색</strong></td>
<td><strong>O(log n)</strong></td>
<td><strong>약 20번</strong> (2²⁰ ≈ 100만)</td>
</tr>
</tbody></table>
<p>절반씩 버려가며 탐색하기 때문에, 데이터가 커질수록 격차가 기하급수적으로 벌어진다.</p>
<p><strong>동작 원리</strong>: <code>[1, 3, 5, 7, 9, 11, 13]</code> 에서 <code>11</code> 찾기</p>
<ol>
<li>가운데(인덱스 3) 값은 <code>7</code>. 찾는 값 11보다 작으니 <strong>왼쪽 반 전부 버림</strong> → <code>left = mid + 1</code></li>
<li>남은 <code>[9, 11, 13]</code> 의 가운데(인덱스 5) 값은 <code>11</code>. <strong>찾음, 반환 5</strong></li>
</ol>
<p>핵심 3줄로 요약하면:</p>
<pre><code class="language-python">mid = (left + right) // 2
if arr[mid] == target: 찾음
elif arr[mid] &lt; target: left = mid + 1   # target은 오른쪽에 있음
else: right = mid - 1                     # target은 왼쪽에 있음</code></pre>
<hr>
<h3 id="2-기본-이진-탐색-템플릿-빈칸-1">2) 기본 이진 탐색 템플릿 (빈칸 1)</h3>
<p><strong>문제</strong>: 정렬된 <code>arr</code> 에서 <code>target</code> 의 인덱스를 반환, 없으면 <code>-1</code>. ㉠, ㉡ 을 채우시오.</p>
<pre><code class="language-python">def solution(arr, target):
    left, right = 0, len(arr) - 1
    while left &lt;= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] &lt; target: # 7 &lt; 11
            left = mid + 1      # ㉠
        else:
            right = mid - 1     # ㉡
    return -1</code></pre>
<p><strong>왜 <code>+1</code>, <code>-1</code> 을 붙이나?</strong></p>
<p><code>arr[mid]</code> 는 이미 target이 아닌 걸 확인했으니 mid 자체는 다시 볼 필요가 없다. 그래서 <code>±1</code> 로 <strong>mid를 제외</strong>하고 범위를 줄여야 한다.</p>
<p>만약 <code>left = mid</code> 로 쓰면? <code>[1, 3]</code> 에서 3 찾을 때 <code>left=0, right=1</code> → <code>mid=0</code> → <code>arr[0]=1 &lt; 3</code> → <code>left=0</code> 그대로 → <strong>무한 루프</strong> 💥</p>
<p><strong>while 조건 <code>&lt;=</code> 에 등호가 꼭 필요한 이유</strong>: 원소가 한 개 남았을 때(<code>left == right</code>) 그 한 개도 검사해야 하기 때문. 등호를 빼면 마지막 남은 원소를 놓친다.</p>
<hr>
<h3 id="3-lower-bound-패턴--target-이상-첫-위치-빈칸-2">3) lower bound 패턴 — &quot;target 이상 첫 위치&quot; (빈칸 2)</h3>
<p>기본 이진 탐색만으로는 부족한 경우가 많다. 예: &quot;6 이상의 값이 처음 나오는 위치&quot; 같은 <strong>경계값 찾기</strong>. COS Pro에서도 종종 이 변형으로 출제된다.</p>
<pre><code class="language-python"># 정렬된 arr에서 target 이상인 값이 처음 등장하는 위치를 반환
# 모든 값이 target보다 작으면 len(arr) 반환
def solution(arr, target):
    left, right = 0, len(arr)     # ← right가 len-1이 아닌 len
    while left &lt; right:            # ← 등호 없음
        mid = (left + right) // 2
        if arr[mid] &gt;= target:
            right = mid            # ← mid도 답 후보이므로 제외하지 않음
        else:
            left = mid + 1
    return left

# 테스트
print(solution([1,3,5,7,9], 6))   # 3  (6 이상 첫 값은 7, 인덱스 3)
print(solution([1,3,5,7,9], 10))  # 5  (없음 → len 반환)</code></pre>
<p><strong>기본 이진 탐색과 3가지 차이점</strong>:</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>기본 이진 탐색</th>
<th>lower bound</th>
</tr>
</thead>
<tbody><tr>
<td>초기 <code>right</code></td>
<td><code>len(arr) - 1</code></td>
<td><code>len(arr)</code></td>
</tr>
<tr>
<td>while 조건</td>
<td><code>left &lt;= right</code></td>
<td><code>left &lt; right</code></td>
</tr>
<tr>
<td>범위 좁히기</td>
<td><code>right = mid - 1</code></td>
<td><code>right = mid</code></td>
</tr>
</tbody></table>
<p><strong>왜 <code>right = mid</code> 이고 <code>mid - 1</code> 이 아닌가?</strong></p>
<ul>
<li>기본 버전: <code>arr[mid] == target</code> 이면 바로 반환이라 mid는 이후 볼 필요 없음 → 제외 가능 → <code>mid ± 1</code></li>
<li>lower bound: <code>arr[mid] &gt;= target</code> 이면 <strong>mid도 답 후보</strong> 로 남겨야 함 → 제외 금지 → <code>right = mid</code></li>
</ul>
<p><strong>왜 <code>&lt;</code> 로 등호를 뺐나?</strong></p>
<ul>
<li>만약 <code>&lt;=</code> 이면 <code>left == right == mid</code> 에서 <code>right = mid</code> 로 범위가 그대로 → 무한 루프</li>
<li><code>&lt;</code> 면 <code>left == right</code> 에서 루프 종료하고 그 값이 답</li>
</ul>
<p>풀이 중에는 <code>print(f&quot;left={left} right={right} mid={mid} arr[mid]={arr[mid]}&quot;)</code> 로 실행을 직접 추적하며 ㉠에 <code>right = mid</code>, ㉡에 <code>left = mid + 1</code> 을 채웠다. 이진 탐색처럼 조건이 헷갈릴 때는 한 번 찍어보고 가는 게 확실하다.</p>
<hr>
<h3 id="4-디버깅---vs--함정">4) 디버깅 — <code>/</code> vs <code>//</code> 함정</h3>
<p><strong>문제</strong>: 한 줄만 고쳐라.</p>
<pre><code class="language-python">def solution(arr, target):
    left, right = 0, len(arr) - 1
    while left &lt;= right:
        mid = (left + right) / 2    # ← 버그!
        if arr[mid] == target:
            return mid
        ...</code></pre>
<p><strong>답</strong>: <code>mid = (left + right) // 2</code> (정수 나눗셈으로 교체)</p>
<p><strong>이유</strong>:</p>
<ul>
<li>Python의 <code>/</code> 는 항상 <strong>실수(float)</strong> 를 반환한다. <code>(0 + 6) / 2</code> 는 <code>3.0</code> 이다.</li>
<li><code>arr[3.0]</code> 은 <code>TypeError: list indices must be integers</code> 에러가 난다. 리스트 인덱스는 반드시 정수여야 한다.</li>
<li>따라서 <strong>정수 나눗셈 <code>//</code></strong> 를 써야 한다.</li>
</ul>
<p><strong>Java와 비교</strong>:</p>
<table>
<thead>
<tr>
<th>언어</th>
<th><code>(0 + 6) / 2</code> 결과</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Java</strong> (<code>int / int</code>)</td>
<td><code>3</code> (자동으로 정수 나눗셈)</td>
</tr>
<tr>
<td><strong>Python</strong></td>
<td><code>3.0</code> (항상 실수)</td>
</tr>
<tr>
<td><strong>Python 정수 나눗셈</strong></td>
<td><code>(0 + 6) // 2</code> → <code>3</code></td>
</tr>
</tbody></table>
<p>Java 경험이 있으면 오히려 이 함정에 걸리기 쉽다. <code>/</code> 가 Java에서처럼 자동으로 정수로 떨어질 거라 기대하기 때문. <strong>이진 탐색 디버깅 단골 함정</strong>이므로 반드시 기억해두자.</p>
<hr>
<h3 id="5-함수-작성--target-등장-횟수-lower--upper-bound-조합">5) 함수 작성 — target 등장 횟수 (lower + upper bound 조합)</h3>
<p><strong>문제</strong>: 정렬된 리스트 <code>arr</code> 에서 <code>target</code> 이 몇 번 등장하는지 반환. 중복 가능.</p>
<p><strong>아이디어</strong>: 두 번의 이진 탐색으로 경계를 찾아 뺀다.</p>
<ul>
<li><code>target</code> 이상 첫 위치 = <code>left_idx</code></li>
<li><code>target + 1</code> 이상 첫 위치 = <code>right_idx</code> (= target 초과 첫 위치)</li>
<li>개수 = <code>right_idx - left_idx</code></li>
</ul>
<p><strong>예</strong>: <code>[1, 2, 2, 2, 3]</code>, target=2</p>
<ul>
<li>2 이상 첫 위치 = 1</li>
<li>3 이상 첫 위치 = 4</li>
<li>개수 = 4 − 1 = <strong>3</strong> ✓</li>
</ul>
<p><strong>풀이</strong>:</p>
<pre><code class="language-python">def solution(arr, target):
    # right_idx 찾기 (target+1 이상 첫 위치)
    left, right = 0, len(arr)
    while left &lt; right:
        mid = (left + right) // 2
        if arr[mid] &gt;= target + 1:
            right = mid
        else:
            left = mid + 1
    right_idx = left

    # left_idx 찾기 (target 이상 첫 위치)
    left, right = 0, right_idx  # 위에서 이미 찾은 범위만 뒤지면 충분
    while left &lt; right:
        mid = (left + right) // 2
        if arr[mid] &gt;= target:
            right = mid
        else:
            left = mid + 1
    left_idx = left

    return right_idx - left_idx</code></pre>
<p>테스트 <code>[1,2,2,2,3,4,5]</code> target=2 → 3, <code>[1,1,1,1,1]</code> target=1 → 5, 빈 리스트 <code>[]</code> → 0 (while 루프 자체에 진입 안 해서 자연스럽게 0 반환) 까지 <strong>전부 통과</strong>.</p>
<p>빈 리스트 대응을 위해 별도 방어 코드를 쓰지 않아도 <code>left=0, right=0</code> 이면 <code>while left &lt; right</code> 가 바로 거짓이 되어 빠져나간다. 이진 탐색 템플릿의 숨겨진 장점 중 하나.</p>
<h4 id="더-간결한-방법-학습자-질문">더 간결한 방법 (학습자 질문)</h4>
<pre><code>수업 중 질문 (풀이 직후): &quot;이진 탐색 로직을 두 번 썼는데 더 간결한 방법도 있을까?&quot;</code></pre><p>크게 두 가지 대안이 있다.</p>
<p><strong>방법 1: <code>bisect</code> 표준 라이브러리 활용</strong></p>
<pre><code class="language-python">from bisect import bisect_left, bisect_right

def solution(arr, target):
    return bisect_right(arr, target) - bisect_left(arr, target)</code></pre>
<ul>
<li><code>bisect_left(arr, x)</code> = x 이상 첫 위치 (= <code>left_idx</code>)</li>
<li><code>bisect_right(arr, x)</code> = x 초과 첫 위치 (= <code>right_idx</code>)</li>
<li>위에서 손으로 짠 두 이진 탐색을 한 줄로 대체</li>
</ul>
<p><strong>방법 2: 헬퍼 함수로 중복 제거</strong></p>
<pre><code class="language-python">def solution(arr, target):
    def lower_bound(x):
        left, right = 0, len(arr)
        while left &lt; right:
            mid = (left + right) // 2
            if arr[mid] &gt;= x:
                right = mid
            else:
                left = mid + 1
        return left
    return lower_bound(target + 1) - lower_bound(target)</code></pre>
<p>이진 탐색 로직이 <strong>한 곳</strong>에만 있어 중복이 제거된다. 바깥 변수 <code>arr</code> 는 클로저로 자연스럽게 참조된다.</p>
<p><strong>결론</strong>: 중복처럼 보여도 <strong>if 조건이 다르기 때문에</strong> 직접 두 번 쓰는 것도 완전히 정답. 리팩터링은 취향.</p>
<hr>
<h3 id="6-시험-전략--bisect-를-시험에서-그대로-써도-되나">6) 시험 전략 — bisect 를 시험에서 그대로 써도 되나?</h3>
<pre><code>수업 중 질문: &quot;시험에서 bisect를 그대로 써도 되는거야?&quot;</code></pre><p>좋은 질문이다. 정답은 <strong>&quot;쓸 수 있다, 하지만 뉘앙스 주의&quot;</strong>.</p>
<p><strong>COS Pro 1급에서 허용되는 표준 라이브러리</strong>:</p>
<pre><code class="language-python">import bisect                          # 이진 탐색
from collections import Counter, deque  # 빈도 / 큐
from itertools import combinations      # 조합
import heapq                            # 우선순위 큐
import math                             # gcd, sqrt</code></pre>
<p><strong>하지만 유형별로 활용 가능 범위가 다르다</strong>:</p>
<table>
<thead>
<tr>
<th>유형</th>
<th>bisect 사용 가능?</th>
<th>실전 팁</th>
</tr>
</thead>
<tbody><tr>
<td><strong>완성형 (함수 작성, 3문제)</strong></td>
<td>O, 자유롭게</td>
<td>bisect 쓰면 코드가 훨씬 짧아져 시간 절약</td>
</tr>
<tr>
<td><strong>빈칸 채우기</strong></td>
<td>출제자가 정해둔 코드를 따라야 함</td>
<td><strong>직접 구현 코드</strong>가 제시되면 그 흐름대로 빈칸 채움</td>
</tr>
<tr>
<td><strong>디버깅</strong></td>
<td>출제자 코드 구조 그대로</td>
<td>bisect가 이미 쓰여있으면 그 안에서 버그 찾기</td>
</tr>
</tbody></table>
<p><strong>왜 빈칸에선 직접 구현이 더 많이 나오나?</strong>
출제자 입장에서 <code>bisect_left(arr, target)</code> 한 줄을 빈칸으로 내면 시험이 너무 쉬워진다. 그래서 <strong>이진 탐색 로직을 풀어쓴 코드</strong>에서 <code>left = mid + 1</code> 같은 부분을 빈칸으로 내는 게 일반적이다.</p>
<p><strong>결론적인 학습 전략</strong>:</p>
<ol>
<li><strong>직접 구현 원리를 확실히 이해</strong> — 빈칸/디버깅 대비 (부분형 7문제 = 700점, 훨씬 큰 비중)</li>
<li><strong>bisect 사용법도 알아두기</strong> — 완성형에서 시간 단축 무기</li>
<li>완성형에서 남은 시간이 빠듯하면 bisect 한 줄로 처리하고 다음 문제로 넘어가는 판단도 가능</li>
</ol>
<p>오늘 세션에서 빈칸 2개 + 디버깅 1개 + 함수 작성 1개 모두 직접 구현으로 풀었으니, 부분형 대비의 기초는 탄탄히 깔린 셈이다.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>푼 문제 4문제 (빈칸 2 + 디버깅 1 + 함수 작성 1), 1차 정답률 <strong>100% (4/4)</strong></li>
<li>이진 탐색 기본 / lower bound 변형 / 개수 세기 응용까지 한 세션에 체화</li>
<li>다음 학습: 수학 (소수 판별 / GCD / LCM) 진입 예정</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260423 오늘의 학습: 인덱스+값 병렬 패턴과 그리디 개념]]></title>
            <link>https://velog.io/@lee_yesol421/20260423-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%9D%B8%EB%8D%B1%EC%8A%A4%EA%B0%92-%EB%B3%91%EB%A0%AC-%ED%8C%A8%ED%84%B4%EA%B3%BC-%EA%B7%B8%EB%A6%AC%EB%94%94-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@lee_yesol421/20260423-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%9D%B8%EB%8D%B1%EC%8A%A4%EA%B0%92-%EB%B3%91%EB%A0%AC-%ED%8C%A8%ED%84%B4%EA%B3%BC-%EA%B7%B8%EB%A6%AC%EB%94%94-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Thu, 23 Apr 2026 13:13:58 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<p>4/20 20차 세션에서 구현/시뮬레이션 범위를 복습하며 18차 약점이던 y 차원 경계 체크(<code>0 &lt;= ny &lt; n</code>)를 힌트 없이 재현하여 정착을 확인했다. 반면 2차원 90도 회전 공식 <code>rotated[i][j]=arr[n-1-j][i]</code> 은 재노출 공백으로 잠시 날아갔고, 경계 순회 문제에선 <code>arr[x][y] not in result</code> 같은 값 기반 중복 체크가 중복 값 반례에서 깨져 좌표 기반 <code>visited = {(0,0)}</code> 방식으로 교정했다. 세션 말미에 스스로 <strong>&quot;루프 전 초기 값 세팅을 자주 깜빡한다&quot;</strong>, <strong>&quot;인덱스+값을 병렬로 다루는 감각이 아직 약하다&quot;</strong> 두 약점을 정확히 진단했고, 오늘은 그 연장선에서 가볍게 워밍업한다.</p>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>20차 자기 진단에서 나온 두 약점(초기값 세팅 누락 / 인덱스+값 병렬 처리 미숙)을 겨냥해 <strong><code>enumerate</code> → <code>zip</code> → 튜플 정렬</strong> 순서로 빈칸 세 문제만 가볍게 푼다. 이 세 도구는 겉보기엔 다르지만 &quot;두 종류의 데이터를 같은 시점에 묶어 다룬다&quot;는 같은 범주에 속한다. 풀이 중 학습자가 제기한 &quot;부호 뒤집기 트릭의 경계 조건&quot; 질문에서 stable sort 기반 2단계 정렬 트릭까지 확장하고, 마지막에 <strong>그리디 알고리즘 개념</strong>을 이미 푼 프린터 우선순위 문제를 매개로 정립한다.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="0-세션-시작-전-질문-알고리즘-용어-점검">0) 세션 시작 전 질문: 알고리즘 용어 점검</h3>
<pre><code>수업 중 질문: &quot;최근에 1급 딴 다른 사람 후기 봤는데 DFS BFS DP 그리디 이런
알고리즘 용어가 나오더라고? 잘 몰랐는데 이미 내가 푼 적도 있는거야?&quot;</code></pre><p>현 커리큘럼 기준으로 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>용어</th>
<th>뜻</th>
<th>학습 상태</th>
</tr>
</thead>
<tbody><tr>
<td><strong>DFS</strong> (깊이 우선 탐색)</td>
<td>한 갈래 끝까지 가보고 막히면 돌아와서 다른 갈래</td>
<td>미학습, 4월 말 진입 예정</td>
</tr>
<tr>
<td><strong>BFS</strong> (너비 우선 탐색)</td>
<td>가까운 곳부터 한 겹씩 퍼져나가며 탐색</td>
<td>미학습, 4월 말 진입 예정</td>
</tr>
<tr>
<td><strong>DP</strong> (동적 프로그래밍)</td>
<td>작은 문제 답을 저장해두고 큰 문제에 재활용</td>
<td>커리큘럼 제외 (합격 전략상 불필요)</td>
</tr>
<tr>
<td><strong>그리디</strong></td>
<td>매 순간 가장 좋아 보이는 선택을 하는 전략</td>
<td><strong>이미 푼 적 있음</strong> (용어만 몰랐을 뿐)</td>
</tr>
</tbody></table>
<p>특히 그리디의 경우 이미 17차/19차에서 푼 <strong>프린터 우선순위</strong> 문제가 정통 그리디 유형이었다는 점을 확인하고, 오늘 세션 마지막에 개념을 명시적으로 정립하기로 했다.</p>
<hr>
<h3 id="1-enumerate--인덱스와-값을-같이-순회">1) enumerate — 인덱스와 값을 같이 순회</h3>
<p><strong>문제</strong>: 숫자 리스트에서 가장 큰 값이 있는 위치(인덱스)를 반환하는 함수를 작성하라. <code>max()</code>, <code>index()</code> 사용 금지.</p>
<pre><code class="language-python">def find_max_index(arr):
    max_idx = 0
    max_val = arr[0]
    for i, v in enumerate(arr):
        if v &gt; max_val:
            max_val = v
            max_idx = i
    return max_idx</code></pre>
<p><strong>핵심</strong>:</p>
<ul>
<li><code>enumerate(arr)</code> 은 <code>(인덱스, 값)</code> 튜플을 하나씩 내어주는 반복자. 이걸 <code>for i, v in ...</code> 로 언패킹하면 한 번의 순회로 두 정보를 동시에 쓸 수 있다.</li>
<li><code>&gt;</code> 부등호(등호 없음) 때문에 동점이면 <strong>먼저 나온 인덱스가 유지</strong>된다. <code>[5, 5, 5]</code> 입력에서 <code>0</code> 이 나오는 이유.</li>
</ul>
<pre><code>수업 중 질문 (풀이 직후): &quot;초기값 세팅은 내가 한 거 아니야.&quot;</code></pre><p>정확한 지적이었다. 이 문제에서 <code>max_idx = 0</code> / <code>max_val = arr[0]</code> 두 줄은 빈칸 바깥에 제시된 코드라 학습자가 직접 쓴 부분은 아니었다. 초기값 세팅 약점 개선은 다음 문제에서 진짜로 확인하기로 했다.</p>
<hr>
<h3 id="2-zip--두-리스트를-값끼리-병렬-순회">2) zip — 두 리스트를 값끼리 병렬 순회</h3>
<p><strong>문제</strong>: 학생 이름 리스트와 점수 리스트가 있을 때, 가장 높은 점수를 받은 학생의 이름을 반환하라.</p>
<pre><code class="language-python">def top_student(names, scores):
    top_score = scores[0]
    top_name = names[0]
    for name, score in zip(names, scores):
        if score &gt; top_score:
            top_score = score
            top_name = name
    return top_name</code></pre>
<p><strong>핵심</strong>:</p>
<ul>
<li><code>zip(names, scores)</code> 는 같은 위치의 두 원소를 <strong>튜플로 묶어</strong> 내어주는 반복자. 길이가 다르면 <strong>짧은 쪽에 맞춰 멈춘다</strong> (그래서 짧은 리스트의 길이까지만 순회됨).</li>
<li><code>enumerate</code> 가 &quot;(인덱스, 값)&quot; 을 묶었다면 <code>zip</code> 은 &quot;(값, 값)&quot; 을 묶는다. 묶는 대상이 다를 뿐 <strong>&quot;두 데이터를 병렬로 쓴다&quot;</strong> 는 목적은 같다.</li>
</ul>
<p>이번 문제에서는 <code>top_score = scores[0]</code>, <code>top_name = names[0]</code> 초기값 세팅 두 줄을 힌트 없이 직접 썼다. 20차 때 스스로 진단했던 <strong>&quot;루프 전 초기값을 자주 깜빡한다&quot;</strong> 약점이 <strong>이번엔 작동하지 않았다</strong> — 한 번의 실전 노출로 개선 신호를 확인한 셈.</p>
<p><strong>enumerate ↔ zip 비교표</strong>:</p>
<table>
<thead>
<tr>
<th>상황</th>
<th>도구</th>
<th>언패킹 예</th>
</tr>
</thead>
<tbody><tr>
<td>한 리스트의 인덱스와 값이 같이 필요</td>
<td><code>enumerate(arr)</code></td>
<td><code>for i, v in ...</code></td>
</tr>
<tr>
<td>두 리스트를 같은 위치끼리 비교</td>
<td><code>zip(a, b)</code></td>
<td><code>for x, y in ...</code></td>
</tr>
<tr>
<td>세 리스트 이상</td>
<td><code>zip(a, b, c)</code></td>
<td><code>for x, y, z in ...</code></td>
</tr>
<tr>
<td>인덱스도 있고 두 리스트도 같이</td>
<td><code>enumerate(zip(a, b))</code></td>
<td><code>for i, (x, y) in ...</code></td>
</tr>
</tbody></table>
<hr>
<h3 id="3-튜플-정렬--다중-key--부호-뒤집기-트릭">3) 튜플 정렬 — 다중 key + 부호 뒤집기 트릭</h3>
<p><strong>문제</strong>: <code>[(이름, 점수), ...]</code> 리스트를 <strong>점수 내림차순</strong>, <strong>동점이면 이름 가나다순</strong>으로 정렬하라.</p>
<pre><code class="language-python">def sort_students(students):
    return sorted(students, key=lambda x: (-x[1], x[0]))</code></pre>
<p><strong>핵심 아이디어</strong>:</p>
<ul>
<li><code>sorted()</code> 의 key 함수가 <strong>튜플을 반환</strong>하면 Python은 튜플을 <strong>앞에서부터 순서대로</strong> 비교한다. 1차 기준이 같을 때만 2차 기준을 본다.</li>
<li>문제의 함정: <strong>점수는 내림차, 이름은 오름차</strong> — 방향이 섞였다. <code>reverse=True</code> 는 튜플 전체를 뒤집기 때문에 둘 다 내림차가 되어 버린다.</li>
<li>해법: <strong>한 쪽만 부호를 뒤집는다</strong>. 점수 <code>-x[1]</code> 로 넣으면 정렬은 오름차로 하면서 실제 결과는 점수 내림차가 된다.</li>
</ul>
<p><strong>Java와 비교</strong>:</p>
<table>
<thead>
<tr>
<th>언어</th>
<th>점수 내림 + 이름 오름 정렬</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Java</strong></td>
<td><code>list.sort(Comparator.comparingInt(Student::score).reversed().thenComparing(Student::name))</code></td>
</tr>
<tr>
<td><strong>Python</strong></td>
<td><code>sorted(list, key=lambda x: (-x[1], x[0]))</code></td>
</tr>
</tbody></table>
<p>Java의 <code>.reversed().thenComparing()</code> 체이닝과 Python의 부호 뒤집기 트릭은 목표는 같지만 표현이 다르다. Python 쪽이 짧은 대신 &quot;숫자에만 먹힌다&quot;는 제약이 있다.</p>
<hr>
<h3 id="4-부호-뒤집기-트릭의-경계-문자열에-적용하려면">4) 부호 뒤집기 트릭의 경계: 문자열에 적용하려면?</h3>
<pre><code>수업 중 질문: &quot;지금은 내림차순 정렬하는 값이 숫자라서 가능한 거지?
만약 이름을 내림차순(사전 반대방향)으로 정렬한다고 하면
어쩔 수 없이 reverse=True 사용해야되겠지?&quot;</code></pre><p>매우 정확한 경계 질문이다. 답을 정리하면:</p>
<pre><code class="language-python">&gt;&gt;&gt; -&quot;abc&quot;
TypeError: bad operand type for unary -: &#39;str&#39;</code></pre>
<p>부호 뒤집기는 <strong>숫자에만</strong> 먹힌다. 그렇다고 <code>reverse=True</code> 만이 유일한 답은 아니다. <strong>Python의 <code>sorted()</code> 가 stable(안정 정렬)</strong> 이라는 성질을 이용하면 문자열에도 &quot;방향이 섞인 다중 정렬&quot;이 가능하다.</p>
<p><strong>Stable sort 의 정의</strong>: 같은 key 값을 가진 원소들의 <strong>상대 순서가 정렬 전후에 유지</strong>된다.</p>
<p><strong>활용법 — 2단계 정렬</strong>: &quot;덜 중요한 기준을 먼저, 더 중요한 기준을 나중에&quot; 두 번 정렬한다.</p>
<pre><code class="language-python"># 예: 점수 오름차 + 이름 내림차 (사전 반대 방향)
students = [(&quot;철수&quot;, 80), (&quot;영희&quot;, 80), (&quot;민수&quot;, 70)]

# 1단계: 덜 중요한 기준 먼저 — 이름 내림차
temp = sorted(students, key=lambda x: x[0], reverse=True)
# → [(&#39;철수&#39;, 80), (&#39;영희&#39;, 80), (&#39;민수&#39;, 70)]

# 2단계: 더 중요한 기준 나중 — 점수 오름차
result = sorted(temp, key=lambda x: x[1])
# → [(&#39;민수&#39;, 70), (&#39;철수&#39;, 80), (&#39;영희&#39;, 80)]
#    점수 오름차 ✓, 동점은 이름 내림차(철 &gt; 영) ✓</code></pre>
<p><strong>정리표</strong>:</p>
<table>
<thead>
<tr>
<th>상황</th>
<th>쓸 수 있는 방법</th>
</tr>
</thead>
<tbody><tr>
<td>모든 기준이 같은 방향</td>
<td><code>reverse=True</code> 하나로 해결</td>
</tr>
<tr>
<td>숫자 기준 방향이 섞임</td>
<td><code>-x</code> 부호 뒤집기 (오늘 푼 문제)</td>
</tr>
<tr>
<td>문자열 기준 방향이 섞임</td>
<td><strong>2단계 정렬</strong> (stable sort 활용)</td>
</tr>
</tbody></table>
<p>COS Pro 1급에서 문자열 내림차 + 다른 기준 조합은 빈도가 낮다. 실전 빈출은 <strong>점수 내림 + 이름 오름</strong> 쪽이라 오늘 푼 <code>(-x[1], x[0])</code> 패턴이 훨씬 자주 나온다. 2단계 정렬은 &quot;이런 카드도 있다&quot; 정도로 알아두면 충분하다.</p>
<hr>
<h3 id="5-그리디-greedy-개념-정립">5) 그리디 (Greedy) 개념 정립</h3>
<pre><code>수업 중 질문: &quot;끝내기 전에 이미 내가 한 적 있다던 그리디에 대해
정리하고 가자. 문제 풀 건 아니고 개념 → 예시 문제 → 바로 풀이법 설명까지.
내가 푼 적 있는 문제면 더 좋고.&quot;</code></pre><p><strong>한 문장 정의</strong>:</p>
<blockquote>
<p>&quot;매 순간, 지금 당장 가장 좋아 보이는 선택을 한다.&quot;</p>
</blockquote>
<ul>
<li>전체 경로를 미리 고민하지 않고 <strong>지역 최적(local optimal)</strong> 을 고른다.</li>
<li>이 선택들을 쌓은 결과가 <strong>전체 최적(global optimal)</strong> 과 일치하는 문제에서만 그리디로 풀 수 있다.</li>
</ul>
<h4 id="이미-푼-예시-프린터-우선순위">이미 푼 예시: 프린터 우선순위</h4>
<p>문제 요약: 대기열에 있는 문서들에 각자 우선순위가 있다. 큐 맨 앞 문서를 꺼냈을 때, 뒤에 더 높은 우선순위가 남아있으면 그 문서를 <strong>맨 뒤로 보낸다</strong>. 없으면 <strong>인쇄한다</strong>.</p>
<p>입력 예: <code>priorities = [2, 1, 3, 2]</code>, 내 문서 위치 = 2 → 출력: <code>1</code></p>
<p><strong>왜 이게 그리디냐</strong>:</p>
<blockquote>
<p>&quot;현재 대기열에서 우선순위가 가장 높은 문서부터 먼저 인쇄한다.&quot;</p>
</blockquote>
<p>지금 남아있는 것 중 <strong>제일 큰 것</strong>을 처리하면 된다 — 이게 곧 탐욕 규칙.</p>
<pre><code class="language-python">from collections import deque

def solution(priorities, location):
    queue = deque((i, p) for i, p in enumerate(priorities))
    count = 0
    while queue:
        idx, pri = queue.popleft()
        if queue and max(p for _, p in queue) &gt; pri:
            queue.append((idx, pri))   # 더 큰 거 있으니 뒤로
        else:
            count += 1
            if idx == location:
                return count</code></pre>
<p><code>max(...) &gt; pri</code> 로 &quot;지금 가장 높은 게 뭐냐&quot; 를 묻는 부분이 그리디의 핵심이다.</p>
<h4 id="그리디가-항상-맞는-건-아니다-중요">그리디가 항상 맞는 건 아니다 (중요)</h4>
<p>반례: 동전 <code>[1원, 4원, 5원]</code> 으로 8원 만들기</p>
<table>
<thead>
<tr>
<th>전략</th>
<th>선택</th>
<th>동전 개수</th>
</tr>
</thead>
<tbody><tr>
<td>그리디 (큰 거부터)</td>
<td>5 + 1 + 1 + 1</td>
<td>4개</td>
</tr>
<tr>
<td>최적</td>
<td>4 + 4</td>
<td><strong>2개</strong></td>
</tr>
</tbody></table>
<p>그래서 그리디를 선택하기 전에 &quot;이 전략이 정말 전체 최적과 일치하는가?&quot; 를 한 번 점검해야 한다. 애매하면 DP 영역으로 넘어가야 한다.</p>
<p>다행히 <strong>COS Pro 1급 수준의 그리디는 대부분 &quot;정렬 후 순서대로 집어먹기&quot;</strong> 패턴으로 해결되어 복잡한 증명 없이 접근 가능하다. 그래서 그리디 문제에는 거의 항상 <code>sorted()</code> 가 등장한다.</p>
<h4 id="대표-유형-이름만-기억">대표 유형 (이름만 기억)</h4>
<table>
<thead>
<tr>
<th>유형</th>
<th>한 줄 설명</th>
</tr>
</thead>
<tbody><tr>
<td>거스름돈 문제</td>
<td>큰 동전부터 최대한 사용</td>
</tr>
<tr>
<td>회의실 배정</td>
<td>끝나는 시간 빠른 것부터 선택</td>
</tr>
<tr>
<td>구명보트 문제</td>
<td>가장 무거운 사람 + 가장 가벼운 사람 짝짓기</td>
</tr>
</tbody></table>
<h4 id="이미-가진-그리디-감각">이미 가진 그리디 감각</h4>
<ul>
<li>17차·19차 프린터 우선순위 — 19차에선 <code>(인덱스, 우선순위)</code> 튜플을 힌트 없이 스스로 설계</li>
<li>20차 점수 순 정렬 — &quot;가장 좋은 것부터&quot; 사고방식은 그리디와 결이 같음</li>
</ul>
<p>용어만 몰랐을 뿐 감각은 이미 있다. 앞으로 문제에서 <strong>&quot;가장 ~한 것부터&quot;</strong> 라는 키워드가 보이면 <strong>&quot;그리디 + 정렬&quot;</strong> 을 먼저 떠올리면 된다.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>푼 문제 3문제 (enumerate / zip / 튜플 정렬 빈칸 채우기), 1차 정답률 100%</li>
<li>20차 자기 진단 약점 &quot;초기값 세팅 깜빡&quot; — 문제 2에서 힌트 없이 직접 세팅하여 개선 확인</li>
<li>다음 학습: 이진 탐색 + 수학(소수/GCD/LCM) 진입</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260420 오늘의 학습: 구현/시뮬레이션 복습과 병렬 리스트 패턴]]></title>
            <link>https://velog.io/@lee_yesol421/20260420-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EA%B5%AC%ED%98%84%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98-%EB%B3%B5%EC%8A%B5%EA%B3%BC-%EB%B3%91%EB%A0%AC-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@lee_yesol421/20260420-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EA%B5%AC%ED%98%84%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98-%EB%B3%B5%EC%8A%B5%EA%B3%BC-%EB%B3%91%EB%A0%AC-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Mon, 20 Apr 2026 12:27:47 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<p>4/17 19차 세션에서 17차 약점이었던 스택/큐 두 가지 — <strong>스택에 무엇을 저장할지 판단</strong>과 <strong>빈 스택 체크</strong> — 를 힌트 없이 재현하며 완전 정착을 확인했다. 프린터 우선순위 문제도 17차와 달리 <code>(인덱스, 우선순위)</code> 튜플을 스스로 설계했다. 다만 괄호 중첩 깊이 문제에서 &quot;깊이&quot;를 &quot;완성된 쌍 수&quot;로 해석해 <code>((())())</code> 반례에서 깨지는 풀이를 낸 적이 있다.</p>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>학습 공백이 좀 있었기 때문에 감각 복구 목적으로 18차 범위(구현/시뮬레이션 기초)를 복습한다. dx/dy 좌표 이동, 2차원 배열 90도 회전, 주기성 활용을 빈칸 → 디버깅 → 함수 작성 순서로 점진적으로 끌어올리고, 마지막에 응용 함수 작성 문제로 현재 실력을 진단한다.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-dxdy-이동--경계-체크--행-열-규약-복습">1) dx/dy 이동 + 경계 체크 — (행, 열) 규약 복습</h3>
<p>로봇이 N×N 격자에서 상하좌우 명령을 받아 이동하는 문제. 격자를 벗어나는 이동은 무시한다.</p>
<pre><code class="language-python">def solution(n, commands):
    direction = {
        &#39;U&#39;: (-1, 0),   # 위 = 행 감소
        &#39;D&#39;: (1, 0),
        &#39;L&#39;: (0, -1),
        &#39;R&#39;: (0, 1)
    }
    x, y = 0, 0
    for cmd in commands:
        nx = x + direction[cmd][0]
        ny = y + direction[cmd][1]
        if 0 &lt;= nx &lt; n and 0 &lt;= ny &lt; n:   # ← 18차 약점이었던 y 차원 체크
            x, y = nx, ny
    return (x, y)</code></pre>
<p><strong>18차 약점 재확인</strong>: 경계 체크 시 x 차원만 검사하고 y 차원을 빠뜨린 실수가 있었다. 오늘은 힌트 없이 <code>0 &lt;= ny &lt; n</code>까지 스스로 썼다 → <strong>완전 정착</strong>.</p>
<p><strong>좌표 규약 핵심</strong>: (행, 열) 규약에서</p>
<ul>
<li>&#39;위&#39;로 가면 <strong>행이 감소</strong> (인덱스 값 작은 쪽 위)</li>
<li>&#39;왼쪽&#39;으로 가면 <strong>열이 감소</strong></li>
</ul>
<hr>
<h3 id="2-2차원-배열-90도-회전--공식-재암기">2) 2차원 배열 90도 회전 — 공식 재암기</h3>
<pre><code>수업 중 질문: &quot;공식 가르쳐줬었는데 기억 안나네&quot;</code></pre><p>시간이 좀 지나니 18차에 배운 회전 공식이 가물가물해졌다. 개념부터 재구축.</p>
<h4 id="직관적-규칙">직관적 규칙</h4>
<pre><code>원본:              시계 90도:
1 2 3              7 4 1
4 5 6       →      8 5 2
7 8 9              9 6 3</code></pre><p><strong>눈에 보이는 규칙</strong>: 원본의 <strong>첫 번째 열</strong> <code>[1, 4, 7]</code>이 회전 후 <strong>맨 윗줄에 뒤집혀서</strong> <code>[7, 4, 1]</code>로 들어간다.</p>
<h4 id="공식">공식</h4>
<pre><code class="language-python">rotated[i][j] = arr[n - 1 - j][i]</code></pre>
<h4 id="왜-이렇게-되는가--인덱스-추적">왜 이렇게 되는가 — 인덱스 추적</h4>
<table>
<thead>
<tr>
<th>회전 후</th>
<th>값</th>
<th>원본 위치</th>
<th>공식 확인</th>
</tr>
</thead>
<tbody><tr>
<td><code>rotated[0][0]</code></td>
<td>7</td>
<td><code>arr[2][0]</code></td>
<td><code>arr[n-1-0][0]</code> ✓</td>
</tr>
<tr>
<td><code>rotated[0][1]</code></td>
<td>4</td>
<td><code>arr[1][0]</code></td>
<td><code>arr[n-1-1][0]</code> ✓</td>
</tr>
<tr>
<td><code>rotated[0][2]</code></td>
<td>1</td>
<td><code>arr[0][0]</code></td>
<td><code>arr[n-1-2][0]</code> ✓</td>
</tr>
</tbody></table>
<h4 id="외우는-팁-18차-때와-동일한-분해">외우는 팁 (18차 때와 동일한 분해)</h4>
<pre><code>i → 원본의 &quot;열&quot;로 간다
j → 원본의 &quot;행&quot;으로 뒤집혀서 간다 (n-1-j)</code></pre><p>행/열이 <strong>서로 바뀌고(transpose)</strong> + 새 행 쪽이 <strong>뒤집힌다(reverse)</strong> → 그래서 <code>zip(*arr[::-1])</code> 한 줄 풀이도 가능했던 것.</p>
<p><strong>교훈</strong>: 한동안 안 보면 공식은 날아간다. 주기적으로 다시 노출할 것.</p>
<hr>
<h3 id="3-k번-회전--주기성">3) K번 회전 + 주기성</h3>
<p>K가 10억이어도 빠르게 동작해야 한다 → 주기성 활용.</p>
<pre><code class="language-python">def solution(arr, k):
    n = len(arr)
    k = k % 4          # ① 90도 회전 4번이면 원상복구 = 주기 4

    for _ in range(k): # ② 나머지 횟수만큼만 회전
        rotated = [[0] * n for _ in range(n)]
        for i in range(n):
            for j in range(n):
                rotated[i][j] = arr[n - 1 - j][i]
        arr = rotated
    return arr</code></pre>
<p>18차에 배운 <strong>K % 주기</strong> 패턴이 새 문제에서도 즉시 적용됐다. 스택/큐에서 &quot;무엇을 저장할지&quot; 판단이 정착한 것처럼, 주기성 감각도 정착 단계.</p>
<p><strong>보너스 — 테스트 시 <code>[row[:] for row in arr]</code>의 의미</strong>: 2차원 배열의 깊은 복사. 바깥 리스트만 복사하는 <code>arr[:]</code>은 내부 행이 공유되어 원본 오염 가능. 각 행까지 복사해야 안전. 16차에 정착한 <strong>원본 보존</strong> 패턴의 연장선.</p>
<hr>
<h3 id="4-2차원-경계-순회--값-기반-중복-체크의-함정">4) 2차원 경계 순회 — &quot;값 기반 중복 체크&quot;의 함정</h3>
<p>N×N 배열의 경계를 시계 방향으로 순회해 값 리스트를 반환하는 함수. 처음 작성한 풀이:</p>
<pre><code class="language-python">def solution(arr):
    dx = [0, 1, 0, -1]   # 우 하 좌 상
    dy = [1, 0, -1, 0]
    n = len(arr)
    d = 0
    x, y = 0, 0
    result = []

    while d &lt; 4:
        if arr[x][y] not in result:   # ← 여기가 문제
            result.append(arr[x][y])
        nx, ny = x + dx[d], y + dy[d]
        if 0 &lt;= nx &lt; n and 0 &lt;= ny &lt; n:
            x, y = nx, ny
        else:
            d += 1
    return result</code></pre>
<p>주어진 테스트 4개(<code>3x3</code>, <code>4x4</code>, <code>1x1</code>, <code>2x2</code>)는 전부 통과. 하지만 <strong>반례가 있었다</strong>.</p>
<h4 id="반례">반례</h4>
<pre><code class="language-python">arr = [[1, 2, 1],
       [3, 4, 5],
       [1, 6, 7]]
# 기대: [1, 2, 1, 5, 7, 6, 1, 3]
# 실제: [1, 2, 5, 7, 6, 3]     ← 중복 값이 전부 사라짐</code></pre>
<h4 id="원인">원인</h4>
<p><code>result</code>에 <strong>값</strong>을 저장하고 <strong>값</strong>으로 중복 체크 → 경계에 같은 값이 여러 번 나오면 하나로 취급된다. <code>arr[x][y] not in result</code> 가드는 &quot;코너에서 방향 바뀔 때 같은 칸을 두 번 기록하는 것&quot;을 막으려는 의도였으나, 값으로 판단해서 다른 위치의 동일 값까지 막아버린다.</p>
<h4 id="올바른-방법--좌표위치-기반-체크">올바른 방법 — 좌표(위치) 기반 체크</h4>
<pre><code class="language-python">def solution(arr):
    dx = [0, 1, 0, -1]
    dy = [1, 0, -1, 0]
    n = len(arr)
    d = 0
    x, y = 0, 0
    result = [arr[0][0]]       # ← 첫 값을 루프 전에 세팅
    visited = {(0, 0)}          # ← 좌표 set으로 방문 체크

    while d &lt; 4:
        nx, ny = x + dx[d], y + dy[d]
        if 0 &lt;= nx &lt; n and 0 &lt;= ny &lt; n and (nx, ny) not in visited:
            x, y = nx, ny
            visited.add((x, y))
            result.append(arr[x][y])
        else:
            d += 1
    return result</code></pre>
<p><strong>핵심 변화</strong>: 방문 체크 대상이 <strong>값 → 좌표</strong>로 바뀌었다. 이것이 인덱스+값 병렬 처리 패턴의 전형.</p>
<h4 id="반복되는-학습-패턴--테스트-통과-≠-정답">반복되는 학습 패턴 — &quot;테스트 통과 ≠ 정답&quot;</h4>
<table>
<thead>
<tr>
<th>차수</th>
<th>사건</th>
</tr>
</thead>
<tbody><tr>
<td>19차</td>
<td>괄호 깊이 문제 — 7개 테스트 통과했지만 <code>((())())</code>에서 깨짐</td>
</tr>
<tr>
<td>20차 (오늘)</td>
<td>경계 순회 — 4개 테스트 통과했지만 중복 값 케이스에서 깨짐</td>
</tr>
</tbody></table>
<p><strong>앞으로 지킬 습관</strong>: 풀이를 내기 전 &quot;이 풀이가 깨지는 입력이 뭘까?&quot; 반례를 상상해보기. 특히 <strong>&quot;값 기반 체크&quot;는 중복을 의심</strong>.</p>
<hr>
<h3 id="5-점수-순-학생-정렬--zip--sorted--key-컴비네이션">5) 점수 순 학생 정렬 — zip + sorted + key 컴비네이션</h3>
<p><code>names[i]</code> 학생이 <code>scores[i]</code>점일 때, 점수 내림차순 이름 반환 (동점은 입력 순서 유지).</p>
<pre><code class="language-python">def solution(names, scores):
    # 단계별 버전
    result = list(zip(names, scores))
    result2 = sorted(result, key=lambda x: x[1], reverse=True)
    result3 = [name for name, score in result2]
    return result3


def solution(names, scores):
    # 한 줄 버전
    return [name for name, _ in sorted(zip(names, scores),
                                       key=lambda x: x[1],
                                       reverse=True)]</code></pre>
<h4 id="세-가지-핵심-도구">세 가지 핵심 도구</h4>
<pre><code class="language-python"># ① zip — 두 리스트를 같은 인덱스로 묶기
list(zip([&quot;A&quot;,&quot;B&quot;,&quot;C&quot;], [1, 2, 3]))
# [(&#39;A&#39;, 1), (&#39;B&#39;, 2), (&#39;C&#39;, 3)]

# ② sorted + key + reverse — 기준과 방향 지정
sorted([(&#39;A&#39;, 85), (&#39;B&#39;, 92)], key=lambda x: x[1], reverse=True)

# ③ enumerate — 값 + 원본 인덱스
list(enumerate([&quot;철수&quot;,&quot;영희&quot;]))
# [(0, &#39;철수&#39;), (1, &#39;영희&#39;)]</code></pre>
<h4 id="동점-시-원래-순서-유지--python-sorted의-stable-sort-특성">동점 시 원래 순서 유지 — Python <code>sorted</code>의 stable sort 특성</h4>
<p>Python <code>sorted()</code>는 <strong>안정 정렬(stable sort)</strong> 이다. 같은 키 값을 가진 원소들은 입력 순서를 보존한다. 그래서 동점 <code>92점</code>인 영희와 지영 중 영희가 먼저 나왔다.</p>
<blockquote>
<p>시험에 &quot;동점 시 원래 순서 유지&quot; 문구가 나오면 별도 처리 없이 <code>sorted</code> 그냥 쓰면 된다.</p>
</blockquote>
<hr>
<h3 id="6-오늘의-자기-진단--다음-학습-주제-선정">6) 오늘의 자기 진단 — 다음 학습 주제 선정</h3>
<pre><code>수업 중 질문: &quot;이해했어. 최초 값 미리 세팅해두는 걸 자꾸 깜빡하네.
             그리고 파이썬에서 인덱스(위치)랑 값을 별도 리스트에 저장한 다음
             같은 인덱스로 돌면서 처리하는 게 많이 나오는데 아직 익숙하지 않은듯&quot;</code></pre><p>정확한 자기 진단이다. 두 포인트가 다음 단계 핵심:</p>
<h4 id="약점-1-첫-값-미리-세팅을-깜빡함">약점 1: 첫 값 미리 세팅을 깜빡함</h4>
<p>루프 안에서 &quot;이동 후 기록&quot; 구조를 쓰면, 시작 위치는 이동 없이 거기 있으니 <strong>루프 전에 먼저 넣어줘야 한다</strong>.</p>
<ul>
<li><code>result = [arr[0][0]]</code>, <code>visited = {(0,0)}</code> 같은 초기화</li>
<li>슬라이딩 윈도우에서 <strong>첫 윈도우 합은 루프 전에</strong> 계산하는 패턴과 동일 (15차)</li>
</ul>
<h4 id="약점-2-인덱스값-병렬-관리-패턴-미숙">약점 2: 인덱스+값 병렬 관리 패턴 미숙</h4>
<p>이미 경험한 문제들을 관통하는 공통 패턴:</p>
<table>
<thead>
<tr>
<th>문제</th>
<th>병렬 관리 방법</th>
</tr>
</thead>
<tbody><tr>
<td>17차 프린터 우선순위</td>
<td><code>(우선순위, 인덱스)</code> 튜플</td>
</tr>
<tr>
<td>19차 프린터 재도전</td>
<td><code>(인덱스, 우선순위)</code> 튜플 스스로 설계</td>
</tr>
<tr>
<td>오늘 경계 순회 (개선)</td>
<td><code>(x, y)</code> 좌표 set</td>
</tr>
<tr>
<td>오늘 점수 정렬</td>
<td><code>zip(names, scores)</code> 튜플 리스트</td>
</tr>
<tr>
<td>dx/dy 이동</td>
<td>방향 리스트 + <code>d</code> 인덱스</td>
</tr>
</tbody></table>
<p>공통 도구: <strong><code>enumerate</code></strong>, <strong><code>zip</code></strong>, <strong>튜플 리스트 + sort</strong>, <strong>좌표 키 set/dict</strong>.</p>
<p>이 다섯 문제를 하나의 범주로 묶어 이해하는 시점이 왔다 → 다음 세션에서 집중 훈련.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<p>총 5문제 풀이, 1차 정답 3/5 (60%) — 감각 복귀 구간치고는 양호. 18차 약점(y 차원 경계 체크)은 힌트 없이 해결하며 정착 확인. 다음 세션은 오늘 스스로 진단한 <strong>인덱스+값 병렬 패턴</strong>을 핵심 주제로 잡고 <code>enumerate</code>/<code>zip</code>/튜플 정렬을 집중 훈련한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260417 오늘의 학습: 스택/큐 복습과 괄호 문제]]></title>
            <link>https://velog.io/@lee_yesol421/20260417-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%8A%A4%ED%83%9D%ED%81%90-%EB%B3%B5%EC%8A%B5%EA%B3%BC-%EA%B4%84%ED%98%B8-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@lee_yesol421/20260417-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%8A%A4%ED%83%9D%ED%81%90-%EB%B3%B5%EC%8A%B5%EA%B3%BC-%EA%B4%84%ED%98%B8-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Fri, 17 Apr 2026 05:59:52 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<p>4/15 18차 세션에서 구현/시뮬레이션 기초로 dx/dy 좌표 이동과 2차원 배열 90도 회전(<code>zip(*arr[::-1])</code>)을 학습했다. 빈칸 3칸 전부 1차 정답, 함수 작성 로직도 완벽했고 (행,열) 좌표 규약도 시각적 분해로 이해했다. 4/13 17차에서 처음 접한 스택/큐는 1차 정답률 50%로, 그때 <strong>&quot;스택에 무엇을 저장할지&quot;</strong> 판단과 <strong>빈 스택 체크</strong> 두 가지가 약점으로 남아있었다.</p>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>17차 스택/큐 약점 2가지를 스스로 극복할 수 있는지 확인하는 것이 오늘의 주요 목표. 괄호 문자열을 재료로 5문제를 풀며 점진적으로 난이도를 올렸다.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-스택에-무엇을-저장할지-판단--인덱스-저장-패턴">1) 스택에 &quot;무엇을 저장할지&quot; 판단 — 인덱스 저장 패턴</h3>
<p>짝이 맞지 않는 여는 괄호의 <strong>위치</strong>를 반환하는 문제였다. 17차 약점이 바로 이 부분이었는데, 이번엔 스스로 판단했다.</p>
<p><strong>핵심 규칙</strong>: 스택에 저장할 값은 <strong>&quot;나중에 뭘 반환해야 하는지&quot;</strong> 에서 역산한다.</p>
<ul>
<li>위치(인덱스)를 반환해야 함 → 스택에 <code>i</code>를 저장</li>
<li>그냥 괄호 자체(<code>(</code>)를 저장하면 나중에 위치 복원 불가</li>
</ul>
<pre><code class="language-python">def solution(s):
    stack = []
    for i in range(len(s)):
        if s[i] == &#39;(&#39;:
            stack.append(i)      # ← 위치 저장
        else:  # &#39;)&#39;
            if not stack:
                return i         # 닫는 괄호가 먼저 → 그 위치
            stack.pop()
    if stack:
        return stack[0]          # &quot;가장 먼저 나온&quot; → 첫 번째
    return -1</code></pre>
<p><strong>포인트</strong>: <code>stack[0]</code> vs <code>stack[-1]</code> 구분.</p>
<ul>
<li>&quot;가장 먼저 나온 것&quot; = 제일 먼저 append된 것 = <code>stack[0]</code></li>
<li>&quot;가장 나중&quot; = <code>stack[-1]</code></li>
</ul>
<p>스택은 단순 LIFO 자료구조가 아니라 <strong>순서 정보를 담은 기록 장치</strong>로 활용하는 감각이 생겼다.</p>
<hr>
<h3 id="2-빈-스택-체크--단락평가short-circuit-패턴">2) 빈 스택 체크 — 단락평가(short-circuit) 패턴</h3>
<p>연속된 같은 문자를 제거하는 &quot;쌍쌍 지우기&quot; 디버깅 문제였다.</p>
<p><strong>버그가 있는 원본</strong>:</p>
<pre><code class="language-python">if stack[-1] == ch:
    stack.pop()</code></pre>
<p>첫 글자에서 스택이 비어있으면 <code>IndexError</code> 발생.</p>
<p><strong>수정</strong>:</p>
<pre><code class="language-python">if stack and stack[-1] == ch:
    stack.pop()</code></pre>
<p>Python의 <code>and</code>는 <strong>단락평가</strong>를 한다. 앞이 False면 뒤는 아예 평가하지 않음.</p>
<ul>
<li><code>stack</code>이 빈 리스트(<code>[]</code>) → Falsy → <code>stack and ...</code> 전체가 False → <code>stack[-1]</code> 아예 접근하지 않음</li>
<li><code>stack</code>이 비어있지 않음 → Truthy → 뒤 조건 평가</li>
</ul>
<p>이 패턴은 <strong><code>stack[-1]</code>, <code>queue[0]</code>, <code>lst[i]</code>, <code>dict[key]</code></strong> 접근 전 어디든 쓸 수 있는 관용구다. <code>not stack</code> 체크 후 return/continue하는 방식과 동등하지만 한 줄로 처리된다.</p>
<hr>
<h3 id="3-큐--인덱스-우선순위-튜플-추적--17차-힌트-유형">3) 큐 + (인덱스, 우선순위) 튜플 추적 — 17차 힌트 유형</h3>
<p>&quot;프린터 우선순위&quot; 유형. 17차에서는 힌트가 필요했지만 이번엔 스스로 설계했다.</p>
<pre><code class="language-python">from collections import deque

def solution(priorities, target):
    queue = deque((idx, p) for idx, p in enumerate(priorities))
    count = 0

    while queue:
        idx, p = queue.popleft()
        if queue and p &lt; max(p for _, p in queue):
            queue.append((idx, p))
        else:
            count += 1
            if idx == target:
                return count</code></pre>
<p><strong>핵심 설계 결정</strong>:</p>
<ul>
<li>큐에 단순 우선순위 값만 넣으면 <strong>원래 위치를 잃어버림</strong> → 튜플로 <code>(인덱스, 우선순위)</code> 함께 저장</li>
<li><code>_</code>로 인덱스 무시: <code>max(p for _, p in queue)</code> — 언패킹 관용구</li>
<li><code>queue and</code> 단락평가로 마지막 하나 남은 경우 <code>max()</code> 에러 방지 → 위 2번에서 배운 패턴을 여기에도 적용</li>
</ul>
<p>17차에서 힌트 받았던 &quot;스택에 무엇을 저장할지&quot;가 큐 문제에서도 같은 원리로 작동한다는 것을 체감했다.</p>
<hr>
<h3 id="4-여러-종류-괄호-매칭--딕셔너리-매핑-활용">4) 여러 종류 괄호 매칭 — 딕셔너리 매핑 활용</h3>
<p><code>(</code>, <code>{</code>, <code>[</code> 세 종류가 섞인 문자열의 올바름을 판단하는 문제.</p>
<pre><code class="language-python">def solution(s):
    brackets = {&quot;[&quot;:&quot;]&quot;, &quot;(&quot;:&quot;)&quot;, &quot;{&quot;:&quot;}&quot;}
    stack = []

    for ch in s:
        if ch in brackets:
            stack.append(ch)
        else:
            if stack and ch == brackets[stack[-1]]:
                stack.pop()
            else:
                return False
    return not stack  # 남은 여는 괄호 없으면 True</code></pre>
<p><strong>핵심 설계</strong>:</p>
<ul>
<li>딕셔너리로 여는→닫는 매핑을 선언 → if/elif 지옥 회피</li>
<li><code>ch in brackets</code> → dict의 키 존재 체크, 여는 괄호 판별로 활용</li>
<li><code>brackets[stack[-1]]</code> → 스택 맨 위 여는 괄호의 <strong>짝(닫는 괄호)</strong> 을 조회</li>
<li><code>return not stack</code> → 마지막에 남은 여는 괄호 없으면 True (Falsy 체크)</li>
</ul>
<hr>
<h3 id="5-중첩-깊이-최댓값--해석-오류와-교정">5) 중첩 깊이 최댓값 — 해석 오류와 교정</h3>
<p>여기서 오늘의 <strong>실수</strong>가 나왔다. &quot;괄호 중첩 깊이&quot;를 <strong>완성된 괄호 쌍의 수</strong>로 해석해 버렸다. 그래서 <strong>연속된 <code>)</code>의 개수</strong>를 추적하는 창의적인(?) 풀이를 만들었다.</p>
<pre><code class="language-python"># 잘못된 풀이 (핵심 부분)
if ch == &quot;(&quot;:
    if count &gt; 0:
        count = 0          # )가 나오다가 (가 나오면 리셋
    stack.append(ch)
elif ch == &quot;)&quot;:
    stack.pop()
    count += 1             # 연속 ) 개수 카운트
    max_count = max(max_count, count)</code></pre>
<p>주어진 7개 테스트는 <strong>전부 통과</strong>했다. 하지만 반례 하나에서 무너졌다.</p>
<p><strong>반례: <code>&quot;((())())&quot;</code></strong></p>
<pre><code>( ( ( ) )  ( ) )
1 2 3 2 1  2 1 0   ← 실제 깊이: 최대 3</code></pre><p>최대 깊이 3을 찍고 2만 닫은 뒤 다시 열기 때문에 &quot;연속 <code>)</code> 개수&quot;는 2밖에 안 된다. 내 풀이는 2를 반환. 정답은 3.</p>
<p><strong>개념 교정</strong>:</p>
<ul>
<li>중첩 깊이 = &quot;그 시점에 <strong>동시에 열려있는</strong> 괄호의 개수&quot; = 스택 크기</li>
<li>완성 여부는 상관 없음. 닫기 전이라도 &quot;열려있으면 카운트&quot;</li>
</ul>
<p>비유하자면 바다 수영에서 깊이 3m는 &quot;지금 발 밑에 물이 3m 있다&quot;이지, &quot;3m까지 내려갔다가 수면까지 다시 올라온 횟수&quot;가 아니다.</p>
<p><strong>교정 후 풀이</strong>:</p>
<pre><code class="language-python">def solution(s):
    max_depth = 0
    depth = 0
    for ch in s:
        if ch == &quot;(&quot;:
            depth += 1
            max_depth = max(max_depth, depth)
        elif ch == &quot;)&quot;:
            depth -= 1
    return max_depth</code></pre>
<p>이 문제에서는 스택이 필요 없다. <strong>지금 몇 개 열려있는가</strong>만 알면 되기 때문에 단순 카운터로 충분하다. <code>depth</code>는 사실상 &quot;스택 크기&quot;와 같은 값을 추적하는 것이다.</p>
<p><strong>도구 선택 감각</strong>: 스택은 &quot;LIFO 순서나 저장했다가 나중에 꺼낼 정보&quot;가 있을 때 빛난다. 이 문제는 그런 요구가 없어서 카운터가 더 직관적이다.</p>
<hr>
<h3 id="오늘의-교훈--반례를-스스로-떠올려보기">오늘의 교훈 — 반례를 스스로 떠올려보기</h3>
<p>주어진 테스트케이스 7개를 전부 통과해도, <strong>출제자가 놓쳤을 법한 패턴</strong>을 스스로 떠올려보는 검증 습관이 필요하다. 특히 <strong>&quot;일반적이지 않은 접근&quot;</strong> 으로 풀었다면 더욱 그렇다. 오늘처럼 &quot;연속 <code>)</code> 개수 추적&quot; 같은 간접 지표로 푸는 경우엔 그 지표가 정말 원래 목표값과 동치인지 반례로 검증해야 한다.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<p>5문제 해결, 1차 정답 4/5(80%). 17차 약점 2가지(<strong>스택에 무엇을 저장할지 / 빈 스택 체크</strong>)는 <strong>완전 정착</strong> 판정.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260415 오늘의 학습: 구현/시뮬레이션 기초]]></title>
            <link>https://velog.io/@lee_yesol421/20260415-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EA%B5%AC%ED%98%84%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@lee_yesol421/20260415-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EA%B5%AC%ED%98%84%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Wed, 15 Apr 2026 15:10:41 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<ul>
<li>스택과 큐 개념 학습: 리스트 기반 스택, <code>collections.deque</code> 기반 큐, 괄호 매칭/요세푸스/프린터 우선순위 등 6문제 풀이</li>
<li>스택 활용 시 &quot;무엇을 저장할지&quot; 판단하는 부분에서 약간의 혼동이 있었으나 힌트 후 해결</li>
<li>이어서 미학습 유형인 <strong>구현/시뮬레이션</strong>으로 넘어감</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>COS Pro 단골 유형인 <strong>구현/시뮬레이션 기초</strong>를 다룬다. 좌표 이동 패턴(dx/dy), 2차원 배열 90도 회전을 핵심 개념으로 잡고 빈칸 2문제 + 함수 작성 1문제를 푼다.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-좌표-이동-dxdy-패턴">1. 좌표 이동 dx/dy 패턴</h3>
<p>시뮬레이션 문제의 출발점. 상하좌우를 일일이 if-else로 쓰지 않고, <strong>방향 배열 두 개</strong>로 한 번에 처리한다.</p>
<pre><code class="language-python"># 북, 동, 남, 서 (시계방향)
dx = [-1, 0, 1, 0]
dy = [0, 1, 0, -1]

for i in range(4):
    nx = x + dx[i]
    ny = y + dy[i]</code></pre>
<p><strong>핵심</strong>: <code>dx</code>, <code>dy</code>의 배열 순서는 방향 순서(N/E/S/W 또는 U/R/D/L)에 따라 달라진다. 문제마다 방향 규약을 먼저 확인해야 한다.</p>
<blockquote>
<p>수업 중 질문: &quot;dx dy에 방향값 넣어두는건 방향 순서에 따라 달라지겠네?&quot;
→ 정확한 일반화. 이전에 푼 소용돌이 문제에서 방향이 우→하→좌→상 순서로 돌았던 것을 떠올리면, 그 문제의 dx/dy와 이번 문제의 dx/dy가 왜 다른지 자연스럽게 연결된다.</p>
</blockquote>
<h3 id="2-경계-체크-out-of-bound-방어">2. 경계 체크 (Out-of-bound 방어)</h3>
<p>좌표 이동 후 <strong>새 좌표가 격자 안에 있는지</strong> 확인하는 패턴.</p>
<pre><code class="language-python">if 0 &lt;= nx &lt; N and 0 &lt;= ny &lt; N:
    x, y = nx, ny   # 유효하면 이동
# 아니면 그대로 (명령 무시)</code></pre>
<p><strong>피드백 포인트</strong>: 경계 체크를 <code>0 &lt;= x &lt; N</code>처럼 <strong>이동 전 좌표</strong>로 쓰거나, <strong>y 차원을 빠뜨리는</strong> 실수가 잦다.</p>
<ul>
<li>체크 대상은 반드시 <strong>이동 후 좌표(<code>nx</code>, <code>ny</code>)</strong></li>
<li>2차원이면 x, y <strong>둘 다</strong> 검사해야 함 (<code>and</code>로 연결)</li>
</ul>
<h3 id="3-2차원-배열-90도-시계-회전">3. 2차원 배열 90도 시계 회전</h3>
<p>공식 버전:</p>
<pre><code class="language-python">def rotate_once(arr):
    N = len(arr)
    rotated = [[0]*N for _ in range(N)]
    for i in range(N):
        for j in range(N):
            rotated[j][N-1-i] = arr[i][j]
    return rotated</code></pre>
<p>Pythonic 버전:</p>
<pre><code class="language-python">rotated = [list(row) for row in zip(*arr[::-1])]</code></pre>
<blockquote>
<p>수업 중 질문: &quot;zip 사용한 부분이 왜 90도 회전이랑 같아지는지 모르겠다.&quot;
→ <strong>시각적 분해</strong>로 이해하면 쉽다.</p>
<ol>
<li><code>arr[::-1]</code> — 위아래 뒤집기 (행 순서 역전)</li>
<li><code>zip(*...)</code> — 전치(transpose), 즉 행↔열 교환</li>
<li>뒤집기 + 전치 = 시계방향 90도 회전</li>
</ol>
<p>수학적 증명보다는 &quot;왜 이게 그렇게 되는지 그림으로 한 번 그려보기&quot;가 기억에 잘 남는다.</p>
</blockquote>
<h3 id="4-주기성-활용--k--4">4. 주기성 활용 — <code>K % 4</code></h3>
<p>같은 회전을 K번 반복할 때, <strong>4번 돌면 원상복귀</strong>하므로 <code>K = K % 4</code>로 불필요한 반복을 제거한다.</p>
<pre><code class="language-python">K = K % 4
for _ in range(K):
    result = rotate_once(result)</code></pre>
<p>이 패턴은 회전뿐 아니라 <strong>순환 구조 전반</strong>에 쓰인다. 요세푸스, 원형 큐, 방향 회전 등.</p>
<h3 id="5-행-열-좌표-규약">5. (행, 열) 좌표 규약</h3>
<p>구현 문제에서 가장 헷갈리는 부분. <strong>리스트 기반 2차원 배열은 수학 좌표계와 다르다.</strong></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>수학 좌표계</th>
<th>리스트 좌표계 (grid[x][y])</th>
</tr>
</thead>
<tbody><tr>
<td>x 증가 방향</td>
<td>오른쪽</td>
<td><strong>아래</strong></td>
</tr>
<tr>
<td>y 증가 방향</td>
<td>위</td>
<td>오른쪽</td>
</tr>
<tr>
<td>북</td>
<td>y+1</td>
<td><strong>x-1</strong></td>
</tr>
<tr>
<td>남</td>
<td>y-1</td>
<td><strong>x+1</strong></td>
</tr>
<tr>
<td>동</td>
<td>x+1</td>
<td>y+1</td>
</tr>
<tr>
<td>서</td>
<td>x-1</td>
<td>y-1</td>
</tr>
</tbody></table>
<p><strong>왜 이렇게 쓰는가?</strong>
<code>grid[x][y]</code>라고 쓰면, <code>grid[x]</code>가 먼저 행을 선택하고, 그 다음 <code>[y]</code>가 열을 고른다. 즉 <strong>x는 행 번호(세로 위치)</strong>, <strong>y는 열 번호(가로 위치)</strong>가 되는 것이 자연스럽다.</p>
<blockquote>
<p>수업 중 질문: &quot;행열 규약이 왜 저런지 아직 이해 못했어.&quot;
→ 수학식으로 이해하려 하지 말고, <strong>리스트 구조가 원래 그렇게 생겼다</strong>고 받아들이는 것이 실전에 빠르다. 문제 풀 때 &quot;북이면 x를 줄인다&quot;만 기억하면 됨.</p>
</blockquote>
<h3 id="6-방향-전환-로직---4">6. 방향 전환 로직 — <code>% 4</code></h3>
<p>L/R 명령으로 방향을 바꿀 때:</p>
<pre><code class="language-python"># 방향: 0(북) 1(동) 2(남) 3(서)
if cmd == &#39;R&#39;:
    d = (d + 1) % 4
elif cmd == &#39;L&#39;:
    d = (d - 1) % 4   # Python은 음수 % 양수도 자동 양수화</code></pre>
<p>Python의 <code>%</code>는 음수에서도 <strong>양수 결과를 반환</strong>하므로 <code>(d-1) % 4</code>가 안전하게 작동한다. (Java/C는 음수 나올 수 있어 주의.)</p>
<h3 id="7-문제에서-예시-답을-의심하는-능력">7. 문제에서 예시 답을 의심하는 능력</h3>
<p><strong>오늘의 중요 발견</strong>: 함수 작성 문제에서 (가로, 세로) 좌표계로 dx/dy를 짠 답과 예시 답이 달랐다. 학습자가 먼저 &quot;예시 답이 정확한지 검증해달라&quot;고 요청 → 확인해보니 <strong>학습자의 로직도 완벽</strong>했고, 단지 좌표 규약이 (가로,세로)냐 (행,열)이냐의 차이였다.</p>
<p><strong>피드백 포인트</strong>:</p>
<ul>
<li>자기 답이 틀렸다고 바로 단정하지 말 것</li>
<li>예시 답과 다르면 <strong>규약 차이</strong>인지 <strong>로직 오류</strong>인지부터 구분해야 함</li>
<li>COS Pro 실전에서는 문제에 명시된 좌표 규약을 <strong>먼저 확인</strong>하고 시작하는 습관이 중요</li>
</ul>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>풀이 3문제 (빈칸 2, 함수 작성 1) / 1차 정답률 100% (경계 체크 부분만 힌트 후 수정)</li>
<li>좌표 이동 dx/dy + 90도 회전 + K%4 주기성 — 구현 3대 기초 패턴 습득</li>
<li>다음 학습(4/16)은 구현/시뮬레이션 기초 마무리 (달팽이 배열, 종이접기 등 복합 시뮬레이션 예정)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260413 오늘의 학습: 스택과 큐]]></title>
            <link>https://velog.io/@lee_yesol421/20260413-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%8A%A4%ED%83%9D%EA%B3%BC-%ED%81%90</link>
            <guid>https://velog.io/@lee_yesol421/20260413-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%8A%A4%ED%83%9D%EA%B3%BC-%ED%81%90</guid>
            <pubDate>Mon, 13 Apr 2026 14:16:47 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<ul>
<li>약점 집중 연습 2회차에서 원본 보존 패턴, 순환 순회 범위 모두 정착 판정</li>
<li>주요 약점 전부 해소 → 실력 강화 기간으로 전환 결정</li>
<li>아직 안 다룬 유형: 스택/큐, 그리디, BFS/DFS, 이진 탐색, DP 등</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>COS Pro 출제 범위 중 미학습 유형인 <strong>스택과 큐</strong>를 집중 학습한다. 개념 설명 후 빈칸/디버깅/함수 작성 6문제를 풀며 핵심 패턴을 익힌다.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-스택-stack--lifo">1. 스택 (Stack) — LIFO</h3>
<p>마지막에 넣은 걸 먼저 꺼내는 구조. Java에서는 <code>Stack</code> 클래스를 쓰지만, Python에서는 <strong>그냥 리스트</strong>로 스택을 구현한다.</p>
<table>
<thead>
<tr>
<th>Java</th>
<th>Python</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>push()</code></td>
<td><code>append()</code></td>
<td>넣기</td>
</tr>
<tr>
<td><code>pop()</code></td>
<td><code>pop()</code></td>
<td>꺼내기 (마지막 원소)</td>
</tr>
<tr>
<td><code>peek()</code></td>
<td><code>[-1]</code></td>
<td>맨 위 보기 (꺼내지 않음)</td>
</tr>
<tr>
<td><code>isEmpty()</code></td>
<td><code>not stack</code></td>
<td>비어있는지 확인</td>
</tr>
</tbody></table>
<h3 id="2-큐-queue--fifo">2. 큐 (Queue) — FIFO</h3>
<p>먼저 넣은 걸 먼저 꺼내는 구조. 리스트의 <code>pop(0)</code>은 O(n)이라 느리므로, <strong><code>collections.deque</code></strong>를 사용한다.</p>
<pre><code class="language-python">from collections import deque

queue = deque()
queue.append(1)        # 뒤에 넣기
front = queue.popleft() # 앞에서 꺼내기 — O(1)</code></pre>
<table>
<thead>
<tr>
<th>연산</th>
<th>리스트</th>
<th>deque</th>
</tr>
</thead>
<tbody><tr>
<td>뒤에 넣기</td>
<td>append() — O(1)</td>
<td>append() — O(1)</td>
</tr>
<tr>
<td>앞에서 꺼내기</td>
<td>pop(0) — O(n)</td>
<td>popleft() — O(1)</td>
</tr>
</tbody></table>
<p><strong>COS Pro 포인트</strong>: 큐 문제가 나오면 <code>deque</code>를 import하는 게 정석이다.</p>
<h3 id="3-빈-컨테이너-체크--not-활용">3. 빈 컨테이너 체크 — <code>not</code> 활용</h3>
<blockquote>
<p>수업 중 질문: &quot;stack이 리스트니까 <code>not list</code>도 되는 거야? dict이나 튜플 같은 다른 자료구조는?&quot;</p>
</blockquote>
<p>Python의 <strong>모든 컨테이너</strong>에 동일하게 적용된다:</p>
<pre><code class="language-python">not []         # True  (빈 리스트)
not {}         # True  (빈 딕셔너리)
not ()         # True  (빈 튜플)
not set()      # True  (빈 집합)
not deque()    # True  (빈 덱)
not &quot;&quot;         # True  (빈 문자열)</code></pre>
<p>Java의 <code>isEmpty()</code>를 모든 자료구조에 통일해서 쓸 수 있는 셈. COS Pro 빈칸에서 <code>not 변수명</code>이 나오면 &quot;비어있는지 확인&quot;이라고 읽으면 된다.</p>
<pre><code class="language-python"># 이 둘은 같은 의미
if len(stack) == 0:    # 명시적
if not stack:          # Pythonic — 시험에서 자주 나오는 형태</code></pre>
<h3 id="4-괄호-매칭--스택의-대표-패턴">4. 괄호 매칭 — 스택의 대표 패턴</h3>
<h4 id="단일-괄호-검사">단일 괄호 검사</h4>
<p>여는 괄호를 만나면 push, 닫는 괄호를 만나면 스택이 비어있는지 확인 후 pop.</p>
<pre><code class="language-python">def is_valid_parentheses(s):
    stack = []
    for char in s:
        if char == &#39;(&#39;:
            stack.append(char)
        elif char == &#39;)&#39;:
            if not stack:       # 짝이 없다
                return False
            stack.pop()
    return not stack            # 남은 괄호 없으면 True</code></pre>
<h4 id="다중-괄호-검사---">다중 괄호 검사 (<code>()</code>, <code>{}</code>, <code>[]</code>)</h4>
<p>딕셔너리로 여는/닫는 괄호의 짝을 매핑한다:</p>
<pre><code class="language-python">def is_valid_brackets(s):
    stack = []
    brackets = {&quot;[&quot;: &quot;]&quot;, &quot;(&quot;: &quot;)&quot;, &quot;{&quot;: &quot;}&quot;}

    for char in s:
        if char in brackets:           # .keys() 생략 가능!
            stack.append(char)
        elif char in brackets.values():
            if not stack or brackets[stack[-1]] != char:
                return False
            stack.pop()
    return not stack</code></pre>
<p><strong>딕셔너리 팁</strong>: <code>&quot;[&quot; in brackets</code>는 기본적으로 key에서 검색한다. <code>.keys()</code> 안 붙여도 된다.</p>
<h3 id="5-브라우저-뒤로가기--스택-활용">5. 브라우저 뒤로가기 — 스택 활용</h3>
<blockquote>
<p>수업 중 질문: &quot;visit과 back 개수가 같으면 &#39;홈&#39;으로 돌아가는 조건이 따로 필요한 거 아니야?&quot;</p>
</blockquote>
<p>&quot;홈&quot;도 visit할 때 스택에 들어가기 때문에 별도 조건 없이 자동으로 복귀된다.</p>
<pre><code class="language-python">def browser_history(actions):
    history = []
    current = &quot;홈&quot;
    for action, value in actions:
        if action == &quot;visit&quot;:
            history.append(current)   # ← 새 페이지가 아니라 &quot;지금 페이지&quot;를 저장!
            current = value
        elif action == &quot;back&quot;:
            if history:
                current = history.pop()
    return current</code></pre>
<p><strong>핵심 포인트</strong>: 스택에 저장하는 건 &quot;어디로 가는지(value)&quot;가 아니라 <strong>&quot;어디서 왔는지(current)&quot;</strong>. 뒤로 가기니까!</p>
<h3 id="6-요세푸스-문제--큐로-원형-순환">6. 요세푸스 문제 — 큐로 원형 순환</h3>
<p>N명이 원형으로 앉아있고 K번째 사람을 순서대로 제거하는 문제. 큐로 원형을 표현하는 트릭: <strong>앞에서 빼서 뒤로 보내면</strong> 원형처럼 동작한다.</p>
<pre><code class="language-python">from collections import deque

def josephus(n, k):
    queue = deque(range(1, n + 1))  # deque는 range를 직접 받을 수 있다
    result = []
    while queue:
        for _ in range(k - 1):         # k-1명 건너뛰기
            queue.append(queue.popleft())  # 앞에서 빼서 뒤로
        result.append(queue.popleft())     # k번째 사람 제거
    return result</code></pre>
<h3 id="7-프린터-우선순위--큐--튜플-추적">7. 프린터 우선순위 — 큐 + 튜플 추적</h3>
<p>우선순위가 높은 문서가 먼저 인쇄되는 프린터. 내 문서가 몇 번째로 인쇄되는지 구하는 문제.</p>
<blockquote>
<p>수업 중 질문: &quot;&#39;deque mutated during iteration&#39; 에러는 뭐야?&quot;</p>
</blockquote>
<p>deque를 <code>for</code>로 순회하는 도중에 내용을 변경하면 발생하는 에러. <code>while</code>로 바꿔서 해결한다.</p>
<pre><code class="language-python">from collections import deque

def print_order(priorities, target):
    queue = deque((p, idx) for idx, p in enumerate(priorities))
    count = 0
    while queue:
        p, idx = queue.popleft()
        if queue and p &lt; max(p for p, _ in queue):
            queue.append((p, idx))    # 우선순위 낮으면 뒤로
        else:
            count += 1
            if idx == target:         # 내 문서면 바로 반환
                return count</code></pre>
<p><strong>핵심</strong>: 큐에 (우선순위, 원래 인덱스) 튜플을 넣어서 내 문서를 추적한다. 인쇄 즉시 카운터로 반환하면 불필요한 순회를 줄일 수 있다.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>스택/큐 개념 + 6문제 풀이 (1차 정답 3문제, 최종 전부 정답)</li>
<li>핵심 패턴 정리: 괄호 매칭, 원형 순환, 우선순위 큐</li>
<li>다음 학습: 그리디 알고리즘 개념 및 문제 풀이</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260412 오늘의 학습: 약점 집중 연습 2회차]]></title>
            <link>https://velog.io/@lee_yesol421/20260412-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%95%BD%EC%A0%90-%EC%A7%91%EC%A4%91-%EC%97%B0%EC%8A%B5-2%ED%9A%8C%EC%B0%A8</link>
            <guid>https://velog.io/@lee_yesol421/20260412-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%95%BD%EC%A0%90-%EC%A7%91%EC%A4%91-%EC%97%B0%EC%8A%B5-2%ED%9A%8C%EC%B0%A8</guid>
            <pubDate>Sun, 12 Apr 2026 05:18:48 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<ul>
<li>약점 집중 연습 1회차에서 슬라이딩 윈도우 완전 정착 확인</li>
<li>문자열 순환 text*2 패턴 정착, 순회 범위 구분은 추가 연습 필요</li>
<li>데이터 변환 후 원본 소실 실수 1건 발견 — 반복 노출 필요 판정</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>남은 약점 2가지(원본 보존, 순환 순회 범위)를 집중 연습하고, 두 패턴을 결합한 종합 문제까지 도전한다.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-원본-보존-패턴--비교는-변환-반환은-원본">1. 원본 보존 패턴 — &quot;비교는 변환, 반환은 원본&quot;</h3>
<p>COS Pro에서 자주 나오는 패턴이다. 대소문자 무시하고 비교하되, 결과는 원래 형태 그대로 돌려줘야 하는 문제.</p>
<p>핵심 원칙은 간단하다:</p>
<pre><code class="language-python">for w in words:
    if w.lower().startswith(prefix_lower):  # 비교할 때만 변환
        result.append(w)                     # 결과엔 원본</code></pre>
<p>주의할 점은 <strong>변환한 리스트를 따로 만들어서 순회하면 안 된다</strong>는 것이다.</p>
<pre><code class="language-python"># 잘못된 패턴
cleaned_words = [w.lower() for w in words]
for word in cleaned_words:  # 여기서 이미 원본을 잃음
    result = word           # 소문자 버전이 저장됨

# 올바른 패턴
for word in words:          # 원본 리스트를 순회
    if word.lower() == ...: # 비교만 변환
        result = word       # 원본 저장</code></pre>
<p>디버깅 문제에서 <code>cleaned_words</code>라는 변수가 만들어져 있었는데, <code>isalpha()</code>로 알파벳만 세는 로직에서는 사실 대소문자 변환 자체가 불필요했다. 이런 <strong>함정용 변수</strong>를 코드에 넣어두고 순회 대상을 바꾸게 만드는 유형이 시험에 나올 수 있으니, 코드 전체를 비판적으로 읽는 습관이 중요하다.</p>
<h3 id="2-중복-제거-순서의-중요성">2. 중복 제거 순서의 중요성</h3>
<p>평균 이상인 학생을 점수 내림차순으로 반환하되, 동명이인(대소문자 무시)은 먼저 나온 학생만 포함하는 함수를 작성했다.</p>
<p>처음에는 <strong>정렬 후 중복 제거</strong>를 했더니, 점수가 높은 쪽이 먼저 처리되어 원래 순서의 이름이 아닌 다른 대소문자 버전이 남았다.</p>
<pre><code class="language-python"># 문제가 되는 순서
students_sorted = sorted(students, key=lambda x: x[1], reverse=True)
# (&quot;ALICE&quot;, 90)이 (&quot;Alice&quot;, 85)보다 먼저 → &quot;ALICE&quot;가 남음

# 올바른 순서: 중복 제거(원본 순서) → 필터 → 정렬</code></pre>
<p><strong>&quot;먼저 나온&quot;이라는 기준은 원본 리스트 순서</strong>를 의미하므로, 정렬 전에 중복 제거를 해야 한다.</p>
<p>수업 중 질문: &quot;코드가 마음에 안 드는데 더 효율적이고 깔끔한 방법 추천해줘&quot;
→ 두 가지 개선 포인트를 배웠다:</p>
<table>
<thead>
<tr>
<th>기존</th>
<th>개선</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>for 루프로 합계 계산</td>
<td><code>sum(s for _, s in students)</code></td>
<td>한 줄로 평균 계산</td>
</tr>
<tr>
<td>리스트 컴프리헨션으로 매번 중복 체크</td>
<td><code>set()</code>으로 체크</td>
<td>O(n) → O(1) 탐색</td>
</tr>
</tbody></table>
<p>특히 <code>set</code>을 이용한 중복 체크는 시험에서도 자주 쓰이는 최적화 패턴이다.</p>
<pre><code class="language-python"># 최종
def above_average_students(students):
    avg = sum(s for _, s in students) / len(students)

    seen = set()
    unique = []
    for name, score in students:
        if name.lower() not in seen and score &gt;= avg:
            seen.add(name.lower())
            unique.append((name, score))

    return [name for name, score in sorted(unique, key=lambda x: x[1], reverse=True)]</code></pre>
<h3 id="3-순환-순회-범위-복습--lentext가-정답">3. 순환 순회 범위 복습 — len(text)가 정답</h3>
<p>원형 문자열에서 길이 k인 부분 문자열을 탐색할 때:</p>
<pre><code class="language-python">extended = text * 2
for i in range(len(text)):  # len(extended)-k 아님!
    sub = extended[i:i+k]</code></pre>
<p><code>text * 2</code>로 확장해도, 원래 문자열의 시작 위치는 <code>len(text)</code>개뿐이다. <code>len(extended) - k</code>를 쓰면 중복 탐색이 발생한다.</p>
<table>
<thead>
<tr>
<th>비교</th>
<th>range</th>
<th>text=&quot;dacb&quot;, k=3</th>
</tr>
</thead>
<tbody><tr>
<td><code>len(text)</code></td>
<td>range(4) → 0,1,2,3</td>
<td>정확히 4개 위치</td>
</tr>
<tr>
<td><code>len(extended)-k</code></td>
<td>range(5) → 0,1,2,3,4</td>
<td>i=4는 &quot;cbd&quot;로 i=2와 중복</td>
</tr>
</tbody></table>
<h3 id="4-종합-문제--순환--원본-보존">4. 종합 문제 — 순환 + 원본 보존</h3>
<p>마지막 문제는 원형 배치된 학생 이름 리스트에서 연속 k명의 이름을 이어붙인 문자열이 사전순으로 가장 빠른 그룹을 찾아 원래 이름 리스트로 반환하는 함수였다.</p>
<p>두 약점을 한 번에 적용해야 하는 종합 문제:</p>
<pre><code class="language-python">def smallest_circular_group(names, k):
    names_extended = names * 2                              # 순환 처리
    names_lowered = [name.lower() for name in names_extended]  # 비교용 변환

    result = &quot;&quot;.join(names_lowered[:k])
    result_idx = 0

    for i in range(1, len(names)):                          # 순회 범위: len(names)
        sub = &quot;&quot;.join(names_lowered[i:k+i])
        if sub &lt; result:
            result = sub
            result_idx = i

    return names_extended[result_idx: result_idx+k]         # 반환은 원본</code></pre>
<ul>
<li><code>names_lowered</code>로 비교하고 <code>names_extended</code>에서 꺼내서 원본 보존</li>
<li>순회 범위는 <code>len(names)</code>로 정확히 제한</li>
<li><code>result_idx</code>로 위치를 추적해서 원본 리스트에서 슬라이싱</li>
</ul>
<p>참고: 디버깅용 <code>print()</code>는 시험 제출 전에 반드시 삭제해야 한다. 감점 요인이 될 수 있다.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>약점 연습 5문제 전부 1차 정답 — 원본 보존, 순환 범위 모두 <strong>정착 판정</strong></li>
<li>주요 약점 모두 해소 완료, 내일부터 프로그래머스/백준 문제 풀이로 전환</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260411 오늘의 학습: 약점 집중 연습]]></title>
            <link>https://velog.io/@lee_yesol421/20260411-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%95%BD%EC%A0%90-%EC%A7%91%EC%A4%91-%EC%97%B0%EC%8A%B5</link>
            <guid>https://velog.io/@lee_yesol421/20260411-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%95%BD%EC%A0%90-%EC%A7%91%EC%A4%91-%EC%97%B0%EC%8A%B5</guid>
            <pubDate>Sat, 11 Apr 2026 04:44:27 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<ul>
<li>구름EDU 기출 3회차 전체 완료 (10문제 모두 최종 정답)</li>
<li>슬라이딩 윈도우, 문자열 순환 처리(text*2) 새로 학습</li>
<li>내장함수명 변수 사용, 하드코딩 습관 재발 → 지속 주의 필요</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<ul>
<li>14차에서 발견된 취약 영역 3가지를 집중 복습</li>
<li>슬라이딩 윈도우 → 문자열 순환 → 함수 작성 종합 순서로 총 6문제</li>
</ul>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-슬라이딩-윈도우--빈칸-채우기">1. 슬라이딩 윈도우 — 빈칸 채우기</h3>
<p>연속된 k개 원소의 합이 최대인 구간의 합을 구하는 함수.</p>
<pre><code class="language-python">def max_subarray_sum(nums, k):
    if len(nums) &lt; k:
        return 0

    window_sum = sum(nums[:k])       # (a) 첫 윈도우
    max_sum = window_sum

    for i in range(k, len(nums)):    # (b) k부터 시작
        window_sum += nums[i] - nums[i-k]  # (c) 왼쪽 제거
        if window_sum &gt; max_sum:
            max_sum = window_sum

    return max_sum</code></pre>
<h4 id="슬라이딩-윈도우-핵심-구조">슬라이딩 윈도우 핵심 구조</h4>
<ol>
<li><strong>첫 윈도우</strong>: <code>sum(nums[:k])</code>로 초기값 계산</li>
<li><strong>이동 시작</strong>: 첫 윈도우 이미 구했으므로 <code>range(k, len(nums))</code></li>
<li><strong>갱신</strong>: 오른쪽 추가(<code>nums[i]</code>) - 왼쪽 제거(<code>nums[i-k]</code>)</li>
</ol>
<p>14차에서는 range 시작값에서 힌트가 필요했는데, 이번엔 바로 정답!</p>
<h3 id="2-슬라이딩-윈도우--함수-작성">2. 슬라이딩 윈도우 — 함수 작성</h3>
<p>연속된 k개 원소의 <strong>평균이 최대인 구간의 시작 인덱스</strong>를 반환하는 함수.</p>
<pre><code class="language-python">def solution(nums, k):
    std_sum = sum(nums[:k])
    std_idx = 0
    max_sum = std_sum

    for i in range(k, len(nums)):
        std_sum += nums[i] - nums[i-k]
        if std_sum &gt; max_sum:
            max_sum = std_sum
            std_idx = i - k + 1

    return std_idx</code></pre>
<h4 id="포인트">포인트</h4>
<ul>
<li><strong>합이 최대면 평균도 최대</strong> — k가 고정이니까 나누기 불필요. 불필요한 연산을 줄이는 판단이 좋았다는 피드백을 받았다.</li>
<li><strong>시작 인덱스</strong>: <code>i - k + 1</code> (빠진 원소의 다음 인덱스)</li>
<li><strong><code>&gt;</code> vs <code>&gt;=</code></strong>: 같은 합일 때 앞쪽 인덱스를 유지해야 하므로 <code>&gt;</code>(초과) 사용</li>
</ul>
<blockquote>
<p>수업 중 질문: &quot;첫번째 문제처럼 len(nums) &lt; k인 경우는 없어?&quot;
→ COS Pro 실제 시험에서는 보통 입력 조건이 보장되어 있어서 예외 처리 없이 바로 로직에 집중하면 된다.</p>
</blockquote>
<h3 id="3-문자열-순환--빈칸-채우기">3. 문자열 순환 — 빈칸 채우기</h3>
<p>문자열을 원형(순환)으로 간주하여 연속된 k글자 중 사전순으로 가장 앞서는 부분 문자열을 반환하는 함수.</p>
<pre><code class="language-python">def earliest_circular_substring(text, k):
    extended = text * 2            # (a) 순환 처리
    result = extended[:k]

    for i in range(1, len(text)):  # (b) 고유 시작점은 len(text)개
        substring = extended[i:i+k]
        if substring &lt; result:
            result = substring

    return result</code></pre>
<h4 id="순환-처리-핵심">순환 처리 핵심</h4>
<ul>
<li><strong><code>text * 2</code></strong>: 문자열을 두 번 이어붙여서 끝을 넘어가는 케이스 해결</li>
<li><strong>순회 범위는 <code>len(text)</code></strong>: 원형 문자열에서 고유한 시작점은 원본 길이만큼만 존재</li>
</ul>
<p>처음에 <code>len(extended) - k</code>로 답했는데, 동작은 하지만 중복 순회가 발생한다. 시험 빈칸 채우기에서는 <code>len(text)</code>가 정답이므로 오답 처리될 수 있다는 피드백을 받았다.</p>
<table>
<thead>
<tr>
<th>i</th>
<th>&quot;dbca&quot;(k=2)</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>db</td>
<td></td>
</tr>
<tr>
<td>1</td>
<td>bc</td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>ca</td>
<td></td>
</tr>
<tr>
<td>3</td>
<td><strong>ad</strong></td>
<td>← 순환으로만 나오는 조합</td>
</tr>
<tr>
<td>4</td>
<td>db</td>
<td>i=0과 동일 (불필요)</td>
</tr>
<tr>
<td>5</td>
<td>bc</td>
<td>i=1과 동일 (불필요)</td>
</tr>
</tbody></table>
<h3 id="4-문자열-순환--함수-작성">4. 문자열 순환 — 함수 작성</h3>
<p>문자열을 순환으로 간주하여 연속된 k글자로 만들 수 있는 <strong>서로 다른 부분 문자열의 개수</strong>를 반환하는 함수.</p>
<pre><code class="language-python">def solution(text, k):
    extended = text * 2
    str_set = set()

    for i in range(0, len(text)):
        sub_str = extended[i:i+k]
        str_set.add(sub_str)

    return len(str_set)</code></pre>
<h4 id="포인트-1">포인트</h4>
<ul>
<li><strong>set()으로 중복 제거</strong> — &quot;서로 다른 개수&quot;에 딱 맞는 자료구조</li>
<li>앞 문제 피드백을 바로 반영하여 <code>len(text)</code> 범위로 정확히 설정</li>
<li>변수명 <code>str_set</code> — <code>str</code>로 지었으면 내장함수를 덮어쓸 뻔했는데 잘 피함</li>
</ul>
<h3 id="5-함수-작성-종합-1--상하위-평균-차이">5. 함수 작성 종합 (1) — 상하위 평균 차이</h3>
<p>학생들의 점수에서 상위 k명과 하위 k명의 평균 차이를 반환하는 함수.</p>
<pre><code class="language-python">def solution(scores, k):
    sorted_scores = sorted(scores)
    high_avg = sum(sorted_scores[-k:]) / k
    low_avg = sum(sorted_scores[:k]) / k

    return high_avg - low_avg</code></pre>
<h4 id="포인트-2">포인트</h4>
<ul>
<li><code>sorted()</code>로 원본 안 건드림 (좋은 습관)</li>
<li><code>[-k:]</code>, <code>[:k]</code> 슬라이싱 정확</li>
<li>변수명 <code>sorted_scores</code> — 내장함수명 안 겹침</li>
</ul>
<blockquote>
<p>수업 중 질문: &quot;abs() 감싼다는 건 마지막 return할 때 <code>abs(high_avg - low_avg)</code> 하라는 거야?&quot;
→ 맞다. 이 문제에서는 정렬 후 상위 &gt;= 하위가 보장되지만, &quot;절댓값으로 반환하라&quot;는 조건이 명시된 문제라면 <code>abs()</code>를 감싸는 게 안전하다.</p>
</blockquote>
<p>풀이 중 출제자가 제시한 예시 답(35.0)이 틀렸다는 것을 직접 계산해서 발견했다 (정답: 40.0). 검증 습관이 좋다는 피드백을 받았다.</p>
<h3 id="6-함수-작성-종합-2--모음-최다-단어">6. 함수 작성 종합 (2) — 모음 최다 단어</h3>
<p>문자열 리스트에서 모음(a, e, i, o, u)이 가장 많은 단어를 반환하는 함수.</p>
<h4 id="첫-번째-시도-버그-있음">첫 번째 시도 (버그 있음)</h4>
<pre><code class="language-python">def solution(words):
    lowered_words = [x.lower() for x in words]  # 여기서 원본 소실!
    chk_list = [&#39;a&#39;, &#39;e&#39;, &#39;i&#39;, &#39;o&#39;, &#39;u&#39;]
    max_count = 0
    result_word = &quot;&quot;

    for w in lowered_words:  # 소문자 버전을 순회
        ...
        result_word = w      # 소문자 버전이 저장됨

    return result_word</code></pre>
<p><strong>문제</strong>: <code>lowered_words</code>를 순회하면서 <code>result_word</code>에 소문자 변환된 값이 저장된다. <code>[&quot;APPLE&quot;]</code> 입력 시 <code>&quot;apple&quot;</code>이 반환됨.</p>
<h4 id="수정-후-정답">수정 후 (정답)</h4>
<pre><code class="language-python">def solution(words):
    chk_list = [&#39;a&#39;, &#39;e&#39;, &#39;i&#39;, &#39;o&#39;, &#39;u&#39;]
    max_count = 0
    result_word = &quot;&quot;

    for w in words:              # 원본을 순회
        cnt = 0
        for ch in w.lower():    # 비교할 때만 소문자로
            if ch in chk_list:
                cnt += 1
        if cnt &gt; max_count:
            max_count = cnt
            result_word = w      # 원본이 저장됨

    return result_word</code></pre>
<h4 id="데이터-변환-시-원본-보존-패턴">데이터 변환 시 원본 보존 패턴</h4>
<table>
<thead>
<tr>
<th>방식</th>
<th>코드</th>
<th>원본 유지</th>
</tr>
</thead>
<tbody><tr>
<td>변환 리스트 순회</td>
<td><code>for w in lowered_words:</code></td>
<td>X</td>
</tr>
<tr>
<td>원본 순회 + 비교만 변환</td>
<td><code>for w in words: ... w.lower()</code></td>
<td>O</td>
</tr>
</tbody></table>
<p><strong>핵심</strong>: 비교/검색에만 변환을 쓰고, 결과에는 원본을 저장할 것!</p>
<p>추가로 <code>chk_list</code> 대신 문자열로도 가능하다는 팁을 받았다: <code>if ch in &quot;aeiou&quot;</code></p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>6문제 풀이, 1차 정답률 67% (4/6) — 함수 작성 3/4로 개선 추세 유지</li>
<li>슬라이딩 윈도우 완전 정착, 내장함수명 변수 사용·하드코딩 미발생</li>
<li>다음 학습: 프로그래머스/백준 문제 풀이로 함수 작성 실전 연습 시작</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260409 오늘의 학습: COS Pro 기출 3회차 완료]]></title>
            <link>https://velog.io/@lee_yesol421/20260409-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-COS-Pro-%EA%B8%B0%EC%B6%9C-3%ED%9A%8C%EC%B0%A8-%EC%99%84%EB%A3%8C</link>
            <guid>https://velog.io/@lee_yesol421/20260409-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-COS-Pro-%EA%B8%B0%EC%B6%9C-3%ED%9A%8C%EC%B0%A8-%EC%99%84%EB%A3%8C</guid>
            <pubDate>Thu, 09 Apr 2026 15:04:38 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<ul>
<li>구름EDU 기출 3회차 전반부(1, 2, 3, 6, 7번) 5문제 전부 1차 정답</li>
<li>배열 회전, 팰린드롬, 소수 판별, 카프리카 수, 비숍 공격 범위 학습</li>
<li>3회차 나머지 5문제(4, 5, 8, 9, 10번) 풀이 예정이었음</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<ul>
<li>구름EDU 기출 3회차 나머지 5문제 풀이 (4, 5, 8, 9, 10번)</li>
<li>디버깅 2문제 + 빈칸 1문제 + 함수 작성 2문제</li>
</ul>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-break-vs-continue-vs-pass--디버깅-문제-8">1. break vs continue vs pass — 디버깅 (문제 8)</h3>
<p>선풍기 대수를 구하는 디버깅 문제. 교실마다 선풍기 4대가 있고, 선풍기 1대당 k명에게 바람을 보낼 때, 추가로 필요한 선풍기 총 대수를 구하는 코드에서 한 줄 수정.</p>
<h4 id="버그-break-→-continue">버그: <code>break</code> → <code>continue</code></h4>
<pre><code class="language-python">for s in student:
    s -= 4 * k
    if s &lt;= 0:
        break       # ❌ 루프 전체 종료 — 뒤 교실 무시
        continue     # ✅ 이 교실 건너뛰고 다음으로
    answer += (s + k - 1) // k</code></pre>
<h4 id="break--continue--pass-차이-정리">break / continue / pass 차이 정리</h4>
<table>
<thead>
<tr>
<th>키워드</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td><code>break</code></td>
<td>루프 <strong>전체 종료</strong></td>
</tr>
<tr>
<td><code>continue</code></td>
<td>이번 반복 <strong>건너뛰고</strong> 다음 반복으로</td>
</tr>
<tr>
<td><code>pass</code></td>
<td><strong>아무것도 안 함</strong> (다음 줄 실행)</td>
</tr>
</tbody></table>
<p>이 문제에서 <code>pass</code>를 쓰면 <code>if</code> 블록 이후의 <code>answer += ...</code> 줄이 실행되어 0이하 값이 더해질 수 있다. <code>continue</code>가 정확한 답이다.</p>
<hr>
<h3 id="2-슬라이딩-윈도우--디버깅-문제-9">2. 슬라이딩 윈도우 — 디버깅 (문제 9)</h3>
<p>팝업스토어를 열 최적의 날짜를 찾는 문제. n일간의 매출액 중 연속 k일의 합이 최대인 값을 구한다.</p>
<h4 id="슬라이딩-윈도우-패턴">슬라이딩 윈도우 패턴</h4>
<p>연속 k개 합을 구할 때, 매번 sum()을 쓰면 느리다. 대신 &quot;하나 빼고 하나 더하기&quot;로 O(n)에 해결한다.</p>
<pre><code class="language-python"># 첫 윈도우 합
rsum = sum(revenue[0:k])
answer = rsum

# 윈도우를 한 칸씩 밀기
for i in range(k, len(revenue)):    # ← i는 k부터!
    rsum = rsum - revenue[i - k] + revenue[i]
    if answer &lt; rsum:
        answer = rsum</code></pre>
<p><strong>핵심 포인트:</strong></p>
<ul>
<li>첫 윈도우(0~k-1)는 이미 구했으니, for문은 <strong>i=k부터</strong> 시작</li>
<li><code>i</code>는 항상 윈도우의 <strong>오른쪽 끝</strong>을 가리킴</li>
<li><code>revenue[i-k]</code>는 빠지는 왼쪽 값, <code>revenue[i]</code>는 새로 들어오는 오른쪽 값</li>
</ul>
<p>예시 (k=4, <code>[1, 1, 9, 3, 7, 6, 5, 10]</code>):</p>
<pre><code>첫 윈도우: [1, 1, 9, 3]              → sum = 14
i=4:        1 [1, 9, 3, 7]           → 14 - 1 + 7 = 20
i=5:           [9, 3, 7, 6]          → 20 - 1 + 6 = 25
i=6:              [3, 7, 6, 5]       → 25 - 9 + 5 = 21
i=7:                 [7, 6, 5, 10]   → 21 - 3 + 10 = 28 ← 최대!</code></pre><blockquote>
<p>수업 중 질문: &quot;range 시작값으로 0이나 1이 아닌 k를 쓰는 이유?&quot;
→ i가 len을 넘지 않으면서도 i-k가 0부터 시작하게 하려는 것. i가 윈도우의 오른쪽 끝이기 때문에, i=k일 때 첫 번째로 빠지는 값이 revenue[0]이 된다.</p>
</blockquote>
<hr>
<h3 id="3-클래스-상속--메서드-오버라이드--빈칸-채우기-문제-10">3. 클래스 상속 + 메서드 오버라이드 — 빈칸 채우기 (문제 10)</h3>
<p>미용실(HairShop)과 레스토랑(Restaurant)이 Shop을 상속받아 각각 예약 조건을 다르게 구현하는 문제. 빈칸 8개를 채워야 한다.</p>
<h4 id="예약-조건">예약 조건</h4>
<ul>
<li><strong>미용실</strong>: 인원수 1명만, 시간 겹침 불가</li>
<li><strong>레스토랑</strong>: 인원수 2~8명, 같은 시간 최대 2팀까지</li>
</ul>
<pre><code class="language-python">class HairShop(Shop):                        # 상속
    def __init__(self):
        super().__init__()

    def reserve(self, customer):             # 메서드 오버라이드
        if customer.num_of_people != 1:      # 1명 아니면 거절
            return False
        for r in self.reserve_list:
            if r.time == customer.time:      # 시간 겹침 체크
                return False
        self.reserve_list.append(customer)
        return True</code></pre>
<pre><code class="language-python">class Restaurant(Shop):
    def reserve(self, customer):
        if customer.num_of_people &lt; 2 or customer.num_of_people &gt; 8:
            return False
        count = 0
        for r in self.reserve_list:
            if r.time == customer.time:
                count += 1
        if count &gt;= 2:          # for문 밖에서 판단!
            return False
        self.reserve_list.append(customer)
        return True</code></pre>
<p><strong>포인트:</strong> Restaurant의 <code>count &gt;= 2</code> 체크는 for문 <strong>밖</strong>에서 한다. 안에서 하면 아직 다 세지도 않았는데 판단하게 될 수 있다.</p>
<p>이 문제는 10차 세션에서 배운 클래스 상속 개념으로 8개 빈칸 전부 1차 정답!</p>
<hr>
<h3 id="4-문자열-겹침-이어붙이기--함수-작성-문제-4">4. 문자열 겹침 이어붙이기 — 함수 작성 (문제 4)</h3>
<p>두 문자열 s1, s2를 이어붙이되, 한쪽 끝과 다른 쪽 시작이 겹치면 한 번만 쓴다. s1+s2, s2+s1 중 더 짧은 길이를 return.</p>
<h4 id="풀이-핵심-두-방향-겹침-중-최대값-찾기">풀이 핵심: 두 방향 겹침 중 최대값 찾기</h4>
<pre><code class="language-python">def solution(s1, s2):
    answer = 0
    std_len = min(len(s1), len(s2))
    dup_len = 0

    for i in range(1, std_len + 1):    # ← +1 주의!
        # s2 뒤에 s1 붙이는 경우 (s1 시작 = s2 끝)
        if s1[:i] == s2[-i:]:
            dup_len = i
        # s1 뒤에 s2 붙이는 경우 (s1 끝 = s2 시작)
        if s1[-i:] == s2[:i] and i &gt; dup_len:
            dup_len = i

    answer = len(s1) + len(s2) - dup_len
    return answer</code></pre>
<p><strong>주의:</strong> <code>range(1, std_len)</code>이 아니라 <code>range(1, std_len + 1)</code> — 겹침이 짧은 쪽 문자열 전체 길이와 같을 수 있기 때문이다.</p>
<hr>
<h3 id="5-전광판-스크롤--함수-작성-문제-5">5. 전광판 스크롤 — 함수 작성 (문제 5)</h3>
<p>화면에 14자를 표시하는 전광판에서, 문구가 왼쪽으로 1초에 한 칸씩 스크롤된다. 주어진 초(second)에 화면에 보이는 문자열을 return.</p>
<h4 id="풀이-핵심-밑줄-패딩--text2로-순환-처리">풀이 핵심: 밑줄 패딩 + text*2로 순환 처리</h4>
<pre><code class="language-python">def solution(phrases, second):
    answer = &#39;&#39;
    text = phrases.zfill(len(phrases) * 2).replace(&quot;0&quot;, &quot;_&quot;)
    d_text = text * 2
    cal_sc = second % len(text)
    answer = d_text[cal_sc:len(phrases) + cal_sc]
    return answer</code></pre>
<p><strong>단계별 동작:</strong></p>
<ol>
<li><code>zfill(28)</code>: &quot;happy-birthday&quot; → &quot;00000000000000happy-birthday&quot; (14개의 0 + 문구)</li>
<li><code>.replace(&quot;0&quot;, &quot;_&quot;)</code>: &quot;______________happy-birthday&quot; (밑줄 14개 + 문구)</li>
<li><code>text * 2</code>: 문자열을 두 번 이어붙여서 순환 처리</li>
<li><code>second % len(text)</code>: 나머지 연산으로 반복 주기 처리</li>
<li>슬라이싱으로 14글자 추출</li>
</ol>
<blockquote>
<p>수업 중 질문: &quot;왜 text를 두 번 붙이나요?&quot;
→ 슬라이싱이 문자열 끝을 넘어가면 잘린다. 예를 들어 t=20일 때 <code>text[20:34]</code>인데 text는 28자라 8글자만 나온다. text*2로 56자를 만들어두면 어디서 잘라도 항상 14글자가 보장된다.</p>
</blockquote>
<p><strong>주의:</strong> <code>str</code>은 Python 내장함수이므로 변수명으로 사용하면 안 된다! <code>text</code>, <code>display_str</code> 같은 이름을 쓰자.</p>
<hr>
<h3 id="6-음수-인덱스와-슬라이싱-복습">6. 음수 인덱스와 슬라이싱 복습</h3>
<p>문제 4에서 <code>s1[-i:]</code> 같은 표현이 나왔는데, 왜 이게 &quot;뒤에서 i글자&quot;가 되는지 정리.</p>
<h4 id="음수-인덱스--뒤에서부터-세는-위치">음수 인덱스 = 뒤에서부터 세는 위치</h4>
<pre><code class="language-python">s = &quot;hello&quot;
#     h  e  l  l  o
# 양수: 0  1  2  3  4
# 음수:-5 -4 -3 -2 -1</code></pre>
<h4 id="슬라이싱에서-생략된-부분">슬라이싱에서 생략된 부분</h4>
<p><code>s[-1:]</code>은 사실 <code>s[-1:len(s)]</code>와 같다. 뒤쪽을 생략하면 <strong>끝까지</strong>라는 뜻.</p>
<pre><code class="language-python">s[-1:]   # s[-1:5] → &quot;o&quot;       (뒤 1글자)
s[-2:]   # s[-2:5] → &quot;lo&quot;      (뒤 2글자)
s[-3:]   # s[-3:5] → &quot;llo&quot;     (뒤 3글자)</code></pre>
<p>마찬가지로 <code>s[:3]</code>은 <code>s[0:3]</code>과 같다. 앞쪽 생략하면 <strong>처음부터</strong>.</p>
<h4 id="주의-음수-인덱스-≠-역순-출력">주의: 음수 인덱스 ≠ 역순 출력</h4>
<blockquote>
<p>수업 중 질문: &quot;음수 인덱스를 쓰면 거꾸로 출력되나요?&quot;
→ 아니다! 음수는 <strong>시작 위치</strong>만 뒤쪽일 뿐, 출력 순서는 왼→오 그대로다.</p>
</blockquote>
<pre><code class="language-python">s = &quot;hello&quot;
s[-3:]    # &quot;llo&quot; ← 뒤에서 3글자를 순서대로
s[::-1]   # &quot;olleh&quot; ← 이게 진짜 역순! (step이 -1)</code></pre>
<p><strong>역순은 step에 -1을 줘야 한다.</strong> 음수가 나오는 위치가 다르다:</p>
<ul>
<li><strong>인덱스의 음수</strong> → 위치 (어디서부터)</li>
<li><strong>step의 음수</strong> → 방향 (어느 쪽으로)</li>
</ul>
<pre><code class="language-python"># 뒤 3글자를 거꾸로 출력하려면?
s[:-4:-1]   # &quot;oll&quot; ← start 생략(=끝), end=-4(=index 1) 전까지, step=-1</code></pre>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>기출 3회차 나머지 5문제 완료 → <strong>3회차 전체 10문제 완료!</strong></li>
<li>1차 정답률 3/5 (60%) — 슬라이딩 윈도우, 전광판 스크롤은 힌트 후 해결</li>
<li>다음 학습: 구름EDU 기출 4회차 (학습용 마지막)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260406 오늘의 학습: COS Pro 기출 2회차 완료]]></title>
            <link>https://velog.io/@lee_yesol421/20260406-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-COS-Pro-%EA%B8%B0%EC%B6%9C-2%ED%9A%8C%EC%B0%A8-%EC%99%84%EB%A3%8C</link>
            <guid>https://velog.io/@lee_yesol421/20260406-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-COS-Pro-%EA%B8%B0%EC%B6%9C-2%ED%9A%8C%EC%B0%A8-%EC%99%84%EB%A3%8C</guid>
            <pubDate>Mon, 06 Apr 2026 12:14:44 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<ul>
<li>구름EDU 기출 2회차 1~4번, 10번 풀이 완료</li>
<li>클래스 상속/추상 클래스, itertools.combinations 새로 학습</li>
<li>combinations에서 상수/변수 혼동 실수 1건 (힌트 후 교정)</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<ul>
<li>구름EDU 기출 2회차 나머지 5~9번 마무리</li>
<li>2회차 완료 후 학습 일정 점검</li>
</ul>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-거스름돈-구하기--빈칸-채우기-문제-7">1. 거스름돈 구하기 — 빈칸 채우기 (문제 7)</h3>
<p>금액을 8가지 화폐(10원~50000원)로 거슬러줄 때 동전+지폐 개수의 합을 최소로 만드는 문제다.</p>
<h4 id="그리디greedy-알고리즘">그리디(Greedy) 알고리즘</h4>
<p>&quot;큰 단위부터 최대한 많이 쓰고, 나머지를 다음 단위로 넘긴다&quot;는 전략이다. 이 문제처럼 화폐 단위가 배수 관계일 때 그리디가 최적해를 보장한다.</p>
<pre><code class="language-python">def solution(money):
    coin = [10, 50, 100, 500, 1000, 5000, 10000, 50000]
    counter = 0
    idx = len(coin) - 1
    while money:
        counter += money // coin[idx]  # 해당 화폐로 몇 개 거슬러줄 수 있는지
        money %= coin[idx]             # 나머지를 다음 화폐로 넘김
        idx -= 1
    return counter</code></pre>
<p>처음에 <code>counter += 1</code>로 작성했다가, 50000원짜리를 0개 써도 카운터가 올라가는 문제를 발견했다. <code>money // coin[idx]</code>로 해당 화폐를 <strong>최대 몇 개</strong> 쓸 수 있는지 계산해야 한다.</p>
<h3 id="2-규칙에-맞는-배열-구하기--디버깅-문제-8">2. 규칙에 맞는 배열 구하기 — 디버깅 (문제 8)</h3>
<p>배열의 앞/뒤에서 번갈아 가져와 새 배열을 만드는 문제다.</p>
<pre><code class="language-python"># 버그 코드
if left % 2 == 0:    # left는 앞에서 가져올 때만 증가

# 수정
if idx % 2 == 0:     # idx는 매 반복마다 증가 → 번갈아 동작</code></pre>
<p><code>left</code>는 앞에서 가져올 때만 증가하므로, 한 번 홀수가 되면 계속 뒤에서만 가져오게 된다. 매 반복마다 1씩 증가하는 <code>idx</code>를 기준으로 짝수/홀수를 판단해야 번갈아 동작한다.</p>
<h3 id="3-비밀번호-검사--디버깅-문제-9">3. 비밀번호 검사 — 디버깅 (문제 9)</h3>
<p>비밀번호에 연속 3자리 이상의 알파벳이나 숫자(abc, 987 등)가 있으면 안전하지 않다고 판단하는 문제다.</p>
<pre><code class="language-python"># 버그 코드
first_check = ord(password[i + 1]) - ord(password[i])
second_check = ord(password[i]) - ord(password[i+1])   # first_check의 부호 반대일 뿐

# 수정
second_check = ord(password[i+2]) - ord(password[i+1])  # 다음 쌍의 차이</code></pre>
<p>3연속을 판단하려면 <strong>연속된 두 쌍</strong>의 차이를 비교해야 한다:</p>
<ul>
<li>i→i+1 차이와 i+1→i+2 차이가 같고, 그 값이 ±1이면 3연속이다.</li>
</ul>
<h3 id="4-언제까지-오르막길이야--함수-작성-문제-5">4. 언제까지 오르막길이야 — 함수 작성 (문제 5)</h3>
<p>배열에서 숫자가 연속해서 증가하는 가장 긴 구간의 길이를 구하는 문제다. 같은 값은 증가로 치지 않는다.</p>
<pre><code class="language-python">def solution(arr):
    answer = 0
    tmp_answer = 1
    std_num = arr[0]

    for i in range(1, len(arr)):
        if arr[i] &gt; std_num:
            tmp_answer += 1
        else:
            tmp_answer = 1
        answer = max(answer, tmp_answer)  # 매 반복마다 갱신!
        std_num = arr[i]

    return answer</code></pre>
<p>수업 중 질문: 처음에 <code>answer = max(answer, tmp_answer)</code>를 <code>else</code> 블록 안에만 넣었더니, 배열 끝까지 증가하는 경우 answer가 갱신되지 않는 문제가 있었다. → <code>for</code> 루프 안, <code>if/else</code> 바깥으로 옮겨서 <strong>매 반복마다</strong> 최댓값을 갱신하도록 수정했다.</p>
<h3 id="5-로봇을-움직여주세요--함수-작성-문제-6">5. 로봇을 움직여주세요 — 함수 작성 (문제 6)</h3>
<p>명령 문자열(L, R, U, D)에 따라 로봇을 이동시킨 최종 위치를 반환하는 문제다.</p>
<pre><code class="language-python">from collections import Counter

def solution(commands):
    x, y = 0, 0
    dir = Counter(commands)
    for key, value in dir.items():
        if key == &quot;L&quot;:
            x -= value
        if key == &quot;R&quot;:
            x += value
        if key == &quot;U&quot;:
            y += value
        if key == &quot;D&quot;:
            y -= value
    return [x, y]</code></pre>
<p>Counter로 각 방향의 횟수를 한 번에 세서 처리하는 방식으로 풀었다. 이 문제는 이동 순서와 상관없이 최종 위치만 구하면 되기 때문에 Counter 방식이 유효하다. 만약 이동 경로를 추적해야 했다면 한 글자씩 순회해야 한다.</p>
<hr>
<h3 id="학습-일정-점검">학습 일정 점검</h3>
<p>구름EDU 기출 6세트의 활용 계획을 정리했다:</p>
<table>
<thead>
<tr>
<th>회차</th>
<th>용도</th>
<th>시기</th>
</tr>
</thead>
<tbody><tr>
<td>1~2회차</td>
<td>✅ 학습용 완료</td>
<td>완료</td>
</tr>
<tr>
<td>3~4회차</td>
<td>학습용 (개념 설명 + 풀이)</td>
<td>4월 중</td>
</tr>
<tr>
<td>5회차</td>
<td>90분 타이머 모의고사</td>
<td>시험 2주 전 (5월 초)</td>
</tr>
<tr>
<td>6회차</td>
<td>90분 타이머 모의고사</td>
<td>시험 1주 전 (5월 둘째 주)</td>
</tr>
</tbody></table>
<p>3~4회차 사이에는 프로그래머스, 백준 등 외부 플랫폼으로 추가 연습을 병행할 예정이다.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>기출 2회차 5~9번 완료 → <strong>2회차 전체 완료</strong> (빈칸 1 + 디버깅 2 + 함수 작성 2)</li>
<li>힌트 1회 (오르막길 문제: 루프 종료 후 answer 갱신 누락)</li>
<li>다음 학습: 구름EDU 기출 3회차 시작</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260402 오늘의 학습: COS Pro 기출 2회차 풀이 (1~4, 10번)]]></title>
            <link>https://velog.io/@lee_yesol421/20260402-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-COS-Pro-%EA%B8%B0%EC%B6%9C-2%ED%9A%8C%EC%B0%A8-%ED%92%80%EC%9D%B4-14-10%EB%B2%88</link>
            <guid>https://velog.io/@lee_yesol421/20260402-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-COS-Pro-%EA%B8%B0%EC%B6%9C-2%ED%9A%8C%EC%B0%A8-%ED%92%80%EC%9D%B4-14-10%EB%B2%88</guid>
            <pubDate>Thu, 02 Apr 2026 14:05:40 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<ul>
<li>구름EDU 기출 1회차 6~10번 풀이 완료 (빈칸 1 + 디버깅 3 + 함수 작성 1)</li>
<li><code>&amp;</code> vs <code>and</code> 혼동, 범위 체크 불일치 등 실수 포인트 정리</li>
<li><code>ord()</code>, <code>max(0, x)</code> 패턴 새로 학습</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<ul>
<li>구름EDU 기출 2회차 시작</li>
<li>새로운 개념(클래스 상속, itertools.combinations)이 등장하면 설명 먼저 → 응용문제 풀기</li>
</ul>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-클래스-상속과-추상-클래스-문제-1-도서-대여점-운영">1. 클래스 상속과 추상 클래스 (문제 1: 도서 대여점 운영)</h3>
<p>기출 1번 문제에서 <strong>추상 클래스와 상속</strong> 개념이 처음 등장했다. Java의 <code>interface</code>/<code>abstract class</code>와 같은 개념이다.</p>
<h4 id="추상-클래스-만들기">추상 클래스 만들기</h4>
<pre><code class="language-python">from abc import ABC, abstractmethod

class Book(ABC):               # ABC를 상속 = 추상 클래스
    @abstractmethod            # 자식이 반드시 구현해야 하는 메서드
    def get_rental_price(self, day):
        pass</code></pre>
<ul>
<li><code>ABC</code> = Abstract Base Class</li>
<li><code>@abstractmethod</code> = Java의 <code>abstract</code> 키워드와 동일</li>
<li>추상 클래스는 직접 객체 생성 불가 (<code>Book()</code>하면 에러)</li>
</ul>
<p>문제 코드에서는 <code>metaclass=ABCMeta</code> 방식이 사용되었는데, <code>ABC</code>를 상속하는 것과 동일한 의미이다.</p>
<pre><code class="language-python"># 방법 1: metaclass 직접 지정 (기출 코드 방식)
class Book(metaclass=ABCMeta):
    ...

# 방법 2: ABC 상속 (더 간단, 실무에서 주로 사용)
class Book(ABC):
    ...</code></pre>
<h4 id="상속-문법">상속 문법</h4>
<pre><code class="language-python"># Java:   class ComicBook extends Book
# Python: class ComicBook(Book)
class ComicBook(Book):
    def get_rental_price(self, day):   # 추상 메서드 구현
        cost = 500
        day -= 2
        if day &gt; 0:
            cost += day * 200
        return cost</code></pre>
<h4 id="응용문제로-연습-주차장-요금-계산">응용문제로 연습 (주차장 요금 계산)</h4>
<p>같은 구조로 <code>Vehicle</code> 추상 클래스 → <code>SmallCar</code>, <code>LargeCar</code> 상속 문제를 풀었다. 기출 구조를 그대로 응용해서 직접 클래스를 작성하고 <code>solution</code> 함수까지 완성했다.</p>
<p><strong>주의</strong>: <code>type</code>은 Python 내장 함수명이라 변수명으로 쓰면 내장 <code>type()</code>을 덮어쓴다. <code>car_type</code> 같은 이름이 안전하다. (이전에 <code>sum</code>을 변수명으로 쓴 것과 같은 패턴!)</p>
<h3 id="2-시간-→-분-변환-문제-2-지하철-기다리기">2. 시간 → 분 변환 (문제 2: 지하철 기다리기)</h3>
<p>빈칸 채우기 문제. <code>&quot;HH:MM&quot;</code> 문자열을 분 단위 정수로 변환하는 <code>func_a</code>가 핵심이었다.</p>
<pre><code class="language-python">def func_a(times):
    hour = int(times[:2])
    minute = int(times[3:])
    return hour * 60 + minute</code></pre>
<p>도착 시간이 순서대로 정렬되어 있으므로, 현재 시간 이후인 첫 번째 지하철을 찾으면 바로 <code>break</code>하는 패턴이다.</p>
<h3 id="3-이름-없는-함수-역할-파악-문제-3-경품-당첨자를-구해주세요">3. 이름 없는 함수 역할 파악 (문제 3: 경품 당첨자를 구해주세요)</h3>
<p><code>func_a</code>, <code>func_b</code>, <code>func_c</code>처럼 이름만으로 역할을 알 수 없는 함수가 나오는 빈칸 채우기 문제.</p>
<p><strong>역할 파악 팁: 간단한 값을 넣어서 결과를 머릿속으로 돌려보기</strong></p>
<table>
<thead>
<tr>
<th>함수</th>
<th>동작</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>func_a(n)</code></td>
<td>10^n (10을 n번 곱함)</td>
<td>func_a(3) → 1000</td>
</tr>
<tr>
<td><code>func_b(n)</code></td>
<td>자릿수 구하기 (<code>n //= 10</code> + 카운트)</td>
<td>func_b(235) → 3</td>
</tr>
<tr>
<td><code>func_c(n)</code></td>
<td>각 자릿수 합 (<code>n % 10</code> + 누적합)</td>
<td>func_c(235) → 10</td>
</tr>
</tbody></table>
<p><strong>자주 나오는 숫자 처리 패턴:</strong></p>
<table>
<thead>
<tr>
<th>패턴</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td><code>n //= 10</code> + 카운트</td>
<td>자릿수 구하기</td>
</tr>
<tr>
<td><code>n % 10</code> + 누적합</td>
<td>각 자릿수 합</td>
</tr>
<tr>
<td><code>10을 반복 곱하기</code></td>
<td>10의 거듭제곱</td>
</tr>
<tr>
<td><code>n % 10</code> + 역순 조립</td>
<td>숫자 뒤집기</td>
</tr>
</tbody></table>
<p>수업 중 질문: &quot;func_a, func_b, func_c 역할 파악하는데 시간이 오래 걸렸어&quot;
→ 코드를 한 줄씩 읽기 전에 <strong>간단한 값을 넣어서 입출력을 먼저 확인</strong>하면 빠르게 파악할 수 있다. 이런 패턴은 반복 출제되므로 몇 번 더 보면 코드 읽기 전에 바로 알아볼 수 있게 된다.</p>
<p><strong>팁</strong>: <code>length / 2</code>는 Python 3에서 <code>float</code>가 된다 (예: <code>6 / 2 → 3.0</code>). 정수 나눗셈 <code>//</code>를 쓰는 습관을 들이자.</p>
<h3 id="4-itertoolscombinations-문제-4-합이-k배가-되는-수">4. itertools.combinations (문제 4: 합이 K배가 되는 수)</h3>
<p>함수 작성 문제에서 <code>itertools.combinations</code>가 처음 등장했다. 배열에서 n개를 뽑는 모든 조합을 구하는 도구이다.</p>
<pre><code class="language-python">from itertools import combinations

arr = [1, 2, 3, 4, 5]
for combo in combinations(arr, 3):   # 3개씩 뽑기
    print(combo)
# (1, 2, 3), (1, 2, 4), (1, 2, 5), ...</code></pre>
<p>Java로 비유하면 for문 3중첩으로 조합을 구하는 걸 한 줄로 해주는 것이다.</p>
<pre><code class="language-java">// Java: 3중 for문
for (int i = 0; i &lt; n; i++)
    for (int j = i+1; j &lt; n; j++)
        for (int k = j+1; k &lt; n; k++)</code></pre>
<pre><code class="language-python"># Python: 한 줄
for combo in combinations(arr, 3)</code></pre>
<p><strong>실수 포인트</strong>: <code>combinations(arr, K)</code>로 썼다가 교정했다. K=3이라 우연히 맞았지만, <strong>K는 배수 조건(변수)이고 뽑는 개수는 3(상수)</strong>이다. 문제에서 변수와 상수를 정확히 구분하자!</p>
<pre><code class="language-python">from itertools import combinations

def solution(arr, K):
    answer = 0
    for combo in combinations(arr, 3):    # 3개 뽑기 (상수!)
        if sum(combo) % K == 0:           # K의 배수 (변수!)
            answer += 1
    return answer</code></pre>
<h3 id="5-디버깅-else에서-조건부-처리-문제-10-0들을-0으로-만들기">5. 디버깅: else에서 조건부 처리 (문제 10: 0들을 0으로 만들기)</h3>
<p>연속된 0을 하나의 0으로 압축하는 디버깅 문제. 한 줄만 수정해야 한다.</p>
<pre><code class="language-python"># 버그 코드
if s[i] == &#39;0&#39; and s[i + 1] != &#39;0&#39;:
    answer += &#39;0&#39;       # 연속 0의 마지막 → 0 추가 (정상)
else:
    answer += &#39;1&#39;       # 버그! 무조건 &#39;1&#39; 추가</code></pre>
<p>else에 들어오는 경우가 세 가지 섞여있었다:</p>
<ul>
<li><code>s[i] == &#39;1&#39;</code> → <code>&#39;1&#39;</code> 추가해야 함</li>
<li><code>s[i] == &#39;0&#39;</code> (연속 0) → 아무것도 안 해야 함</li>
<li><code>s[i] == &#39;#&#39;</code> (끝 표시) → 아무것도 안 해야 함</li>
</ul>
<p><strong>수정</strong>: 11행을 <code>if s[i] == &#39;1&#39;: answer += &#39;1&#39;</code>로 변경</p>
<pre><code class="language-python">else:
    if s[i] == &#39;1&#39;: answer += &#39;1&#39;   # &#39;1&#39;일 때만 추가, 나머지는 자동 패스</code></pre>
<p>Python은 한 줄에 <code>if 조건: 동작</code>을 쓸 수 있어서 한 줄 수정으로 해결 가능하다.</p>
<p>수업 중 질문: &quot;else: if 대신 그냥 if만 쓰거나, elif를 써도 똑같지 않아?&quot;
→ 안 된다! 11행은 <code>else:</code> 블록 안에 있는 코드라서, <code>elif</code>로 바꾸려면 <code>else:</code>(10행)도 지워야 해서 <strong>두 줄 수정</strong>이 된다. 독립 <code>if</code>도 마찬가지.</p>
<p><strong>디버깅 팁</strong>: else에서 무조건 고정값을 넣는 코드가 보이면 의심하자!</p>
<h3 id="6---공백-실수-주의">6. <code>-=</code> 공백 실수 주의</h3>
<p>응용문제에서 발견한 실수:</p>
<pre><code class="language-python">hours -= 1   # hours = hours - 1  (3 → 2) ✅
hours = -1   # hours에 -1 대입     (3 → -1) ❌</code></pre>
<p><code>+=</code>는 헷갈릴 일이 없지만, <code>-=</code>는 공백 하나로 의미가 완전히 바뀐다. 시험에서는 반드시 <strong>붙여서</strong> 쓰자!</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>기출 2회차 5문제 + 응용 1문제 풀이, 1차 정답률 83% (5/6)</li>
<li>클래스 상속, itertools.combinations 새로 학습</li>
<li>다음 시간: 기출 2회차 5~9번 마무리 → 함수작성 응용문제</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260401 오늘의 학습: COS Pro 기출 1회차 풀이 (6~10번)]]></title>
            <link>https://velog.io/@lee_yesol421/20260401-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-COS-Pro-%EA%B8%B0%EC%B6%9C-1%ED%9A%8C%EC%B0%A8-%ED%92%80%EC%9D%B4-610%EB%B2%88</link>
            <guid>https://velog.io/@lee_yesol421/20260401-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-COS-Pro-%EA%B8%B0%EC%B6%9C-1%ED%9A%8C%EC%B0%A8-%ED%92%80%EC%9D%B4-610%EB%B2%88</guid>
            <pubDate>Wed, 01 Apr 2026 15:33:23 GMT</pubDate>
            <description><![CDATA[<h2 id="지난-학습-요약">지난 학습 요약</h2>
<ul>
<li>구름EDU 기출 1회차 1~5번 풀이 완료 (빈칸 3 + 함수 작성 2)</li>
<li>클래스 상속 문법, 인덱스 vs 값 혼동, 그리드 시뮬레이션 초기값 누락 등 실수 포인트 정리</li>
<li>Phase 2 본격 시작</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>구름EDU COS Pro 1급 기출 1회차 6~10번 문제 풀이 리뷰.
빈칸 채우기 1문제 + 디버깅 2문제 + 함수 작성 2문제 구성.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="문제-7--병합-and-정렬-빈칸-채우기">문제 7 — 병합 and 정렬 (빈칸 채우기)</h3>
<p>오름차순으로 정렬된 두 배열을 하나의 정렬된 배열로 합치는 문제. 병합 정렬(merge sort)의 merge 단계와 같은 로직.</p>
<p><strong>빈칸:</strong></p>
<pre><code class="language-python">while arrA_len &gt; arrA_idx and arrB_len &gt; arrB_idx:  # 빈칸 1: 두 배열 모두 남아있을 때
    ...비교하며 작은 값 append...

while arrA_len &gt; arrA_idx:   # 빈칸 2: arrA에 남은 원소 처리
    ...

while arrB_len &gt; arrB_idx:   # 빈칸 3: arrB에 남은 원소 처리
    ...</code></pre>
<p><strong>수업 중 질문: <code>&amp;</code>랑 <code>and</code>는 다른 거야?</strong></p>
<p>→ Python에서 <code>&amp;</code>는 <strong>비트 연산자</strong>, <code>and</code>는 <strong>논리 연산자</strong>다.</p>
<table>
<thead>
<tr>
<th></th>
<th><code>&amp;</code> (비트)</th>
<th><code>and</code> (논리)</th>
</tr>
</thead>
<tbody><tr>
<td>용도</td>
<td>비트 단위 AND</td>
<td>조건 연결</td>
</tr>
<tr>
<td>예시</td>
<td><code>5 &amp; 3</code> → <code>1</code></td>
<td><code>True and False</code> → <code>False</code></td>
</tr>
<tr>
<td>주의</td>
<td>연산자 우선순위가 비교 연산보다 높음</td>
<td>조건문에서는 항상 <code>and</code> 사용</td>
</tr>
</tbody></table>
<p>COS Pro 시험에서 조건 연결할 때는 반드시 <code>and</code>를 쓸 것!</p>
<hr>
<h3 id="문제-8--누가-당선-되나요-디버깅">문제 8 — 누가 당선 되나요 (디버깅)</h3>
<p>투표 결과에서 가장 많은 표를 받은 후보 번호를 반환하는 문제. 공동 1위면 번호를 오름차순으로 모두 반환.</p>
<p><strong>버그 위치: 14번 줄</strong></p>
<pre><code class="language-python"># 수정 전
answer.append(vote_counter[idx])  # 득표수를 넣고 있음

# 수정 후
answer.append(idx)                # 후보 번호를 넣어야 함</code></pre>
<p><strong>핵심:</strong> 문제가 요구하는 반환값이 무엇인지 정확히 파악하기. <code>vote_counter[idx]</code>는 idx번 후보의 <strong>득표수</strong>이고, 문제가 원하는 건 <strong>후보 번호</strong> <code>idx</code> 자체다.</p>
<hr>
<h3 id="문제-9--계단-게임-디버깅">문제 9 — 계단 게임 (디버깅)</h3>
<p>두 학생이 가위바위보를 하면서 계단을 오르내리는 게임. A가 이기면 +3칸, 지면 -1칸, 비기면 제자리. 단, 0칸 아래로는 내려갈 수 없음.</p>
<p><strong><code>func</code> 함수 동작:</strong></p>
<pre><code class="language-python">def func(record):   # 이기는 패를 반환
    0(가위) → 1(바위)
    1(바위) → 2(보)
    2(보)   → 0(가위)</code></pre>
<p><strong>버그 위치: 19번 줄</strong></p>
<pre><code class="language-python"># 수정 전
cnt = cnt - 1              # 0 아래로 내려갈 수 있음

# 수정 후
cnt = max(0, cnt - 1)      # 0 아래로 못 내려가게 보호</code></pre>
<p><strong>기억 포인트:</strong> <code>max(0, 값)</code>은 &quot;0 아래로 내려가지 않게&quot; 할 때 자주 쓰는 패턴. 시험에서 디버깅은 한 줄 교체이므로, if문을 추가하는 것보다 <code>max()</code> 한 줄로 처리하는 게 안전하다.</p>
<hr>
<h3 id="문제-10--주식으로-최대-수익을-내세요-디버깅">문제 10 — 주식으로 최대 수익을 내세요 (디버깅)</h3>
<p>n일 동안의 주가에서 한 번 사고 한 번 팔았을 때 최대 수익을 구하는 문제. 산 날에 바로 못 팔고, 최소 하루 뒤에 팔아야 한다. 수익이 음수일 수도 있다.</p>
<p><strong>버그 위치: 10번 줄</strong></p>
<pre><code class="language-python"># 수정 전
answer = max(answer, tmp - price)    # 매수가 - 판매가 (반대!)

# 수정 후
answer = max(answer, price - tmp)    # 판매가 - 매수가 (정답!)</code></pre>
<p><strong>코드 구조 이해:</strong></p>
<pre><code class="language-python">for price in prices:
    if tmp != INF:                      # ① 먼저 팔아보기 (매수한 적 있을 때만)
        answer = max(answer, price - tmp)
    tmp = min(tmp, price)               # ② 그 다음 사볼지 결정</code></pre>
<p><strong>수업 중 질문: <code>if tmp != INF:</code> 이 조건은 왜 있는 거야?</strong></p>
<p>→ 이 조건은 <strong>&quot;산 날에 바로 못 팔고, 최소 하루 뒤에 팔아야 한다&quot;</strong> 규칙을 지키기 위한 것이다.</p>
<ul>
<li>첫 번째 날: <code>tmp=INF</code> → if 건너뜀 → tmp만 세팅 (산 것)</li>
<li>두 번째 날부터: tmp에 이전 최소가가 있으므로 → 수익 계산 가능 (팔기)</li>
</ul>
<p>&quot;<strong>먼저 팔아보고(answer 갱신) → 그 다음 살지 결정(tmp 갱신)</strong>&quot; 이 순서가 하루 뒤 매도 규칙을 보장한다. 순서를 바꾸면 같은 날 사고파는 셈이 되어 <code>[3, 1]</code> 같은 케이스에서 수익이 0이 나오는 버그가 생긴다.</p>
<hr>
<h3 id="문제-6--체스의-나이트-함수-작성">문제 6 — 체스의 나이트 (함수 작성)</h3>
<p>체스판 위 나이트의 위치가 주어지면, 한 번에 이동할 수 있는 칸의 개수를 반환하는 문제.</p>
<p><strong>작성한 코드:</strong></p>
<pre><code class="language-python">def solution(pos):
    answer = 0
    r = &quot;ABCDEFGH&quot;.find(pos[0])   # A=0, B=1, ... H=7
    c = int(pos[1]) - 1            # 1~8 → 0~7로 통일

    moves = [(-2, -1), (-2, 1), (-1, -2), (-1, 2),
             (1, -2), (1, 2), (2, -1), (2, 1)]

    for move in moves:
        nr, nc = r + move[0], c + move[1]
        if nr &gt;= 0 and nr &lt;= 7 and nc &gt;= 0 and nc &lt;= 7:
            answer += 1

    return answer</code></pre>
<p><strong>풀면서 실수한 포인트:</strong></p>
<table>
<thead>
<tr>
<th>실수</th>
<th>원인</th>
<th>수정</th>
</tr>
</thead>
<tbody><tr>
<td>범위 체크 <code>0~8</code></td>
<td>r(0<del>7)과 c(1</del>8)의 범위가 달랐음</td>
<td>c를 <code>-1</code>해서 둘 다 0~7로 통일</td>
</tr>
</tbody></table>
<p><strong>위치 파싱 두 가지 방법:</strong></p>
<pre><code class="language-python"># 방법 1: find()
r = &quot;ABCDEFGH&quot;.find(pos[0])

# 방법 2: ord()
r = ord(pos[0]) - ord(&#39;A&#39;)</code></pre>
<p><code>ord()</code>는 문자를 아스키 코드(숫자)로 변환하는 함수. <code>chr()</code>는 반대. Java의 <code>(char) - &#39;A&#39;</code> 패턴과 동일하다.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>기출 1회차 6~10번 완료 (빈칸 1 + 디버깅 3 + 함수 작성 1), <strong>기출 1회차 전체 10문제 완료</strong></li>
<li>주요 포인트: <code>and</code> vs <code>&amp;</code> 차이, <code>max(0, x)</code> 패턴, 주식 문제의 매수/매도 순서 보장 로직</li>
<li>다음 시간: 구름EDU 기출 2회차 시작</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260330 오늘의 학습: COS Pro 기출 1회차 풀이 (1~5번)]]></title>
            <link>https://velog.io/@lee_yesol421/20260330-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-COS-Pro-%EA%B8%B0%EC%B6%9C-1%ED%9A%8C%EC%B0%A8-%ED%92%80%EC%9D%B4-15%EB%B2%88</link>
            <guid>https://velog.io/@lee_yesol421/20260330-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-COS-Pro-%EA%B8%B0%EC%B6%9C-1%ED%9A%8C%EC%B0%A8-%ED%92%80%EC%9D%B4-15%EB%B2%88</guid>
            <pubDate>Mon, 30 Mar 2026 13:06:41 GMT</pubDate>
            <description><![CDATA[<h2 id="어제-학습-요약">어제 학습 요약</h2>
<ul>
<li>Phase 1 종합 복습 완료 — 딕셔너리+컴프리헨션, set 교집합, 다중 정렬 key 등 마무리</li>
<li>Phase 1 전 범위 종료, Phase 2 전환 준비 완료</li>
<li>총 8회 세션, 67문제 풀이 (1차 정답률 84%)</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>Phase 2 첫 번째 세션. 구름EDU COS Pro 1급 기출 1회차 1~5번 문제를 직접 풀고 리뷰.
빈칸 채우기 3문제 + 함수 작성 2문제 구성.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="문제-1--음식전문점-운영-빈칸-채우기">문제 1 — 음식전문점 운영 (빈칸 채우기)</h3>
<p>클래스 상속 구조를 읽고 빈칸을 채우는 문제.</p>
<p><strong>빈칸:</strong></p>
<pre><code class="language-python">class PizzaStore(DeliveryStore):       # 1. 상속
    def set_order_list(self, order_list):  # 2. 추상 메서드 구현
    def get_total_price(self):             # 3. 추상 메서드 구현</code></pre>
<p><strong>Python 클래스 상속 문법 정리:</strong></p>
<table>
<thead>
<tr>
<th></th>
<th>Java</th>
<th>Python</th>
</tr>
</thead>
<tbody><tr>
<td>상속</td>
<td><code>class Child extends Parent</code></td>
<td><code>class Child(Parent):</code></td>
</tr>
<tr>
<td>인터페이스 구현</td>
<td><code>class A implements B</code></td>
<td><code>class A(B):</code> (동일 문법)</td>
</tr>
<tr>
<td>추상 메서드</td>
<td><code>@Override</code></td>
<td>같은 이름으로 그냥 정의 (시그니처 맞춰야 함)</td>
</tr>
<tr>
<td>추상 클래스 선언</td>
<td><code>abstract class</code></td>
<td><code>metaclass=ABCMeta</code> + <code>@abstractmethod</code></td>
</tr>
</tbody></table>
<p><strong>핵심:</strong> Python에서 추상 메서드를 구현할 때는 <code>self</code>를 포함한 시그니처를 부모와 동일하게 맞춰야 한다.</p>
<p>이 문제에서 함수명은 바로 알았지만 상속 문법은 인터넷 검색 후 해결. → 별도 정리 필요 영역으로 기록.</p>
<hr>
<h3 id="문제-2--해밍-거리-구하기-빈칸-채우기">문제 2 — 해밍 거리 구하기 (빈칸 채우기)</h3>
<p>두 이진수 문자열의 해밍 거리(같은 위치에서 다른 문자 수)를 구하는 문제.
길이가 다르면 짧은 쪽 앞에 0을 채워 맞춤.</p>
<p><strong>빈칸:</strong></p>
<pre><code class="language-python">padSize = length - len(string)    # 채워야 할 0의 개수
if binaryA[i] != binaryB[i]:     # 같은 위치에서 다르면 해밍 거리 +1</code></pre>
<p>막힘 없이 해결.</p>
<hr>
<h3 id="문제-3--계산기-by-문자열-빈칸-채우기">문제 3 — 계산기 by 문자열 (빈칸 채우기)</h3>
<p><code>&quot;123+12&quot;</code> 같은 문자열 수식을 받아 계산 결과를 return하는 문제.
<code>func_a</code> (계산), <code>func_b</code> (연산자 위치 탐색), <code>func_c</code> (숫자 분리) 세 함수가 주어지고, <code>solution</code>의 빈칸을 채우는 구조.</p>
<p><strong>빈칸:</strong></p>
<pre><code class="language-python">exp_index = func_b(expression)                          # 연산자 위치(인덱스)
first_num, second_num = func_c(expression, exp_index)   # 두 숫자 분리
result = func_a(first_num, second_num, expression[exp_index])  # 계산</code></pre>
<p><strong>수업 중 질문: 마지막 인자를 <code>exp_index</code>로 넣었더니 <code>None</code>이 출력됐어</strong></p>
<p>→ <code>exp_index</code>는 연산자의 위치를 나타내는 <strong>숫자(int)</strong> 이고, <code>func_a</code>가 필요로 하는 건 <code>&#39;+&#39;</code>, <code>&#39;-&#39;</code>, <code>&#39;*&#39;</code> 같은 <strong>문자(str)</strong> 다.
<code>expression[exp_index]</code>로 인덱스 위치의 값을 꺼내야 한다.</p>
<pre><code class="language-python">exp_index = 3           # 숫자 — 위치
expression[exp_index]   # 문자 — 그 위치의 값 &#39;+&#39;</code></pre>
<p><strong>기억 포인트:</strong> 인덱스를 구했으면 값을 꺼낼 때 <code>[인덱스]</code>를 한 번 더 써줘야 한다.</p>
<hr>
<h3 id="문제-4--타임머신-함수-작성">문제 4 — 타임머신 (함수 작성)</h3>
<p>0이 없는 수 체계(1, 2, ..., 9, 11, 12, ...)에서 주어진 수의 다음 수를 return하는 문제.</p>
<p><strong>작성한 코드:</strong></p>
<pre><code class="language-python">def solution(num):
    tmp_num = str(num + 1)
    answer = int(tmp_num.replace(&quot;0&quot;, &quot;1&quot;))
    return answer</code></pre>
<p><strong>왜 이게 맞는가?</strong></p>
<p><code>num</code>은 0이 없는 수이므로, <code>num + 1</code>을 하면 올림(carry)이 9를 타고 올라가면서 <strong>0은 반드시 뒤에서부터(suffix)만</strong> 생긴다.</p>
<pre><code>예: 1899999 + 1 = 1900000
    └─ 뒤의 9들이 연쇄 올림 → suffix에만 0 생성</code></pre><p>suffix의 0을 전부 1로 바꾸면 → 0 없는 수 중 가장 작은 다음 수가 된다.
<code>replace(&quot;0&quot;, &quot;1&quot;)</code>이 항상 정답인 이유가 바로 이 입력 조건 덕분이다.</p>
<hr>
<h3 id="문제-5--소용돌이-수-함수-작성">문제 5 — 소용돌이 수 (함수 작성)</h3>
<p>n×n 격자에 1~n²을 소용돌이(나선) 순서로 채운 뒤, <strong>주 대각선(좌상→우하)</strong> 값의 합을 return하는 문제.</p>
<p><strong>작성한 코드 (핵심 구조):</strong></p>
<pre><code class="language-python">def solution(n):
    if n == 1: return 1  # 엣지케이스

    grid = [[0]*n for _ in range(n)]
    dr = [0, 1, 0, -1]   # 우 하 좌 상
    dc = [1, 0, -1, 0]
    r, c, d = 0, 0, 0

    answer = 0
    for num in range(1, n*n+1):
        grid[r][c] = num
        if r == c:
            answer += num

        nr, nc = r + dr[d], c + dc[d]
        if nr &lt; 0 or nr &gt;= n or nc &lt; 0 or nc &gt;= n:
            d = (d+1) % 4
            nr, nc = r + dr[d], c + dc[d]
        if grid[nr][nc] != 0:
            d = (d+1) % 4
            nr, nc = r + dr[d], c + dc[d]
        r, c = nr, nc

    return answer</code></pre>
<p><strong>풀면서 실수한 포인트:</strong></p>
<table>
<thead>
<tr>
<th>실수</th>
<th>원인</th>
<th>수정</th>
</tr>
</thead>
<tbody><tr>
<td><code>d = 0</code> 누락</td>
<td>방향 초기값 빠트림</td>
<td>그리드+4방향+시작점+<strong>현재방향</strong> 세트로 기억</td>
</tr>
<tr>
<td><code>range(n*n)</code></td>
<td>0부터 시작해 합계 틀림</td>
<td><code>range(1, n*n+1)</code></td>
</tr>
<tr>
<td><code>(d+1) % 4</code> 헷갈림</td>
<td>방향 순환 공식</td>
<td>0→1→2→3→0 반복, 항상 <code>% 4</code></td>
</tr>
</tbody></table>
<p><strong>대각선 조건:</strong> <code>r == c</code> 이면 주 대각선. 단순하고 정확한 판별식.</p>
<p><strong>그리드 시뮬레이션 세팅 체크리스트:</strong></p>
<ol>
<li>그리드 생성</li>
<li>4방향 정의 (dr, dc)</li>
<li>시작점 (r, c)</li>
<li><strong>현재 방향 (d = 0)</strong>  ← 자주 빠트림!</li>
</ol>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>기출 1회차 1~5번 완료 (빈칸 3 + 함수 작성 2), 전부 최종 정답</li>
<li>1차 실수: 문제 3 인덱스vs값 혼동, 문제 5 초기값 누락</li>
<li>다음 시간: 기출 1회차 6~10번 + 클래스 상속/오버라이드 보충 정리</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260327 오늘의 학습: Phase 1 종합 복습]]></title>
            <link>https://velog.io/@lee_yesol421/20260327-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-Phase-1-%EC%A2%85%ED%95%A9-%EB%B3%B5%EC%8A%B5</link>
            <guid>https://velog.io/@lee_yesol421/20260327-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-Phase-1-%EC%A2%85%ED%95%A9-%EB%B3%B5%EC%8A%B5</guid>
            <pubDate>Fri, 27 Mar 2026 17:43:28 GMT</pubDate>
            <description><![CDATA[<h2 id="어제-학습-요약">어제 학습 요약</h2>
<ul>
<li>Phase 1 종합 복습 테스트 10문제 풀이 (1차 정답률 60%)</li>
<li>dict+key 패턴, 다중 정렬, 달팽이 수열 등 약점 확인</li>
<li>Phase 2 전환 전 보충 학습 필요로 판단</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>짧은 세션으로 Phase 1 마무리 복습. 그동안 배운 딕셔너리, set, 정렬, 함수 작성을 골고루 점검하고, 주말부터 Phase 2 전환을 준비한다.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="문제-1-빈칸--딕셔너리--리스트-컴프리헨션-필터링">문제 1 (빈칸) — 딕셔너리 + 리스트 컴프리헨션 필터링</h3>
<p>학생 점수 딕셔너리에서 평균 이상인 학생 이름만 리스트로 반환하는 문제.</p>
<pre><code class="language-python">def above_average(scores):
    avg = sum(scores.values()) / len(scores)
    result = [name for name, score in scores.items() if score &gt;= avg]
    return result</code></pre>
<p><code>.items()</code>로 <code>name, score</code> 언패킹 → 조건에 맞는 <code>name</code>만 리스트에 담는 패턴. 딕셔너리 컴프리헨션과 조건 필터링의 조합 문제.</p>
<p>✅ 1차 정답</p>
<h3 id="문제-2-디버깅--set-합집합교집합-연산">문제 2 (디버깅) — set 합집합/교집합 연산</h3>
<p>여러 리스트에서 <strong>공통 원소</strong>만 반환해야 하는데, <code>|</code>(합집합)을 쓰고 있어서 모든 원소가 다 나오는 버그.</p>
<pre><code class="language-python"># 버그
result = result | set(lst)    # 합집합 → 하나라도 있으면 포함

# 수정
result = result &amp; set(lst)    # 교집합 → 모든 리스트에 있어야 포함</code></pre>
<table>
<thead>
<tr>
<th>연산자</th>
<th>메서드</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>|</code></td>
<td><code>.union()</code></td>
<td>합집합</td>
</tr>
<tr>
<td><code>&amp;</code></td>
<td><code>.intersection()</code></td>
<td>교집합</td>
</tr>
<tr>
<td><code>-</code></td>
<td><code>.difference()</code></td>
<td>차집합</td>
</tr>
<tr>
<td><code>^</code></td>
<td><code>.symmetric_difference()</code></td>
<td>대칭 차집합</td>
</tr>
</tbody></table>
<p>✅ 1차 정답 — 원인까지 정확히 설명</p>
<h3 id="문제-3-함수-작성--다중-조건-정렬">문제 3 (함수 작성) — 다중 조건 정렬</h3>
<p>문자열 리스트를 <strong>길이 내림차순 + 같으면 사전순 오름차순</strong>으로 정렬하는 함수.</p>
<pre><code class="language-python">def sort_by_length(words):
    return sorted(words, key=lambda x: (-len(x), x))</code></pre>
<p>9차 종합 테스트에서 배운 <strong>다중 정렬 key 튜플</strong> 패턴을 완벽하게 활용. <code>-len(x)</code>로 내림차순, <code>x</code>로 오름차순을 하나의 lambda에서 처리.</p>
<p>✅ 1차 정답</p>
<h3 id="문제-4-빈칸--set-순서-유지-중복-제거">문제 4 (빈칸) — set 순서 유지 중복 제거</h3>
<p>리스트에서 중복을 제거하되 원래 순서를 유지하는 문제. 빈 <code>set()</code>으로 시작해서 <code>add()</code>로 이미 본 원소를 추적한다.</p>
<pre><code class="language-python">def remove_duplicates(lst):
    seen = set()
    result = []
    for item in lst:
        if item not in seen:
            seen.add(item)
            result.append(item)
    return result</code></pre>
<p>이전에 <code>set()</code> vs <code>set(items)</code> 초기값 혼동이 있었는데, 이번에는 빈 set으로 정확히 시작. set의 <code>in</code> 검사가 O(1)이라 효율적이다.</p>
<p>✅ 1차 정답</p>
<h3 id="문제-5-디버깅--초기값-설정-함정">문제 5 (디버깅) — 초기값 설정 함정</h3>
<p>딕셔너리에서 value가 가장 큰 key를 찾는 함수인데, <code>max_val = 0</code>으로 초기화해서 <strong>음수만 있는 경우</strong> 조건을 만족하는 값이 없어 <code>None</code>이 반환되는 버그.</p>
<pre><code class="language-python"># 버그
max_val = 0                        # 모든 value가 음수면 업데이트 안 됨

# 수정
max_val = list(d.values())[0]      # 실제 데이터의 첫 값으로 초기화</code></pre>
<p>다른 방법으로 <code>float(&#39;-inf&#39;)</code>(음의 무한대)도 가능. 어떤 숫자와 비교해도 항상 더 작기 때문에, 첫 번째 비교에서 무조건 업데이트가 된다.</p>
<pre><code class="language-python">float(&#39;-inf&#39;) &lt; -99999999  # True — 어떤 수보다도 작음
max_val = float(&#39;-inf&#39;)    # 최대값 초기값으로 사용
min_val = float(&#39;inf&#39;)     # 반대로 최소값 찾을 때는 양의 무한대</code></pre>
<table>
<thead>
<tr>
<th>방법</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><code>float(&#39;-inf&#39;)</code></td>
<td>빈 딕셔너리여도 에러 안 남</td>
<td>외워야 함</td>
</tr>
<tr>
<td><code>list(d.values())[0]</code></td>
<td>직관적</td>
<td>빈 딕셔너리면 IndexError</td>
</tr>
</tbody></table>
<p>초기값을 0이나 빈 값으로 두면 <strong>음수/특수 케이스</strong>에서 실패할 수 있다는 점을 기억하자.</p>
<p>✅ 1차 정답</p>
<h3 id="문제-6-함수-작성--문자-빈도-카운팅--정렬">문제 6 (함수 작성) — 문자 빈도 카운팅 + 정렬</h3>
<p>문자열에서 각 문자의 등장 횟수를 <strong>횟수 내림차순</strong>으로 정렬된 딕셔너리로 반환하는 문제.</p>
<blockquote>
<p>수업 중 질문: &quot;Counter를 import해서 써도 되나?&quot;
→ COS Pro 시험에서 <code>collections</code> 모듈 사용 가능. 실전에서는 <code>Counter</code>를 쓰는 게 효율적.</p>
</blockquote>
<p><strong>풀이 1 — Counter 사용</strong>:</p>
<pre><code class="language-python">from collections import Counter

def char_count(s):
    tmp_dict = Counter(s)
    tmp_list = sorted(tmp_dict.items(), key=lambda x: -x[1])
    return dict(tmp_list)</code></pre>
<p><strong>풀이 2 — Counter 없이 dict.get() 활용</strong>:</p>
<pre><code class="language-python">def char_count(s):
    tmp_dict = {}
    for w in s:
        tmp_dict[w] = tmp_dict.get(w, 0) + 1
    return dict(sorted(tmp_dict.items(), key=lambda x: -x[1]))</code></pre>
<p>두 가지 방식 모두 작성할 수 있으면 실전에서 유연하게 대응 가능.</p>
<p>✅ 1차 정답 — 두 가지 풀이 모두 자발적으로 작성</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>6문제 전부 1차 정답, 정답률 <strong>100%</strong> — Phase 1 기초 완전히 정착</li>
<li>이전 약점이었던 set 초기값, 함수 작성 모두 안정적으로 해결</li>
<li>다음 학습(주말): <strong>Phase 2 전환</strong> — 구름EDU COS Pro 기출 1회차 시작 예정</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260326 오늘의 학습: Phase 1 종합 복습 테스트]]></title>
            <link>https://velog.io/@lee_yesol421/20260326-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-Phase-1-%EC%A2%85%ED%95%A9-%EB%B3%B5%EC%8A%B5-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@lee_yesol421/20260326-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-Phase-1-%EC%A2%85%ED%95%A9-%EB%B3%B5%EC%8A%B5-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Thu, 26 Mar 2026 16:33:27 GMT</pubDate>
            <description><![CDATA[<h2 id="어제-학습-요약">어제 학습 요약</h2>
<ul>
<li>예외 처리(try/except/else/finally) 학습, Java의 try/catch와 비교</li>
<li>10문제 풀이, 1차 정답률 70%</li>
<li>Phase 1 마지막 주제까지 완료</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>Phase 1 전 영역 종합 복습 테스트. 리스트, 딕셔너리, 문자열, 집합, 내장함수, 튜플, 함수, 예외 처리까지 골고루 10문제를 풀어 Phase 2 전환 여부를 판단한다.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="문제-1-빈칸--2차원-배열-대각선-합">문제 1 (빈칸) — 2차원 배열 대각선 합</h3>
<p>n×n 행렬의 주대각선 합을 구하는 문제. 대각선 원소는 행과 열 인덱스가 같으므로 <code>matrix[i][i]</code>로 접근한다.</p>
<pre><code class="language-python">total += matrix[i][i]  # (0,0), (1,1), (2,2) → 주대각선</code></pre>
<p>✅ 1차 정답</p>
<h3 id="문제-2-빈칸--딕셔너리-최빈-문자">문제 2 (빈칸) — 딕셔너리 최빈 문자</h3>
<p>문자열에서 가장 많이 등장한 문자를 반환하는 문제.</p>
<p><strong>1차 시도</strong>: <code>sorted(count)[0]</code> → 오답. <code>sorted(dict)</code>는 키를 알파벳순으로 정렬할 뿐, 등장 횟수와 무관하다.</p>
<p><strong>정답</strong>: <code>max(count, key=count.get)</code></p>
<pre><code class="language-python">max(count, key=count.get)   # 값(등장 횟수)이 가장 큰 키를 반환
min(count, key=count.get)   # 값이 가장 작은 키를 반환</code></pre>
<blockquote>
<p>수업 중 질문: &quot;key=count.get할 때 get()에 키 값이 없어도 되나?&quot;
→ <code>count.get</code>은 호출(<code>count.get(&#39;a&#39;)</code>)이 아니라 <strong>함수 자체를 넘기는 것</strong>. <code>max</code>가 내부적으로 각 키를 넣어서 호출해주기 때문에 우리가 키를 지정할 필요가 없다. Java의 메서드 레퍼런스(<code>count::get</code>)와 같은 개념.</p>
</blockquote>
<h3 id="문제-3-빈칸--문자열-capitalize--join">문제 3 (빈칸) — 문자열 capitalize + join</h3>
<p>각 단어의 첫 글자를 대문자로 바꾸는 문제. <code>capitalize()</code> 메서드를 써봤더니 동작했다.</p>
<pre><code class="language-python">result.append(w.capitalize())   # 첫 글자 대문자 + 나머지 소문자
return &quot; &quot;.join(result)</code></pre>
<table>
<thead>
<tr>
<th>방법</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td><code>w.capitalize()</code></td>
<td>첫 글자 대문자 + 나머지 소문자</td>
</tr>
<tr>
<td><code>w[0].upper() + w[1:]</code></td>
<td>첫 글자만 대문자 + 나머지 그대로</td>
</tr>
<tr>
<td><code>w.title()</code></td>
<td>각 단어 첫 글자 대문자 (문자열 전체에 적용)</td>
</tr>
</tbody></table>
<p><code>capitalize()</code>를 몰라도 <code>w[0].upper() + w[1:]</code>로 대체 가능.</p>
<p>✅ 1차 정답</p>
<h3 id="문제-4-빈칸--set-교집합--순서-유지">문제 4 (빈칸) — set 교집합 + 순서 유지</h3>
<p>두 리스트의 공통 원소를 원래 순서 유지하며 반환하는 문제.</p>
<p><strong>1차 시도</strong>: 리스트 컴프리헨션으로 교집합 리스트를 만들어 비교 → 동작은 하지만 set을 활용하는 문제 의도와 다름.</p>
<p><strong>정답</strong>: <code>list2</code>를 set으로 변환 후 <code>in</code> 검사.</p>
<pre><code class="language-python">common = set(list2)
return [x for x in list1 if x in common]</code></pre>
<p>set을 쓰면 <code>in</code> 검색이 O(1)으로 빠르다.</p>
<h3 id="문제-5-빈칸--다중-정렬-키">문제 5 (빈칸) — 다중 정렬 키</h3>
<p>점수 내림차순 + 이름 오름차순으로 정렬하는 문제. <strong>정렬 기준이 여러 개일 때 튜플로 묶어서 반환</strong>한다는 걸 새로 배웠다.</p>
<pre><code class="language-python">sorted(students, key=lambda x: (-x[1], x[0]))</code></pre>
<p><code>-</code>를 붙이면 크기 관계가 뒤집혀서 내림차순이 된다. 단, <strong>숫자에만 가능</strong>.</p>
<blockquote>
<p>수업 중 질문: &quot;조건 2개 이상이라 튜플로 줘야 될 때 reverse=True는 어디에?&quot;
→ <code>reverse=True</code>는 <code>sorted()</code>의 매개변수로 넣지만 <strong>전체 기준이 다 뒤집히므로</strong>, 방향이 섞여 있으면 사용 불가.</p>
</blockquote>
<table>
<thead>
<tr>
<th>상황</th>
<th>방법</th>
</tr>
</thead>
<tbody><tr>
<td>기준이 같은 방향</td>
<td><code>key=(기준1, 기준2)</code> + <code>reverse=True/False</code></td>
</tr>
<tr>
<td>숫자가 다른 방향</td>
<td><code>-</code> 트릭: <code>(-x[1], x[0])</code></td>
</tr>
<tr>
<td>문자열이 다른 방향</td>
<td><code>sorted</code> 두 번 (덜 중요한 기준 먼저)</td>
</tr>
</tbody></table>
<p><code>sorted</code> 두 번 쓰는 방법은 Python의 <strong>안정 정렬(stable sort)</strong> 을 이용. 같은 값이면 이전 순서를 유지하므로, 덜 중요한 기준을 먼저 정렬하고 더 중요한 기준을 나중에 정렬하면 된다.</p>
<h3 id="문제-6-디버깅--nonlocal-클로저">문제 6 (디버깅) — nonlocal 클로저</h3>
<p>바깥 함수의 변수를 내부 함수에서 수정하려면 <code>nonlocal</code> 선언이 필요하다.</p>
<pre><code class="language-python">def increment(n):
    nonlocal count      # 함수 시작 부분에 선언 (관례)
    for i in range(n):
        count += 1</code></pre>
<p>✅ 1차 정답</p>
<h3 id="문제-7-디버깅--except에서-continue">문제 7 (디버깅) — except에서 continue</h3>
<p>변환 실패한 값을 &quot;건너뛰는&quot; 함수인데, <code>except</code>에서 실패한 값을 리스트에 추가하고 있었다. <code>result.append(s)</code> → <code>continue</code>로 수정.</p>
<blockquote>
<p>수업 중 질문: &quot;int(4.5)는 4가 되는데 int(&#39;4.5&#39;)는 ValueError가 뜨는 이유?&quot;
→ <code>int()</code>는 입력 타입에 따라 동작이 다르다. float → 소수점 버림, 문자열 → 정수 형태만 파싱 가능. <code>&quot;4.5&quot;</code>를 정수로 바꾸려면 <code>int(float(&quot;4.5&quot;))</code>로 두 단계 거쳐야 한다.</p>
</blockquote>
<p>✅ 1차 정답</p>
<h3 id="문제-8-함수-작성--투표-집계">문제 8 (함수 작성) — 투표 집계</h3>
<pre><code class="language-python">def get_winner(votes):
    result = {}
    for vote in votes:
        result[vote] = result.get(vote, 0) + 1
    return max(result, key=result.get)</code></pre>
<p>문제 2에서 배운 <code>max(dict, key=dict.get)</code> 패턴을 바로 활용했다. 개선점으로 <code>if/else</code> 분기 없이 <code>get(vote, 0)</code>만으로 충분하고, <code>sorted()[0]</code> 대신 <code>max()</code>가 더 효율적이라는 피드백을 받았다. <code>sorted()</code>는 전체를 정렬(O(n log n))하지만 <code>max()</code>는 한 번 순회(O(n))만 하면 되기 때문.</p>
<p>✅ 1차 정답</p>
<h3 id="문제-9-함수-작성--애너그램-판별">문제 9 (함수 작성) — 애너그램 판별</h3>
<p><strong>사고 과정</strong>: palindrome과 헷갈림 → set 교집합은 중복 제거돼서 안됨 → 값을 빼가면서 체크? → 힌트 후 sorted 비교로 해결.</p>
<pre><code class="language-python">def is_anagram(s1, s2):
    return sorted(s1) == sorted(s2)</code></pre>
<p>정렬하면 글자 구성이 같은지 바로 비교 가능. <code>Counter(s1) == Counter(s2)</code>도 유효.</p>
<blockquote>
<p>수업 중 질문: &quot;Counter가 뭔지 찾아봤는데 딕셔너리끼리도 == 비교가 가능한가?&quot;
→ 가능. 키와 값이 모두 같으면 True, <strong>순서와 무관</strong>. Java의 <code>map1.equals(map2)</code>와 동일.</p>
</blockquote>
<h3 id="문제-10-함수-작성--달팽이-수열-spiral-matrix">문제 10 (함수 작성) — 달팽이 수열 (Spiral Matrix)</h3>
<p>가장 난이도가 높았던 문제. 힌트를 받아 <strong>방향 배열 + 방향 전환</strong> 구조로 직접 구현했다.</p>
<pre><code class="language-python">def make_spiral(n):
    grid = [[0] * n for _ in range(n)]
    dr = [0, 1, 0, -1]       # 오른쪽 → 아래 → 왼쪽 → 위
    dc = [1, 0, -1, 0]
    r, c = 0, 0
    d = 0

    for num in range(1, n * n + 1):
        grid[r][c] = num
        nr, nc = r + dr[d], c + dc[d]

        if nr &lt; 0 or nr &gt;= n or nc &lt; 0 or nc &gt;= n or grid[nr][nc] != 0:
            d = (d + 1) % 4
            nr, nc = r + dr[d], c + dc[d]

        r, c = nr, nc
    return grid</code></pre>
<p><strong>디버깅 과정</strong>: n=3은 통과했는데 n=4에서 실패. 원인은 <code>if d == n</code>으로 방향 리셋을 했기 때문. n=3일 때 <code>d == 3</code>이 우연히 맞았지만, n=4일 때 <code>d == 4</code>는 절대 발생하지 않음. <code>(d + 1) % 4</code>로 수정하여 해결.</p>
<p><strong>자발적 추가 연습</strong>: 반시계 방향 + 역순(n×n → 1)도 직접 구현. 방향 배열만 바꾸고 range를 역순으로 변경하여 성공.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>Phase 1 종합 테스트 10문제 완료, 1차 정답률 60% (6/10), 최종 전부 정답</li>
<li>1차 정답률 80% 미달이므로 Phase 2 전환 전 약점 보충 필요 (dict+key 패턴, 다중 정렬, 알고리즘 구현)</li>
<li>다음 학습: 약점 영역 보충 연습 후 Phase 2 전환 예정</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260324 오늘의 학습: 예외 처리 (try/except)]]></title>
            <link>https://velog.io/@lee_yesol421/20260324-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-tryexcept</link>
            <guid>https://velog.io/@lee_yesol421/20260324-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-tryexcept</guid>
            <pubDate>Tue, 24 Mar 2026 12:02:02 GMT</pubDate>
            <description><![CDATA[<h2 id="어제-학습-요약">어제 학습 요약</h2>
<ul>
<li>튜플 활용(언패킹, sorted+lambda) + 함수 심화(<em>args, *</em>kwargs, 클로저)</li>
<li>10문제 풀이, 1차 정답률 70%</li>
<li>함수 작성에서 불필요한 dict 변환 시도 → 컴프리헨션으로 수정</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>Phase 1 마지막 주제인 예외 처리(try/except) 학습. Java의 try/catch와 비교하면서 Python만의 차이점을 익히고, 빈칸/디버깅/함수 작성 문제로 확인.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-java-trycatch-vs-python-tryexcept">1. Java try/catch vs Python try/except</h3>
<table>
<thead>
<tr>
<th>Java</th>
<th>Python</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>try { }</code></td>
<td><code>try:</code></td>
<td>예외가 발생할 수 있는 코드</td>
</tr>
<tr>
<td><code>catch (Exception e)</code></td>
<td><code>except Exception as e:</code></td>
<td>예외 잡기</td>
</tr>
<tr>
<td><code>finally { }</code></td>
<td><code>finally:</code></td>
<td>항상 실행</td>
</tr>
<tr>
<td><code>throw new Exception()</code></td>
<td><code>raise Exception()</code></td>
<td>예외 던지기</td>
</tr>
<tr>
<td><em>(없음)</em></td>
<td><code>else:</code></td>
<td>예외가 <strong>안 났을 때만</strong> 실행</td>
</tr>
</tbody></table>
<p>Java와 거의 동일한 구조인데, Python만의 <code>else</code> 블록이 추가된 것이 특징이다.</p>
<h3 id="2-tryexceptelsefinally-실행-순서">2. try/except/else/finally 실행 순서</h3>
<pre><code class="language-python">try:
    result = 10 / 2
except ZeroDivisionError:
    print(&quot;0으로 나눌 수 없음&quot;)
else:
    print(f&quot;결과: {result}&quot;)  # 예외 안 났을 때만 실행
finally:
    print(&quot;항상 실행&quot;)</code></pre>
<p><strong>핵심 실행 순서:</strong></p>
<ul>
<li>예외 발생 → <code>except</code> → <code>finally</code></li>
<li>예외 없음 → <code>else</code> → <code>finally</code></li>
<li><code>finally</code>는 <strong>무조건</strong> 실행 (return이 있어도!)</li>
</ul>
<h3 id="3-자주-나오는-예외-종류-cos-pro-빈출">3. 자주 나오는 예외 종류 (COS Pro 빈출)</h3>
<table>
<thead>
<tr>
<th>예외</th>
<th>발생 상황</th>
</tr>
</thead>
<tbody><tr>
<td><code>ZeroDivisionError</code></td>
<td>0으로 나눌 때</td>
</tr>
<tr>
<td><code>ValueError</code></td>
<td><code>int(&quot;abc&quot;)</code> 같은 잘못된 변환</td>
</tr>
<tr>
<td><code>IndexError</code></td>
<td>리스트 범위 초과</td>
</tr>
<tr>
<td><code>KeyError</code></td>
<td>딕셔너리에 없는 키</td>
</tr>
<tr>
<td><code>TypeError</code></td>
<td><code>&quot;3&quot; + 5</code> 같은 타입 불일치</td>
</tr>
</tbody></table>
<h3 id="4-valueerror-vs-typeerror--헷갈리기-쉬운-구분">4. ValueError vs TypeError — 헷갈리기 쉬운 구분</h3>
<p><code>int()</code> 함수를 예로 들면:</p>
<table>
<thead>
<tr>
<th>코드</th>
<th>에러</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td><code>int(&quot;abc&quot;)</code></td>
<td><code>ValueError</code></td>
<td>str→int 변환 지원하지만 <strong>값</strong>이 잘못됨</td>
</tr>
<tr>
<td><code>int(None)</code></td>
<td><code>TypeError</code></td>
<td>None은 int 변환 대상이 아닌 <strong>타입</strong></td>
</tr>
</tbody></table>
<blockquote>
<p>수업 중 질문: &quot;int(None)과 int(&quot;abc&quot;)의 차이를 잘 모르겠어. 왜 각각 다른 에러가 나는 거지?&quot;
→ 함수가 해당 타입을 받을 수 있느냐의 문제. <code>int()</code>는 문자열을 받을 수 있지만(변환 지원) 값이 이상하면 ValueError, 애초에 받을 수 없는 타입이면 TypeError. 이 구분은 <code>sum += &quot;없음&quot;</code>이 TypeError인 이유와도 연결된다 — int와 str의 <code>+</code> 연산 자체가 지원되지 않는 타입 문제이므로.</p>
</blockquote>
<h3 id="5-finally는-return보다-먼저-실행된다">5. finally는 return보다 먼저 실행된다</h3>
<pre><code class="language-python">def convert(value):
    try:
        num = int(value)
    except ValueError:
        return &quot;변환 실패&quot;
    else:
        return num * 2
    finally:
        print(&quot;처리 완료&quot;)

result = convert(&quot;abc&quot;)
print(result)</code></pre>
<p>출력:</p>
<pre><code>처리 완료
변환 실패</code></pre><p><code>except</code>에서 <code>return &quot;변환 실패&quot;</code>가 준비되지만, <strong>반환되기 전에 finally가 먼저 실행</strong>된다. 이건 Java도 동일한 동작이다.</p>
<h3 id="6-finally에-return이-있으면-다른-return을-덮어쓴다">6. finally에 return이 있으면 다른 return을 덮어쓴다</h3>
<pre><code class="language-python">def test():
    try:
        return 1
    except:
        return 2
    finally:
        return 3

print(test())  # 3</code></pre>
<p><code>try</code>에서 <code>return 1</code>을 준비했지만, <code>finally</code>의 <code>return 3</code>이 이를 완전히 덮어써버린다. 그래서 실무에서는 <strong>finally에 return을 넣지 않는 것</strong>이 원칙. 하지만 COS Pro에서 함정 문제로 나올 수 있으니 알아두자.</p>
<h3 id="7-다중-except-괄호-묶기">7. 다중 except 괄호 묶기</h3>
<pre><code class="language-python"># 개별 except
except KeyError:
    continue
except TypeError:
    continue

# 한 줄로 묶기
except (KeyError, TypeError):
    continue</code></pre>
<p>처리 로직이 같으면 괄호로 묶어서 한 번에 잡을 수 있다.</p>
<h3 id="8-tryexcept-활용--안전한-변환-패턴">8. try/except 활용 — 안전한 변환 패턴</h3>
<pre><code class="language-python">def safe_convert(items):
    result = []
    for item in items:
        try:
            num = int(item)
            result.append(num)
        except ValueError:
            continue
    return result

print(safe_convert([&quot;10&quot;, &quot;hello&quot;, &quot;20&quot;, &quot;3.5&quot;, &quot;30&quot;]))
# [10, 20, 30]</code></pre>
<p>변환이 보장되면 <code>[int(x) for x in items]</code> 한 줄이면 되지만, 예외 처리가 필요하면 try/except + for문이 정석이다. try는 컴프리헨션 안에 넣을 수 없기 때문.</p>
<h3 id="9-딕셔너리-안전-합산-패턴">9. 딕셔너리 안전 합산 패턴</h3>
<pre><code class="language-python">def safe_sum(dict_list, key):
    total = 0
    for d in dict_list:
        try:
            total += d[key]
        except (KeyError, TypeError):
            continue
    return total</code></pre>
<ul>
<li><code>KeyError</code>: 키가 없는 딕셔너리 건너뛰기</li>
<li><code>TypeError</code>: <code>int + str</code> 연산 불가능한 경우 건너뛰기</li>
</ul>
<blockquote>
<p>수업 중 질문: &quot;TypeError인지 ValueError인지 고민했는데, 형변환 과정이 없으니 TypeError가 맞나?&quot;
→ 정확하다. <code>int(&quot;없음&quot;)</code>처럼 형변환을 시도하면 ValueError, <code>0 + &quot;없음&quot;</code>처럼 형변환 없이 바로 더하면 int+str 연산이 되어 TypeError. 이 코드에서는 형변환 없이 바로 <code>+=</code>하니까 TypeError.</p>
</blockquote>
<h3 id="10-내장함수명을-변수명으로-쓰지-말-것">10. 내장함수명을 변수명으로 쓰지 말 것</h3>
<pre><code class="language-python"># 위험한 코드
sum = 0           # 내장함수 sum()을 덮어씀
dict = {&quot;a&quot;: 1}   # 내장타입 dict를 덮어씀

# 안전한 코드
total = 0
d = {&quot;a&quot;: 1}</code></pre>
<p>Python은 내장함수/타입도 변수처럼 재할당할 수 있어서, 실수로 덮어쓰면 이후 <code>sum()</code>, <code>dict()</code>를 사용할 수 없게 된다.</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>9문제 풀이, 1차 정답률 89% (8/9)</li>
<li>예외 처리 개념이 Java 경험에서 잘 전이됨, 함수 작성 2문제 모두 1차 정답</li>
<li>다음 시간: Phase 1 종합 복습 → Phase 2 전환</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20260323 오늘의 학습: 튜플 활용 + 함수 심화]]></title>
            <link>https://velog.io/@lee_yesol421/20260323-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%ED%8A%9C%ED%94%8C-%ED%99%9C%EC%9A%A9-%ED%95%A8%EC%88%98-%EC%8B%AC%ED%99%94</link>
            <guid>https://velog.io/@lee_yesol421/20260323-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%ED%8A%9C%ED%94%8C-%ED%99%9C%EC%9A%A9-%ED%95%A8%EC%88%98-%EC%8B%AC%ED%99%94</guid>
            <pubDate>Mon, 23 Mar 2026 19:06:47 GMT</pubDate>
            <description><![CDATA[<h2 id="어제-학습-요약">어제 학습 요약</h2>
<ul>
<li>Python 내장함수 정리 (sorted, enumerate, zip, map, filter, any/all, sum)</li>
<li>6문제 풀이, 1차 정답률 100%</li>
<li>함수 작성에서 하드코딩 없이 일반화 성공 (개선 추세)</li>
</ul>
<h2 id="오늘-수업-계획">오늘 수업 계획</h2>
<p>Phase 1 마무리를 향해 남은 두 주제를 진행: 튜플 활용법과 함수 심화(<em>args, *</em>kwargs, 클로저). 문제 10개로 확인.</p>
<hr>
<h2 id="학습-내용-정리">학습 내용 정리</h2>
<h3 id="1-튜플tuple--수정-못하는-리스트">1. 튜플(tuple) — 수정 못하는 리스트</h3>
<pre><code class="language-python"># 리스트 — 변경 가능 (mutable)
fruits = [&quot;사과&quot;, &quot;바나나&quot;, &quot;딸기&quot;]
fruits[0] = &quot;포도&quot;  # OK

# 튜플 — 변경 불가 (immutable)
colors = (&quot;빨강&quot;, &quot;파랑&quot;, &quot;초록&quot;)
colors[0] = &quot;노랑&quot;  # TypeError!</code></pre>
<p>Java에는 튜플이 없어서 생소할 수 있지만, Python에서는 엄청 자주 쓰인다. &quot;이 데이터는 바뀌면 안 돼&quot;라는 의도를 코드로 표현하는 것.</p>
<h3 id="2-튜플을-쓰는-주요-상황">2. 튜플을 쓰는 주요 상황</h3>
<table>
<thead>
<tr>
<th>상황</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>좌표처럼 묶인 데이터</td>
<td><code>pos = (3, 5)</code></td>
</tr>
<tr>
<td>함수에서 여러 값 반환</td>
<td><code>return min_val, max_val</code></td>
</tr>
<tr>
<td>딕셔너리 키로 사용</td>
<td><code>grid[(x, y)] = &quot;벽&quot;</code></td>
</tr>
<tr>
<td>언패킹</td>
<td><code>name, age = (&quot;홍길동&quot;, 25)</code></td>
</tr>
</tbody></table>
<p>특히 <strong>함수 반환값</strong>과 <strong>언패킹</strong>은 COS Pro에서 거의 매번 나온다.</p>
<h3 id="3-튜플-핵심-문법">3. 튜플 핵심 문법</h3>
<pre><code class="language-python"># 생성
t1 = (1, 2, 3)
t2 = 1, 2, 3        # 괄호 없어도 튜플!
t3 = (42,)           # 원소 1개일 때 반드시 콤마!
t4 = (42)            # 이건 그냥 int 42

# 인덱싱/슬라이싱 — 리스트와 동일
t1[0]      # 1
t1[1:]     # (2, 3)

# 스왑 (Java에서는 temp 변수 필요!)
a, b = b, a

# 함수에서 여러 값 반환
def min_max(nums):
    return min(nums), max(nums)

lo, hi = min_max([3, 1, 7, 2])  # lo=1, hi=7</code></pre>
<h3 id="4-튜플-리스트--sorted--lambda--cos-pro-단골-패턴">4. 튜플 리스트 + sorted + lambda — COS Pro 단골 패턴</h3>
<pre><code class="language-python">students = [(&quot;김철수&quot;, 85), (&quot;이영희&quot;, 92), (&quot;박민수&quot;, 78), (&quot;정수진&quot;, 95)]

# 점수 기준 내림차순 정렬
students_sorted = sorted(students, key=lambda x: x[1], reverse=True)

# 1등 언패킹
top_name, top_score = students_sorted[0]</code></pre>
<p>이 조합은 시험에서 정말 자주 나오니까 익숙해져야 한다.</p>
<h3 id="5-python-동시-할당--temp-변수-없이-스왑">5. Python 동시 할당 — temp 변수 없이 스왑</h3>
<p>디버깅 문제에서 나온 포인트. 첫 번째와 마지막 요소를 교환할 때:</p>
<pre><code class="language-python"># Java 스타일 — temp 필요
first = data[0]
data[0] = data[-1]
data[-1] = first

# Python — 동시 할당이라 한 줄이면 끝
data[0], data[-1] = data[-1], data[0]</code></pre>
<p>Python은 우변을 먼저 전부 평가한 뒤 좌변에 할당하기 때문에 중간 변수가 필요 없다. 디버깅 문제에서 <code>data[0], data[-1] = first, last</code>처럼 같은 순서로 넣으면 아무것도 안 바뀌는 게 함정이었다.</p>
<h3 id="6-딕셔너리-그룹핑-패턴">6. 딕셔너리 그룹핑 패턴</h3>
<pre><code class="language-python"># 키가 없으면 빈 리스트 만들고 → append
if grade not in groups:
    groups[grade] = []
groups[grade].append(name)

# 한 줄 버전
groups.setdefault(grade, []).append(name)</code></pre>
<blockquote>
<p>수업 중 질문: &quot;dict로 변환해서 keys()로 이름을 꺼내면 안 되나?&quot;
→ 동작은 하지만 불필요한 변환이다. 이미 정렬된 튜플 리스트에서 리스트 컴프리헨션으로 바로 꺼내는 게 깔끔하다: <code>[x[0] for x in sorted_list[:n]]</code></p>
</blockquote>
<hr>
<h3 id="6-기본값-매개변수-default-parameter">6. 기본값 매개변수 (Default Parameter)</h3>
<pre><code class="language-python"># Java에서는 오버로딩 2개 만들어야 했지만
# Python은 기본값 하나면 끝
def greet(name, greeting=&quot;안녕하세요&quot;):
    return f&quot;{greeting}, {name}님!&quot;

greet(&quot;홍길동&quot;)              # &quot;안녕하세요, 홍길동님!&quot;
greet(&quot;홍길동&quot;, &quot;반갑습니다&quot;)  # &quot;반갑습니다, 홍길동님!&quot;</code></pre>
<h3 id="7-args와-kwargs">7. <em>args와 *</em>kwargs</h3>
<pre><code class="language-python"># *args — 가변 위치 인자 (튜플로 들어옴)
def add_all(*args):
    return sum(args)

add_all(1, 2, 3, 4)  # 10

# **kwargs — 가변 키워드 인자 (딕셔너리로 들어옴)
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f&quot;{key}: {value}&quot;)

print_info(name=&quot;홍길동&quot;, age=25)</code></pre>
<p>Java의 <code>int... numbers</code> (가변인자)와 비슷하지만, <code>**kwargs</code>는 Java에 없는 개념이다.</p>
<p><strong>매개변수 순서</strong>: <code>일반 → 기본값 → *args → **kwargs</code></p>
<h3 id="8-예약어를-키워드-인자로-넘기기">8. 예약어를 키워드 인자로 넘기기</h3>
<pre><code class="language-python"># class는 Python 예약어라서 직접 쓰면 SyntaxError!
make_tag(&quot;Image&quot;, &quot;div&quot;, class=&quot;container&quot;)  # 에러!

# 딕셔너리 언패킹으로 우회
make_tag(&quot;Image&quot;, &quot;div&quot;, **{&quot;class&quot;: &quot;container&quot;})  # OK!</code></pre>
<p>딕셔너리 키는 문자열이라 예약어 충돌이 없다. <code>**</code>로 언패킹하면 키워드 인자로 넘어간다.</p>
<h3 id="9-nonlocal--클로저에서-바깥-변수-수정">9. nonlocal — 클로저에서 바깥 변수 수정</h3>
<pre><code class="language-python">def create_counter(start=0):
    count = start

    def increment():
        nonlocal count   # &quot;count는 바깥 함수 변수야&quot;
        count += 1
        return count

    return increment</code></pre>
<table>
<thead>
<tr>
<th>키워드</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>global</code></td>
<td>모듈(파일) 최상위 변수 참조</td>
</tr>
<tr>
<td><code>nonlocal</code></td>
<td>바깥 함수의 변수 참조</td>
</tr>
<tr>
<td>아무것도 안 쓰면</td>
<td>현재 함수의 지역변수</td>
</tr>
</tbody></table>
<blockquote>
<p>수업 중 질문: &quot;함수 외부에 변수 안 두고 값을 저장해둘 수 있는 거야?&quot;
→ Python의 클로저(closure)가 이걸 가능하게 한다. 안쪽 함수가 바깥 함수의 변수를 기억하고 있다가, <code>nonlocal</code>을 선언하면 수정까지 할 수 있다. Java에서는 클래스 필드에 저장해야 했던 걸, Python에서는 클로저로 간단하게 처리할 수 있다.</p>
</blockquote>
<h3 id="10-함수를-인자로-넘기기">10. 함수를 인자로 넘기기</h3>
<pre><code class="language-python">def apply_all(value, *functions):
    result = value
    for func in functions:
        result = func(result)
    return result

double = lambda x: x * 2
add_ten = lambda x: x + 10
apply_all(3, double, add_ten)  # 3 → 6 → 16</code></pre>
<p>Python에서는 함수도 변수처럼 넘기고 호출할 수 있다.</p>
<h3 id="11-args--all로-다중-조건-필터링">11. *args + all()로 다중 조건 필터링</h3>
<pre><code class="language-python">def filter_all(items, *conditions):
    result = []
    for item in items:
        if all(condition(item) for condition in conditions):
            result.append(item)
    return result

is_positive = lambda x: x &gt; 0
is_even = lambda x: x % 2 == 0
is_small = lambda x: x &lt; 50

filter_all([-3, 4, 7, 12, -8, 50, 22], is_positive, is_even, is_small)
# → [4, 12, 22]</code></pre>
<p><code>*conditions</code>로 조건 함수를 여러 개 받고, <code>all()</code>로 모든 조건을 만족하는지 확인하는 패턴. 함수를 인자로 넘기는 것과 <code>*args</code>, <code>all()</code>이 합쳐진 종합 문제다.</p>
<p>리스트 컴프리헨션 버전:</p>
<pre><code class="language-python">def filter_all(items, *conditions):
    return [item for item in items if all(c(item) for c in conditions)]</code></pre>
<h3 id="12-lambda-루프-함정--알아두면-좋은-것">12. lambda 루프 함정 — 알아두면 좋은 것</h3>
<pre><code class="language-python"># 이렇게 쓰면 전부 같은 값이 나온다!
multipliers = []
for i in range(1, 4):
    multipliers.append(lambda x: x * i)  # i를 값이 아니라 변수 자체를 기억

# 전부 15 (i가 마지막에 3이 되어있으니까)
print(multipliers[0](5))  # 15!

# 함수로 감싸면 해결
def make_multiplier(n):
    return lambda x: x * n  # n에 그 시점의 값이 복사됨</code></pre>
<p>lambda는 변수를 기억하지, 값을 기억하는 게 아니다. 루프 안에서 직접 쓸 때 주의!</p>
<hr>
<h2 id="오늘의-결과">오늘의 결과</h2>
<ul>
<li>10문제 풀이, 1차 정답률 70% (7/10), 최종 전부 정답</li>
<li>튜플 활용 안정적, 함수 심화 개념 빠르게 흡수</li>
<li>다음 시간: Phase 1 마무리 — 예외 처리(try/except) + 종합 복습</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>