오늘은 백준 2775번 부녀회장이 될테야 풀이를 할 예정이다. 그간 이런저런 일이 많아서 블로그에 소홀했는데,, 여튼! 시작해보자


바로 본론으로 들어가서,,

a층 b호에 살려면 자신의 아래(a-1)층의 1호부터 b호까지 사람들 수의 합만큼 사람들을 데려와 살야아 한다. 이 때, 0층 N호에는 총 N명이 산다.
0층 1호에는 1명, 2호에는 2명, ..., 14호에는 14명 이 산다.
→ 1층 3호에 살려면 6명, 2층 3호에 살려면 10명을 데려와 살아야 한다.

문제에 따르면 1층 3호에 살려면 6명은 다음과 같이 구해진다

(1-1)층 1호~3호까지 사람의 수의 합 = 0층 1호 + 0층 2호 + 0층 3호 = 1 + 2 + 3 = 6명 이다.

마찬가지로, 2층 3호의 사람을 구해보면 (2-1층) 1호~3호까지 사람 수의 합 = 1층 1호 + 1층 2호 + 1층 3호 = 1 + 3 + 6 = 10명

subtree가 반복되는 구조다.

잘 보면, subtree가 반복되는걸 확인할 수 있다. 만약 3층 4호, 5층 6호 ... 로 확장해 나갈수록, k-1, k-2, k-3, ... (k-m >= 0)이 반복되고 이에 대응하는 n호에 대한 사람 수도 subtree에서 반복되어 나타날 것이다. (만약 머리에 안그려진다면, 직접 그려서 확인하는걸 추천한다.)

subtree가 반복될 때 우리는 Dynamic Programming을 통해 쉽게 해당 문제를 풀 수 있다. 층에 대한 사람 수를 결정해 놓고, 해당 호수를 출력하면 문제 풀이는 끝이난다.

0층 1호에는 1명, 2호에는 2명, ..., 14호에는 14명 이 산다.

위 조건에 맞게, 1호~14호 까지는 1~14의 값을 넣어주고, 이후 층 부터 (k-1)층 n호에 대한 사람 수를 불러와 더해주면 된다.

문제 풀이를 위해 1차원 배열을 선언하고, 14개씩 끊어서 하나의 층을 표현할 예정이다.

        for(int i = 14; i < rooms.length; i++){
            int floor = i / 14;
            int number = (i % 14) + 1;
            int underFloor = floor-1;
            int underValue = 0;
            for(int j = 0; j < number; j++){
                underValue += rooms[underFloor * 14 + j];
            }
            rooms[i] = underValue;
        }

 

따라서 위의 표정을 보면, 층수는 ( i / 14 )의 값을 갖게 되고, 호수(number)는 ( i % 14 ) +1이 된다. 1을 더해준 이유는, 실제 호수는 0~13이 아닌 1~14호기 때문이다.

이후 아래층의 1호~number호 까지의 방에 살고있는 사람수 (underValue)를 계산한다. 이 때, underFloor*14인 경우는 1차원 배열로 선언했기 때문에, 바로 아래층의 위치를 구하기 위해 14를 곱해줘야 하기 때문이다.

 


최종 코드

public class Number2775 {
    public static void main(String[] args) throws IOException {
        int[] rooms = new int[15*14];
        for(int i = 0; i < 14; i++){
            rooms[i] = i+1;
        }
        for(int i = 14; i < rooms.length; i++){
            int floor = i / 14;
            int number = (i % 14) + 1;
            int underFloor = floor-1;
            int underValue = 0;
            for(int j = 0; j < number; j++){
                underValue += rooms[underFloor * 14 + j];
            }
            rooms[i] = underValue;
        }

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = Integer.parseInt(br.readLine());
        while(T-- > 0){
            int k = Integer.parseInt(br.readLine());
            int n = Integer.parseInt(br.readLine());
            System.out.println(rooms[k * 14 + (n-1)]);
        }

    }
}

rooms의 배열이 14 * 14가 아닌 이유는, 아파트의 층수가 0층~14층까지 존재하기 때문에 총 15개의 층에 대해 연산해야 하기 때문이다.

결과는.. 정답!

오늘은 백준 벌집 문제를 풀거다. 한번에 맞추지 못했다. 배열 써서 풀려 했는데, 이유 모를 OutOfBounds 오류와 배열 사용으로 인한 메모리 초과 오류가 발생했다. 심지어 정답직전에 테스트 출력까지 포함해버려서 (...) 총 6번의 리트만에 정답... OutOfBounds는 도저히 내 컴퓨터에서 재현이 안된다... 1~10억까지 다 넣어봤는데..난 안난다....


벌집 문제

문제를 딱 보면, "... 최소 개수의 방을 지나서 갈 때 ..."로 인해 Shortest Path를 찾는 것인가 싶다.
하지만 그림을 잘 보면, 규칙이 있다.

너무 대충 그렸나?

이 규칙을 찾았으면, 다음 규칙도 보인다.

수열이 보인다!

벌집의 겉에는 원자의 전자껍질과 같은 구조를 발견할 수 있다. 심지어 해당 껍질은 등비수열을 이룬다. 그리고 제일 안쪽 껍질은 무조건 밟아야 함에 착안해서, 두 번째 껍질의 시작을 1로 맞춰주면 문제를 풀기 더 쉽다.
$$ a_n = 6^{(n-1)} $$
벌써 문제를 다 풀어버렸다.

입력값으로 N이 주어지고, 해당 N까지의 거리를 구해야 한다. 껍질의 시작을 1로 맞춰주기로 했으니, 입력값 N에서 1을 빼준다.

N = N - 1 ~> 두 번째 껍질의 시작을 1로 맞춰주기 위함

그리고 껍질의 층 수를 나타내는 변수 base와 껍질을 벗겨내는 반복자를 이용하여 한 층 씩 벗겨내면 된다. 벌집의 방 번호는 항상 양수이기 때문에, 현재 껍질이 음수가 되어서는 안된다.
이를 위한 연산을 입력값과 벗겨낼 껍질 층으로 구할 것이다. 입력 값을 N, 반복자를 i라 할 때, 다음과 같다.

입력 값: N
현재 껍질 수: i
현재 껍질 층: base

다음 껍질 수 i = i + 6 * (base+1);
남은 입력 값 N = N - i

위 내용을 바탕으로 수도코드로 정리하면 이렇게 된다.

WHILE ( N - i >= 0) THEN
i = i + 6 * (base);
base += 1;
END WHILE

이후 While문이 종료되면 base는 입력 값 N에 대한 껍질 층이 반환되고, 이 값은 1로부터 얼마나 떨어져있는지가 된다.


최종 코드

import java.util.Scanner; public class Number2292 { public static void main(String[] args){ Scanner scan = new Scanner(System.in); int N = scan.nextInt(); int temp = N-1, base = 1, i = 1; while(temp - i >= 0){ System.out.print("bef: " + i); i += 6 * (base++); System.out.println(", aft: " + i + ", cal: " + temp); } System.out.println(base); } }

결과는 정답!

오늘은 백준 1152번 풀거다! 와 이게 쉬운데 함정이 많더라구요.

저처럼 문제 대충 쓱 보고 풀면 틀려요!

ㅋㅋㅋ 문제만 보고 예제를 제대로 보지도 않고 그냥 '그냥 공백으로 자르면 되는거 아닌가..?' 하고 바로 냈더니 바로 "틀렸습니다" ... 

그래서 예제를 살펴봤더니, 예제 입력 2를 보면 다른 녀석들과는 다르게 제일 앞에 공백이 있는거에요! " Maza..." 그래서 아~ 이런~ 하고 제일 앞이 공백일때만 처리해줬더니 또 다시 보기 좋게 "틀렸습니다" ... 

아 도대체 이 문제에 어떤 예외를 넣었을까 열심히 또 돌려봤죠. 그러고서 알아냈어요.. 역시 처음부터 쉽게보면 안돼요.. 모든 문제는 이유가 있어요..


서론이 너무 길었어요! 그냥 부끄러워서..

문제는 쉽습니다. 제가 생각했던 것과 마찬가지로 공백으로 자르면 돼요. 하나하나 들어가 볼게요!

...  단어는 띄어쓰기 한 개로 구분되며, 공백이 연속해서 나오는 경우는 없다. 또한 문자열의 앞과 뒤에는 공백이 있을 수도 있다.

우선 단어는 띄어쓰기 한 개로 구분이 되니, 원래 제 시도처럼 공백으로 자르면 됩니다.

1. 띄어쓰기를 통해 문자열을 구분짓는다.

문제는 바로 빨간줄로 표시한 '공백이 연속해서 나오는 경우는 없다.'에요. 공백이 연속해서 나오진 않지만, 바로 그 뒤를 보면 '문자열의 앞과 뒤에는 공백이 있을 수도 있다.' 그냥 말장난인것같긴 한데,,

정리해 보면 다음과 같아요.

1. 띄어쓰기를 통해 문자열을 구분짓는다.
- apple banana chocolate
2. 문자열 앞과 뒤에는 공백이 있을 수 있다.
- apple  banana chocolate
-  apple banana chocolate
- ...
3. 공백이 연속해서 나오는 경우는 없다.
-  apple   banana (불가능)

즉 3번은 안되지만, 2-1번과 같이 문자열 뒤, 그리고 문자열 앞 에 존재하는 각각의 공백은 겹칠 수 있어요!

따라서 우리는 공백으로 문자열을 자를 때, 잘라진 문자열에 대한 배열이 비어있는지 확인해 주면 끝입니다.

2-1번에 대하여 우리가 공백으로 문자열을 자르면, 다음과 같은 형상이기 때문입니다.

[0]: apple
[1]: 
[2]: banana
[3]: chocolate

최종코드

import java.util.Scanner;

public class Number1152 {
    public static void main(String[] args){
        String[] str = new Scanner(System.in).nextLine().split(" ");
        int count = 0;
        for(int i = 0; i < str.length; i++)
            if(!str[i].isEmpty())
                count++;
        System.out.println(count);
    }
}

끝!

갤럭시 Z Flip LTE 화면이 깨져서 고치고 왔다.

코로나때문에 액정 재고가 없어서, 7월 20일즈음 재고 예약을 했고, 오늘 도착했다는 연락이 와서 다녀왔다.

내가 수리한 부품은 액정메인보드카메라 글라스, 뒷판 글라스 2개 이렇게 5개다. 그런데 액정에는 테두리, 배터리, 힌지가 일체형이라 같이 교체된다.

메인보드도 수리하겠다고 말하니, 기사님께서 "메인보드는 증상이 어떠시길래 고치시나요?" 라고 물어보셨고, 너무 뜨겁다 했더니 원래 뜨겁다더라. 그래서 "혹시 수리 안된다 하면 물에 빠트려서 다시 갖고올거에요! 지금 물에 적셔올까요?"했더니 놀라셔서 아니라고 수리 해주겠다 하시더라.

문제가 있었는데 삼성케어플러스를 구독중인데, 삼성서비스센터에서 가입조회가 안된다더라.. (...)

그래서 내 계정으로 로그인 하고 결제 내역과 가입 사실을 보여주니, 고객님이 가입되어 있는건 사실이니 수리하고, 해당 사실을 관련 부서에 연락해서 알아보도록 하겠다더라.

뭐.. 어쩌겠나.. ㅋㅋ

전준태를 갖추고, 알았다고 답변한 뒤 수리를 기다렸다. 나중에 여쭤보니 중간에 누락된것같다고 죄송하다고 말씀주셨다.

와 ㅋㅋ 삼케플 없었으면 77만원 내고 고쳐야 했다..ㅋㅋㅋㅋ

여튼 새로 태어난 내 플립을 받아들고, 집에왔다.

태생적으로 이 아이는 약하고 여리고 여러모로 정말 별로인 아이라, 일단 기스가 잘 안나게 외부 필름을 붙여줬다. 예쁜걸로..

이건 못참지 ㅋㅋㅋㅋㅋㅋㅋㅋ

스티커 붙였더니 칙칙한 그 검정이 아니라, 좀 상큼(?)해졌다! 붙이는 난이도는 있었지만, 그래도 뭐 재밌으면 됐다.

빨리 플립을 처분하고, 다른 폰 쓰고싶다.. 너무 뜨겁고,, 배터리도 너무 빨리닳고,,, 여러모로 정말 별로인 아이.... 이 폰을 쓰고 처음으로,, 아이폰이 쓰고싶어졌다....(?)..

MkAuthToken을 개발하긴 했는데,, Node와는 너무 다른 그것이기 때문에 살짝 사용법을 정립하기가 어려웠다.

JWT 특성상 서버에서는 단지 토큰 생성, 검증만 수행하기 때문에 모든 관리를 클라이언트가 해야하는 부담이 있고, MkWeb은 프론트 개발자가 최대한 이러한 활동에 집중하지 않게 하자는 목표가 있었기 때문에 더욱 그러했다.

하지만, JWT를 사용하기로 결정했고, 따라서 "클라이언트에서 이를 관리할 필요가 있다." 고 결론을 내렸다.

그럼 어떻게 Client에서 사용하게 할 것인가를 고민해 봤는데, html에서 지원하는 cookie를 이용하여 저장할 예정이다.


먼저 토큰을 생성하자.

JWT를 저장하려면 우선 JWT가 생성되어 있어야 한다. 로그인과 같은 행위를 통해 JWT를 발급받아고, 해당 토큰 값을 내가 알고 있어야 한다.

서버를 배포할 때 설정한 mkweb.auth.uri를 통해 (ajax를 사용하든 어떤 방법이 되었든...) token을 생성하자. (이 때, 모든 처리는 method: POST, Cotent-Type: application/json 이어야 한다.)

$("#login").click(function(){
	$.ajax({
		type : "POST", 
		url : "/auth/login",
		dataType : "json",
		data : {
        	"user_id" : $("#userid").val(),
			"user_pw" : $("#userpw").val()
		},
		success : function(rd){
			if(rd.token){
				...
			}
		}     
	});
});

토큰생성에 성공했다면, ajax결과로 다음과 같은 형식의 데이터를 전달받는다.

{"code": 200, "token":"your_token"}

이 후, document.cookie를 통해 토큰 값을 저장한다.

$("#login").click(function(){
	$.ajax({
		...
		success : function(rd){
			if(rd.token){
				document.cookie = createCookie(token);
			}
		}     
	});
});

let __MK_TOKEN_LIFETIME__ = 600;
let __MK_TOKEN_NAME__ = 'mkauthtoken';

function createCookie(token){
    var date = new Date();
    date.setTime(date.getTime() + __MK_TOKEN_LIFETIME__ * 1000);
    let cookieInfo = __MK_TOKEN_NAME__ + '=' + token + ';expires=' + date.toUTCString() + ';path=/';
    return cookieInfo;
}

토큰의 사용

토큰을 구하고, 삭제하는 방법은 다음과 같다.

function getToken(cookieInfo){
    var value = document.cookie.match('(^|;) ?' + __MK_TOKEN_NAME__ + '=([^;]*)(;|$)');
    return value ? value[2] : null;
}

function removeTokenCookie(cookieInfo) {
    var date = new Date();
    document.cookie = __MK_TOKEN_NAME__ + "= ; expires=" + date.toUTCString() + "; path=/";
}

주의사항

let __MK_TOKEN_LIFETIME__ = 600;
let __MK_TOKEN_NAME__ = 'mkauthtoken';

자바스크립트에 작성된 위 두 옵션은 MkWeb.conf에 설정된 mkweb.auth.lifetime, mkweb.auth.controller.name 두 속성과 일치하는 값을 가져야 한다.


꼭 JSP / HTML일 필요는 없다!

React 등 프론트 개발을 위해 다른 프레임워크 / 라이브러리를 사용해도 서버를 MkWeb을 이용할 때, 단지 프론트에서 토큰을 잘 가지고 있기만 하면 된다! 나는 프론트 개발은 HTML + Javascript + Css 밖에 안해봤기 때문에,, ㅎㅎ

GET, HEAD, OPTIONS only allow URI options
PUT allow URI options for condition, body parameter for update
DELETE allow body parameter for deleting
POST allow body parameter

기타 pretty, paging 등 : QueryString

키는 기본적으로 Authorization에 포함되어야 하지만, HTTP조회일 경우도 있기 때문에 query string을 예외적으로 허용.

Content-Type이 application/json이 아닐 경우, 거부

ElasticSearch Index 삭제 방법

$ curl --request DELETE "elastic_search_url/index" --user ID:PW

ElasticSearch 인덱스의 특정 데이터 삭제 방법

$curl --request POST "elastic_search_url/index/_delete_by_query" --user ID:PW --data /
"
{
    "query":{
          "match":{
                "컬럼명":"조건"
          }
    }
}
"

이 때, --data부분은 한줄로 써야 한다. 

예시:

$curl --request POST "elastic_search_url/index/_delete_by_query" --user ID:PW --data "{\"query\":{\"match\":{\"id\":\"hongildong@gmail.com\"}}}"

 

오늘은 백준 3052번 문제를 풀자! 해당 문제를 봤을 때, 바로 떠오른 방법이 두 가지가 있다.

MkWeb을 개발하면서 HashMap을 엄청나게 썼기 때문에, 첫 번째로 HashMap이 떠올랐고, 그 다음으로 Queue를 이용한 풀이가 떠올랐다.

해시맵의 경우, 중복되는 키를 허용하지 않기 때문에 나머지를 구한 다음에 HashMap에 각각 넣어준 뒤, HashMap의 사이즈를 반환하면 끝이다. 너무 쉽기 때문에, Queue를 이용하여 문제를 풀이하려 한다.

사실 큐를 이용하나 ArrayList, Stack 등 다른 자료구조를 이용하나 풀이는 비슷하지만, 큐의 풀이가 더 직관적이기 때문에 큐를 선택했다.


이쯤 되면 나머지 수를 구하는 것은 쉬우니, 설명은 넘어가도록 하겠다.

val = 입력값 % 42

우리는 큐에 값을 하나 씩 넣어줄 예정인데, 이전에 큐에 이미 값이 존재하는지 확인해야 한다. 이 때, 값이 존재하지 않는다면 값을 추가하고, 존재한다면 해당 값은 따로 추가하지 않을 것이다.

이때 '하나의 큐로 어떻게 가능하지?'라는 의문이 들텐데, 당연히 두 개의 큐를 사용할 것이다. (물론, 하나는 배열, 하나는 큐 등 어떤 형태를 취하든 상관 없으나, 위에서 말한대로 큐가 직관적이기 때문에 큐를 사용할 것이다.)

mainQueue: 나머지가 저장될 큐
tempQueue: mainQueue에 존재하지 않는 val들을 임시적으로 저장할 큐

매 회차마다 mainQueue로부터 tempQueue로 하나씩 옮겨담을 것이다. 이 때, val이 mainQueue에 존재하는지 확인하고, 존재하지 않는다면 값을 추가할 것이다.

따라서 mainQueue에서는 dequeue가 주로 사용될 것이며, tempQueue에는 enqueue가 주로 사용될 것이다.

While size of mainQueue > 0 Then
    peek is mainQueue.dequeue();
    If peek is not val Then
        tempQueue.enqueue(peek);
    End If
End While

수도 코드를 표현하려 했는데.. VB의 아련한 기억이...

while 반복문이 끝나면, queue를 tempQueue로 바꿔주고, queue에 val을 추가해주면 된다. val을 추가하는 이유는 while문 내에서 val을 제외한 값들만 옮겨 넣었기 때문에, mainQueue에는 val이 없다.

최종적으로 mainQueue의 size를 출력해주면 해당 문제는 끝!


전체 코드

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

public class Number3052 {
    public static void main(String args[]) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        Queue<Integer> queue = new LinkedList<>();
        for(int i = 0; i < 10; i++) {
            int val = (Integer.parseInt(bufferedReader.readLine())) % 42;
            Queue<Integer> tempQueue = new LinkedList<>();
            while(queue.size() > 0) {
                int peek = queue.poll();
                if(peek != val){
                    tempQueue.add(peek);
                }
            }
            queue = tempQueue;
            queue.add(val);
        }

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

결과는 정답!

+ Recent posts