<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jung-min-ju.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sat, 16 May 2026 13:26:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jung-min-ju.log</title>
            <url>https://velog.velcdn.com/images/jung-min-ju/profile/be3005b0-b67a-42b6-80b8-2fdb03eb0ee7/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jung-min-ju.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jung-min-ju" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[인터페이스 vs 추상클래스]]></title>
            <link>https://velog.io/@jung-min-ju/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-vs-%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@jung-min-ju/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-vs-%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Sat, 16 May 2026 13:26:58 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[is-A vs has-A ]]></title>
            <link>https://velog.io/@jung-min-ju/1%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@jung-min-ju/1%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Sat, 16 May 2026 13:26:19 GMT</pubDate>
            <description><![CDATA[<h2 id="is-a-vs-has-a">is-A vs has-A</h2>
<h3 id="is-a-관계">is-A 관계</h3>
<p>is-A는 <strong>“A는 B의 한 종류다”</strong> 라고 말할 수 있는 관계이다.<br>주로 <strong>상속</strong>에서 사용된다.</p>
<p>예시:</p>
<ul>
<li>강아지는 동물이다.</li>
<li>자동차는 탈것이다.</li>
</ul>
<pre><code class="language-java">class Dog extends Animal {
}</code></pre>
<hr>
<h3 id="has-a-관계">has-A 관계</h3>
<p>has-A는 <strong>“A는 B를 가지고 있다”</strong> 라고 말할 수 있는 관계이다.<br>주로 <strong>객체를 필드로 포함</strong>할 때 사용된다.</p>
<p>예시:</p>
<ul>
<li>자동차는 엔진을 가지고 있다.</li>
<li>컴퓨터는 CPU를 가지고 있다.</li>
</ul>
<pre><code class="language-java">class Car {
    private Engine engine;
}</code></pre>
<hr>
<h2 id="인터페이스-vs-추상클래스">인터페이스 vs 추상클래스</h2>
<h3 id="추상클래스">추상클래스</h3>
<p>추상모 클래스*<em>이다.<br><code>is-A 관계</code>가 자연스러울 클래스는 *</em>공통 속성과 기능을 물려주기 위한 부때 사용한다.</p>
<pre><code class="language-java">abstract class Animal {
    void eat() {
        System.out.println(&quot;먹는다&quot;);
    }

    abstract void sound();
}</code></pre>
<hr>
<h3 id="인터페이스">인터페이스</h3>
<p>인터페이스는 <strong>반드시 구현해야 할 기능 규칙을 정하는 것</strong>이다.<br><code>can-do 관계</code>, 즉 <strong>“~할 수 있다”</strong> 가 자연스러울 때 사용한다.</p>
<pre><code class="language-java">interface Flyable {
    void fly();
}</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 용돈 관리(6236)]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%9A%A9%EB%8F%88-%EA%B4%80%EB%A6%AC6236</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%9A%A9%EB%8F%88-%EA%B4%80%EB%A6%AC6236</guid>
            <pubDate>Thu, 02 Apr 2026 10:57:06 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어볼 문제는 <a href="https://www.acmicpc.net/problem/6236">⭐용돈관리</a> 이다.</p>
<h2 id="1-문제요약">1. 문제요약</h2>
<p>N일 동안 사용할 금액이 주어질 때, 매번 같은 금액 <code>K</code>만 인출해서 생활한다.<br>돈이 부족하면 다시 <code>K</code>원을 인출하며, 필요하면 일부러 인출 횟수를 늘려 정확히 <code>M</code>번도 맞출 수 있다.</p>
<p>따라서 구해야 하는 값은<br><strong>최소 필요 인출 횟수가 <code>M</code> 이하가 되는 최소 <code>K</code></strong> 이다.</p>
<h2 id="2-알고리즘">2. 알고리즘</h2>
<p>이 문제는 <code>K</code>를 기준으로 하는 <strong>이분탐색</strong> 문제다.</p>
<h3 id="21-핵심상태">2.1 핵심상태</h3>
<ul>
<li>탐색값: 인출 금액 <code>K</code></li>
<li>판별: <code>K</code>로 생활할 때 최소 인출 횟수가 <code>M</code> 이하인가</li>
</ul>
<h3 id="22-핵심과정">2.2 핵심과정</h3>
<ul>
<li><code>lo</code>는 하루 사용 금액의 최댓값</li>
<li><code>hi</code>는 전체 사용 금액의 합</li>
<li><code>mid</code>를 <code>K</code> 후보로 두고 인출 횟수를 계산</li>
<li>횟수가 <code>M</code> 이하이면 더 작은 <code>K</code> 탐색</li>
<li>횟수가 <code>M</code> 초과이면 <code>K</code>를 더 키움</li>
</ul>
<h3 id="23-핵심-규칙">2.3 핵심 규칙</h3>
<ul>
<li><code>K</code>는 하루 사용 금액보다 작을 수 없다</li>
<li><code>K</code>가 커질수록 인출 횟수는 줄어든다</li>
<li>최소 필요 인출 횟수 <code>&lt;= M</code> 이면 가능한 <code>K</code>다</li>
<li>따라서 <strong>가능한 최소 <code>K</code></strong> 를 찾으면 된다</li>
</ul>
<h2 id="3-코드">3. 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static int N, M, REMAIN;
    static int [] MONEY;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        MONEY = new int[N];

        int lo=0;
        int hi=0;
        for(int i = 0; i &lt; N; i++){
            MONEY[i] = Integer.parseInt(br.readLine());
            hi += MONEY[i];
            lo = Math.max(lo, MONEY[i]);
        }

        REMAIN = 10000;
        System.out.println(binarySearch(lo, hi));
    }

    static int binarySearch(int lo, int hi) {

        int mid;
        while (lo &lt; hi) {
            mid = (lo + hi) / 2;
            if (canTakeOut(mid)) hi = mid; 
            else lo = mid + 1;
        }
        return lo;
    }

    static boolean canTakeOut(int standard) {
        int takeOutCnt = 1;
        int remain = standard;
        for(int money : MONEY) {
            if(money &gt; standard) return false;
            if(money &gt; remain) {
                takeOutCnt ++;
                remain = standard - money;
            }
            else {
                remain -= money;
            }
        }

        return takeOutCnt &lt;= M;
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 블로그(21921) / 슬라이딩윈도우]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%B8%94%EB%A1%9C%EA%B7%B821921</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%B8%94%EB%A1%9C%EA%B7%B821921</guid>
            <pubDate>Wed, 18 Mar 2026 07:24:26 GMT</pubDate>
            <description><![CDATA[<p>⭐ 오늘 풀어본 문제는 <a href="https://www.acmicpc.net/problem/21921">블로그</a> 이다.</p>
<h2 id="1-문제요약">1. 문제요약</h2>
<ul>
<li>블로그 총 기간 N, 특정 기간 X가 주어짐</li>
<li>X일동안 최대 접속자 수 구하기</li>
</ul>
<h2 id="2-알고리즘">2. 알고리즘</h2>
<h3 id="21-누적합으로-풀기">2.1 누적합으로 풀기.</h3>
<ol>
<li>prefix 배열 생성 (N+1)</li>
<li>prefix[i] = prefix[i-1] + 입력값</li>
<li>for (end = X end = X ~ N; end++)</li>
<li>sum = prefix[end] - prefix[end-X]</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.StringTokenizer;

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 X = Integer.parseInt(st.nextToken());

        int[] prefix = new int[N + 1];

        st = new StringTokenizer(br.readLine());
        for (int i = 1; i &lt;= N; i++) {
            prefix[i] = prefix[i - 1] + Integer.parseInt(st.nextToken());
        }

        int max = 0;
        int cnt = 0;

        for (int end = X; end &lt;= N; end++) {
            int sum = prefix[end] - prefix[end - X];

            if (sum &gt; max) {
                max = sum;
                cnt = 1;
            } else if (sum == max) {
                cnt++;
            }
        }

        System.out.println(max == 0 ? &quot;SAD&quot; : max+&quot;\n&quot;+cnt);
    }
}</code></pre>
<h3 id="22-슬라이딩-윈도우로-풀기">2.2 슬라이딩 윈도우로 풀기</h3>
<ol>
<li>처음 X개 합을 먼저 구해서 windowSum 초기화</li>
<li>max = windowSum, cnt = 1로 시작</li>
<li>for (right = X ~ N-1)</li>
<li>windowSum += arr[right]</li>
<li>windowSum -= arr[right-X]</li>
</ol>
<pre><code class="language-java">import java.io.*;
import java.util.StringTokenizer;

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 X = Integer.parseInt(st.nextToken());

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

        int windowSum = 0;
        for (int i = 0; i &lt; X; i++) {
            windowSum += arr[i];
        }

        int max = windowSum;
        int cnt = 1;

        for (int right = X; right &lt; N; right++) {
            windowSum += arr[right];
            windowSum -= arr[right - X];

            if (windowSum &gt; max) {
                max = windowSum;
                cnt = 1;
            } else if (windowSum == max) {
                cnt++;
            }
        }

         System.out.println(max == 0 ? &quot;SAD&quot; : max+&quot;\n&quot;+cnt);
    }
}</code></pre>
<h3 id="두-알고리즘-차이">두 알고리즘 차이!</h3>
<blockquote>
<h4 id="누적합">누적합</h4>
</blockquote>
<ul>
<li>전체 누적합 배열을 만든 뒤</li>
<li>구간합을 prefix[end] - prefix[start-1]로 계산</li>
</ul>
<blockquote>
<h4 id="슬라이딩-윈도우">슬라이딩 윈도우</h4>
</blockquote>
<ul>
<li>첫 구간합을 먼저 만든 뒤</li>
<li>다음 구간합은 이전 합에서 앞값을 빼고 뒷값을 더해 갱신</li>
</ul>
<p>고정 길이 구간 문제에서는 슬라이딩 윈도우가 더 직관적이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 색종이 붙이기(17136)]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%83%89%EC%A2%85%EC%9D%B4-%EB%B6%99%EC%9D%B4%EA%B8%B017136</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%83%89%EC%A2%85%EC%9D%B4-%EB%B6%99%EC%9D%B4%EA%B8%B017136</guid>
            <pubDate>Tue, 17 Mar 2026 06:51:40 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어본 문제는 <a href="https://www.acmicpc.net/problem/17136">⭐색종이 붙이기</a> 라는 문제이다.</p>
<h2 id="1-문제요약">1. 문제요약</h2>
<ul>
<li>1×1, 2×2, 3×3, 4×4, 5×5 색종이가 총 5개씩 있다.</li>
<li>10×10 색종이를 위 5종류의 색종이로 채워야한다.<ul>
<li>각 칸에는 0,1이 적혀있다.</li>
<li>1에는 색종이로 가려야 하고, 0은 가려선 안된다.</li>
</ul>
</li>
<li>겹치면 안된다.</li>
</ul>
<h2 id="2-입출력">2. 입출력</h2>
<h3 id="출력">출력</h3>
<p>불가능한 경우 -1 출력</p>
<h2 id="3-알고리즘">3. 알고리즘</h2>
<ul>
<li>핵심 상태<ul>
<li>paper[][] : 현재 남아있는 1 영역</li>
<li>remain [] : 색종이 남은 개수</li>
<li>answer : 최소 사용 개수</li>
</ul>
</li>
<li>진행 방식<ul>
<li>dfs 함수 내에서, 이중 for문으로 행,열 처음부터 보며 1 찾음<ul>
<li>1 찾으면 즉시 중단 및 1 찾았다는 boolean 변수 true로 변경</li>
</ul>
</li>
<li>1 찾은 위치에 대해 5×5 ~ 1×1 색종이를 모두 시도<ul>
<li>붙일 수 있다면 다음 재귀로 이동(인자는 현재까지 계산된 색종이의 총 합)</li>
</ul>
</li>
</ul>
</li>
<li>핵심 규칙<ul>
<li>붙일 때 해당 영역 0으로 변경</li>
<li>재귀 후 다시 1로 복귀</li>
<li>색종이 개수도 다시 복구</li>
<li>used &gt;= answer 면 가지치기로 재귀 탐색 x</li>
</ul>
</li>
<li>이동 처리<ul>
<li>매 DFS마다 왼쪽 위부터 첫 번째 1 탐색</li>
<li>(r,c)에서 size×size 범위 검사</li>
</ul>
</li>
<li>종료 조건<ul>
<li>더 이상 1이 없으면 정답 갱신</li>
</ul>
</li>
<li>시간복잡도<ul>
<li>완전탐색 + 백트래킹</li>
<li>가지치기로 줄임</li>
</ul>
</li>
</ul>
<h2 id="4-코드">4. 코드</h2>
<pre><code class="language-java">public class Main {
    static int[][] PAPER;
    static int[] TYPE_CNT = {0, 5, 5, 5, 5, 5};
    static int ANSWER;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;
        PAPER = new int[10][10];
        ANSWER = Integer.MAX_VALUE;

        for (int i = 0; i &lt; 10; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; 10; j++) {
                PAPER[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        dfs(0);

        System.out.println(ANSWER == Integer.MAX_VALUE ? -1 : ANSWER);
    }

    static void dfs(int used) {
        if (used &gt;= ANSWER) {
            return;
        }

        boolean found = false;
        int r = 0;
        int c = 0;
        for (r = 0; r &lt; 10; r++) {
            for (c = 0; c &lt; 10; c++) {
                if (PAPER[r][c] == 1) {
                    found = true;
                    break;
                }
            }
            if (found) {
                break;
            }
        }

        if (!found) {
            ANSWER = Math.min(ANSWER, used);
            return;
        }

        for (int size = 1; size &lt;= 5; size++) {
            if (!canGo(r,c,size))
                continue;
            attach(r, c, size, 0);
            TYPE_CNT[size]--;
            dfs(used + 1);
            attach(r, c, size, 1);
            TYPE_CNT[size]++;
        }
    }

    static boolean canGo(int r, int c, int size) {
        if (r + size &gt; 10 || c + size &gt; 10 || TYPE_CNT[size] == 0) {
            return false;
        }

        for (int nr = r; nr &lt; r + size; nr++) {
            for (int nc = c; nc &lt; c + size; nc++) {
                if (PAPER[nr][nc] == 0) {
                    return false;
                }
            }
        }

        return true;
    }

    static void attach(int r, int c, int size, int value) {
        for (int nr = r; nr &lt; r + size; nr++) {
            for (int nc = c; nc &lt; c + size; nc++) {
                PAPER[nr][nc] = value;
            }
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 색종이 붙이기(17136)]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%83%89%EC%A2%85%EC%9D%B4-%EB%B6%99%EC%9D%B4%EA%B8%B017136</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%83%89%EC%A2%85%EC%9D%B4-%EB%B6%99%EC%9D%B4%EA%B8%B017136</guid>
            <pubDate>Tue, 17 Mar 2026 06:51:35 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어본 문제는 <a href="https://www.acmicpc.net/problem/17136">⭐색종이 붙이기</a> 라는 문제이다.</p>
<h2 id="1-문제요약">1. 문제요약</h2>
<ul>
<li>1×1, 2×2, 3×3, 4×4, 5×5 색종이가 총 5개씩 있다.</li>
<li>10×10 색종이를 위 5종류의 색종이로 채워야한다.<ul>
<li>각 칸에는 0,1이 적혀있다.</li>
<li>1에는 색종이로 가려야 하고, 0은 가려선 안된다.</li>
</ul>
</li>
<li>겹치면 안된다.</li>
</ul>
<h2 id="2-입출력">2. 입출력</h2>
<h3 id="출력">출력</h3>
<p>불가능한 경우 -1 출력</p>
<h2 id="3-알고리즘">3. 알고리즘</h2>
<ul>
<li>핵심 상태<ul>
<li>paper[][] : 현재 남아있는 1 영역</li>
<li>remain [] : 색종이 남은 개수</li>
<li>answer : 최소 사용 개수</li>
</ul>
</li>
<li>진행 방식<ul>
<li>dfs 함수 내에서, 이중 for문으로 행,열 처음부터 보며 1 찾음<ul>
<li>1 찾으면 즉시 중단 및 1 찾았다는 boolean 변수 true로 변경</li>
</ul>
</li>
<li>1 찾은 위치에 대해 5×5 ~ 1×1 색종이를 모두 시도<ul>
<li>붙일 수 있다면 다음 재귀로 이동(인자는 현재까지 계산된 색종이의 총 합)</li>
</ul>
</li>
</ul>
</li>
<li>핵심 규칙<ul>
<li>붙일 때 해당 영역 0으로 변경</li>
<li>재귀 후 다시 1로 복귀</li>
<li>색종이 개수도 다시 복구</li>
<li>used &gt;= answer 면 가지치기로 재귀 탐색 x</li>
</ul>
</li>
<li>이동 처리<ul>
<li>매 DFS마다 왼쪽 위부터 첫 번째 1 탐색</li>
<li>(r,c)에서 size×size 범위 검사</li>
</ul>
</li>
<li>종료 조건<ul>
<li>더 이상 1이 없으면 정답 갱신</li>
</ul>
</li>
<li>시간복잡도<ul>
<li>완전탐색 + 백트래킹</li>
<li>가지치기로 줄임</li>
</ul>
</li>
</ul>
<h2 id="4-코드">4. 코드</h2>
<pre><code class="language-java">public class Main {
    static int[][] PAPER;
    static int[] TYPE_CNT = {0, 5, 5, 5, 5, 5};
    static int ANSWER;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;
        PAPER = new int[10][10];
        ANSWER = Integer.MAX_VALUE;

        for (int i = 0; i &lt; 10; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j &lt; 10; j++) {
                PAPER[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        dfs(0);

        System.out.println(ANSWER == Integer.MAX_VALUE ? -1 : ANSWER);
    }

    static void dfs(int used) {
        if (used &gt;= ANSWER) {
            return;
        }

        boolean found = false;
        int r = 0;
        int c = 0;
        for (r = 0; r &lt; 10; r++) {
            for (c = 0; c &lt; 10; c++) {
                if (PAPER[r][c] == 1) {
                    found = true;
                    break;
                }
            }
            if (found) {
                break;
            }
        }

        if (!found) {
            ANSWER = Math.min(ANSWER, used);
            return;
        }

        for (int size = 1; size &lt;= 5; size++) {
            if (!canGo(r,c,size))
                continue;
            attach(r, c, size, 0);
            TYPE_CNT[size]--;
            dfs(used + 1);
            attach(r, c, size, 1);
            TYPE_CNT[size]++;
        }
    }

    static boolean canGo(int r, int c, int size) {
        if (r + size &gt; 10 || c + size &gt; 10 || TYPE_CNT[size] == 0) {
            return false;
        }

        for (int nr = r; nr &lt; r + size; nr++) {
            for (int nc = c; nc &lt; c + size; nc++) {
                if (PAPER[nr][nc] == 0) {
                    return false;
                }
            }
        }

        return true;
    }

    static void attach(int r, int c, int size, int value) {
        for (int nr = r; nr &lt; r + size; nr++) {
            for (int nc = c; nc &lt; c + size; nc++) {
                PAPER[nr][nc] = value;
            }
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 후보 추천하기]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%ED%9B%84%EB%B3%B4-%EC%B6%94%EC%B2%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%ED%9B%84%EB%B3%B4-%EC%B6%94%EC%B2%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 12 Mar 2026 04:13:27 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어본 문제는 <a href="https://www.acmicpc.net/problem/1713">⭐후보 추천하기</a>라는 문제이다.</p>
<h2 id="1-문제요약">1. 문제요약</h2>
<ol>
<li>사진틀은 비어있다.</li>
<li>특정 학생 추천 시, 등록된다.</li>
<li>사진틀이 비어있지 않다면 가장 추천횟수가 적은 학생을 삭제한다.<ul>
<li>추천횟수가 같다면 가장 오래된 사진을 삭제한다.</li>
<li>삭제된 학생의 추천 수는 0이 된다.</li>
</ul>
</li>
<li>이미 있는 학생이 추천받으면 추천 수가 오른다.</li>
</ol>
<h2 id="2-입출력">2. 입출력</h2>
<h3 id="입력">[입력]</h3>
<ul>
<li>사진틀의 개수 N이 주어진다. (1 ≤ N ≤ 20)</li>
<li>총 추천횟수 (1000 이하)</li>
<li>추천된 학생의 번호</li>
</ul>
<h2 id="3-알고리즘">3. 알고리즘</h2>
<ul>
<li>pq를 이용하는 문제</li>
</ul>
<h2 id="4-코드">4. 코드</h2>
<p>오랜만에 Map을 쓰니 좀 헷갈렸다. Map 복습용 문제!</p>
<pre><code class="language-java">
class Info implements Comparable&lt;Info&gt; {
    int num;
    int great;
    int order;

    public Info(int num, int great, int order) {
        this.num=num;
        this.great=great;
        this.order=order;
    }

    @Override
    public int compareTo(Info o){
        if(this.great == o.great)
            return this.order - o.order;
        return this.great - o.great;
    }

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

        int N = Integer.parseInt(br.readLine());
        int greatCnt = Integer.parseInt(br.readLine());

        //번호, 자료구조
        Map&lt;Integer, Info&gt; list = new HashMap&lt;&gt;();

        st = new StringTokenizer(br.readLine());
        int order = 0;
        while (greatCnt --&gt; 0) {
            int now = Integer.parseInt(st.nextToken());
            order++;
            if(list.containsKey(now))
            {
                list.get(now).great++;
                continue;
            }
            if(list.size() &gt;= N &amp;&amp; !list.containsKey(now)) {
                Info out = findOut(list);
                list.remove(out.num);
            }
            list.put(now, new Info(now,1, order));
        }

        List&lt;Integer&gt; keys = new ArrayList&lt;&gt;(list.keySet());
        Collections.sort(keys);

        StringBuilder sb = new StringBuilder();
        for (int key : keys) {
            sb.append(key).append(&quot; &quot;);
        }
        System.out.println(sb);
    }

    static Info findOut(Map&lt;Integer, Info&gt; list){
        PriorityQueue&lt;Info&gt; queue = new PriorityQueue&lt;&gt;();

        for(Info value : list.values()){
            queue.add(value);
        }

        return queue.poll();
    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/jung-min-ju/post/f2fd2a4f-3055-4a31-b763-ae1c0b7a1a77/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 - 수식 최대화]]></title>
            <link>https://velog.io/@jung-min-ju/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%88%98%EC%8B%9D-%EC%B5%9C%EB%8C%80%ED%99%94</link>
            <guid>https://velog.io/@jung-min-ju/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%88%98%EC%8B%9D-%EC%B5%9C%EB%8C%80%ED%99%94</guid>
            <pubDate>Thu, 05 Mar 2026 09:13:07 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어본 문제는 <a href="https://school.programmers.co.kr/learn/courses/30/lessons/67257">⭐수식 최대화</a> 이다.</p>
<h2 id="1-문제-요약">1. 문제 요약</h2>
<ul>
<li>3가지 연산 기호 <code>+</code>, <code>-</code>, <code>*</code>의 우선순위를 다르게 설정하여 계산</li>
<li>주어진 수식에서 만들 수 있는 결과값의 <strong>절댓값 중 최댓값</strong>을 구하는 문제</li>
<li>계산 과정에서 <strong>음수 결과가 발생할 수 있으므로 <code>-</code> 연산자와 음수 값의 구분이 필요</strong></li>
<li>연산자 우선순위는 총 <strong>3! = 6가지 조합</strong>이 존재하므로 모든 경우를 확인해야 함.</li>
</ul>
<h2 id="2-입출력">2. 입출력</h2>
<h3 id="입력">입력</h3>
<ul>
<li>expression<ul>
<li>숫자와 연산자(<code>+</code>, <code>-</code>, <code>*</code>)로 이루어진 문자열</li>
<li>길이: <strong>3 이상 100 이하</strong></li>
</ul>
</li>
<li>계산 중 값이 커질 수 있으므로 <strong>long 타입 사용</strong></li>
</ul>
<h3 id="출력">출력</h3>
<ul>
<li>주어진 식에서 <strong>연산자 우선순위를 적절히 설정했을 때 얻을 수 있는 결과값의 절댓값 중 최댓값 반환</strong></li>
</ul>
<h2 id="3-알고리즘">3. 알고리즘</h2>
<h3 id="1-수식-파싱">1. 수식 파싱</h3>
<ul>
<li><p>StringTokenizer를 이용하여 수식을 숫자 리스트(nums)와 연산자 리스트(ops)로 분리한다.</p>
</li>
<li><p>예시<br>100-200*300-500+20</p>
<p>nums = [100, 200, 300, 500, 20]<br>ops  = [&#39;-&#39;, &#39;*&#39;, &#39;-&#39;, &#39;+&#39;]</p>
</li>
</ul>
<h3 id="2-연산자-우선순위-경우의-수-생성">2. 연산자 우선순위 경우의 수 생성</h3>
<ul>
<li>가능한 연산자는 +, -, * 총 3개</li>
<li>모든 우선순위 경우는 3! = 6가지</li>
<li>permute()를 사용하여 연산자 우선순위 순열 생성</li>
</ul>
<h3 id="3-우선순위에-따른-계산">3. 우선순위에 따른 계산</h3>
<ul>
<li>각 우선순위마다 evaluate() 실행</li>
<li>연산자 우선순위 순서대로 식을 계산하면서 리스트를 줄여나감</li>
</ul>
<blockquote>
<h4 id="예시">[예시]</h4>
</blockquote>
<p><em><em>초기 상태  *</em>
nums = [100, 200, 300, 500, 20]<br>ops  = [&#39;-&#39;, &#39;</em>&#39;, &#39;-&#39;, &#39;+&#39;]</p>
<blockquote>
</blockquote>
<hr>
<p><strong>우선순위가 * 먼저일 경우</strong></p>
<ul>
<li>ops[1] = *;</li>
<li>nums[1]과 nums[2]가 피연산자!<blockquote>
</blockquote>
즉 200 * 300 = 60000 의 새로운 결과 도출됨<blockquote>
</blockquote>
</li>
<li><em>리스트 갱신*</em><blockquote>
</blockquote>
1) 60000의 결과값을 현재 nums[i] 에 넣음 (i=1)<ul>
<li>nums = [100, 60000, 300, 500, 20]<blockquote>
<pre><code class="language-java">nums.set(i, 60000);</code></pre>
<p>2) 쓰인 피연산자 300을 nums에서 삭제해줌</p>
<pre><code class="language-java">nums.remove(i + 1);</code></pre>
</blockquote>
3) 쓰인 연산자 *을 ops에서 삭제해줌<pre><code> - ops  = [&#39;-&#39;, &#39;-&#39;, &#39;+&#39;]</code></pre><blockquote>
<pre><code class="language-java">ops.remove(i);</code></pre>
<hr>
</blockquote>
이 과정을 우선순위 순서대로 반복</li>
</ul>
</li>
</ul>
<h3 id="4-최대-절댓값-갱신">4. 최대 절댓값 갱신</h3>
<ul>
<li>모든 계산이 끝나면 결과는 nums[0]</li>
<li>결과의 절댓값을 계산하여 최대값 갱신</li>
</ul>
<p>best = max(best, abs(result))</p>
<h3 id="5-최종-결과-반환">5. 최종 결과 반환</h3>
<ul>
<li>모든 우선순위(6가지)를 검사한 후</li>
<li>가장 큰 절댓값 반환</li>
</ul>
<h2 id="4-코드">4. 코드</h2>
<pre><code class="language-java">import java.util.*;

class Solution {
    private static final char[] OPS = {&#39;+&#39;, &#39;-&#39;, &#39;*&#39;};
    private static boolean[] used;
    private static long best;

    public long solution(String expression) {
        List&lt;Long&gt; nums = new ArrayList&lt;&gt;();
        List&lt;Character&gt; ops = new ArrayList&lt;&gt;();
        used = new boolean[3];

        parse(expression, nums, ops);
        permute(0, new char[3], nums, ops);

        return best;
    }

    private static void parse(String expr, List&lt;Long&gt; nums, List&lt;Character&gt; ops) {
        StringTokenizer st = new StringTokenizer(expr, &quot;+-*&quot;, true);

        while (st.hasMoreTokens()) {
            String token = st.nextToken();

            if (token.equals(&quot;+&quot;) || token.equals(&quot;-&quot;) || token.equals(&quot;*&quot;)) {
                ops.add(token.charAt(0));
            } else {
                nums.add(Long.parseLong(token));
            }
        }
    }

    private static void permute(int depth, char[] order, List&lt;Long&gt; baseNums, List&lt;Character&gt; baseOps) {
        if (depth == 3) {
            long val = evaluate(order, baseNums, baseOps);
            best = Math.max(best, Math.abs(val));
            return;
        }

        for (int i = 0; i &lt; 3; i++) {
            if (used[i]) continue;
            used[i] = true;
            order[depth] = OPS[i];
            permute(depth + 1, order, baseNums, baseOps);
            used[i] = false;
        }
    }

    private static long evaluate(char[] order, List&lt;Long&gt; baseNums, List&lt;Character&gt; baseOps) {
        // ⭐복사본에서 작업
        List&lt;Long&gt; nums = new ArrayList&lt;&gt;(baseNums);
        List&lt;Character&gt; ops = new ArrayList&lt;&gt;(baseOps);

        for (char target : order) {
            for (int i = 0; i &lt; ops.size(); ) {
                if (ops.get(i) == target) {
                    long a = nums.get(i);
                    long b = nums.get(i + 1);
                    long r = calc(a, b, target);

                    nums.set(i, r);
                    nums.remove(i + 1);
                    ops.remove(i);
                } else {
                    i++;
                }
            }
        }
        return nums.get(0);
    }

    private static long calc(long a, long b, char op) {
        if (op == &#39;+&#39;) return a + b;
        if (op == &#39;-&#39;) return a - b;
        return a * b;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 도영이 음식(2961) / 비트마스킹 + 조합]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%8F%84%EC%98%81%EC%9D%B4-%EC%9D%8C%EC%8B%9D2961-%EB%B9%84%ED%8A%B8%EB%A7%88%EC%8A%A4%ED%82%B9-%EC%A1%B0%ED%95%A9</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%8F%84%EC%98%81%EC%9D%B4-%EC%9D%8C%EC%8B%9D2961-%EB%B9%84%ED%8A%B8%EB%A7%88%EC%8A%A4%ED%82%B9-%EC%A1%B0%ED%95%A9</guid>
            <pubDate>Wed, 04 Mar 2026 18:18:30 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어본 문제는 <a href="https://www.acmicpc.net/problem/2961">⭐도영이가 만든 맛있는 음식</a> 이라는 문제다.</p>
<h2 id="1-문제-요약">1. 문제 요약</h2>
<ul>
<li>각 재료마다 쓴 맛과 신 맛이 주어진다.</li>
<li>주어진 재료들을 적당히 섞어 쓴 맛과 신 맛의 차이를 최소로 줄이고자 한다.<ul>
<li>신 맛은 사용된 재료들의 총곱 / 쓴 맛은 사용된 재료들의 총합</li>
</ul>
</li>
<li>재료는 1개 이상 무조건 사용한다.</li>
</ul>
<h2 id="2-입출력">2. 입출력</h2>
<h3 id="입력">[입력]</h3>
<ul>
<li>재료의 개수 N(1 ≤ N ≤ 10)</li>
<li>1,000,000,000보다 작은 양의 정수</li>
</ul>
<h3 id="출력">[출력]</h3>
<ul>
<li>신맛과 쓴맛의 가장 최소 차이를 출력하라.</li>
</ul>
<h2 id="3-알고리즘">3. 알고리즘</h2>
<ul>
<li>조합 문제</li>
<li>자료의 개수가 10개. boolean 대신 비트마스킹을 써서 표시해보기</li>
<li>Long형 써야 함</li>
</ul>
<h2 id="4-코드">4. 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
    static int N;
    static int [][] TASTE;
    static long ANSWER;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        N = Integer.parseInt(br.readLine());
        TASTE = new int[N][2];

        for(int i=0; i&lt;N; i++){
            TASTE[i] = Arrays.stream(br.readLine().split(&quot; &quot;))
                    .mapToInt(s -&gt; Integer.parseInt(s))
                    .toArray();
        }

        ANSWER = Long.MAX_VALUE;
        johap(0, 0, 1, 0);

        System.out.println(ANSWER);
    }

    static void johap (int start, int visit, long sour, long bitter) {
        if(start == N)
            return;

        for(int i = start; i&lt;N; i++){
            if ((visit &amp; (1 &lt;&lt; i)) != 0) continue;
            visit |= (1&lt;&lt;i);
            long ns = sour * TASTE[i][0];
            long nb = bitter + TASTE[i][1];
            long newTaste = Math.abs(ns - nb);
            ANSWER = Math.min(ANSWER, newTaste);
            johap(i + 1, visit, sour * TASTE[i][0], bitter + TASTE[i][1]);
            visit &amp;= ~(1&lt;&lt;i);
        }
    }
}</code></pre>
<h2 id="5-알게된-점">5. 알게된 점</h2>
<p>사실 비트마스킹을 연습해보고자 푼 문제다. </p>
<blockquote>
<h3 id="비트마스킹으로-현재-방문했는지-확인하는-조건문">비트마스킹으로 현재 방문했는지 확인하는 조건문</h3>
</blockquote>
<pre><code class="language-java">if ((visit &amp; (1 &lt;&lt; i)) != 0) {
    continue;
}</code></pre>
<blockquote>
<h3 id="비트마스킹으로-i번째-방문체크">비트마스킹으로 i번째 방문체크</h3>
</blockquote>
<pre><code class="language-java"> visit |= (1&lt;&lt;i);</code></pre>
<p>visit의 초기값은 0에서 시작한다.</p>
<blockquote>
<h3 id="비트마스킹으로-i번째-방문해제">비트마스킹으로 i번째 방문해제</h3>
</blockquote>
<pre><code class="language-java">visit &amp;= ~(1&lt;&lt;i);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 로또(6603) / 조합]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%A1%9C%EB%98%906603-%EC%A1%B0%ED%95%A9</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%A1%9C%EB%98%906603-%EC%A1%B0%ED%95%A9</guid>
            <pubDate>Wed, 04 Mar 2026 17:09:46 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어본 문제는 <a href="https://www.acmicpc.net/problem/6603">⭐로또</a> 이다.</p>
<h2 id="1-문제-요약">1. 문제 요약</h2>
<ul>
<li>각 줄마다 k와 k개의 수가 주어짐. 해당 주어진 수들 중 6개의 수를 선택하는 경우의 수를 모두 출력할것</li>
<li>각 케이스마다 경우의 수는 오름차순으로 출력하고, 케이스간 한 줄씩 띄어쓰기로 출력한다.</li>
</ul>
<h2 id="2-알고리즘">2. 알고리즘</h2>
<p>해당 문제는 조합론 공부를 위해 풀어보았다.</p>
<p>이 문제의 중요점은 다음과 같다.</p>
<ul>
<li>한 번 선택된 번호는 다시 뽑을 수 없다.</li>
<li>6개의 번호만을 뽑아야 한다.</li>
</ul>
<p>처음에는 아무생각없이 순열 만드듯이 하다가 틀렸다는 걸 깨달았다.</p>
<h2 id="3-코드">3. 코드</h2>
<p>어렵지 않으니 까먹을 때 마다 자주봐두자</p>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    static int K;
    static int[] ARR;

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

        while (true) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            K = Integer.parseInt(st.nextToken());
            if (K == 0) break;

            ARR = new int[K];
            for (int i = 0; i &lt; K; i++) ARR[i] = Integer.parseInt(st.nextToken());
            Arrays.sort(ARR);

            johap(0, 0, new StringBuilder());
            System.out.println();
        }
    }

    static void johap(int start, int depth, StringBuilder sb) {
        if (depth == 6) {
            System.out.println(sb.toString().trim());
            return;
        }

        for (int i = start; i &lt; K; i++) {
            int len = sb.length();
            sb.append(ARR[i]).append(&#39; &#39;);
            johap(i + 1, depth + 1, sb);
            sb.setLength(len); // 백트래킹(원복)
        }
    }
}</code></pre>
<h2 id="4-알아둬야-할-점">4. 알아둬야 할 점</h2>
<blockquote>
<p>sb.setLength(len); // 백트래킹(원복)</p>
</blockquote>
<p>StringBuilder는 자주 쓰니까, 해당 함수 정도는 기억해두자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 반도체 설계(2352) / LIS + 이분탐색]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%B0%98%EB%8F%84%EC%B2%B4-%EC%84%A4%EA%B3%842352-mhr89ahm</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%B0%98%EB%8F%84%EC%B2%B4-%EC%84%A4%EA%B3%842352-mhr89ahm</guid>
            <pubDate>Tue, 03 Mar 2026 15:41:58 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어볼 문제는 <a href="https://www.acmicpc.net/problem/2352">⭐반도체 설계</a>라는 문제이다.</p>
<h2 id="1-문제-요약">1. 문제 요약</h2>
<ul>
<li>서로 다른 포트를 연결하고자 한다.</li>
<li>이떄 포트간 선이 겹치지 않게 놓을 수 있는 최대 갯수를 구하라.
<img src="https://velog.velcdn.com/images/jung-min-ju/post/1e5dcd81-887c-46a0-8e8b-90d1bc746926/image.png" alt=""></li>
</ul>
<h2 id="2-입출력">2. 입출력</h2>
<h3 id="입력">[입력]</h3>
<ul>
<li>n개의 정수(1 ≤ n ≤ 40,000)</li>
<li>차례로 1번 포트부터, n개까지 연결되어야 하는 반대쪽 포트 번호가 주어진다.</li>
</ul>
<h3 id="출력">[출력]</h3>
<ul>
<li>포트 선이 겹치지 않게 최대 연결 개수를 출력한다.</li>
</ul>
<h2 id="3-알고리즘">3. 알고리즘</h2>
<ul>
<li>특정 조건을 만족하는 최대의 개수 구하기 <a href="https://velog.io/@jung-min-ju/LIS%EC%B5%9C%EC%9E%A5-%EC%A6%9D%EA%B0%80-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4">(=LIS)</a></li>
<li>개수가 무려 40,000이므로, 일반적인 LIS O(n^2)가 아닌, 이분탐색을 활용한 O(LogN2)로 풀어야 한다.</li>
</ul>
<h2 id="4-코드">4. 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

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

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

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

            // lower_bound
            int l = 0, r = len;
            while (l &lt; r) {
                int m = (l + r) &gt;&gt;&gt; 1;
                if(binary[m] &gt;= value)
                    r = m;
                else
                    l = m + 1;
            }
            binary[l] = value;
            if (l == len) len++; //⭐핵심코드!!⭐
        }

        System.out.println(len);
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/jung-min-ju/post/99e8e748-59a7-425c-803b-57f75a6839a4/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[최장 증가 부분 수열 (LIS)]]></title>
            <link>https://velog.io/@jung-min-ju/LIS%EC%B5%9C%EC%9E%A5-%EC%A6%9D%EA%B0%80-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4</link>
            <guid>https://velog.io/@jung-min-ju/LIS%EC%B5%9C%EC%9E%A5-%EC%A6%9D%EA%B0%80-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4</guid>
            <pubDate>Mon, 02 Mar 2026 09:11:40 GMT</pubDate>
            <description><![CDATA[<h2 id="lis-최장-증가-부분-수열이란">LIS, 최장 증가 부분 수열이란?</h2>
<p>원소가 n개인 배열의 일부 원소를 골라내서 만든 부분 수열 중, 각 원소가 이전 원소보다 크다는 조건을 만족하고, 그 길이가 최대인 부분 수열을 최장 증가 부분 수열이라고 합니다.</p>
<blockquote>
<p>예를 들어, { 6, <strong>2</strong>, <strong>5</strong>, 1, <strong>7</strong>, 4, <strong>8</strong>, 3} 이라는 배열이 있을 경우, LIS는 { 2, 5, 7, 8 } 이 됩니다.
{ 2, 5 }, { 2, 7 } 등 증가하는 부분 수열은 많지만 그 중에서 가장 긴 것이 { 2, 5, 7, 8 } 입니다.</p>
</blockquote>
<hr>
<p>일반적으로 최장 증가 부분 수열의 길이가 얼마인지 푸는 간편한 방법은 DP를 이용하는 것입니다.</p>
<p>아래에서 length[i]는 i번째 인덱스에서 끝나는 최장 증가 부분 수열의 길이를 의미합니다.</p>
<pre><code class="language-java">for (int i = 0; i &lt; n; i++){
    length[i] = 1;
    for (int k = 0; k &lt; i; k++){
        if (arr[k] &lt; arr[i]){
            length[i] = max(length[i], length[k] + 1);
        }
    }
}</code></pre>
<p>주어진 배열에서 인덱스를 하나씩(i+1) 늘려가면서 확인합니다. 
그리고 내부 반복문으로 i보다 작은 인덱스들을 하나씩 살펴보면서 arr[k] &lt; arr[i]인 것이 있을 경우, length[i]를 업데이트합니다.</p>
<p>업데이트하는 기준은,</p>
<ul>
<li>(1) k번째 인덱스에서 끝나는 최장 증가 부분 수열의 마지막에 arr[i]를 추가했을 때의 LIS 길이와</li>
<li>(2) 추가하지 않고 기존의 length[i] 값</li>
</ul>
<p>둘 중에 더 큰 값으로 length[i] 값을 업데이트합니다.</p>
<h2 id="lis-최적화-방법">LIS 최적화 방법</h2>
<p>위와 같은 방식으로 진행할 시, 시간복잡도는 O(N^2)에 도달합니다.</p>
<p>이런 느린 알고리즘을 최적화 하는 방법은 이분탐색을 활용하는 것 입니다.</p>
<p><img src="https://velog.velcdn.com/images/jung-min-ju/post/0ec1a048-70b5-4e23-b203-5b23176aa537/image.png" alt=""></p>
<p>시간복잡도를  O(nlog n)으로 줄일 수 있습니다.</p>
<h2 id="관련-문제">관련 문제</h2>
<p><a href="https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%B0%98%EB%8F%84%EC%B2%B4-%EC%84%A4%EA%B3%842352">- 반도체 설계(b_2352)</a></p>
<hr>
<p>참고 블로그</p>
<p><a href="https://chanhuiseok.github.io/posts/algo-49/#2352%EB%B2%88--%EB%B0%98%EB%8F%84%EC%B2%B4-%EC%84%A4%EA%B3%84---%EB%AC%B8%EC%A0%9C-%EC%A1%B0%EA%B1%B4%EA%B3%BC-%EC%84%A4%EB%AA%85">LIS란?</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 상자넣기(1965) / LIS]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%83%81%EC%9E%90%EB%84%A3%EA%B8%B01965</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%83%81%EC%9E%90%EB%84%A3%EA%B8%B01965</guid>
            <pubDate>Mon, 02 Mar 2026 09:07:38 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어본 문제는<a href="https://www.acmicpc.net/problem/1965"> ⭐상자넣기(1965) </a>라는 문제이다.</p>
<h2 id="1-문제-요약">1. 문제 요약</h2>
<ul>
<li>상자의 크기가 일렬로 주어지고, 앞 상자(바로 앞x)가 현재 상자보다 작으면, 현재 상자에 넣을 수 있다.</li>
<li>한 번에 넣을 수 있는 최대 상자 개수?</li>
</ul>
<h2 id="2-입출력">2. 입출력</h2>
<h3 id="입력">입력</h3>
<ul>
<li>상자의 개수 (1 ≤ n ≤ 1000)</li>
</ul>
<h3 id="출력">출력</h3>
<p>넣을 수 있는 최대의 상자 개수</p>
<h2 id="3-알고리즘">3. 알고리즘</h2>
<ul>
<li>그리디 아닌 이유 -&gt; 현재 상자를 뒤에 등장할 상자에 넣을 수 있는지 없는지 현재 시점에서 알 수 없음.</li>
<li>DP 문제</li>
</ul>
<p>그 중에서도 <strong>LIS(최장 증가 부분 수열)</strong> 문제임</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 숫자 카드2 (10816) / 이분탐색]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%88%AB%EC%9E%90-%EC%B9%B4%EB%93%9C2</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%88%AB%EC%9E%90-%EC%B9%B4%EB%93%9C2</guid>
            <pubDate>Sat, 28 Feb 2026 13:54:21 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어본 문제는 <a href="https://www.acmicpc.net/problem/10816">⭐숫자 카드2</a> 라는 문제이다.</p>
<h2 id="1-문제-요약">1. 문제 요약</h2>
<ul>
<li>정수가 적힌 숫자 카드가 있다.</li>
<li>상근이는 여러 장의 카드를 가지고 있다. (같은 숫자의 카드가 여러 장 있을 수 있음)</li>
<li>특정 숫자가 적힌 카드를 몇 장 가지고 있는지 구하는 문제이다.</li>
</ul>
<hr>
<h2 id="2-입출력">2. 입출력</h2>
<h3 id="입력">입력</h3>
<h4 id="①-상근이가-가진-카드-정보">① 상근이가 가진 카드 정보</h4>
<ul>
<li>카드 개수 <code>N</code> (1 ≤ N ≤ 500,000)</li>
<li>각 카드에 적힌 정수<br>(-10,000,000 ≤ 숫자 ≤ 10,000,000)</li>
</ul>
<h4 id="②-확인해야-할-카드-정보">② 확인해야 할 카드 정보</h4>
<ul>
<li>알고 싶은 카드 개수 <code>M</code></li>
<li>확인할 카드에 적힌 정수<br>(-10,000,000 ≤ 숫자 ≤ 10,000,000)</li>
</ul>
<hr>
<h3 id="출력">출력</h3>
<ul>
<li>입력으로 주어진 각 숫자 카드에 대해<br>상근이가 몇 장 가지고 있는지 공백으로 구분하여 출력한다.</li>
</ul>
<hr>
<h2 id="3-첫-번째-구현-방법">3. 첫 번째 구현 방법</h2>
<h3 id="접근-방식">접근 방식</h3>
<ol>
<li>카드 배열을 정렬한다.</li>
<li>이분 탐색으로 특정 숫자를 찾는다.</li>
<li>찾은 위치에서 왼쪽/오른쪽으로 확장하며 같은 숫자의 개수를 센다.</li>
</ol>
<hr>
<h3 id="문제점">문제점</h3>
<p>이 방식은 최악의 경우 시간 초과가 발생할 수 있다.</p>
<p>이분 탐색 자체는 <code>O(log N)</code>이지만,</p>
<ul>
<li>같은 숫자가 많을 경우 왼쪽으로 최대 <code>N/2</code></li>
<li>오른쪽으로 최대 <code>N/2</code></li>
</ul>
<p>까지 탐색해야 한다.</p>
<p>즉, 한 번의 질의에 최대 <code>O(N)</code>이 걸릴 수 있다.</p>
<p>*<em>만약 카드의 개수가 최대치라면? *</em></p>
<ul>
<li>카드 하나를 찾는 데 걸리는 최악 시간: <code>O(N)</code></li>
<li>질의가 M개일 때: <code>O(N × M)</code></li>
</ul>
<p>N, M이 최대 500,000일 경우 
500,000 × 500,000 = 250,000,000,000</p>
<p>사실상 <code>O(N²)</code> 수준이 되어 시간 초과가 발생한다.</p>
<hr>
<h2 id="4-정답-구현-방법">4. 정답 구현 방법</h2>
<h3 id="핵심-아이디어">핵심 아이디어</h3>
<p>정렬된 배열에서 특정 값 x의 개수는 다음처럼 구할 수 있다.</p>
<ul>
<li><code>lower_bound(x)</code> : x가 처음 등장하는 위치</li>
<li><code>upper_bound(x)</code> : x보다 큰 값이 처음 등장하는 위치</li>
</ul>
<p>따라서 x의 등장 횟수는</p>
<p>count(x) = upper_bound(x) - lower_bound(x)</p>
<p><img src="https://velog.velcdn.com/images/jung-min-ju/post/2fb7a4c7-89fc-4c56-bc88-f253a4c982bc/image.png" alt=""></p>
<p>이분탐색 upper bound / lower bound 찾기</p>
<p>둘 다 구조가 동일하다. 하지만 lower의 경우, 찾는 범위가 =에 포함되어야 하고, upper은 포함되지 않는다는 것이 다르다.</p>
<blockquote>
<h4 id="lower-bound">[lower bound]</h4>
<p>: lower의 경우, 현재 타겟과 같은 값을 찾았다면 그건 정답일 가능성이 있다. 
=&gt; 그렇기에 인덱스를 옮기지 않고 hi만 내린다.</p>
</blockquote>
<pre><code class="language-java">int lo = 0;
int hi = N;

while( lo &lt; hi ) {
  int mid = (lo + hi) / 2;
  if ( value [mid] &lt;= target )
    hi = mid;
 else 
    lo = mid + 1;
}
return lo;</code></pre>
<blockquote>
<h4 id="upper-bound">[upper bound]</h4>
<p>:  upper의 경우, 현재 값이 찾는 값과 같다면, 범위에서 벗어나는 것이다. 
(upper은 찾는 수가 처음으로 초과되는 인덱스를 찾는다.) 
=&gt; 그렇기에 같은 경우는 밑의 케이스은 mid + 1을 통해 lo를 올린다.</p>
</blockquote>
<pre><code class="language-java">int lo = 0;
int hi = N;

while( lo &lt; hi ) {
  int mid = (lo + hi) / 2;
  if ( value [mid] &lt; target ) 
    hi = mid;
 else 
    lo = mid + 1;
}
return lo;</code></pre>
<hr>
<p><img src="https://velog.velcdn.com/images/jung-min-ju/post/779a0bba-2c7f-4047-a068-ff5352a8ad23/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jung-min-ju/post/cbc381fa-8a31-44c2-b2a3-b891d81b238a/image.png" alt=""></p>
<h2 id="5-정답-코드">5. 정답 코드</h2>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    static int N;
    static int[] number;

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

        N = Integer.parseInt(br.readLine());
        number = new int[N];

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

        Arrays.sort(number);

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

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i &lt; M; i++) {
            int x = Integer.parseInt(st.nextToken());
            sb.append(upperBound(x) - lowerBound(x));
            if (i &lt; M - 1) {
                sb.append(&quot; &quot;);
            }
        }

        System.out.println(sb.toString());
    }

    static int lowerBound(int target) {
        int lo = 0;
        int hi = N;

        while (lo &lt; hi) {
            int mid = (hi + lo) / 2;
            if (number[mid] &gt;= target) {
                hi = mid;
            } else {
                lo = mid + 1;
            }
        }
        return lo;
    }

    static int upperBound(int target) {
        int lo = 0;
        int hi = N;

        while (lo &lt; hi) {
            int mid = (hi + lo) / 2;
            if (number[mid] &gt; target) {
                hi = mid;
            } else {
                lo = mid + 1;
            }
        }
        return lo;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 트럭(13335)]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%ED%8A%B8%EB%9F%AD13335</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%ED%8A%B8%EB%9F%AD13335</guid>
            <pubDate>Fri, 27 Feb 2026 05:52:41 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어본 문제는 <a href="https://www.acmicpc.net/problem/13335">⭐트럭</a> 이란 문제다.</p>
<h2 id="1-문제요약">1. 문제요약</h2>
<ul>
<li>n개의 트럭이 다리를 건너려고 한다.</li>
<li>1초에 1씩 움직인다는 가정. 다리에 올라간 트럭 무게 총 합 &lt;= L</li>
<li>트럭길이는 모두 1로 돋일</li>
<li>땅과 걸쳐져있는 트럭은 무게에 합하지 않음(자리 차지만)</li>
<li>모든 트럭이 건너는 최단 시간 구하기</li>
</ul>
<h2 id="2-입력">2. 입력</h2>
<ul>
<li>n개의 트럭 무게</li>
<li>다리길이 w</li>
<li>다리하중 L</li>
</ul>
<h2 id="3-알고리즘---그리디">3. 알고리즘 - 그리디</h2>
<ul>
<li>다리길이만큼 어레이리스트를 만듦</li>
<li>큐에 트럭 무게, 끝을 나타내는 boolean 변수 담은 클래스 넣음</li>
<li>큐에 w만큼 0을 넣음. (빈 칸이라는 의미)</li>
</ul>
<h2 id="4-내-코드">4. 내 코드</h2>
<pre><code class="language-java">import java.io.*;
import java.util.*;

class Info {
    int weight;
    boolean end;

    public Info(int weight, boolean end){
        this.weight = weight;
        this.end = end;
    }
}

public class Main {
    static Queue&lt;Info&gt; bridge;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int n,w,l;

        n = Integer.parseInt(st.nextToken());
        w = Integer.parseInt(st.nextToken());
        l = Integer.parseInt(st.nextToken());

        bridge = new ArrayDeque&lt;&gt;();
        Queue&lt;Info&gt; truck = new ArrayDeque&lt;&gt;();

        st = new StringTokenizer(br.readLine());
        for(int i=0; i&lt;n; i++) {
            int weight = Integer.parseInt(st.nextToken());
            boolean end = i==n-1;
            truck.add(new Info(weight, end));
        }

        for(int i=0; i&lt;w; i++){
            bridge.add(new Info(0, false));
        }

        Info nowTruck = bridge.peek();
        int time = 0;
        while (!nowTruck.end) {
            time++;
            bridge.poll();
            int nowBridgeWeight = calculateBridgeSum();
            if(!truck.isEmpty() &amp;&amp; truck.peek().weight  + nowBridgeWeight &lt;= l)
                bridge.add(truck.poll());
            else
                bridge.add(new Info(0,false));
            nowTruck = bridge.peek();
        }

        System.out.println(time+1);
    }

    static int calculateBridgeSum() {
        int sum = 0;
        for(Info truck : bridge) {
            sum += truck.weight;
        }
        return sum;
    }

}
</code></pre>
<h2 id="5-알게-된-점">5. 알게 된 점</h2>
<p>문제를 풀면서 궁금했던 점이 있다.</p>
<p>바로 두 가지 변수가 들어있는 클래스에 대한 List를 Stream으로 간편하게 계산할 수 있을까? 였다.</p>
<p>되긴 한다!!</p>
<pre><code class="language-java">            int nowBridgeWeight = bridge.stream()
                    .mapToInt(info -&gt; info.weight)
                    .sum();</code></pre>
<p>그러나 스트림으로 사용할 경우, for문보다 많이 느린 것을 확인할 수 있었다.</p>
<p>스트림은 다음과 같은 과정을 거친다.</p>
<blockquote>
<p>Collection → Spliterator → Pipeline → Terminal Operation</p>
</blockquote>
<p>그렇기에 단순 더하기만 하면 되는 기존 코드보다 느려질 수 밖에 없다.
스트림은 대용량 환경에서만 유리하다는 것!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL 공부 - null]]></title>
            <link>https://velog.io/@jung-min-ju/SQL-%EA%B3%B5%EB%B6%80-null</link>
            <guid>https://velog.io/@jung-min-ju/SQL-%EA%B3%B5%EB%B6%80-null</guid>
            <pubDate>Thu, 26 Feb 2026 14:46:23 GMT</pubDate>
            <description><![CDATA[<h2 id="1-null이란-무엇인가">1. NULL이란 무엇인가?</h2>
<p>NULL은 다음을 의미한다.</p>
<ul>
<li>값이 없음</li>
<li>값이 아직 정해지지 않음</li>
<li>값이 존재하지 않음</li>
</ul>
<p>중요한 점은</p>
<blockquote>
<p>NULL ≠ 0<br>NULL ≠ &#39;&#39; (빈 문자열)</p>
</blockquote>
<p>NULL은 아예 <strong>값 자체가 존재하지 않는 상태</strong>다.</p>
<hr>
<h2 id="2-null-비교는--로-하면-안-된다">2. NULL 비교는 = 로 하면 안 된다</h2>
<p>가장 많이 하는 실수.</p>
<pre><code class="language-sql">SELECT *
FROM DOCTOR
WHERE TLNO = NULL;</code></pre>
<p>이 조건은 항상 참이 되지 않는다.  </p>
<p>NULL은 값이 없기 때문에 <code>=</code> 비교 자체가 성립하지 않는다.</p>
<h3 id="올바른-방법">올바른 방법</h3>
<pre><code class="language-sql">SELECT *
FROM DOCTOR
WHERE TLNO IS NULL;</code></pre>
<p>NULL이 아닌 경우:</p>
<pre><code class="language-sql">SELECT *
FROM DOCTOR
WHERE TLNO IS NOT NULL;</code></pre>
<hr>
<h2 id="3-null과-연산">3. NULL과 연산</h2>
<p>NULL이 포함된 연산은 대부분 결과가 NULL이 된다.</p>
<pre><code class="language-sql">SELECT 10 + NULL;   -- 결과: NULL
SELECT NULL * 5;    -- 결과: NULL</code></pre>
<p>값이 없는데 계산할 수 없기 때문이다.</p>
<hr>
<h2 id="4-집계-함수와-null">4. 집계 함수와 NULL</h2>
<p>집계 함수는 NULL을 자동으로 제외한다.</p>
<p>예시 데이터:</p>
<table>
<thead>
<tr>
<th>score</th>
</tr>
</thead>
<tbody><tr>
<td>100</td>
</tr>
<tr>
<td>90</td>
</tr>
<tr>
<td>NULL</td>
</tr>
</tbody></table>
<pre><code class="language-sql">SELECT AVG(score)
FROM exam;</code></pre>
<p>결과는 95이다.<br>NULL은 계산에서 제외된다.</p>
<h3 id="count의-차이">COUNT의 차이</h3>
<pre><code class="language-sql">SELECT COUNT(*), COUNT(score)
FROM exam;</code></pre>
<ul>
<li>COUNT(*) → 전체 행 개수</li>
<li>COUNT(score) → NULL 제외 개수</li>
</ul>
<hr>
<h2 id="5-null-처리-함수">5. NULL 처리 함수</h2>
<h3 id="①-ifnull-mysql">① IFNULL (MySQL)</h3>
<pre><code class="language-sql">SELECT IFNULL(TLNO, &#39;번호없음&#39;)
FROM DOCTOR;</code></pre>
<p>NULL이면 &#39;번호없음&#39;으로 바꿔준다.</p>
<hr>
<h3 id="②-coalesce-표준-sql">② COALESCE (표준 SQL)</h3>
<pre><code class="language-sql">SELECT COALESCE(TLNO, &#39;번호없음&#39;)
FROM DOCTOR;</code></pre>
<ul>
<li>TLNO가 있으면 → 그대로 출력</li>
<li>TLNO가 NULL이면 → &#39;전화번호없음&#39; 출력</li>
<li>인자가 여러개여도, 왼쪽 -&gt; 오른쪽 순으로 대체재를 마련해주는 것</li>
</ul>
<pre><code class="language-sql">SELECT COALESCE(NULL, NULL, &#39;A&#39;, &#39;B&#39;);
-- 결과: A</code></pre>
<ul>
<li>여러 값 중 NULL이 아닌 첫 번째 값을 반환한다.</li>
</ul>
<hr>
<h2 id="6-where-절에서-null이-함정인-이유">6. WHERE 절에서 NULL이 함정인 이유</h2>
<pre><code class="language-sql">SELECT *
FROM EMP
WHERE salary &gt; 3000;</code></pre>
<p>salary가 NULL인 행은 비교 자체가 불가능하다.<br>TRUE도 FALSE도 아닌 <strong>UNKNOWN</strong>이 된다.</p>
<p>SQL은 3값 논리를 사용한다.</p>
<ul>
<li>TRUE</li>
<li>FALSE</li>
<li>UNKNOWN</li>
</ul>
<p>WHERE 절은 TRUE만 통과한다.</p>
<hr>
<h2 id="7-정렬에서-null">7. 정렬에서 NULL</h2>
<p>DBMS마다 다르지만, MySQL 기준:</p>
<ul>
<li>ASC → NULL 먼저</li>
<li>DESC → NULL 나중</li>
</ul>
<p>명시적으로 제어하는 방법 (Oracle 등):</p>
<pre><code class="language-sql">SELECT *
FROM EMP
ORDER BY salary DESC NULLS LAST;</code></pre>
<hr>
<h2 id="8-join에서-null-활용">8. JOIN에서 NULL 활용</h2>
<p>LEFT JOIN에서 자주 발생한다.</p>
<pre><code class="language-sql">SELECT *
FROM A
LEFT JOIN B ON A.id = B.id;</code></pre>
<p>매칭되지 않는 B의 컬럼은 NULL이 된다.</p>
<p>이때 다음 조건을 사용하면:</p>
<pre><code class="language-sql">SELECT *
FROM A
LEFT JOIN B ON A.id = B.id
WHERE B.id IS NULL;</code></pre>
<p>→ A에는 존재하지만 B에는 없는 데이터만 조회할 수 있다.</p>
<p>실무에서 매우 자주 쓰이는 패턴이다.</p>
<hr>
<h2 id="9-자주-틀리는-패턴">9. 자주 틀리는 패턴</h2>
<h3 id="❌-잘못된-예">❌ 잘못된 예</h3>
<pre><code class="language-sql">WHERE TLNO &lt;&gt; NULL;</code></pre>
<h3 id="✅-올바른-예">✅ 올바른 예</h3>
<pre><code class="language-sql">WHERE TLNO IS NOT NULL;</code></pre>
<hr>
<h2 id="10-핵심-요약">10. 핵심 요약</h2>
<ul>
<li>NULL은 값이 아니다.</li>
<li>= 비교 불가 → 반드시 IS NULL 사용.</li>
<li>연산 결과는 대부분 NULL.</li>
<li>집계 함수는 NULL 제외.</li>
<li>WHERE는 TRUE만 통과.</li>
<li>JOIN에서 NULL은 중요한 필터 조건이 된다.</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL 공부 - date / datetime / timestamp]]></title>
            <link>https://velog.io/@jung-min-ju/SQL-%EA%B3%B5%EB%B6%80-date-datetime-timestamp</link>
            <guid>https://velog.io/@jung-min-ju/SQL-%EA%B3%B5%EB%B6%80-date-datetime-timestamp</guid>
            <pubDate>Thu, 26 Feb 2026 10:09:03 GMT</pubDate>
            <description><![CDATA[<h2 id="1-date-vs-datetime-vs-timestamp">1. DATE vs DATETIME vs TIMESTAMP</h2>
<table>
<thead>
<tr>
<th>타입</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>DATE</td>
<td>날짜만 저장 (YYYY-MM-DD)</td>
</tr>
<tr>
<td>DATETIME</td>
<td>날짜 + 시간 저장 (YYYY-MM-DD HH:MM:SS)</td>
</tr>
<tr>
<td>TIMESTAMP</td>
<td>날짜 + 시간 저장 (시간대 영향 받음, 시스템 시간 기반)</td>
</tr>
</tbody></table>
<pre><code class="language-sql">DATE      → 2021-10-05
DATETIME  → 2021-10-05 14:32:10</code></pre>
<hr>
<h2 id="2-날짜-비교의-기본-원리">2. 날짜 비교의 기본 원리</h2>
<p>DATETIME은 문자열처럼 보이지만 실제로는 <strong>날짜 타입</strong>이다.<br>따라서 문자열 비교(LIKE)보다 <strong>범위 비교</strong>가 정확하다.</p>
<h3 id="❌-잘못된-방식-문자열처럼-처리">❌ 잘못된 방식 (문자열처럼 처리)</h3>
<pre><code class="language-sql">WHERE JOINED LIKE &#39;2021%&#39;</code></pre>
<ul>
<li>DATE 타입을 문자열처럼 비교</li>
<li>인덱스를 못 탈 가능성 있음</li>
<li>실무에서는 권장되지 않음</li>
</ul>
<hr>
<h3 id="✅-올바른-방식-범위-비교">✅ 올바른 방식 (범위 비교)</h3>
<pre><code class="language-sql">WHERE JOINED &gt;= &#39;2021-01-01&#39;
  AND JOINED &lt;  &#39;2022-01-01&#39;</code></pre>
<p>이 방식의 장점:</p>
<ol>
<li>2021년 전체를 정확히 포함</li>
<li>인덱스 사용 가능</li>
<li>가장 안정적인 방법</li>
</ol>
<hr>
<h2 id="3-특정-연도월-추출하기">3. 특정 연도/월 추출하기</h2>
<h3 id="year-함수">YEAR 함수</h3>
<pre><code class="language-sql">WHERE YEAR(JOINED) = 2021</code></pre>
<h3 id="month-함수">MONTH 함수</h3>
<pre><code class="language-sql">WHERE MONTH(JOINED) = 10</code></pre>
<p>주의할 점:</p>
<blockquote>
<p>컬럼에 함수를 적용하면 인덱스를 사용하기 어려워질 수 있다.</p>
</blockquote>
<p>따라서 대용량 데이터에서는 범위 조건이 더 좋다.</p>
<hr>
<h2 id="4-between-사용법">4. BETWEEN 사용법</h2>
<h3 id="날짜-범위-조회">날짜 범위 조회</h3>
<pre><code class="language-sql">WHERE JOINED BETWEEN &#39;2021-01-01&#39; AND &#39;2021-12-31&#39;</code></pre>
<p>주의:</p>
<ul>
<li>BETWEEN은 양 끝 값을 <strong>포함</strong>한다.</li>
<li>DATETIME 타입이라면 시간까지 고려해야 한다.</li>
</ul>
<p>예:</p>
<pre><code class="language-sql">&#39;2021-12-31 23:59:59&#39;</code></pre>
<p>을 포함하려면 정확히 명시해야 한다.</p>
<p>그래서 실무에서는 보통 다음 방식을 더 많이 사용한다:</p>
<pre><code class="language-sql">JOINED &gt;= &#39;2021-01-01&#39;
AND JOINED &lt;  &#39;2022-01-01&#39;</code></pre>
<p><span style = color:red> <strong>주의점!!!</strong></span></p>
<p>만약 날짜 리터럴은 반드시 작은따옴표로 감싸지 않으면?</p>
<p>SQL 서버에서는 이를 &quot;계산식&quot;으로 착각한다.
즉 2021 - 1 - 1 = 2019? 로 인식하게 된다.!!</p>
<hr>
<h2 id="5-가장-빠른-데이터-찾기">5. 가장 빠른 데이터 찾기</h2>
<h3 id="가장-오래된-날짜-1개-조회">가장 오래된 날짜 1개 조회</h3>
<pre><code class="language-sql">SELECT *
FROM ANIMAL_INS
ORDER BY DATETIME ASC
LIMIT 1;</code></pre>
<h3 id="가장-최신-날짜-1개-조회">가장 최신 날짜 1개 조회</h3>
<pre><code class="language-sql">SELECT *
FROM ANIMAL_INS
ORDER BY DATETIME DESC
LIMIT 1;</code></pre>
<hr>
<h2 id="6-집계와-함께-사용하기">6. 집계와 함께 사용하기</h2>
<h3 id="연도별-가입자-수">연도별 가입자 수</h3>
<pre><code class="language-sql">SELECT YEAR(JOINED) AS YEAR, COUNT(*) AS CNT
FROM USER_INFO
GROUP BY YEAR(JOINED);</code></pre>
<h3 id="월별-가입자-수">월별 가입자 수</h3>
<pre><code class="language-sql">SELECT MONTH(JOINED) AS MONTH, COUNT(*) AS CNT
FROM USER_INFO
GROUP BY MONTH(JOINED);</code></pre>
<hr>
<h2 id="7-null과-날짜">7. NULL과 날짜</h2>
<p>날짜 컬럼이 NULL이면:</p>
<pre><code class="language-sql">WHERE JOINED &gt;= &#39;2021-01-01&#39;</code></pre>
<p>조건에서 자동으로 제외된다.</p>
<p>NULL을 포함하고 싶다면:</p>
<pre><code class="language-sql">WHERE JOINED IS NULL</code></pre>
<hr>
<h2 id="8-정리">8. 정리</h2>
<ol>
<li>날짜는 문자열처럼 다루지 않는다.</li>
<li>특정 연도 조회는 범위 조건이 가장 안전하다.</li>
<li>BETWEEN은 양 끝값을 포함한다.</li>
<li>함수(YEAR, MONTH)는 편리하지만 인덱스에 불리할 수 있다.</li>
<li>가장 빠른/늦은 날짜는 ORDER BY + LIMIT 1이 직관적이다.</li>
</ol>
<hr>
<h2 id="9-날짜-데이터-포맷하기">9. 날짜 데이터 포맷하기</h2>
<h3 id="mysql">MYSQL</h3>
<blockquote>
<p>DATE_FORMAT(DATE, &#39;%Y-%m-%d&#39;)</p>
</blockquote>
<table>
<thead>
<tr>
<th>의미</th>
<th>MySQL 포맷</th>
</tr>
</thead>
<tbody><tr>
<td>연도 4자리</td>
<td>%Y</td>
</tr>
<tr>
<td>월 2자리</td>
<td>%m</td>
</tr>
<tr>
<td>일 2자리</td>
<td>%d</td>
</tr>
</tbody></table>
<h3 id="oracle">ORACLE</h3>
<blockquote>
<p>TO_CHAR(PUBLISHED_DATE, &#39;YYYY-MM-DD&#39;)</p>
</blockquote>
<table>
<thead>
<tr>
<th>의미</th>
<th>Oracle 포맷</th>
</tr>
</thead>
<tbody><tr>
<td>연도 4자리</td>
<td>YYYY</td>
</tr>
<tr>
<td>월 2자리</td>
<td>MM</td>
</tr>
<tr>
<td>일 2자리</td>
<td>DD</td>
</tr>
</tbody></table>
<h2 id="한-줄-요약">한 줄 요약</h2>
<blockquote>
<p>DATETIME은 문자열이 아니라 &quot;비교 가능한 날짜 타입&quot;이다.<br>실무에서는 LIKE 대신 범위 비교를 사용한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL 공부 - 집계함수/where/group by/having]]></title>
            <link>https://velog.io/@jung-min-ju/SQL-%EA%B3%B5%EB%B6%80</link>
            <guid>https://velog.io/@jung-min-ju/SQL-%EA%B3%B5%EB%B6%80</guid>
            <pubDate>Wed, 25 Feb 2026 14:18:47 GMT</pubDate>
            <description><![CDATA[<h2 id="1-함수">1. 함수</h2>
<h3 id="집계함수">집계함수</h3>
<p>집계함수는 여러 행(row)의 값을 모아서 <strong>하나의 값</strong>으로 계산하는 함수이다.<br>주로 <code>GROUP BY</code>와 함께 사용하며, “부서별/지역별/상품별 통계” 같은 요약 결과를 만들 때 사용한다.</p>
<p>대표적인 집계함수는 다음과 같다.</p>
<ul>
<li><code>COUNT(*)</code> : 행의 개수를 센다.</li>
<li><code>COUNT(col)</code> : <code>NULL</code>이 아닌 값의 개수를 센다.</li>
<li><code>SUM(col)</code> : 합계를 구한다. (숫자 컬럼)</li>
<li><code>AVG(col)</code> : 평균을 구한다. (숫자 컬럼)</li>
<li><code>MAX(col)</code> : 최댓값을 구한다.</li>
<li><code>MIN(col)</code> : 최솟값을 구한다.</li>
</ul>
<p>예시) 부서별 인원 수</p>
<pre><code class="language-sql">SELECT dept, COUNT(*) AS cnt
FROM emp
GROUP BY dept;</code></pre>
<p>주의) 집계함수와 함께 일반 컬럼을 출력하려면, 그 컬럼은 <code>GROUP BY</code>에 포함되어야 한다.</p>
<pre><code class="language-sql">-- ❌ 오류 가능: name은 그룹 기준이 아니라 그룹(부서) 내에 여러 값이 존재할 수 있음
SELECT dept, name, COUNT(*)
FROM emp
GROUP BY dept;

-- ✅ 가능: dept+name 조합으로 그룹을 만들면 name도 한 그룹당 1개로 확정됨
SELECT dept, name, COUNT(*)
FROM emp
GROUP BY dept, name;</code></pre>
<hr>
<h2 id="2-절">2. 절</h2>
<h3 id="where-절">WHERE 절</h3>
<p><code>WHERE</code> 절은 <strong>행(row) 단위 조건</strong>을 적용하여 데이터를 미리 걸러내는 절이다.<br>즉, <code>GROUP BY</code>로 묶기 전에 “어떤 행을 대상으로 집계/조회할지”를 결정한다.</p>
<ul>
<li><code>WHERE</code>에는 일반 컬럼 조건을 자유롭게 쓸 수 있다.</li>
<li><code>WHERE</code>에는 보통 집계함수(<code>COUNT</code>, <code>SUM</code> 등)를 쓸 수 없다. (집계 전 단계라서)</li>
</ul>
<p>예시) 개발팀만 먼저 필터링한 뒤 부서별 인원 집계</p>
<pre><code class="language-sql">SELECT dept, COUNT(*) AS cnt
FROM emp
WHERE dept = &#39;개발팀&#39;
GROUP BY dept;</code></pre>
<hr>
<h3 id="group-by-절">GROUP BY 절</h3>
<p><code>GROUP BY</code> 절은 특정 컬럼을 기준으로 여러 행을 묶어 <strong>그룹(group)</strong> 을 만드는 절이다.<br>그룹별로 집계함수를 적용해 “요약 결과”를 만들 때 사용한다.</p>
<ul>
<li><code>GROUP BY dept</code>라면 결과는 <strong>부서당 1행</strong>이 된다.</li>
<li>따라서 <code>SELECT</code>에는 다음만 올 수 있다.<ul>
<li><code>GROUP BY</code>에 포함된 컬럼</li>
<li>집계함수로 계산된 값</li>
</ul>
</li>
</ul>
<p>예시) 부서별 평균 급여</p>
<pre><code class="language-sql">SELECT dept, AVG(salary) AS avg_salary
FROM emp
GROUP BY dept;</code></pre>
<p>잘못된 예시</p>
<pre><code class="language-sql">-- ❌ name은 같은 dept 안에 여러 값이 있어서, 그룹 결과 1행에 무엇을 넣을지 결정 불가

SELECT dept, name, COUNT(*)
FROM emp
GROUP BY dept;</code></pre>
<hr>
<h3 id="having-절">HAVING 절</h3>
<p><code>HAVING</code> 절은 <strong>그룹(group) 단위 조건</strong>을 적용하는 절이다.<br>즉, <code>GROUP BY</code>로 그룹을 만든 뒤 “어떤 그룹만 남길지” 필터링한다.</p>
<ul>
<li><code>HAVING</code>에는 보통 다음만 쓸 수 있다.<ul>
<li><code>GROUP BY</code>에 쓴 컬럼</li>
<li>집계함수 결과 (<code>COUNT</code>, <code>SUM</code>, <code>AVG</code> 등)</li>
</ul>
</li>
<li><code>GROUP BY</code>에 없는 일반 컬럼을 <code>HAVING</code>에 쓰면 오류가 날 수 있다.</li>
</ul>
<p>예시) 부서별 인원 수 중 3명 이상인 부서만</p>
<pre><code class="language-sql">SELECT dept, COUNT(*) AS cnt
FROM emp
GROUP BY dept
HAVING COUNT(*) &gt;= 3;</code></pre>
<p>예시) GROUP BY에 쓴 컬럼으로 HAVING 조건</p>
<pre><code class="language-sql">SELECT dept, COUNT(*) AS cnt
FROM emp
GROUP BY dept
HAVING dept &lt;&gt; &#39;인턴팀&#39;;</code></pre>
<p>오류 예시</p>
<pre><code class="language-sql">-- ❌ name은 그룹 기준도 아니고 집계값도 아니라 그룹 결과에서 확정 불가
SELECT dept, COUNT(*)
FROM emp
GROUP BY dept
HAVING name = &#39;Kim&#39;;</code></pre>
<hr>
<h2 id="실행-순서이해용">실행 순서(이해용)</h2>
<p>일반적으로 다음 순서로 처리된다고 이해하면 된다.</p>
<p><code>FROM → WHERE → GROUP BY → HAVING → SELECT</code></p>
<ul>
<li><code>WHERE</code>: 행 필터(집계 전)</li>
<li><code>HAVING</code>: 그룹 필터(집계 후)</li>
</ul>
<p>SELECT가 논리적으로 <strong>GROUP BY 뒤</strong>에 처리되기 때문에, 이미 데이터가 “그룹 단위”로 바뀐 상태.
그래서 <strong>SELECT에서는 그룹 기준 컬럼이나 집계값만 안전하게 꺼낼 수 있음</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 아기 상어(16236)]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%95%84%EA%B8%B0-%EC%83%81%EC%96%B416236</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EC%95%84%EA%B8%B0-%EC%83%81%EC%96%B416236</guid>
            <pubDate>Thu, 12 Feb 2026 06:45:09 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어볼 문제는 <a href="https://www.acmicpc.net/problem/16236">아기 상어</a>라는 문제이다.</p>
<h2 id="1-문제요약">1. 문제요약</h2>
<ul>
<li>N * N 크기의 공간에 물고리 M마리와 아기 상어 1마리가 있다.<ul>
<li>아기상어의 초기 크기는 2이고, 물고기는 입력값으로 주어진다.</li>
</ul>
</li>
<li>아기 상어는 자기보다 큰 물고기의 칸은 지나갈 수 없다.</li>
<li>아기 상어는 자기와 같은 크기의 물고 칸은 지나갈 수 있다.</li>
<li>아기 상어는 자기보다 작은 물고기를 이동과 동시에 먹는다. 
  중요포인트 : 자기의 크기와 같은 수의 물고기를 먹어야지만 크기가 1 커진다.</li>
</ul>
<p>[아기 상어의 움직임]</p>
<ul>
<li>먹을 수 있는 물고기가 여러 마리라면 거리가 가장 가까운 물고기를 먹는다.<ul>
<li>거리가 동일하다면, 가장 위에 있는 물고기, 모두 위에 있다면 가장 왼쪽 물고기를 먹는다.</li>
</ul>
</li>
<li>거리 측정은 지나가야 하는 칸의 최솟값이다.</li>
<li>아기 상어의 이동은 1초 걸린다. (1칸에 1초 걸림.)</li>
</ul>
<p>더 이상 먹을 수 있는 물고기가 없을 때까지 몇초가 지나는지 구하라.</p>
<h2 id="2-입출력">2. 입출력</h2>
<h3 id="입력">[입력]</h3>
<ul>
<li>공간의 크기 N(2 ≤ N ≤ 20)</li>
<li>N개의 줄에 공간의 상태가 주어짐<ul>
<li>0: 빈 칸</li>
<li>1, 2, 3, 4, 5, 6: 칸에 있는 물고기의 크기</li>
<li>9: 아기 상어의 위치</li>
</ul>
</li>
</ul>
<h3 id="출력">[출력]</h3>
<ul>
<li>더 이상 먹을 수 있는 물고기까지 없을 때 까지. 몇 초가 지났는지 구하라.
   ( = 상어가 얼마나 이동했는지 거리를 구하라. )</li>
</ul>
<h2 id="3-알고리즘">3. 알고리즘</h2>
<ul>
<li><p>필요한 전역 자료구조</p>
<ul>
<li>전체 map 저장할 int 배열 : 입력값으로 주어진 배열</li>
<li>현재 상어의 몸무게, 먹은 물고기 카운팅 전역 변수 선언.</li>
</ul>
</li>
<li><p>bfs 시 필요한 자료구조</p>
<ul>
<li>상어보다 작고, 거리가 같은 물고기들의 위치 정보 모아줄 우선순위 큐<ul>
<li>우선순위 큐의 정렬 조건(1. 위쪽인가 3. 왼쪽인가)</li>
</ul>
</li>
<li>bfs 돌아줄 ArrayDeque</li>
</ul>
</li>
</ul>
<ul>
<li>매번 가장 최소 경로 찾는 루트 -&gt; bfs<ul>
<li>현재 위치에서 bfs 돌리면서 첫 번째 물고기가 발견됐을 때의 거리 기록
 (이후  해당 거리보다 멀다면 bfs를 돌리지 않기 위함.)<ul>
<li>먹을 수 있는 물고기가 발견된다면 우선순위큐에 저장</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="4-코드">4. 코드</h2>
<pre><code class="language-java">import java.io.*;
import java.util.ArrayDeque;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;

import javax.print.DocFlavor.READER;


class Info implements Comparable &lt;Info&gt; {
    int x;
    int y;
    int dis;

    public Info (int x, int y, int dis){
        this.x=x;
        this.y=y;
        this.dis = dis;
    }

    @Override
    public int compareTo(Info o) {
        if (this.x == o.x)
            return this.y-o.y;
        return this.x-o.x;
    }
}

public class Main {
    static int N, SHARK_WEIGHT, EAT_CNT, TIME;
    static int [][] MAP;
    static int DIR[][] = {{0,1}, {0,-1}, {1,0}, {-1,0}};
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        N = Integer.parseInt(br.readLine());
        MAP = new int[N][N];

        SHARK_WEIGHT = 2;
        EAT_CNT = 0;
        TIME = 0;

        Info shark = null;
        for(int i=0; i&lt;N; i++){
            st = new StringTokenizer(br.readLine());
            for(int j=0; j&lt;N; j++) {
                MAP[i][j] = Integer.parseInt(st.nextToken());
                if(MAP[i][j] == 9){
                    shark = new Info(i, j,0);
                    MAP[i][j] = 0;
                }
            }
        }

        while (shark != null) {
            if(EAT_CNT &gt;= SHARK_WEIGHT) {
                SHARK_WEIGHT++;
                EAT_CNT = 0;
            }
            shark = findFish(shark);
        }

        System.out.println(TIME);
    }

    static Info findFish(Info shark) {
        Queue&lt;Info&gt; pq = new PriorityQueue&lt;&gt;();
        Queue&lt;Info&gt; bfs = new ArrayDeque&lt;&gt;();
        boolean [][] visit = new boolean[N][N];

        shark.dis = 0;
        bfs.add(shark);
        visit[shark.x][shark.y] = true;

        int minDis = -1;

        while (!bfs.isEmpty()) {
            Info now = bfs.poll();

            for(int i = 0; i &lt; 4; i++) {
                int nx = now.x+DIR[i][0];
                int ny = now.y+DIR[i][1];

                if(!canGo(nx, ny) || visit[nx][ny]) 
                    continue;
                if(minDis != -1 &amp;&amp; now.dis+1 &gt; minDis)
                    continue;
                visit[nx][ny] = true;
                Info next = new Info(nx, ny, now.dis+1);

                if(MAP[nx][ny] &gt; 0 &amp;&amp; MAP[nx][ny] &lt; SHARK_WEIGHT){
                    pq.add(next);
                    if(minDis == -1)
                        minDis = next.dis;
                }
                bfs.add(next);
            }
        }

        Info fish = pq.poll();
        if(fish != null){
            EAT_CNT++;
            TIME += fish.dis;
            MAP[fish.x][fish.y] = 0;
        }

        return fish;
    }

    static boolean canGo(int nx, int ny) {
        if(nx &lt; 0 || ny &lt; 0 || nx &gt;= N || ny &gt;= N)
            return false;
        if(MAP[nx][ny] &gt; SHARK_WEIGHT)
            return false;
        return true;
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/jung-min-ju/post/e72dfa44-154b-4ba8-a9a0-36393fd5b4b4/image.png" alt=""></p>
<p>한 시간만에 풀어서 뿌듯하다 구현 실력이 올라간 것 같다!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 - 나무 재테크(16235)]]></title>
            <link>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%82%98%EB%AC%B4-%EC%9E%AC%ED%85%8C%ED%81%AC16235</link>
            <guid>https://velog.io/@jung-min-ju/%EB%B0%B1%EC%A4%80-%EB%82%98%EB%AC%B4-%EC%9E%AC%ED%85%8C%ED%81%AC16235</guid>
            <pubDate>Wed, 11 Feb 2026 09:07:37 GMT</pubDate>
            <description><![CDATA[<p>오늘 풀어볼 문제는 <a href="https://www.acmicpc.net/problem/16235">나무 재테크</a> 라는 문제이다.</p>
<h2 id="1-문제요약">1. 문제요약</h2>
<ul>
<li><p>N * N 크기의 땅이 있고, 각 땅에는 초기에 5만큼의 양분이 있다.</p>
</li>
<li><p>나무는 사계절을 보내며 다음과 같은 과정을 거친다.</p>
</li>
<li><p>봄</p>
<ul>
<li>나무가 자신의 나이만큼 현재 위치한 땅의 양분을 먹고, 나이가 1 증가한다.</li>
<li>하나의 칸에 여러 나무가 있다면, 가장 어린 나무부터 양분을 먹는다.</li>
<li>나이만큼 먹을 양분이 부족하다면 해당 나무는 양분을 흡수하지 않고 바로 죽는다.</li>
</ul>
</li>
<li><p>여름</p>
<ul>
<li>봄에 죽은 나무가 양분으로 변한다. (해당 나무 나이 / 2)</li>
</ul>
</li>
<li><p>가을</p>
<ul>
<li>나이가 5의 배수인 나무들만 번식한다. 인접한 8개의 칸에 나이가 1인 나무를 추가한다.</li>
</ul>
</li>
<li><p>겨울</p>
<ul>
<li>땅에 양분을 추가한다. 추가되는 양분의 양은 입력값으로 주어진다.</li>
</ul>
</li>
<li><p>K년이 지난 후, 살아있는 나무의 개수를 구하라.</p>
</li>
</ul>
<h2 id="2-입출력">2. 입출력</h2>
<h3 id="입력">[입력]</h3>
<ul>
<li>땅 크기 N, 나무 그루 수 M, 주어질 년 수 K 주어짐 </li>
<li>N개의 줄에 양분의 양이 주어짐. 매해 겨울마다 추가해줘야 함.</li>
<li>다음 M개의 줄에 나무의 정보 나타내는 X, Y, Z 주어짐 (위치 X,Y, 나이 Z)</li>
</ul>
<h2 id="3-제한">3. 제한</h2>
<ul>
<li>1 ≤ N ≤ 10</li>
<li>1 ≤ K ≤ 1,000</li>
<li>1 ≤ A[r][c] ≤ 100</li>
<li>1 ≤ 입력으로 주어지는 나무의 나이 ≤ 10</li>
</ul>
<h2 id="4-시간">4. 시간</h2>
<ul>
<li>0.3초 </li>
</ul>
<h2 id="5-알고리즘">5. 알고리즘</h2>
<p>단순 빡구현</p>
<p>나무 관련 3개의 자료구조</p>
<ul>
<li>죽은 나무 정보 담을 리스트1</li>
<li>새롭게 나이를 먹은 나무 정보 담을 리스트2</li>
<li>현재 나무의 생/사 결정 위한 정보 담을 우선순위큐1</li>
</ul>
<p>땅 관련 2개의 배열</p>
<ul>
<li>매 겨울마다 추가해줄 초기 입력값 양분 배열</li>
<li>매해마다 갱신되는 양분 배열</li>
</ul>
<ol>
<li>나이 순대로 우선순위큐에 나무 위치 저장.</li>
<li>해당 큐가 빌 때까지 돌림
 2.1 해당 땅에 나무 나이만큼의 양분이 있다면, 해당 땅의 양분 줄인 후, 나무 나이 추가해 리스트2에 담음
 2.2                                  없다면, 리스트1에 담음</li>
<li>리스트1에 담긴 나무들의 나이 / 2 만큼의 양분을 땅에 추가한다.</li>
<li>리스트1의 size만큼 돌린다.
 4.1 나이가 5배수에 해당하는 나무들의 8개의 인접 땅에 1살 나무를 추가.</li>
<li>입력값으로 주어졌던 양분을 땅에 더해준다.</li>
</ol>
<p>위 과정을 k년만큼 반복한 후, 최종 리스트2의 크기를 반환한다.</p>
<h2 id="6-정답-코드">6. 정답 코드</h2>
<p>위 로직대로 코드를 짜니, 쉽게 정답을 맞출 수 있었다.</p>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

class Info implements Comparable &lt;Info&gt; {
    int x;
    int y;
    int age;

    public Info(int x, int y, int age)
    {
        this.x=x;
        this.y=y;
        this.age=age;
    }

    @Override
    public int compareTo(Info o){
        return this.age-o.age;
    }
}

public class Main {
    static int N;
    static int [][] GROUND;
    static int [][] ENERGY;
    static List&lt;Info&gt; DEAD;
    static List&lt;Info&gt; LIVE;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int M, K;

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());

        ENERGY = new int[N+1][N+1];
        GROUND = new int[N+1][N+1];
        DEAD = new ArrayList&lt;&gt;();
        LIVE = new ArrayList&lt;&gt;();

        for(int i=1; i&lt;=N; i++){ 
            st = new StringTokenizer(br.readLine());
            for(int j=1; j&lt;=N; j++){
                ENERGY[i][j] = Integer.parseInt(st.nextToken());
                GROUND[i][j] = 5;
            }
        }

        for(int i=0; i&lt;M; i++) {
            st = new StringTokenizer(br.readLine());
            int x = Integer.parseInt(st.nextToken());
            int y = Integer.parseInt(st.nextToken());
            int age = Integer.parseInt(st.nextToken());

            LIVE.add(new Info(x, y, age));
        }

        while (K --&gt; 0) {
            spring();
            summer();
            fall();
            winter();
        }

        System.out.println(LIVE.size());
    }

    static void spring() {
        Queue&lt;Info&gt; pq = new PriorityQueue&lt;&gt;();
        List&lt;Info&gt; tmp = new ArrayList&lt;&gt;();

        for(int i=0; i&lt;LIVE.size(); i++){
            pq.add(LIVE.get(i));
        }

        while (!pq.isEmpty()) {
            Info tree = pq.poll();

            if(tree.age &gt; GROUND[tree.x][tree.y]){
                DEAD.add(tree);
                continue;
            }
            GROUND[tree.x][tree.y]-=tree.age;
            tree.age++;
            tmp.add(tree);
        }
        LIVE = tmp;
    }

    static void summer(){
        for(Info dead : DEAD){
            GROUND[dead.x][dead.y] += (dead.age / 2);
        }
        DEAD = new ArrayList&lt;&gt;();
    }

    static void fall() {
        int [][] dir = {{1,0}, {-1,0}, {0,1}, {0, -1}, {1,1}, {-1,-1}, {1,-1}, {-1,1}};

        for(int i=0; i&lt;LIVE.size(); i++) {
            Info tree = LIVE.get(i);
            if(tree.age % 5 != 0)
                continue;
            for(int j=0; j&lt;8; j++) {
                int nx = tree.x+dir[j][0];
                int ny = tree.y+dir[j][1];
                if(nx &lt;= 0 || ny &lt;= 0 || nx &gt; N || ny &gt; N) 
                    continue;
                LIVE.add(new Info(nx, ny, 1));
            }
        }

    }

    static void winter(){
        for(int i=1; i&lt;=N; i++){
            for(int j=1; j&lt;=N; j++){
                GROUND[i][j] += ENERGY[i][j];
            }
        }
    }

}</code></pre>
<p><img src="https://velog.velcdn.com/images/jung-min-ju/post/e2ee4c6f-915b-4550-ae4b-dd9abcea728c/image.png" alt=""></p>
<p>근데 메모리도, 시간도 너무너무너무 비효율적이었다.</p>
<p>그래서 가장 빠른 코드를 찾아봤는데, 나랑 정말 다른 접근법으로 푸셨길래 분석해봤다.</p>
<h2 id="7-다른-사람-풀이-코드">7. 다른 사람 풀이 코드</h2>
<p>이 코드는 헤더 노드가 있는 원형 이중 연결리스트로 풀이한 버전이다.
코테 하면서 한 번도 이런 형태로 푼 적이 없는데, 참고하면 좋을 것 같다.</p>
<p><a href="https://www.acmicpc.net/source/72936278">해당 코드 링크 이동</a></p>
<h3 id="원형-이중-연결리스트">원형 이중 연결리스트</h3>
<p>일반 연결리스트는 다음과 같이 생겼을 것이다.</p>
<blockquote>
<pre><code class="language-md">A -&gt; B -&gt; C -&gt; null</code></pre>
</blockquote>
<pre><code>
그러나 원형 이중 연결리스트란, 다음과 같이 연결되어있다.

![](https://velog.velcdn.com/images/jung-min-ju/post/dd408edd-035a-4ba9-962b-e445a5bad231/image.png)

모든 노드가 원형으로 연결되어있으며, 좌우로도 연결되어 있다.

```java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    static int n;
    static int m;
    static int k;
    static int[][] winterFood;
    static int[][] soil;
    static Tree[][] trees;
    static int[] dx = {0, 0, -1, 1, 1, 1, -1, -1};
    static int[] dy = {-1, 1, 0, 0, 1, -1, 1, -1};
    static StringBuilder sb = new StringBuilder();

    static class Tree {
        int cnt = 0;
        int age = 0;
        Tree before;
        Tree next;

        public Tree() { // 헤더 트리 만들기
            before = next = this;
        }

        public Tree(int age) { // 문제에서 주어지는 트리 만들기
            this.age = age;
            cnt = 1;
        }

        public Tree(int age, int cnt) { // 번식할 트리 만들기(개수도 필요함)
            this.age = age;
            this.cnt = cnt;
        }

        // 문제에서 입력으로 주어지는 나무의 위치는 모두 다르다고 했다.
        // 이후, 나무가 번식을 하면 새로운 나무를 추가해 주어야 하는데,
        // 현재 Tree의 두번째 부분(첫번째는 편의상 빈 나무임 = 헤더)에 넣어주면 된다.

        // A(헤더)의 뒤에 B를 넣으면 된다.
        // A -&gt; B -&gt; C   =&gt;    A -&gt; D -&gt; B -&gt; C
        public void add(Tree tree) {
            this.next.before = tree;
            tree.next = this.next;
            tree.before = this;
            this.next = tree;
        }

        // X.delete(Y)
        // X 트리부터 Y트리 전까지 연결됨.

        // A -&gt; D -&gt; C -&gt; B 가 있다고 할 때
        // A.delete(B) : A -&gt; D
        // A.delete(C) : A -&gt; D -&gt; B
        // D.delete(B) : D -&gt; C
        // X == Y라면 자기 자신만 남음.
        // X &lt; Y라면 그대로.

        // add를 하면 나이 순서대로 정렬이 될텐데
        // 양분을 못 먹는 나무 이후로부터는 전부 죽어버린다.
        // A -&gt; B -&gt; C -&gt; D 가 있을때, C부터는 양분을 못먹어서 죽어버린다고 할 때
        // A.delete(C)를 해주면 A -&gt; B만 남게 되는 것이다.
        public void delete(Tree tree) {
            tree = tree.before;
            tree.next = this;
            this.before = tree;
        }
    }

    static void setInputs() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        k = Integer.parseInt(st.nextToken());

        winterFood = new int[n][n];
        soil = new int[n][n];
        trees = new Tree[n][n];

        for (int r = 0; r &lt; n; r++) {
            st = new StringTokenizer(br.readLine());
            for (int c = 0; c &lt; n; c++) {
                winterFood[r][c] = Integer.parseInt(st.nextToken());
                soil[r][c] = 5;
                trees[r][c] = new Tree();
            }
        }

        for (int i = 0; i &lt; m; i++) {
            st = new StringTokenizer(br.readLine());
            int x = Integer.parseInt(st.nextToken()) - 1;
            int y = Integer.parseInt(st.nextToken()) - 1;
            int z = Integer.parseInt(st.nextToken());

            trees[x][y].add(new Tree(z));
        }
    }

    private static void performSpringAndSummerOperation() { // 봄 행동(여름을 곁들인)
        for (int i = 0; i &lt; n; i++) {
            for (int j = 0; j &lt; n; j++) {

                boolean canAbsorbSoil = true;
                Tree treeToDelete = null;
                Tree tree = trees[i][j].next;

                while (tree.age &gt; 0) { // 나무 끝까지 탐색해서 다시 헤더로 돌아 온다면 종료
                    // 양분 섭취
                    if (canAbsorbSoil) {
                        int sum = tree.cnt * tree.age; // 해당 나무가 양분을 얼마나 섭취할 수 있니?
                        if (soil[i][j] &gt;= sum) { // 양분 섭취가 가능하다!
                            soil[i][j] -= sum;
                            tree.age++;
                        } else { // 양분 섭취를 못해요!
                            /**
                             * 여름
                             */
                            treeToDelete = performSummerOperation(i, j, tree);
                            canAbsorbSoil = false; // 더이상 양분을 먹을 나무는 없고 전부다 토양으로 돌아가요! 슬픕니다.
                        }
                    } else { // 너흰 이제 토양으로 돌아가라.
                        soil[i][j] += tree.cnt * (tree.age / 2);
                    }

                    tree = tree.next; // 다음 트리를 탐색합니다.
                }

                // 모든 트리를 탐색했으니, 이제 제거할 트리가 있는지 찾아볼까요?
                if (treeToDelete == null) {
                    continue;
                } // 제거할 트리가 있다면?? 제거해주면 됩니다.
                trees[i][j].delete(treeToDelete);
            }
        }
    }

    static Tree performSummerOperation(int i, int j, Tree tree) { // 여름 행동
        Tree treeToDelete;
        int ableTreesCount = soil[i][j] / tree.age; // 현재 토양에서 지금 나이의 나무 몇그루가 양분을 섭취할 수 있나?

        soil[i][j] -= ableTreesCount * tree.age; // 양분을 먹었어요!
        soil[i][j] +=
                (tree.cnt - ableTreesCount) * (tree.age / 2); // 죽은 나무는 나이의 절반만큼 양분으로 돌아갑니다!

        if (ableTreesCount &gt; 0) { // 양분을 먹은 나무가 있었다면
            tree.cnt = ableTreesCount; // tree의 cnt를 다시 갱신해준다.
            tree.age++; // 한 살 더 먹으렴
            treeToDelete = tree.next; // 이 다음 나무부터는 제거 해버린다.
        } else { // 아무도 양분을 못먹었다면 이 나무부터 제거 해버린다.
            treeToDelete = tree;
        }
        return treeToDelete;
    }

    private static void performAutumnOperation() { // 가을 행동
        for (int i = 0; i &lt; n; i++) {
            for (int j = 0; j &lt; n; j++) {
                Tree tree = trees[i][j].next;
                while (tree.age &gt; 0) { // 헤더로 다시 돌아오면 종료.
                    // 나이가 5의 배수인 나무의 경우
                    if (tree.age % 5 == 0) {
                        for (int dir = 0; dir &lt; 8; dir++) { // 8방 탐색 슛~~
                            int nx = i + dx[dir];
                            int ny = j + dy[dir];

                            if (nx &lt; 0 || ny &lt; 0 || nx &gt;= n || ny &gt;= n) { // 맵 밖으로 벗어나면 continue
                                continue;
                            }

                            if (trees[nx][ny].next.age == 1) { // 이미 해당칸에 다른칸에서 번식해온 나무가 있으면
                                // 헤더만 존재했다면 해더의 age는 0이라 조건을 만족 못해요.
                                trees[nx][ny].next.cnt += tree.cnt; // 이제 번식할 나무의 개수만 더해주면 된다.
                                continue;
                            }

                            trees[nx][ny].add(
                                    new Tree(1, tree.cnt)); // 해당 칸에 처음 번식을 하는거라면 나이는 1, 번식할 나무의 개수로 tree 생성

                        }
                    }
                    tree = tree.next; // 다음 트리 탐색
                }
            }
        }
    }

    private static void performWinterOperation() { // 겨울 행동
        for (int i = 0; i &lt; n; i++) {
            for (int j = 0; j &lt; n; j++) {
                soil[i][j] += winterFood[i][j];
            }
        }
    }

    static int calculateAnswer() { // 답 계산
        int ans = 0;
        for (int i = 0; i &lt; n; i++) {
            for (int j = 0; j &lt; n; j++) {
                Tree tree = trees[i][j].next;
                while (tree.age &gt; 0) { // 헤더로 다시 돌아오면 종료
                    ans += tree.cnt; // 나무 count 해주기
                    tree = tree.next; // 다음 트리로 Go
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) throws Exception {
        setInputs();

        for (int year = 1; year &lt; k + 1; year++) { // 1년부터 K년 까지

            performSpringAndSummerOperation();
            performAutumnOperation();
            performWinterOperation();
        }

        int ans = calculateAnswer();

        sb.append(ans);
        System.out.println(sb);
    }
}</code></pre>]]></description>
        </item>
    </channel>
</rss>