<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>0woy_.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sat, 08 Feb 2025 16:49:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>0woy_.log</title>
            <url>https://velog.velcdn.com/images/0woy_/profile/bcf70c3d-b124-42fa-a8ac-072952839879/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 0woy_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/0woy_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[백준-자바] N_1783 병든 나이트]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N1783-%EB%B3%91%EB%93%A0-%EB%82%98%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N1783-%EB%B3%91%EB%93%A0-%EB%82%98%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Sat, 08 Feb 2025 16:49:05 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/ba5541d6-bacf-4330-9c1e-605c93316707/image.PNG" width=100% />

<Image src="https://velog.velcdn.com/images/0woy_/post/a00b4c64-2754-41ff-8d77-46151421e9c6/image.PNG" width=100% />

<ul>
<li>NxM 크기의 체스 보드</li>
<li>나이트의 시작 위치: (N,1)  <code>0이 아닌 1부터 시작한다고 가정</code></li>
<li>이동할 수 있는 칸의 최대 개수 구하기 
  이때, 최대 개수가 5이상인 경우, 모든 방법을 한 번 이상 사용해야함.</li>
</ul>
<hr>
<h3 id="생각하기">생각하기</h3>
<p>만일, 모든 방법을 다 쓴다고 가정할 때, 최소 배열의 크기를 생각해 보자.</p>
<Image src="https://velog.velcdn.com/images/0woy_/post/6e0a4089-0463-4b68-9083-44921ea883f7/image.png" width=80% />

<p>위 그림에서 보듯, <strong>최소 세로<code>(N)</code>≥3, 가로<code>(M)</code>≥7</strong> 인 경우 4가지 방법을 모두 사용할 수 있다.</p>
<h3 id="1-n-2">1. N =2</h3>
<p><img src="https://velog.velcdn.com/images/0woy_/post/fea02180-15eb-42fb-ad0e-47a3a34671b7/image.png" alt=""></p>
<p>세로가 2인 경우, ①, ④ 방법은 쓸 수 없다. <code>(왜 ??? 체스판을 벗어나니까.)</code></p>
<p>따라서 아무리 이동을 많이 할 수 있어도 문제 조건을 충족할 수 없기 때문에 이동 가능한 칸 수는 최대 4칸이다.</p>
<blockquote>
<p>시작 칸을 제외하면 가로 길이는 M-1이 된다.
②, ③ 방법을 쓰면, 2칸씩 이동하므로 이동 가능한 칸 수 = (M-1)/2
시작 위치도 포함해야 하기 때문에 +1을 더한다.</p>
<p><em>** ∴ 최대 이동 칸 = Math.min(4, (M-1)/2 + 1)**</em></p>
</blockquote>
<h3 id="2-m--7">2. M &lt; 7</h3>
<Img src="https://velog.velcdn.com/images/0woy_/post/172ddb5b-e696-4650-b9dc-ced976c138c4/image.png" width=80% />

<p>나이트가 이동할 수 있는 방법은 모두 우측으로 <code>+1칸</code> 혹은 <code>+2칸</code>씩 이동한다.
그래서 시작 위치를 제외하고 최소 <code>1+1+2+2 = 6칸</code>이 있어야 4가지 방법을 모두 사용할 수 있다.</p>
<blockquote>
<p>4칸 이하는 특정 방법만 사용해도 되므로, ①, ④ 방법만 취하면 된다.
<em>** ∴ 최대 이동 칸 = Math.min(4, M)**</em></p>
</blockquote>
<h3 id="3-n≥3-m≥7">3. N≥3, M≥7</h3>
<p>4가지 방법을 사용할 수 있는 체스판의 최소 크기를 충족한 경우에는 어떨까?
우측으로 +2 이동하는 방법은 1번 씩만 써서 2칸을 이동하고, 나머지를 우측으로 +1만 이동하면 된다.</p>
<blockquote>
<ol>
<li>②, ③ 방법을 사용 시 남은 가로 길이: M-4(2+2) </li>
<li>①, ④ 방법으로 나머지 <code>M-4</code> 길이 이동</li>
</ol>
</blockquote>
<p><em>** ∴ 최대 이동 칸 = 2+M-4 = M-2**</em> </p>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

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

        int count =0;
        if(N==1){
            count=1;
        }else if(N==2){      
            count = Math.min(4, (M-1)/2+1);
        }else if(M&lt;7){
            count = Math.min(4,M);
        }else{
            count = M-2;
        }
        System.out.println(count);
    }
}
</code></pre>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/8f6da657-304c-49c0-9e3b-b3c60c4885b3/image.PNG" alt=""></p>
<p>문제도 이해 못해서 빠르게 답을 봤다.
답을 보니 머리가 똑똑해진 기분 세상엔 영리한 사람들이 참 많다!
나도 그렇게 될 거지롱</p>
<p>참고
<a href="https://jaewoo2233.tistory.com/77">jaewoo, [백준 No.1783 병든 나이트 - JAVA]</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 토큰 기반 인증, JWT 2편]]></title>
            <link>https://velog.io/@0woy_/Spring-%ED%86%A0%ED%81%B0-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-JWT-2%ED%8E%B8</link>
            <guid>https://velog.io/@0woy_/Spring-%ED%86%A0%ED%81%B0-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-JWT-2%ED%8E%B8</guid>
            <pubDate>Sat, 01 Feb 2025 08:46:14 GMT</pubDate>
            <description><![CDATA[<h2 id="1-jwt-동작-방식">1. JWT 동작 방식</h2>
<p>인증 과정에서 사용자가 성공적으로 <em>자격 증명을 완료하면 JWT가 반환</em> 됨
<strong>토큰 자체로 인증 수단</strong>이므로, 보안에 주의를 기울여야 함
<em>∴ 필요한 시간 보다 더 오래 보관하면 안 됨</em></p>
<p>사용자가 특정 경로나 리소스에 접근하려고 할 때, JWT를 같이 보내줘야함.
일반적으로 Authorization 헤더에 Bearer 스키마로 전달
<code>Authorization: Bearer &lt;token&gt;</code></p>
<blockquote>
<p>HTTP 헤더를 통해 JWT를 전달하는 경우, 토큰에 너무 많은 정보를 담으면 안 됨
일부 서버는 헤더의 크기가 <code>8KB</code>를 초과하는 걸 참지 않아..</p>
</blockquote>
<h3 id="1-authorization-헤더">1) Authorization 헤더</h3>
<p>HTTP 프로토콜에서 클라이언트가 서버에 인가된 상태임을 전달하는 수단으로 사용됨
서버에서 사용자의 신원을 증명하기 위해 필수적 (HTTP/1.1 표준에서 처음 정의)
<code>Authorization: &lt;스키마&gt; &lt;자격 증명&gt;</code></p>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>스키마</td>
<td>인증 방식 (ex. Basic, Bearer)</td>
</tr>
<tr>
<td>자격 증명</td>
<td>클라이언트의 인증 정보</td>
</tr>
</tbody></table>
<h3 id="2-bearer-토큰">2) Bearer 토큰</h3>
<p>클라이언트가 특정 리소스에 접근할 수 있는 권한을 증명하기 위해 서버에 전달하는 토큰
HTTPS를 통해 전달되어야 하며, 이를 통해 토큰이 네트워크에서 탈취됨을 방지</p>
<h4 id="oauth-20의-등장">OAuth 2.0의 등장</h4>
<p><code>클라이언트</code>가 <code>리소스 소유자</code>를 대신해 
<strong>서버에 접근할 수 있는 인증과 권한 위임 방식</strong>을 표준화한 프레임워크
민감한 사용자 정보를 직접 전송하지 않고, <strong>액세스 토큰</strong>을 사용해 인증 수행</p>
<p>이 과정에서 Authorization 헤더는 토큰 기반 인증의 전달 수단으로 채택,
Bearer 스키마가 OAuth 2.0의 핵심 인증 방식으로 자리잡음</p>
<blockquote>
<p><strong>❓ 클라이언트랑 리소스 소유자 같은 말 아닌가여??</strong></p>
</blockquote>
<ul>
<li><em>리소스 소유자</em></li>
<li><em>홍길동*</em>이라는 사용자가 있습니다. 홍길동은 자신의 개인 정보를 포함한 데이터(예: 이메일, 프로필 사진 등)를 소셜 미디어 서비스(예: 구글, 페이스북)에 가지고 있습니다. <em><strong>홍길동은 이 데이터에 대한 권한을 가지고 있는</strong></em> 리소스 소유자입니다.<blockquote>
<ul>
<li><em>클라이언트</em>
홍길동이 가입하고자 하는 <strong>앱 A</strong>가 있습니다. 이 앱은 사용자가 구글 계정을 통해 로그인을 하여 서비스를 이용할 수 있도록 제공합니다. 이때 <em><strong>앱 A는 리소스 소유자인 홍길동 대신 구글에 접근하여 홍길동의 인증 정보를 받아옵니다</strong></em>. 따라서 앱 A는 클라이언트 역할을 합니다.</li>
</ul>
</blockquote>
</li>
</ul>
<hr>
<h2 id="2-refresh-token">2. Refresh Token</h2>
<p>토큰을 주고 받는 환경이 보안에 취약해서, 토큰이 노출된다면?
토큰은 그 자체로 인증 수단이므로 서버는 누가 요청했는지 확인할 수가 없음.</p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/7b7733f0-376a-485a-b0b8-32401bedc107/image.png" alt=""></p>
<p><em><strong>토큰의 유효기간이 24시간이면, 도둑은 하루종일 토큰을 가지고 무엇이든 할 수 있음</strong></em>
<img src="https://velog.velcdn.com/images/0woy_/post/0324eee0-22a3-4469-81ca-8743131c4408/image.png" alt=""></p>
<blockquote>
<p>❓ 그러면 토큰의 유효기간이 짧으면 되잖아요
<strong>사용자가 귀찮아 하잖아!!!!!!!!!!!!</strong></p>
</blockquote>
<p>이런 문제를 해결하기 위해 리프레시 토큰 등장</p>
<h3 id="refresh-토큰">Refresh 토큰?</h3>
<p>액세스 토큰이 만료되었을 때** 새로운 액세스 토큰** <code>(ex. JWT)</code>을** 발급**하기 위해 사용</p>
<table>
<thead>
<tr>
<th>토큰</th>
<th>설명</th>
<th>유효기간 설정</th>
</tr>
</thead>
<tbody><tr>
<td>Access Token</td>
<td>사용자 인증</td>
<td>짧게</td>
</tr>
<tr>
<td>Refresh Token</td>
<td>액세스 토큰 재발급</td>
<td>액세스 토큰보다 길게</td>
</tr>
</tbody></table>
<p>토큰 도둑이 액세스 토큰 탈취해도 몇 분 뒤에는 쓸모 없어져서 안전하다.</p>
<h4 id="리프레시-토큰-사용법">리프레시 토큰 사용법</h4>
<p><img src="https://velog.velcdn.com/images/0woy_/post/31360ced-82b0-40b2-886c-2af8d6cb3825/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/29a0db68-5d46-4dd5-951f-67d1b497da9c/image.png" alt=""></p>
<hr>
<p>이전 글 -  <a href="https://velog.io/@0woy_/Spring-%ED%86%A0%ED%81%B0-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-JWT">[Spring] 토큰 기반 인증, JWT</a></p>
<p>참고 자료
<a href="https://blog.wonkooklee.com/docs/network-and-security/why-authorization-bearer/#bearer-%ED%86%A0%ED%81%B0%EC%9D%B4%EB%9E%80">왜 Authorization &quot;Bearer&quot;인가요?</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 토큰 기반 인증, JWT]]></title>
            <link>https://velog.io/@0woy_/Spring-%ED%86%A0%ED%81%B0-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-JWT</link>
            <guid>https://velog.io/@0woy_/Spring-%ED%86%A0%ED%81%B0-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-JWT</guid>
            <pubDate>Sun, 26 Jan 2025 11:10:57 GMT</pubDate>
            <description><![CDATA[<h3 id="0-사용자-인증">0. 사용자 인증</h3>
<p>사용자가 서버에 접근할 떄 이 사용자가 괜춘한(인증된) 사용자인지 확인하는 방법은
대표적으로 <strong><code>서버 기반 인증</code></strong> 과 <strong><code>토큰 기반 인증</code></strong>이 있다.</p>
<p>스프링 시큐리티에서는 기본적으로 <strong>세션 기반 인증</strong> 제공한다.
-&gt; 사용자마다 사용자의 정보를 담은 세션 생성 &amp; 저장하여 인증함</p>
<hr>
<h2 id="1-토큰-기반-인증">1. 토큰 기반 인증</h2>
<p>토큰 기반 인증은 말 그대로 <strong><code>토큰</code></strong> 사용</p>
<blockquote>
<p>❓ <strong>토큰</strong>
서버에서 클라이언트를 구분하기 위한 <strong><em>유일한 값</em></strong></p>
</blockquote>
<ul>
<li>서버가 토큰을 생성해 클라이언트에게 제공</li>
<li>클라이언트는 이 토큰을 갖고 있다가 <em>여러 요청을 토큰이랑 같이 넘김</em></li>
<li>서버는 토큰만 보고 유효한 사용자인지 검증</li>
</ul>
<h3 id="1-토큰을-전달하고-인증받는-과정">1) 토큰을 전달하고 인증받는 과정</h3>
<p><img src="https://velog.velcdn.com/images/0woy_/post/5128b8db-cd8b-47d2-a078-62aed97a1dc8/image.png" alt=""></p>
<p><a href="">출처: 나 자신</a></p>
<hr>
<h3 id="2-토큰-기반-인증의-특징">2) 토큰 기반 인증의 특징</h3>
<h4 id="1-무상태성">1. 무상태성</h4>
<ul>
<li><p>사용자 인증 정보가 담겨있는 토큰이 클라이언트에 있으므로 서버에 저장할 필요 없음
<code>서버가 데이터를 유지하려면 그만큼 자원을 소비해야 함.</code></p>
</li>
<li><p>상태 관리를 할 필요가 없어서 완전한 <em><strong>무상태 (stateless)</strong></em> 로 효율적인 검증 가능</p>
<blockquote>
<p>❓ <strong>상태 관리</strong>
사용자의 인증 상태를 유지하면서 요청을 처리하는 것</p>
</blockquote>
</li>
</ul>
<h4 id="2-확장성">2. 확장성</h4>
<p><em><strong>무상태성은 확장성에 영향을 줌</strong></em>
*<em>오ㅐ❓ *</em> &gt; 서버를 확장할 때 상태 관리를 신경 안 쓰니까 서버 확장도 쉽게 함</p>
<blockquote>
<p>IF. <strong><code>주문 서버</code></strong>, <strong><code>결제 서버</code></strong> IN 물건 파는 서비스 </p>
</blockquote>
<table>
<thead>
<tr>
<th align="center">인증법</th>
<th>접근</th>
</tr>
</thead>
<tbody><tr>
<td align="center">세션</td>
<td>각각 API에서 인증 처리해야 함</td>
</tr>
<tr>
<td align="center">토큰</td>
<td>하나의 토큰으로 결제 서버와 주문 서버에 요청을 보냄</td>
</tr>
</tbody></table>
<h4 id="3-무결성">3. 무결성</h4>
<p>토큰 방식은 <strong>HMAC</strong> <code>(Hash-based Message AuthentiCation)</code> 기법이라고도 함
토큰을 발급한 이후, 토큰 정보 변경하는 행위는 할 수 없음
<Img src="https://velog.velcdn.com/images/0woy_/post/9f452de1-9903-4a84-8777-ef82e138c11d/image.png" width=80% /></p>
<p>즉, 토큰의 무결성이 보장..
하나라도 바꾸면 서버는 토큰이 유효하지 않다고 판단함</p>
<hr>
<h2 id="2-jwt란">2. JWT란?</h2>
<ul>
<li>JWT(JSON Web Token)은 정보를 JSON 객체로 안전하게 전송하는 방법을 정의하는 개방형 표준</li>
<li>크기가 작고, 독립적인 (self-contained) 형태를 가지며, 
디지털 서명을 통해 검증할 수 있어 신뢰 가능</li>
<li><code>비밀키 (HMAC algorithm)</code> 방식 또는 <code>공개/개인키 (RSA, ECDSA)</code> 방식으로 서명</li>
</ul>
<blockquote>
<p>❓ <strong>JSON (JavaScript Object Notation)</strong>
데이터를 키-값 <code>(key-value)</code> 쌍으로 구성하는 <strong>경량 데이터</strong> 형식</p>
</blockquote>
<ul>
<li>사람이 읽기 쉽고, 기계가 빠르게 파싱 가능</li>
<li>웹에서 데이터를 주고 받을 때 많이 사용됨<pre><code class="language-js">&gt;
{ 
&quot;name&quot;: &quot;Alice&quot;,
&quot;age&quot;: 25,
&quot;isStudent&quot;: false,
&quot;skills&quot;: [&quot;Java&quot;, &quot;Spring Boot&quot;, &quot;Vue.js&quot;],
&quot;address&quot;: {
  &quot;city&quot;: &quot;Seoul&quot;,
  &quot;country&quot;: &quot;Korea&quot;
}
}</code></pre>
</li>
</ul>
<p>JWT를 암호화 하면 <em><strong>기밀성을 포함</strong></em> 할 수 있음 = JWE (Json Web Encryption)</p>
<table>
<thead>
<tr>
<th></th>
<th>JWT (서명된 토큰)</th>
<th>JWE(암호화된 토큰)</th>
</tr>
</thead>
<tbody><tr>
<td>기능</td>
<td>무결성 검증 (변조 방지)</td>
<td>기밀성 제공 (내용 숨김)</td>
</tr>
<tr>
<td>Payload 노출</td>
<td>누구나 읽을 수 있음</td>
<td>암호화 되어 읽을 수 없음</td>
</tr>
<tr>
<td>보안성</td>
<td>변조 방지</td>
<td>변조 방지 + 데이터 보호</td>
</tr>
<tr>
<td>예시</td>
<td>OAuth2, 인증 토큰</td>
<td>민감한 데이터 전송</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-jwt-구조">3. JWT 구조</h2>
<p><strong><code>.</code></strong> 을 기준으로 <strong>Header</strong>(헤더), <strong>Payload</strong>(내용), <strong>Signature</strong>(서명)으로 이루어짐 
<code>ex) xxxx.yyyy.zzzz</code></p>
<h3 id="①-header">① Header</h3>
<p>헤더는 일반적으로 &#39;<em><strong>토큰 유형</strong></em> &amp; 사용되는 <em><strong>서명 알고리즘</strong></em> &#39; 두 부분으로 구성됨.</p>
<pre><code class="language-js">{ // 예시
  &quot;alg&quot;: &quot;HS256&quot;,
  &quot;typ&quot;: &quot;JWT&quot;
}
</code></pre>
<p>Header는 Base64Url로 인코딩 되어 JWT의 첫 번째 부분을 구성함</p>
<blockquote>
<p>❓ <strong>Base64Url 인코딩</strong></p>
</blockquote>
<p><strong>Base64 인코딩</strong> 의 변형된 형태</p>
<blockquote>
</blockquote>
<p>기존 Base64 인코딩은 <strong><em>URL에 사용 시 문제가 될 수 있는 문자를 포함</em></strong> 할 수 있어서 이른 URL에 적합하게 수정한 것</p>
<blockquote>
</blockquote>
<ul>
<li>바이너리 데이터를 아스키 문자열로 변환하는 방법</li>
<li>주로 텍스트나 이미지 등의 데이터를 웹에서 안전하게 전송하기 위해 사용함</li>
<li>일반적으로 6비트씩 64개 문자 사용</li>
</ul>
<h3 id="②-payload">② Payload</h3>
<p>토큰과 관련된 정보(Claim)를 담음</p>
<blockquote>
<p>❓ <strong>Claim</strong>
엔티티와 추가적인 데이터에 대한 설명
 <strong>Registerd</strong>(등록된), <strong>public</strong> (공개), <strong>private</strong> (비공개) claim으로 나뉨</p>
</blockquote>
<h4 id="1-registered-claim">1. Registered Claim</h4>
<p>토큰에 대한 정보를 담는 데 사용
JWT는 간결하게 작성 돼야 하므로 _<strong>클레임 이름은 3글자</strong>_로 제한</p>
<table>
<thead>
<tr>
<th>이름</th>
<th>설명</th>
<th>형식</th>
</tr>
</thead>
<tbody><tr>
<td>iss (Issuer)</td>
<td>토큰 발급자</td>
<td></td>
</tr>
<tr>
<td>sub (Subject)</td>
<td>토큰 제목</td>
<td></td>
</tr>
<tr>
<td>aud (Audience)</td>
<td>토큰 대상자</td>
<td></td>
</tr>
<tr>
<td>exp (Expriration)</td>
<td>토큰 만료시간, 현재 시간 이후 설정</td>
<td>Numeric Data</td>
</tr>
<tr>
<td>nbf (Not before)</td>
<td>토큰의 활성 날짜, 해당 날짜 전까지 토큰 처리 X</td>
<td>Numeric Data</td>
</tr>
<tr>
<td>iat (Issued At)</td>
<td>토큰이 발급된 시간</td>
<td>Numeric Data</td>
</tr>
<tr>
<td>jti (Jwt Id)</td>
<td>JWT의 고유 식별자</td>
<td>대소문자 구분</td>
</tr>
</tbody></table>
<h4 id="2-public-claim">2. Public Claim</h4>
<p>공개되어도 상관 없는 클레임
다른 시스템과의 상호운용성을 위해 사용될 수 있음
다른 시스템과의 충돌을 피하기 위해 <strong>유니크한 이름</strong>을 가져야 하며, 보통 <em><strong>URI로 작성</strong></em></p>
<h4 id="3-private-claim">3. Private Claim</h4>
<p>발급자와 수신자 간에만 의미 있는 클레임
<em><strong>다른 시스템과 공유되지 않으며</strong></em>, 해당 애플리케이션이나 서비스에서만 사용</p>
<blockquote>
<p><em><strong>JWT는 Header와 Payload를 Base64Url로 인코딩 하여 토큰 생성</strong></em></p>
</blockquote>
<p>디코딩이 정말 쉽기때문에, 민감한 정보는 넣지 말아야 함</p>
<h3 id="③-signature">③ Signature</h3>
<p> 해당 토큰이 조작되었거나 변경되지 않았음을 확인하는 용도
 Header 인코딩 값, Payload 인코딩 값 그리고 비밀키를 사용해 <em><strong>해시 값 생성</strong></em>
 <Img src="https://velog.velcdn.com/images/0woy_/post/086e3ecf-9726-41d8-9d03-7fadac15b867/image.png" width=80% /></p>
<hr>
<p>글이 길어지면 읽기 싫은 관계로 <a href="https://velog.io/@0woy_/Spring-%ED%86%A0%ED%81%B0-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-JWT-2%ED%8E%B8">[Spring] 토큰 기반 인증, JWT 2편</a> 에서 계속...</p>
<p>참고
<a href="https://jwt.io/introduction">Introduction to JSON Web Tokens</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] Spring Security]]></title>
            <link>https://velog.io/@0woy_/Spring-Spring-Security</link>
            <guid>https://velog.io/@0woy_/Spring-Spring-Security</guid>
            <pubDate>Sat, 25 Jan 2025 14:42:48 GMT</pubDate>
            <description><![CDATA[<p>❓ <strong>인증과 인가</strong></p>
<ol>
<li><strong>인증 (Authentication)</strong>: 사용자의 <strong><em>신원을 입증</em></strong> 하는 과정</li>
<li><strong>인가 (Authorization)</strong>: 사이트의 특정 부분에 접근할 수 있는지 <strong>권한</strong>을 확인하는 과정</li>
</ol>
<hr>
<h2 id="1-스프링-시큐리티">1. 스프링 시큐리티</h2>
<p>스프링 기반 애플리케이션의 보안을 담당하는 스프링 하위 프레임워크</p>
<ul>
<li>보안 관련 옵션 제공</li>
<li>CSRF 공격, 세션 고정 공격 방어 및 요청 헤더 보안 처리</li>
<li><strong>필터 기반 동작</strong></li>
</ul>
<blockquote>
<p>❓ <strong>CSRF 공격 &amp; 세션 고정 공격</strong></p>
</blockquote>
<ul>
<li><strong>CSRF 공격</strong>: 사용자의 권한을 가지고 특정 동작을 수행하도록 유도하는 공격</li>
<li><strong>세션 고정 공격</strong>: 사용자의 인증 정보를 탈취하거나 변조하는 공격</li>
</ul>
<h3 id="1-필터-구조">1) 필터 구조</h3>
<h4 id="1-security-context-persistence-filter">1. Security Context Persistence Filter</h4>
<p>Security Context Repository에서 <code>Security Context</code>를 가져오거나 저장하는 역할</p>
<h4 id="2-logout-filter">2. Logout Filter</h4>
<p>설정된 로그아웃 URL로 오는 요청을 확인해 해당 사용자 로그아웃 처리</p>
<h4 id="3-username-password-authentication-filter">3. Username Password Authentication Filter</h4>
<p><strong>인증관리자</strong>, 폼 기반 로그인 시 사용되는 필터로 아이디 &amp; 패스워드 데이터를 파싱해 인증 요청을 위임한다.</p>
<table>
<thead>
<tr>
<th align="center"></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td align="center">인증 성공</td>
<td>Authentication Success Handler 실행</td>
</tr>
<tr>
<td align="center">인증 실패</td>
<td>Authentication Failure Handler 실행</td>
</tr>
</tbody></table>
<h4 id="4-default-login-page-generating-filter">4. Default Login Page Generating Filter</h4>
<p>사용자가 로그인 페이지를 따로 지정하지 않았을 때 기본으로 설정하는 로그인 페이지 관련 필터</p>
<h4 id="5-basic-authentication--filter">5. Basic Authentication  Filter</h4>
<p>요청 헤더에 있는 아이디와 패스워드를 파싱해서 인증 요청 위임</p>
<table>
<thead>
<tr>
<th align="center"></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td align="center">인증 성공</td>
<td>Authentication Success Handler 실행</td>
</tr>
<tr>
<td align="center">인증 실패</td>
<td>Authentication Failure Handler 실행</td>
</tr>
</tbody></table>
<h4 id="6-request-cache-aware-filter">6. Request Cache Aware Filter</h4>
<p>로그인 성공 후, 관련 캐시 요청이 있는지 확인 및 캐시 요청 처리
<code>ex) 로그인 전 방문 페이지 기억 -&gt; 로그인 후 해당 페이지로 이동</code></p>
<h4 id="7-security-context-holder-aware-request-filter">7. Security Context Holder Aware Request Filter</h4>
<p>HttpServletRequest 정보를 감싼다.
필터 체인 상의 다음 필터들에게 부가 정보 제공</p>
<h4 id="8-anonymous-authentication-filter">8. Anonymous Authentication Filter</h4>
<p>필터가 호출되는 시점까지 인증되지 않은 경우, 
익명 사용자 전용 객체인 <code>Anonymous Authentication</code>을 만들어 <code>Security Context</code>에 삽입</p>
<h4 id="9-session-management-filter">9. Session Management Filter</h4>
<p>인증된 사용자와 관련된 <em><strong>세션 관련 작업</strong></em> 진행
세션 변조 방지 전략 설정, 유효하지 않은 세션에 대한 처리 및 세션 생성 전략 세움</p>
<h4 id="10-exception-translation-filter">10. Exception Translation Filter</h4>
<p>요청을 처리하는 중에 발생할 수 있는 예외 위임 &amp; 전달</p>
<h4 id="11-filter-security-interceptor">11. Filter Security Interceptor</h4>
<p><em><strong>접근 결정 관리자</strong></em>
<code>Access Decision Manager</code>로 권한 부여 처리를 위임함으로써 접근 제어 결정 쉽게 함</p>
<blockquote>
<p>위 과정에서 이미 사용자가 인증되어 있으므로 유효한 사용자인지 확인 가능
즉, <strong>인가 관련 설정</strong></p>
</blockquote>
<hr>
<h3 id="2-인증-처리-과정">2) 인증 처리 과정</h3>
<p>아이디 &amp; 패스워드 기반 폼 로그인 시도 시, 스프링 시큐리티에서 어떤 절차로 인증하는가?</p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/a5b149ff-7bba-456a-8de5-dfc14705131a/image.png" alt=""></p>
<ol>
<li><p>사용자가 폼에 아이디와 패스워드 입력 시 <code>HttpServletRequest</code>에 아이디와 비밀번호 정보가 전달됨</p>
<blockquote>
<p>❓ <strong>Http Servlet Request</strong></p>
</blockquote>
<p><code>Java Servlet API</code>에서 제공하는 인터페이스
클라이언트로부터 들어온 HTTP 요청의 모든 정보를 캡슐화한 객체</p>
<blockquote>
</blockquote>
<p>이 객체를 통해 클라이언트가 보낸 데이터(파라미터, 헤더, 쿠키 등)를 읽거나, 요청과 관련된 정보를 확인할 수 있음</p>
</li>
<li><p>Authentication Filter는 넘어온 정보 가지고 실제 구현체인 <code>UsernamePasswordAuthenticationToken</code> (이하 token) 만듦.</p>
</li>
<li><p>token을 <code>Authentication Manager</code>로 넘긴 후, 4. <code>Authentication Provider</code>에 보냄</p>
<blockquote>
<p>❓  <strong>Manager ? Provider ?</strong></p>
<p>Authentication Manager는 인터페이스, 기본 구현체가 Provider Manager.
Provider Manager 내부에 있는 Authentication Provider(s)를 사용하는 거임</p>
</blockquote>
</li>
</ol>
<ol start="5">
<li>사용자 아이디를 UserDetailService에 보낸 후, 6. 아이디로 찾은 사용자 정보를 UserDetails 객체로 만듦</li>
</ol>
<ol start="7">
<li>입력 정보와 UserDetails의 정보를 비교해 실제 인증 처리</li>
</ol>
<blockquote>
<p>❓ <strong>인증 처리</strong> </p>
<table>
<thead>
<tr>
<th align="center"></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td align="center">인증 성공</td>
<td>Authentication 객체 반환</td>
</tr>
<tr>
<td align="center">인증 실패</td>
<td>AuthenticationException 발생</td>
</tr>
</tbody></table>
</blockquote>
<ol start="8">
<li><ol start="9">
<li><ol start="10">
<li>인증이 성공한 경우 SecurityContextHolder에 Authentication 저장</li>
</ol>
</li>
</ol>
</li>
</ol>
<hr>
<h3 id="3-security-context-holder">3) Security Context Holder</h3>
<h4 id="--security-context-holder">- Security Context Holder</h4>
<ul>
<li><p>Spring Security가 현재 인증된 사용자의 세부 사항을 저장하는 공간
Spring Security는 Holder에 뭐가 어떻게 저장되는지 신경 안 씀.</p>
</li>
<li><blockquote>
<p>걍 여기 있으면 인증된 친구들인갑다 생각함</p>
</blockquote>
</li>
<li><p>기본값으로 <code>ThreadLocal</code>을 사용함</p>
<blockquote>
<p>각 스레드마다 독립적인 변수를 저장할 수 있게 해주는 클래스
즉, 여러 스레드가 동시 실행될 때 각 스레드가 자신만의 값을 가짐. 서로 터치 X</p>
</blockquote>
</li>
<li><p>Security Context Holder는 <code>Security Context</code>를 보관 &amp; 제공하는 공간</p>
</li>
</ul>
<h4 id="--security-context">- Security Context</h4>
<ul>
<li>현재 인증된 사용자의 보안 정보를 담고 있는 객체
주로 Authentication 객체를 포함하며 이 객체는 사용자가 로그인 후 인증된 상태에서 어떤 역할(권한)을 가지고 있는지 등의 정보를 제공함</li>
</ul>
<p><a href="https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-securitycontextholder">출처: Servlet Authentication Architecture</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[LeetCode-자바] N_494 Target Sum]]></title>
            <link>https://velog.io/@0woy_/LeetCode-%EC%9E%90%EB%B0%94-N494-Target-Sum</link>
            <guid>https://velog.io/@0woy_/LeetCode-%EC%9E%90%EB%B0%94-N494-Target-Sum</guid>
            <pubDate>Tue, 14 Jan 2025 12:36:56 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Img src="https://velog.velcdn.com/images/0woy_/post/0e0535ef-38c9-41c7-a9a3-070c8923b2db/image.PNG" width=100% />

<ul>
<li>숫자가 저장된 배열 (<code>nums</code>)와 목표값 (<code>target</code>)이 주어진다.</li>
<li>배열 내의 모든 숫자를 더하거나 빼서 <code>target</code>을 만들 수 있는 경우의 수 반환 </li>
</ul>
<h3 id="생각하기">생각하기</h3>
<p>처음 문제를 읽고 <em>&quot;내가 이거 풀 수 있나?&quot;</em> 라는 생각이 먼저 듦.
그래서 그냥 배열 끝까지 탐색하고 target 값이 0이 되면 경우의 수 count 올리는 방식으로 접근했다.</p>
<hr>
<h2 id="🍳-전체-소스-코드-1">🍳 전체 소스 코드 1</h2>
<pre><code class="language-java">class Solution {
        static int count;
    public int findTargetSumWays(int[] nums, int target) {
        count=0;
        dfs(nums, target, 0, 0);
        return count;
    }
      public static void dfs(int [] nums, int target, int idx, int total){
        if(idx == nums.length){
            if(target == total){
                count++;
            }
            return;
        }

        dfs(nums, target, idx+1, total+nums[idx]);
        dfs(nums, target, idx+1, total-nums[idx]);
    }
}</code></pre>
<ul>
<li><code>count</code>: 경우의 수 </li>
<li><code>dfs()</code>: 현재 값을 누적값에 더하거나 빼기</li>
</ul>
<p><strong>이렇게 배열의 끝까지 도달했을 때 target과 누적값(<code>total</code>)이 같으면 count 1증가</strong></p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/df009081-b0e0-4c43-8bba-d22e21678f4c/image.PNG" alt=""></p>
<blockquote>
<p>풀리긴 풀리는데 시간복잡도가 외딴섬 로맨틱 -&gt; 넘 오래 걸려유</p>
</blockquote>
<p>그렇다면.. 중복탐색을 안하게끔 만들어 주면 되겠다~</p>
<hr>
<h2 id="🍳-전체-소스-코드-2">🍳 전체 소스 코드 2</h2>
<pre><code class="language-java">class Solution {
    static Map&lt;String,Integer&gt; memo;
    public int findTargetSumWays(int[] nums, int target) {
         memo = new HashMap&lt;&gt;();
        int res =dfs(nums, target, 0,0);
        return res;
    }
      public static int dfs(int [] nums, int target, int idx,int total){
        if(idx == nums.length){
            return target == total?1:0;
        }
        if(memo.containsKey(idx+&quot;,&quot;+total)){
            return memo.get(idx+&quot;,&quot;+total);
        }

        int res = dfs(nums, target, idx+1,total+nums[idx])
                + dfs(nums, target, idx+1,total-nums[idx]);

        memo.put(idx+&quot;,&quot;+total, res);
        return res;
    }
}</code></pre>
<ul>
<li><code>memo</code>:
<code>key</code>: 현재idx, 누적값 <code>value</code>: target 도달 여부</li>
</ul>
<p>이번에는 memo라는 Map을 사용해서 현재 인덱스의 누적값이 memo에 존재하는 경우, 
<strong><em>탐색을 진행하지 않고 저장된 value를 반환</em></strong> 하는 코드를 추가했다.</p>
<blockquote>
<p><strong>이게 되나?</strong>
어차피 우리는 순차적으로 배열을 탐색하기 때문에.. 뒤는 안 봐도 비디오임
못 믿을 수도 있으니 해볼게유</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/0woy_/post/cdbbe7c0-3011-4b48-b309-06295924c05c/image.png" alt=""></p>
<p>이 <code>2)</code>에서 인덱스가 3이고 누적값이 1인 경우는 이미 위에서 확인했잖아요?
그래서 <code>4)</code>에서 해당 값을 고대로 사용해가지고 탐색하지 않는 겁니다..</p>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/f1edc3b0-1d37-4c79-ad36-4ae399878768/image.PNG" alt=""></p>
<p>어려워도.. 냅다 박치기 하면 풀린다..
리팩토링은 일단 풀고 나서 생각하자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Springboot #10] resolved[org.springframework.web.multipart.maxuploadsizeexceededexception: maximum upload size exceeded]]]></title>
            <link>https://velog.io/@0woy_/Springboot-10-resolvedorg.springframework.web.multipart.maxuploadsizeexceededexception-maximum-upload-size-exceeded</link>
            <guid>https://velog.io/@0woy_/Springboot-10-resolvedorg.springframework.web.multipart.maxuploadsizeexceededexception-maximum-upload-size-exceeded</guid>
            <pubDate>Mon, 02 Dec 2024 07:36:56 GMT</pubDate>
            <description><![CDATA[<h2 id="💣-문제">💣 문제</h2>
<p>이미지를 받아서 저장하는 로직을 구현하는 중 <em><strong><code>Maximum upload size exceeded</code></strong></em>  에러가 발생했다.</p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/5546a736-19ca-439a-92f8-e12e4b7efa9a/image.PNG" alt=""></p>
<p>에러메시지 그대로 사진이 넘 커서 저장하기 싫다는 말이다.
나는 따로 사이즈를 지정을 안 해줘서 최대 크기인 1MB만 받을 수 있는데, 나는 그 용량을 초과했다.</p>
<hr>
<h2 id="✨-해결">✨ 해결</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/ec2fa9b4-2460-42c1-a6c9-4ac1bdc23ce4/image.PNG" alt=""></p>
<blockquote>
<p>위처럼 최대 사이즈를 수동으로 저장해조야한다.</p>
</blockquote>
<p>참고: <a href="https://eungae-d.tistory.com/175">[오류해결] resolved [org.springframework.web.multipart.maxuploadsizeexceededexception: maximum upload size exceeded]</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_18111 마인크래프트]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N18111-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N18111-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8</guid>
            <pubDate>Thu, 28 Nov 2024 06:34:01 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/33015640-69f3-4c4c-91da-c366ef386420/image.PNG" width=80% />

<Image src="https://velog.velcdn.com/images/0woy_/post/6a0ed8d4-1e5a-4a8c-88dc-ff5306e85772/image.PNG" width=80% />

<ul>
<li>각 칸의 높이를 저장한 <code>NxM 배열</code>, 현재 갖고 있는 블럭 수(<code>b</code>)가 주어짐</li>
<li>최단시간에 모든 칸의 높이가 같도록 해야함.
ⓛ 블럭 쌓기 = 1초
② 블럭 제거 &amp; inventory 저장 = 2초</li>
<li>답이 여러 개인 경우, 높이가 가장 큰 값을 반환</li>
</ul>
<hr>
<h3 id="생각하기">생각하기</h3>
<p>현재 배열 내에서 최소 높이(<code>이하 min</code>), 최대 높이(<code>이하 max</code>)를 알면,
답으로 나올 수 있는 높이의 범위는 <code>min&lt;= height &lt;= max</code> 임.
그러면 위 범위에서 반복문을 돌려서 가장 적은 시간 &amp; 높이를 구하면 됨.</p>
<p>예제에 보면 같은 높이를 가진 칸이 여러 개일 수 있으니까,  map을 이용해서 풀면 어떨까?
map: <code>key: 높이</code>, <code>value: 해당 key 높이를 가진 칸의 개수</code> </p>
<blockquote>
<p>특정 높이 (height)를 기준으로,
<strong>count</strong> = map.get(h) : 배열 내 h 높이를 가진 칸의 개수</p>
</blockquote>
<p><em>** 1. 높이(h) &gt; height인 경우**</em>
 높이 차이(diff) 만큼  블럭 제거
 <code>time += diff x count x 2</code> &amp;&amp; <code>b += count</code></p>
<blockquote>
</blockquote>
<p><em>** 2. 높이(h) &lt; height인 경우**</em></p>
<blockquote>
<p>높이 차이 (diff) 만큼 블럭 쌓기
<code>time += diff x count</code> &amp;&amp; <code>b -= count</code></p>
</blockquote>
<p>계산이 끝나고 난 후 현재 가지고 있는 블럭(<code>b</code>)가 음수인 경우,
사용할 수 있는 블럭을 초과했으므로 해당 높이는 답이 될 수 없음.</p>
<p>따라서 <code>b &gt;= 0</code>인 경우만, 나올 수 있는 경우로 간주하고 진행해야한다.</p>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">import java.io.*;
import java.util.*;

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

        int cMax = -1;
        int cMin = 256;
        ground = new HashMap&lt;&gt;();
        while(n-- &gt; 0){
            st= new StringTokenizer(br.readLine());
            while(st.hasMoreTokens()){
                int k = Integer.parseInt(st.nextToken());
                ground.put(k,ground.getOrDefault(k, 0)+1);
                cMax = Math.max(cMax, k);
                cMin = Math.min(cMin, k);
            }
        }
        if(ground.keySet().size()==1){
            System.out.println(0+&quot; &quot;+cMax);
            return;
        }

        int res= Integer.MAX_VALUE;
        int height =0;
        for(int h =cMin;h&lt;=cMax;h++){
            int time = getHeight(h);
            if(time &gt; -1){
                if(time &lt;= res){
                    res =time;
                    height = Math.max(height, h);
                }
            }
        }
        System.out.println(res+&quot; &quot;+height);
    }

    public static int getHeight(int height){
        int blocks = b;
        int  time =0;
        for(int h : ground.keySet()){
            int count =ground.get(h);
            int diff = Math.abs(height-h); // 높이차
            if(h&gt;height){   // 제거
                time +=count*diff*2;
                blocks+=count*diff;
            }
            else if(h &lt; height){    // 추가
                time +=count*diff;
                blocks-=count*diff;
            }
        }
        if(blocks&lt;0){
            return -1;
        }
        return time;
    }
}

</code></pre>
<hr>
<h2 id="✍-부분-코드-설명">✍ 부분 코드 설명</h2>
<h4 id="변수-선언--초기화">변수 선언 &amp; 초기화</h4>
<pre><code class="language-java">public class Main{
    static Map&lt;Integer, Integer&gt; ground;
    static int b;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());
        b = Integer.parseInt(st.nextToken());

        int cMax = -1;
        int cMin = 256;
        ground = new HashMap&lt;&gt;();
        while(n-- &gt; 0){
            st= new StringTokenizer(br.readLine());
            while(st.hasMoreTokens()){
                int k = Integer.parseInt(st.nextToken());
                ground.put(k,ground.getOrDefault(k, 0)+1);
                cMax = Math.max(cMax, k);
                cMin = Math.min(cMin, k);
            }
        }
    ...   </code></pre>
<ul>
<li><code>ground</code>:  높이 별로 땅의 개수 저장</li>
<li><code>b</code>: 현재 갖고 있는 블럭 개수</li>
<li><code>cMax</code>: 배열 내 최대 높이</li>
<li><code>cMin</code>: 배열 내 최소 높이</li>
</ul>
<p>높이에 따른 땅의 개수를 <code>groud</code>에 저장하고, 최대 높이, 최소 높이를 구한다.</p>
<hr>
<h4 id="높이-계산하기">높이 계산하기</h4>
<pre><code class="language-java">        ...

  if(ground.keySet().size()==1){
            System.out.println(0+&quot; &quot;+cMax);
            return;
        }

        int res= Integer.MAX_VALUE;
        int height =0;
        for(int h =cMin;h&lt;=cMax;h++){
            int time = getHeight(h);
            if(time &gt; -1){
                if(time &lt;= res){
                    res =time;
                    height = Math.max(height, h);
                }
            }
        }
        System.out.println(res+&quot; &quot;+height);
    }
            ...</code></pre>
<p>- <code>res</code> :  최소 시간
- <code>height</code>: 최대 높이</p>
<ul>
<li><strong>key가 1개인 경우</strong>, 배열 내 모든 땅의 높이가 같다는 의미</li>
<li><blockquote>
<p> 땅을 고를 필요가 없으므로 <code>0, cMax</code> 반환 및 종료</p>
</blockquote>
</li>
</ul>
<blockquote>
</blockquote>
<p><strong><code>cMin ~ cMax</code></strong> 범위 내에서 각 값을 기준 높이(h)로, <code>getHeight()</code> 함수 호출</p>
<blockquote>
</blockquote>
<p>함수 반환 값이 -1이 아니면, 땅을 고를 수 있는 경우임
-&gt; 현재 저장된 최소 시간(<code>res</code>) 보다 작거나 같은 경우, <code>res</code>값 갱신, 최대 높이(<code>height</code>) 갱신.</p>
<hr>
<h4 id="getheight-함수">getHeight 함수</h4>
<pre><code class="language-java">        ...

   public static int getHeight(int height){
        int blocks = b;
        int  time =0;
        for(int h : ground.keySet()){
            int count =ground.get(h);
            int diff = Math.abs(height-h); // 높이차
            if(h&gt;height){   // 제거
                time +=count*diff*2;
                blocks+=count*diff;
            }
            else if(h &lt; height){    // 추가
                time +=count*diff;
                blocks-=count*diff;
            }
        }
        if(blocks&lt;0){
            return -1;
        }
        return time;
    }
}</code></pre>
<p>- <code>blocks</code> :  현재 사용 가능한 블럭 수 
- <code>time</code> : 총 소요시간</p>
<p><code>ground</code>에 저장된 모든 높이를 순회하면서, <code>time</code>과  <code>blocks</code>를 계산한다.</p>
<blockquote>
<p><em>*<em>1. blocks가 음수인 경우 *</em></em>
사용할 수 있는 블럭을 초과해서 땅을 고른 경우이기 때문에 해당 높이는 답이될 수 없음
<strong>∴  -1반환</strong></p>
<p><strong><em>2. blocks가 0이상인 경우</em></strong>
사용가능한 블럭 내에서 땅을 골랐으므로 <strong>소요시간을 반환</strong>한다.</p>
</blockquote>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/81e148f4-ade8-4258-a182-45e4c06ff7b6/image.PNG" alt=""></p>
<p>처음에 높이로 삼을 수 있는 건 블럭 내의 높이만 가능하다고 생각해서 풀었더니 틀렸다.
최소 높이~최대 높이 사이의 모든 값을 기준으로 삼고 브루트 포스로 풀어야 한다...</p>
<p>나처럼 바보 상자가 되지 말기를</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_3018 캠프파이어]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N3018-%EC%BA%A0%ED%94%84%ED%8C%8C%EC%9D%B4%EC%96%B4</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N3018-%EC%BA%A0%ED%94%84%ED%8C%8C%EC%9D%B4%EC%96%B4</guid>
            <pubDate>Mon, 25 Nov 2024 07:36:34 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/b78e2dcd-2689-44e4-b64f-2958bab8a354/image.PNG" width=80% />

<Image src="https://velog.velcdn.com/images/0woy_/post/a9f5f6a3-2ff2-4bd2-90c2-370b1a910532/image.PNG" width=80% />

<ul>
<li>선영이(1번)가 캠프파이어에 참여하는 날에는 새로운 노래 등장</li>
<li>선영이 없으면, 참가자들끼리 아는 모든 노래 공유</li>
</ul>
<p>MT가 끝난 이후, 모든 노래를 아는 친구들 오름차순 출력</p>
<hr>
<h3 id="생각하기">생각하기</h3>
<p>선영이는 모든 노래를 알고 있다...
왜냐하면 선영이가 대장이라 선영이 있을 때만 새로운 노래를 부르기때문이다.
그럼 매일 두 가지 경우로 나누어서 처리해야한다.</p>
<p><strong>1) 선영이가 있는 경우</strong>
현재 참가자들끼리 *<em>새로운 노래 *</em>추가</p>
<p><strong>2) 선영이가 없는 경우</strong>
자기들이 알고 있는 노래를 공유하므로, <strong>공유한 모든 노래들을 당일 참가자</strong>에게 추가.</p>
<blockquote>
<p>처음에는 <code>Set</code>만 사용해서 현재 <strong>모든 노래를 알고 있는 참가자</strong>만 <strong>저장</strong>했지, 
<em><strong>참가자별로 알고 있는 노래들은 저장하지 않았다.</strong></em></p>
</blockquote>
<p>선영이가 있는 날, 아래 친구들을 <code>set</code>에서 제외한다.</p>
<ol>
<li>이전 노래를 모르는 친구</li>
<li>현재 모든 노래를 알고 있는 친구들 중 오늘 참가하지 않은 친구<blockquote>
</blockquote>
선영이가 없는 날이면, 
현재 참가자 중에 <code>set</code>에 포함된 친구가 있으면 참가자 모두를 set에 넣어줬다.</li>
</ol>
<p>그런데, 이렇게 풀면 아래 반례에서 걸린다.</p>
<table>
<thead>
<tr>
<th>날짜</th>
<th align="center">참여자</th>
<th align="center">set</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td align="center">1, 4</td>
<td align="center">1, 4</td>
</tr>
<tr>
<td>2</td>
<td align="center">1, 3</td>
<td align="center">1</td>
</tr>
<tr>
<td>3</td>
<td align="center">1, 2</td>
<td align="center">1</td>
</tr>
<tr>
<td>4</td>
<td align="center">2, 3, 4</td>
<td align="center">1</td>
</tr>
</tbody></table>
<p>내 접근대로 풀면 모든 노래를 아는 친구는 <code>1번</code>만 있다고 나온다.</p>
<p>하지만, 4일째에 <code>2, 3, 4</code>번이 서로 알고 있는 노래를 공유하게 되므로 
결과적으로 <code>1, 2, 3 ,4</code> 번이 모든 노래를 알 수 있다.</p>
<blockquote>
<p>위 반례를 통해<em>** 친구들 별로 자신들이 알고 있는 노래를 저장**</em> 해야 함을 알게 돼서 다 갈아엎었음. ㅎ</p>
</blockquote>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">import java.io.*;
import java.util.*;

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

        StringTokenizer st;
        int n = Integer.parseInt(br.readLine());
        int days = Integer.parseInt(br.readLine());
        Map&lt;Integer, Set&lt;Integer&gt;&gt; map = new HashMap&lt;&gt;();
        for(int d=0;d&lt;days;d++){
            st = new StringTokenizer(br.readLine());
            st.nextToken();
            List&lt;Integer&gt; participants = new ArrayList&lt;&gt;();
            while (st.hasMoreTokens()){
                participants.add(Integer.parseInt(st.nextToken()));
            }
            if(participants.contains(1)){
                for(int p: participants){
                    Set&lt;Integer&gt; know = map.getOrDefault(p, new HashSet&lt;&gt;());
                    know.add(d);
                    map.put(p, know);
                }
            }else{
                Set&lt;Integer&gt; shares =new HashSet&lt;&gt;();
                for(int p : participants){
                    if(map.get(p)!=null)
                        shares.addAll(map.get(p));
                }
                for(int p: participants){
                    map.put(p, new HashSet&lt;&gt;(shares));
                }
            }
        }

        List&lt;Integer&gt; result = new ArrayList&lt;&gt;();
        int goal = map.get(1).size();
        for(int p: map.keySet()){
            if(map.get(p).size() ==goal){
                result.add(p);
            }
        }
        Collections.sort(result);
        for(int v: result){
            bw.write(v+&quot;\n&quot;);
        }
        bw.flush();
        bw.close();
    }
}
</code></pre>
<hr>
<h2 id="✍-부분-코드-설명">✍ 부분 코드 설명</h2>
<h4 id="변수-선언--초기화">변수 선언 &amp; 초기화</h4>
<pre><code class="language-java"> public class Main{
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        StringTokenizer st;
        int n = Integer.parseInt(br.readLine());
        int days = Integer.parseInt(br.readLine());
        Map&lt;Integer, Set&lt;Integer&gt;&gt; map = new HashMap&lt;&gt;();
        for(int d=0;d&lt;days;d++){
            st = new StringTokenizer(br.readLine());
            st.nextToken();
            List&lt;Integer&gt; participants = new ArrayList&lt;&gt;();
            while (st.hasMoreTokens()){
                participants.add(Integer.parseInt(st.nextToken()));
            }
    ...   </code></pre>
<ul>
<li><code>n</code>: MT 참가자 수</li>
<li><code>days</code>: MT 날짜</li>
<li><code>map</code>: <code>key: 참가자 번호</code>, <code>value: 해당 참가자가 아는 노래</code></li>
<li><code>participants</code>: 당일 참가자 목록</li>
</ul>
<p><code>participants</code> 리스트에 당일 참가자들을 저장한다.</p>
<hr>
<h4 id="선영이-유무에-따른-처리">선영이 유무에 따른 처리</h4>
<pre><code class="language-java">        ...

    if(participants.contains(1)){
                for(int p: participants){
                    Set&lt;Integer&gt; know = map.getOrDefault(p, new HashSet&lt;&gt;());
                    know.add(d);
                    map.put(p, know);
                }
            }else{
                Set&lt;Integer&gt; shares =new HashSet&lt;&gt;();
                for(int p : participants){
                    if(map.get(p)!=null)
                        shares.addAll(map.get(p));
                }
                for(int p: participants){
                    map.put(p, new HashSet&lt;&gt;(shares));
                }
            }
        }
            ...</code></pre>
<p><strong>1. 선영이 있는 경우</strong>
참가자 전원 오늘 날짜 추가 (= 새로운 노래는 하나만 부르니까 걍 오늘 날짜 저장해도 됨)</p>
<p><strong>2. 선영이 없는 경우</strong>
잡도리하는 선영이 없어서 자기들이 아는 노래 공유하는 시간 갖는다.
그래서 <code>shares</code>  집합을 만들어서 참가자들이 알고 있는 노래들을 다 저장함.
그리고 참가자들 노래 목록을 <code>shares</code>로 업데이트</p>
<hr>
<h4 id="모든-노래-알고-있는-친구-찾기">모든 노래 알고 있는 친구 찾기</h4>
<pre><code class="language-java">        ...

   List&lt;Integer&gt; result = new ArrayList&lt;&gt;();
        int goal = map.get(1).size();
        for(int p: map.keySet()){
            if(map.get(p).size() ==goal){
                result.add(p);
            }
        }
        Collections.sort(result);
        for(int v: result){
            bw.write(v+&quot;\n&quot;);
        }
        bw.flush();
        bw.close();
    }
}</code></pre>
<p>위에 문제 설명에서 말했듯, 선영이는 모든 노래를 알고 있잖아요?
그러면, <em><strong>선영이가 아는 노래 개수 = 참가자가 아는 노래 개수</strong></em> 인 경우 해당 친구는 모든 노래를 알고 있다고 간주해도 되겠죠?</p>
<p>그리고 오름차순으로 출력하라고 했으니 <code>Collections.sort</code> 로 오름차순으로 만들어주고 출력!</p>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/70f9b381-b265-4788-99a5-374cb8b72a62/image.PNG" alt=""></p>
<p>개오래걸림
그래두 풀었으니 뭐.. 네..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[LeetCode-자바] N_207, N_210 Course Schedule I, II]]></title>
            <link>https://velog.io/@0woy_/LeetCode-%EC%9E%90%EB%B0%94-N207-Course-Schedule</link>
            <guid>https://velog.io/@0woy_/LeetCode-%EC%9E%90%EB%B0%94-N207-Course-Schedule</guid>
            <pubDate>Tue, 19 Nov 2024 07:29:41 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Img src="https://velog.velcdn.com/images/0woy_/post/8df242c1-185f-4755-bba8-25ba7cbf5198/image.PNG" width=100% />


<ul>
<li><code>0</code> ~ <code>numCourse-1</code>까지 모든 과목을 수강할 수 있는지 여부 반환</li>
<li>선수 과목이 있는 경우, 선수 과목을 먼저 수강해야 함.</li>
</ul>
<h3 id="생각하기">생각하기</h3>
<p>처음 고민했을 땐 위상정렬이라고는 생각을 못했다.
<code>[0,1], [1,0]</code> 선이수 과목끼리 _<strong>싸이클이 생기게 되면 모든 과목을 들을 수가 없음</strong>_을 깨닫고,
싸이클이 생기면 false를 반환하면 되겠다고 생각해서 코드를 쳤다. <del>근데 걍 틀림</del></p>
<blockquote>
<p>싸이클 여부를 찾은 건 잘했는데, <strong>위상 정렬</strong>이랑 안 친해서.. 몰랐어유
그래서 오늘 위상정렬 공부했음</p>
</blockquote>
<hr>
<h2 id="❓-위상정렬">❓ 위상정렬</h2>
<h3 id="위상-정렬topology-sort이란">위상 정렬(Topology Sort)이란?</h3>
<p>방향이 있는 비순환 그래프 (DAG, Directed Acyclic Graph)에서 사용</p>
<ul>
<li><p>작업 i와  작업 j 사이에 간선<code>(i, j)</code>가 존재한다면 작업 i는 반드시 작업 j보다 먼저 수행되고, 그래프의 모든 간선이 이런 성질을 만족하는 정렬을 의미한다.</p>
</li>
<li><p>위상 정렬은 사이클이 없는 유향 그래프<code>G=(V, E)</code>에서 V의 모든 정점을 정렬하되 정렬 결과에서 정점 i는 반드시 정점 j보다 앞에 위치해야한다.</p>
</li>
</ul>
<blockquote>
<p><em><strong>만일 그래프에 사이클이 존재한다면, 해당 성질은 결코 만족될 수 없으므로 위상 정렬은 할 수 없다.</strong></em></p>
</blockquote>
<p>넘 이론적인 얘기라서 이해가 어려울 수 있다.
간단히 대학교 전공 수업처럼 선이수 과목 먼저 수강해야 다음 거 들을 수 있음.
그런데 a의 선이수 과목이 b고 b의 선이수 과목이 a면 말도 안 된다는 의미</p>
<blockquote>
<p><strong>위상 정렬 방법</strong></p>
</blockquote>
<ul>
<li><code>Kahn&#39;s Algorithm</code>: 진입차수와 큐를 이용 </li>
<li><code>DFS</code>: 그래프를 DFS로 탐색하면서 작업을 스택에 쌓음</li>
</ul>
<p>문제 풀 때 칸 알고리즘 사용했으니 먼저 소개하겠다.
dfs 풀이는 <code>210. Course Schedule II</code> 문제도 같이 풀어서 아래에 설명</p>
<hr>
<h2 id="1-kahns-algorithm">1. Kahn&#39;s Algorithm</h2>
<pre><code class="language-java"> public static boolean canFinish(int numCourses, int[][] prerequisites) {
        List&lt;Integer&gt;[] list =new List[numCourses];
        List&lt;Integer&gt; res = new ArrayList&lt;&gt;();
        int [] indegree = new int[numCourses];
        for(int [] pair : prerequisites){
            int after  =pair[0];
            int before = pair[1];
            if(list[before]==null){
                list[before] = new ArrayList&lt;&gt;();
            }
            list[before].add(after);
            indegree[after]++;
        }
        Queue&lt;Integer&gt; que = new ArrayDeque&lt;&gt;();
        for(int i=0;i&lt;numCourses;i++){
            if(indegree[i]==0){
                que.offer(i);
            }
        }

        while(!que.isEmpty()){
            int cur = que.poll();
            res.add(cur);

            if(list[cur]!=null){
                for(int v: list[cur]){
                    indegree[v]--;
                    if(indegree[v]==0){
                        que.offer(v);
                    }
                }
            }
        }
        System.out.println( res.size() == numCourses);
        return res.size() == numCourses;
    }</code></pre>
<hr>
<h2 id="✍-부분-코드-설명">✍ 부분 코드 설명</h2>
<p>** 변수 선언 및 초기화 **</p>
<pre><code class="language-java"> public static boolean canFinish(int numCourses, int[][] prerequisites) {
        List&lt;Integer&gt;[] list =new List[numCourses];
        List&lt;Integer&gt; res = new ArrayList&lt;&gt;();
        int [] indegree = new int[numCourses];
        for(int [] pair : prerequisites){
            int after  =pair[0];
            int before = pair[1];
            if(list[before]==null){
                list[before] = new ArrayList&lt;&gt;();
            }
            list[before].add(after);
            indegree[after]++;                      
        }
            ...</code></pre>
<ul>
<li><code>list</code>: 정점 별 진출 정점 저장</li>
<li><code>indegree</code>: 정점 별 진입 차수 저장</li>
<li><code>res</code>: 진입 차수가 0인 (방문 가능한) 정점 저장</li>
</ul>
<hr>
<p><strong>위상 정렬</strong></p>
<pre><code class="language-java">    ...

 Queue&lt;Integer&gt; que = new ArrayDeque&lt;&gt;();
        for(int i=0;i&lt;numCourses;i++){
            if(indegree[i]==0){
                que.offer(i);
            }
        }
        while(!que.isEmpty()){
            int cur = que.poll();
            res.add(cur);

            if(list[cur]!=null){
                for(int v: list[cur]){
                    indegree[v]--;
                    if(indegree[v]==0){
                        que.offer(v);
                    }
                }
            }
        }
        System.out.println( res.size() == numCourses);
        return res.size() == numCourses;
    }</code></pre>
<blockquote>
<ol>
<li>진입 차수가 0인 정점 que에 삽입</li>
<li>순차적으로 que에서 정점 제거 및 res에 해당 정점 삽입</li>
<li>현재 정점 (cur)의 진출 정점이 존재하는 경우, 해당 진출 정점들의 진입 차수 감소.</li>
<li><code>1 ~ 3</code>과정을 que가 빌 때까지 반복</li>
</ol>
</blockquote>
<p>백문이 불여일견</p>
<Image src="https://velog.velcdn.com/images/0woy_/post/52ad7efe-e1ee-4d80-a71a-e25e0211d86e/image.%25EC%259C%2584%25EC%2583%2581%2520%25EC%25A0%2595%25EB%25A0%25AC" width = 85% />

<hr>
<h2 id="2-dfs--stack">2. DFS &amp; Stack</h2>
<pre><code class="language-java">package Arrays_Hashing;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class N_210 {
    static List&lt;List&lt;Integer&gt;&gt; list;
    static Stack&lt;Integer&gt; stack;
    static boolean [] visited;
    static boolean [] finish;
    static boolean cycle;
    public static int[]canFinish(int numCourses, int[][] prerequisites) {
        list =new ArrayList&lt;&gt;();
        stack = new Stack&lt;&gt;();
        visited = new boolean[numCourses];
        finish = new boolean[numCourses];
        cycle =false;

        for(int i=0;i&lt;numCourses;i++){
            list.add(new ArrayList&lt;&gt;());
        }

        for(int [] pair: prerequisites){
            int after= pair[0];
            int before= pair[1];
            list.get(before).add(after);
        }

        for(int i=0;i&lt;numCourses;i++){
            if(!visited[i]){
                dfs(i);
            }
        }
        int [] res = new int[numCourses];
        if(!cycle){
            int idx =0;
            while(!stack.isEmpty()){
                res[idx++] = stack.pop();
            }
        }
        else{
            return res;
        }
        System.out.println(true);
        return res;
    }
    public static void dfs(int cur){
        if(cycle){
            return;
        }
        visited[cur] = true;
        for(int next : list.get(cur)){
            if(!visited[next]) {
                dfs(next);
            }
            else if(!finish[next]){
                cycle = true;
                return;
            }
        }
        finish[cur]=true;
        stack.push(cur);
    }

    public static void main(String[] args) {
        int numCourses = 2;
        int [][] prerequisites = new int[][]{
                {1,0},
                {0,1}
        };

        canFinish(numCourses, prerequisites);
    }
}
</code></pre>
<hr>
<h2 id="✍-부분-코드-설명-1">✍ 부분 코드 설명</h2>
<p>** 변수 선언 및 초기화 **</p>
<pre><code class="language-java">
public class N_210 {
    static List&lt;List&lt;Integer&gt;&gt; list;
    static Stack&lt;Integer&gt; stack;
    static boolean [] visited;
    static boolean [] finish;
    static boolean cycle;
    public static int[]canFinish(int numCourses, int[][] prerequisites) {
        list =new ArrayList&lt;&gt;();
        stack = new Stack&lt;&gt;();
        visited = new boolean[numCourses];
        finish = new boolean[numCourses];
        cycle =false;

        for(int i=0;i&lt;numCourses;i++){
            list.add(new ArrayList&lt;&gt;());
        }

        for(int [] pair: prerequisites){
            int after= pair[0];
            int before= pair[1];
            list.get(before).add(after);
        }

            ...</code></pre>
<ul>
<li><code>list</code>: 정점 별 진출 정점 저장</li>
<li><code>visited</code>: 정점 별 방문 여부</li>
<li><code>finish</code>: 정점 별 방문 완료 여부</li>
<li><code>cycle</code>: cycle 존재 여부</li>
</ul>
<hr>
<p><strong>위상 정렬</strong></p>
<pre><code class="language-java">    ...

     for(int i=0;i&lt;numCourses;i++){
            if(!visited[i]){
                dfs(i);
            }
        }
        int [] res = new int[numCourses];
        if(!cycle){
            int idx =0;
            while(!stack.isEmpty()){
                res[idx++] = stack.pop();
            }
        }
        else{
            return res;
        }
        System.out.println(true);
        return res;
    }
    public static void dfs(int cur){
        if(cycle){
            return;
        }
        visited[cur] = true;
        for(int next : list.get(cur)){
            if(!visited[next]) {
                dfs(next);
            }
            else if(!finish[next]){
                cycle = true;
                return;
            }
        }
        finish[cur]=true;
        stack.push(cur);
    }</code></pre>
<blockquote>
<ol>
<li>현재 방문하지 않은 정점 기준, dfs 호출</li>
<li>현재 정점 방문 처리 &amp; 진출 정점 확인
2-1) 진출 정점에 방문하지 않은 경우 해당 정점 기준 재귀 호출
2-2) 진출 정점에 이미 방문 했지만, 완료된 정점이 아닌 경우, 그래프 내에  <strong>싸이클</strong>이 존재하므로 탐색 하지 않고 <code>RETURN</code></li>
<li>더이상 진출 정점이 없는 경우, 방문 완료 처리 및 stack 삽입</li>
</ol>
</blockquote>
<p>이렇게 하면, 방문해야 하는 순서대로 stack에 저장되므로 순서도 파악할 수 있다.</p>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/9ee7b623-2ac6-4b7a-a5fc-c2ac640cea46/image.res" alt=""></p>
<p>학부생때 위상 정렬 배웠는데,, 아예 안써서 기억의 저편으로 넘어가버렸다.
그래도 오늘 다시 공부했으니 다행~</p>
<p>내가 접근한 풀이를 보여주자면</p>
<pre><code class="language-java"> static boolean [] visited;
    static Map&lt;Integer, List&lt;Integer&gt;&gt; map;
    static boolean res;
    public static boolean canFinish(int numCourses, int[][] prerequisites) {
        res = true;
        map =new HashMap&lt;&gt;();
        for(int [] pre: prerequisites){
            List&lt;Integer&gt; tmp = map.getOrDefault(pre[0], new ArrayList&lt;&gt;());
            tmp.add(pre[1]);
            map.put(pre[0],tmp);
        }
        for(int i=0;i&lt;numCourses;i++){
            visited = new boolean[numCourses];
            if(!visited[i] &amp;&amp; map.containsKey(i)){
                for(int v : map.get(i)){
                    dfs(v);
                    visited[i]=true;
                }
            }
            if(!res){
                System.out.println(res);
                return false;
            }
        }
        System.out.println(res);
        return res;
    }
    public static void dfs(int cur){
        if(map.get(cur)==null){
            return;
        }
        if(!visited[cur]){
            visited[cur]=true;
            for(int v: map.get(cur)){
                dfs(v);
            }
            if(!res){
                return;
            }
        }else{
            res = false;
        }
    }</code></pre>
<p>dfs로 접근헀는데,, 싸이클 체크를 괴상하게 구현해서 틀림 ㅎㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_14501 퇴사]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N1244-%EC%8A%A4%EC%9C%84%EC%B9%98-%EC%BC%9C%EA%B3%A0-%EB%81%84%EA%B8%B0-t4904ame</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N1244-%EC%8A%A4%EC%9C%84%EC%B9%98-%EC%BC%9C%EA%B3%A0-%EB%81%84%EA%B8%B0-t4904ame</guid>
            <pubDate>Sun, 10 Nov 2024 13:52:06 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/63255dbf-d1fc-40d9-84bf-94e11e522ddd/image.PNG" width=100% />

<Image src="https://velog.velcdn.com/images/0woy_/post/1fdc4fde-9cf9-4aae-aff2-d02c52fd1415/image.PNG" width=80% />

<ul>
<li>퇴사 전까지 돈을 땡기고 싶은 백준이에게.. 최대 이익 선물해 주기</li>
</ul>
<p>예전에 풀다가 대차게 틀렸던 문제였다.
분명 그때 풀이 보고 다음에 만나면 짓밟아 주겠다고 생각했는데 오늘 보니 또 새롭네요</p>
<hr>
<h3 id="생각하기">생각하기</h3>
<p>dp 냄새가 납니다. 
백준이가 벌 수 있는 최대의 돈.. 뭔가 이전 최댓값과 비교해서 현재값이 더 크면 그게 정답이 될 것 같은 느낌이 들었어요.</p>
<blockquote>
<p><code>i</code>번째 날 작업이 걸리는 시간이 n인 경우, <code>i+n</code>날 이후 모든 날의 이익은 최소 i번째 날의 이익이라는 것을 깨닫는 게 중요하다.</p>
</blockquote>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</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));
        int n = Integer.parseInt(br.readLine());
        int [][] dp =new int[n+1][2];
        int [] total = new int[n+2];
        dp[0][0] = 0;
        dp[0][1] = 0;
        for(int i=1;i&lt;=n;i++){
            StringTokenizer st = new StringTokenizer(br.readLine());
            dp[i][0] = Integer.parseInt(st.nextToken());
            dp[i][1] = Integer.parseInt(st.nextToken());
        }
        int max =0;
        for(int i=1;i&lt;=n;i++){
            int time = dp[i][0]+i;
            if(time &lt;= n+1){
                for(int t = time;t&lt;=n+1;t++){
                   total[t] =  Math.max(total[t], dp[i][1]+total[i]);
                }
            }
        }

        System.out.println(total[n+1]);
    }
}</code></pre>
<p>백문이 불여일견</p>
<ul>
<li><code>dp</code>: 날짜별 작업 시간 &amp; 이익 저장 </li>
<li><code>total</code> 날짜별 누적 최대 이익 저장</li>
</ul>
<p>현재 날짜에서 작업시간을 더한 날을 <code>next</code>라고 칭했을 때,
<code>next</code> 값이 퇴사날 이전인 경우 계산을 시작한다.</p>
<ul>
<li><p>현재 날짜까지 최대 이익 <code>(total[i])</code> +
현재 날짜를 선택함으로써 얻을 수 있는 이익 <code>(dp[i][1])</code> </p>
</li>
<li><p>next에 누적된 이익 <code>(total[next])</code></p>
</li>
</ul>
<p>위 두 개의 값 중 더 큰 값을 <code>total[next]</code>에 저장한다.</p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/d9eeeec7-b7d4-427c-96d4-1d38c96268cb/image.CALC" alt=""></p>
<p>숫자에 <strong>빨간</strong> 표시한 부분이 <em><strong>최대 이익이 갱신된 경우</strong></em> 다.
위와 같은 로직으로 계산이 이루어진다고 생각하면 된다.</p>
<p>우리는 배열을 만들 때, 퇴사날인 8일의 공간을 만들어 줘서 <code>total[8]</code>에 <strong>최대이익</strong>을 저장하면 된다. 나는 이 방법을 반복문 내에, <code>i+n</code>일 이후부터 <code>퇴사날</code>까지  반복문을 하나 더 두어서 최대 이익을 저장할 수 있게 했다.</p>
<blockquote>
<p>이중 반복문이기에 내 풀이는 효율적이진 않다.
반복문을 돌리지 않고 풀 수 있는 방법이 있지만, 문제를 풀고 나서 깨달았다.</p>
</blockquote>
<pre><code class="language-java">for(int i=1;i&lt;=n;i++){
    int time = dp[i][0]+i;
    if(time &lt;= n+1){
        total[time] =  Math.max(total[time], dp[i][1]+total[i]);
    }
    total[i+1]=Math.max(total[i],total[i+1]);
}</code></pre>
<p>이중 반복문이 아닌, 현재 날짜의 다음 날짜의 최대 이익을 비교해서 다음 날짜의 최대이익을 갱신하면 된다.</p>
<p>그렇게 되면, 우리는 퇴사날까지 우리가 구한 최대 이익을 들고 갈 수가 있다.</p>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/4a5a2c3c-f8e4-4d0e-949a-d40b9fa418ee/image.res" alt=""></p>
<p>음. 혼자 푼 거 꽤괜
시간 단축만 하면 될 거 같다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_1244 스위치 켜고 끄기]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N1244-%EC%8A%A4%EC%9C%84%EC%B9%98-%EC%BC%9C%EA%B3%A0-%EB%81%84%EA%B8%B0</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N1244-%EC%8A%A4%EC%9C%84%EC%B9%98-%EC%BC%9C%EA%B3%A0-%EB%81%84%EA%B8%B0</guid>
            <pubDate>Sat, 09 Nov 2024 05:28:06 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/ed9dfa9e-d4f7-43c2-9eb0-431269855663/image.PNG" width=100% />

<Image src="https://velog.velcdn.com/images/0woy_/post/11ddb2ab-c683-405a-bf67-2543789b51a9/image.PNG" width=100% />

<ul>
<li>남학생: 받은 숫자 <strong>배수</strong> 스위치 상태 변경</li>
<li>여학생: 받은 숫자 기준, <strong>대칭</strong>이 되는 모든 스위치 상태 변경</li>
<li>스위치 상태는  20개 씩 출력 후 줄바꿈</li>
</ul>
<p>그렇게 어려운 문제는 아닌데, 오래걸렸다..
처음에 문제를 제대로 안 읽어서 배수인 줄 몰랐고,, 20개 줄바꿈도 안 했고
무엇보다 인덱스를 잘 못 다뤘다.</p>
<hr>
<h3 id="생각하기">생각하기</h3>
<p>풀이 방법은 간단하다. 
그냥 위 조건대로 구현을 하면 되는 문제긴 한데, 인덱스 조절하는 게 좀 번거로웠다.</p>
<p>배열의 경우 0부터 시작을 하는데, 문제에서는 스위치 번호가 1부터 시작을 해서 
배열의 크기를 <code>주어진 수+1</code> 만큼 해주고 구현하면 된다.</p>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">import java.io.*;
import java.util.StringTokenizer;

public class N_1244 { 
    static int[] switches;

    public static void printSwitches(){
        StringBuilder sb = new StringBuilder();
        for(int i=1;i&lt;switches.length;i++){
            sb.append(switches[i]+&quot; &quot;);
            if(i%20==0){
                sb.append(&quot;\n&quot;);
            }
        }
        System.out.println(sb.toString());
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st =new StringTokenizer(br.readLine());

        switches = new int[n+1];
        for(int i=1;i&lt;=n;i++){
            switches[i] = Integer.parseInt(st.nextToken());
        }
        int student = Integer.parseInt(br.readLine());
        while(student -- &gt; 0){
            st = new StringTokenizer(br.readLine());
            int sex = Integer.parseInt(st.nextToken());
            int pos = Integer.parseInt(st.nextToken());

            calc(sex, pos);
        }
        printSwitches();
    }
    public static void calc(int sex, int pos){
        if(sex == 1){
            for(int i=1;pos*i&lt;switches.length;i++){
                switches[pos*i] =
                        (switches[pos*i]==0)?1:0;
            }
        }else{
            int start, end;
            start = end=pos;
            while(start &gt;=2 &amp;&amp; end&lt;switches.length-1){
                start--;
                end++;
                if(switches[start]!=switches[end]){
                    start ++;
                    end--;
                    break;
                }
            }
            for(int i=start;i&lt;=end;i++){
                switches[i] =
                        (switches[i]==0)?1:0;
            }
        }
    }

}
</code></pre>
<ul>
<li><code>main</code> : 입력 및 초기화</li>
<li><code>calc</code> : 남&amp;여 학생에 따른 스위치 변화</li>
<li><code>printSwitches</code>: 스위치 상태 출력</li>
</ul>
<hr>
<h2 id="✍-부분-코드-설명">✍ 부분 코드 설명</h2>
<p><strong>Main 함수 - 입력 &amp; 변수 초기화</strong></p>
<pre><code class="language-java">     static int[] switches;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st =new StringTokenizer(br.readLine());

        switches = new int[n+1];
        for(int i=1;i&lt;=n;i++){
            switches[i] = Integer.parseInt(st.nextToken());
        }
        int student = Integer.parseInt(br.readLine());
        while(student -- &gt; 0){
            st = new StringTokenizer(br.readLine());
            int sex = Integer.parseInt(st.nextToken());
            int pos = Integer.parseInt(st.nextToken());

            calc(sex, pos);
        }
        printSwitches();
    }

    ...   </code></pre>
<ul>
<li><code>switches</code>: 스위치 상태 저장 배열</li>
<li><code>n</code>: 스위치 개수</li>
<li><code>student</code>: 학생 수</li>
<li><code>sex</code>: 학생 성별</li>
<li><code>pos</code>: 스위치 변경 기준 위치</li>
</ul>
<p>들어온 입력대로 스위치 상태를 저장해 주고, 학생 수 만큼 <code>calc</code> 함수를 호출해서 스위치 상태를 변경한다.</p>
<hr>
<p><strong>calc 함수</strong></p>
<pre><code class="language-java">    public static void calc(int sex, int pos){
        if(sex == 1){
            for(int i=1;pos*i&lt;switches.length;i++){
                switches[pos*i] =
                        (switches[pos*i]==0)?1:0;
            }
        }else{
            int start, end;
            start = end=pos;
            while(start &gt;=2 &amp;&amp; end&lt;switches.length-1){
                start--;
                end++;
                if(switches[start]!=switches[end]){
                    start ++;
                    end--;
                    break;
                }
            }
            for(int i=start;i&lt;=end;i++){
                switches[i] =
                        (switches[i]==0)?1:0;
            }
        }
    }</code></pre>
<p>sex 매개변수에 남학생은 1로, 여학생은 2로 들어온다.</p>
<h3 id="1-남학생">1. 남학생</h3>
<p>남학생은 스위치 번호가 자신이 받은 수의 배수이면 상태를 바꾼다.
예를 들어 <code>3</code>이 들어온 경우, 우리는 <code>3번</code>,<code>6번</code>, <code>9번</code> .. 스위치를 바꾼다.
 <strong>i 값을 증가</strong>해 가면서 배수를 찾으면 돼서 여학생보다 쉽다.</p>
<h3 id="2-여학생">2. 여학생</h3>
<p>여학생은 자신이 받은 수의 스위치 번호를 기준으로 양 옆이 대칭인 모든 스위치의 상태를 바꾼다.</p>
<blockquote>
<p>예를 들어 스위치의 초기상태가 <code>1 1 0 0 0 1 0</code> 이고, 여학생이 <code>4번</code>을 받은 경우,
<code>[3번, 5번]</code> 스위치가 <code>0</code>으로 대칭이고, <code>[2번, 6번]</code> 스위치가 <code>1</code>로 대칭이다.
하지만, <code>[1번, 7번]</code> 스위치는 각각 <code>1</code>과 <code>0</code>이므로 대칭이 아니다.</p>
</blockquote>
<p>결국, 2번~6번까지의 스위치의 상태를 변화시키므로 <code>1 0 1 1 1 0 0</code> 이 된다.</p>
<p><code>start</code>와 <code>end</code> 변수를 이용해서 pos기준 왼쪽과 오른쪽을 탐색한다.</p>
<p>걍 보여주면서 설명</p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/0f64ee48-efc8-4df8-896f-1286a474d5e4/image.switch" alt=""></p>
<p>여학생인 경우 위와 같은 순서로 스위치 상태가 변경 된다.</p>
<blockquote>
<p>*<em>Q. *</em> start가 2이하면 종료라 했는데, 3)과정에서 에서 start가 1인데 왜 안 끝나고 실행 돼?</p>
<p><strong>A.</strong> 어 왜냐면 3) 과정은 조건문에서 start가 2일 때 실행이 됐는데,
실행문에서 <code>start--</code>로   줄여준 거니까 while은 start가 1인줄 아직 몰랑
그 담에 검사할 때 1인거 앎</p>
</blockquote>
<p>보통 배열 탐색 반복문의 종료 조건은 0이상, 마지막 수 미만으로 설정한다.
그래서 처음에 while의 조건을 <strong>1이상, 9 (switches.lengthh) 미만</strong>으로 설정했더니 틀렸음</p>
<p>왜냐하면, 만약에 모든 스위치가 좌우 대칭인 경우에 end = 9가 되고, start는 0으로 종료가 될 건데, 그렇게 하면 우리는 배열의 범위를 넘어서기 때문에.. _<strong>인덱스 초과 에러</strong>_가 나요~</p>
<p>그래서 while문의 <strong>종료조건이 start가 2이상, end가 마지막에서 두 번째 번호 미만</strong>으로 설정했다.</p>
<blockquote>
<p>어차피 실행문 내에서 증감을 하기에 모든 원소를 살펴볼 수 있음</p>
</blockquote>
<p>  <img src="https://velog.velcdn.com/images/0woy_/post/979e47be-0ca8-40b7-9bf1-f7ba46b9c807/image.res" alt=""></p>
<p> 대충 요런 식으로 흘러감</p>
<hr>
<p><strong>printSwitches 함수</strong></p>
<pre><code class="language-java">  public static void printSwitches(){
        StringBuilder sb = new StringBuilder();
        for(int i=1;i&lt;switches.length;i++){
            sb.append(switches[i]+&quot; &quot;);
            if(i%20==0){
                sb.append(&quot;\n&quot;);
            }
        }
        System.out.println(sb.toString());
    }
</code></pre>
<p><strong>20개 마다 줄바꿈</strong> 하라고 했으니 20으로 나누어 떨어지면 줄바꿈 문자 삽입
그대로 배열 출력하면 됨</p>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/f8f7be06-efb3-455d-b0c2-db4fa7cfec37/image.res" alt=""></p>
<p>내가 젤 싫어하는 인덱스 초과 ;;;
보다시피 실버 4인데 오래걸렸다. 왜 구현에 이렇게 오래 걸렸지 ???
왜냐면 처음부터 문제 제대로 안 읽고 쉬워 보인다고 빠져가지고 생각 덜 해서 그래</p>
<p>구현 문제 좀 풀어봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_18870 좌표 압축]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N18870-%EC%A2%8C%ED%91%9C-%EC%95%95%EC%B6%95</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N18870-%EC%A2%8C%ED%91%9C-%EC%95%95%EC%B6%95</guid>
            <pubDate>Thu, 07 Nov 2024 04:47:12 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/6da1b995-ab14-45f2-86a3-f247cae06980/image.PNG" width=100% />

<Image src="https://velog.velcdn.com/images/0woy_/post/0adec2d0-5911-47a2-a351-03eee12c7556/image.PNG" width=100% />

<ul>
<li>문제 이름에 어그로 끌려서 압축을 어떻게 했길래 입출력이 이모양이지 생각했지만, _*<em>모든 원소 중 현재 원소보다 작은 친구들 개수 *</em>_구하는 문제.</li>
</ul>
<hr>
<h3 id="생각하기">생각하기</h3>
<p>난 인덱스 초과 에러 다음으로 시간 초과에 걸리면 기분이 나쁘기에
시간 제한과 N의 최댓값을 보고 시간 복잡도를 먼저 생각합니다. <code>(최근에 들인 좋은 습관)</code></p>
<p>1초당 보통 1억 번의 연산이라고 생각하면 돼서, 2초니까 우린 <strong>2억 번 연산 내로</strong> 풀어야 해요.
<code>O(N²)</code>= 1조니까 중첩 반복문 돌리는 순간 시간 초과 엔딩남</p>
<p>그렇다면, <code>O(NlogN)</code>은 어떨까?
N=1,000,000일 때,Log(1,000,000) ≒ 20.
즉, 2천만번이라서 다행히 시간초과 안 납니다.</p>
<p>그런데 처음에 귀신 씌어서 Log(1,000,000)을 1,000으로 계산해가지고 <code>O(NlogN)</code>도 시간초과 나는줄 알고 좀 고민 많이 했다...</p>
<blockquote>
<p>왜 고민했냐면, 배열을 정렬하는 <code>Arrays.sort</code> 함수가 <code>O(NlogN)</code> 걸려서 이거 쓰면  안 되는 줄 알았음..</p>
</blockquote>
<p>그래놓고 우선순위 큐 썼는데, 알고보니 PQ 풀이도 같은 시간복잡도라서 멍청 시간만 썼다. </p>
<p>그래서 두 가지 풀이가 있다.  우선순위 큐, 그리고 배열 정렬
둘 다 설명할 건데,<em>** 배열 정렬이 더 효율적임**</em></p>
<hr>
<h2 id="✍-풀이-방법">✍ 풀이 방법</h2>
<h3 id="1-priority-queue--hashmap">1. Priority Queue &amp; HashMap</h3>
<pre><code class="language-java">   public static void main(String[] args) throws IOException {
        BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());
        int [] arr = new int[n];
        Map&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();
        PriorityQueue&lt;Integer&gt; pq = new PriorityQueue&lt;&gt;();
        int i=0;
        while(st.hasMoreTokens()){
            int val = Integer.parseInt(st.nextToken());
            pq.add(val);
            arr[i++]=val;
        }

        int count = 0;
        while(!pq.isEmpty()){
            int prev = pq.poll();
            map.put(prev, count);
            if(!pq.isEmpty() &amp;&amp; prev!=pq.peek()){
                count++;
            }
        }
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        for(int key: arr){
            bw.write(map.get(key)+&quot; &quot;);
        }
        bw.flush();
        bw.close();
    }</code></pre>
<ul>
<li><code>arr</code> : 주어진 순서대로 숫자 저장</li>
<li><code>map</code> : (key: 숫자, value: key보다 작은 원소 개수)</li>
<li><code>pq</code> : 오름차순 정렬에 사용</li>
</ul>
<ol>
<li><p>입력 받은 원소들을 pq와 arr 배열에 삽입
<code>pq</code>는 나중에 뽑아낼 때 작은 친구들 부터 뽑아낼 거고, <code>arr</code>은 출력에 쓰려고 함.</p>
</li>
<li><p>map의 <code>key</code>는 pq에서 뽑아낸 값, <code>value</code>는  count 값 저장.
중복값이 존재할 수도 있으니, 현재 원소와 그 다음으로 작은 원소가 같으면 count를 증가시키지 않습니다.</p>
</li>
<li><p>입력 받은 순서대로 값을 출력해야 하니 <code>arr</code> 배열에 저장된 원소를 key값으로 map에서 value를 취해 출력</p>
</li>
</ol>
<blockquote>
<p><strong>Q.</strong> arr배열 안 만들고 대신 순서 보장해 주는 LinkedHashMap 써서, map.values() 하면 안 됨?</p>
</blockquote>
<p><strong>A.</strong> ㅇㅇ 해봤는데 중복 원소 허용이라 안 됨
ex) <code>2, 4, -10, 4, -9</code> 들어오면  <code>1, 3, 0, 1</code> 나와서 4가 씹힙니다.</p>
<p>암튼 이렇게 풀면 배열 두 개 놓고 정렬 원소 쓰는 것보다
메모리, 시간 면에서 비효율적입니다. </p>
<p>그냥 <del>굳이</del> 이렇게 풀 수도 있구나.. 라고 생각하면 됨</p>
<hr>
<h3 id="2-arrayssort--hashmap">2. Arrays.sort &amp; HashMap</h3>
<pre><code class="language-java"> public static void main(String[] args) throws IOException {
        BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());
        Map&lt;Integer, Integer&gt; map = new HashMap&lt;&gt;();
        int [] arr  =new int[n];
        int [] sorted  =new int[n];

        for(int i=0;i&lt;n;i++){
            int v =  Integer.parseInt(st.nextToken());
            arr[i] =v;
            sorted[i] =v;
        }
        Arrays.sort(sorted);
        int count =0;
        for(int v: sorted){
            if(map.containsKey(v)){
                continue;
            }
            map.put(v, count++);
        }

        for(int v: arr){
            bw.write(map.get(v)+&quot; &quot;);
        }
        bw.flush();
        bw.close();
    }</code></pre>
<ul>
<li><code>arr</code>, <code>map</code> : PQ 풀이와 역할 같음</li>
<li><code>sorted</code>: 원소 정렬 후 배열</li>
</ul>
<ol>
<li><code>arr</code>, <code>sorted</code> 배열에 원소 저장</li>
<li>Arrays.sort 함수를 이용해 sorted 배열 오름차순 정렬</li>
<li>오름차순대로 map에 해당 원소 value 저장 <code>(원리는 pq 풀이와 동일)</code></li>
<li>arr 배열 원소 순서대로 출력</li>
</ol>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/70736b9d-b778-44f6-aae9-453d71df4734/image.PNG" alt=""></p>
<p>확실히 차이 나죠? 
결론: 계산을 잘 하자, 그래두 시간 복잡도 생각해서 푼 건 잘했어</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_1260 DFS와 BFS]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N1260-DFS%EC%99%80-BFS</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N1260-DFS%EC%99%80-BFS</guid>
            <pubDate>Tue, 05 Nov 2024 14:50:13 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/9d44664a-95b2-4368-8565-20ed877fa65b/image.PNG" width=80% />

<Image src="https://velog.velcdn.com/images/0woy_/post/738cfacf-c42b-4cd4-b29e-b40cdb556915/image.PNG" width=80% />

<ul>
<li>문제 이름에서 알 수 있듯이 양방향 그래프가 주어지면 <code>DFS</code>, <code>BFS</code> 각각의 방법으로 방문하는 정점을 출력하면 된다.</li>
<li>단, 정점 방문 시 작은 숫자부터 방문 </li>
</ul>
<hr>
<h3 id="생각하기">생각하기</h3>
<p>솔직히 컴공 졸업했으면 이거 고민하면 안 된다~ <code>난 컴공 탈락</code></p>
<p>몇 개월 전만 해도 BFS, DFS 로직 바로 구현이 가능했는데, 오래 안 만졌다고 그새 까먹는 건 
반성 많이 해야 합니다. 오늘 제대로 공부했으니 그래두 ㄱㅊ아
<del>다음에 까먹으면 걍 죽으면 됨</del></p>
<p>개념을 알면 풀 수 있기에 생각할 게 따로 있는 건 아니고,
DFS, BFS를 설명하려 한다.</p>
<hr>
<h3 id="1-dfs">1. DFS</h3>
<p>DFS는 <strong>Depth-First Search</strong>의 약어로 <strong>깊이 우선 탐색</strong>이라고 한다. 
이름에서 어떤 친구인지 대충 유추가 가능하지만, 백문이불여일견</p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/5912e288-4f2b-4d7c-b42b-6423c379fbe2/image.dfs" alt=""></p>
<ol>
<li><code>1번</code> 부터 시작한다고 가정했을 때, 1번 정점과 연결 된 친구들 탐색 <code>(1번 방문 처리)</code></li>
<li><code>2번</code>, <code>3번</code>, <code>4번</code>이 연결 됐지만, 가장 작은 숫자를 탐색한다고 하면, <strong><code>2번</code>으로 이동</strong></li>
<li><code>2번</code> 정점과 연결된 친구들 탐색 <em><strong>(방문 처리가 되지 않은 정점만 탐색)</strong></em></li>
<li>이미 방문한 <code>1번</code>을 제하고,  <strong><code>4번</code>으로 이동</strong> <code>(2번 방문 처리)</code></li>
<li><code>4번</code> 정점과 연결된 친구들 탐색 </li>
<li>마찬가지로 이미 방문한 <code>1번</code>, <code>2번</code>을 제하고 <strong>3번으로 이동</strong> <code>(4번 방문처리)</code></li>
<li><code>3번</code> 정점과 연결된 다른 모든 정점이 모두 방문 됐으므로 종료.</li>
</ol>
<p>따라서 <code>1, 2, 4, 3</code> 순서로  DFS가 구현된다.
만일, 4번 정점과 연결된 5번이 있다면, 방문 순서는 <code>1, 2, 4, 3, 5</code>가 된다.</p>
<blockquote>
<p>즉, 현재 정점에서 이동할 수 있는 여러 정점이 있더라도,
다른 정점으로 계속 이동하고 난 후 다시 돌아와서 방문하는 개념이다.</p>
</blockquote>
<p>BFS와 같이 보면 더 쉬울 거다.</p>
<hr>
<h3 id="2-bfs">2. BFS</h3>
<p>BFS는 <strong>Breadth-First Search</strong>의 약어로** 너비 우선 탐색**이라고 한다. 
마찬가지로.. 같은 그림을 보여주고 설명해조야지</p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/64d060bc-5b31-49cc-8918-6db82ea5567a/image.bfs" alt=""></p>
<p>비슷하게 생겼지만, 설명을 보면 다르다.</p>
<ol>
<li><code>1번</code> 부터 시작한다고 가정했을 때, 1번 정점과 연결 된 친구들 탐색 <code>(1번 방문 처리)</code></li>
<li>작은 숫자부터 <code>2번</code>, <code>3번</code>, <code>4번</code> 방문할 예정 <code>(모두 방문 처리)</code></li>
<li><code>2번</code> 정점과 연결된 미방문 친구들 탐색</li>
<li><code>3번</code> 정점과 연결된 미방문 친구들 탐색 </li>
<li><code>4번</code> 정점과 연결된 미방문 친구들 탐색 </li>
<li>연결된 모든 정점을 방문했으므로 종료.</li>
</ol>
<p>따라서 <code>1, 2, 3, 4</code> 순서대로 BFS가 구현된다.
만일, 2번에 5번 정점이 연결된 경우,  <code>1, 2, 3, 4, 5</code> 순서가 된다.</p>
<blockquote>
<p>즉, 현재 정점과 연결된 정점들을 우선으로 탐색하는 개념이다.</p>
</blockquote>
<p>조금 이해가 되려나ㅎ..
중요한 건 위 로직을 코드로 구현하는 방법이니까 함 설명해 보겠다.</p>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main{
    static boolean[] visited;
    static StringBuilder sb;
    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 e = Integer.parseInt(st.nextToken());
        int start = Integer.parseInt(st.nextToken());

        Map&lt;Integer, PriorityQueue&lt;Integer&gt;&gt; map = new HashMap&lt;&gt;();
        visited = new boolean[n+1];
        sb  = new StringBuilder();

        while (e -- &gt;0){
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());

            PriorityQueue&lt;Integer&gt; list = map.getOrDefault(a, new PriorityQueue&lt;&gt;());
            list.add(b);
            map.put(a,list);

            list = map.getOrDefault(b, new PriorityQueue&lt;&gt;());
            list.add(a);
            map.put(b,list);
        }

        dfs(map, start);
        sb.append(&quot;\n&quot;);
        visited = new boolean[n+1];
        bfs(map,start);

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

    public static void dfs(Map&lt;Integer, PriorityQueue&lt;Integer&gt;&gt; map, int current){
        visited[current]=true;
        sb.append(current+&quot; &quot;);
        if(map.get(current) == null){
            return;
        }
        PriorityQueue&lt;Integer&gt; pq = new PriorityQueue&lt;&gt;(map.get(current));
        while(!pq.isEmpty()){
            int v = pq.poll();
            if(!visited[v]){
                dfs(map, v);
            }
        }
        return;
    }

    public static void bfs(Map&lt;Integer, PriorityQueue&lt;Integer&gt;&gt; map, int start){
        Queue&lt;Integer&gt; que = new ArrayDeque&lt;&gt;();
        que.add(start);
        visited[start]=true;
        while (!que.isEmpty()){
            int cur = que.poll();
            sb.append(cur+&quot; &quot;);
            if(map.get(cur)==null){
                continue;
            }
            PriorityQueue&lt;Integer&gt; pq = map.get(cur);
            while(!pq.isEmpty()){
                int v = pq.poll();
                if(!visited[v]){
                    visited[v]=true;
                    que.add(v);
                }
            }
        }
    }
}
</code></pre>
<p>함수별로 살펴보자</p>
<hr>
<h2 id="✍-부분-코드-설명">✍ 부분 코드 설명</h2>
<p><strong>Main 함수 - 입력 &amp; 변수 초기화</strong></p>
<pre><code class="language-java">public class Main{
    static boolean[] visited;
    static StringBuilder sb;
    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 e = Integer.parseInt(st.nextToken());
        int start = Integer.parseInt(st.nextToken());

        Map&lt;Integer, PriorityQueue&lt;Integer&gt;&gt; map = new HashMap&lt;&gt;();
        visited = new boolean[n+1];
        sb  = new StringBuilder();

        while (e -- &gt;0){
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());

            PriorityQueue&lt;Integer&gt; list = map.getOrDefault(a, new PriorityQueue&lt;&gt;());
            list.add(b);
            map.put(a,list);

            list = map.getOrDefault(b, new PriorityQueue&lt;&gt;());
            list.add(a);
            map.put(b,list);
        }

        dfs(map, start);
        sb.append(&quot;\n&quot;);
        visited = new boolean[n+1];
        bfs(map,start);

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

...
</code></pre>
<ul>
<li><code>n</code>: 정점 개수</li>
<li><code>e</code>: 간선 개수</li>
<li><code>start</code>: 탐색 시작 위치</li>
<li><code>map</code>: 정점간 연결을 담는 map (<code>key: 정점 value: 정점과 연결된 다른 정점 list</code>)</li>
<li><code>visited</code>: 정점의 방문 여부 확인</li>
</ul>
<p>그 간선 수만큼 연결 관계를 입력 받잖아요?
그런데 양방향 연결이니까, 들어오는 두 개의 정점이 서로의 <code>key</code>, <code>value</code> 둘 다 해조야 합니다.</p>
<blockquote>
<p>value 리스트로 <strong>우선순위 큐</strong>를 쓴 이유는 문제에서 정점 번호가 작은 순서대로 방문하라고 했기에 써줬구요.
ArrayList로 했다가, 하나 하나 다시 정렬하자니 머리 아프더라구요</p>
</blockquote>
<p>그렇게 입력을 받고난 후 <code>dfs</code>, <code>bfs</code> 함수를 호출합니다.</p>
<hr>
<p><strong>dfs 함수</strong></p>
<pre><code class="language-java">  public static void dfs(Map&lt;Integer, PriorityQueue&lt;Integer&gt;&gt; map, int current){
        visited[current]=true;
        sb.append(current+&quot; &quot;);
        if(map.get(current) == null){
            return;
        }
        PriorityQueue&lt;Integer&gt; pq = new PriorityQueue&lt;&gt;(map.get(current));
        while(!pq.isEmpty()){
            int v = pq.poll();
            if(!visited[v]){
                dfs(map, v);
            }
        }
        return;
    }
</code></pre>
<p><code>bfs</code>와 다른 점이 있다면, <em><strong>재귀 호출</strong></em> 로 이루어진다는 점?
왜냐 우린 깊이 깊이 들어가야 하니까..</p>
<ol>
<li>우선 current로 들어온 현재 정점을 <strong>방문 처리 하고 시작</strong>한다.</li>
<li>만일, current 정점과 연결된 다른 정점이 없는 경우 <code>return</code></li>
<li>현재 정점과 연결된 다른 정점들을 pq에 저장</li>
<li>pq에서 하나씩 빼서 <strong>방문하지 않은 노드</strong>가 있다면, 해당 노드를 <code>current</code> 로 dfs 호출</li>
<li>위 과정 반복</li>
</ol>
<hr>
<p><strong>bfs 함수</strong></p>
<pre><code class="language-java">    public static void bfs(Map&lt;Integer, PriorityQueue&lt;Integer&gt;&gt; map, int start){
        Queue&lt;Integer&gt; que = new ArrayDeque&lt;&gt;();
        que.add(start);
        visited[start]=true;
        while (!que.isEmpty()){
            int cur = que.poll();
            sb.append(cur+&quot; &quot;);
            if(map.get(cur)==null){
                continue;
            }
            PriorityQueue&lt;Integer&gt; pq = map.get(cur);
            while(!pq.isEmpty()){
                int v = pq.poll();
                if(!visited[v]){
                    visited[v]=true;
                    que.add(v);
                }
            }
        }
    }
</code></pre>
<p><code>bfs</code>는 <em><strong>선입선출</strong></em> 의 특징을 가진 큐를 가지고 구현합니다.</p>
<ol>
<li>que 선언 및, start 정점 삽입</li>
<li>start 정점을 방문 처리</li>
<li>que에서 앞에 있는 정점 빼내어 <code>cur</code> 변수에 저장</li>
<li>만일 cur 정점과 연결된 다른 정점이 없는 경우, que에서 다른 정점 빼내기</li>
<li>연결된 정점이 있는 경우, <strong>연결된 모든 정점을 que에 삽입 및 방문 처리</strong></li>
<li>que가 빌 때까지 위 과정 반복</li>
</ol>
<p>기억해야 할 점은 que에 넣으면서 방문 처리까지 해주는 거?
중복 삽입이 될 수 있기에..</p>
<blockquote>
<p>ex) 
<code>1</code> - <code>2</code> ,<code>4</code>
<code>2</code> - <code>3</code>, <code>4</code></p>
</blockquote>
<p>이렇게 연결 돼 있다고 하면 
1번 정점에서 4번 정점이 이미 연결돼 방문할 예정이므로
2번 정점에선 4번 정점을 무시해야합니다~</p>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/bdeec299-470f-46e1-8c28-d13de3030865/image.PNG" alt=""></p>
<p>까먹어서 나에게 실망했지만,, 
그만큼 중요한 개념이기때문에, 또 공부해서 리마인드 했으니 오히려 좋다.
그 나중에 응용 문제 많이 나오니까 또 까먹지 말자~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_17626 Four Squares]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N17626-Four-Squares</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N17626-Four-Squares</guid>
            <pubDate>Mon, 04 Nov 2024 14:58:57 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/e0526a39-c18f-4621-b57e-215470127340/image.PNG" width=100% />

<Image src="https://velog.velcdn.com/images/0woy_/post/d478a884-2c72-4b49-80b7-ecb6de60d7e7/image.PNG" width=80% />

<p>모든 자연수는 4개 이하의 제곱수의 합으로 표현 가능.
어떤 자연수는 해당 제곱수의 합이 여러개일 수도 있다.</p>
<ul>
<li>어떤 자연수가 주어질 때, 표현되는 제곱수가 <em><strong>가장 적은 개수</strong></em> 찾아라.<blockquote>
<p>ex) N = 26
<code>5²+1²</code> : 2개 
<code>4²+3²+1²</code>: 3개
답: 2개</p>
</blockquote>
</li>
</ul>
<hr>
<h3 id="생각하기">생각하기</h3>
<p>틀렸어요. 답 봤구요.. </p>
<p>처음엔 그리디인줄 알고 풀었는데 그리디가 아니었다.
내가 접근한 방식을 잠깐 설명하자면,</p>
<blockquote>
<ol>
<li>처음 주어지는 자연수<code>(이하 n)</code>의 제곱근<code>(이하 sqrt)</code> 찾기 (소수점 내림).</li>
<li>n에서 sqrt² 뺀 값을 n에 저장. (n = n-sqrt²)</li>
</ol>
</blockquote>
<p>n이 0이 될 때까지 위 과정을 반복.</p>
<p>이렇게 풀고 제출했는데, 46%에서 틀렸다.
반례는 <code>27532</code> 자연수가 들어왔을 때 나왔다.</p>
<p> 답은  <code>27532 = 26²+ 66² + 150²</code>로 3개가 나와야 한다.
 나의 로직은 아래와 같은 계산 과정으로 제곱수 개수를 구한다.</p>
<p>|계산 과정|계산 결과||
|---|---|
|√27532| = 165.92...|
|27532 - 165²| = 307|
|√307| = 17.52..|
|307 -17²| = 18|
|√18| = 4.24...|
|18 - 4²| = 2|
|2| = 1²+1²|
<code>27532 = 1²+1²+4²+17²+165²</code> 로 가장 작은 개수도 아닐 뿐더러 4개를 초과한다. ㅋㅋ
그래서 가장 큰 값만 취하는 그리디 방식이 답이 아님을 알게 됐다...</p>
<p>그리디만 철썩 같이 믿었는데.. ㅜ
아무튼 다른 분들의 풀이를 봤다.</p>
<hr>
<h3 id="접근-방식">접근 방식</h3>
<p>브루트 포스로 풀 수도 있고, dp로 풀 수 있다고 한다.
dp가 훨씬 효율적이기에 dp 풀이를 참고했다.</p>
<table>
<thead>
<tr>
<th align="center">n</th>
<th align="center">최소 제곱근 개수</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1</td>
<td align="center">1개 (1²)</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">2개 (1²+1²)</td>
</tr>
<tr>
<td align="center">3</td>
<td align="center">3개 (1²+1²+1²)</td>
</tr>
<tr>
<td align="center">4</td>
<td align="center"><strong>1개 (2²)</strong></td>
</tr>
<tr>
<td align="center">5</td>
<td align="center">2개 (2²+1²)</td>
</tr>
</tbody></table>
<p>앞서 말했듯 n은 √n 이하의 제곱수들의 합으로 구성이 된다.</p>
<p>n이 4인 경우를 예로들자.</p>
<ul>
<li>1²+1²+1²+1²</li>
<li>2²</li>
</ul>
<p>위 처럼 표현이 가능한데, 우리는 2²을 취해야 한다.
그런데,<code>1²+1²+1²+1²</code>은 3의 제곱수합에서 <code>1²</code>을 더한 값이다.</p>
<p>그러면 1부터 x²&lt;=n일 때까지 x를 증가해 나가면서 가장 적은 개수를 저장하면 되나?
라고 생각할 수 있는데, 처음 보면 뭔 소리인지 싶다.</p>
<blockquote>
<p>dp 배열에 해당 자연수에 대한 최소 값들이 저장돼 있다고 가정해 보자.
n=4,
<code>idx = 4-1² =&gt; dp[3]</code>
<code>idx = 4-2² =&gt; dp[0]</code></p>
</blockquote>
<p>dp[3]과 dp[0]중 가장 적은 친구를 취해서 +1 하면,
그게 4를 만들 수 있는 가장 적은 개수라는 말이다.</p>
<p>3을 만들 수 있는 최소 제곱수 개수는 3개이고(1²+1²+1²),
0을 만들 수 있는 최소 제곱수는 0개이므로 우리는 0을 택한다.
그렇게 하면 <code>dp[4]=1</code>이므로 옳게 나온다.</p>
<blockquote>
<p>16을 예로 들면, 아래 경우 중 최소 값을 취하여 저장한다.</p>
</blockquote>
<p>16-1² = 15. <code>(dp[15])</code>+1개
16-2² = 12 <code>(dp[12])</code>+1개
16-3² = 7 <code>(dp[7])</code>+1개
16-4² = 0 <code>(dp[0])</code>+1개</p>
<p>이해가 됐으면 좋겠다ㅠ</p>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main{
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;
        for(int i=2;i&lt;=n;i++){
            int min = Integer.MAX_VALUE;
            for(int j=1;j*j&lt;=i;j++){
                min = Math.min(min,dp[i-j*j]);
            }
            dp[i] = min+1;
        }
        System.out.println(dp[n]);
    }
}</code></pre>
<ol>
<li>최소 개수를 저장하는 dp 배열을 선언 &amp; 초기화</li>
<li>dp[0]과 dp[1]에는 값 미리 저장</li>
<li>최소 개수 저장<ul>
<li>i는 2부터 n까지</li>
<li>j는 1부터 j*j &lt;= i 까지 반복.</li>
<li>제일 적은 개수를 min에 저장</li>
<li>최종적으로 찾은 <code>min</code>에 1을 더한 값을 <code>dp[i]</code>에 저장</li>
</ul>
</li>
<li><code>dp[n]</code> 출력</li>
</ol>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/a2313c15-6605-4f7b-afb7-d49f78a8e0ec/image.PNG" alt=""></p>
<p>진짜 알다가도 모를.. 아니 그냥 모르는 dp...
해당 문제 북마크 해두고 한 달 뒤에나 한 번 더 풀어봐야겠다.
이번 문제 설명하기 넘 어려웠다. 하지만 최선은 다했다 ㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_2630 색종이 만들기]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N2630-%EC%83%89%EC%A2%85%EC%9D%B4-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N2630-%EC%83%89%EC%A2%85%EC%9D%B4-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 03 Nov 2024 10:52:14 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/70213a5a-48ae-4877-bf38-7b9824196962/image.PNG" width=100% />

<Image src="https://velog.velcdn.com/images/0woy_/post/9d7a987b-ac30-47f7-bff2-fe5275dc08a4/image.PNG" width=100% />

<Image src="https://velog.velcdn.com/images/0woy_/post/28627ede-6aa1-4cda-8403-80485d608347/image.PNG" width=100% />

<ul>
<li>문제는 위에서 자세히 설명했으므로 설명은 패스</li>
<li>그 정사각형인데, 숫자가 같아야 해요.</li>
</ul>
<hr>
<h3 id="생각하기">생각하기</h3>
<Img src="https://velog.velcdn.com/images/0woy_/post/da52a4b5-d22b-430e-ba30-a965bc95559d/image.jpg" width=70% />

<p>예전에 풀었던 문제~ 그래두 확실히 하기 위해서 끄적거렸음</p>
<p>물구나무 서서도 재귀 문제라 쉽게 풀리겠지 생각했지만
내가 물구나무에 소질이 없어서 1시간 걸렸다.</p>
<h3 id="접근-방식">접근 방식</h3>
<p>먼저, 순회를 시작하기 전에 맨 처음 원소를 flag 변수로 잡고 시작
순회 하다가 flag랑 다른 애 나오면, 4분할을 해야 하잖아요?</p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/1f3d01ef-4a67-4c64-bceb-2e52106f97b9/image.adf" alt=""></p>
<p>위 처럼 길이가 4인 정사각형이 주어져있다고 했을 때,
현재 <code>flag (1)</code>과 다른 <code>0</code>을 만나면 우측 그림처럼 4분할해서 4번의 재탐색이 필요함.</p>
<p>4분할의 기점인 위치를 살펴보면,</p>
<table>
<thead>
<tr>
<th align="center">위치</th>
<th align="center">좌표</th>
</tr>
</thead>
<tbody><tr>
<td align="center">좌상단</td>
<td align="center">(0,0)</td>
</tr>
<tr>
<td align="center">우상단</td>
<td align="center">(0,2)</td>
</tr>
<tr>
<td align="center">좌하단</td>
<td align="center">(2,0)</td>
</tr>
<tr>
<td align="center">우하단</td>
<td align="center">(2,2)</td>
</tr>
<tr>
<td align="center">시작점이 <code>(len=2)</code>을 따른다는 사실을 알 수 있다.</td>
<td align="center"></td>
</tr>
<tr>
<td align="center">이런 접근 방식을 가지고 코드를 짜면 됨</td>
<td align="center"></td>
</tr>
</tbody></table>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class N_2630 {
    private static int res[];
    public static void recur(int [][] map,  int row, int col, int len){
        int flag =  map[row][col];
        if(len == 1){
            res[flag]++;
            return;
        }
        for(int i=row; i&lt;row+len;i++){
            for(int j=col;j&lt;col+len;j++){
                if(flag != map[i][j]){
                    int half = len/2;
                    recur(map, row,col, half);
                    recur(map, row,col+half, half);
                    recur(map, row+half,col, half);
                    recur(map, row+half,col+half, half);
                    return;
                }
            }
        }
        res[flag]++;
        return;
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());

        int [][] map  = new int[n][n];
        res = new int[2];
        for(int i=0;i&lt;map.length;i++){
            StringTokenizer st  = new StringTokenizer(br.readLine());
            for(int j=0;j&lt;map[0].length;j++){
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        recur(map, 0,0,n);
        System.out.println(res[0]+&quot; &quot;+res[1]);
    }
}
</code></pre>
<p>하나씩 살펴봅시다.</p>
<hr>
<h2 id="✍-부분-코드-설명">✍ 부분 코드 설명</h2>
<p><strong>입력</strong></p>
<pre><code class="language-java">public class N_2630 {
    private static int res[];

    ...

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

        int [][] map  = new int[n][n];
        res = new int[2];
        for(int i=0;i&lt;map.length;i++){
            StringTokenizer st  = new StringTokenizer(br.readLine());
            for(int j=0;j&lt;map[0].length;j++){
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        recur(map, 0,0,n);
        System.out.println(res[0]+&quot; &quot;+res[1]);
    }
}
</code></pre>
<ul>
<li><code>n</code>: 초기에 주어지는 종이 크기</li>
<li><code>map</code>: 배열 저장</li>
<li><code>res</code>: 색종이 개수 저장</li>
</ul>
<p>2차원 배열인 map에 입력으로 주어지는 초기 색종이 값 저장
recur 함수를 호출하여 탐색</p>
<hr>
<p><strong>recur 함수</strong></p>
<pre><code class="language-java"> public class N_2630 {
    private static int res[];
    public static void recur(int [][] map,  int row, int col, int len){
        int flag =  map[row][col];
        if(len == 1){
            res[flag]++;
            return;
        }
        for(int i=row; i&lt;row+len;i++){
            for(int j=col;j&lt;col+len;j++){
                if(flag != map[i][j]){
                    int half = len/2;
                    recur(map, row,col, half);
                    recur(map, row,col+half, half);
                    recur(map, row+half,col, half);
                    recur(map, row+half,col+half, half);
                    return;
                }
            }
        }
        res[flag]++;
        return;
    }

    ...
</code></pre>
<p>recur 함수는 <code>row</code>, <code>col</code> 매개변수에 현재 종이의 flag가 될 <strong>시작점</strong>을 받고,
len에는 현재 <strong>종이의 길이</strong>를 받음</p>
<p>만일 len이 1인 경우에는 한 칸만 탐색하면 되기때문에, 
res 배열의 <em><strong>flag 위치의 값을 1을 증가.</strong></em></p>
<p>그 외의 경우 배열을 순회하며 flag와 다른 값을 마추진 경우 4분할을 다시 시작합니다.</p>
<blockquote>
<p>여기서  row, col 값을 넘겨줄 때, <code>현재 인덱스  (i,j)</code> 를 넘겨주는 실수를 했는데,
 그렇게 넘겨주면 내가 원하는 곳이 아닌 엉뚱한 곳을 시작점으로 4분할 하게 되므로.. 
 꼭 <code>row, col</code>을 넘겨줘야한다.</p>
<p>넘 당연한 소리 같지만, 나는 이게 발목을 좀 잡았다.</p>
</blockquote>
<p>나는 이 코드의 핵심이  <code>return</code>이라고 생각한다.</p>
<p>왜냐하면 처음에 <code>return</code>이 아닌 <code>break</code>를 썼다가 색종이 개수가 280개인가 웃기지도 않은 숫자가 나왔다.</p>
<p>이중반복문이기 때문에, break를 써도 상위 반복문<code>(현재 i)</code>은 그대로 진행이 돼서
우리가 앞전에 4분할해서 탐색한 부분을 <em><strong>중복 탐색</strong></em> 하기 때문이다.</p>
<p>우리는 한 번 안 맞으면 더 안 볼 거잖아요? 
그러려면 <strong>함수를 탈출하는 return을 작성</strong>해줘야 원하는 로직으로 프로그램이 동작합니다.</p>
<blockquote>
<p>궁금하면, 디버깅 해보면 됨</p>
</blockquote>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/36c7bc13-2c48-4e79-934b-e0db93b60119/image.PNG" alt=""></p>
<p><a href="https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N9375-%ED%8C%A8%EC%85%98%EC%99%95-%EC%8B%A0%ED%95%B4%EB%B9%88">해빈이 문제</a>에서 조합이 나의 약점이라고 했는데 조합은 재귀를 활용한 문제라서 
정확히는 재귀가 나의 약점이다.</p>
<p>이번 문제와 비슷한 문제를 3~4번 정도 풀었었는데, 1시간이나 걸리면 안됐다.
조금 더 생각하고 풀어서 돌아가는 일이 없도록 해야겠다.</p>
<p>그래도 풀었죠? ㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_11726 2xn 타일링]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N11726-2xn-%ED%83%80%EC%9D%BC%EB%A7%81</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N11726-2xn-%ED%83%80%EC%9D%BC%EB%A7%81</guid>
            <pubDate>Thu, 31 Oct 2024 04:33:30 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/554bc74b-d4a1-4060-855a-14cbf0d1d7eb/image.PNG" width=80% />

<Image src="https://velog.velcdn.com/images/0woy_/post/2c2be93e-1da7-475c-a275-5ecd47734b1c/image.PNG" width=80% />

<ul>
<li>세로 길이가 2로 고정된 타일에 가로 길이 n이 주어지면,
<code>2x1</code> (이하 세로 타일), <code>1x2</code> (이하 가로 타일) 타일로 채우는 방법의 수 구하기.</li>
</ul>
<p>예전에 얘랑 씨름하다가 결국 답 봤던 것 같은데 마법처럼 어떻게 풀었는지 기억이 안 나서 새마음 새출발했다.</p>
<hr>
<h3 id="생각하기">생각하기</h3>
<Img src="https://velog.velcdn.com/images/0woy_/post/9d143075-abed-4d66-b01f-0bd3f1c127d0/image.jpg" width=70% />

<p>규칙을 찾기 위해 끄적인 노트를 가져왔다..</p>
<p>파블로프의 개처럼 <code>경우의수 = dp</code> 라고 떠올리게 돼서 냅다 규칙을 찾았다.
규칙이 보일랑 말랑하지만 딱 떨어지지 않아서 예제를 쳐다봤는데 익숙한 숫자 <strong><code>55</code></strong>가 보였다.
맞다. 이 숫자는 _<strong>피보나치 수열에서 10번째로 오는 수</strong>_다.</p>
<blockquote>
<p>그래서 피보나치로 풀었음.</p>
</blockquote>
<p>그치만, 예제를 보고 힌트를 얻어서 풀었기에 이렇게 넘어가면 안 된다.</p>
<h3 id="접근-방식">접근 방식</h3>
<Img src="https://velog.velcdn.com/images/0woy_/post/5e063c19-ac29-4451-abac-3a77d4eda3ad/image.%25EA%25B7%259C%25EC%25B9%2599" width=80% />

<p>n이 3인 경우, (2x3)타일링을 하는 경우의 수는 위 그림에 나와있다.
자세히 보니 <code>2x2 타일링</code>에서 세로타일을 오른쪽에 붙이고, <code>2x1 타일링</code>에서 가로 타일을 갖다 붙인 모양새다.</p>
<p>그렇다면, 가로 길이가 <code>n인 타일링의 방법의 수</code> = <code>n-1 타일링 방법 + n-2 타일링 방법</code>임을 알 수 있겠다.</p>
<p>나는 처음 규칙을 찾을 때 n-2 타일링 방법을 이용하는 게 어려웠다.
왜냐하면 n-2타일링에서 세로타일 2개를 붙여서 n 타일링을 만들 수 있는데, 
그렇게 하면 <strong>n-1 타일링에서 세로타일을 붙인 경우와 중복</strong>되는 부분이 있었기 때문이다.</p>
<blockquote>
<p>다시 생각해 보니, n-2에서 세로타일을 붙인 경우는 n-1에서 세로타일을 붙인 경우에 <strong>모두 존재</strong>하기 때문에 신경쓸 필요가 없겠다.</p>
</blockquote>
<p>세로 지옥에서 빨리 벗어났다면 피보나치 힌트를 보지 않고 풀 수 있지 않았을까 싶다.</p>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">package DP;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class N_11726 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int [] dp = new int[n+2];
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i&lt;=n;i++){
            dp[i]=dp[i-2]%10007+dp[i-1]%10007;
        }
        System.out.println(dp[n]);
    }
}
</code></pre>
<p>장황한 설명에 비해 코드는 간결하다.
피보나치 수열을 n번째까지 dp 배열에 저장하고, n번째 수를 반환하면 된다.</p>
<blockquote>
<p>문제에서 <code>10,007</code>로 나눈 나머지 값을 반환하라 했으므로 계산을 하면서 적용한다.</p>
</blockquote>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/590c169c-6632-4ada-a7c9-48153635909a/image.PNG" alt=""></p>
<p>생각보다 빨리, 생각보다 쉽게 풀었다.
성장했나부다..
항상 dp에게 떨면서 눈물만 흘리던 내가 아니야 흥</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_9375 패션왕 신해빈]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N9375-%ED%8C%A8%EC%85%98%EC%99%95-%EC%8B%A0%ED%95%B4%EB%B9%88</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N9375-%ED%8C%A8%EC%85%98%EC%99%95-%EC%8B%A0%ED%95%B4%EB%B9%88</guid>
            <pubDate>Tue, 29 Oct 2024 04:53:47 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/26a2d842-e3da-4a8d-96e1-1ed1e032a404/image.PNG" width=80% />

<Image src="https://velog.velcdn.com/images/0woy_/post/843eff5e-a63f-42c9-9d1f-29a87234a58a/image.PNG" width=80% />

<ul>
<li>해빈이가 옷 하나만 걸쳐도 되니까 나체만 아닌 모든 경우의 수</li>
<li>종류별로 한 개만 입을 수 있음 <code>ex) 모자 두 개 안 됨</code></li>
</ul>
<hr>
<h3 id="생각하기">생각하기</h3>
<p>내가 제일 약한 분류인 조합 문제이다.
그런데 걸치는 옷의 조합을 나열하는 게 아닌 단지 <strong>경우의 수만 구하는</strong> 거라서 수학으로 풀 수 있지 않을까 싶었다. <code>(재귀 짜다가 번뜩 생각남)</code>
하지만, 기껏 짜놓은 재귀가 아깝기도 했고.. 시간도 많이 지나서 그냥 제출하고 25%에서 실패해서 답 봤다. </p>
<blockquote>
<p>풀이 방법은 결국 수학이지롱</p>
</blockquote>
<h3 id="접근-방식">접근 방식</h3>
<p><strong>headgear</strong> - <code>hat</code>, <code>turban</code>
<strong>eyewear</strong> -  <code>sunglasses</code></p>
<p>위와 같이 예제가 주어졌지만, 문제에서는 해빈이가 나체만 아니면 된다고 했다.
즉, 우리는 아래와 같이 _<strong>아무것도 안 입는 선택지</strong>_도 분류에 넣어주고 경우의 수를 구해야 한다.</p>
<p><strong>headgear</strong> - <code>hat</code>, <code>turban</code>,<code>NONE</code>
<strong>eyewear</strong> -  <code>sunglasses</code>, <code>NONE</code></p>
<blockquote>
<p>그러면, 우리가 구할 수 있는 경우의 수는
headgear 종류에서 1개  * eyewear 종류에서 1개
 = <strong>₃C₁X  ₂C₁ = 3 X 2 = 6</strong></p>
</blockquote>
<p> 이때, 두 종류 모두 NONE을 고르는 경우가 포함돼 있으므로 <em><strong>결과값에서 1을 빼주면</strong></em> 답이 된다.</p>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">import java.io.*;
import java.util.*;

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

        while(tc-- &gt;0){
            Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
            int count  = Integer.parseInt(br.readLine());
            while(count -- &gt;0){
                StringTokenizer st = new StringTokenizer(br.readLine());
                st.nextToken();
                String sort = st.nextToken();
                map.put(sort, map.getOrDefault(sort,1)+1);
            }
            long result = 1;
            for(String key: map.keySet()){
                result*=map.get(key);
            }
            result-=1;
            bw.write(result+&quot;\n&quot;);
        }
        bw.flush();
        bw.close();
    }
}
</code></pre>
<hr>
<h2 id="✍-부분-코드-설명">✍ 부분 코드 설명</h2>
<p><strong>입력</strong></p>
<pre><code class="language-java">public class Main{
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        int tc = Integer.parseInt(br.readLine());

        while(tc-- &gt;0){
            Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
            int count  = Integer.parseInt(br.readLine());
             while(count -- &gt;0){
                StringTokenizer st = new StringTokenizer(br.readLine());
                st.nextToken();
                String sort = st.nextToken();
                map.put(sort, map.getOrDefault(sort,1)+1);
            }
        ...</code></pre>
<ul>
<li>Map: key에는 옷의 종류, value에는 해당 종류의 옷 개수를 넣어준다.</li>
</ul>
<hr>
<p><strong>경우의 수 구하기</strong></p>
<pre><code class="language-java">     long result = 1;
            for(String key: map.keySet()){
                result*=map.get(key);
            }
            result-=1;
            bw.write(result+&quot;\n&quot;);
        }
        bw.flush();
        bw.close();
    }
}</code></pre>
<ul>
<li>map을 순회 하면서 구한 경우의 수를 result 변수에 삽입한다.</li>
<li>나체인 경우의 수가 포함돼 있으니 result에서 1을 제한다.</li>
</ul>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/6744bfa1-32f5-4596-9e97-2bb3d4e84cf4/image.PNG" alt=""></p>
<p>조합만 나오면 벌벌 떤다.
이번 문제는 나열하는 게 아니기 때문에 수학적으로 접근하여 푸는 거였지만
나열하라는 문제가 나오는 걸 대비해서 외우든가 알아도야겠다.</p>
<hr>
<h2 id="📖-참고">📖 참고</h2>
<p><a href="https://st-lab.tistory.com/164">Stranger&#39;s LAB - [백준] 9375번 : 패션왕 신해빈</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준-자바] N_2606 바이러스]]></title>
            <link>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N2606-%EB%B0%94%EC%9D%B4%EB%9F%AC%EC%8A%A4</link>
            <guid>https://velog.io/@0woy_/%EB%B0%B1%EC%A4%80-%EC%9E%90%EB%B0%94-N2606-%EB%B0%94%EC%9D%B4%EB%9F%AC%EC%8A%A4</guid>
            <pubDate>Sun, 27 Oct 2024 10:53:23 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Image src="https://velog.velcdn.com/images/0woy_/post/16280dcd-c7f6-476e-9aa0-a95b813ff855/image.PNG" width=80% />

<Image src="https://velog.velcdn.com/images/0woy_/post/66d15e78-c380-4d3a-bf9c-b2b88dad8a80/image.PNG" width=80% />

<ul>
<li>양방향 연결된 네트워크 쌍이 주어짐</li>
<li>1번 컴퓨터를 시작으로 바이러스에 감염된 컴퓨터 개수 찾기</li>
</ul>
<hr>
<h4 id="생각하기">생각하기</h4>
<p>Map을 이용해서 풀면 되겠다고 문제 보고 생각이 들었다.
key에는 컴퓨터 번호, value 값으로 연결된 컴퓨터 list를 넣으면 되고..
<code>key값에 있는 친구들 빼면서 개수 세면 되겠지?</code> 했지만 5%에서 걍 틀리기</p>
<p>양방향인 거 알면서 양방향으로 구현하지 않은 내 잘못이다.
<code>1 2</code> 이렇게 들어오면,  <code>1-&gt;2</code>, <code>2-&gt;1</code> 모두 구현해 주고 풀어야한다.</p>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">package DFS_BFS;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class N_2606 {
    public static void insert(Map&lt;Integer, List&lt;Integer&gt;&gt; map, int s, int e){
        if(!map.containsKey(s)){
            map.put(s, new ArrayList&lt;&gt;());
        }
        if(!map.containsKey(e)) {
            map.put(e, new ArrayList&lt;&gt;());
        }
            map.get(s).add(e);
            map.get(e).add(s);
    }

    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 con = Integer.parseInt(br.readLine());
        boolean [] visited = new boolean[n+1];
        Map&lt;Integer, List&lt;Integer&gt;&gt; map = new HashMap&lt;&gt;();
        while (con &gt; 0) {
            st = new StringTokenizer(br.readLine());
            int s = Integer.parseInt(st.nextToken());
            int e = Integer.parseInt(st.nextToken());
            insert(map,s,e);
            con--;
        }
        int virus = 0;
        Queue&lt;Integer&gt; que = new ArrayDeque&lt;&gt;();
        que.add(1);
        visited[1]=true;
        while(!que.isEmpty()){
            int key = que.poll();
            if(!map.containsKey(key)){continue;}
            for(int i: map.get(key)){
                if(visited[i]){
                    continue;
                }
                visited[i] = true;
                que.add(i);
                virus++;
            }
        }
        System.out.println(virus);
    }
}
</code></pre>
<hr>
<h2 id="✍-부분-코드-설명">✍ 부분 코드 설명</h2>
<p><strong>입력</strong></p>
<pre><code class="language-java"> 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 con = Integer.parseInt(br.readLine());
        boolean [] visited = new boolean[n+1];
        Map&lt;Integer, List&lt;Integer&gt;&gt; map = new HashMap&lt;&gt;();

        while (con &gt; 0) {
            st = new StringTokenizer(br.readLine());
            int s = Integer.parseInt(st.nextToken());
            int e = Integer.parseInt(st.nextToken());
            insert(map,s,e);
            con--;
        }
        ...</code></pre>
<ul>
<li>n : 전체 컴퓨터 수</li>
<li>con: 네트워크 쌍</li>
<li>visited: 컴퓨터 방문 처리 배열</li>
<li>Map: 네트워크 쌍 저장</li>
</ul>
<hr>
<p><strong>insert  함수</strong></p>
<pre><code class="language-java">public static void insert(Map&lt;Integer, List&lt;Integer&gt;&gt; map, int s, int e){
        if(!map.containsKey(s)){
            map.put(s, new ArrayList&lt;&gt;());
        }
        if(!map.containsKey(e)) {
            map.put(e, new ArrayList&lt;&gt;());
        }
            map.get(s).add(e);
            map.get(e).add(s);
    }</code></pre>
<ul>
<li>매개변수 s 와 e로 들어온 컴퓨터 둘 다 key, value 값으로 하여  map에 저장한다.</li>
</ul>
<hr>
<p>** virus 카운트**</p>
<pre><code class="language-java">    ...    
     int virus = 0;
    Queue&lt;Integer&gt; que = new ArrayDeque&lt;&gt;();
    que.add(1);
    visited[1]=true;
    while(!que.isEmpty()){
        int key = que.poll();
        if(!map.containsKey(key)){
            continue;
        }
        for(int i: map.get(key)){
               if(visited[i]){
                continue;
            }
            visited[i] = true;
            que.add(i);
            virus++;
        }
    }
    System.out.println(virus);
}
</code></pre>
<ul>
<li>que: 다음 key 값이 될 컴퓨터  (바이러스 감염됨)</li>
</ul>
<p>문제에서 1번 컴퓨터가 숙주 컴퓨터라고 했으니, que에 1을 넣고 방문처리를 해주면서 시작한다.</p>
<p>map에 현재 1번에 들어있는 list를 순회한다.
해당 컴퓨터를 이미 방문한 경우는 건너뛰고, 그렇지 않은 경우 que에 삽입 &amp; 방문처리 한다.
<code>virus</code> 변수를 1씩 증가한다.</p>
<p>que에 남아 있는 원소가 없을 때까지 반복하고, virus를 반환하며 종료한다.</p>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/88a990cd-7d1d-4f3b-a24c-21a5a3098ce6/image.PNG" alt=""></p>
<p>양방향인 걸 알았는데 양방향으로 구현을 안 해서 오래 걸렸다.
그냥 쉽게 갈걸... .. 
그래도 풀어서 기분 조타</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[LeetCode-자바] N_15 3Sum]]></title>
            <link>https://velog.io/@0woy_/LeetCode-%EC%9E%90%EB%B0%94-N15-3Sum</link>
            <guid>https://velog.io/@0woy_/LeetCode-%EC%9E%90%EB%B0%94-N15-3Sum</guid>
            <pubDate>Wed, 09 Oct 2024 05:03:16 GMT</pubDate>
            <description><![CDATA[<h2 id="📜-문제">📜 문제</h2>
<Img src="https://velog.velcdn.com/images/0woy_/post/1b3d8f8b-a0b0-4f80-8ef1-61ec50ad20dd/image.PNG" width=100% />


<p>배열 내의 3개의 원소를 더한 값이 0이 되는 모든 경우의 수 찾기
-&gt; 중복 경우의 수는 제외~</p>
<h3 id="생각하기">생각하기</h3>
<p>배열을 정렬하고, 양수와 음수를 나눠서 풀어야 한다고 생각했다.
그래서 이진탐색으로 양수와 음수 경계의 인덱스를 먼저 찾고,
<code>List&lt;Integer&gt; plus</code> <code>List&lt;Integer&gt; minus</code>에 양수, 음수 원소들을 저장했다.</p>
<p>합이 0이 되는 경우</p>
<ul>
<li>양수 2개+음수 1개 = 0</li>
<li>음수 2개 + 양수 1개 = 0</li>
<li>양수 1개 + 음수 1개 + 0 = 0 (배열에 0이 존재 하는 경우,)</li>
</ul>
<p>같이 3가지 경우 중 하나라도 만족하면 답이 된다.</p>
<p>막상 나누고 보니, 위 세 조건을 구현하는 게 막막했다.
30분 동안 씨름 후 답을 봤다.</p>
<p>결론부터 말하자면, </p>
<blockquote>
<p>숫자 1개를 고정하고, 남은 원소들을 투 포인터를 사용해 접근하는 방식이었다.</p>
</blockquote>
<hr>
<h2 id="🍳-전체-소스-코드">🍳 전체 소스 코드</h2>
<pre><code class="language-java">class Solution {

   public static int findIdx(int[] nums){
        int start = 0;
        int end = nums.length-1;
        while(start&lt;=end){
            int half = (start+end)/2;
            if(nums[half]==0){ return half;}
            else if(nums[half]&lt;0){
                start =  half+1;
            }
            else{
                end = half-1;
            }
        }
        return start;
    }
    public List&lt;List&lt;Integer&gt;&gt; threeSum(int[] nums) {

           Arrays.sort(nums);
        List&lt;List&lt;Integer&gt;&gt; res = new ArrayList&lt;&gt;();

        if(nums[0]&gt;0 || nums[nums.length-1]&lt;0){
            return res;
        } 

        int lastIdx = findIdx(nums);
        if(lastIdx &lt;0) lastIdx++;
        for(int i=0;i&lt;=lastIdx;i++){
            if(i&gt;0 &amp;&amp; nums[i]==nums[i-1]){
                continue;
            }
            int j = i+1;
            int k = nums.length-1;
            while(j&lt;k){
                int total  = nums[i]+nums[j]+nums[k];
                if(total&lt;0){
                    j++;
                }else if( total&gt;0){
                    k--;
                }else{
                    res.add(Arrays.asList(nums[i],nums[j],nums[k]));
                    j++;
                    while(nums[j]==nums[j-1] &amp;&amp; j&lt;k){
                        j++;
                    }
                }
            }
        }
        return res;
    }
}
</code></pre>
<hr>
<h2 id="✍-부분-코드-설명">✍ 부분 코드 설명</h2>
<h3 id="1-변수-선언">1. 변수 선언</h3>
<pre><code class="language-java">   public List&lt;List&lt;Integer&gt;&gt; threeSum(int[] nums) {
          Arrays.sort(nums);
        List&lt;List&lt;Integer&gt;&gt; res = new ArrayList&lt;&gt;();

        if(nums[0]&gt;0 || nums[nums.length-1]&lt;0){
            return res;
        } 

        int lastIdx = findIdx(nums);
        if(lastIdx &lt;0) lastIdx++;

        ...
</code></pre>
<ul>
<li><code>res</code>: 경우의 수를 저장하는  리스트</li>
<li><code>lastIdx</code>: 첫 양수 인덱스 저장 변수</li>
</ul>
<ol>
<li>nums 배열 정렬</li>
<li>반환할 res 변수 선언</li>
<li>만일 배열 내에 <strong>음수 혹은 양수가 없다면</strong> 답이 존재할 수 없으므로 종료</li>
<li><code>findIdx</code> 함수를 호출 하여, 첫 양수 인덱스를 구한다.</li>
</ol>
<hr>
<h3 id="2-findidx-함수">2. findIdx 함수</h3>
<pre><code class="language-java">  public static int findIdx(int[] nums){
        int start = 0;
        int end = nums.length-1;
        while(start&lt;=end){
            int half = (start+end)/2;
            if(nums[half]==0){ return half;}
            else if(nums[half]&lt;0){
                start =  half+1;
            }
            else{
                end = half-1;
            }
        }
        return start;
    }</code></pre>
<p>이진 탐색을 이용하여 양수 시작점 찾기. (0인 경우 0 인덱스 반환)</p>
<hr>
<h3 id="3-변수-선언">3. 변수 선언</h3>
<pre><code class="language-java">    ...
for(int i=0;i&lt;=lastIdx;i++){
    if(i&gt;0 &amp;&amp; nums[i]==nums[i-1]){
        continue;
    }
    int j = i+1;
    int k = nums.length-1;
    while(j&lt;k){
        int total  = nums[i]+nums[j]+nums[k];
        if(total&lt;0){
            j++;
        }
        else if(total&gt;0){
            k--;
        }
        else{
            res.add(Arrays.asList(nums[i],nums[j],nums[k]));
            j++;
            while(nums[j]==nums[j-1] &amp;&amp; j&lt;k){
                j++;
            }    // while
        }    // else
    }    // while
}    // for
    return res;
} // threeSum</code></pre>
<ul>
<li><code>i</code>: 고정수의 인덱스.</li>
<li><code>j</code>: i+1 ~ k까지</li>
<li><code>k</code>: 배열의 끝 ~ j까지</li>
</ul>
<p>중복된 경우를 허용하지 않았으므로, 중복값이 있다면 진행하지 않고 다음 원소로 넘어간다.</p>
<Img src="https://velog.velcdn.com/images/0woy_/post/299da73b-104c-474c-8e21-a23bb32f1817/image.1" width=90% />

<Img src="https://velog.velcdn.com/images/0woy_/post/95613bc3-4f64-4d49-8197-d3a20a0a434d/image.2" width=75% />

<p>끗!</p>
<hr>
<h2 id="✨-결과">✨ 결과</h2>
<p><img src="https://velog.velcdn.com/images/0woy_/post/5ec18722-61db-4595-a9ca-2707b5fa9521/image.PNG" alt=""></p>
<p>세 원소를 가지고 풀어야 하는 문제를 또 마주치게 된다면 이 접근법을 사용해야겠다.
넘 어려워 보였지만,, 풀고 정리까지 하니 좀 친해졌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[GreenLog] AWS RDS 퍼블릭 액세스 허용 안 함, springboot 연결]]></title>
            <link>https://velog.io/@0woy_/GreenLog-RDS-%ED%8D%BC%EB%B8%94%EB%A6%AD-%EC%95%A1%EC%84%B8%EC%8A%A4-%ED%97%88%EC%9A%A9-%EC%95%88-%ED%95%98%EA%B3%A0-%EB%82%9C-%EB%92%A4-%EC%84%9C%EB%B2%84-%EC%8B%A4%ED%96%89-%EC%95%88-%EB%90%98%EB%8A%94-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@0woy_/GreenLog-RDS-%ED%8D%BC%EB%B8%94%EB%A6%AD-%EC%95%A1%EC%84%B8%EC%8A%A4-%ED%97%88%EC%9A%A9-%EC%95%88-%ED%95%98%EA%B3%A0-%EB%82%9C-%EB%92%A4-%EC%84%9C%EB%B2%84-%EC%8B%A4%ED%96%89-%EC%95%88-%EB%90%98%EB%8A%94-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Tue, 01 Oct 2024 07:27:38 GMT</pubDate>
            <description><![CDATA[<p>하나 고치면 하나 에러 터져서 걍 모음집 맹글기</p>
<h3 id="에러-1">에러 1</h3>
<p><img src="https://velog.velcdn.com/images/0woy_/post/6885418b-0ef4-422e-b526-d7a1478e2bcf/image.PNG" alt=""></p>
<blockquote>
<p>The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.</p>
</blockquote>
<p>패킷 보냈는데 driver가 서버로 부터 아무 패킷도 못 받았다고 함.
<del>있었는데 없어졌어요</del></p>
<p>구글링 해서 mysql 설정을 바꿔주면 된다고 해서 바꿔줬지만 그래도 여전.
<a href="https://yeongunheo.tistory.com/entry/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-The-last-packet-sent-successfully-to-the-server-was-0-milliseconds-ago-The-driver-has-not-received-any-packets-from-the-server">mysqld.conf 설정 참고 게시글</a></p>
<hr>
<h3 id="에러-2">에러 2</h3>
<blockquote>
<p>Failed to initialize JPA EntityManagerFactory: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to open JDBC Connection for DDL execution</p>
</blockquote>
<p>그런데 이런 에러 로그도 있길래 바로 구글링 때려벌임
<img src="https://velog.velcdn.com/images/0woy_/post/5cbe9097-b0c8-44b5-8234-54de544f1f8c/image.PNG" alt=""></p>
<p>변경 전 <code>spring.jpa.hibernate.ddl-auto=create</code> 
변경 후 <code>spring.jpa.hibernate.hbm2ddl.auto=create</code> </p>
<p>이래 변경하고 실행하면,
<img src="https://velog.velcdn.com/images/0woy_/post/7a37e7e5-f331-499b-9de6-c56cee83d61f/image.PNG" alt="">
실행이 됨</p>
<hr>
<h3 id="에러-3">에러 3</h3>
<p><img src="https://velog.velcdn.com/images/0woy_/post/bb7346dd-44ee-43b1-a894-c9243f102287/image.PNG" alt=""></p>
<p>RDS와 제대로 연동이 됐는지 봐야하니까 정보 입력하고 회원가입 버튼 눌렀더니 하단의 에러 폭발</p>
<blockquote>
<p>java.net.ConnectException: Connection timed out: no further information
    at java.base/sun.nio.ch.Net.pollConnect(Native Method) ~[na:na]
    at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672) ~[na:na]
    at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:549) ~[na:na]</p>
</blockquote>
<p>스프링에서 데이터베이스 접근하여 접속을 기다리다가 뱉는 타임아웃 에러라고 한다.</p>
<p> 에러 원인으로 <em><strong>spring.datasource.url이 RDS 엔드포인트로 되어있어서 그런가?</strong></em> 라는 생각이 들었다.
돈 내기 싫어서 퍼블릭 액세스 허용을 안 해뒀거든요..</p>
<p>저번 게시글에 ec2 -&gt; rds 접속까지 했는데
<code>springboot -&gt; ec2 -&gt; rds</code>는 도당체 멀까.. 라고 생각하면서 오랜 친구인 gpt를 찾아갔다.</p>
<p><img src="https://velog.velcdn.com/images/0woy_/post/36a681c3-5e1b-45d7-a9ac-7f90857b21d1/image.PNG" alt=""></p>
<p>나: springboot에서 ec2를 통해 rds로 터널링 하고싶음, 근데 나 putty 써.
gpt: [내가 전에 만든 putty ssh 터널 설정] (<a href="https://velog.io/@0woy_/EC2-%EC%83%9D%EC%84%B1-%EB%B0%8F-RDS-%EC%97%B0%EB%8F%99#3-putty%EB%A1%9C-ec2-%EC%84%9C%EB%B2%84-%EC%A0%91%EC%86%8D">https://velog.io/@0woy_/EC2-%EC%83%9D%EC%84%B1-%EB%B0%8F-RDS-%EC%97%B0%EB%8F%99#3-putty%EB%A1%9C-ec2-%EC%84%9C%EB%B2%84-%EC%A0%91%EC%86%8D</a>)  하고 나서 <code>properties</code> 설정 ㄱㄱ
<img src="https://velog.velcdn.com/images/0woy_/post/337ec85d-960c-4f18-ab10-cec8d041a809/image.PNG" alt=""></p>
<blockquote>
<p>datasource.url에서 rds 엔드포인트가 아닌 <strong>localhost</strong>라는 점!</p>
</blockquote>
<hr>
<h3 id="에러-4">에러 4</h3>
<p> <img src="https://velog.velcdn.com/images/0woy_/post/2c5d13e6-607b-44ba-a12f-ccedf0b825ce/image.PNG" alt=""></p>
<blockquote>
<p>Access denied for user &#39;groot&#39;@&#39;localhost&#39; (using password: YES)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:130) ~[mysql-connector-j-8.3.0.jar:8.3.0]</p>
</blockquote>
<p>새로운 에러 발생.. 오히려 좋아. groot 사용자가 localhost 접근 권한이 없는듯</p>
<p>내 db 사용자에 groot는 있지만 host가 localhost는 아니라서 새로 맹글어줌</p>
<pre><code class="language-sql">CREATE USER &#39;groot&#39;@&#39;localhost&#39; IDENTIFIED BY &#39;password&#39;;
GRANT ALL PRIVILEGES ON *.* TO &#39;groot&#39;@&#39;localhost&#39;;
FLUSH PRIVILEGES;</code></pre>
<p>비밀번호는 알아서..</p>
<p>그런데 문제가 생겼다. 이미 3306 포트로 로컬 db가 있는데, rds에 저장되는 게 아니라 이 로컬에 저장이 돼버리는 사태;;</p>
<p>그래서 putty 수정했어요.
<img src="https://velog.velcdn.com/images/0woy_/post/635c192d-e3e3-4783-bfaa-860d8f06e48e/image.PNG" alt=""></p>
<p>기존에 있던 forwarded ports 지워주고, Source port를 3307로 바꿔서 다시 저장했다.
그리고 <code>.properties</code> 파일에 <code>localhost:3306</code> -&gt; <code>localhost:3307</code>로 포트 번호 같이 변경해 주었다.</p>
<hr>
<p>실행했더니 이제 저장도 잘 된다. 더이상 에러가 발생하지도 않고.. 정말 행복하다.</p>
<p>이렇게 local 스프링에서 ec2이용해 rds로 접속까지 끗..
 이틀동안 삽질했다. 많은 일이 었었어..</p>
]]></description>
        </item>
    </channel>
</rss>