<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dduk.ddak.log</title>
        <link>https://velog.io/</link>
        <description>생각이 현실이 될 수 있도록 노력하는 중입니다.</description>
        <lastBuildDate>Mon, 18 May 2026 11:29:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dduk.ddak.log</title>
            <url>https://velog.velcdn.com/images/melon-chicken/profile/45162ad7-f034-4cb6-8159-539f87dedb90/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dduk.ddak.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/melon-chicken" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Reading Note] (1) Concept of VLA: Integration Vision, Language, Action]]></title>
            <link>https://velog.io/@melon-chicken/Reading-Note-1-Concept-of-VLA-Integration-Vision-Language-Action</link>
            <guid>https://velog.io/@melon-chicken/Reading-Note-1-Concept-of-VLA-Integration-Vision-Language-Action</guid>
            <pubDate>Mon, 18 May 2026 11:29:13 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<blockquote>
<p>이 글은 Vision-Language-Action Models 논문을 읽으며, VLA가 왜 등장했고, 기존 Vision-Language Model과 무엇이 다른지를 정리하기 위해 작성되었습니다. 논문을 보며 메모한 내용들을 나열한 것이기에 다소 거친 면이 있으며 실제 논문과 같이 보면 더 따라가기 쉽습니다.</p>
</blockquote>
<p><strong>AI가 ‘보고 이해하는 것’을 넘어 실제 행동으로 연결되기 위해 어떤 구조가 필요한지</strong>를 중심으로 살펴보고자 하며 VLA 분야에 대해 처음 접하는 사람들을 위해 최대한 관련 개념들도 풀어서 작성했습니다.</p>
<blockquote>
<p>부정확한 내용이 있다면 댓글로 비판해주시면 감사하겠습니다.</p>
</blockquote>
<h2 id="paper-information">Paper Information</h2>
<blockquote>
<p><strong>Title:</strong> Vision-Language-Action Models: Concepts, Progress, Applications and Challenges
<strong>Authors:</strong> Ranjan Sapkota, Yang Cao, Konstantinos I. Roumeliotis, Manoj Karkee 
<strong>arXiv ID:</strong> <a href="https://arxiv.org/abs/2505.04769">2505.04769</a><br><strong>Topic:</strong> Vision-Language-Action Models, Robotics, Embodied AI, AI Agents </p>
</blockquote>
<p>최근 <strong>Vision-Language Model (VLM)</strong>은 기계가 이미지와 텍스트를 함께 이해하게끔 하는 방향으로 기여를 했지만, 실제 세계에서 움직이고 상호작용해야 하는 로봇이나 embodied agent에게는 단순한 이해만으로는 부족했습니다.</p>
<p>예를 들어, 모델이 “빨간 사과”를 인식하고 “사과를 집어라”라는 문장을 이해하더라도, 그 사과를 향해 팔을 움직이고, 적절한 힘으로 잡고, 상황 변화에 따라 동작을 수정할 수 없다면 실제 행동을 수행하는 agent라고 보기는 어렵습니다.</p>
<p>이러한 문제의식에서 등장한 개념이 바로 <strong>Vision-Language-Action Model(VLA)</strong>입니다.</p>
<h1 id="1-introduction">1. Introduction</h1>
<p>VLA가 발전되기 전 robotics와 AI 분야의 발전 양상</p>
<blockquote>
<p>각각의 시스템은 잘 작동하지만 정작 시스템을 통합하려니 안되고 새롭거나 예측할 수 없는 상황이 벌어지면 작동을 못함</p>
</blockquote>
<h2 id="11-각자-따로-놀던-시대">1.1. 각자 따로 놀던 시대</h2>
<ol>
<li><strong>VIsion System :</strong> See and recognize images<ul>
<li>extensive labeled datasets</li>
<li>cumbersome retraining for even slight shifts in environment or objectives</li>
<li>Lacked any understanding of language</li>
<li>Lacked the ability to convert visual insifgts into purposeful actions</li>
</ul>
</li>
<li><strong>Language System :</strong> understand and generate text<ul>
<li>restricted to processing language without tcapability to perceive or reason about world</li>
</ul>
</li>
<li><strong>Action System :</strong> control movement<ul>
<li>relying heavily on hand-crafted policies or reinforcement learning</li>
<li>demaned painstaking engineering</li>
<li>failed to generalize beyond narrowly scripted scenarios</li>
</ul>
</li>
</ol>
<h2 id="12-통합을-시도한-시대">1.2. 통합을 시도한 시대</h2>
<ul>
<li>VLM (Vision-Language Model): Vision + Language<ul>
<li>inability to generate or execute coherent actions based on umltimodal input</li>
</ul>
</li>
</ul>
<p>Vision-Language, Language-Action, Vision-Action 처럼 두개의 요소에 대한 System에 대한 통합이 다였음</p>
<p>→ Fragmented Pipeline architecture에 그침 → brittle generalization and labor-intensive engineering efforts</p>
<blockquote>
<p>이게 문제인 이유가 embodied AI같은 분야에서 인지와 이해 그리고 액션이 통합되어 작동하는 시스템이 없다보니 발전 측면에서 병목현상을 발생시킴</p>
</blockquote>
<p>→ VLA의 필요성 대두됨</p>
<p>VLA라는 컨셉 자체는 이미 2021년 - 2022년에 대두된 주제로 Google DeepMind’s Robotic Transformer 2 (RT-2)와 같은 모습으로 연구되어온 주제로 연구되어왔음 그래서 VLA는 결론적으로 vision inputs, language comprehension, moto control capabilities를 통합하여 embodied agent의 상황과 복잡한 지시에 대한 이해 및 실행을 가능하게 함</p>
<p>[224] Zitkovich, B., Yu, T., Xu, S., Xu, P., Xiao, T., Xia, F., Wu, J., Wohlhart, P., Welker, S., Wahid, A., et al., 2023. Rt-2: Vision-language-action models transfer web knowledge to robotic control, in: Conference on Robot Learning, PMLR. pp. 2165–2183.</p>
<h2 id="13-early-vla-approaches">1.3. Early VLA Approaches</h2>
<ol>
<li>Extending vision-language models to include action tokens — numerical or symbolic representations of robot motor commands<ul>
<li>모델이 <strong>서로 대응되어 묶여 있는 시각 데이터, 언어 데이터, 궤적 데이터</strong>로부터 학습할 수 있게 했다.</li>
<li>학습 과정에서 못본 객체에 대한 일반화 (Generalize to unseen objects),</li>
<li>새로운 명령어에 대한 변환 (interpret novel language commands),</li>
<li>비구조화된 환경에서의 다중 단계 추론이 가능하게 됨 (perform multi-step reasoning in unstructured environments)</li>
</ul>
</li>
</ol>
<h1 id="2-concepts-of-vision-language-action-models">2. Concepts of Vision-Language-Action Models</h1>
<blockquote>
<p>What constitutes a VLA model, its historical evolution, multimodal integration mechanisms, and language-based tokenization and encoding strategies</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/85574aa4-dfe7-4f26-b2cf-3f430391fd95/image.png" alt=""></p>
<p>VLA = </p>
<ul>
<li>Vision Encoders (e.g. CNNs, ViTs) +</li>
<li>Language models (e.g. LLMs, Transformers) +</li>
<li>Policy modules or planners to achieve task-conditioned control</li>
</ul>
<p>⇒ multimodal fusion techniques를 활용한 결합 (e.g. cross-attention, concatenated embeddings, token unification)</p>
<p>VLA는 traditional visuomotor pipelines와는 다르게 <code>sematic grounding</code> , <code>context-aware reasoning</code>, <code>affordance detection</code>, <code>temporal planning</code> 이 가능했음</p>
<blockquote>
<p>❓ <strong>traditional visuomotor pipeline</strong></p>
</blockquote>
<p>: 시각 인식과 행동 제어가 모듈별로 나뉜 로봇 제어 방식</p>
<blockquote>
</blockquote>
<p>이미지 입력 → 물체 인식/위치 추정 → 계획 수립 → 제어 명령 생성 → 로봇 실행</p>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/ec8d866a-1405-47ef-925e-65db2e72ad2e/image.png" alt=""></p>
<h2 id="21-발전-양상-evolution-and-timeline">2.1. 발전 양상 (Evolution and Timeline)</h2>
<h3 id="211-foundational-integration-2022-2023">2.1.1. Foundational Integration (2022-2023)</h3>
<ol>
<li><p>basic visuomotor coordination을 modal fusion architectures를 통해  Early VLA를 구현함</p>
<ul>
<li><p>CLIP embedding을 motion primitives와 함께 결합</p>
<p>[141] Reed, S., Zolna, K., Parisotto, E., Colmenarejo, S.G., Novikov, A., Barth-Maron, G., Gimenez, M., Sulsky, Y., Kay, J., Springenberg, J.T., et al., 2022. A generalist agent. arXiv preprint arXiv:2205.06175 .</p>
<p>→ 604개의 다양한 작업에서 <strong>범용적으로 작동할 수 있는 능력</strong>을 보였다. (Generalist Capabilities)</p>
<p>→ Transformer-based planner를 통해 <strong>시간적 추론(temporal reasoning)능력</strong>을 보여줌</p>
<blockquote>
<p>Temperal reasoning : 작업을 시간 순서에 따라 이해하고, 여러 행동을 단계적으로 연결하는 능력</p>
</blockquote>
</li>
</ul>
</li>
<li><p>2023년에는 visual chain-of-though reasoning을 구현하고</p>
<blockquote>
<p>visual chain-of-though reasoning: 이미지/장면을 보고, 바로 행동하지 않고, 중간 추론 단계를 거쳐 행동을 결정하는 능력</p>
</blockquote>
<p> → vision을 기반으로 한 단계적 reasoning</p>
<p> [224] Zitkovich, B., Yu, T., Xu, S., Xu, P., Xiao, T., Xia, F., Wu, J., Wohlhart, P., Welker, S., Wahid, A., et al., 2023. Rt-2: Vision-language-action models transfer web knowledge to robotic control, in: Conference on Robot Learning, PMLR. pp. 2165–2183.</p>
</li>
<li><p>stochastic action prediction을 diffusion process를 통해 발전시켰다</p>
<blockquote>
<p><strong>stochastic action prediction:</strong> 행동을 하나의 정답처럼 고정해서 예측하는 것이 아니라, 가능한 여러 행동 분포에서 샘플링하거나 생성하는 방식</p>
</blockquote>
<blockquote>
<p><strong>diffusion process:</strong> 처음에는 노이즈에 가까운 상태에서 시작해서, 점점 의미 있는 결과로 정제해 나가는 방식</p>
<p>이상한 움직임 후보 (무작위 행동 궤적) </p>
<p>→ 장애물 피하기 → 손목 방향 조정 </p>
<p>→ 컵을 안정적으로 잡는 경로 (자연스러운 로봇 동작 궤적)</p>
</blockquote>
<p> [34] Chi, C., Xu, Z., Feng, S., Cousineau, E., Du, Y., Burchfiel, B., Tedrake, R., Song, S., 2023. Diffusion policy: Visuomotor policy learning via action diffusion. The International Journal of Robotics Research , 02783649241273668.</p>
</li>
</ol>
<ul>
<li>최종적으로 low-level control을 해결했지만, Lacked compositional reasoning 문제가 있었음<blockquote>
<p>❓
  <strong>lacked compositional reasoning</strong></p>
</blockquote>
  개별 개념은 알아도 그것들을 조합해 새로운 복합 작업을 해결하지 못하는 문제<blockquote>
</blockquote>
  → 실제 로봇 환경의 명령과 상황이 대부분 조합적이고 매번 조금씩 다르다.<blockquote>
</blockquote>
</li>
</ul>
<h3 id="312-specialization-and-embodied-reasoning-2024">3.1.2. Specialization and Embodied Reasoning (2024)</h3>
<blockquote>
<p>Second-generation VLA는 domain-specific inductive biases를 활용했다.</p>
<p>→ <strong>범용 모델 하나로 다 해결</strong>하기 보다는, 특정 도메인에서 잘 작동하도록 <strong>구조적 힌트(inductive bias)</strong>를 넣기 시작했다</p>
</blockquote>
<ul>
<li><p>Enhanced <strong>few-shot adaptation</strong> through retrieval-augmented traing</p>
<blockquote>
<p><strong>retrieval-augmented</strong>는 모델이 모든 것을 내부 파라미터에 기억하는 대신, 비슷한 예시나 지식을 외부 데이터베이스에서 찾아와 참고한다</p>
</blockquote>
<p>  <strong>Inductive Bia:</strong> “로봇 작업은 과거의 유사한 조작 경험이 새 작업에 도움이 된다”</p>
</li>
<li><p>optmized navigation via <strong>3D scene-graph integration</strong></p>
<blockquote>
<p>공간을 <strong>노드와 관계(edge)</strong>로 표현</p>
</blockquote>
<p>  <strong>Inductive Bia:</strong> “내비게이션에서는 픽셀 자체보다 “어디에 무엇이 있고, 어떤 공간이 연결되어 있는가”가 중요하다”</p>
</li>
<li><p>introduced reversible architectures for memory efficency</p>
<blockquote>
<p>중간 계산값을 전부 저장하지 않고도 역방향 계산을 할 수 있게 만든 구조</p>
<p>→ backpropagation시 메모리 사용량 감소 → 더 큰 모델이나 긴 sequence 처리 가능</p>
</blockquote>
<p>  <strong>Inductive Bia:</strong> “긴 시간 흐름과 여러 모달리티를 처리하려면 메모리 효율성이 중요하다”</p>
</li>
<li><p>addressed partial observability with physics-informed attention</p>
</li>
<li><p>improved disentanglement</p>
<blockquote>
<p>“빨간 컵”을 볼 때 
→ 빨간색 = color, 컵 = object category, 잡을 수 있음 = affordance, 테이블 위 = spatial relation
→ 하나의 물체를 개념에 따라 분리해서 다각화 이해 (뭉뚱그려 이해하지 않는다)</p>
</blockquote>
</li>
<li><p>extended applications to autonomous driving via multi-modal sensor fusion</p>
</li>
</ul>
<p>⇒ required new benchmarking methodologies (기존 평가 방식으로는 VLA의 성능을 제대로 평가할 수 없게 되었다)</p>
<blockquote>
<p>2024년 VLA는 단순히 vision-language-action을 연결하는 수준을 넘어서, <strong>로봇 조작, 내비게이션, 물리 환경, 자율주행 같은 특정 도메인의 구조적 지식</strong>을 모델에 반영하면서 더 잘 일반화하고 더 안정적으로 행동하도록 발전</p>
</blockquote>
<h3 id="313-generalization-and-safety-critical-deployment-2025">3.1.3. Generalization and Safety-Critical Deployment (2025)</h3>
<blockquote>
<p>2025년부터는 robustness와 human alignment를 중요시하고 있음</p>
<ul>
<li><strong>human alignment</strong>는 모델이 명령을 수행하는 것을 넘어서, <strong>사람이 의도한 바와 안전 기준에 맞게 행동하도록 조정되는 것</strong></li>
</ul>
</blockquote>
<ul>
<li>Integrated Formal verification for risk-aware decisions</li>
<li>Demostrate Whole-body control을 hierachical VLA로 구현</li>
</ul>
<p>[42] Ding, P., Ma, J., Tong, X., Zou, B., Luo, X., Fan, Y., Wang, T., Lu, H., Mo, P., Liu, J., et al., 2025b. Humanoid-vla: Towards universal humanoid control with visual integration. arXiv preprint arXiv:2502.14795 .</p>
<h4 id="effect">Effect</h4>
<ul>
<li><p>embedded 개발 환경에서 계산 효율성을 최적화하기도 함</p>
</li>
<li><p>Affordance chaing과 sim-to-real transfer learning과 같은 paradigm이 제시되면서 cross-embodiment challenge를 해결함</p>
<ul>
<li><p><strong>affordance</strong>는 어떤 물체가 제공하는 <strong>행동 가능성</strong></p>
</li>
<li><p><strong>affordance chaining</strong>은 복잡한 행동을 <strong>“무엇을 어떻게 사용할 수 있는가”의 연쇄</strong>로 나누어 수행하는 방법</p>
<blockquote>
<p>e.g. 문을 열고 방에 들어가라 : 문손잡이를 찾는다 → 손잡이를 잡는다 → 돌린다 → 문을 민다 → 열린 공간으로 이동한다</p>
</blockquote>
</li>
<li><p><strong>sim-to-real transfer learning</strong>은 시뮬레이션에서 학습한 능력을 실제 로봇 환경으로 옮기는 학습 방식</p>
</li>
<li><p><strong>cross-embodiment challenge</strong>는 한 로봇에서 배운 능력을 다른 로봇에 옮기기 어렵다는 문제</p>
</li>
</ul>
</li>
<li><p>Natural Language Grounding을 통해 VLA와 Human-in-the-loop interfaces를 연결</p>
<ul>
<li><strong>human-in-the-loop</strong>는 사람이 시스템의 판단이나 실행 과정에 중간에 개입하는 구조</li>
</ul>
</li>
</ul>
<h2 id="22-멀티모달은-어떻게-하나가-되었나">2.2. 멀티모달은 어떻게 하나가 되었나</h2>
<blockquote>
<p>이전에 언급되었듯 Vision, Language, Action을 하나의 Achitecture로 만드려는 시도는 있어왔음</p>
</blockquote>
<p>e.g. Traditional Robotics → Perception + Natural Language Process + Control = Discrete Modules linked with manual defined interfaces and data transofmation</p>
<blockquote>
<p>이를 극복하기 위해 <strong>Large-scale Pretrained Encoders</strong>와 <strong>Transfromer Based Architectures</strong>를 통해 end-to-end module을 구현함</p>
</blockquote>
<p>→ 이제 모델이 interpret visual observations and linguistic instructions <strong>within the same computational space</strong>, allowing flexible, context-aware reasoning할 수 있게됨 e.g. CLIPort, VIMA</p>
<blockquote>
<p>최근에는 이를 더 발전시켜 temporal, spatial grounding을 활용해 더 발전해왔음 e.g. RT-2, Octo</p>
</blockquote>
<h2 id="23-vla는-어떻게-세상을-이해하는가-tokenization-and-representation">2.3. VLA는 어떻게 세상을 이해하는가 (Tokenization and Representation)</h2>
<blockquote>
<p>위와 같이 VLA가 발전할 수 있었던 이유는 <strong>token-based representation framework</strong> 덕분이었음</p>
</blockquote>
<p>→ <strong>semantic reasoning</strong> (What needs to be done) 뿐만 아니라 <strong>Control policy execution</strong> (How to do it)을 learnable하며 compositional 한 방식으로 구현함</p>
<h3 id="231-prefix-tokens--encoding-context-and-instruction">2.3.1. Prefix Tokens — Encoding Context and Instruction</h3>
<blockquote>
<p>Contextual Backbone of VLA Models</p>
</blockquote>
<ul>
<li>These tokens encode the <strong>environmental scene (via images or video)</strong> and the accompanying <strong>natural language instruction</strong> into compact embeddings that prime the model’s internal representations</li>
</ul>
<h3 id="232-state-tokens--embedding-the-robots-configuration">2.3.2. State Tokens — Embedding the Robot’s Configuration</h3>
<blockquote>
<p>be aware of their internal physical state</p>
</blockquote>
<ul>
<li>State tokens, which encode real-time information about the agent’s configuration—joint positions, force-torque readings, gripper status, end-effector pose, and even the locations of nearby  objects</li>
</ul>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/af63b3d1-be3e-48c1-b3bf-b282c0ee2162/image.png" alt=""></p>
<ul>
<li>state tokens encapsulate spatial features</li>
<li>The transformer model integrates this state representation with <strong>environmental and instructional context</strong> to generate navigation actions that dynamically adapt to changing surroundings.</li>
</ul>
<h3 id="233-action-tokens--autogressive-control-generation">2.3.3. Action Tokens — Autogressive Control Generation</h3>
<blockquote>
<p>autoregressively generated by the model to represent the next step in motor control</p>
<p>→ Each token corresponds to a <strong>low-level control signal</strong>, such as joint angle updates, torque values, wheel velocities, or high-level movement primitives</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/7fd6d69b-3a42-4da0-a5fc-0a5692b48498/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/f0dd9701-df93-4548-9ddb-6914b84a6292/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/11499667-5341-4e5f-9ad4-85dff39b8e59/image.png" alt=""></p>
<ul>
<li><p>Training VLA model을 위해서는 hybrid learning paradigm 이 요구됨</p>
<ul>
<li>Web-based semantic knowledge</li>
<li>task-grounded information from robotics datasets</li>
</ul>
</li>
<li><p>그래서 어떤 데이터 소스를 사용해야하는가?</p>
<ol>
<li><p>Large-scale internet-derived corpora form the backbone of the model’s semantic prior.</p>
<p> e.g. Image-caption pairs, instruction-following datasets, visual questioning-answering corpora</p>
<ul>
<li>Vision encoder와 language encoder의 pretaining을 가능하게 한다</li>
<li>contrastive나 masked modeling objectives를 사용하는데 (e.g. CLIP-style contrastive learning or language modeling losses) 이는 vision modalities와 language modalities를 shared embedding space에 놓기 위함이다.</li>
<li>semantic understanding alone is insufficient for physical task execution</li>
</ul>
</li>
<li><p>Grounding the model in embodied experience</p>
<ul>
<li><strong>Robot trajectory datasets</strong>—collected either from realworld robots or high-fidelity simulators<ul>
<li>to teach the model how language and perception translate into action</li>
<li>e.g. RoboNet [37], BridgeData [50], and RT-X</li>
</ul>
</li>
</ul>
</li>
</ol>
<ul>
<li><p>최근 동향에서는 multistage or multitask training strategies를 활용하고 있다고 한다</p>
<ul>
<li><p>pretrained on vision-language datasets</p>
<ol>
<li>using <strong>masked language modeling</strong><ul>
<li>fine-tuned on ronot demostration data using token level autogressive loss</li>
</ul>
</li>
<li><strong>using curriculum learning</strong><ul>
<li>simpler tasks (e.g. object pushing)→ more complex tasks (e.g.multistep manipulation)</li>
</ul>
</li>
<li>Domain Adaptation으로 접근하기도 함<ul>
<li>Open-VLA or sim-to-real transfer to bridge gap between synthetic and real-work distributions</li>
</ul>
</li>
</ol>
<blockquote>
<p>By unifying semantic priors with task execution data, these learning paradigms allow VLA models to generalize across tasks, domains, and embodiments—forming the backbone of scalable, instructionfollowing agents capable of robust real-world operation.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="25-adaptive-control-and-real-time-execution">2.5. Adaptive Control and Real-Time Execution</h2>
<ul>
<li><p>This training paradigm not only helps</p>
<ol>
<li><p>the model understand <strong>object affordances</strong> (e.g., apples can be grasped) and <strong>action outcomes</strong> (e.g., lifting requires force and trajectory)</p>
</li>
<li><p>promotes generalization <strong>to novel scenarios</strong></p>
<p>e.g. Google DepMind’s RT-2 (Robot Transformer 2)</p>
<p>Action generation as a form of text generation where each action token corresponds to a discrete command in a robot’s control space </p>
</li>
</ol>
</li>
</ul>
<blockquote>
<p>Another strength of VLAs lies in their ability to perform <strong>adaptive control</strong>, using real-time feedback from sensors to adjust behavior on the fly</p>
<p>[153] Serpiva, V., Lykov, A., Myshlyaev, A., Khan, M.H., Abdulkarim, A.A.,  Sautenkov, O., Tsetserukou, D., 2025. Racevla: Vla-based racing drone navigation with human-like behaviour. arXiv preprint arXiv:2503.02572 </p>
</blockquote>
<p>execution이 진행되는 동안 state token이 실시간으로 업데이트되며 (e.g. seonsor inputs, joint feedback) action plan을 수정할 수있게 된다</p>
<p>→ human-like adaptability and core advantage of VLA systems over pipeline-based robots</p>
<h1 id="3-notes">3. Notes</h1>
<p>이번 논문을 읽고 정리한 VLA의 핵심은 다음과 같습니다.</p>
<blockquote>
<p>VLA는 <strong>시각 정보와 언어 지시를 실제 행동으로 변환하는 embodied AI 모델</strong></p>
</blockquote>
<p>기존의 Vision-Language Model이 “무엇이 보이고, 그것이 무엇을 의미하는가”를 다뤘다면, VLA는 한 단계 더 나아가 “그렇다면 지금 무엇을 해야 하는가”를 다룹니다.</p>
<p>이 점에서 VLA는 로봇, 자율주행, embodied agent, physical AI와 강하게 연결됩니다.</p>
<p>특히 제가 관심 있는 animal behavior analysis나 embodied interaction 관점에서도 VLA는 단순히 행동을 분류하는 모델을 넘어 <strong>환경을 보고, 행동 맥락을 이해하고, 다음 행동을 예측하거나 상호작용하는 agent 구조</strong>로 확장될 가능성이 있다고 느꼈습니다.</p>
<hr>
<blockquote>
<p>오늘은 Vision-Language-Action Model의 Concept 파트를 중심으로 정리해보았습니다. 부정확한 내용이나 더 궁금한 점이 있다면 댓글로 남겨주시면 감사하겠습니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[오리를 관찰하는 AI를 만들고 있습니다 — BMOD 개발기 #1]]></title>
            <link>https://velog.io/@melon-chicken/%EC%98%A4%EB%A6%AC%EB%A5%BC-%EA%B4%80%EC%B0%B0%ED%95%98%EB%8A%94-AI%EB%A5%BC-%EB%A7%8C%EB%93%A4%EA%B3%A0-%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4-BMOD-%EA%B0%9C%EB%B0%9C%EA%B8%B0-1</link>
            <guid>https://velog.io/@melon-chicken/%EC%98%A4%EB%A6%AC%EB%A5%BC-%EA%B4%80%EC%B0%B0%ED%95%98%EB%8A%94-AI%EB%A5%BC-%EB%A7%8C%EB%93%A4%EA%B3%A0-%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4-BMOD-%EA%B0%9C%EB%B0%9C%EA%B8%B0-1</guid>
            <pubDate>Fri, 15 May 2026 14:39:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>개발 계기, 방향성, 그리고 지금 어디까지 왔는지</p>
</blockquote>
<hr>
<h2 id="bmo에서-오리까지">BMO에서 오리까지</h2>
<blockquote>
<p>시작은 유튜브 영상 하나였습니다.</p>
</blockquote>
<p>!youtube[l5ggH-YhuAw?si=DsvleeSHJ7Jy7cn4]</p>
<p><a href="https://www.youtube.com/watch?v=l5ggH-YhuAw">BMO local agent를 라즈베리파이로 구현하는 영상</a>을 보고, *&quot;이거 재밌겠다&quot;* 싶어서 그 뒤에는 매일 당근마켓에서 중고 라즈베리파이를 찾아보다 결국 구매까지 해버렸습니다.</p>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/6d9efeb6-d2f3-44d2-b524-5afbd5694bb3/image.png" alt=""></p>
<p style="color: grey;">하지만 생각보다 비쌌다는..</p>

<p>이전에 <strong>핀과 제이크의 어드벤처 타임</strong>을 재밌게 보기도 했고 BMO처럼 말 걸면 반응하는 로컬 AI가 가슴을 설레게 했었습니다.</p>
<p>그런데 막상 따라 만들다 보니 질문이 생겼습니다.</p>
<blockquote>
<p><em>단순히 재현하는 것 이상으로, 어떤 가치를 더할 수 있을까?</em></p>
</blockquote>
<p>그 고민을 하던 차에 이전에 실험을 진행했던 <a href="https://github.com/MelonChicken/WildlifeVision-Baseline">카메라 트랩 이미지 처리</a>가 생각났습니다.</p>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/009db8fd-8d62-45ce-ae10-54df727ece77/image.png" alt=""></p>
<p>이 프로젝트 자체는 Animal Classification에 지나지 않았지만 어떻게 하면 단순 분류에서 더 나아가 연구를 할 수 있을까하고 생각을 하게 되었고, 여기서 Vision-Language Model이 &quot;이게 어떤 동물이다&quot;를 넘어서 &quot;어떤 동물이 어떤 상황에서 이런 행동을 한다&quot;라고 설명을 더 풍부하게 해줄 수 있다는 점을 배웠기 때문입니다.</p>
<p>그때부터 <code>라즈베리파이 + 비전 + 행동 인식</code> 으로 실제 구현을 해보고 싶다라는 개발 방향이 잡혔습니다. </p>
<p>그 뒤 그럼 어떤 영상을 대상으로 진행을 할까 생각했고 이전에 영국에 갔을때 공원에서 흔히 볼 수 있었던 오리가 생각나 유튜브 오리 영상을 보며 영상이 풍부하게 있다는 점, 사람들에게 낯설지 않은 동물이라는 점을 근거로 오리 영상을 선정하게 되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/3fd7229e-ae8c-4f70-a163-632d2274343a/image.png" alt=""></p>
<p>*&quot;오리가 지금 뭘 하는지 알아채고, 행동이 바뀌면 반응하고, 세션이 끝나면 요약까지 해주는 걸 만들면 어떨까?&quot;*</p>
<p>기존 행동 분류 시스템은 대부분 &quot;이게 A다, B다&quot;를 판단하는 데서 끝납니다. 하지만 제가 만들고 싶었던 것은 단순 분류 결과를 출력하는 시스템이 아니라, <strong>관찰 결과에 반응하고 세션 단위로 요약해주는 작은 인터페이스</strong>였습니다.</p>
<p>물론 실제로 만드는 경험 자체가 저에게 도움이 될거라 생각하기도 했습니다.</p>
<hr>
<h2 id="무엇을-만드는가">무엇을 만드는가</h2>
<blockquote>
<p>깃허브 링크: <a href="https://github.com/MelonChicken/be-more-agent-duck">https://github.com/MelonChicken/be-more-agent-duck</a></p>
</blockquote>
<p><strong>BMOD</strong>는 오리 영상을 입력받아 행동을 분류하고, 행동 변화에 반응하며, 세션이 끝나면 리포트를 생성하는 <strong>lightweight behaviour observation system</strong>입니다.</p>
<p>파이프라인을 한 줄로 정리하면 이렇습니다:</p>
<pre><code>입력 영상
  → 5초 구간 분할
  → 구간당 10프레임 샘플링
  → CLIP 임베딩 평균
  → Logistic Regression 분류
  → 이벤트 감지
  → 반응 생성
  → 세션 리포트 (CSV + JSON)</code></pre><p>행동 클래스는 세 가지입니다: <strong>resting</strong>, <strong>feeding</strong>, <strong>exploring</strong>. 그리고 분류 신뢰도가 낮을 때를 위한 <strong>uncertain</strong> 상태도 따로 두었습니다.</p>
<h3 id="왜-이-스택인가">왜 이 스택인가</h3>
<p>처음엔 성능만 따지면 훨씬 좋은 end-to-end video model도 고려했습니다. 그런데 이 프로젝트의 최종 목표는 <strong>Raspberry Pi 5 위에서 오프라인으로 돌아가는 것</strong>이었기에 무거운 모델은 선택지가 될 수 없었습니다.</p>
<p>CLIP 임베딩 + Logistic Regression 조합은 다음 이유로 선택했습니다:</p>
<ul>
<li>Pi에 이식하기 충분히 가볍다</li>
<li>노트북에서 개발하고 Pi로 옮기기 쉽다</li>
<li>행동 분류에 필요한 visual feature를 CLIP이 이미 잘 뽑아준다</li>
</ul>
<p>물론 리스크도 있습니다. CLIP feature가 행동 자체보다 <strong>장면 특성</strong> (배경, 조명 등)에 반응할 수 있다는 점. 그래서 초기 라벨링은 클래스당 20구간씩, 대표 구간만 먼저 구성해서 threshold validation을 꼼꼼히 할 계획입니다.</p>
<hr>
<h2 id="지금-어디까지-왔나">지금 어디까지 왔나</h2>
<p>현재는 핵심 파이프라인 구현을 진행 중입니다.</p>
<ul>
<li>✅ 전체 아키텍처 설계 완료</li>
<li>✅ ffmpeg 기반 프레임 추출 (5초 구간, 10프레임)</li>
<li>✅ CLIP 임베딩 추출 및 구간 평균</li>
<li>✅ Logistic Regression 분류기 (dummy fallback 포함)</li>
<li>▢ 행동 전환 기반 이벤트 감지 로직</li>
<li>▢ <code>SessionLogger</code> 클래스 설계 (CSV + JSON 듀얼 출력)</li>
<li>▢ tkinter GUI 연동</li>
<li>▢ 실제 오리 데이터셋 라벨링 (클래스당 20구간 목표)</li>
<li>▢ Pi 이식 및 오프라인 실행 검증</li>
</ul>
<p>몇 가지 설계 결정을 공유하면:</p>
<p><strong>행동 전환 반응은 최소 3구간(15초) 유지 후에만 발생합니다.</strong> 5초 단위 분류가 짧게 흔들릴 수 있어서, 노이즈 반응을 막기 위한 smoothing 규칙입니다. (<code>MIN_STABLE_SEGMENTS = 3</code>으로 상수화)</p>
<p><strong>uncertain 처리도 신중하게 설계하는 중입니다.</strong> <code>CONFIDENCE_THRESHOLD = 0.55</code> 미만이면 uncertain으로 처리하는데, 연속 uncertain이 일정 횟수 이상 발생할 때만 별도 반응을 보여줍니다. 시연 중 uncertain이 너무 자주 뜨면 몰입감이 깨지기 때문입니다.</p>
<hr>
<h2 id="앞으로의-계획">앞으로의 계획</h2>
<p>단기적으로는 이렇게 진행합니다:</p>
<ul>
<li><strong>2주 내:</strong> 데이터셋 구성 완료 + notebook 기반 baseline 검증</li>
<li><strong>이후:</strong> Pi 이식 → 오프라인 시연 가능한 형태로 완성</li>
<li><strong>더 나아가:</strong> 날짜별 행동 패턴 분석 파이프라인, 다른 동물로 확장</li>
</ul>
<p>리포트 포맷도 이미 정해놨습니다. 세션이 끝나면 <strong>행동 타임라인 + 대표 이벤트 5개 + 2~3문장 요약</strong>이 나옵니다. 연구자나 학생이 오리를 오래 관찰한 뒤 &quot;오늘 뭐가 있었지?&quot;를 바로 파악할 수 있습니다.</p>
<hr>
<h2 id="마치며">마치며</h2>
<blockquote>
<p>아직 데모와 실험 결과는 완성되지 않았지만, 프로젝트의 구조와 방향성은 어느 정도 잡혔고 코드는 조금씩 쌓이고 있습니다.</p>
</blockquote>
<p>다음 글에서는 데이터셋 라벨링 기준과 CLIP feature가 행동을 얼마나 잘 구분하는지 초기 실험 결과를 들고 와보겠습니다.</p>
<blockquote>
</blockquote>
<p>오리를 관찰하는 AI를 만드는 일이 생각보다 재미있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 15649번 N과 M (1)]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-15649%EB%B2%88-N%EA%B3%BC-M-1</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-15649%EB%B2%88-N%EA%B3%BC-M-1</guid>
            <pubDate>Tue, 20 Jan 2026 03:39:47 GMT</pubDate>
            <description><![CDATA[<h1 id="15649-n과-m-1">[15649] N과 M (1)</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2026-01-20</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/14ebe765-4331-4654-bafe-85dc6e2ad3e0/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 백트래킹(Backtracking), 순열</li>
<li><strong>요구사항</strong>: 1부터 N까지의 자연수 중에서 <strong>중복 없이</strong> M개를 고른 <strong>모든 순열</strong>을 출력해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>int[]</code> : 현재 선택된 수열을 저장</li>
<li><code>boolean[]</code> : 숫자 사용 여부(중복 방지) 체크</li>
<li><code>StringBuilder</code> : 출력 성능 최적화</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>재귀 (recursion)</li>
<li>백트래킹 (backtracking)</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>순열 (permutation)</li>
<li>깊이 (depth)</li>
<li>상태 복원 (backtracking)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어">풀이 아이디어</h2>
<blockquote>
<h3 id="1-문제-분해">1. 문제 분해</h3>
</blockquote>
<ul>
<li>길이가 M인 수열을 만든다.</li>
<li>각 자리에 1부터 N까지의 숫자를 하나씩 시도한다.</li>
<li>이미 사용한 숫자는 다시 사용할 수 없다.</li>
<li>길이가 M이 되면 하나의 완성된 순열로 출력한다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<blockquote>
<h3 id="2-재귀-함수의-목적-정의-핵심">2. 재귀 함수의 목적 정의 (핵심)</h3>
<p><strong><code>sequence(n, depth)</code>의 역할</strong></p>
</blockquote>
<ul>
<li><code>depth == M</code>
→ 길이 M의 수열이 완성되었으므로 출력하고 종료한다.</li>
<li>그렇지 않으면
→ 1부터 N까지 순회하면서 아직 사용하지 않은 숫자를 선택한다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<blockquote>
</blockquote>
<h3 id="3-핵심-로직-흐름">3. 핵심 로직 흐름</h3>
<blockquote>
</blockquote>
<pre><code class="language-text">sequence(depth):
    if depth == M:
        수열 출력
        return
&gt;
    for i = 1 to N:
        if i가 아직 사용되지 않았다면:
            i를 선택
            sequence(depth + 1)
            i 선택 취소 (상태 복원)</code></pre>
<blockquote>
</blockquote>
<hr>
<blockquote>
</blockquote>
<h3 id="4-백트래킹의-본질">4. 백트래킹의 본질</h3>
<blockquote>
</blockquote>
<ul>
<li><strong>선택</strong>: <code>visited[i] = true</code>, <code>tempArr[depth] = i</code></li>
<li><strong>재귀 호출</strong>: 다음 자리로 이동</li>
<li><strong>복원</strong>: <code>visited[i] = false</code><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.util.*;

class Main {
    public static int[] tempArr;
    public static boolean[] visited;
    public static StringBuilder sb = new StringBuilder();

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int n = sc.nextInt();
        int r = sc.nextInt();

        tempArr = new int[r];
        visited = new boolean[n + 1];

        sequence(n, 0);
        System.out.println(sb);
    }

    public static void sequence(int n, int depth) {
        if (depth == tempArr.length) {
            for (int i = 0; i &lt; tempArr.length; i++) {
                sb.append(tempArr[i]).append(&quot; &quot;);
            }
            sb.append(&quot;\n&quot;);
            return;
        }

        for (int i = 1; i &lt;= n; i++) {
            if (!visited[i]) {
                visited[i] = true;
                tempArr[depth] = i;
                sequence(n, depth + 1);
                visited[i] = false; // 상태 복원
            }
        }
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<ul>
<li><strong>시간 복잡도</strong>: O(N! / (N−M)!)</li>
<li><strong>공간 복잡도</strong>: O(N + M)</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>재귀를 사용해야 한다는 것은 알았지만 <strong>재귀 함수 하나가 정확히 무엇을 책임지는지</strong>를 정의하지 못해 접근이 어려웠다.</li>
<li>“이 함수가 한 번 호출될 때 정확히 무엇을 결정하는가”를 잡지 못해 로직을 외부 설명에 의존하게 되었다.</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>재귀 문제에서는 반드시 <strong>“이 함수는 어떤 단위의 문제를 해결하는가”</strong>를 먼저 문장으로 정의해야 한다.</li>
<li>백트래킹은 <code>선택 → 재귀 → 복원</code> 이 3단계가 항상 세트로 움직인다.</li>
<li>순열/조합 문제에서 <code>depth</code>는 <strong>현재까지 몇 개를 골랐는지</strong>를 의미하는 지표로 이해하면 훨씬 명확해진다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/15649">https://www.acmicpc.net/problem/15649</a></li>
<li>참고 자료: <a href="https://velog.io/@gayeong39/%EB%B0%B1%ED%8A%B8%EB%9E%98%ED%82%B9-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-BackTracking">https://velog.io/@gayeong39/백트래킹-알고리즘-BackTracking</a></li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p><strong>비슷한 유형 (GPT 추천)</strong></p>
<ul>
<li><a href="https://www.acmicpc.net/problem/15650">백준 - 15650번 N과 M (2)</a></li>
<li><a href="https://www.acmicpc.net/problem/15651">백준 - 15651번 N과 M (3)</a></li>
</ul>
</li>
<li><p><strong>확장 문제 (GPT 추천)</strong></p>
<ul>
<li><a href="https://www.acmicpc.net/problem/9742">백준 - 9742번 순열</a></li>
<li><a href="https://www.acmicpc.net/problem/6603">백준 - 6603번 중복 없는 조합</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 2447번 별 찍기 - 10]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-2447%EB%B2%88-%EB%B3%84-%EC%B0%8D%EA%B8%B0-10</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-2447%EB%B2%88-%EB%B3%84-%EC%B0%8D%EA%B8%B0-10</guid>
            <pubDate>Mon, 19 Jan 2026 03:14:02 GMT</pubDate>
            <description><![CDATA[<h1 id="2447-별-찍기---10">[2447] 별 찍기 - 10</h1>
<blockquote>
<p>난이도: ★★★☆☆  •  solved on: 2026-01-19</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/5068dfb6-24e8-4e97-8cb5-523ad75585c3/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 재귀, 분할 정복</li>
<li><strong>요구사항</strong>: 크기 N의 정사각형에 대해 <strong>중앙이 비어 있는 별 패턴</strong>을 재귀적으로 출력해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>List&lt;String&gt;</code> : 각 줄을 문자열 단위로 관리</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>재귀 (Recursion)</li>
<li>분할 정복 (Divide and Conquer)</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>패턴 생성 (pattern generation)</li>
<li>재귀적 구조 (recursive structure)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어">풀이 아이디어</h2>
<blockquote>
<ol>
<li><strong>문제 분해</strong></li>
</ol>
</blockquote>
<ul>
<li>N이 1일 때는 <code>&quot;*&quot;</code> 하나로 종료되는 <strong>기저 조건</strong>을 둔다.</li>
<li>N이 1보다 클 경우, 크기 <code>n/3</code>의 별 패턴을 먼저 만든다.</li>
<li>만들어진 작은 패턴을 이용해<blockquote>
</blockquote>
<ul>
<li>위: <code>***</code></li>
<li>중간: <code>* *</code></li>
<li>아래: <code>***</code>
형태로 결합한다.<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<ol start="2">
<li><strong>핵심 로직 흐름</strong><blockquote>
</blockquote>
<pre><code class="language-text">getStars(n):
    if n == 1:
        return [&quot;*&quot;]
&gt;
    small = getStars(n / 3)
&gt;
    for s in small:
        add s + s + s
&gt;
    for s in small:
        add s + blank + s
&gt;
    for s in small:
        add s + s + s</code></pre>
<blockquote>
</blockquote>
</li>
<li><strong>예외 처리</strong><blockquote>
</blockquote>
<ul>
<li><code>n == 1</code> 인 경우 바로 리스트를 반환하여 재귀를 종료한다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.util.*;
import java.lang.*;
import java.io.*;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        List&lt;String&gt; result = getStars(n);
        StringBuilder sb = new StringBuilder();
        for(String s : result) {
            sb.append(s).append(&quot;\n&quot;);
        }
        System.out.println(sb);
    }

    public static List&lt;String&gt; getStars(int n) {

        List&lt;String&gt; result = new ArrayList&lt;&gt;();

        if(n == 1) {
            result.add(&quot;*&quot;);
            return result;
        }

        List&lt;String&gt; smallStars = getStars(n / 3);
        for(String s : smallStars) {
            result.add(s + s + s);
        }

        String blank = &quot;&quot;;
        for(int i = 0; i &lt; n / 3; i++){
            blank += &quot; &quot;;
        }

        for(String s : smallStars) {
            result.add(s + blank + s);
        }

        for(String s : smallStars) {
            result.add(s + s + s);
        }
        return result;
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<ul>
<li><p><strong>시간 복잡도</strong>: O(N²)</p>
<ul>
<li>최종적으로 N×N 크기의 별 패턴을 생성해야 하므로 출력량 자체가 O(N²)이다.</li>
</ul>
</li>
<li><p><strong>공간 복잡도</strong>: O(N²)</p>
<ul>
<li>모든 줄을 문자열로 저장하는 리스트 때문이며, 재귀 호출 스택은 O(log₃N) 수준이다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>처음에 <strong>접근 자체가 되지 않았다</strong>.</li>
<li>재귀를 쓰는 것은 알겠지만, 반환 타입을 <code>String</code>, <code>List&lt;String&gt;</code>, <code>String[][]</code> 중에서 무엇으로 할지 결정하지 못해 설계 단계에서 막혔다.</li>
<li>결국 재귀 함수의 역할을 <strong>n 크기의 패턴 전체를 반환한다</strong>로 명확히 정의하면서 해결할 수 있었다. (<code>int</code>로 받고 <code>List&lt;String&gt;</code>으로 리턴하기)</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li><p>재귀 문제에서는 <strong>함수의 역할을 한 문장으로 정의하는 것</strong>이 매우 중요하다.</p>
<ul>
<li><p>재귀 함수는 유닛 단위로 반복하는 것이기 때문</p>
</li>
<li><p>예: <code>getStars(n)</code>은 <strong>n 크기의 별 패턴을 문자열 리스트로 반환한다</strong></p>
</li>
</ul>
</li>
<li><p>출력 형태가 줄 단위로 고정되어 있다면 <code>List&lt;String&gt;</code>이 매우 자연스럽다.</p>
</li>
<li><p>중앙을 비우는 문제는 대부분 <strong>3등분 구조</strong>를 가지므로, 먼저 작은 패턴을 만든 뒤 조합하는 방식이 핵심이다.</p>
</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/2447">https://www.acmicpc.net/problem/2447</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p><strong>비슷한 유형 (GPT 추천)</strong></p>
<ul>
<li><a href="https://www.acmicpc.net/problem/4779">백준 - 4779번 칸토어 집합</a></li>
<li><a href="https://www.acmicpc.net/problem/10993">백준 - 10993번 별 찍기 - 18</a></li>
</ul>
</li>
<li><p><strong>확장 문제 (GPT 추천)</strong></p>
<ul>
<li><a href="https://www.acmicpc.net/problem/10991">백준 - 10991번 별 찍기 - 16</a></li>
<li><a href="https://www.acmicpc.net/problem/10997">백준 - 10997번 별 찍기 - 22</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 20920번 영단어 암기는 괴로워]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-20920%EB%B2%88-%EC%98%81%EB%8B%A8%EC%96%B4-%EC%95%94%EA%B8%B0%EB%8A%94-%EA%B4%B4%EB%A1%9C%EC%9B%8C</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-20920%EB%B2%88-%EC%98%81%EB%8B%A8%EC%96%B4-%EC%95%94%EA%B8%B0%EB%8A%94-%EA%B4%B4%EB%A1%9C%EC%9B%8C</guid>
            <pubDate>Sat, 17 Jan 2026 08:21:34 GMT</pubDate>
            <description><![CDATA[<h1 id="20920-영단어-암기는-괴로워">[20920] 영단어 암기는 괴로워</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2026-01-17</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/c11fba7d-e5dd-430a-9451-ae1f3301b41b/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><p><strong>문제 유형</strong>: 문자열 처리, 정렬, 해시</p>
</li>
<li><p><strong>요구사항</strong>:</p>
<ul>
<li><p>길이가 <strong>M 이상인 단어만</strong> 대상으로 한다</p>
</li>
<li><p>단어를 다음 기준으로 정렬해야 한다</p>
<ol>
<li>자주 나오는 단어일수록 앞에 온다</li>
<li>길이가 긴 단어일수록 앞에 온다</li>
<li>사전 순으로 앞서는 단어일수록 앞에 온다</li>
</ol>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>HashMap&lt;String, EnglishWord&gt;</code> : 단어별 등장 횟수 저장</li>
<li><code>List&lt;EnglishWord&gt;</code> : 정렬을 위한 리스트</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>해시를 이용한 빈도 카운팅</li>
<li><code>Comparable</code> 구현을 통한 커스텀 정렬</li>
<li>스트림 정렬 (<code>stream().sorted()</code>)</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>빈도 기반 정렬 (frequency sort)</li>
<li>다중 조건 정렬 (multi-level sort)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--custom-comparator">방법 1 : Custom Comparator</h3>
<blockquote>
<ol>
<li><strong>문제 분해</strong></li>
</ol>
</blockquote>
<ul>
<li>단어를 입력받으며 길이가 M 미만이면 무시한다</li>
<li><code>HashMap</code>에 단어를 key로, 등장 정보를 value로 저장한다</li>
<li>이미 등장한 단어면 횟수만 증가시킨다<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li><strong>정렬 전략</strong><blockquote>
</blockquote>
<ul>
<li><code>EnglishWord</code> 클래스에서 <code>Comparable</code>을 구현한다</li>
<li>정렬 기준<blockquote>
</blockquote>
<ol>
<li>등장 횟수 오름차순</li>
<li>단어 길이 오름차순</li>
<li>사전 역순<blockquote>
</blockquote>
</li>
</ol>
</li>
<li><code>Collections.reverseOrder()</code>로 전체 정렬 결과를 뒤집는다</li>
</ul>
</li>
<li><strong>출력</strong><blockquote>
</blockquote>
<ul>
<li>정렬된 리스트를 순회하며 단어만 출력한다</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.util.*;
import java.lang.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] cmds = br.readLine().split(&quot; &quot;);
        int n = Integer.parseInt(cmds[0]);
        int m = Integer.parseInt(cmds[1]);

        HashMap&lt;String, EnglishWord&gt; map = new HashMap&lt;&gt;();

        for(int i = 0; i &lt; n; i++) {
            String word = br.readLine();
            if(word.length() &lt; m) continue;

            if(!map.containsKey(word)) {
                map.put(word, new EnglishWord(word));
            } else {
                map.get(word).updateCnt();
            }
        }

        List&lt;EnglishWord&gt; list = map.values().stream()
                .sorted(Collections.reverseOrder())
                .toList();

        StringBuilder sb = new StringBuilder();
        for(EnglishWord word : list) {
            sb.append(word.word).append(&quot;\n&quot;);
        }

        System.out.println(sb);
    }

    static class EnglishWord implements Comparable&lt;EnglishWord&gt; {
        String word;
        int cnt;

        public EnglishWord(String word) {
            this.word = word;
            this.cnt = 1;
        }

        @Override
        public int compareTo(EnglishWord anotherWord){
            if(cnt - anotherWord.cnt != 0){
                return cnt - anotherWord.cnt;
            }
            if(word.length() - anotherWord.word.length() != 0){
                return word.length() - anotherWord.word.length();
            }
            return -1 * word.compareTo(anotherWord.word);
        }

        public void updateCnt(){
            cnt++;
        }
    }
}</code></pre>
<hr>
<h3 id="방법-2--custom-class-없이-comparator-활용">방법 2 : Custom Class 없이 Comparator 활용</h3>
<blockquote>
<ol>
<li><strong>개선 포인트</strong></li>
</ol>
</blockquote>
<ul>
<li>별도의 클래스를 만들지 않는다</li>
<li>정렬 기준을 <code>Comparator</code> 체이닝으로 한눈에 드러낸다</li>
<li><code>reverseOrder()</code>를 쓰지 않아도 된다<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li><strong>정렬 기준 명시</strong><blockquote>
</blockquote>
<ul>
<li>빈도 내림차순</li>
<li>길이 내림차순</li>
<li>사전 오름차순</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.*;
import java.util.stream.*;

class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());

        Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();

        for(int i = 0; i &lt; n; i++) {
            String word = br.readLine();
            if(word.length() &lt; m) continue;
            map.put(word, map.getOrDefault(word, 0) + 1);
        }

        List&lt;String&gt; result = map.keySet().stream()
                .sorted((a, b) -&gt; {
                    if(map.get(a) != map.get(b))
                        return map.get(b) - map.get(a);
                    if(a.length() != b.length())
                        return b.length() - a.length();
                    return a.compareTo(b);
                })
                .toList();

        StringBuilder sb = new StringBuilder();
        for(String w : result) sb.append(w).append(&quot;\n&quot;);
        System.out.print(sb);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1">방법 1</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N log N)</li>
<li><strong>공간 복잡도</strong>: O(N)</li>
</ul>
<blockquote>
<h4 id="방법-2">방법 2</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N log N)</li>
<li><strong>공간 복잡도</strong>: O(N)</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>HashMap의 value 객체들을 기준에 맞게 정렬하려다 보니 클래스를 만들고 Comparable을 구현하는 과정이 길어졌다</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li><strong>정렬 기준이 명확한 문제는 Comparator가 가독성이 더 좋다</strong></li>
<li><code>Comparable + reverseOrder()</code> 방식은 기준이 많아질수록 직관성이 떨어질 수 있다</li>
<li>단순 빈도 문제는 <code>Map&lt;String, Integer&gt;</code> 구조만으로도 충분하다</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/20920">https://www.acmicpc.net/problem/20920</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/1302">백준 - 1302반 베스트셀러</a></li>
<li><a href="https://www.acmicpc.net/problem/11536">백준 - 11536번 줄 세우기</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/1181">백준 - 1181번 단어 정렬</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 2108번 통계학]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-2108%EB%B2%88-%ED%86%B5%EA%B3%84%ED%95%99</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-2108%EB%B2%88-%ED%86%B5%EA%B3%84%ED%95%99</guid>
            <pubDate>Sat, 17 Jan 2026 07:28:26 GMT</pubDate>
            <description><![CDATA[<h1 id="2108-통계학">[2108] 통계학</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2026-01-17</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/64dcf335-345d-4f48-89cb-4b8fed0e58be/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 정렬, 구현, 해시/카운팅</li>
<li><strong>요구사항</strong>: N개의 정수에 대해 <strong>산술평균(반올림)</strong>, <strong>중앙값</strong>, <strong>최빈값</strong>, <strong>범위</strong>를 출력해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li>방법 1: <code>int[]</code>, <code>HashMap&lt;Integer,Integer&gt;</code>, <code>ArrayList&lt;Integer&gt;</code></li>
<li>방법 2: <code>int[] count (8001)</code> 카운팅 배열</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>방법 1: 정렬 + 해시맵 빈도</li>
<li>방법 2: 카운팅 (Counting) + 누적합 스캔</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>반올림 (rounding)</li>
<li>최빈값 tie-breaking (동률 시 두 번째로 작은 값)</li>
<li>값 범위 고정 (-4000~4000)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--hashmap--정렬">방법 1 : HashMap + 정렬</h3>
<blockquote>
<ol>
<li>문제 분해</li>
</ol>
</blockquote>
<ul>
<li>입력을 받으면서 <code>min</code>, <code>max</code>, <code>total</code>을 갱신한다.</li>
<li><code>HashMap</code>으로 각 수의 빈도를 센다.</li>
<li>최빈값 후보를 <code>ArrayList</code>에 모은 뒤 정렬로 규칙(두 번째로 작은 값)을 맞춘다.</li>
<li><code>arr</code>를 정렬해 중앙값을 구한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">입력:
    min/max/total 갱신
    map[num]++
&gt;
map 순회:
    최대 빈도 cnt 갱신
    cnt와 같으면 후보 추가
    cnt보다 크면 후보 초기화 후 추가
&gt;
arr 정렬 -&gt; 중앙값
후보 정렬 -&gt; 최빈값 결정(두 번째로 작은 값)</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>평균은 <code>Math.round(1.0 * total / n)</code> 형태로 반올림한다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.util.*; 
import java.lang.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n =  Integer.parseInt(br.readLine());

        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        int total = 0;
        int freq = 4001;
        int cnt = 0;
        ArrayList&lt;Integer&gt; arrayList = new ArrayList&lt;&gt;();
        HashMap&lt;Integer,Integer&gt; map = new HashMap&lt;&gt;();
        int[] arr = new int[n];
        map.put(freq, -1);

        for(int i = 0; i &lt; n; i++){
            int temp =  Integer.parseInt(br.readLine());
            if(temp &lt; min) min = temp;
            if(temp &gt; max) max = temp;

            total += temp;

            if(map.containsKey(temp)){
                map.put(temp,map.get(temp)+1);
            } else {
                map.put(temp,1);
            }
            arr[i] = temp;
        }
        for(Integer key : map.keySet()){
            if(map.get(key).equals(-1)){
                continue;
            }
            if(map.get(key).equals(cnt)){
                arrayList.add(key);
            }
            if(map.get(key).compareTo(cnt)&gt;0){
                arrayList.clear();
                arrayList.add(key);
                cnt = map.get(key);
            }
        }
        Arrays.sort(arr);
        arrayList.sort(Collections.reverseOrder());
        System.out.println(Math.round(1.0 * total / n));
        System.out.println(arr[n/2]);
        if(arrayList.size() == 1){
            System.out.println(arrayList.get(0));
        } else {
            System.out.println(arrayList.get(arrayList.size()-2));
        }
        System.out.println(max-min);
    }
}</code></pre>
<hr>
<h3 id="방법-2--카운팅-배열로-동시-처리">방법 2 : 카운팅 배열로 동시 처리</h3>
<blockquote>
<ol>
<li>문제 분해</li>
</ol>
</blockquote>
<ul>
<li>값 범위가 <code>-4000 ~ 4000</code>으로 고정이므로 <code>count[8001]</code>로 빈도를 센다(인덱스는 <code>x + 4000</code>).</li>
<li>평균: <code>sum / n</code>을 <code>Math.round</code>로 반올림한다.</li>
<li>중앙값: 누적 빈도가 <code>(n+1)/2</code> 이상이 되는 지점을 찾는다(문제에서 N은 홀수).</li>
<li>최빈값: 최대 빈도를 찾은 뒤, <strong>오름차순 스캔 중 최빈값을 만나는 두 번째 값</strong>을 답으로 한다(동률 규칙 충족).</li>
<li>범위: <code>max - min</code>을 출력한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">입력:
    sum, min, max 갱신
    count[x+4000]++
&gt;
median:
    누적합 &gt;= (n+1)/2 되는 최초 값
&gt;
mode:
    maxFreq 구함
    i=0..8000 오름차순으로 스캔하며
        count[i]==maxFreq 인 값을 발견할 때마다 found++
        found==1이면 mode=값
        found==2이면 mode=값(두 번째로 작은 값)로 확정</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>평균 반올림은 음수도 포함하므로 <code>Math.round((double)sum / n)</code>으로 처리한다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.io.*;

class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());

        int[] count = new int[8001]; // -4000~4000 =&gt; +4000 shift
        long sum = 0;

        int min = 4001;
        int max = -4001;

        for (int i = 0; i &lt; n; i++) {
            int x = Integer.parseInt(br.readLine());
            sum += x;
            if (x &lt; min) min = x;
            if (x &gt; max) max = x;
            count[x + 4000]++;
        }

        // 1) 산술평균
        int mean = (int) Math.round((double) sum / n);

        // 2) 중앙값 (N은 홀수)
        int target = (n + 1) / 2;
        int cumulative = 0;
        int median = 0;
        for (int i = 0; i &lt; 8001; i++) {
            cumulative += count[i];
            if (cumulative &gt;= target) {
                median = i - 4000;
                break;
            }
        }

        // 3) 최빈값 (여러 개면 두 번째로 작은 값)
        int maxFreq = 0;
        for (int i = 0; i &lt; 8001; i++) {
            if (count[i] &gt; maxFreq) maxFreq = count[i];
        }

        int mode = 0;
        int found = 0;
        for (int i = 0; i &lt; 8001; i++) {
            if (count[i] == maxFreq) {
                mode = i - 4000;
                found++;
                if (found == 2) break; // 두 번째로 작은 최빈값 확정
            }
        }

        // 4) 범위
        int range = max - min;

        StringBuilder sb = new StringBuilder();
        sb.append(mean).append(&#39;\n&#39;);
        sb.append(median).append(&#39;\n&#39;);
        sb.append(mode).append(&#39;\n&#39;);
        sb.append(range).append(&#39;\n&#39;);
        System.out.print(sb);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1">방법 1</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N log N) (정렬)</li>
<li><strong>공간 복잡도</strong>: O(N) (배열 + 맵/리스트)</li>
</ul>
<blockquote>
<h4 id="방법-2">방법 2</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N + K) (K=8001 고정) → 사실상 O(N)</li>
<li><strong>공간 복잡도</strong>: O(K) → 사실상 O(1)</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>최빈값을 어떻게 저장하고 찾아낼지에 대해 골머리를 앓았다 처음 접근 (방법1)에서는 따로 <code>HashMap</code>을 저장하여 <code>cnt</code>를 확인했는데 나중 접근을 통해 카운팅 배열을 활용해볼 수 있다는 걸 확인했다. </li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>값의 범위가 작고 고정이면 <code>HashMap</code>정렬보다 <strong>카운팅 배열</strong>이 구현도 단순하고 성능도 안정적이다.</li>
<li>최빈값 동률 규칙은 정렬 후 두 번째를 요구하므로, <strong>오름차순 스캔 중 두 번째로 만나는 최빈값</strong>을 고르는 방식이 가장 안전하다. (시간 복잡도는 증가하나, 공간복잡도는 간결하다)</li>
<li>평균 반올림은 음수도 포함하므로 <code>Math.round((double)sum / n)</code>처럼 double로 변환해 처리하는 편이 명확하다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/2108">https://www.acmicpc.net/problem/2108</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/10989">백준 - 10989번 수 정렬하기 3</a></li>
<li><a href="https://www.acmicpc.net/problem/2587">백준 - 2587번 대표값2</a></li>
<li><a href="https://www.acmicpc.net/problem/2592">백준 - 2592번 최빈수 구하기</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/11650">백준 - 11650번 좌표 정렬하기</a></li>
<li><a href="https://www.hackerrank.com/challenges/30-operators/problem">Hackerrank - Day 0: Mean, Median, and Mode</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 1037번 약수]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1037%EB%B2%88-%EC%95%BD%EC%88%98</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1037%EB%B2%88-%EC%95%BD%EC%88%98</guid>
            <pubDate>Thu, 15 Jan 2026 02:29:58 GMT</pubDate>
            <description><![CDATA[<h1 id="1037-약수">[1037] 약수</h1>
<blockquote>
<p>난이도: ★☆☆☆☆  •  solved on: 2026-01-15</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/fa1b67dd-fcee-4868-827e-781c7a4922b4/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 수학, 약수 성질</li>
<li><strong>요구사항</strong>: 어떤 수 (N)의 <strong>진짜 약수들</strong>(1과 (N) 제외)이 주어질 때, (N)을 구해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li>입력 순회(최솟값/최댓값 갱신)</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>약수의 <strong>짝(pair) 성질</strong> 이용</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>약수 쌍(divisor pair), 최소/최대 약수</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어">풀이 아이디어</h2>
<h3 id="왜-최소-약수-×-최대-약수--n-이-되는가">왜 최소 약수 × 최대 약수 = N 이 되는가?</h3>
<p>핵심은 <strong>약수는 항상 쌍으로 존재한다</strong>는 성질이다.</p>
<ul>
<li>어떤 수 $N$에 대해 약수 $d$가 있으면, 항상 $N/d$도 약수이다.</li>
<li>따라서 약수들은 $(d, N/d)$ 형태의 <strong>짝</strong>으로 묶인다.</li>
<li>이때 각 짝의 곱은 항상 ($d \times (N/d) = N$) 이다.</li>
</ul>
<hr>
<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());

        long max = Long.MIN_VALUE;
        long min = Long.MAX_VALUE;

        for (int i = 0; i &lt; n; i++) {
            long temp = Long.parseLong(st.nextToken());
            if (temp &gt; max) max = temp;
            if (temp &lt; min) min = temp;
        }

        System.out.println(min * max);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<ul>
<li><strong>시간 복잡도</strong>: O(n)</li>
<li><strong>공간 복잡도</strong>: O(1)</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>왜 최소 약수와 최대 약수를 곱하면 원래 수 N이 되는지 직관적으로 이해가 안됐다. 하지만 테스트 케이스를 기준으로 규칙성을 찾아 이해는 안되지만 구현하니 됐다.</li>
<li>“약수는 쌍으로 묶이고, 가장 작은 약수의 짝이 가장 큰 약수”라는 연결이 바로 떠오르지 않았다.</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>약수 문제는 대부분 <strong>짝(pair) 성질</strong>을 떠올리면 식이 단순해진다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/1037">https://www.acmicpc.net/problem/1037</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천) :</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/2501">백준 - 2501번 약수 구하기</a></li>
<li><a href="https://www.acmicpc.net/problem/9506">백준 - 9506번 약수들의 합</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천) :</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/2609">백준 - 2609번 최대공약수와 최소공배수</a></li>
<li><a href="https://www.acmicpc.net/problem/3036">백준 - 3036번 링</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 24511번 queuestack]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-24511%EB%B2%88-queuestack</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-24511%EB%B2%88-queuestack</guid>
            <pubDate>Tue, 13 Jan 2026 06:24:20 GMT</pubDate>
            <description><![CDATA[<h1 id="24511-queuestack">[24511] queuestack</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2026-01-13</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/38361a99-e5d8-4fc2-abcc-47bfa821265e/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 자료구조(Queue/Stack), 시뮬레이션 최적화</li>
<li><strong>요구사항</strong>: 주어진 <code>type</code>(0=queue, 1=stack)과 초기 값들로 구성된 구조에 대해, 입력 <code>m</code>개를 순서대로 넣고 최종적으로 빠져나오는 값을 출력해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>Deque</code> : 앞/뒤 삽입·삭제가 O(1)</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>불필요한 시뮬레이션 제거</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>queue만 결과에 영향</li>
<li>초기 queue 값의 “흐름”을 한 덩어리로 처리</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--시뮬레이션-기반-풀이-시간-초과로-실패">방법 1 : 시뮬레이션 기반 풀이 (시간 초과로 실패)</h3>
<blockquote>
<p>전체를 매 입력마다 끝까지 시뮬레이션해서 <code>m × n</code>이 발생하는 구조라 시간 복잡도가 커진다.</p>
</blockquote>
<h4 id="deque를-활용해-구현">Deque를 활용해 구현</h4>
<pre><code class="language-java">//  queuestack

import java.util.*;
import java.lang.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(br.readLine());

        ArrayList&lt;Deque&lt;Integer&gt;&gt; arr = new ArrayList&lt;&gt;();
        String[] types = br.readLine().split(&quot; &quot;);
        String[] values = br.readLine().split(&quot; &quot;);

        int m = Integer.parseInt(br.readLine());

        StringTokenizer st = new StringTokenizer(br.readLine(), &quot; &quot;);
        StringBuilder sb = new StringBuilder();
        String num = &quot;&quot;;
        String temp = &quot;&quot;;
        for(int i = 0; i &lt; m; i++){
            num = st.nextToken();
            for(int j = 0; j &lt; n; j++){
                if(types[j].equals(&quot;0&quot;)){
                    temp =  values[j];
                    values[j] = num;
                    num = temp;
                } else {
                    continue;
                }
            }

            sb.append(num+&quot; &quot;);
        }
        System.out.println(sb);
    }
}</code></pre>
<h4 id="배열을-활용해-구현">배열을 활용해 구현</h4>
<pre><code class="language-java">//  queuestack

import java.util.*;
import java.lang.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(br.readLine());

        ArrayList&lt;Deque&lt;Integer&gt;&gt; arr = new ArrayList&lt;&gt;();
        int[] types = new int[n];

        StringTokenizer st = new StringTokenizer(br.readLine(), &quot; &quot;);
        StringTokenizer st2 = new StringTokenizer(br.readLine(), &quot; &quot;);

        int m = Integer.parseInt(br.readLine());

        StringTokenizer st3 = new StringTokenizer(br.readLine(), &quot; &quot;);
        StringBuilder sb = new StringBuilder();

        for(int i=0;i&lt;n;i++){
            arr.add(new ArrayDeque&lt;&gt;());
            types[i] = Integer.parseInt(st.nextToken());
            arr.get(i).add(Integer.parseInt(st2.nextToken()));
        }
        int num;
        for(int i = 0; i &lt; m; i++){
            arr.get(0).add(Integer.parseInt(st3.nextToken()));
            for(int j = 0; j &lt; n-1; j++){
                if(types[j] == 0){
                    num = arr.get(j).pollFirst();
                } else {
                    num = arr.get(j).pollLast();
                }
                arr.get(j+1).add(num);
            }

            if(types[n-1] == 0){
                num = arr.get(n-1).pollFirst();
            } else {
                num = arr.get(n-1).pollLast();
            }
            sb.append(num+&quot; &quot;);
        }
        System.out.println(sb);
    }
}</code></pre>
<hr>
<h3 id="방법-2--관찰로-onm-만들기">방법 2 : 관찰로 O(n+m) 만들기</h3>
<blockquote>
<ol>
<li><strong>stack(type=1)은 “넣고 바로 빼는 효과”</strong>만 내서 최종 출력 흐름에 남는 값이 없다.</li>
<li><strong>queue(type=0)의 초기 값들만</strong> 결과 스트림에 영향을 준다.</li>
<li>따라서 <strong>초기 queue 값들을 한 번에 모아두고</strong>, 매 쿼리마다 <code>입력값 push → 맨 앞 pop</code>만 하면 된다.</li>
</ol>
</blockquote>
<p><strong>핵심 로직 흐름</strong></p>
<blockquote>
</blockquote>
<pre><code class="language-text">q = deque()
&gt;
// 초기 상태에서 type=0(queue)인 값만 모음
for i in 0..n-1:
    if type[i] == 0:
        q.addLast(value[i])
&gt;
// 실제 출력은 가장 &quot;앞에서&quot; 빠져나오므로,
// 위에서 모은 queue 값들의 순서를 &quot;역방향&quot;으로 맞춰야 한다.
// (구현에서는 q에 넣는 순서를 뒤에서부터 넣거나, 앞에 addFirst로 넣는다)
&gt;
for each x in queries:
    q.addLast(x)
    print q.pollFirst()</code></pre>
<p><strong>Java 코드</strong></p>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(br.readLine());
        int[] type = new int[n];
        int[] val = new int[n];

        StringTokenizer st1 = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; n; i++) type[i] = Integer.parseInt(st1.nextToken());

        StringTokenizer st2 = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; n; i++) val[i] = Integer.parseInt(st2.nextToken());

        int m = Integer.parseInt(br.readLine());
        StringTokenizer st3 = new StringTokenizer(br.readLine());
        StringBuilder sb = new StringBuilder();

        Deque&lt;Integer&gt; q = new ArrayDeque&lt;&gt;();

        for (int i = n - 1; i &gt;= 0; i--) {
            if (type[i] == 0) q.addFirst(val[i]);
        }

        for (int i = 0; i &lt; m; i++) {
            int x = Integer.parseInt(st3.nextToken());
            q.addLast(x);
            sb.append(q.pollFirst()).append(&#39; &#39;);
        }

        System.out.print(sb);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1">방법 1</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(n × m)</li>
<li><strong>공간 복잡도</strong>: O(n)</li>
</ul>
<blockquote>
<h4 id="방법-2">방법 2</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(n + m)</li>
<li><strong>공간 복잡도</strong>: O(n)  (type=0인 값 개수만큼)</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>처음 실제 시뮬레이션 기반의 풀이에서 시간복잡도를 어떻게 더 줄여야 하는지 감이 잡히지 않았다.</li>
<li>매 쿼리마다 n개 구조를 끝까지 시뮬레이션하는 방식에서 병목이 생겼다.</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>전체를 그대로 시뮬레이션하지 말고, <strong>출력에 실제로 영향을 주는 요소만 남기는 관찰</strong>이 핵심이다.</li>
<li>이 문제는 <code>type=1(stack)</code>이 결과 흐름에서 <strong>사라지는 요소</strong>라서, <strong>type=0(queue)만 모아 한 번에 처리</strong>하면 된다.</li>
<li><code>Deque</code>로 <code>addLast / pollFirst</code>만 하면 쿼리당 O(1)로 끝난다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/24511">https://www.acmicpc.net/problem/24511</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천) :</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/12789">백준 - 12789번 도키도키 간식드리미</a></li>
<li><a href="https://www.acmicpc.net/problem/18258">백준 - 18258번 큐 2</a></li>
<li><a href="https://www.acmicpc.net/problem/10866">백준 - 10866번 덱</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천) :</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/1021">백준 - 1021번 회전하는 큐</a></li>
<li><a href="https://www.acmicpc.net/problem/1966">백준 - 1966번 프린터 큐</a></li>
<li><a href="https://www.hackerrank.com/challenges/queue-using-two-stacks/problem">HackerRank - Queue using Two Stacks</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Reading Note] The Birth of Deep CNNs: Why AlexNet Worked]]></title>
            <link>https://velog.io/@melon-chicken/Research-Note-The-Birth-of-Deep-CNNs-Why-AlexNet-Worked</link>
            <guid>https://velog.io/@melon-chicken/Research-Note-The-Birth-of-Deep-CNNs-Why-AlexNet-Worked</guid>
            <pubDate>Sun, 04 Jan 2026 15:19:01 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<blockquote>
<p>이 글은 딥러닝 역사에서 전환점으로 평가받는 AlexNet 논문을 읽으며,
이 모델이 왜 “작동했는가”를 정리하기 위해 작성되었습니다.</p>
</blockquote>
<p>성능 비교가 아니라, 당시 기술적 제약 속에서 어떤 선택들이 문제를 해결했는지를 중심으로 살펴보고자 합니다. 또한 이 글은 Standford University의 CS231n의 일부를 참고로 하였습니다. 따라서 Computer Vision에 대해 더 궁금하신 독자께서는 링크를 참고해주세요. <a href="https://cs231n.stanford.edu/schedule#:~:text=Date%20Description%20Lecturer%20Course%20Materials,Image%20Classification%20Problem%20Linear%20Classification"><strong>link</strong></a></p>
<h2 id="paper-information">Paper Information</h2>
<blockquote>
<p><strong>Title:</strong> ImageNet Classification with Deep Convolutional Neural Networks</p>
<p><strong>Authors:</strong> Alex Krizhevsky, Ilya Sutskever, Geoffrey E. Hinton</p>
<p><strong>Journal:</strong> Advances in Neural Information Processing Systems (<strong>NeurIPS 2012</strong>) <a href="https://papers.nips.cc/paper_files/paper/2012/hash/c399862d3b9d6b76c8436e924a68c45b-Abstract.html"><strong>link</strong></a></p>
</blockquote>
<p>arXiv ID: <strong>NFI</strong> </p>
<hr>
<h1 id="ⅰ-introduction">Ⅰ. Introduction</h1>
<blockquote>
<p><em>“To recognize the real world, how much capacity does a model really need?”</em></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/bd268fdf-3693-46c5-8c4b-93385c39bd0d/image.png" alt=""></p>
<figcaption style="text-align:center; font-size:15px; color:#808080;">Figure 2: An illustration of the architecture of our CNN (AlexNet), explicitly showing the delineation of responsibilities
between the two GPUs.</figcaption>

<h2 id="ⅰ1-데이터-규모와-문제-복잡성"><strong>Ⅰ.1. 데이터 규모와 문제 복잡성</strong></h2>
<p>이 논문에 대한 이야기를 하기 전, 먼저 이러한 모델이 나올 수 있었고 효과가 있었던 배경에 대해 설명하려 합니다.</p>
<p>디지털 사진이 막 도입된 시점 (1960s), 객체 탐지 (Object Recognition)은 상당히 어려운 분야였습니다. 대상이 어디있는가? 어디까지 대상으로 할 것인가?에 대한 Vision Representation에 대한 고전적인 질문과 함께 (Stages of Visual Representation, David Marr, 1970s) 아직은 디지털 데이터에 대해 정리하고 확립하는 과정이었습니다. </p>
<p>그 과정에서 나온 데이터 셋은 <a href="https://cs.nyu.edu/~yann/data/norb-v1.0/">NORB</a>와 <a href="https://www.kaggle.com/datasets/jessicali9530/caltech256">Caltech-101/256</a>,
<a href="https://www.cs.toronto.edu/~kriz/cifar.html">CIFAR-10/100</a> 등이 있습니다. 이들은 당시 기준으로는 표준적이고 잘 만들어진 데이터셋이지만 데이터의 규모는 객체 인식 연구를 하기에는 충분하지 않은 규모 (수만 장)였습니다.</p>
<p>물론 간단한 인식은 labrl-preserving transformation릏 통한 데이터 증강을 활용하여 이 규모에서도 꽤나 잘 이루어졌지만 문제는 가려짐, 배경 변화, 시점 변화 같은 상황의 경우 단순한 증강으로는 한계점이 있어 더 많은 데이터가 필요해졌습니다. </p>
<p>이때 등장한 것이 바로 <a href="https://www.image-net.org/about.php">ImageNet</a>이었습니다. ImageNet은 2009년 대규모 객체 인식을 위해 등장한 이미지 데이터셋으로. 한 객체 범주 (synset) 당 평균 1000개의 이미지라는 대규모의 데이터셋입니다. 이로써 객체 인식을 위한 데이터의 규모는 갖추어졌지만, 많은 데이터가 필요해짐에 따라 또한 모델의 용량도 이에 맞춰 더 커져야 했습니다.</p>
<hr>
<h2 id="ⅰ2-모델-용량capacity의-필요성"><strong>Ⅰ.2. 모델 용량(Capacity)의 필요성</strong></h2>
<p>데이터의 규모가 커진 만큼 데이터의 복잡도 (e.g. 시점 변화, 조명 변화, 배경 변화...)도 느러 연구진은 CNN을 선택하게 됩니다. CNN의 특성은 바로 다음 섹션에서 설명합니다.</p>
<p>이러한 배경에서 연구진들은 다음과 같은 선택을 통해 모델의 용량을 키워 AlexNet의 성능을 끌어올립니다.</p>
<blockquote>
<ul>
<li>약 6천만개의 파라미터</li>
</ul>
</blockquote>
<ul>
<li>5개의 Convolution Layer와 3개의 Fully Connected Layer</li>
<li>상대적으로 큰 Neural Network</li>
</ul>
<hr>
<h1 id="ⅱ-key-design-choices">Ⅱ. Key Design Choices</h1>
<blockquote>
<p><em>Architecture is not just structure; it is prior knowledge.</em></p>
</blockquote>
<h2 id="ⅱ1-convolution-neural-network-cnn">Ⅱ.1. Convolution Neural Network (CNN)</h2>
<p>CNN은 기존의 Neural Network에 기반하였기에 학습 가능한 weights와 bias를 가진 neuron을 가지고 있습니다. 하지만 가장 큰 변화점은 입력값이 이미지라는 가정하에 다양한 이미지의 특성을 구조에 녹일 수 있다는 점입니다. </p>
<p>다시 말해 <strong>&quot;이미지의 내부에는 인접한 픽셀 간의 강한 상관관계가 있다 (Locality)&quot;</strong>라는 가정과 <strong>&quot;이미지의 통계적 특성은 픽셀의 위치에 따라 크게 변하지 않는다 (Stationarity)&quot;</strong>라는 가정을 전제하에 두고 있습니다. </p>
<p>그렇기에 기존의 Nerual Network와는 다르게 더 적은 connection와 parameter를 가지고 더 쉽게 학습 가능하게 됩니다. 물론 이때 약간의 성능하락은 존재하지만 여전히 매력적인 선택지입니다.</p>
<p>하지만 그럼 왜 진작 사용하지 않았는가? 라고 묻는다면 이 모델을 실행할 하드웨어 자원이 부족했기 때문입니다. 하지만 연구진은 당시에 개발된 GPU (GTX 580 3GB GPU)를 CNN에 맞게 적용하여 사용해서 큰 CNN 모델에 대한 학습을 가능하게 합니다. 이를 연구진은 아래와 같이 말합니다.</p>
<blockquote>
<p>Luckily, current GPUs, <strong>paired with a highly-optimized implementation of 2D convolution</strong>, are powerful enough to facilitate the training of interestingly-large CNNs, and recent datasets such as ImageNet contain enough labeled examples to train such models without severe overfitting.</p>
</blockquote>
<p>하지만 연구진은 Convolution layer만 사용하지 않고 기존의 Fully connected network 역시 사용합니다. (It contains eight learned layers — five convolutional and three fully-connected.)</p>
<blockquote>
<p><strong>Fully-connected layer란?</strong>
한 레이어의 모든 뉴런이 바로 이전 레이어의 모든 뉴런과 연결되어 있는 레이어를 말합니다.</p>
<ol>
<li>convolutional layer</li>
</ol>
</blockquote>
<ul>
<li>국소 영역 (local receptive field)만 봄</li>
<li>가중치 공유 (weight sharing)</li>
<li>공간 구조 (spatial structure)를 유지</li>
</ul>
<hr>
<ol start="2">
<li>fully-connected layer</li>
</ol>
<ul>
<li>공간 구조를 더 이상 고려하지 않음</li>
<li>입력 전체를 하나의 벡터로 보고</li>
<li>모든 입력을 종합해서 판단</li>
</ul>
<hr>
<h2 id="ⅱ2-rectified-linear-units-relus-nonlinearity">Ⅱ.2. Rectified Linear Units (ReLUs) Nonlinearity</h2>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/142ffedd-4df5-479d-9fb2-624eeb77eb20/image.png" alt=""></p>
<figcaption style="text-align:center; font-size:15px; color:#808080;">Figure 1: A four-layer convolutional neural network with ReLUs (solid line) reaches a 25% training error rate on CIFAR-10 six times faster than an equivalent network with tanh neurons
(dashed line).</figcaption>

<p>기존의 $tanh$, sigmoid 계열의 활성화 함수는 깊은 네트워크 (입력값의 절댓값이 커질 수록) gradient saturation 문제를 유발하게 됩니다. (e.g. $sigmoid(x) = (1 + e^{-x})^{-1}$) 이런 문제를 해결하고자 연구진은 $f(x) = max(0,x)$ ReLU를 활성화 함수로 지정하여 이러한 saturation 문제를 해결합니다.</p>
<blockquote>
<p>Saturation은 왜 문제가 되는가?
Fig. 1에서 나타나듯이 Epoch에 따라 Training Error Rate를 보면 ReLU가 tanh에 비해 훨씬 빠른 학습 속도를 보여줍니다. 이 이유는 입력값의 절댓값이 커질 수록 출력이 거의 일정해지고 그렇기에 gradient가 매우 작아지며 학습이 느려지게 되기 때문입니다.</p>
</blockquote>
<p>이 ReLU를 통해 학습 속도는 대폭 향상되고, 큰 모델에서도 non-saturating한 활성 함수를 사용하기에 실험을 반복적으로 수행 가능하며, 이러한 깊은 CNN 학습이 현실적인 시간 내에 완료되도록 합니다.</p>
<hr>
<h2 id="ⅱ3-gpu-parallelization">Ⅱ.3. GPU parallelization</h2>
<p>연구진이 사용한 GTX 580 GPU는 3GB의 메모리 크기를 가지고 있기에 1,200,000개의 훈련 데이터를 학습하기에 적합한 Network를 돌리기에는 한 GPU 가지고는 모자랐습니다. 그렇기에 연구진의 선택은 두개의 GPU를 통해 병렬 연산을 진행하는 방식을 선택했습니다. </p>
<p>단순히 Kernel이나 Neuron을 절반으로 나누어 각각 넣는게 아니라 GPU간의 communication을 특정 레이어에서만 전체 연결을 허용합니다. 예를 들어 3번 레이어의 Kernel은 2번 레이어의 모든 Kernel로부터 정보를 받습니다. (이는 두 GPU에서의 값을 모두 받는다는 것을 의미합니다. Communication 발생) </p>
<p>하지만 4번 레이어의 Kernel의 경우 동일 GPU에 있는 3번 레이어의 데이터만 입력값으로 받게 됩니다. 이렇게 함으로써 개별 GPU가 소화할 수 있는 적정한 연산량 안에서의 communication 양을 세밀하게 조절할 수 있게 해줍니다.</p>
<p>이러한 구조는 <strong>Columnar CNN</strong>과 상당히 유사하지만 AlexNet은 column이 독립적이지 않다라는 차이점이 있습니다. </p>
<p>정리하자면 이러한 GPU 병렬화는 개별 GPU로는 수행하지 못하는 양의 연산을 수행하고, 단일 Convolution layer에서 개별 GPU로만 구현했을 때보다 약간 빠르게 수행합니다. </p>
<hr>
<h2 id="ⅱ4-reducing-overfitting">Ⅱ.4. Reducing Overfitting</h2>
<p>물론 모델이 커짐에 따라 너무나도 학습 데이터를 <strong>잘</strong> 학습한 나머지 발생하는 과적합은 필연적이게 됩니다. 이러한 과적합을 연구진은 계속해서 언급했고, 아래의 두가지 전략을 통해 과적합을 줄입니다.</p>
<h3 id="ⅱ41-data-augmentation">Ⅱ.4.1. Data Augmentation</h3>
<p>AlexNet에서는 두가지 방법으로 데이터 증강을 진행합니다. 첫번째는 이미지를 임의로 224 by 224 patch로 잘라내고 좌우 반전을 하는 방식으로 증강을 이루는 것입니다. 이러한 데이터 증강은 훈련 데이터셋의 사이즈를 키우는 방식으로 과적합을 줄여줍니다.</p>
<p>두번째 방법으로는 학습 이미지의 RGB 채널의 강도를 PCA (Primary Component Analysis)를 통해 도출된 principal component를 곱하는 방식으로 조정을 해줍니다. 또한 RGB pixel 값들의 eigenvector와 eigenvalue를 활용해서 단순히 색을 무작위로 변화하는 방식으로 증강한 것이 아니라 현실적인 조명 변화를 흉내내며 증강하게 됩니다.</p>
<p>그래서 <strong>Generating image translations and horizontal reflec
tions</strong>와 <strong>Altering the intensities of the RGB channels in
training images</strong> 두가지 데이터 증강 전략을 통해 과적합을 방지할 수 있었다고 합니다. </p>
<h3 id="ⅱ42-dropout">Ⅱ.4.2. Dropout</h3>
<p>당시 비교적 최근에 소개된 dropout 전략이 AlexNet에 적용된 두번째 과적합 방지 전략입니다. Dropout 전략은 각각의 히든 뉴런의 출력값에 대해서 일정한 확률 (AlexNet에서는 0.5)로 0이 되게끔 설정하는 전략입니다. 이러한 방식으로 누락된 (&quot;Dropped out&quot;) 뉴런들은 다음 레이어에 영향을 미치지 않아 back propagation에도 참여하지 못하게 됩니다. </p>
<p>이렇게 함으로써 학습을 진행할 때마다 구조가 조금씩 다른 네트워크를 학습하는 동시에 weight 값은 공유를 하기에 샘플마다 다른 개별 특징에 초점이 맞춰지는게 아니라 더 일반적인 공통된 특징에 더 집중되어 학습을 진행하게 됩니다. 이렇게 진행함으로써 과적합을 방지할 수 있게 됩니다.</p>
<hr>
<h1 id="ⅲ-limitations--conclusion">Ⅲ. Limitations &amp; Conclusion</h1>
<p>결론적으로 AlexNet은 당시의 GPU 발전과 함께 대규모의 깊은 CNN을 구현했고 그 해 2012년 Large Scale Visual Recognition Challenge (LSVRC)에서 top-5 test error rate로 15.3%로 두번째로 낮았던 26.2%보다 훨씬 뛰어난 성능을 보였습니다.</p>
<p>물론 실험을 단순하게 하기 위해 Unsupervised pre-traing을 사용하지 않았으므로 성능을 더 끌어올릴 수 있는 여지가 있다는 점, 인간의 시각 시스템에 비해서는 아직 너무나도 작은 모델이라는 연구진의 자기 평가가 있었지만 그럼에도 불구하고 깊은 CNN 하나만으로 순수 지도학습에서 당시 최고 성능을 달성했다는 점에서 의미있는 시도였습니다.</p>
<hr>
<blockquote>
<p>오늘은 Computer Vision에서 굵직한 마일 스톤 중 하나인 AlexNet에 대한 논문을 읽어보았습니다. 이번에도 긴 글 읽어주셔서 감사합니다.</p>
<p>의견이나 질문이 있다면 댓글로 남겨주시기 바랍니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 1929번 소수 구하기]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1929-%EC%86%8C%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1929-%EC%86%8C%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 03 Jan 2026 05:28:39 GMT</pubDate>
            <description><![CDATA[<h1 id="1929-소수-구하기">[1929] 소수 구하기</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2026-01-03</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/27a8f1f8-7866-4e7d-ad33-d7fa6eb463b6/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 수학, 에라토스테네스의 체, 구현</li>
<li><strong>요구사항</strong>: 입력된 구간 <code>[n, m]</code> 안에 존재하는 <strong>모든 소수</strong>를 한 줄씩 출력해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>boolean[]</code> : 합성수 여부 (표시 배열)</li>
<li><code>StringBuilder</code> : 출력 누적</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>에라토스테네스의 체 (Sieve of Eratosthenes)</li>
<li>배수 마킹 (Marking multiples)</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>소수 (prime number)</li>
<li>합성수 (composite number)</li>
<li>배수 (multiple)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어">풀이 아이디어</h2>
<blockquote>
<ol>
<li><strong>문제 분해</strong></li>
</ol>
</blockquote>
<ul>
<li><code>0</code>과 <code>1</code>은 소수가 아니므로 먼저 합성수 취급(<code>true</code>)로 표시한다.</li>
<li><code>2</code>부터 시작해, 아직 체크되지 않은 수(소수 후보)를 만나면 그 수의 배수들을 합성수(<code>true</code>)로 표시한다.</li>
<li>최종적으로 <code>arr[i] == false</code>인 수만 출력한다(소수만 남김).<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li><strong>핵심 로직 흐름</strong><blockquote>
</blockquote>
<pre><code class="language-text">arr[0] = arr[1] = true  // 소수 아님(합성수 취급)
for i = 2 .. m:
    if arr[i] == true: continue
    for j = 2 while i*j &lt;= m:
        arr[i*j] = true  // 합성수 표시
&gt;
for i = n .. m:
    if arr[i] == false: print i</code></pre>
<blockquote>
</blockquote>
</li>
<li><strong>예외 처리</strong><blockquote>
</blockquote>
<ul>
<li><code>0</code>, <code>1</code>은 소수가 아니므로 반드시 제외한다.</li>
<li>boolean 배열의 기본값이 <code>false</code>이므로, “표시되면 합성수(true)” 방식으로 관리한다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    public static boolean[] arr;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        arr = new boolean[m+1];
        StringBuilder sb = new StringBuilder();
        makeSlieve();

        for(int i = n; i &lt;= m; i++){
            if(!arr[i]) sb.append(i).append(&quot;\n&quot;);
        }
        System.out.println(sb);
    }
    public static void makeSlieve(){
        arr[0] = true;
        arr[1] = true;
        for(int i = 2; i &lt; arr.length; i++){
            if(arr[i]) continue;
            for(int j = 2; i*j &lt; arr.length; j++){
                arr[i*j] = true;
            }
        }
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<ul>
<li><p><strong>시간 복잡도</strong>: 대략 <code>O(M log log M)</code> 수준 (에라토스테네스의 체)</p>
<ul>
<li>구현상 <code>i*j</code> 형태로 마킹하므로 상수는 커질 수 있으나, 기본 아이디어는 체 방식이다.</li>
</ul>
</li>
<li><p><strong>공간 복잡도</strong>: <code>O(M)</code> (<code>boolean[m+1]</code>)</p>
</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li><code>boolean</code> 배열의 기본값이 무엇인지 몰라서 혼란이 있었다.</li>
<li>처음에는 <strong>소수만 true</strong>를 기대했지만 실제로는 기본값이 전부 <code>false</code>라서 의도와 반대로 동작하는 문제가 생겼다.</li>
<li>그래서 <strong>합성수만 true로 표시하고, 끝까지 false로 남은 값이 소수</strong>가 되도록 방향을 바꿔 해결했다.</li>
<li>참고 블로그를 통해 boolean 기본값이 <code>false</code>임을 확인했다.</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>Java에서 <code>boolean[]</code>의 기본값은 <code>false</code>라서, 보통은 <strong>합성수만 true로 마킹</strong>하는 형태가 구현이 편하다.</li>
<li>배수 마킹은 <code>j = i*i</code>부터 시작하거나 $i &lt;= \sqrt{m}$까지만 돌리면 더 빠르게 만들 수 있다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/1929">https://www.acmicpc.net/problem/1929</a></li>
<li>참고 블로그/깃허브: <a href="https://nanarin.tistory.com/53">https://nanarin.tistory.com/53</a></li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/1978">백준 - 1978번 소수 찾기</a></li>
<li><a href="https://www.acmicpc.net/problem/4948">백준 - 4948번 베르트랑 공준</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/17103">백준 - 17103번 골드바흐 파티션</a></li>
<li><a href="https://www.hackerrank.com/challenges/30-running-time-and-complexity/problem">HackerRank - Day 25: Running Time and Complexity</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 4134번 다음 소수]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-4134%EB%B2%88-%EB%8B%A4%EC%9D%8C-%EC%86%8C%EC%88%98</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-4134%EB%B2%88-%EB%8B%A4%EC%9D%8C-%EC%86%8C%EC%88%98</guid>
            <pubDate>Sat, 03 Jan 2026 05:00:53 GMT</pubDate>
            <description><![CDATA[<h1 id="4134-다음-소수">[4134] 다음 소수</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2026-01-03</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/11f8c3ca-2bdf-4021-be60-129f1a3b3227/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 수학, 소수 판정, 브루트포스</li>
<li><strong>요구사항</strong>: 각 테스트케이스마다 입력된 수 <code>n</code> 이상에서 <strong>가장 작은 소수</strong>를 찾아 출력해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>StringBuilder</code> : 여러 줄 출력 누적</li>
<li><code>BufferedReader</code> : 빠른 입력</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>소수 판정(Prime Check)</li>
<li><code>n</code>부터 1씩 증가시키며 첫 소수 탐색</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>소수(prime number)</li>
<li>√n 까지 나눗셈 검사</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어">풀이 아이디어</h2>
<blockquote>
<ol>
<li><strong>문제 분해</strong></li>
</ol>
</blockquote>
<ul>
<li>각 입력 <code>target</code>에 대해 <code>current = target</code>으로 시작한다.</li>
<li><code>current</code>가 소수인지 확인하고, 아니면 <code>current++</code> 하며 반복한다.</li>
<li>처음으로 소수 판정을 통과한 <code>current</code>를 출력한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li><strong>핵심 로직 흐름</strong><blockquote>
</blockquote>
<pre><code class="language-text">for i in 1..T:
    current = target
    while true:
        if isPrime(current):
            print current
            break
        current++</code></pre>
<blockquote>
</blockquote>
</li>
<li><strong>예외 처리</strong><blockquote>
</blockquote>
<ul>
<li><code>0</code>, <code>1</code>은 소수가 아니므로 <code>false</code>로 처리한다.</li>
<li><code>2</code>는 소수이므로 <code>true</code>로 처리한다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="코드">코드</h2>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        long target;
        StringBuilder sb = new StringBuilder();

        for(int i = 0; i &lt; n; i++){
            String s = br.readLine();
            target = Long.parseLong(s);
            long current = target;
            while(true){
                if(isPrime(current)){
                    sb.append(current).append(&quot;\n&quot;);
                    break;
                }
                current++;
            }
        }
        System.out.print(sb);
    }
    public static boolean isPrime(long n){
        if(n == 1 || n == 0) return false;
        if(n == 2) return true;
        for(long i = 2; i*i &lt;= n; i++){
            if(n % i == 0){
                return false;
            }
        }
        return true;
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<ul>
<li><p><strong>시간 복잡도</strong>: 최악의 경우 <code>O(K × √M)</code></p>
<ul>
<li><code>K</code> = 소수를 찾을 때까지 증가한 횟수</li>
<li><code>M</code> = 검사 중인 수 크기</li>
</ul>
</li>
<li><p><strong>공간 복잡도</strong>: <code>O(1)</code> (입력/출력 버퍼 제외)</p>
</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li><code>0</code>, <code>1</code>이 소수가 아니라는 예외를 놓치면 오답이 나기 쉽다.</li>
<li><code>target</code>부터 시작해서 다음 소수를 찾는 반복 구조에서, 소수 판정 함수가 정확해야 한다.</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>소수 판정은 <code>2</code>부터 <code>√n</code>까지만 나눠보면 된다.</li>
<li>밀러-라빈 테스트를 구현하면 더 효율적인 검증이 가능하지만 아직 어려워 이해하지 못했다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/4134">https://www.acmicpc.net/problem/4134</a></li>
<li>참고 블로그/깃허브: <ul>
<li><a href="https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test#Testing_against_small_sets_of_bases">Miller–Rabin primality test</a></li>
<li><a href="https://velog.io/@frog_slayer/Miller-Rabin-Primality-Test">[Algo] 밀러-라빈 소수 판별법(Miller-Rabin Primality Test)</a></li>
</ul>
</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/1929">백준 - 1929번 소수 구하기</a></li>
<li><a href="https://www.acmicpc.net/problem/4948">백준 - 4948번 베르트랑 공준</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/17103">백준 - 17103번 골드바흐 파티션</a></li>
<li><a href="https://www.hackerrank.com/challenges/30-running-time-and-complexity/problem">HackerRank - Day 25: Running Time and Complexity</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 2485번 가로수]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-2485%EB%B2%88-%EA%B0%80%EB%A1%9C%EC%88%98</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-2485%EB%B2%88-%EA%B0%80%EB%A1%9C%EC%88%98</guid>
            <pubDate>Sat, 03 Jan 2026 03:15:58 GMT</pubDate>
            <description><![CDATA[<h1 id="2485-가로수">[2485] 가로수</h1>
<blockquote>
<p>난이도: ★★★☆☆  •  solved on: 2026-01-03</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/d7fbc60c-606f-44f4-8a17-039d05b1c2ee/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 수학, 최대공약수(GCD)</li>
<li><strong>요구사항</strong>: 기존 가로수 위치를 기준으로 모든 가로수 사이의 간격이 동일해지도록 만들 때, <strong>추가로 심어야 하는 가로수의 개수</strong>를 구해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>long[]</code> : 가로수 좌표 및 간격 저장</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>정렬</li>
<li>유클리드 호제법 (Euclidean Algorithm)</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>인접 간격</li>
<li>간격들의 최대공약수 (GCD)</li>
<li><code>(gap / gcd) - 1</code></li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--현재-가로수의-위치-정보를-저장">방법 1 : 현재 가로수의 위치 정보를 저장</h3>
<blockquote>
<ol>
<li><strong>문제 분해</strong></li>
</ol>
</blockquote>
<ul>
<li>가로수 위치를 배열에 저장한다.</li>
<li>인접한 가로수 사이의 간격 배열 <code>gaps</code>를 만든다.</li>
<li>모든 간격의 GCD를 구해 <code>currentGcd</code>로 저장한다.</li>
<li>첫 위치부터 마지막 위치까지 <code>currentGcd</code> 간격으로 하나씩 훑으면서,
기존 가로수가 없는 위치를 카운트한다.<ol start="2">
<li><strong>핵심 로직 흐름</strong><pre><code class="language-text">gaps 생성
gaps 전체 GCD 계산 → currentGcd
start부터 end까지 currentGcd 간격으로 순회
기존 위치에 없으면 count++</code></pre>
<blockquote>
</blockquote>
</li>
<li><strong>한계</strong><blockquote>
</blockquote>
</li>
</ol>
</li>
<li>좌표 범위가 커질 경우, while 문 순회 횟수가 불필요하게 많아질 수 있다.</li>
<li>실제로는 계산으로 바로 구할 수 있는 값을 시뮬레이션으로 처리하고 있다.</li>
</ul>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        long[] positions = new long[n];
        long[] gaps = new long[n-1];
        long currentGcd = 1;

        for(int i=0;i&lt;n;i++){
            positions[i] = Long.parseLong(br.readLine());
            if(i &gt; 0){
                gaps[i-1] = positions[i] - positions[i-1];
            }
            if(i == n-1){
                break;
            }
        }

        Arrays.sort(positions);

        for(int i=0;i&lt;n-1;i++){
            if(i == 1){
                currentGcd = gcd(gaps[i-1], gaps[i]);
            } else if (i &gt; 1){
                currentGcd = gcd(currentGcd, gaps[i]);
            }
        }

        int i = 0;
        int j = 0;
        long start = positions[0];
        int count = 0;

        while((start + currentGcd * j) &lt;= positions[positions.length-1]){
            if(positions[i] == start + currentGcd * j){
                i ++;
                j ++;
                continue;
            } else {
                count++;
                j ++;
            }
        }
        System.out.println(count);
    }

    public static long gcd(long a,long b){
        while(b != 0){
            long t = a % b;
            a = b;
            b = t;
        }
        return a;
    }
}</code></pre>
<hr>
<h3 id="방법-2--위치-정보-대신-간격-합산에-집중">방법 2 : 위치 정보 대신 간격 합산에 집중</h3>
<blockquote>
<ol>
<li><strong>핵심 관찰</strong></li>
</ol>
</blockquote>
<ul>
<li>최종적으로 모든 가로수 사이의 간격은
“인접 간격들의 최대공약수(GCD)”가 된다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li><strong>계산 방식</strong><blockquote>
</blockquote>
<ul>
<li>어떤 간격 <code>gap</code>이 있고 최종 간격이 <code>gcd</code>라면,</li>
<li>해당 구간은 <code>gap / gcd</code>개의 구간으로 나뉜다.</li>
<li>이미 양 끝에 나무가 있으므로, 추가로 필요한 나무 수는 <code>(gap / gcd) - 1</code>이다.</li>
</ul>
</li>
<li><strong>전체 답</strong><blockquote>
</blockquote>
<ul>
<li>모든 인접 간격에 대해 위 값을 더한 합이다.</li>
</ul>
</li>
<li><strong>핵심 로직 흐름</strong><blockquote>
</blockquote>
<pre><code class="language-text">positions 정렬
모든 인접 gap의 GCD 계산 → g
answer = Σ (gap / g - 1)</code></pre>
</li>
</ol>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    static long gcd(long a, long b) {
        while (b != 0) {
            long t = a % b;
            a = b;
            b = t;
        }
        return a;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());

        long[] pos = new long[N];
        for (int i = 0; i &lt; N; i++) {
            pos[i] = Long.parseLong(br.readLine());
        }

        Arrays.sort(pos);

        long g = pos[1] - pos[0];
        for (int i = 2; i &lt; N; i++) {
            g = gcd(g, pos[i] - pos[i - 1]);
        }

        long answer = 0;
        for (int i = 1; i &lt; N; i++) {
            long gap = pos[i] - pos[i - 1];
            answer += (gap / g) - 1;
        }

        System.out.println(answer);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1">방법 1</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: $O(N log N + R / G)$ (R: 좌표 범위, G: 최종 간격)</li>
<li><strong>공간 복잡도</strong>: $O(N)$</li>
</ul>
<blockquote>
<h4 id="방법-2">방법 2</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: $O(N log N)$</li>
<li><strong>공간 복잡도</strong>: $O(N)$</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>구현 자체는 단순했지만, <strong>시뮬레이션을 수식으로 치환할 수 있다는 발상</strong>에 도달하기 어려웠다.</li>
<li>“모든 위치를 직접 세어보지 않아도 된다”는 점을 떠올리지 못해 시간복잡도 개선 방향을 잡는 데 시간이 걸렸다.</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>동일 간격을 만드는 문제는 대부분 <strong>간격들의 GCD</strong>로 귀결된다.</li>
<li>좌표 문제에서 “하나씩 훑는 방식”이 보이면, <strong>간격 단위로 계산할 수 있는지</strong> 먼저 의심해보는 것이 좋다. (위치 정보의 필요성에 대한 의심)</li>
<li>추가 개수는 시뮬레이션보다
$Σ (gap / gcd - 1)$ 형태의 수식이 훨씬 안전하고 빠르다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/2485">https://www.acmicpc.net/problem/2485</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/2609">백준 - 2609번 최대공약수와 최소공배수</a></li>
<li><a href="https://www.acmicpc.net/problem/2981">백준 - 2981번 검문</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/14476">백준 - 14476번 최대공약수 하나 빼기</a></li>
<li><a href="https://www.acmicpc.net/problem/1735">백준 - 1735번 분수 합</a>
`</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 1934번 최소공배수]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1934%EB%B2%88-%EC%B5%9C%EC%86%8C%EA%B3%B5%EB%B0%B0%EC%88%98</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1934%EB%B2%88-%EC%B5%9C%EC%86%8C%EA%B3%B5%EB%B0%B0%EC%88%98</guid>
            <pubDate>Fri, 02 Jan 2026 09:52:39 GMT</pubDate>
            <description><![CDATA[<h1 id="1934-최소공배수">[1934] 최소공배수</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2026-01-02</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/08f29208-415f-4db0-89be-7a9b62d72a6f/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 유클리드 호제법, 수학</li>
<li><strong>요구사항</strong>: 테스트 케이스마다 두 자연수의 <strong>최소공배수(LCM)</strong> 를 출력해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>StringTokenizer</code>, <code>StringBuilder</code></li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>최대공약수 (GCD)</li>
<li>최소공배수 (LCM) 관계식: $LCM(a,b) = (a / GCD(a,b)) * b$</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>유클리드 호제법 (Euclidean Algorithm)</li>
<li>공약수/공배수</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--공통-인수-탐색">방법 1 : 공통 인수 탐색</h3>
<blockquote>
<ol>
<li>문제 분해</li>
</ol>
</blockquote>
<ul>
<li>두 수의 공약수를 작은 값부터 나눠가며 공통 인수를 <code>result</code>에 누적한다.</li>
<li>나눠 떨어지면 <code>a</code>, <code>b</code>를 그 인수로 나누고, 나눠 떨어지지 않을 때만 <code>divisor++</code> 한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">result = 1
divisor = 2
while divisor &lt; a or divisor &lt; b:
    if a%divisor==0 and b%divisor==0:
        result *= divisor
        a /= divisor
        b /= divisor
    else:
        divisor++
result *= a*b</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>한 수가 다른 수의 배수인 경우, 더 큰 수가 LCM이므로 바로 출력한다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringBuilder sb = new StringBuilder();
        int a = 0;
        int b = 0;
        int result = 1;

        for(int i = 0; i &lt; n; i++){
            String[] lines = br.readLine().split(&quot; &quot;);

            a = Integer.parseInt(lines[0]);
            b = Integer.parseInt(lines[1]);
            result = 1;

            if(a%b == 0 || b%a == 0){
                sb.append(Math.max(a,b)).append(&quot;\n&quot;);
                continue;
            }
            int min = Math.min(a, b);
            int divisor = 2;
            while(divisor &lt; a || divisor &lt; b){
                if(a%divisor==0 &amp;&amp; b%divisor==0){
                    result *= divisor;
                    a /= divisor;
                    b /= divisor;
                } else {
                    divisor++;
                }
            }

            result *= a * b;
            sb.append(result).append(&quot;\n&quot;);
        }
        System.out.println(sb);
    }
}</code></pre>
<hr>
<h3 id="방법-2--유클리드-호제법으로-gcd-→-lcm">방법 2 : 유클리드 호제법으로 GCD → LCM</h3>
<blockquote>
<ol>
<li>문제 분해</li>
</ol>
</blockquote>
<ul>
<li>최소공배수는 공약수를 “직접” 모두 찾기보다, <strong>최대공약수(GCD)</strong> 만 구하면 바로 계산할 수 있다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">gcd(a,b):
    while b != 0:
        t = a % b
        a = b
        b = t
    return a
&gt;
lcm = a / gcd(a,b) * b</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>곱이 커질 수 있으므로 <code>long</code>을 사용한다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.*;

class Main {
    static long gcd(long a, long b) {
        while (b != 0) {
            long t = a % b;
            a = b;
            b = t;
        }
        return a;
    }

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int t = Integer.parseInt(br.readLine());
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i &lt; t; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            long a = Long.parseLong(st.nextToken());
            long b = Long.parseLong(st.nextToken());

            long g = gcd(a, b);
            long l = (a / g) * b;   // overflow 방지를 위해 a/g 먼저 수행
            sb.append(l).append(&#39;\n&#39;);
        }
        System.out.print(sb);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1-">방법 1 :</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: 최악 O(max(a,b))에 가깝게 커질 수 있다. (작은 약수부터 전부 시도하는 구조)</li>
<li><strong>공간 복잡도</strong>: O(1)</li>
</ul>
<blockquote>
<h4 id="방법-2-">방법 2 :</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(log(min(a,b))) (유클리드 호제법)</li>
<li><strong>공간 복잡도</strong>: O(1)</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>처음에는 <code>for (divisor++)</code>로 약수를 검사했는데, 이 방식은 <strong>같은 소수가 여러 번(예: p²)</strong> 나눠지는 경우를 연속으로 반영하기 어렵다는 문제가 있었다.</li>
<li>그래서 “약수를 찾지 못할 때만 ++ 한다”는 방식으로 <code>while</code>로 바꿔 중복 약수를 처리하도록 수정했다.</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>최소공배수는 공약수를 직접 누적하기보다 <strong>GCD만 구해도</strong> <code>LCM = a / GCD * b</code>로 바로 계산한다.</li>
<li>유클리드 호제법은 구현이 짧고 입력 범위가 커져도 안정적으로 빠르다.</li>
<li>곱셈이 들어가는 문제는 <code>int</code> 대신 <code>long</code>을 먼저 떠올리는 습관이 중요하다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/1934">https://www.acmicpc.net/problem/1934</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/2609">백준 - 2609번 최대공약수와 최소공배수</a></li>
<li><a href="https://www.acmicpc.net/problem/2981">백준 - 2981번 검문</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/3036">백준 - 3036번 링</a></li>
<li><a href="https://www.hackerrank.com/challenges/euclid-algorithm/problem">HackerRank - Euclid&#39;s Algorithm</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 1269번 대칭 차집합]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1269%EB%B2%88-%EB%8C%80%EC%B9%AD-%EC%B0%A8%EC%A7%91%ED%95%A9</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1269%EB%B2%88-%EB%8C%80%EC%B9%AD-%EC%B0%A8%EC%A7%91%ED%95%A9</guid>
            <pubDate>Thu, 01 Jan 2026 11:22:36 GMT</pubDate>
            <description><![CDATA[<h1 id="1269-대칭-차집합">[1269] 대칭 차집합</h1>
<blockquote>
<p>난이도: ★☆☆☆☆  •  solved on: 2026-01-01</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/b8b50c2f-9215-4dad-a411-8ef2aa17c8b7/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 집합(Set), 해시(Hash)</li>
<li><strong>요구사항</strong>: 두 집합 A, B에 대해 <strong>대칭 차집합</strong> $((A-B) ∪ (B-A))$의 <strong>원소 개수</strong>를 출력해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>HashSet</code> : 원소 존재 여부를 평균 O(1)로 검사한다.</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>집합 연산의 크기 계산</li>
<li>교집합 개수 세기</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>대칭 차집합 (symmetric difference)</li>
<li>교집합 (intersection)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--합집합을-만들고-교집합-개수만큼-빼기">방법 1 : 합집합을 만들고 교집합 개수만큼 빼기</h3>
<blockquote>
<ol>
<li>문제 분해</li>
</ol>
</blockquote>
<ul>
<li>A, B를 각각 <code>HashSet</code>에 넣는다.</li>
<li>A∪B 역할을 하는 <code>setAorB</code>를 만든다.</li>
<li><code>setAorB</code>를 순회하며 A와 B에 모두 있으면(교집합) <code>count++</code> 한다.</li>
<li>최종 답을 <code>|A∪B| - |A∩B|</code> 로 계산한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">setAorB = A ∪ B
count = |A ∩ B|
answer = |setAorB| - count</code></pre>
</li>
</ol>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br =  new BufferedReader(new InputStreamReader(System.in));
        String[] nums = br.readLine().split(&quot; &quot;);
        int n = Integer.parseInt(nums[0]);
        int m = Integer.parseInt(nums[1]);
        int count = 0;

        HashSet&lt;String&gt; setA = new HashSet&lt;&gt;(n);
        HashSet&lt;String&gt; setB = new HashSet&lt;&gt;(m);
        HashSet&lt;String&gt; setAorB = new HashSet&lt;&gt;();
        StringTokenizer stA = new StringTokenizer(br.readLine());
        StringTokenizer stB = new StringTokenizer(br.readLine());

        while(stA.hasMoreTokens()){
            String temp = stA.nextToken();
            setA.add(temp);
            setAorB.add(temp);
        }

        while(stB.hasMoreTokens()){
            String temp = stB.nextToken();
            setB.add(temp);
            setAorB.add(temp);
        }

        for(String a : setAorB){
            if(setA.contains(a)&amp;&amp;setB.contains(a)){
                count++;
            }
        }

        System.out.println(setAorB.size()-count);
    }
}</code></pre>
<hr>
<h3 id="방법-2--교집합만-세서-n--m---2common로-바로-계산">방법 2 : 교집합만 세서 <code>n + m - 2*common</code>로 바로 계산</h3>
<blockquote>
<ol>
<li>개선 포인트</li>
</ol>
</blockquote>
<ul>
<li>대칭 차집합 크기 = (|A-B| + |B-A|) 이고, 이는 (|A| + |B| - 2|A∩B|) 로 정리된다.</li>
<li>따라서 <strong>합집합 set을 따로 만들 필요가 없다</strong>.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">common = 0
A를 set에 저장
B를 읽으면서 A에 있으면 common++
answer = n + m - 2*common</code></pre>
</li>
<li>추가 개선<blockquote>
</blockquote>
<ul>
<li>입력이 자연수이므로 <code>HashSet&lt;Integer&gt;</code>로 처리하는 편이 의미상 맞고, 변환 비용도 줄인다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.*;

class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());

        HashSet&lt;Integer&gt; setA = new HashSet&lt;&gt;(n);

        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; n; i++) {
            setA.add(Integer.parseInt(st.nextToken()));
        }

        int common = 0;
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; m; i++) {
            int x = Integer.parseInt(st.nextToken());
            if (setA.contains(x)) common++;
        }

        int answer = n + m - 2 * common;
        System.out.println(answer);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1">방법 1</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N + M) 평균 (해시 연산 가정)</li>
<li><strong>공간 복잡도</strong>: O(N + M) (<code>setA</code>, <code>setB</code>, <code>setAorB</code> 저장)</li>
</ul>
<blockquote>
<h4 id="방법-2">방법 2</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N + M) 평균</li>
<li><strong>공간 복잡도</strong>: O(N) (<code>setA</code>만 유지)</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>없음</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>대칭 차집합 크기는 <strong>$n + m - 2 * |A∩B|$</strong> 로 바로 계산하는 편이 구현과 메모리 측면에서 더 단순하다.</li>
<li>이 문제는 최대 200,000개 원소까지 가능하므로, 이중 루프 대신 <strong>해시 기반 포함 검사</strong>로 풀어야 한다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/1269">https://www.acmicpc.net/problem/1269</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/14425">백준 - 14425번 문자열 집합</a></li>
<li><a href="https://www.acmicpc.net/problem/1764">백준 - 1764번 듣보잡</a></li>
<li><a href="https://www.acmicpc.net/problem/7785">백준 - 7785번 회사에 있는 사람</a></li>
<li><a href="https://www.acmicpc.net/problem/10816">백준 - 10816번 숫자 카드 2</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천):</p>
<ul>
<li><a href="https://www.hackerrank.com/challenges/no-idea/problem">HackerRank - No Idea!</a></li>
<li><a href="https://www.hackerrank.com/challenges/two-strings/problem">HackerRank - Two Strings</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 1764번 듣보잡]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1764%EB%B2%88-%EB%93%A3%EB%B3%B4%EC%9E%A1</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1764%EB%B2%88-%EB%93%A3%EB%B3%B4%EC%9E%A1</guid>
            <pubDate>Thu, 01 Jan 2026 04:19:24 GMT</pubDate>
            <description><![CDATA[<h1 id="1764-듣보잡">[1764] 듣보잡</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2026-01-01</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/66eb35ad-5b42-4a47-b6fe-45f0c6193a66/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 해시 (Hash), 문자열, 정렬</li>
<li><strong>요구사항</strong>: 듣도 못한 사람과 보도 못한 사람의 <strong>교집합</strong>을 찾아 <strong>사전식(오름차순)으로 출력해야 한다</strong>.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>HashMap&lt;String, Integer&gt;</code> / <code>HashSet&lt;String&gt;</code></li>
<li><code>ArrayList&lt;String&gt;</code> : 교집합 결과 저장</li>
<li><code>Collections.sort()</code> : 사전식 정렬</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>해시를 이용한 <strong>중복/존재 여부 체크</strong></li>
<li>교집합 추출 후 정렬</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>교집합 (intersection)</li>
<li>사전식 정렬 (lexicographical order)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--hashmap-카운팅으로-교집합-판별">방법 1 : HashMap 카운팅으로 교집합 판별</h3>
<blockquote>
<ol>
<li>문제 분해</li>
</ol>
</blockquote>
<ul>
<li>듣도 못한 사람 N명을 <code>map</code>에 넣고 값 1로 기록한다.</li>
<li>보도 못한 사람 M명을 입력받아 <code>map.getOrDefault(name, 0) + 1</code>로 누적한다.</li>
<li>최종적으로 값이 2인 키가 교집합(듣보잡)이므로 리스트에 담는다.</li>
<li>리스트를 사전식으로 정렬한 뒤 개수와 이름들을 출력한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">map에 N명은 1로 저장
M명을 읽으며 map[name] += 1
map 전체 순회하며 value==2인 key만 list에 저장
list 정렬 후 출력</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>교집합이 0명일 수도 있으므로, 출력 형식을 그대로 유지한다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] s = br.readLine().split(&quot; &quot;);
        int n =  Integer.parseInt(s[0]);
        int m = Integer.parseInt(s[1]);
        HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
        int total = 0;
        StringBuilder sb = new StringBuilder();
        ArrayList&lt;String&gt; list = new ArrayList&lt;&gt;();

        for(int i = 0; i &lt; n; i++){
            map.put(br.readLine(), 1);
        }

        for(int i = 0; i &lt; m; i++){
            String name = br.readLine();
            map.put(name, map.getOrDefault(name, 0) + 1);
        }

        for(String key : map.keySet()){
            if(map.get(key)==2){
                total++;
                list.add(key);
            }
        }
        System.out.println(total);

        Collections.sort(list);

        for(int i = 0; i &lt; list.size(); i++){
            sb.append(list.get(i)).append(&quot;\n&quot;);
        }

        System.out.println(sb);

    }
}</code></pre>
<hr>
<h3 id="방법-2--hashset으로-존재-확인-후-교집합만-수집">방법 2 : HashSet으로 존재 확인 후 교집합만 수집</h3>
<blockquote>
<ol>
<li>개선 포인트</li>
</ol>
</blockquote>
<ul>
<li><code>HashMap</code>으로 전체를 카운팅하지 않고, <strong>듣도 목록을 Set에 저장</strong>한 뒤
보도 입력을 읽으면서 <code>set.contains(name)</code>인 경우만 결과 리스트에 추가한다.</li>
<li>교집합 후보만 모으므로, 불필요한 <code>map.keySet()</code> 전체 순회가 사라지고 코드가 더 단순해진다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">set에 N명 저장
M명을 읽으며 set에 있으면 result에 추가
result 정렬 후 출력</code></pre>
</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());

        HashSet&lt;String&gt; heard = new HashSet&lt;&gt;(n * 2);
        ArrayList&lt;String&gt; result = new ArrayList&lt;&gt;();

        for (int i = 0; i &lt; n; i++) {
            heard.add(br.readLine());
        }

        for (int i = 0; i &lt; m; i++) {
            String name = br.readLine();
            if (heard.contains(name)) {
                result.add(name);
            }
        }

        Collections.sort(result);

        StringBuilder sb = new StringBuilder();
        sb.append(result.size()).append(&#39;\n&#39;);
        for (String name : result) sb.append(name).append(&#39;\n&#39;);
        System.out.print(sb);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1">방법 1</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N + M + K log K)
(K = 교집합 크기, 정렬 비용)</li>
<li><strong>공간 복잡도</strong>: O(N + M) 수준 (map에 둘 다 들어갈 수 있음)</li>
</ul>
<blockquote>
<h4 id="방법-2">방법 2</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N + M + K log K)</li>
<li><strong>공간 복잡도</strong>: O(N + K) (듣도 set + 결과만 보관)</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>처음에는 <code>HashMap</code>으로 <code>false/true</code> 판별 방식도 고려했지만, 사전식 정렬을 해야 해서 결국 카운팅 방식으로 저장한뒤 정렬 로직을 따로 구성했다.</li>
<li><code>Set.contains(value)</code>는 평균적으로 <strong>O(1)</strong>인 것을 까먹었다</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li><strong>교집합 문제는 “한 쪽을 Set에 넣고, 다른 쪽을 보면서 contains로 걸러내기”</strong>가 가장 단순한 정석 패턴이다.</li>
<li>사전식 정렬은 결과 리스트만 정렬하면 되므로, 전체 자료구조를 정렬형(<code>TreeSet</code>)으로 유지할 필요는 보통 없다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/1764">https://www.acmicpc.net/problem/1764</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/14425">백준 - 14425번 문자열 집합</a></li>
<li><a href="https://www.acmicpc.net/problem/10816">백준 - 10816번 숫자 카드 2</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/1764">백준 - 1764번 듣보잡</a> (정렬 대신 출력 포맷/자료구조 변형 연습)</li>
<li><a href="https://www.hackerrank.com/challenges/ctci-ransom-note/problem">HackerRank - Hash Tables: Ransom Note</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 10816번 숫자 카드 2]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-10816%EB%B2%88-%EC%88%AB%EC%9E%90-%EC%B9%B4%EB%93%9C-2</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-10816%EB%B2%88-%EC%88%AB%EC%9E%90-%EC%B9%B4%EB%93%9C-2</guid>
            <pubDate>Wed, 31 Dec 2025 14:32:11 GMT</pubDate>
            <description><![CDATA[<h1 id="10816-숫자-카드-2">[10816] 숫자 카드 2</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2025-12-31</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/6fccfa90-7ece-43a4-97b7-d5b312a0c992/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 해시맵(HashMap), 카운팅(counting)</li>
<li><strong>요구사항</strong>: N개의 정수 카드에서 각 질의 수가 몇 개 있는지 개수를 출력해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>HashMap&lt;Integer, Integer&gt;</code>: (숫자 → 등장 횟수) 저장</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>빈도수 카운팅 (frequency counting)</li>
<li><code>getOrDefault</code>를 이용한 안전 조회</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>평균 O(1) 조회</li>
<li>중복 원소 개수(count)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--hashmap을-활용한-누적-카운팅">방법 1 : HashMap을 활용한 누적 카운팅</h3>
<blockquote>
<ol>
<li>문제 분해</li>
</ol>
</blockquote>
<ul>
<li>N개의 카드를 읽으면서 <code>map</code>에 숫자별 등장 횟수를 누적한다.</li>
<li>M개의 질의를 읽으면서 <code>map.getOrDefault(q, 0)</code>으로 개수를 출력한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">입력 N
카드 N개를 map에 카운팅
&gt;
입력 M
질의 M개에 대해 map[q] 출력 (없으면 0)</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>질의 숫자가 없는 경우 0 출력 (<code>getOrDefault</code>)</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());

        HashMap&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();
        StringBuilder sb = new StringBuilder();
        String m = br.readLine();
        StringTokenizer st2 = new StringTokenizer(br.readLine());

        while(st.hasMoreTokens()){
            int temp = Integer.parseInt(st.nextToken());
            map.put(temp, map.getOrDefault(temp, 0) + 1);
        }

        while(st2.hasMoreTokens()){
            int q = Integer.parseInt(st2.nextToken());
            sb.append(map.getOrDefault(q, 0)).append(&quot; &quot;);
        }

        System.out.println(sb);
    }
}</code></pre>
<hr>
<h3 id="방법-2--정렬--이분-탐색">방법 2 : 정렬 + 이분 탐색</h3>
<blockquote>
<ol>
<li>개선 포인트</li>
</ol>
</blockquote>
<ul>
<li>해시맵 풀이도 정답이고 빠르지만, 이 문제는 전형적으로 <strong>정렬 후 lower/upper bound</strong>로 개수를 세는 풀이도 자주 사용한다.</li>
<li>해시맵은 평균 O(1)이지만, 정렬 기반 풀이는 성능이 안정적이고(결정적), 구현 연습 가치가 있다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">카드 배열 정렬
count(x) = upperBound(x) - lowerBound(x)</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>카드에 없는 값이면 두 bound가 같아 0</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    static int lowerBound(int[] arr, int target) {
        int l = 0, r = arr.length; // [l, r)
        while (l &lt; r) {
            int mid = (l + r) &gt;&gt;&gt; 1;
            if (arr[mid] &gt;= target) r = mid;
            else l = mid + 1;
        }
        return l;
    }

    static int upperBound(int[] arr, int target) {
        int l = 0, r = arr.length; // [l, r)
        while (l &lt; r) {
            int mid = (l + r) &gt;&gt;&gt; 1;
            if (arr[mid] &gt; target) r = mid;
            else l = mid + 1;
        }
        return l;
    }

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(br.readLine());
        int[] cards = new int[n];

        StringTokenizer st = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; n; i++) cards[i] = Integer.parseInt(st.nextToken());
        Arrays.sort(cards);

        int m = Integer.parseInt(br.readLine());
        st = new StringTokenizer(br.readLine());

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i &lt; m; i++) {
            int q = Integer.parseInt(st.nextToken());
            int cnt = upperBound(cards, q) - lowerBound(cards, q);
            sb.append(cnt).append(&#39; &#39;);
        }
        System.out.print(sb);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1-hashmap">방법 1 (HashMap)</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N + M) (평균)</li>
<li><strong>공간 복잡도</strong>: O(N) (서로 다른 값 개수만큼)</li>
</ul>
<blockquote>
<h4 id="방법-2-정렬--이분-탐색">방법 2 (정렬 + 이분 탐색)</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N log N + M log N)</li>
<li><strong>공간 복잡도</strong>: O(N)</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>없음</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li><p>이 문제는 <strong>카운팅</strong>이므로 해시맵이 가장 직관적이다.</p>
</li>
<li><p>코딩 테스트 관점에서는</p>
<ul>
<li><strong>빠르게 맞추기</strong>: 해시맵</li>
<li><strong>기본기/안정성 연습</strong>: 정렬 + lower/upper bound
둘 다 정석 범위에 들어간다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/10816">https://www.acmicpc.net/problem/10816</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/10815">백준 - 10815번 숫자 카드</a></li>
<li><a href="https://www.acmicpc.net/problem/14425">백준 - 14425번 문자열 집합</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/1764">백준 - 1764번 듣보잡</a></li>
<li><a href="https://www.hackerrank.com/challenges/frequency-queries/problem">HackerRank - Frequency Queries</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 1620번 나는야 포켓몬 마스터 이다솜]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1620%EB%B2%88-%EB%82%98%EB%8A%94%EC%95%BC-%ED%8F%AC%EC%BC%93%EB%AA%AC-%EB%A7%88%EC%8A%A4%ED%84%B0-%EC%9D%B4%EB%8B%A4%EC%86%9C</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1620%EB%B2%88-%EB%82%98%EB%8A%94%EC%95%BC-%ED%8F%AC%EC%BC%93%EB%AA%AC-%EB%A7%88%EC%8A%A4%ED%84%B0-%EC%9D%B4%EB%8B%A4%EC%86%9C</guid>
            <pubDate>Wed, 31 Dec 2025 13:51:48 GMT</pubDate>
            <description><![CDATA[<h1 id="1620-나는야-포켓몬-마스터-이다솜">[1620] 나는야 포켓몬 마스터 이다솜</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2025-12-31</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/f21b1ccf-49b7-4a42-bf93-f512dac1d005/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 해시맵(HashMap), 문자열 처리, 구현</li>
<li><strong>요구사항</strong>: 포켓몬 이름 ↔ 번호를 빠르게 상호 변환하여 질의 M개에 답해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>HashMap&lt;String, Integer&gt;</code>: 이름 → 번호 조회</li>
<li><code>String[] names</code>: 번호 → 이름 조회 (인덱스 기반)</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>양방향 매핑 (bidirectional mapping)</li>
<li>질의가 숫자인지/문자인지 판별하여 분기 처리</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>해시 조회 </li>
<li>문자열 판별 </li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--내-풀이">방법 1 : 내 풀이</h3>
<blockquote>
<ol>
<li>문제 분해</li>
</ol>
</blockquote>
<ul>
<li><code>names[i]</code>에 i번 포켓몬 이름을 저장한다.</li>
<li><code>map</code>에 (이름 → 번호)를 저장한다.</li>
<li>질의가 숫자면 <code>names[index]</code>, 문자면 <code>map.get(name)</code>을 출력한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">입력 N, M
for i=1..N:
    names[i] = pokemon
    map[pokemon] = i
&gt;
for each question:
    if (question is number) -&gt; names[number]
    else -&gt; map[question]</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>없음</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] str = br.readLine().split(&quot; &quot;);
        int n = Integer.parseInt(str[0]);
        int m = Integer.parseInt(str[1]);
        String[] names = new String[n+1];
        HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
        StringBuilder sb = new StringBuilder();

        for(int i = 1; i &lt;= n; i++){
            String pokemon = br.readLine();
            names[i] = pokemon;
            map.put(pokemon, i);
        }

        for(int i = 0; i &lt; m; i++){
            String question = br.readLine();
            if(question.toUpperCase().equals(question)){
                int index = Integer.parseInt(question);
                sb.append(names[index]).append(&quot;\n&quot;);
            } else {
                sb.append(map.get(question)).append(&quot;\n&quot;);
            }
        }

        System.out.println(sb);
    }
}</code></pre>
<hr>
<h3 id="방법-2--개선된-최적의-풀이-숫자-판별-로직-교정">방법 2 : 개선된 최적의 풀이 (숫자 판별 로직 교정)</h3>
<blockquote>
<ol>
<li>개선 포인트</li>
</ol>
</blockquote>
<ul>
<li><code>question.toUpperCase().equals(question)</code>는 <strong>“질의가 숫자인지”</strong> 판별하는 조건으로 부적절하다.<blockquote>
</blockquote>
<ul>
<li>숫자 문자열은 대소문자 변화가 없어서 true가 되지만, <strong>이름이 전부 대문자인 경우도</strong> true가 되어 <code>Integer.parseInt()</code>에서 예외가 난다.</li>
<li>하지만 이 문제에서는 예외가 발생하지 않았다. 와우.<blockquote>
</blockquote>
</li>
</ul>
</li>
<li>따라서 <strong>첫 글자가 숫자인지</strong>로 판별하는 방식이 안전하고 빠르다. (<code>Character.isDigit(question.charAt(0))</code>)<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">if Character.isDigit(question.charAt(0)):
    idx = Integer.parseInt(question)
    print names[idx]
else:
    print map.get(question)</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>없음 (문제 조건상 숫자 질의는 항상 올바른 번호로 주어진다)</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());

        String[] names = new String[n + 1];
        HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;(n * 2);

        for (int i = 1; i &lt;= n; i++) {
            String pokemon = br.readLine();
            names[i] = pokemon;
            map.put(pokemon, i);
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i &lt; m; i++) {
            String q = br.readLine();
            if (Character.isDigit(q.charAt(0))) {
                sb.append(names[Integer.parseInt(q)]).append(&#39;\n&#39;);
            } else {
                sb.append(map.get(q)).append(&#39;\n&#39;);
            }
        }
        System.out.print(sb);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1">방법 1</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N + M)</li>
<li><strong>공간 복잡도</strong>: O(N)</li>
</ul>
<blockquote>
<h4 id="방법-2">방법 2</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: O(N + M)</li>
<li><strong>공간 복잡도</strong>: O(N)</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>없음</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>“문자열이 숫자인지” 판별할 때 대소문자 변환 비교는 목적에 맞지 않다.</li>
<li>이 문제처럼 입력이 “번호 또는 이름”으로만 주어질 때는 <code>Character.isDigit(첫 글자)</code>가 가장 간단하고 안전하다.</li>
<li>입출력은 <code>BufferedReader + StringTokenizer + StringBuilder</code> 조합이 안정적으로 빠르다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/1620">https://www.acmicpc.net/problem/1620</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/14425">백준 - 14425번 문자열 집합</a></li>
<li><a href="https://www.acmicpc.net/problem/7785">백준 - 7785번 회사에 있는 사람</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천):</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/1764">백준 - 1764번 듣보잡</a></li>
<li><a href="https://www.acmicpc.net/problem/10816">백준 - 10816번 숫자 카드 2</a></li>
<li><a href="https://www.hackerrank.com/challenges/ctci-ransom-note/problem">HackerRank - Hash Tables: Ransom Note</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 18870번 좌표 압축]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-18870%EB%B2%88-%EC%A2%8C%ED%91%9C-%EC%95%95%EC%B6%95</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-18870%EB%B2%88-%EC%A2%8C%ED%91%9C-%EC%95%95%EC%B6%95</guid>
            <pubDate>Mon, 29 Dec 2025 06:33:19 GMT</pubDate>
            <description><![CDATA[<h1 id="18870-좌표-압축">[18870] 좌표 압축</h1>
<blockquote>
<p>난이도: ★★★☆☆  •  solved on: 2025-12-29</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/0af12ecb-4ac0-4491-95c6-3a9788b47b36/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 정렬, 해시(좌표 압축)</li>
<li><strong>요구사항</strong>: 주어진 수열의 각 값에 대해, <strong>중복 제거 후 정렬했을 때의 인덱스를 원래 순서대로 출력해야 한다.</strong></li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>HashSet</code> : 중복 제거</li>
<li><code>ArrayList</code> : 유니크 값 정렬</li>
<li><code>HashMap</code> : 값 → 압축 인덱스 매핑</li>
<li><code>int[]</code> : 숫자 배열 처리 </li>
<li><code>StringTokenizer</code> : 빠른 입력 파싱 </li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li><strong>좌표 압축 (coordinate compression)</strong>:<ul>
<li>중복 제거 → 정렬 → rank(0..k-1) 부여 → 원본 순서대로 출력</li>
<li>원래 가지고 있던 값들의 의미는 모두 사라지고 오직 순서에 따른 정보만 남게 되는 테크닉</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>value-to-rank mapping     (값→순위 매핑)</li>
<li>unique + sort + map</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--hashset-중복-제거--정렬--hashmap-매핑">방법 1 : HashSet 중복 제거 + 정렬 + HashMap 매핑</h3>
<blockquote>
<ol>
<li><strong>문제 분해</strong></li>
</ol>
</blockquote>
<ul>
<li>입력 수열을 문자열 배열로 받고 <code>HashSet</code>으로 중복 제거한다.</li>
<li>유니크 값 리스트(<code>arrList</code>)를 정렬한다.</li>
<li>정렬된 유니크 리스트의 인덱스를 <code>HashMap</code>에 저장해 값→순위 매핑을 만든다.</li>
<li>원본 배열을 순회하면서 <code>map.get(num)</code>으로 순위를 출력한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li><strong>핵심 로직 흐름</strong><blockquote>
</blockquote>
<pre><code class="language-text">arr = 입력(문자열)
set = 중복 제거
arrList = set -&gt; 리스트 변환
arrList 정렬
&gt;
map[value] = 정렬 인덱스
for num in arr:
    출력 map[num]</code></pre>
</li>
<li><strong>예외 처리</strong><blockquote>
</blockquote>
<ul>
<li>중복이 많아도 <code>HashSet</code>으로 유니크만 정렬하므로 정렬 대상이 줄어든다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.util.*;
import java.io.*;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int n = Integer.parseInt(sc.nextLine());
        String line = sc.nextLine();
        String[] arr = line.split(&quot; &quot;);
        HashSet&lt;String&gt; set = new HashSet&lt;&gt;(Arrays.asList(arr));
        ArrayList&lt;String&gt; arrList = new ArrayList&lt;&gt;(set);

        arrList.sort((a,b)-&gt; {
            return Integer.valueOf(a).compareTo(Integer.valueOf(b));
        });
        HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
        for(int i = 0; i &lt; arrList.size(); i++) {
            map.put(arrList.get(i), i);
        }
        StringBuilder sb = new StringBuilder();
        for(String num : arr){
            sb.append(map.get(num)).append(&quot; &quot;);
        }
        System.out.println(sb);
    }
}</code></pre>
<hr>
<h3 id="방법-2--int-배열-복사본-정렬--중복-스캔으로-rank-매핑--빠른-입력">방법 2 : int 배열 복사본 정렬 + 중복 스캔으로 rank 매핑 + 빠른 입력</h3>
<blockquote>
<ol>
<li><strong>핵심 개선 포인트</strong></li>
</ol>
</blockquote>
<ul>
<li><code>Scanner</code>/<code>split</code> 대신 <code>BufferedReader + StringTokenizer</code>로 입력 비용을 줄인다.</li>
<li><code>String</code> 대신 <code>int</code>로 처리해 변환/비교 비용을 줄인다.</li>
<li><code>HashSet</code> 없이도, 정렬된 배열을 한 번 스캔하며 중복을 제거하면서 <code>map</code>을 만들 수 있다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li><strong>핵심 로직 흐름</strong><blockquote>
</blockquote>
<pre><code class="language-text">origin = 원본 int[]
sorted = origin 복사본
sorted 정렬
&gt;
rank = 0
for i in 0..n-1:
    if i==0 or sorted[i] != sorted[i-1]:
        map[sorted[i]] = rank++
for x in origin:
    출력 map[x]</code></pre>
</li>
<li><strong>예외 처리</strong><blockquote>
</blockquote>
<ul>
<li>음수/큰 수여도 정렬 후 인덱스 기반이므로 문제 없음</li>
<li>출력 마지막 공백은 조건으로 처리</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());

        int[] origin = new int[n];
        int[] sorted = new int[n];

        StringTokenizer st = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; n; i++) {
            int v = Integer.parseInt(st.nextToken());
            origin[i] = v;
            sorted[i] = v;
        }

        Arrays.sort(sorted);

        HashMap&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;(n * 2);
        int rank = 0;
        for (int i = 0; i &lt; n; i++) {
            if (i == 0 || sorted[i] != sorted[i - 1]) {
                map.put(sorted[i], rank++);
            }
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i &lt; n; i++) {
            sb.append(map.get(origin[i]));
            if (i + 1 &lt; n) sb.append(&#39; &#39;);
        }
        System.out.print(sb);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1">방법 1</h4>
</blockquote>
<ul>
<li><p><strong>시간 복잡도: 대략 <code>O(NlongN)</code></strong></p>
<ul>
<li>중복 제거 <code>O(N)</code></li>
<li>유니크 정렬 <code>O(U log U)</code></li>
<li>매핑/출력 <code>O(N)</code></li>
<li>단, <code>Scanner</code>와 <code>split</code>, <code>Integer.valueOf</code> 반복 호출로 체감 시간이 늘 수 있다.</li>
</ul>
</li>
<li><p><strong>공간 복잡도</strong>: <strong><code>O(N)</code></strong></p>
</li>
</ul>
<blockquote>
<h4 id="방법-2">방법 2</h4>
</blockquote>
<ul>
<li><strong>시간 복잡도</strong>: <strong><code>O(N log N)</code></strong></li>
<li><strong>공간 복잡도</strong>: <strong><code>O(N)</code></strong></li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li><p>처음 시행착오에서는 정렬된 유니크 리스트에서 순위를 얻기 위해 <code>arrList.indexOf(num)</code> 같은 방식으로 접근했는데, 이 경우 <code>TimeOut</code>이 발생한다. </p>
<ul>
<li>이유는 원소마다 리스트를 선형 탐색하게 되어 전체가 최악 <strong>O(N²)</strong> 로 커질 수 있었기 때문이다.</li>
</ul>
</li>
<li><p>결국 “값의 순위를 찾는 과정”은 반복 탐색이 아니라, <strong><code>HashMap</code>으로 값→순위를 미리 만들어서 즉시 조회</strong>해야 한다는 점이 핵심이었다.</p>
</li>
<li><p>추가로, 입력이 커질 때 <code>Scanner + split</code> 조합이 병목이 될 수 있다는 점도 체감할 수 있는 포인트였다.</p>
</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>좌표 압축은 <strong>정렬 + 매핑</strong>이 핵심이며, 출력 단계는 반드시 <code>O(1)</code> 조회로 끝나야 한다.</li>
<li>대량 입력 문제는 알고리즘이 같아도 <strong>입력 방식(Scanner vs BufferedReader)</strong> 에서 제출 성능 차이가 크게 난다.</li>
<li>숫자는 가능하면 <code>String</code>으로 들고 가지 말고 <code>int</code>로 즉시 파싱하는 편이 깔끔하고 빠르다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/18870">https://www.acmicpc.net/problem/18870</a></li>
<li>참고 블로그/깃허브: 좌표 압축 개념 참고 <a href="https://wikidocs.net/214469">https://wikidocs.net/214469</a> </li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천) :</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/11650">백준 - 11650번 좌표 정렬하기</a></li>
<li><a href="https://www.acmicpc.net/problem/11651">백준 - 11651번 좌표 정렬하기 2</a></li>
<li><a href="https://www.acmicpc.net/problem/10814">백준 - 10814번 나이순 정렬</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천) :</p>
<ul>
<li><a href="https://www.hackerrank.com/challenges/no-idea/problem">Hackerrank - No Idea!</a></li>
<li><a href="https://www.hackerrank.com/challenges/ctci-comparator-sorting/problem">Hackerrank - Sorting: Comparator</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 10814번 나이순 정렬]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-10814%EB%B2%88-%EB%82%98%EC%9D%B4%EC%88%9C-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-10814%EB%B2%88-%EB%82%98%EC%9D%B4%EC%88%9C-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Mon, 29 Dec 2025 05:32:57 GMT</pubDate>
            <description><![CDATA[<h1 id="10814-나이순-정렬">[10814] 나이순 정렬</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2025-12-29</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/21494a27-ed01-4c27-b1c1-b83233b8c09a/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 정렬 (Sorting), 안정 정렬 (Stable Sort)</li>
<li><strong>요구사항</strong>: 회원을 <strong>나이 오름차순</strong>으로 정렬하되, <strong>나이가 같으면 가입한 순 (입력 순)</strong> 을 유지해서 출력한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>String[][]</code> : 입력순 인덱스, &quot;나이 이름&quot; 원문을 한번에 저장</li>
<li><code>Member[]</code> : 나이/이름을 분리 저장 (커스텀 클래스)</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>정렬 (Comparator)</li>
<li><strong>안정 정렬 (stable sort)</strong> 활용</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>안정 정렬 (stable sort), 입력 순서 유지 (input order)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--배열을-활용한-인덱스-저장">방법 1 : 배열을 활용한 인덱스 저장</h3>
<blockquote>
<ol>
<li>문제 분해</li>
</ol>
</blockquote>
<ul>
<li>각 입력에 대해 원래 순서를 <code>arr[i][0]</code>에 문자열로 저장한다.</li>
<li>Comparator에서 나이를 비교하고, 같으면 저장한 순서를 비교한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">arr[i] = (i, &quot;age name&quot;)
정렬: age 오름차순, age 같으면 i 오름차순
arr[i][1] 출력</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>나이가 같은 경우 입력 순서 유지</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.util.*;
import java.lang.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        String[][] arr = new String[n][2];
        for(int i = 0; i &lt; n; i++){
            arr[i][0] = i + &quot;&quot;;
            arr[i][1] = br.readLine();
        }
        Arrays.sort(arr, (a, b) -&gt; {
            String[] person1 = a[1].split(&quot; &quot;);
            int person1Age = Integer.parseInt(person1[0]);

            String[] person2 = b[1].split(&quot; &quot;);
            int person2Age = Integer.parseInt(person2[0]);

            if(person1Age != person2Age){
                return person1Age - person2Age;
            }
            return Integer.parseInt(a[0]) - Integer.parseInt(b[0]);
        });

        StringBuilder sb = new StringBuilder();
        for(String[] data : arr){
            sb.append(data[1]).append(&quot;\n&quot;);
        }
        System.out.println(sb);
    }
}</code></pre>
<hr>
<h3 id="방법-2--입력-파싱-1회--안정-정렬-활용">방법 2 : 입력 파싱 1회 + 안정 정렬 활용</h3>
<blockquote>
<ol>
<li>개선 포인트 (시간/공간)</li>
</ol>
</blockquote>
<ul>
<li>방법 1은 <strong>정렬 비교할 때마다 <code>split()</code>과 <code>parseInt()</code></strong>가 반복되어 비용이 크다.<blockquote>
</blockquote>
</li>
<li>입력 받을 때 <strong>나이/이름을 미리 분리 저장</strong>하면 비교가 매우 가벼워진다.<blockquote>
</blockquote>
</li>
<li>또한 이 문제는 “나이가 같으면 입력 순서 유지”인데, <strong>Java에서 객체 배열 정렬은 안정 정렬</strong>이므로 <strong>나이만 비교해도 입력 순서가 유지</strong>된다.
→ “순서 인덱스”를 따로 저장할 필요가 없어진다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">members[i] = (age, name)
안정 정렬: age 오름차순만 비교
출력: age + &quot; &quot; + name</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>나이가 같을 때는 Comparator가 0을 반환 → 안정 정렬이라 입력 순서 유지</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {

    static class Member {
        int age;
        String name;
        Member(int age, String name) {
            this.age = age;
            this.name = name;
        }
    }

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());

        Member[] members = new Member[n];
        for (int i = 0; i &lt; n; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int age = Integer.parseInt(st.nextToken());
            String name = st.nextToken();
            members[i] = new Member(age, name);
        }

        Arrays.sort(members, (a, b) -&gt; a.age - b.age); // 나이만 비교 (안정 정렬 활용)

        StringBuilder sb = new StringBuilder();
        for (Member m : members) {
            sb.append(m.age).append(&#39; &#39;).append(m.name).append(&#39;\n&#39;);
        }
        System.out.print(sb);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1">방법 1</h4>
</blockquote>
<ul>
<li><p><strong>시간 복잡도</strong>: <code>O(N log N * L)</code></p>
<ul>
<li>비교 함수가 호출될 때마다 <code>split()</code> 등 문자열 처리 비용이 반복됨 (<code>L</code> = 문자열 길이)</li>
</ul>
</li>
<li><p><strong>공간 복잡도</strong>: <code>O(N)</code></p>
</li>
</ul>
<blockquote>
<h4 id="방법-2">방법 2</h4>
</blockquote>
<ul>
<li><p><strong>시간 복잡도</strong>: <code>O(N log N)</code></p>
</li>
<li><p><strong>공간 복잡도</strong>: <code>O(N)</code></p>
</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>입력 순서 정보를 어떻게 유지할지 설계하는 부분에서 난감했고, 처음에는 배열에 인덱스를 같이 저장하는 방식으로 해결했다.</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li>이 문제의 핵심은 나이 같으면 입력 순서 유지인데, <strong>안정 정렬(stable sort)</strong> 을 활용하면 <strong>인덱스 저장</strong> 자체가 필요 없어질 수 있다.</li>
<li>Comparator 안에서 <code>split()</code> 같은 문자열 파싱을 하면, 정렬 과정에서 같은 문자열을 계속 쪼개게 되어 비효율이 커진다. 따라서 파싱은 최초 저징시 진행하는 것이 좋다.</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/10814">https://www.acmicpc.net/problem/10814</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천)</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/1181">백준 - 1181번 단어 정렬</a></li>
<li><a href="https://www.acmicpc.net/problem/11650">백준 - 11650번 좌표 정렬하기</a></li>
<li><a href="https://www.acmicpc.net/problem/10825">백준 - 10825번 국영수</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천)</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/18870">백준 - 18870번 좌표 압축</a></li>
<li><a href="https://www.hackerrank.com/challenges/java-sort/problem">Hackerrank - Sort</a></li>
<li><a href="https://www.hackerrank.com/challenges/java-comparator/problem">Hackerrank - Comparator</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준 문제 풀이] 1181번 단어 정렬]]></title>
            <link>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1181%EB%B2%88-%EB%8B%A8%EC%96%B4-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@melon-chicken/%EB%B0%B1%EC%A4%80-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1181%EB%B2%88-%EB%8B%A8%EC%96%B4-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Mon, 29 Dec 2025 04:57:00 GMT</pubDate>
            <description><![CDATA[<h1 id="1181-단어-정렬">[1181] 단어 정렬</h1>
<blockquote>
<p>난이도: ★★☆☆☆  •  solved on: 2025-12-29</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/melon-chicken/post/f3f3df9b-c715-4b86-b667-0af592843f7e/image.png" alt=""></p>
<hr>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li><strong>문제 유형</strong>: 정렬 (Sorting), 문자열 (String), 중복 제거 (Deduplication)</li>
<li><strong>요구사항</strong>: 단어를 <strong>길이 오름차순</strong>, 길이가 같으면 <strong>사전순 오름차순</strong>으로 정렬하되 <strong>중복 단어는 한 번만</strong> 출력해야 한다.</li>
</ul>
<hr>
<h2 id="사용-개념">사용 개념</h2>
<ol>
<li><p><strong>자료구조</strong></p>
<ul>
<li><code>ArrayList&lt;String&gt;</code>: 단어 저장 및 정렬</li>
<li><code>HashSet&lt;String&gt;</code>: 중복 제거(개선 풀이)</li>
</ul>
</li>
<li><p><strong>알고리즘/기법</strong></p>
<ul>
<li>커스텀 정렬 기준 (Comparator)</li>
<li>중복 제거 후 정렬</li>
</ul>
</li>
<li><p><strong>핵심 키워드</strong></p>
<ul>
<li>사전순 정렬 (lexicographical order)</li>
<li>다중 키 정렬 (multi-key sort)</li>
<li>해시 기반 중복 제거 (hash-based dedup)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="풀이-아이디어-및-코드">풀이 아이디어 및 코드</h2>
<h3 id="방법-1--처음-풀이">방법 1 : 처음 풀이</h3>
<blockquote>
<ol>
<li>문제 분해</li>
</ol>
</blockquote>
<ul>
<li>입력을 받으면서 <code>ArrayList</code>에 저장하되, <code>contains</code>로 중복이면 건너뛴다.</li>
<li><code>Comparator</code>로 (길이 → 사전순) 기준으로 정렬한다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">for each word:
    if words.contains(word) continue
    words.add(word)
&gt;
words.sort(길이 오름차순, 같으면 사전순)
출력</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li>중복 단어는 <code>contains</code>로 제거</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.util.*;
import java.lang.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        ArrayList&lt;String&gt; words = new ArrayList&lt;&gt;();

        for(int i = 0; i &lt; n; i++){
            String word = br.readLine();
            if(words.contains(word)){
                continue;
            }
            words.add(word);
        }

        words.sort(new StringComparator());

        StringBuilder sb = new StringBuilder();
        for(String word : words){
            sb.append(word).append(&quot;\n&quot;);
        }
        System.out.println(sb);
    }

    public static class StringComparator implements Comparator&lt;String&gt;{
        @Override
        public int compare(String o1, String o2) {
            if(o1.length() != o2.length()){
                return o1.length() - o2.length();
            }
            if(!o1.equals(o2)){
                return o1.compareTo(o2);
            }
            return 0;
        }
    }
}</code></pre>
<hr>
<h3 id="방법-2--hashset으로-중복-제거-후-정렬">방법 2 : HashSet으로 중복 제거 후 정렬</h3>
<blockquote>
<ol>
<li>개선 포인트 (시간/공간)</li>
</ol>
</blockquote>
<ul>
<li>방법 1의 병목은 <code>words.contains(word)</code>가 <strong>O(N)</strong> 이라서, 전체가 최악 <strong>O(N²)</strong> 로 커질 수 있다는 점이다.</li>
<li>중복 제거는 <code>HashSet</code>이 평균 <strong>O(1)</strong> 이므로 입력 단계가 훨씬 빨라진다.<blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>핵심 로직 흐름<blockquote>
</blockquote>
<pre><code class="language-text">set에 단어 N개 추가(중복 자동 제거)
set -&gt; list로 변환
list.sort(길이 오름차순, 같으면 사전순)
출력</code></pre>
</li>
<li>예외 처리<blockquote>
</blockquote>
<ul>
<li><code>HashSet</code>이 중복을 자동으로 제거하므로 별도 처리 불필요</li>
</ul>
</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());

        HashSet&lt;String&gt; set = new HashSet&lt;&gt;();
        for (int i = 0; i &lt; n; i++) {
            set.add(br.readLine()); // 중복 자동 제거
        }

        ArrayList&lt;String&gt; words = new ArrayList&lt;&gt;(set);

        words.sort((a, b) -&gt; {
            if (a.length() != b.length()) return a.length() - b.length();
            return a.compareTo(b);
        });

        StringBuilder sb = new StringBuilder();
        for (String w : words) sb.append(w).append(&#39;\n&#39;);
        System.out.print(sb);
    }
}</code></pre>
<hr>
<h2 id="시간·공간-복잡도">시간·공간 복잡도</h2>
<blockquote>
<h4 id="방법-1">방법 1</h4>
</blockquote>
<ul>
<li><p><strong>시간 복잡도</strong>: 최악 <code>O(N² + M log M)</code></p>
<ul>
<li>중복 체크 <code>contains</code>가 누적되어 최악 <code>O(N²)</code> 가능</li>
<li><code>M</code> = 중복 제거 후 단어 수</li>
</ul>
</li>
<li><p><strong>공간 복잡도</strong>: <code>O(M)</code></p>
</li>
</ul>
<blockquote>
<h4 id="방법-2">방법 2</h4>
</blockquote>
<ul>
<li><p><strong>시간 복잡도</strong>: 평균 <code>O(N + M log M)</code></p>
<ul>
<li><code>HashSet</code> 삽입 평균 <code>O(1)</code> × N</li>
<li>정렬 <code>O(M log M)</code></li>
</ul>
</li>
<li><p><strong>공간 복잡도</strong>: <code>O(M)</code></p>
</li>
</ul>
<hr>
<h2 id="어려웠던-점">어려웠던 점</h2>
<ul>
<li>없음</li>
</ul>
<hr>
<h2 id="배운-점-및-팁">배운 점 및 팁</h2>
<ul>
<li><p><strong>중복 제거 + 정렬</strong> 문제는 보통</p>
<ul>
<li>입력 단계: <code>HashSet</code> (또는 <code>TreeSet</code>/정렬 필요 시)로 중복 처리</li>
<li>출력 단계: <code>List</code>로 옮겨 <code>sort</code> 
이 조합이 가장 자주 쓰인다고 한다.</li>
</ul>
</li>
<li><p>비교 함수에서 <code>if(!o1.equals(o2)) return o1.compareTo(o2);</code> 부분은, 길이가 같다면 그냥 <code>return o1.compareTo(o2);</code> 로 충분하다.</p>
</li>
</ul>
<hr>
<h2 id="참고-및-링크">참고 및 링크</h2>
<ul>
<li>문제 링크: <a href="https://www.acmicpc.net/problem/1181">https://www.acmicpc.net/problem/1181</a></li>
<li>참고 블로그/깃허브: 없음</li>
</ul>
<hr>
<h2 id="추가-연습-문제">추가 연습 문제</h2>
<ul>
<li><p>비슷한 유형 (GPT 추천)</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/10825">백준 - 10825번 국영수</a></li>
<li><a href="https://www.acmicpc.net/problem/11650">백준 - 11650번 좌표 정렬하기</a></li>
<li><a href="https://www.acmicpc.net/problem/10814">백준 - 10814번 나이순 정렬</a></li>
</ul>
</li>
<li><p>확장 문제 (GPT 추천)</p>
<ul>
<li><a href="https://www.acmicpc.net/problem/18870">백준 - 18870번 좌표 압축</a></li>
<li><a href="https://www.hackerrank.com/challenges/java-comparator/problem">Hackerrank - Java Comparator</a></li>
<li><a href="https://www.hackerrank.com/challenges/java-sort/problem">Hackerrank - Java Sort</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>