<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hyejin</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 25 Apr 2025 13:33:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hyejin</title>
            <url>https://velog.velcdn.com/images/clara-shin/profile/898220dd-f6c3-4aa6-be6d-9213d125fac9/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hyejin. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/clara-shin" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[99클럽 코테 스터디 20일차 TIL - 나의 인생에는 수학과 함께 (DFS)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-20%EC%9D%BC%EC%B0%A8-TIL-%EB%82%98%EC%9D%98-%EC%9D%B8%EC%83%9D%EC%97%90%EB%8A%94-%EC%88%98%ED%95%99%EA%B3%BC-%ED%95%A8%EA%BB%98-DFS</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-20%EC%9D%BC%EC%B0%A8-TIL-%EB%82%98%EC%9D%98-%EC%9D%B8%EC%83%9D%EC%97%90%EB%8A%94-%EC%88%98%ED%95%99%EA%B3%BC-%ED%95%A8%EA%BB%98-DFS</guid>
            <pubDate>Fri, 25 Apr 2025 13:33:30 GMT</pubDate>
            <description><![CDATA[<p>링크: <a href="https://www.acmicpc.net/problem/17265">https://www.acmicpc.net/problem/17265</a></p>
<h2 id="문제-정의">문제 정의</h2>
<ul>
<li>N x N 크기의 바둑판이 있고, 각 칸에는 숫자 또는 연산자가 들어 있습니다.</li>
<li>(1,1)에서 시작하여 (N,N)까지 최단 경로로 이동해야 합니다(오른쪽 또는 아래로만 이동).</li>
<li>경로에서 만나는 숫자와 연산자를 순서대로 적용하여 최종 결과를 계산합니다.</li>
<li>연산자 우선순위는 고려하지 않고 순서대로 계산합니다.</li>
<li>가능한 모든 경로 중에서 결과값의 최대값과 최소값을 구해야 합니다.</li>
</ul>
<h2 id="접근-방법">접근 방법</h2>
<ul>
<li><code>DFS</code>로 모든 가능한 경로를 탐색</li>
<li>각 경로마다 숫자와 연산자를 순서대로 적용하여 결과값을 계산</li>
<li>모든 경로 중에서 최대값과 최소값을 탐색</li>
</ul>
<h2 id="코드">코드</h2>
<pre><code class="language-js">const fs = require(&#39;fs&#39;);
const input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39;\n&#39;);

const N = parseInt(input[0]);
const board = [];

for (let i = 1; i &lt;= N; i++) {
  board.push(input[i].split(&#39; &#39;));
}

const findMinMaxValues = () =&gt; {
  let maxVal = -Infinity;
  let minVal = Infinity;

  // DFS를 통해 모든 가능한 경로를 탐색
  function dfs(r, c, values = []) {
    // 현재 위치의 값 추가
    const currentValue = board[r][c];
    const newValues = [...values, currentValue];

    // 학교에 도착한 경우 결과 계산
    if (r === N - 1 &amp;&amp; c === N - 1) {
      // 표현식 계산
      let result = calculate(newValues);

      maxVal = Math.max(maxVal, result);
      minVal = Math.min(minVal, result);
      return;
    }

    // 오른쪽으로 이동
    if (c + 1 &lt; N) {
      dfs(r, c + 1, newValues);
    }

    // 아래쪽으로 이동
    if (r + 1 &lt; N) {
      dfs(r + 1, c, newValues);
    }
  }

  // 표현식 계산 함수
  const calculate = (values) =&gt; {
    let result = parseInt(values[0]); // 첫 번째 값은 항상 숫자

    for (let i = 1; i &lt; values.length; i += 2) {
      if (i + 1 &lt; values.length) {
        const op = values[i];
        const num = parseInt(values[i + 1]);

        switch (op) {
          case &#39;+&#39;: result += num; break;
          case &#39;-&#39;: result -= num; break;
          case &#39;*&#39;: result *= num; break;
        }
      }
    }

    return result;
  }

  // DFS 시작
  dfs(0, 0);

  return { maxVal, minVal };
}

const result = findMinMaxValues();
console.log(`${result.maxVal} ${result.minVal}`);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 19일차 TIL - 김밥천국의 계단 (DP)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-19%EC%9D%BC%EC%B0%A8-TIL-%EA%B9%80%EB%B0%A5%EC%B2%9C%EA%B5%AD%EC%9D%98-%EA%B3%84%EB%8B%A8-DP</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-19%EC%9D%BC%EC%B0%A8-TIL-%EA%B9%80%EB%B0%A5%EC%B2%9C%EA%B5%AD%EC%9D%98-%EA%B3%84%EB%8B%A8-DP</guid>
            <pubDate>Thu, 24 Apr 2025 14:49:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/28069">https://www.acmicpc.net/problem/28069</a>
알고리즘 분류: 너비 우선 탐색, 다이나믹 프로그래밍, 그래프 이론, 그래프 탐색</p>
</blockquote>
<h2 id="문제-정의">문제 정의</h2>
<p>최대 K번의 행동으로 N번 계단에 도달할 수 있는가
N번 계단에 도달하는 데 필요한 최소 행동 수가 K 이하인가</p>
<h2 id="dp접근법">DP접근법</h2>
<p>각 위치마다 도달하는 데 필요한 최소 행동 수만 기록하면 되고, 
마지막에 dp[N] ≤ K인지만 확인</p>
<h2 id="코드">코드</h2>
<pre><code class="language-js">const fs = require(&#39;fs&#39;);
const [N, K] = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39; &#39;).map(Number);

const dp = new Array(N + 1).fill(Infinity);
dp[0] = 0;

for (let i = 0; i &lt;= N; i++) {

  if (i + 1 &lt;= N) {
    dp[i + 1] = Math.min(dp[i + 1], dp[i] + 1);
  }

  const jump = i + Math.floor(i / 2);
  if (jump &lt;= N) {
    dp[jump] = Math.min(dp[jump], dp[i] + 1);
  }
}

console.log(dp[N] &lt;= K ? &#39;minigimbob&#39; : &#39;water&#39;);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 18일차 TIL - 강아지는 많을수록 좋다 (BFS/DP)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-18%EC%9D%BC%EC%B0%A8-TIL-%EA%B0%95%EC%95%84%EC%A7%80%EB%8A%94-%EB%A7%8E%EC%9D%84%EC%88%98%EB%A1%9D-%EC%A2%8B%EB%8B%A4-BFSDP</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-18%EC%9D%BC%EC%B0%A8-TIL-%EA%B0%95%EC%95%84%EC%A7%80%EB%8A%94-%EB%A7%8E%EC%9D%84%EC%88%98%EB%A1%9D-%EC%A2%8B%EB%8B%A4-BFSDP</guid>
            <pubDate>Wed, 23 Apr 2025 15:31:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/27971">https://www.acmicpc.net/problem/27971</a>
알고리즘 분류: 너비 우선 탐색, 다이나믹 프로그래밍, 그래프 이론, 그래프 탐색</p>
</blockquote>
<h2 id="문제-정의">문제 정의</h2>
<ul>
<li>호무라는 정확히 N마리의 강아지를 키우고 싶습니다.</li>
<li>두 가지 마법을 사용할 수 있습니다: A마리 또는 B마리의 강아지를 생성하는 마법</li>
<li>제약 사항: 특정 닫힌 구간 [L,R]에 해당하는 수의 강아지가 만들어지면 모두 사라집니다.</li>
<li>처음에는 강아지가 0마리 있습니다.</li>
<li><strong>최소 행동 횟수</strong>로 정확히 N마리를 만들어야 합니다.</li>
</ul>
<h2 id="문제-접근-방식">문제 접근 방식</h2>
<p><strong>최단 경로 문제 → BFS</strong></p>
<ol>
<li><p>0부터 N까지의 강아지 수를 노드로 생각하고, A마리나 B마리를 추가하는 것을 간선으로 볼 수 있다. </p>
</li>
<li><p>제약 구간에 포함되는 상태는 갈 수 없는 노드이다.</p>
</li>
<li><p>BFS(너비 우선 탐색)를 사용하여 최소 행동 횟수를 찾을 수 있다:</p>
<ul>
<li>초기 상태는 0마리에서 시작</li>
<li>각 상태에서 A마리 또는 B마리를 추가했을 때의 새로운 상태를 검사</li>
<li>제약 구간에 포함되는 상태는 방문하지 않음</li>
<li>N마리에 도달하면 해당 시점의 행동 횟수가 답</li>
</ul>
</li>
</ol>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-js">const fs = require(&#39;fs&#39;);
let input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39;\n&#39;);

const [N, M, A, B] = input[0].split(&#39; &#39;).map(Number);

// 제약 구간
const restrictedRanges = [];
for (let i = 1; i &lt;= M; i++) {
    const [L, R] = input[i].split(&#39; &#39;).map(Number);
    restrictedRanges.push([L, R]);
}

// 특정 수가 제약 구간에 포함되는지 검사
const isRestricted = (num) =&gt; {
    for (const [L, R] of restrictedRanges) {
        if (num &gt;= L &amp;&amp; num &lt;= R) {
            return true;
        }
    }
    return false;
}

// BFS로 최소 행동 횟수 계산
const findMinActions = () =&gt; {
    // N이 제약 구간에 포함되면 불가능
    if (isRestricted(N)) {
        return -1;
    }

    // 방문 배열 (각 강아지 수에 도달하기 위한 최소 행동 횟수 저장)
    const visited = new Array(N + 1).fill(-1);
    visited[0] = 0; // 초기 상태: 0마리

    // BFS를 위한 큐
    const queue = [0];

    while (queue.length &gt; 0) {
        const current = queue.shift();

        // N에 도달했으면 최소 행동 횟수 반환
        if (current === N) {
            return visited[current];
        }

        // A마리 추가하는 경우
        const addA = current + A;
        if (addA &lt;= N &amp;&amp; !isRestricted(addA) &amp;&amp; visited[addA] === -1) {
            visited[addA] = visited[current] + 1;
            queue.push(addA);
        }

        // B마리 추가하는 경우
        const addB = current + B;
        if (addB &lt;= N &amp;&amp; !isRestricted(addB) &amp;&amp; visited[addB] === -1) {
            visited[addB] = visited[current] + 1;
            queue.push(addB);
        }
    }

    // N에 도달할 수 없는 경우
    return -1;
}

console.log(findMinActions());</code></pre>
<h4 id="시간time-복잡도">시간(time) 복잡도:</h4>
<p>BFS의 시간 복잡도: <code>O(V+E)</code></p>
<ul>
<li>V는 노드 수(최대 N), E는 간선 수(최대 2N)</li>
<li>각 상태에서 제약 구간 검사: O(M)</li>
</ul>
<p>전체 시간 복잡도: <code>O(N*M)</code></p>
<h4 id="공간space-복잡도">공간(space) 복잡도:</h4>
<p>방문 배열: O(N)
큐: O(N)
전체 공간 복잡도: O(N)</p>
<p>제한 조건(N ≤ 100,000, M ≤ 100)을 충분히 만족</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 17일차 TIL - 너구리 구구(BFS)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-17%EC%9D%BC%EC%B0%A8-TIL-%EB%84%88%EA%B5%AC%EB%A6%AC-%EA%B5%AC%EA%B5%ACBFS</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-17%EC%9D%BC%EC%B0%A8-TIL-%EB%84%88%EA%B5%AC%EB%A6%AC-%EA%B5%AC%EA%B5%ACBFS</guid>
            <pubDate>Tue, 22 Apr 2025 08:53:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/18126">https://www.acmicpc.net/problem/18126</a>
알고리즘 분류: 너비 우선 탐색, 깊이 우선 탐색, 그래프 이론, 그래프 탐색, 트리</p>
</blockquote>
<h2 id="문제-접근">문제 접근</h2>
<p>트리 구조에서 루트(1번 입구)에서 가장 먼 노드를 찾는 문제</p>
<p>입력</p>
<pre><code>4
1 2 3
2 3 2
2 4 4</code></pre><p>출력</p>
<pre><code>7</code></pre><img src='https://velog.velcdn.com/images/clara-shin/post/a9b6c8f2-1224-4f70-8bcc-a6cbe03cf9b5/image.png' width='300'/>


<h2 id="내-코드-초기">내 코드 (초기)</h2>
<pre><code class="language-js">const fs = require(&#39;fs&#39;);
const [N, ...edges] = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39;\n&#39;);
const numN = Number(N);
const graph = Array.from({ length: numN + 1 }, () =&gt; []);

for(let i = 0; i &lt; edges.length; i++){
    const [a, b, c] = edges[i].split(&#39; &#39;).map(Number);
    graph[a].push({ node: b, distance: c });
    graph[b].push({ node: a, distance: c });
}

const findFarthestRoom = (start) =&gt; {
    const distances = Array(numN + 1).fill(-1);
    distances[start] = 0;

    const queue = [start];
    let maxDistance = 0;
    let farthestRoom = start;

    while(queue.length &gt; 0){
        const current = queue.shift();
        for(const {node, distance } of graph[current]){
            if(distances[node] === -1){
                distances[node] = distances[current] + distance;
                queue.push(node);
            }
            // 더 먼 방을 찾으면 업데이트
            if(distances[node] &gt; maxDistance){
                maxDistance = distances[node];
                farthestRoom = node;
            }
        }
    }
    return { maxDistance, farthestRoom };
}

const result = findFarthestRoom(1);
console.log(result.maxDistance);
</code></pre>
<h2 id="개선">개선</h2>
<pre><code class="language-js">const fs = require(&#39;fs&#39;);
const [N, ...edges] = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39;\n&#39;);
const numN = Number(N);
const graph = Array(numN + 1).fill().map(()=&gt;[]); // 인접리스틀로 그래프 표현

for(let i = 0; i &lt; edges.length; i++){
    const [a, b, c] = edges[i].split(&#39; &#39;).map(Number);
    graph[a].push([b, c]); // 배열사용: 객체생성 오버헤드 제거
    graph[b].push([a, c]);
}

const findFarthestRoom = (start) =&gt; {
    const distances = Array(numN + 1).fill(-1);
    distances[start] = 0;

    const queue = [start];
    let maxDistance = 0;
    let farthestRoom = start;

    let queueIndex = 0; // shift() 댓신 인덱스 사용 =&gt; 성능향상

    while(queueIndex &lt; queue.length){
        const current = queue[queueIndex++];

        for(const [nextNode, dist] of graph[current]){
            if(distances[nextNode] === -1){
                const newDistance = distances[current] + dist;
                distances[nextNode] = newDistance;
                queue.push(nextNode);

                // 더 먼 방을 찾으면 업데이트
                if(newDistance &gt; maxDistance){
                    maxDistance = newDistance;
                    farthestRoom = nextNode;
                }
            }
        }
    }
    return maxDistance;
}
console.log(findFarthestRoom(1));</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 16일차 TIL - 신규 아이디 추천(구현)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-16%EC%9D%BC%EC%B0%A8-TIL-%EC%8B%A0%EA%B7%9C-%EC%95%84%EC%9D%B4%EB%94%94-%EC%B6%94%EC%B2%9C</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-16%EC%9D%BC%EC%B0%A8-TIL-%EC%8B%A0%EA%B7%9C-%EC%95%84%EC%9D%B4%EB%94%94-%EC%B6%94%EC%B2%9C</guid>
            <pubDate>Mon, 21 Apr 2025 14:05:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://school.programmers.co.kr/learn/courses/30/lessons/72410">https://school.programmers.co.kr/learn/courses/30/lessons/72410</a>
코딩테스트 연습 &gt; 2021 KAKAO BLIND RECRUITMENT</p>
</blockquote>
<h2 id="접근방법">접근방법:</h2>
<p>단계별 조건에 따라 구현한다
정규식 / 자바스크립트 내장함수 / 문자열 조작 순서를 공부해야 한다</p>
<ul>
<li>1단계 new_id의 모든 대문자를 대응되는 소문자로 치환합니다.</li>
<li>2단계 new_id에서 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거합니다.</li>
<li>3단계 new_id에서 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환합니다.</li>
<li>4단계 new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거합니다.</li>
<li>5단계 new_id가 빈 문자열이라면, new_id에 &quot;a&quot;를 대입합니다.</li>
<li>6단계 new_id의 길이가 16자 이상이면, new_id의 첫 15개의 문자를 제외한 나머지 문자들을 모두 제거합니다. 만약 제거 후 마침표(.)가 new_id의 끝에 위치한다면 끝에 위치한 마침표(.) 문자를 제거합니다.</li>
<li>7단계 new_id의 길이가 2자 이하라면, new_id의 마지막 문자를 new_id의 길이가 3이 될 때까지 반복해서 끝에 붙입니다.</li>
</ul>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">function solution(new_id) {
    new_id = new_id.toLowerCase(); // 1단계

    let result = &#39;&#39;; // 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)만 남길 문자열
    for(let i = 0; i &lt; new_id.length; i++){
        const char = new_id[i];
        if((char &gt;= &#39;a&#39; &amp;&amp; char &lt;= &#39;z&#39;)||
           (char &gt;= &#39;0&#39; &amp;&amp; char &lt;= &#39;9&#39;)||
           (char === &#39;-&#39;) ||
           (char === &#39;_&#39;)||
           (char === &#39;.&#39;)
          ){
            result += char;
        }
    }
    new_id = result;

    result = &#39;&#39;; // 초기화
    for(let i = 0; i &lt; new_id.length; i++){
        if(new_id[i] === &#39;.&#39; &amp;&amp; new_id[i+1] === &#39;.&#39;){
            continue;
        }
        result += new_id[i];
    }

    new_id = result;


    if(new_id.length &gt; 0 &amp;&amp; new_id[0] === &#39;.&#39;) {
        new_id = new_id.substring(1);
    }
    if(new_id.length &gt; 0 &amp;&amp; new_id[new_id.length - 1] === &#39;.&#39;){
        new_id = new_id.substring(0, new_id.length - 1);
    }

    if(new_id === &#39;&#39;){
        new_id = &#39;a&#39;;
    }

    if(new_id.length &gt;= 16){
        new_id = new_id.substring(0, 15);
        if(new_id[new_id.length - 1] === &#39;.&#39;){
            new_id = new_id.substring(0, new_id.length - 1);
        }
    }

    while(new_id.length &lt;= 2){
        new_id += new_id[new_id.length - 1];
    }

    return new_id;
}</code></pre>
<h2 id="정규식을-활용한-개선된-코드">정규식을 활용한 개선된 코드</h2>
<pre><code class="language-javascript">function solution(new_id) {
    // 1단계: 대문자를 소문자로 치환
    new_id = new_id.toLowerCase();

    // 2단계: 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자 제거
    new_id = new_id.replace(/[^\w\-\.]/g, &#39;&#39;);
    // \w는 알파벳, 숫자, 밑줄(_)을 포함하는 문자 클래스
    // \-는 빼기 기호, \.는 마침표를 나타냄

    // 3단계: 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표로 치환
    new_id = new_id.replace(/\.{2,}/g, &#39;.&#39;);

    // 4단계: 마침표(.)가 처음이나 끝에 위치한다면 제거
    new_id = new_id.replace(/^\.|\.$/g, &#39;&#39;);

    // 5단계: 빈 문자열이라면, &quot;a&quot;를 대입
    if (new_id === &#39;&#39;) {
        new_id = &#39;a&#39;;
    }

    // 6단계: 길이가 16자 이상이면, 첫 15개 문자를 제외한 나머지 문자들을 모두 제거
    if (new_id.length &gt;= 16) {
        new_id = new_id.slice(0, 15);
        // 제거 후 마침표(.)가 끝에 위치한다면 제거
        new_id = new_id.replace(/\.$/, &#39;&#39;);
    }

    // 7단계: 길이가 2자 이하라면, 마지막 문자를 길이가 3이 될 때까지 반복해서 붙임
    while (new_id.length &lt;= 2) {
        new_id += new_id.charAt(new_id.length - 1);
    }

    return new_id;
}</code></pre>
<h2 id="개선된-최종-코드">개선된 최종 코드</h2>
<p>메서드 체이닝과 자바스크립트 내장함수 <code>.padEnd()</code>를 이용해 조금더 코드를 간결하게 개선할 수 있다.</p>
<pre><code class="language-javascript">function solution(new_id) {
    return new_id
        .toLowerCase()                       // 1단계: 소문자로 변환
        .replace(/[^\w\-\.]/g, &#39;&#39;)           // 2단계: 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자 제거
        .replace(/\.{2,}/g, &#39;.&#39;)             // 3단계: 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표로 치환
        .replace(/^\.|\.$/g, &#39;&#39;)             // 4단계: 처음과 끝의 마침표 제거
        .replace(/^$/, &#39;a&#39;)                  // 5단계: 빈 문자열이면 &#39;a&#39; 대입
        .slice(0, 15)                        // 6단계: 길이가 16자 이상이면, 첫 15개 문자만 남김
        .replace(/\.$/, &#39;&#39;)                  // 6단계: 끝에 마침표가 있으면 제거
        .padEnd(3, new_id.slice(-1) || &#39;a&#39;); // 7단계: 길이가 2자 이하라면, 마지막 문자를 길이가 3이 될 때까지 반복
}</code></pre>
<h4 id="허용되지-않은-문자-제거-replacew-g-">허용되지 않은 문자 제거 <code>replace(/[^\w\-\.]/g, &#39;&#39;)</code></h4>
<p><code>\w</code>: 알파벳, 숫자, 밑줄(_)을 포함하는 문자 클래스
<code>\-\.</code>: 빼기(-), 마침표(.)
<code>[^...]</code>: 괄호 안의 문자를 제외한 모든 문자와 매칭</p>
<p>따라서, 허용된 문자 외의 모든 것이 제거 됨</p>
<h4 id="연속된-마침표-치환-replace2g-">연속된 마침표 치환 <code>replace(/\.{2,}/g, &#39;.&#39;)</code></h4>
<p><code>\.{2,}</code>: 마침표가 2번 이상 연속된 부분 → 이를 하나의 마침표로 치환</p>
<h4 id="처음과-끝의-마침표-제거-replaceg-">처음과 끝의 마침표 제거 <code>replace(/^\.|\.$/g, &#39;&#39;)</code></h4>
<p><code>^\.</code>: 문자열 시작의 마침표
<code>\.$</code>: 문자열 끝의 마침표</p>
<p>이 두 가지 경우를 모두 빈 문자열로 대체</p>
<h4 id="빈-문자열-처리-replace-a">빈 문자열 처리 <code>replace(/^$/, &#39;a&#39;)</code></h4>
<p><code>^$</code>: 빈 문자열에 매칭</p>
<p>빈 문자열인 경우 &#39;a&#39;로 대체</p>
<h4 id="길이-제한-slice0-15">길이 제한 <code>slice(0, 15)</code></h4>
<p>문자열을 처음부터 15자까지만 남긴다
길이가 15 미만이면 전체 문자열이 그대로 유지한다</p>
<h4 id="끝의-마침표-제거-replace-">끝의 마침표 제거 <code>replace(/\.$/, &#39;&#39;)</code></h4>
<p>길이 제한 후 끝에 마침표가 있다면 제거</p>
<h4 id="길이-보완-padend3-new_idslice-1--a">길이 보완 <code>padEnd(3, new_id.slice(-1) || &#39;a&#39;)</code></h4>
<p><code>padEnd(3, ...)</code>: 문자열 길이가 3이 될 때까지 지정된 문자로 채운다
<code>new_id.slice(-1) || &#39;a&#39;</code>: 마지막 문자(없으면 &#39;a&#39;를 사용)길이가 1이나 2인 경우 마지막 문자를 반복해 길이가 3이 되게 한다</p>
<h4 id="✅-주의할-점">✅ 주의할 점</h4>
<p>7단계에서 <code>new_id.slice(-1)</code>은 원본 new_id의 마지막 문자를 가져오는 것이 아니라, 체이닝 과정에서 현재 변환된 문자열의 마지막 문자를 가져온다</p>
<p>빈 문자열이 될 경우 slice(-1)은 빈 문자열을 반환하므로, || &#39;a&#39;를 통해 기본값 &#39;a&#39;를 제공해야 한다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 15일차 TIL - 리그 오브 레전설 (DP)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-15%EC%9D%BC%EC%B0%A8-TIL-%EB%A6%AC%EA%B7%B8-%EC%98%A4%EB%B8%8C-%EB%A0%88%EC%A0%84%EC%84%A4-DP</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-15%EC%9D%BC%EC%B0%A8-TIL-%EB%A6%AC%EA%B7%B8-%EC%98%A4%EB%B8%8C-%EB%A0%88%EC%A0%84%EC%84%A4-DP</guid>
            <pubDate>Fri, 18 Apr 2025 10:43:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/17271">https://www.acmicpc.net/problem/17271</a>
알고리즘 분류: 다이나믹 프로그래밍, DP</p>
</blockquote>
<h2 id="문제-이해">문제 이해</h2>
<ul>
<li>N초 동안 스킬을 사용한다</li>
<li>A 스킬은 1초 소요, B 스킬은 M초 소요</li>
<li>빈 시간 없이 스킬을 연속해서 사용해야 한다</li>
<li>가능한 모든 스킬 조합의 수를 찾아야 한다</li>
</ul>
<h2 id="접근-방법-다이나믹-프로그래밍">접근 방법: 다이나믹 프로그래밍</h2>
<p><code>dp[i]</code> = &quot;<code>i</code>초까지 가능한 모든 스킬 조합의 수&quot;</p>
<p>i초에서 두 가지 경우의 수:</p>
<ul>
<li>마지막에 A 스킬(1초)을 사용하는 경우: <code>dp[i-1]</code></li>
<li>마지막에 B 스킬(M초)을 사용하는 경우: <code>dp[i-M]</code></li>
</ul>
<p>점화식은,
<code>dp[i] = dp[i-1] + dp[i-M]</code></p>
<img src='https://velog.velcdn.com/images/clara-shin/post/10fadfc4-6bdf-4243-8ca0-a958192796ac/image.png' width='650px'/>


<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39;\n&#39;);
const [N, M] = input[0].split(&#39; &#39;).map(Number);

// 다이나믹 프로그래밍 배열 초기화
const dp = Array(N + 1).fill(0);
const MOD = 1000000007;

// 초기값 설정
dp[0] = 1; // 0초일 때는 아무 스킬도 사용하지 않는 한 가지 경우

// 점화식 적용
for (let i = 1; i &lt;= N; i++) {
    // A 스킬을 사용하는 경우 (1초 전의 상태에서 가능한 조합 수)
    if (i &gt;= 1) {
        dp[i] = (dp[i] + dp[i - 1]) % MOD;
    }

    // B 스킬을 사용하는 경우 (M초 전의 상태에서 가능한 조합 수)
    if (i &gt;= M) {
        dp[i] = (dp[i] + dp[i - M]) % MOD;
    }
}

console.log(dp[N]);</code></pre>
<p>참고
<a href='https://bedecked-operation-4d1.notion.site/99-15-TIL-Permutation-Combination-1d9eb405261e80a08441c22e38b6c0ff' target='_black'>참고링크1</a>
<a href='https://velog.io/@deun/99클럽-코테-스터디-15일차-TIL-리그-오브-레전설-Small-dp' target='_black'>참고링크2</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 14일차 TIL - 진우의 달 여행 (3차원DP)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-14%EC%9D%BC%EC%B0%A8-TIL-%EC%A7%84%EC%9A%B0%EC%9D%98-%EB%8B%AC-%EC%97%AC%ED%96%89-3%EC%B0%A8%EC%9B%90DP</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-14%EC%9D%BC%EC%B0%A8-TIL-%EC%A7%84%EC%9A%B0%EC%9D%98-%EB%8B%AC-%EC%97%AC%ED%96%89-3%EC%B0%A8%EC%9B%90DP</guid>
            <pubDate>Thu, 17 Apr 2025 10:58:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/17484">https://www.acmicpc.net/problem/17484</a>
알고리즘 분류: 3차원 다이나믹 프로그래밍, 브루트포스</p>
</blockquote>
<p>어렵다.. DP 유형 많이 풀어봐야 겠다</p>
<h2 id="문제-파악">문제 파악</h2>
<p>최소 연료 소비로 달까지 가는 경로 구하는 문제</p>
<ul>
<li><code>N×M</code> 행렬에서 첫 행(지구)에서 출발해 마지막 행(달)에 도착해야 함</li>
<li>우주선은 왼쪽 아래(↙️), 아래(⬇️), 오른쪽 아래(↘️) 방향으로만 이동 가능</li>
<li>같은 방향으로 두 번 연속 이동할 수 없음</li>
<li>목표: 최소한의 연료로 도착하기</li>
</ul>
<h2 id="문제-접근-다이나믹-프로그래밍">문제 접근: 다이나믹 프로그래밍</h2>
<p>다이나믹 프로그래밍으로 접근한 이유</p>
<ol>
<li>최적 부분 구조(Optimal Substructure): 어떤 위치에서의 최소 연료는 이전 위치들의 최소 연료에 기반</li>
<li>중복되는 부분 문제(Overlapping Subproblems): 여러 경로가 같은 중간 지점을 지날 수 있다</li>
<li>방향 제약 조건: 이전 이동 방향을 기억해야 한다는 제약이 있어서 상태를 저장해야 한다</li>
</ol>
<h2 id="코드">코드</h2>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39;\n&#39;);

const [N, M] = input[0].split(&#39; &#39;).map(Number);  // N: 행, M: 열
const space = [];  // 연료 소비량을 저장할 2차원 배열
for (let i = 1; i &lt;= N; i++) {
    space.push(input[i].split(&#39; &#39;).map(Number));
}

const LEFT_DOWN = 0;   // 왼쪽 아래 ↙️
const DOWN = 1;        // 아래 ⬇️
const RIGHT_DOWN = 2;  // 오른쪽 아래 ↘️

// DP 배열 초기화: dp[row][col][dir]
// - row, col: 현재 위치 (0부터 시작)
// - dir: 이전에 움직인 방향 (0: 왼쪽 아래, 1: 아래, 2: 오른쪽 아래)
// - 초기값은 무한대(도달할 수 없음)로 설정
const dp = Array(N).fill().map(() =&gt; 
    Array(M).fill().map(() =&gt; 
        Array(3).fill(Infinity)
    )
);

// 첫 번째 행(지구)의 모든 위치에서 시작 가능
// 첫 번째 행에서는 이전 방향이 없으므로 모든 방향에서 해당 위치의 연료만큼 초기화
for (let j = 0; j &lt; M; j++) {
    // 첫 행의 각 위치에 도달하는 초기 비용은 그 위치의 연료 소비량과 동일
    dp[0][j][LEFT_DOWN] = space[0][j];
    dp[0][j][DOWN] = space[0][j];
    dp[0][j][RIGHT_DOWN] = space[0][j];
}

// 두 번째 행부터 시작
for (let i = 1; i &lt; N; i++) {
    for (let j = 0; j &lt; M; j++) {

        // 1. 왼쪽 아래로 오는 경우
        if (j &lt; M - 1) { // j+1이 배열 범위 내에 있어야 함 (오른쪽 끝 열이 아닌 경우)
            // 이전 위치 (i-1, j+1)에서 아래(1) 또는 오른쪽 아래(2) 방향으로 왔을 때의 최소값
            // 같은 방향(왼쪽 아래=0)으로 연속 이동할 수 없으므로 제외
            dp[i][j][LEFT_DOWN] = Math.min(
                dp[i-1][j+1][DOWN],       // 이전에 아래로 움직였던 경우
                dp[i-1][j+1][RIGHT_DOWN]  // 이전에 오른쪽 아래로 움직였던 경우
            ) + space[i][j];  
        }

        // 2. 아래로 오는 경우
        // 이전 위치 (i-1, j)에서 왼쪽 아래(0) 또는 오른쪽 아래(2) 방향으로 왔을 때의 최소값
        // 같은 방향(아래=1)으로 연속 이동할 수 없으므로 제외
        dp[i][j][DOWN] = Math.min(
            dp[i-1][j][LEFT_DOWN],    // 이전에 왼쪽 아래로 움직였던 경우
            dp[i-1][j][RIGHT_DOWN]    // 이전에 오른쪽 아래로 움직였던 경우
        ) + space[i][j];  

        // 3. 오른쪽 아래로 오는 경우
        if (j &gt; 0) { // j-1이 배열 범위 내에 있어야 함 (왼쪽 끝 열이 아닌 경우)
            // 이전 위치 (i-1, j-1)에서 왼쪽 아래(0) 또는 아래(1) 방향으로 왔을 때의 최소값
            // 같은 방향(오른쪽 아래=2)으로 연속 이동할 수 없으므로 제외
            dp[i][j][RIGHT_DOWN] = Math.min(
                dp[i-1][j-1][LEFT_DOWN], // 이전에 왼쪽 아래로 움직였던 경우
                dp[i-1][j-1][DOWN]       // 이전에 아래로 움직였던 경우
            ) + space[i][j]; 
        }
    }
}

// 마지막 행(달)에 도착했을 때 최소 연료(minFuel) 계산
// 어느 위치든 도착할 수 있으므로 마지막 행의 모든 열에 대해 최소값 찾기
let minFuel = Infinity;
for (let j = 0; j &lt; M; j++) {
    // 세 가지 가능한 방향 중 최소값 찾기
    minFuel = Math.min(
        minFuel,
        dp[N-1][j][LEFT_DOWN],   // 왼쪽 아래로 마지막에 도착한 경우
        dp[N-1][j][DOWN],        // 아래로 마지막에 도착한 경우
        dp[N-1][j][RIGHT_DOWN]   // 오른쪽 아래로 마지막에 도착한 경우
    );
}
console.log(minFuel);
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 13일차 TIL - JadenCase (문자열)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-13%EC%9D%BC%EC%B0%A8-TIL-JadenCase-%EB%AC%B8%EC%9E%90%EC%97%B4</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-13%EC%9D%BC%EC%B0%A8-TIL-JadenCase-%EB%AC%B8%EC%9E%90%EC%97%B4</guid>
            <pubDate>Wed, 16 Apr 2025 12:33:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://school.programmers.co.kr/learn/courses/30/lessons/12951">https://school.programmers.co.kr/learn/courses/30/lessons/12951</a>
타입: 문자열 조작 구현</p>
</blockquote>
<h2 id="문제-설명">문제 설명</h2>
<p>JadenCase란 모든 단어의 첫 문자가 대문자이고, 그 외의 알파벳은 소문자인 문자열입니다. 
단, 첫 문자가 알파벳이 아닐 때에는 이어지는 알파벳은 소문자로 쓰면 됩니다. (첫 번째 입출력 예 참고)
문자열 s가 주어졌을 때, s를 JadenCase로 바꾼 문자열을 리턴하는 함수, solution을 완성해주세요.</p>
<h3 id="제한-조건">제한 조건</h3>
<p>s는 길이 1 이상 200 이하인 문자열입니다.
s는 알파벳과 숫자, 공백문자(&quot; &quot;)로 이루어져 있습니다.
숫자는 단어의 첫 문자로만 나옵니다.
숫자로만 이루어진 단어는 없습니다.
공백문자가 연속해서 나올 수 있습니다.</p>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">function solution(s) {
    return s.split(&quot; &quot;).map(word =&gt; {
            if (word === &quot;&quot;) return &quot;&quot;;
            return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
        })
        .join(&quot; &quot;);
}</code></pre>
<h2 id="내장-메서드">내장 메서드</h2>
<p><code>split(separator)</code></p>
<p>문자열을 지정된 구분자(separator)를 기준으로 나눠서 배열로 반환
s.split(&quot; &quot;)는 공백 문자를 기준으로 문자열 s를 여러 단어로 나눔
예: &quot;hello world&quot;.split(&quot; &quot;) → [&quot;hello&quot;, &quot;world&quot;]
연속된 구분자가 있으면 빈 문자열이 포함 됨
예: &quot;hello  world&quot;.split(&quot; &quot;) → [&quot;hello&quot;, &quot;&quot;, &quot;world&quot;]</p>
<p><code>map(callback)</code></p>
<p>배열의 각 요소에 주어진 함수(callback)를 적용하여 새 배열을 생성
원본 배열의 각 요소를 변환하는 데 사용
예: [1, 2, 3].map(x =&gt; x * 2) → [2, 4, 6]</p>
<p><code>charAt(index)</code></p>
<p>문자열에서 지정된 인덱스에 있는 문자를 반환
<code>word.charAt(0)</code>은 단어의 첫 번째 문자를 가져옴
예: &quot;hello&quot;.charAt(0) → &quot;h&quot;</p>
<p><code>toUpperCase()</code></p>
<p>문자열의 모든 문자를 대문자로 변환
<code>word.charAt(0).toUpperCase()</code>는 단어의 첫 번째 문자를 대문자로 변환
숫자나 특수 문자에 적용해도 변화가 없음
예: &quot;hello&quot;.toUpperCase() → &quot;HELLO&quot;</p>
<p><code>slice(start, end)</code></p>
<p>문자열의 일부를 추출하여 새 문자열로 반환
start 인덱스부터 end 인덱스 전까지의 부분을 잘라냄
end를 생략하면 문자열의 끝까지 추출
<code>word.slice(1)</code>은 두 번째 문자부터 끝까지의 부분 문자열을 가져옴
예: &quot;hello&quot;.slice(1) → &quot;ello&quot;</p>
<p><code>toLowerCase()</code></p>
<p>문자열의 모든 문자를 소문자로 변환
<code>word.slice(1).toLowerCase()</code>는 단어의 두 번째 문자부터 끝까지를 소문자로 만듬
예: &quot;HELLO&quot;.toLowerCase() → &quot;hello&quot;</p>
<p><code>join(separator)</code></p>
<p>배열의 모든 요소를 연결해 하나의 문자열로 만듬
요소 사이에는 지정된 구분자(separator)가 삽입
.join(&quot; &quot;)는 배열의 각 요소를 공백으로 구분하여 하나의 문자열로 합침
예: [&quot;hello&quot;, &quot;world&quot;].join(&quot; &quot;) → &quot;hello world&quot;</p>
<p>참고
<a href="https://bedecked-operation-4d1.notion.site/99-13-TIL-JavaScript-sort-method-1d7eb405261e80a6a2b3d3c2186c7fc5">https://bedecked-operation-4d1.notion.site/99-13-TIL-JavaScript-sort-method-1d7eb405261e80a6a2b3d3c2186c7fc5</a>
<a href="https://velog.io/@deun/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-13%EC%9D%BC%EC%B0%A8-TIL-JadenCase-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D">https://velog.io/@deun/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-13%EC%9D%BC%EC%B0%A8-TIL-JadenCase-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 12일차 TIL - 포도주 시식 (DP)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-12%EC%9D%BC%EC%B0%A8-TIL-%ED%8F%AC%EB%8F%84%EC%A3%BC-%EC%8B%9C%EC%8B%9D-DP</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-12%EC%9D%BC%EC%B0%A8-TIL-%ED%8F%AC%EB%8F%84%EC%A3%BC-%EC%8B%9C%EC%8B%9D-DP</guid>
            <pubDate>Tue, 15 Apr 2025 08:55:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/2156">https://www.acmicpc.net/problem/2156</a>
알고리즘 분류: 다이나믹 프로그래밍, DP</p>
</blockquote>
<h2 id="문제-파악">문제 파악</h2>
<ul>
<li>포도주 잔을 선택하면 그 잔의 포도주는 모두 마셔야 함</li>
<li>연속으로 3잔을 모두 마실 수 없음</li>
<li>가능한 많은 양의 포도주를 마시는 것이 목표</li>
</ul>
<h2 id="접근-방법-동적-프로그래밍">접근 방법: 동적 프로그래밍</h2>
<ul>
<li>계산이 진행되면서 바뀌는 상황에서, 최적의 전략</li>
<li>계산했던 걸 저장해서 재사용</li>
</ul>
<h3 id="각-위치에서-최대로-마실-수-있는-포도주의-양-구하기">각 위치에서 최대로 마실 수 있는 포도주의 양 구하기</h3>
<p><strong>경우의 수</strong></p>
<ol>
<li>현재 포도주를 마시고, 이전 포도주도 마신 경우 (2연속)</li>
<li>현재 포도주를 마시고, 이전 포도주는 마시지 않은 경우</li>
<li>현재 포도주를 마시지 않는 경우</li>
</ol>
<p><strong>DP 상태</strong>
<code>dp[i][j]</code>: <code>i</code>번째 포도주까지 고려했을 때, <code>j</code>개의 연속된 포도주를 마셨을 때의 최대 양</p>
<ul>
<li><code>dp[i][0]</code>: <code>i</code>번째 잔을 마시지 않았을 때의 최대값</li>
<li><code>dp[i][1]</code>: <code>i</code>번째 잔을 마시고, <code>i-1</code>번째 잔은 마시지 않았을 때의 최대값</li>
<li><code>dp[i][2]</code>: <code>i</code>번째 잔을 마시고, <code>i-1</code>번째 잔도 마셨을 때의 최대값</li>
</ul>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39;\n&#39;).map(Number);

const n = input[0];  // 포도주 잔의 개수
const wines = [0];  // 포도주의 양 (인덱스를 1부터 시작하기 위해 0 추가)

// 포도주 양 입력 처리
for (let i = 1; i &lt;= n; i++) {
    wines.push(input[i]);
}

// 동적 프로그래밍 배열 초기화
// dp[i][j]: i번째 잔까지 고려했을 때, j개 연속으로 마셨을 때의 최대 양
// dp[i][0]: i번째 포도주를 마시지 않은 경우
// dp[i][1]: i번째 포도주를 마시고, i-1번째는 마시지 않은 경우
// dp[i][2]: i번째 포도주를 마시고, i-1번째도 마신 경우
const dp = Array.from({ length: n + 1 }, () =&gt; Array(3).fill(0));

// 초기값 설정
dp[1][0] = 0;             // 첫 번째 포도주를 마시지 않음
dp[1][1] = wines[1];      // 첫 번째 포도주를 마심

for (let i = 2; i &lt;= n; i++) {
    dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1], dp[i-1][2]);  
    dp[i][1] = dp[i-1][0] + wines[i];
    dp[i][2] = dp[i-1][1] + wines[i];
}

// 최대값 계산
const maxAmount = Math.max(dp[n][0], dp[n][1], dp[n][2]);
console.log(maxAmount);</code></pre>
<h3 id="각-상태-점화식">각 상태 점화식</h3>
<p><code>dp[i][0] = max(dp[i-1][0], dp[i-1][1], dp[i-1][2])</code> → 현재 잔을 마시지 않으면 이전 상태 중 최대값 선택
<code>dp[i][1] = dp[i-1][0] + wines[i]</code> → 현재 잔을 마시고, 이전 잔은 마시지 않은 경우
<code>dp[i][2] = dp[i-1][1] + wines[i]</code> → 현재 잔과 이전 잔을 연속으로 마시는 경우</p>
<p>참고 
<a href="https://velog.io/@deun/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-12%EC%9D%BC%EC%B0%A8-TIL-%ED%8F%AC%EB%8F%84%EC%A3%BC-%EC%8B%9C%EC%8B%9D-dp">deun블로그</a>
<a href="https://bedecked-operation-4d1.notion.site/99-12-TIL-DP-2156-1d6eb405261e8085b424f799344a670d">https://bedecked-operation-4d1.notion.site/99-12-TIL-DP-2156-1d6eb405261e8085b424f799344a670d</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 11일차 TIL - 과자 나눠주기(이진탐색)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-11%EC%9D%BC%EC%B0%A8-TIL-%EA%B3%BC%EC%9E%90-%EB%82%98%EB%88%A0%EC%A3%BC%EA%B8%B0%EC%9D%B4%EC%A7%84%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-11%EC%9D%BC%EC%B0%A8-TIL-%EA%B3%BC%EC%9E%90-%EB%82%98%EB%88%A0%EC%A3%BC%EA%B8%B0%EC%9D%B4%EC%A7%84%ED%83%90%EC%83%89</guid>
            <pubDate>Mon, 14 Apr 2025 08:43:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/16401">https://www.acmicpc.net/problem/16401</a>
알고리즘 분류: 이분 탐색, 매개 변수 탐색</p>
</blockquote>
<h2 id="문제-파악">문제 파악</h2>
<p>주어진 과자들을 특정 길이로 잘라서, 조카들에게 나눠줄 수 있는 최대 길이를 찾는 문제</p>
<ul>
<li><p><code>M</code>명의 조카와 <code>N</code>개의 과자</p>
</li>
<li><p>모든 조카에게 같은 길이의 과자를 나눠줘야 한다</p>
</li>
<li><p>과자는 여러 조각으로 <strong>나눌 수 있지만, 합칠 수는 없다</strong></p>
</li>
<li><p>조카 1명에게 줄 수 있는 과자의 최대 길이 구하기</p>
</li>
</ul>
<h2 id="접근-방법-이진-탐색-binary-search">접근 방법: 이진 탐색 (Binary Search)</h2>
<p>결정 문제(Decision Problem)
&quot;과자 길이가 <code>mid</code>일 때, <code>M</code>명의 조카에게 나눠줄 수 있어?&quot;</p>
<ol>
<li><p>가능한 과자 길이의 범위:  1 부터 가장 긴 과자 길이까지</p>
</li>
<li><p>특정 길이 <code>mid</code>로 자를 때, 만들 수 있는 과자 조각의 수 <code>count</code> 계산</p>
</li>
<li><p><code>count</code>가 <code>M</code> 이상 ➡️ 더 긴 길이를 찾아</p>
</li>
<li><p><code>count</code>가 <code>M</code> 미만 ➡️ 더 짧은 길이 찾아</p>
</li>
</ol>
<h3 id="예시-✅">예시 ✅</h3>
<p>조카<code>M</code> 3명, 과자길이<code>snacks = [1,2,3,4,5,6,7,8,9,10]</code></p>
<p>이진 탐색 과정:</p>
<ol>
<li><p>첫 번째 시도 할 과자길이 <code>mid = 5</code> (1~10의 중간값)
총 조각 수<code>count</code> = <code>7개 &gt;= 3명</code>(가능) → 더 긴 길이로 시도 → <strong><code>left = mid + 1 = 6</code></strong>, right = 10</p>
</li>
<li><p>두 번째 시도 할 과자길이 <code>mid = 8</code> (6~10의 중간값)
총 조각 수<code>count</code> = <code>3개 &gt;= 3명</code>(가능) → 더 긴 길이로 시도 → <strong><code>left = mid + 1 = 9</code></strong>, right = 10</p>
</li>
<li><p>세 번째 시도 할 과자길이 <code>mid = 9</code> (9~10의 중간값)
총 조각 수<code>count</code> = <code>2개 &lt; 3명</code>(불가능) → 더 짧은 길이로 시도 → left = 9, <strong><code>right = mid - 1 = 8</code></strong> → 종료</p>
</li>
</ol>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39;\n&#39;);

const [M, N] = input[0].split(&#39; &#39;).map(Number); 
const snacks = input[1].split(&#39; &#39;).map(Number); 

const findMaxSnackLength = (nephews, snacks) =&gt; {
    let left = 1;
    let right = Math.max(...snacks);
    let result = 0;

    while (left &lt;= right) {
        const mid = Math.floor((left + right) / 2); 
        let count = 0;
        for (const snack of snacks) {
            count += Math.floor(snack / mid); 
        }

        if (count &gt;= nephews) {
            result = mid; 
            left = mid + 1; 
        } else {
            right = mid - 1;
        }
    }

    return result;
}

const answer = findMaxSnackLength(M, snacks);
console.log(answer);</code></pre>
<h2 id="시간-복잡도">시간 복잡도</h2>
<ul>
<li>이진 탐색: O(log(max_length))</li>
<li>각 단계에서 모든 과자를 검사: O(N)</li>
<li>전체 시간 복잡도: O(N * log(max_length))</li>
</ul>
<p>과자의 최대 길이가 10^9이므로, 이진 탐색은 최대 약 30번 정도의 반복으로 결과 도출</p>
<p>참고
<a href="https://bedecked-operation-4d1.notion.site/99-11-TIL-16401-1d5eb405261e80c1a245db9ed837464f">https://bedecked-operation-4d1.notion.site/99-11-TIL-16401-1d5eb405261e80c1a245db9ed837464f</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 10일차 TIL - 병든 나이트 (그리디)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-10%EC%9D%BC%EC%B0%A8-TIL-%EB%B3%91%EB%93%A0-%EB%82%98%EC%9D%B4%ED%8A%B8-%EA%B7%B8%EB%A6%AC%EB%94%94</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-10%EC%9D%BC%EC%B0%A8-TIL-%EB%B3%91%EB%93%A0-%EB%82%98%EC%9D%B4%ED%8A%B8-%EA%B7%B8%EB%A6%AC%EB%94%94</guid>
            <pubDate>Fri, 11 Apr 2025 11:34:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/1783">https://www.acmicpc.net/problem/1783</a>
알고리즘 분류: 많은 조건 분기, 그리디 알고리즘, 구현</p>
</blockquote>
<h2 id="문제-파악">문제 파악</h2>
<p>병든 나이트🐴가 체스판에서 최대한 많은 칸을 방문하는 문제</p>
<p>주요 조건:</p>
<ol>
<li>N × M 크기의 체스판에서 가장 왼쪽 아래 칸에서 시작</li>
<li>병든 나이트는 4가지 이동 방법만 가능</li>
</ol>
<ul>
<li>2칸 위로, 1칸 오른쪽</li>
<li>1칸 위로, 2칸 오른쪽</li>
<li>1칸 아래로, 2칸 오른쪽</li>
<li>2칸 아래로, 1칸 오른쪽</li>
</ul>
<ol start="3">
<li>이동 횟수가 4번 이상이면 모든 이동 방법을 한 번씩 사용해야 함</li>
<li>이동 횟수가 4번 미만이면 제약 없음</li>
</ol>
<h2 id="접근-방법-그리디greedy-알고리즘">접근 방법: 그리디(Greedy) 알고리즘</h2>
<ol>
<li>체스판 크기에 따라 가능한 이동 횟수가 제한 됨</li>
<li>최대 방문 칸 수는, <strong>처음 위치 포함하여 <code>이동 횟수 + 1</code></strong></li>
<li>이동 방향:</li>
</ol>
<ul>
<li>모든 이동은 오른쪽으로 이동</li>
<li>위/아래 이동은 <strong>체스판 경계</strong>에 영향을 받는다</li>
</ul>
<ol start="4">
<li>4번 이상 이동할 때는 최소 한번씩 모든 방향으로 이동해야 한다</li>
</ol>
<h3 id="단계별-접근">단계별 접근</h3>
<ol>
<li>체스판의 크기에 따라 가능한 최대 이동 횟수를 계산</li>
<li>이동 횟수가 4번 미만인 경우와 4번 이상인 경우를 나누어 처리</li>
<li>4번 이상일 때는 모든 방향을 한 번씩 써야 하므로, 체스판을 벗어나지 않게 이동 방법을 결정해야 함</li>
</ol>
<h3 id="특수-케이스-처리">특수 케이스 처리</h3>
<p><code>N = 1</code> 또는 <code>M = 1</code>인 경우:</p>
<p>체스판이 한 줄짜리면 시작 위치 외에 더 이동할 수 없으므로 1을 반환</p>
<p><code>N = 2</code>인 경우:</p>
<p>세로가 2칸인 경우 <code>1칸 위로, 2칸 오른쪽</code>과 <code>1칸 아래로, 2칸 오른쪽</code> 두 가지 방법만 사용 가능 
➡️ 이 경우 가로로 최대 <code>(M+1)/2</code> 칸까지 이동 가능하지만, 4번까지만 허용 됨</p>
<p><code>N ≥ 3</code>, <code>M &lt; 7</code>인 경우:</p>
<p>가로 길이가 충분하지 않아 제약 조건을 만족하기 어려운 경우
최대 M개 칸까지 방문 가능하지만, 4칸으로 제한 됨</p>
<p><code>N ≥ 3</code>, <code>M ≥ 7</code>인 경우:</p>
<p>충분히 큰 체스판에서는 모든 이동 방법을 한 번씩 사용하고 나서도 최적의 방법으로 이동 가능
모든 이동 방법을 한 번씩 사용하면 오른쪽으로 총 6칸 이동하게 됨
그 후로는 <code>1칸 위, 2칸 오른쪽</code>과 <code>1칸 아래, 2칸 오른쪽</code>을 번갈아 사용하여 오른쪽으로 이동
➡️ 결과적으로 <code>M-2</code>개의 칸을 방문할 수 있음</p>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const [N, M] = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39; &#39;).map(Number);

function solution(N, M) {
    // 시작 위치 포함하여 최소 1개의 칸은 방문
    if (N === 1 || M === 1) {
        return 1;
    }

    // 세로 길이가 1 초과, 가로 길이가 1일 때는 방문할 수 없음
    if (N &gt; 1 &amp;&amp; M === 1) {
        return 1;
    }

    // 세로 길이가 2일 때는 특별한 경우
    if (N === 2) {
        // 가로로 움직일 수 있는 최대 횟수 (1칸 위로, 2칸 오른쪽 / 1칸 아래로, 2칸 오른쪽만 사용 가능)
        // 이동 횟수는 (M-1)/2 이하이고 최대 4회까지 가능
        return Math.min(4, Math.floor((M + 1) / 2));
    }

    // N &gt;= 3, M &lt; 7인 경우 최대 4칸까지만 이동 가능
    if (M &lt; 7) {
        return Math.min(4, M);
    }

    // 일반적인 경우, N &gt;= 3, M &gt;= 7
    // 4번 이상 이동할 때 모든 이동 방법을 한 번씩 사용해야 함
    // 모든 이동 방법을 한 번씩 사용하면 오른쪽으로 총 6칸 이동
    // 그 후로는 1칸 위, 2칸 오른쪽 + 1칸 아래, 2칸 오른쪽 조합으로 반복
    return M - 2;
}

console.log(solution(N, M));</code></pre>
<p>참고
<a href="https://bedecked-operation-4d1.notion.site/99-10-TIL-1783-1d2eb405261e803fb153f95bec2f86a1">https://bedecked-operation-4d1.notion.site/99-10-TIL-1783-1d2eb405261e803fb153f95bec2f86a1</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 9일차 TIL - 저울 (그리디)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-9%EC%9D%BC%EC%B0%A8-TIL-%EC%A0%80%EC%9A%B8-%EA%B7%B8%EB%A6%AC%EB%94%94</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-9%EC%9D%BC%EC%B0%A8-TIL-%EC%A0%80%EC%9A%B8-%EA%B7%B8%EB%A6%AC%EB%94%94</guid>
            <pubDate>Thu, 10 Apr 2025 05:11:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/2437">https://www.acmicpc.net/problem/2437</a>
알고리즘 분류: 그리디 알고리즘, 정렬</p>
</blockquote>
<h2 id="문제-파악">문제 파악</h2>
<p>그리디 알고리즘을 활용해 풀 수 있는 문제
주어진 저울추로 측정할 수 없는 가장 작은 양의 정수를 찾아야 함</p>
<h2 id="문제-접근">문제 접근</h2>
<p>저울추를 오름차순으로 정렬한 뒤, 현재까지 만들 수 있는 최대 무게를 계속 추적</p>
<ol>
<li><p>저울추를 오름차순으로 정렬</p>
</li>
<li><p>변수 target을 1로 초기화 → 현재까지 1부터 <code>target-1</code>까지의 모든 무게를 만들 수 있다</p>
</li>
<li><p>각 저울추를 순회하며:
만약 현재 저울추의 무게가 target보다 크다면, target이 만들 수 없는 최소 무게이므로 반복을 종료
그렇지 않다면, 현재 저울추를 사용하여 target에 현재 저울추의 무게를 더함
→ 1부터 <code>target + weightArr[i] - 1</code>까지의 모든 무게를 만들 수 있음</p>
</li>
<li><p>모든 저울추를 처리한 후, <code>target</code>은 만들 수 없는 최소 무게가 됩니다.</p>
</li>
</ol>
<pre><code>예제 입력값 3 1 6 2 7 30 1에 대해:

오름차순 정렬: [1, 1, 2, 3, 6, 7, 30]

초기 target = 1
첫 번째 추(1): target = 1 + 1 = 2 (1까지 만들 수 있음)
두 번째 추(1): target = 2 + 1 = 3 (2까지 만들 수 있음)
세 번째 추(2): target = 3 + 2 = 5 (4까지 만들 수 있음)
네 번째 추(3): target = 5 + 3 = 8 (7까지 만들 수 있음)
다섯 번째 추(6): target = 8 + 6 = 14 (13까지 만들 수 있음)
여섯 번째 추(7): target = 14 + 7 = 21 (20까지 만들 수 있음)
일곱 번째 추(30): target = 21 + 30 = 51 (50까지 만들 수 있음)

최종적으로 측정할 수 없는 가장 작은 양의 정수: 21</code></pre><h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const [n, weights] = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39;\n&#39;);

const weightArr = weights.split(&#39; &#39;).map(Number).sort((a, b) =&gt; a - b);

let target = 1;

for (let i = 0; i &lt; weightArr.length; i++) {
  // 현재 저울추의 무게가 target보다 크면, target이 만들 수 없는 최소 무게
  if (weightArr[i] &gt; target) {
    break;
  }

  // 현재 저울추를 사용해 target을 업데이트
  // target + weightArr[i]까지의 모든 무게를 만들 수 있게 됨
  target += weightArr[i];
}

console.log(target);</code></pre>
<h2 id="그리디-알고리즘greedy-algorithm">그리디 알고리즘(Greedy Algorithm)</h2>
<ul>
<li><p>매 선택 단계에서 현재 상황에서 가장 최적인 선택(locally optimal choice)을 하는 알고리즘</p>
</li>
<li><p>예) 거스름돈 문제: 최소한의 동전 개수로 거스름돈을 만드는 문제</p>
<pre><code class="language-javascript">function coinChange(amount, coins) {
  // 큰 금액의 동전부터 사용하기 위해 내림차순 정렬
  coins.sort((a, b) =&gt; b - a);

  let totalCoins = 0;
  let remainingAmount = amount;
  let result = {};

  for (let coin of coins) {
      const count = Math.floor(remainingAmount / coin);
      if (count &gt; 0) {
          result[coin] = count;
          totalCoins += count;
          remainingAmount -= coin * count;
      }
  }

  return {
      totalCoins: totalCoins,
      coinsUsed: result,
      remainingAmount: remainingAmount
  };
}
</code></pre>
</li>
</ul>
<p>// 예시: 한국 화폐 단위로 4,760원을 거슬러주기
const koreanCoins = [5000, 1000, 500, 100, 50, 10];
console.log(coinChange(4760, koreanCoins));</p>
<p>```</p>
<p>참고
<a href="https://bedecked-operation-4d1.notion.site/99-9-TIL-1d1eb405261e80bfa59ec5cbd1da3343">https://bedecked-operation-4d1.notion.site/99-9-TIL-1d1eb405261e80bfa59ec5cbd1da3343</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 8일차 TIL - 한국이 그리울 땐 서버에 접속하지 (문자열)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-8%EC%9D%BC%EC%B0%A8-TIL-%ED%95%9C%EA%B5%AD%EC%9D%B4-%EA%B7%B8%EB%A6%AC%EC%9A%B8-%EB%95%90-%EC%84%9C%EB%B2%84%EC%97%90-%EC%A0%91%EC%86%8D%ED%95%98%EC%A7%80-%EB%AC%B8%EC%9E%90%EC%97%B4</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-8%EC%9D%BC%EC%B0%A8-TIL-%ED%95%9C%EA%B5%AD%EC%9D%B4-%EA%B7%B8%EB%A6%AC%EC%9A%B8-%EB%95%90-%EC%84%9C%EB%B2%84%EC%97%90-%EC%A0%91%EC%86%8D%ED%95%98%EC%A7%80-%EB%AC%B8%EC%9E%90%EC%97%B4</guid>
            <pubDate>Wed, 09 Apr 2025 11:12:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/9996">https://www.acmicpc.net/problem/9996</a>
알고리즘 분류: 정규 표현식, 문자열</p>
</blockquote>
<h2 id="문제-정의">문제 정의</h2>
<p>패턴은 알파벳 소문자와 별표(*) 하나로 구성됨
파일 이름은 알파벳 소문자로만 구성됨
별표는 임의의 문자열(빈 문자열 포함)로 대체 가능
각 파일 이름이 패턴과 일치하는지 확인해야 함</p>
<h2 id="문제-파악">문제 파악</h2>
<p>패턴은 별표를 기준으로 앞부분(<code>prefix</code>)과 뒷부분(<code>suffix</code>)으로 나눌 수 있음
파일 이름이 패턴과 일치하려면:</p>
<p>파일 이름이 패턴의 앞부분으로 시작해야 함
파일 이름이 패턴의 뒷부분으로 끝나야 함
파일 이름의 길이가 최소한 (<code>prefix</code> + <code>suffix</code>) 이상이어야 함</p>
<h2 id="접근-방법">접근 방법</h2>
<p>패턴을 별표(<code>*</code>)를 기준으로 앞부분과 뒷부분으로 분리
각 파일 이름에 대해:</p>
<p>파일 이름이 패턴의 앞부분으로 시작하는지 확인
파일 이름이 패턴의 뒷부분으로 끝나는지 확인
중간 부분이 겹치지 않는지 확인 (<code>prefix</code>와 <code>suffix</code>가 겹치는 경우 고려)</p>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim();

const lines = input.split(&#39;\n&#39;);
const n = parseInt(lines[0]);
const pattern = lines[1];

const parts = pattern.split(&#39;*&#39;);
const prefix = parts[0];
const suffix = parts[1];

const results = [];

for (let i = 0; i &lt; n; i++) {
  const filename = lines[i + 2];

  // 파일 이름 길이가 prefix + suffix보다 짧으면 패턴과 일치 불가능
  if (filename.length &lt; prefix.length + suffix.length) {
    results.push(&quot;NE&quot;);
    continue;
  }

  // 파일 이름이 패턴의 앞부분으로 시작하는지 확인
  if (!filename.startsWith(prefix)) {
    results.push(&quot;NE&quot;);
    continue;
  }

  // 파일 이름이 패턴의 뒷부분으로 끝나는지 확인
  if (!filename.endsWith(suffix)) {
    results.push(&quot;NE&quot;);
    continue;
  }

  // 모든 조건을 만족하면 패턴과 일치
  results.push(&quot;DA&quot;);
}

console.log(results.join(&#39;\n&#39;));
</code></pre>
<p>참고
<a href="https://bedecked-operation-4d1.notion.site/99-8-TIL-1d0eb405261e80f68c5ed675c161efe7">https://bedecked-operation-4d1.notion.site/99-8-TIL-1d0eb405261e80f68c5ed675c161efe7</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 7일차 TIL - 쇠막대기 (스택)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-7%EC%9D%BC%EC%B0%A8-TIL-%EC%87%A0%EB%A7%89%EB%8C%80%EA%B8%B0-%EC%8A%A4%ED%83%9D</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-7%EC%9D%BC%EC%B0%A8-TIL-%EC%87%A0%EB%A7%89%EB%8C%80%EA%B8%B0-%EC%8A%A4%ED%83%9D</guid>
            <pubDate>Tue, 08 Apr 2025 13:23:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/10799">https://www.acmicpc.net/problem/10799</a>
알고리즘 분류: 자료 구조, 스택</p>
</blockquote>
<h2 id="문제-파악">문제 파악</h2>
<p>괄호로 표현된 쇠막대기와 레이저의 배치를 해석하는 문제</p>
<ul>
<li><p><code>(</code> 와 <code>)</code> 가 인접한 경우는 레이저를 의미</p>
</li>
<li><p>나머지 <code>(</code>는 쇠막대기의 시작, <code>)</code>는 쇠막대기의 끝을 의미</p>
</li>
<li><p>레이저는 쇠막대기를 자르며, 쇠막대기는 <code>자신을 지나는 레이저의 수 + 1</code>만큼 조각으로 나뉨</p>
</li>
<li><p>최종적으로 잘려진 쇠막대기 조각의 총 개수를 구하자</p>
</li>
</ul>
<h2 id="접근-방법-스택stack">접근 방법: 스택(Stack)</h2>
<ol>
<li><p>문자열을 왼쪽에서 오른쪽으로 순회</p>
</li>
<li><p><code>(</code>를 만나면: 스택에 넣는다 (잠재적인 쇠막대기 시작 또는 레이저 시작)</p>
</li>
<li><p><code>)</code>를 만나면:</p>
</li>
</ol>
<ul>
<li>직전 문자가 <code>(</code>였다면: 이는 <strong>레이저</strong>를 의미 
  → 스택에서 <code>(</code>를 pop하고, 현재 스택에 남아있는 <code>(</code>의 개수만큼 조각이 생김</li>
<li>직전 문자가 <code>(</code>가 아니었다면: 이는 <strong>쇠막대기의 끝</strong>을 의미 
  → 스택에서 <code>(</code>를 pop하고, 조각 개수를 <code>1 증가</code></li>
</ul>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim();
let answer = 0;
let stack = [];
let prevChar = &#39;&#39;;

for (let char of input) {
    if (char === &#39;(&#39;) {
        stack.push(char);
    } else { // 닫는 괄호인 경우
        stack.pop();

        if (prevChar === &#39;(&#39;) {
            // 레이저인 경우
            answer += stack.length;
        } else {
            // 막대기 끝인 경우
            answer += 1;
        }
    }
    prevChar = char;
}
console.log(answer);</code></pre>
<p>참고
<a href="https://bedecked-operation-4d1.notion.site/99-7-TIL-10799-Stack-1cfeb405261e8067bebdc807a89a6112">https://bedecked-operation-4d1.notion.site/99-7-TIL-10799-Stack-1cfeb405261e8067bebdc807a89a6112</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 6일차 TIL - 섬의 개수 (DFS/BFS)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-6%EC%9D%BC%EC%B0%A8-TIL-%EC%84%AC%EC%9D%98-%EA%B0%9C%EC%88%98-DFSBFS</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-6%EC%9D%BC%EC%B0%A8-TIL-%EC%84%AC%EC%9D%98-%EA%B0%9C%EC%88%98-DFSBFS</guid>
            <pubDate>Mon, 07 Apr 2025 09:03:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/4963">https://www.acmicpc.net/problem/4963</a>
알고리즘 분류: 그래프 이론, 그래프 탐색, 너비 우선 탐색(BFS), 깊이 우선 탐색(DFS)</p>
</blockquote>
<h2 id="문제-파악">문제 파악</h2>
<ul>
<li><p>그래프 이론에서 주로 다루는 &quot;연결 요소(Connected Components)&quot;를 찾는 문제</p>
</li>
<li><p>섬의 개수는 연결된 땅(1)의 그룹 수를 세는 것과 같다</p>
</li>
<li><p>지도는 0(바다)과 1(땅)로 구성된 2차원 배열</p>
</li>
<li><p>가로, 세로, 대각선으로 연결된 1(땅)은 하나의 섬으로 간주</p>
</li>
</ul>
<h2 id="접근-방법---dfs깊이우선탐색">접근 방법 - <code>DFS(깊이우선탐색)</code></h2>
<ol>
<li><p>지도의 모든 칸을 순회</p>
</li>
<li><p>0 땅(1)을 발견하면 해당 위치에서 DFS함수를 시작해 연결된 모든 땅을 방문하고 표시</p>
</li>
<li><p>한 번의 DFS함수가 완료되면 하나의 섬을 발견한 것이므로 섬 카운터 증가시킴</p>
</li>
<li><p>모든 칸을 순회할 때까지 1-3번 반복</p>
</li>
</ol>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim().split(&#39;\n&#39;);

let lineIndex = 0;
const results = [];

while (lineIndex &lt; input.length) {

  const [w, h] = input[lineIndex++].split(&#39; &#39;).map(Number);

  if (w === 0 &amp;&amp; h === 0) break;

  const map = [];
  for (let i = 0; i &lt; h; i++) {
    map.push(input[lineIndex++].split(&#39; &#39;).map(Number));
  }

  const visited = Array(h).fill().map(() =&gt; Array(w).fill(false));

  let islandCount = 0;

  for (let i = 0; i &lt; h; i++) {
    for (let j = 0; j &lt; w; j++) {
      if (map[i][j] === 1 &amp;&amp; !visited[i][j]) {
        dfs(map, visited, i, j, h, w);
        islandCount++;
      }
    }
  }

  results.push(islandCount);
}

function dfs(map, visited, i, j, h, w) {

  if (i &lt; 0 || i &gt;= h || j &lt; 0 || j &gt;= w) return;

  if (visited[i][j] || map[i][j] === 0) return;

  visited[i][j] = true;

  const directions = [
    [-1, -1], [-1, 0], [-1, 1],
    [0, -1],           [0, 1],
    [1, -1],  [1, 0],  [1, 1]
  ];

  for (const [dx, dy] of directions) {
    dfs(map, visited, i + dx, j + dy, h, w);
  }
}

console.log(results.join(&#39;\n&#39;));</code></pre>
<h2 id="성능">성능</h2>
<ul>
<li><p>시간복잡도(실행속도): 172 ms</p>
</li>
<li><p>공간복잡도(메모리): 12464 KB</p>
</li>
</ul>
<h2 id="개선할-점">개선할 점</h2>
<ul>
<li><p>재귀 호출을 사용하는 DFS는, 지도 크기가 클 경우 스택 오버플로우 발생 가능성 ⬆️</p>
</li>
<li><p><code>BFS</code>를 사용하면 이런 문제를 피할 수 있고, 특히 자바스크립트에서는 더 효율적일 수 있음</p>
</li>
</ul>
<p>참고
<a href="https://bedecked-operation-4d1.notion.site/99-6-TIL-1ceeb405261e809699ffcb380cbc3e32">https://bedecked-operation-4d1.notion.site/99-6-TIL-1ceeb405261e809699ffcb380cbc3e32</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 5일차 TIL - 수열 (슬라이딩 윈도우)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-5%EC%9D%BC%EC%B0%A8-TIL-%EC%88%98%EC%97%B4</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-5%EC%9D%BC%EC%B0%A8-TIL-%EC%88%98%EC%97%B4</guid>
            <pubDate>Fri, 04 Apr 2025 14:03:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/2559">https://www.acmicpc.net/problem/2559</a>
알고리즘 분류: 누적 합, 슬라이딩 윈도우, 두 포인터</p>
</blockquote>
<h2 id="문제-파악">문제 파악</h2>
<p><strong>N일 동안의 온도 데이터</strong>가 주어짐
<strong>연속된 K일 동안의 온도 합을 계산</strong>
가능한 모든 연속된 K일 기간 중에서 <strong>온도 합이 최대인 값을 찾아야</strong> 함</p>
<h2 id="접근-방법">접근 방법</h2>
<h3 id="슬라이딩-윈도우sliding-window-알고리즘"><code>슬라이딩 윈도우(Sliding Window)</code> 알고리즘</h3>
<ul>
<li>배열이나 문자열과 같은 데이터 구조에서 연속된 요소들의 집합을 처리할 때 사용하는 기법</li>
</ul>
<h3 id="슬라이딩-윈도우-접근법">슬라이딩 윈도우 접근법:</h3>
<ul>
<li><p>처음 K개 요소의 합을 계산</p>
</li>
<li><p>윈도우를 한 칸씩 오른쪽으로 이동하면서:</p>
<ul>
<li>왼쪽 끝 요소를 제거</li>
<li>오른쪽에 새로운 요소 추가</li>
<li>최대값 갱신</li>
</ul>
</li>
</ul>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim();

function findMaxTemperatureSum(N, K, temperatures) {
  // 처음 K일 동안의 온도 합 계산
  let currentSum = 0;
  for (let i = 0; i &lt; K; i++) {
    currentSum += temperatures[i];
  }

  // 최대값을 현재 합으로 초기화
  let maxSum = currentSum;

  // 슬라이딩 윈도우 적용
  for (let i = K; i &lt; N; i++) {
    // 윈도우 이동: 왼쪽 끝 요소 제거하고 오른쪽 새 요소 추가
    currentSum = currentSum - temperatures[i - K] + temperatures[i];

    // 최대값 갱신
    maxSum = Math.max(maxSum, currentSum);
  }

  return maxSum;
}

// 입력 처리
function solution(input) {
  const lines = input.trim().split(&#39;\n&#39;);
  const [N, K] = lines[0].split(&#39; &#39;).map(Number);
  const temperatures = lines[1].split(&#39; &#39;).map(Number);

  return findMaxTemperatureSum(N, K, temperatures);
}

console.log(solution(input));</code></pre>
<p>참고:
<a href="https://bedecked-operation-4d1.notion.site/99-5-TIL-1cbeb405261e8038a22fcdea4309d5c4">https://bedecked-operation-4d1.notion.site/99-5-TIL-1cbeb405261e8038a22fcdea4309d5c4</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 4일차 TIL - 안전 영역 (DFS)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%9D%BC%EC%B0%A8-TIL-%EC%95%88%EC%A0%84-%EC%98%81%EC%97%AD-DFS</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%9D%BC%EC%B0%A8-TIL-%EC%95%88%EC%A0%84-%EC%98%81%EC%97%AD-DFS</guid>
            <pubDate>Thu, 03 Apr 2025 14:25:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/2468">https://www.acmicpc.net/problem/2468</a>
알고리즘 분류: 너비 우선 탐색, 브루트포스 알고리즘, 깊이 우선 탐색, 그래프 이론, 그래프 탐색</p>
</blockquote>
<h2 id="컨디션-이슈">컨디션 이슈</h2>
<ul>
<li>감기에 제대로 걸려서 오늘 하루 책상에 앉아있는 게 고역이었다</li>
<li>3시간 고민하고도 문제 해결이 어려워서 이론부터 공부하기로 했다</li>
<li>구글링과 ai의 힘을 빌려 힌트를 얻고, 답을 완성해나갔다</li>
<li>더 나은 개선방안이 있는 지 추후 고민하고 정리해보려고 한다</li>
</ul>
<h2 id="문제-요약">문제 요약</h2>
<ul>
<li>N×N 크기의 2차원 배열에 각 지점의 높이가 주어진다</li>
<li>비가 내려 특정 높이 이하의 지점이 모두 물에 잠긴다</li>
<li>물에 잠기지 않는 안전한 영역이란 물에 잠기지 않는 지점들이 상하좌우로 인접해 있는 지역이다</li>
<li>다양한 강수량(비의 양)에 따라 안전한 영역의 개수가 달라질 때, 안전한 영역의 최대 개수를 구해야 한다</li>
</ul>
<h2 id="접근-방법">접근 방법</h2>
<p>이 지역에서 가능한 모든 강수량에 대해 시뮬레이션을 해봐야 합니다.
각 시뮬레이션에서 안전한 영역의 개수를 계산하고, 그 중 최댓값을 찾습니다.
안전한 영역의 개수는 DFS나 BFS를 이용해 구할 수 있습니다.</p>
<h2 id="dfs와-bfs-개념-설명">DFS와 BFS 개념 설명</h2>
<h3 id="dfs깊이-우선-탐색">DFS(깊이 우선 탐색)</h3>
<ul>
<li>그래프나 트리에서 깊이를 우선적으로 탐색하는 알고리즘</li>
<li>한 경로를 끝까지 탐색한 후 다른 경로를 탐색</li>
<li>주로 재귀 함수나 스택을 이용하여 구현</li>
</ul>
<h3 id="bfs너비-우선-탐색">BFS(너비 우선 탐색)</h3>
<ul>
<li>그래프나 트리에서 너비를 우선적으로 탐색하는 알고리즘</li>
<li>시작점에서 가까운 노드부터 차례대로 탐색</li>
</ul>
<p>참고
<a href="https://bedecked-operation-4d1.notion.site/99-4-TIL-DFS-BFS-1caeb405261e80ca84a8ecaef620f1ab">https://bedecked-operation-4d1.notion.site/99-4-TIL-DFS-BFS-1caeb405261e80ca84a8ecaef620f1ab</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 3일차 TIL - 바탕화면 정리]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%94%ED%83%95%ED%99%94%EB%A9%B4-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%94%ED%83%95%ED%99%94%EB%A9%B4-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 02 Apr 2025 07:20:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://school.programmers.co.kr/learn/courses/30/lessons/161990">https://school.programmers.co.kr/learn/courses/30/lessons/161990</a>
바탕화면에 있는 모든 파일(&#39;#&#39;)을 포함하는 <strong>가장 작은</strong> 직사각형을 찾는 문제</p>
</blockquote>
<h2 id="문제분석">문제분석</h2>
<ul>
<li>바탕화면은 격자 형태이며, 각 칸은 &#39;.&#39;(빈 칸) 또는 &#39;#&#39;(파일)로 표시</li>
<li>모든 파일을 한 번에 선택하기 위한 최소 크기의 드래그 영역을 찾아야 한다</li>
<li>드래그 영역은 시작점 (lux, luy)와 끝점 (rdx, rdy)로 정의</li>
</ul>
<h2 id="문제-접근방법">문제 접근방법</h2>
<ul>
<li>모든 파일 위치를 확인하며 가장 작은 x, y 좌표와 가장 큰 x, y 좌표를 찾아야 함</li>
<li>가장 작은 좌표 → 드래그 시작점 S(lux, luy)</li>
<li>가장 큰 좌표+1 → 드래그 끝점 E(rdx, rdy)</li>
<li>⚠️ 끝점은 파일 위치보다 1 더 크게 설정해야 함 <strong>why? 파일을 완전히 포함하기 위해</strong><ul>
<li>드래그는 격자점을 기준인데, 파일은 격자 칸에 있음</li>
<li>파일이 있는 칸을 완전히 포함하려면 그 칸의 오른쪽 아래 격자점까지 드래그해야 함</li>
</ul>
</li>
</ul>
<h2 id="내-코드">내 코드</h2>
<pre><code class="language-javascript">function solution(wallpaper) {
    // 초기값 설정 (최소값은 가능한 최대로, 최대값은 가능한 최소로 설정)
    let lux = wallpaper.length;
    let luy = wallpaper[0].length;
    let rdx = 0;
    let rdy = 0;

    // 모든 파일 위치 확인
    for (let i = 0; i &lt; wallpaper.length; i++) {
        for (let j = 0; j &lt; wallpaper[i].length; j++) {
            if (wallpaper[i][j] === &#39;#&#39;) {
                // 파일을 발견하면 시작점과 끝점 좌표 업데이트
                lux = Math.min(lux, i);
                luy = Math.min(luy, j);
                rdx = Math.max(rdx, i);
                rdy = Math.max(rdy, j);
            }
        }
    }

    // 드래그 끝점은 파일 위치보다 1 더 크게 설정해야 함
    return [lux, luy, rdx + 1, rdy + 1];
}</code></pre>
<h3 id="내-코드-성능">내 코드 성능</h3>
<p>메모리: 33.6 MB, 시간: 0.54 ms</p>
<hr>
<h2 id="개선된-코드">개선된 코드</h2>
<p>참고: <a href="https://velog.io/@deun/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%9D%BC%EC%B0%A8-TIL-%EB%B0%94%ED%83%95%ED%99%94%EB%A9%B4-%EC%A0%95%EB%A6%AC">deun블로그</a></p>
<pre><code class="language-javascript">function solution(wallpaper) {
    let lux = wallpaper.length;
    let luy = wallpaper[0].length;
    let rdx = 0;
    let rdy = 0;

    for (let i = 0; i &lt; wallpaper.length; i++) {
        if (!wallpaper[i].includes(&#39;#&#39;)) continue; // ✅

        lux = Math.min(lux, i);
        rdx = Math.max(rdx, i + 1);
        luy = Math.min(luy, wallpaper[i].indexOf(&#39;#&#39;)); // ✅
        rdy = Math.max(rdy, wallpaper[i].lastIndexOf(&#39;#&#39;) + 1); // ✅
    }

    return [lux, luy, rdx, rdy];
}</code></pre>
<ul>
<li>중첩된 반복문 제거 후 내장 메서드(<code>indexOf</code>, <code>lastIndexOf</code>)를 활용<ul>
<li>하나의 반복문으로 모든 계산을 수행</li>
<li>각 행마다 최소/최대 인덱스를 바로 찾아 처리</li>
</ul>
</li>
<li>파일이 없는 행(<code>if (!wallpaper[i].includes(&#39;#&#39;)) continue;</code>)은 빠르게 건너 뜀</li>
<li>성능 개선<ul>
<li>메모리: 33.6 MB → 33.4 MB,</li>
<li>시간: 0.54 ms → 0.23 ms</li>
</ul>
</li>
</ul>
<hr>
<h2 id="indexof-lastindexof--리뷰하기"><code>indexOf()</code>, <code>lastIndexOf()</code>  리뷰하기</h2>
<h3 id="indexof-메서드"><code>indexOf()</code> 메서드</h3>
<p>배열이나 문자열에서 특정 요소나 문자의 <strong><code>첫 번째 등장 위치(인덱스)</code>를 찾아 반환</strong> 
찾는 요소가 없으면 <code>-1</code>을 반환</p>
<pre><code class="language-javascript">array.indexOf(searchElement, [fromIndex])
string.indexOf(searchValue, [fromIndex])</code></pre>
<p><code>searchElement</code>/<code>searchValue</code>: 찾을 요소 또는 문자열
<code>fromIndex</code> (선택적): 검색을 시작할 인덱스 (안 써주면 기본값 0)</p>
<p>사용 예) 특정 문자 이후의 내용 추출 - URL에서 쿼리 파라미터 추출</p>
<pre><code class="language-javascript">const url = &#39;https://example.com/search?query=javascript&amp;limit=10&#39;;
const queryStartIndex = url.indexOf(&#39;?&#39;);

if (queryStartIndex !== -1) {
  const queryString = url.substring(queryStartIndex + 1);
  console.log(&#39;쿼리 문자열:&#39;, queryString); // &#39;query=javascript&amp;limit=10&#39;
}</code></pre>
<h3 id="lastindexof-메서드"><code>lastIndexOf()</code> 메서드</h3>
<p>배열이나 문자열에서 특정 요소나 문자의 <strong><code>마지막 등장 위치(인덱스)</code>를 찾아 반환</strong>
찾는 요소가 없으면 <code>-1</code>을 반환</p>
<pre><code class="language-javascript">array.lastIndexOf(searchElement, [fromIndex])
string.lastIndexOf(searchValue, [fromIndex])</code></pre>
<p><code>searchElement</code>/<code>searchValue</code>: 찾을 요소 또는 문자열
<code>fromIndex</code> (선택적): <strong>역방향 검색을 시작할</strong> 인덱스 (기본값: array.length-1 또는 string.length-1)</p>
<p>사용 예 1) 파일 경로에서 파일명 추출하기</p>
<pre><code class="language-javascript">const filePath = &#39;/users/documents/projects/report.pdf&#39;;
const lastSlashIndex = filePath.lastIndexOf(&#39;/&#39;);
const fileName = filePath.substring(lastSlashIndex + 1);
console.log(&#39;파일명:&#39;, fileName); // &#39;report.pdf&#39;</code></pre>
<p>사용 예 2) 중복 요소 중 마지막 위치 찾기 </p>
<pre><code class="language-javascript">// 배열에서 중복된 요소의 마지막 위치 찾기
const numbers = [2, 5, 9, 2, 7, 5, 3, 5];
const lastFiveIndex = numbers.lastIndexOf(5); // 7 반환

console.log(&#39;마지막 5의 위치:&#39;, lastFiveIndex);

// 특정 위치 이전의 마지막 등장 위치 찾기
const previousFiveIndex = numbers.lastIndexOf(5, 6); // 5 반환
console.log(&#39;인덱스 6 이전의 마지막 5 위치:&#39;, previousFiveIndex);</code></pre>
<h3 id="실제-사용-범위">실제 사용 범위</h3>
<ol>
<li>문자열 처리 작업 단순화:</li>
</ol>
<ul>
<li>URL 파싱</li>
<li>파일 경로 처리</li>
<li>이메일 검증</li>
<li>텍스트 분석</li>
</ul>
<ol start="2">
<li>데이터 검증과 처리:</li>
</ol>
<ul>
<li>입력값 유효성 검사</li>
<li>데이터 추출 및 변환</li>
</ul>
<p>참고
<a href="https://bedecked-operation-4d1.notion.site/99-3-TIL-1c9eb405261e80f2b3b8e14bf17779e9">https://bedecked-operation-4d1.notion.site/99-3-TIL-1c9eb405261e80f2b3b8e14bf17779e9</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 2일차 TIL - 피보나치 비스무리한 수열(DP)]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%9D%BC%EC%B0%A8-TIL-%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98-%EB%B9%84%EC%8A%A4%EB%AC%B4%EB%A6%AC%ED%95%9C-%EC%88%98%EC%97%B4</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-2%EC%9D%BC%EC%B0%A8-TIL-%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98-%EB%B9%84%EC%8A%A4%EB%AC%B4%EB%A6%AC%ED%95%9C-%EC%88%98%EC%97%B4</guid>
            <pubDate>Tue, 01 Apr 2025 14:13:21 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제: <a href="https://www.acmicpc.net/problem/14495">https://www.acmicpc.net/problem/14495</a>
알고리즘 분류: 다이나믹 프로그래밍</p>
</blockquote>
<h2 id="초기-접근-방식">초기 접근 방식</h2>
<ol>
<li>점화식 이해: <code>f(n) = f(n-1) + f(n-3)</code>는 현재 값이 바로 전 값과 3단계 전 값의 합임</li>
<li>초기값 설정: <code>f(1) = f(2) = f(3) = 1</code>로 명확하게 설정</li>
<li>효율적인 메모리 사용: 수열을 계산할 때 이전 값들을 재사용하는 <strong>동적 프로그래밍</strong> 접근법이 필요</li>
</ol>
<h2 id="내-코드1차-시도">내 코드(1차 시도)</h2>
<pre><code class="language-js">let fs = require(&#39;fs&#39;);
let input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim();

let n = Number(input);

// 피보나치 비스무리한 수열 계산
function fibonacciLike(n) {
    // 기본 케이스: f(1) = f(2) = f(3) = 1
    let fib = [1, 1, 1]; // 배열초기화

    // n이 3 이하면 바로 결과 반환
    if (n &lt;= 3) {
        return fib[n-1];
    }

    // n이 4 이상이면 수열 계산
    for (let i = 3; i &lt; n; i++) {
        // f(n) = f(n-1) + f(n-3) 공식 적용
        fib[i] = fib[i-1] + fib[i-3];
    }

    return fib[n-1];
}

console.log(fibonacciLike(n));</code></pre>
<h3 id="추가-설명">추가 설명</h3>
<pre><code class="language-javascript">let fib = [1, 1, 1];

for (let i = 3; i &lt; n; i++) {
  fib[i] = fib[i-1] + fib[i-3];
}</code></pre>
<ul>
<li><code>fib[0]</code>은 f(1), <code>fib[1]</code>은 f(2), <code>fib[2]</code>는 f(3) </li>
</ul>
<p>그러므로,</p>
<ul>
<li>f(n)은 <code>fib[n-1]</code></li>
<li>f(n-1)은 <code>fib[n-2]</code></li>
<li>f(n-3)은 <code>fib[n-4]</code></li>
</ul>
<p>for루프에서 i는 배열의 인덱스이고, </p>
<ul>
<li><code>fib[i]</code>는 f(i+1)</li>
<li><code>fib[i-1]</code>은 f(i)</li>
<li><code>fib[i-3]</code>은 f(i-2)</li>
</ul>
<p>결과적으로, 이 코드는 <code>f(i+1) = f(i) + f(i-2)</code>를 계산하는데, 이는 수학적 표기법으로는 <code>f(n) = f(n-1) + f(n-3)</code>와 동일
➡️ 인덱스와 수열의 위치 사이에 1의 차이가 있어서(<code>fib[i]</code>가 <code>f(i+1)</code>을 나타냄)</p>
<h2 id="배열-인덱싱과-관련된-오류와-코드수정2차-시도">배열 인덱싱과 관련된 오류와 코드수정(2차 시도)</h2>
<pre><code class="language-javascript">let fs = require(&#39;fs&#39;);
let input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim();

let n = Number(input);

// 피보나치 비스무리한 수열 계산
function fibonacciLike(n) {
    // 기본 케이스: f(1) = f(2) = f(3) = 1
    let dp = new Array(n+1);
    dp[1] = dp[2] = dp[3] = 1;

    // n이 3 이하면 바로 결과 반환
    if (n &lt;= 3) {
        return dp[n];
    }

    // n이 4 이상이면 수열 계산
    for (let i = 4; i &lt;= n; i++) {
        dp[i] = dp[i-1] + dp[i-3];
    }

    return dp[n];
}

console.log(fibonacciLike(n));</code></pre>
<h3 id="추가-설명-1">추가 설명</h3>
<ol>
<li>배열 이름변경: fib → <strong>dp</strong> (동적 프로그래밍 접근임을 명시)</li>
<li><strong>배열을</strong> 0부터가 아닌 <strong>1부터 인덱싱</strong>하도록 변경 (<code>new Array(n+1)</code> 사용)
➡️ 코드의 가독성을 높이기 위해 <strong>배열의 인덱스를 수열의 위치와 일치</strong></li>
<li><code>dp[1], dp[2], dp[3]</code>을 직접 1로 초기화</li>
<li>반복문은 <code>i = 4</code>부터 시작하여 <code>i &lt;= n</code>까지 실행</li>
<li><code>dp[n]</code>을 반환하여 n번째 값을 리턴</li>
</ol>
<h3 id="1-based-indexing-방식">1-based indexing 방식</h3>
<p>배열 자체는 여전히 0부터 시작하지만, 우리가 0번 인덱스를 의도적으로 무시하고 1번부터 사용하는 것</p>
<p><code>new Array(n+1)</code>로 배열을 생성하면:</p>
<p>크기가 <code>n+1</code>인 배열이 만들어 짐
예를 들어 n=5라면 크기가 6인 배열이 생성되고, 이 배열은 0번 인덱스부터 n번 인덱스까지 가짐
<code>[undefined, undefined, undefined, undefined, undefined, undefined]</code></p>
<p>이제 0번 인덱스는 사용하지 않고, 1번부터 n번 인덱스를 사용:</p>
<p><em>dp[1]은 수열의 첫 번째 항
dp[2]는 수열의 두 번째 항
dp[3]은 수열의 세 번째 항
...
dp[n]은 수열의 n번째 항</em></p>
<p>이렇게 하면 코드에서 <code>dp[i]</code>가 정확히 수열의 i번째 항을 가리키게 되어, 수학적 표기법과 코드의 표기법이 일치하게 됨
따라서, 점화식 <code>f(n) = f(n-1) + f(n-3)</code>을 <strong>그대로 <code>dp[i] = dp[i-1] + dp[i-3]</code>으로 구현</strong>할 수 있어 코드가 더 직관적이고 이해하기 쉬워짐</p>
<h2 id="런타임에러와-구현범위와-bitint최종-코드">런타임에러와 구현범위와 BitInt(최종 코드)</h2>
<ul>
<li><p>구현범위가 <strong>n이 최대 116까지 갈 수 있다</strong>고 명시되어 있으므로, 피보나치 비스무리한 수열의 값이 JavaScript의 Number 타입으로 표현할 수 있는 범위를 초과할 수 있음 </p>
</li>
<li><p>JavaScript에서 안전하게 표현할 수 있는 정수의 최대값은 <code>Number.MAX_SAFE_INTEGER</code>로 약 9007조(9,007,199,254,740,991)</p>
</li>
<li><p>n이 116일 때의 값은 이 범위를 넘어갈 가능성이 높음</p>
</li>
<li><p>따라서, <strong>BigInt를 사용하여 코드를 수정 → 큰 수에 대한 처리가 필요</strong></p>
</li>
</ul>
<pre><code class="language-javascript">let fs = require(&#39;fs&#39;);
let input = fs.readFileSync(&#39;/dev/stdin&#39;).toString().trim();

let n = Number(input);

// 피보나치 비스무리한 수열 계산
function fibonacciLike(n) {
    // 기본 케이스: f(1) = f(2) = f(3) = 1
    let dp = new Array(n+1);
    dp[1] = dp[2] = dp[3] = 1n; // BigInt 리터럴 사용 ✅

    // n이 3 이하면 바로 결과 반환
    if (n &lt;= 3) {
        return dp[n];
    }

    // n이 4 이상이면 수열 계산
    for (let i = 4; i &lt;= n; i++) {
        dp[i] = dp[i-1] + dp[i-3];
    }

    return dp[n];
}

console.log(fibonacciLike(n).toString()); // BigInt를 문자열로 변환하여 출력</code></pre>
<h3 id="주요-변경-사항">주요 변경 사항</h3>
<ul>
<li>초기값을 1n으로 설정하여 BigInt 타입을 사용</li>
<li>결과 반환 시 <code>.toString()</code>을 사용하여 BigInt 값을 문자열로 변환 (BigInt는 console.log에서 &quot;n&quot;이 붙어서 출력됨)</li>
<li>이 수정으로 큰 수에 대해서도 정확한 계산이 가능</li>
</ul>
<blockquote>
<p>피보나치 수열은 매우 빠르게 증가하기 때문에, n이 큰 경우에는 이런 BigInt 처리가 필수적</p>
</blockquote>
<h2 id="맺으며">맺으며</h2>
<ul>
<li>처음엔 단순 구현을 위해 배열의 인덱스와 수열의 위치가 달랐고,(1차 시도)</li>
<li><strong>동적 프로그래밍</strong> 접근임을 명시 후 <strong>인덱스와 수열의 위치를 일치</strong>시켰다. (2차 시도)</li>
<li>런타임 에러 후 <strong>구현범위를 고려하여 BigInt를 사용</strong>해 최종 코드를 만들었다.</li>
<li>피보나치 수열은 매우 빠르게 증가하기 때문에 BigInt처리가 필수적이라는 것도 알게 되었다.</li>
<li>동적 프로그래밍과 BigInt에 대해 조금 더 공부해 봐야 겠다.</li>
</ul>
<p>참고
<a href="https://bedecked-operation-4d1.notion.site/99-2-TIL-DP-Dynamic-programming-1c8eb405261e805b9f60e5da9096caaa">https://bedecked-operation-4d1.notion.site/99-2-TIL-DP-Dynamic-programming-1c8eb405261e805b9f60e5da9096caaa</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 1일차 TIL - 가장 빈도가 높은 k개의 요소 찾기]]></title>
            <link>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%9D%BC%EC%B0%A8-TIL-%EB%B3%B4%EB%84%88%EC%8A%A4-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@clara-shin/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%9D%BC%EC%B0%A8-TIL-%EB%B3%B4%EB%84%88%EC%8A%A4-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Mon, 31 Mar 2025 15:13:54 GMT</pubDate>
            <description><![CDATA[<h2 id="가장-빈도가-높은-k개의-요소-찾기">가장 빈도가 높은 k개의 요소 찾기</h2>
<p>문제: <a href="https://leetcode.com/problems/top-k-frequent-elements/description/">https://leetcode.com/problems/top-k-frequent-elements/description/</a>
알고리즘 분류: 
<code>Array</code> <code>Hash Table</code> <code>Divide and Conquer</code> <code>Sorting</code> <code>Heap (Priority Queue)</code>
<code>Bucket Sort</code> <code>Counting</code> <code>Quickselect</code></p>
<h3 id="접근방법">접근방법</h3>
<ul>
<li>빈도수 계산<ul>
<li>Map을 사용 ➡️ 각 숫자가 배열에서 몇 번 등장하는지 계산</li>
</ul>
</li>
<li>버킷 정렬<ul>
<li>문제의 요구사항에 따라 O(n log n)보다 빠른 시간 복잡도가 필요</li>
<li>버킷 정렬 시간 복잡도 : O(n)</li>
</ul>
</li>
<li>결과 추출<ul>
<li>가장 높은 빈도수를 가진 버킷부터 시작 ➡️ k개의 요소를 결과 배열에 추가</li>
</ul>
</li>
</ul>
<h3 id="내-코드">내 코드</h3>
<pre><code class="language-javascript">function topKFrequent(nums, k) {
    // 각 숫자의 빈도수를 저장할 Map 생성
    const frequencyMap = new Map();

    // 모든 숫자의 빈도수 계산
    for (const num of nums) {
        frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1);
    }

    // 빈도수를 기준으로 내림차순 정렬된 배열 생성
    // 시간 복잡도는 O(n log n)이 아닌 O(n)을 달성하기 위해 버킷 정렬 사용
    const buckets = Array(nums.length + 1).fill().map(() =&gt; []);

    // 각 숫자를 빈도수에 해당하는 버킷에 넣기
    for (const [num, freq] of frequencyMap) {
        buckets[freq].push(num);
    }

    // 결과 배열
    const result = [];

    // 가장 높은 빈도수부터 시작하여 k개의 요소를 결과 배열에 추가
    for (let i = buckets.length - 1; i &gt;= 0 &amp;&amp; result.length &lt; k; i--) {
        if (buckets[i].length &gt; 0) {
            result.push(...buckets[i].slice(0, k - result.length));
        }
    }

    return result;
}
</code></pre>
]]></description>
        </item>
    </channel>
</rss>