이 영역을 누르면 첫 페이지로 이동
Stranger's LAB 블로그의 첫 페이지로 이동

Stranger's LAB

페이지 맨 위로 올라가기

Stranger's LAB

프로그래밍과 관련하여 다양한 알고리즘 문제를 풀어보고, 프로그래밍 언어를 이해해 볼 수 있도록 돕고자 만든 블로그 입니다.

[백준] 11866번 : 요세푸스 문제 0 - JAVA [자바]

  • 2021.01.23 13:39
  • JAVA - 백준 [BAEK JOON]/큐, 덱
글 작성자: ST_
728x90

 





 
www.acmicpc.net/problem/11866

 

11866번: 요세푸스 문제 0

첫째 줄에 N과 K가 빈 칸을 사이에 두고 순서대로 주어진다. (1 ≤ K ≤ N ≤ 1,000)

www.acmicpc.net

 

 









  • 문제



 

 

 

 

 

 

 

 

 

 

 

 

 





  • 알고리즘 [접근 방법]

 



 

큐를 이용한 매우 쉬운 문제다.

 

예제를 이해해보자면 이렇다. 1부터 N까지 나열 된 수에서 K번째 수 마다 차례대로 뽑아 낸 수열을 출력하는 것이다.

 

예제를 예로 들어보자.

 

seq {1, 2, 3, 4, 5, 6, 7}, result {}

 

round 1 : seq {1, 2, 3, 4, 5, 6, 7},   result {3}

round 2 : seq {1, 2, 4, 5, 6, 7},   result {3, 6}

round 3 : seq {1, 2, 4, 5, 7},   result {3, 6, 2}

round 4 : seq {1, 4, 5, 7},   result {3, 6, 2, 7}

round 5 : seq {1, 4, 5},    result {3, 6, 2, 7, 5}

round 6 : seq {1, 4},    result {3, 6, 2, 7, 5, 1}

round 7 : seq {4},    result {3, 6, 2, 7, 5, 1, 4}

 

 

 

이렇게 삭제된 현재 위치에서 K번 째 수를 찾아 가고, 그 수를 삭제하고.. 이렇게 원소가 남지 않을 때 까지 무한 반복을 하면 되는 것이다.

 

이 것을 어떻게 큐로 이용해야 할까?

 

가장 보편적인 방법은 이렇다.

K번 쨰 수가 되기 직전까지 맨 앞의 원소를 K-1 번 꺼내오고(poll) 꺼내온 원소들을 맨 뒤로 넣는다.(offer)

 

그리고 K번째로 뽑힌(poll) 원소는 출력하면 되는 것이다.

 

 

대강 이렇게 보면 된다.

 

round 1

   loop 1 : {1, 2, 3, 4, 5, 6, 7}  → {2, 3, 4, 5, 6, 7, 1} 

   loop 2 : {2, 3, 4, 5, 6, 7, 1}  → {3, 4, 5, 6, 7, 1, 2} 

   loop 3 : {3, 4, 5, 6, 7, 1, 2}  → 3 출력

 

round 2

   loop 1 : {4, 5, 6, 7, 1, 2}  → {5, 6, 7, 1, 2, 4} 

   loop 2 : {5, 6, 7, 1, 2, 4}  → {6, 7, 1, 2, 4, 5} 

   loop 3 : {6, 7, 1, 2, 4, 5}  → 6 출력

 

round 3

   loop 1 : {7, 1, 2, 4, 5}  → {1, 2, 4, 5, 7} 

   loop 2 : {1, 2, 4, 5, 7}  → {2, 4, 5, 7, 1} 

   loop 3 : {2, 4, 5, 7, 1}  → 2 출력

 

round 4

   loop 1 : {4, 5, 7, 1}  → {5, 7, 1, 4} 

   loop 2 : {5, 7, 1, 4}  → {7, 1, 4, 5} 

   loop 3 : {7, 1, 4, 5}  → 7 출력

 

round 5

   loop 1 : {1, 4, 5}  → {4, 5, 1} 

   loop 2 : {4, 5, 1}  → {5, 1, 4} 

   loop 3 : {5, 1, 4}  → 5 출력

 

round 6

   loop 1 : {1, 4}  → {4, 1} 

   loop 2 : {4, 1}  → {1, 4} 

   loop 3 : {1, 4}  → 1 출력

 

round 7

   loop 1 : {4}  → {4} 

   loop 2 : {4}  → {4} 

   loop 3 : {4}  → 4 출력

 

 

 

이렇게 가장 간단한 방법으로 알고리즘을 구성할 수 있다. 한마디로 K-1번만큼 poll과 offer을 한 뒤, K번 째 값을 poll만하고 해당 원소를 출력해주면 되는 것이다.

 

간단하게 코드로 보자면 이렇다.

 

Queue<Integer> q;	// 1~N까지 큐에 입력되어있다고 가정
int K;	// K번째 수

while(!q.isEmpty()) {

	// K-1번 앞에 있는 원소를 뒤로 보낸다.
	for(int i = 0; i < K - 1; i++) {
		int val = q.poll();
		q.offer(val);
	}

	print(q.poll());	// K번째로 나오는 원소를 삭제함과 동시에 출력한다.
}

 

 

 

 

 

 

물론 이런 방식 말고 큐 대신 리스트를 활용하여 K값을 매 번 K씩 증가시키며 해당 원소를 삭제하면서 해당 값을 출력하는 방법이 있다.

 

물론 리스트의 시작 index는 0부터 시작하고, 매 번 원소가 한 개씩 줄기 때문에 때문에 처음 시작 값 및 증가 값은 K값에 -1을 해야한다.

 

또한 중요한 점은, 참조하려는 인덱스가 입력받은 수보다 작아질 경우가 생기는데, 이럴 경우 참조 범위 밖으로 에러가 생길 수 있다. 그러니 반드시 현재 큐의 크기를 나눈 나머지 값으로 증가시켜야 한다.

 

위 예제를 그대로 예로들면 이렇다. 

 

index = 2

  → index 2 의 원소 삭제 : {1, 2, 3, 4, 5, 6, 7} 

 

index = 4 [index = (index + (K - 1)) % size     →   (2 + 2) % 6 = 4]

  → index 4 의 원소 삭제 : {1, 2, 4, 5, 6, 7}

 

index = 1  [index = (index + (K - 1)) % size     →   (4 + 2) % 5 = 1]

  → index 1 의 원소 삭제 : {1, 2, 4, 5, 7}

 

index = 3  [index = (index + (K - 1)) % size     →   (1 + 2) % 4 = 3]

  → index 3 의 원소 삭제 : {1, 4, 5, 7}

 

index = 2  [index = (index + (K - 1)) % size     →   (3 + 2) % 3 = 2]

  → index 2 의 원소 삭제 : {1, 4, 5}

 

index = 0  [index = (index + (K - 1)) % size     →   (2 + 2) % 2 = 0]

  → index 0 의 원소 삭제 : {1, 4}

 

index = 0  [index = (index + (K - 1)) % size     →   (0 + 2) % 1 = 0]

  → index 0 의 원소 삭제 : {1}

 

 

 

이런 식으로 할 수도 있다.

 

코드로 보자면 이렇다.

 

LinkedList<Integer> list;	// 1~N까지 리스트에 입력되어있다고 가정
int K;	// K번째 수

int index;	// 리스트에서 삭제할 요소를 참조할 인덱스

while(!list.isEmpty()) {

	index = (index + (K - 1)) % list.size();

	print(list.get(index));	// index위치에 있는 요소를 출력

	list.remove(index);	// index위치에 있는 요소를 삭제
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 





  • 4가지 방법을 사용하여 풀이한다.

 



이전 포스팅과 여타 다를 바 없이 아래와 같이 두 가지 입력 방법을 통해 성능을 비교하려 한다.

그리고 각 입력방법에 대해 필자가 설명한 두 가지 방식의 알고리즘을 적용해보도록 하겠다. 출력 내용을 모두 StringBuilder를 사용하여 한 번에 묶은 뒤, 한 번에 출력 할 것이니 이 점 미리 알고계시길 바란다.

 

 

1 . Scanner + 알고리즘1

2 . Scanner + 알고리즘2

3. BufferedReader + 알고리즘1

4. BufferedReader + 알고리즘2

 

 

 






  • 풀이





- 방법 1 : [Scanner + 알고리즘 1]

 

import java.util.Scanner;
import java.util.LinkedList;
import java.util.Queue;

public class Main {

	public static void main(String[] args) {
		
		Scanner in = new Scanner(System.in);
		Queue<Integer> q = new LinkedList<>();
		
		int N = in.nextInt();
		int K = in.nextInt();
		
		
		for(int i = 1; i <= N; i++) {
			q.add(i);
		}
		
		StringBuilder sb = new StringBuilder();
		sb.append('<');
		
		/*
		 *  마지막 부분의 출력은 > 괄호 전에 공백이 없기 때문에
		 *  일괄적으로 출력하기 위해 마지막 원소만 남겨질 때까지만
		 *  반복하고 마지막 원소는 그대로 출력한다.
		 */
		while(q.size() > 1) {
			
			for(int i = 0; i < K - 1; i++) {
				int val = q.poll();
				q.offer(val);
			}
			
			sb.append(q.poll()).append(", ");
		}

		// 마지막 원소 출력한 뒤 > 도 붙여준다.
		sb.append(q.poll()).append('>');
		System.out.println(sb);
	}

}

 

 

가장 기본적인 방법이라 할 수 있겠다.

 

중요한 점은 주석으로도 달아놓았지만, 출력 형식을 잘 보면 원소 사이사이에 쉼표(,)와 공백(" ") 이 있지만 마지막 출력 부분은 공백이 없다. 그렇기 때문에 마지막 원소 부분은 반복문으로 돌리지 말고 그 전까지만 반복문을 돌린 뒤, 마지막 원소는 따로 출력해주도록 만들었다.

 

 











- 방법 2 : [Scanner + 알고리즘 2]

 

 

 

입력 방법을 Scanner 대신 BufferedReader 을 사용하여 풀이하는 방법이다. 단, BufferedReader 는 문자열을 한 줄로 읽기 때문에 N과 M을 구분하기 위해 공백을 기준으로 문자열을 분리해주어야하니 StringTokenizer 을 사용하여 풀도록 하겠다.

 

 

import java.util.Scanner;
import java.util.LinkedList;

public class Main {

	public static void main(String[] args) {
		
		Scanner in = new Scanner(System.in);	
		LinkedList<Integer> list = new LinkedList<Integer>();

		int N = in.nextInt();
		int K = in.nextInt();
		
		for(int i = 1; i <= N; i++) {
			list.add(i);
		}
		
		
		StringBuilder sb = new StringBuilder();
		sb.append('<');
		
		int index = 0;	// 리스트에서 삭제할 요소를 참조할 인덱스 변수
		
		while(list.size() > 1) {
			index = (index + (K - 1)) % list.size();
			
			// index위치에 있는 요소를 삭제함과 동시에 출력 
			sb.append(list.remove(index)).append(", ");
		}
		
		// 마지막으로 남은 요소 삭제함과 동시에 출력
		sb.append(list.remove()).append('>');
		System.out.println(sb);
	}
}

 

 

 

리스트 계열 중 어떤 것을 써도 무방하다. 다만 중간 삭제가 많은만큼 ArrayList보다는 약간이나마 LinkedList가 더 효율적이라 LinkedList로 했다.

 

 

 











- 방법 3 : [BufferedReader + 알고리즘 1]

 

 

 

방법 1에서 입력 방법을 Scanner 대신 BufferedReader 을 사용하여 풀이하는 방법이다. 단, BufferedReader 는 문자열을 한 줄로 읽기 때문에 N과 K를 구분하기 위해 공백을 기준으로 문자열을 분리해주어야하니 StringTokenizer 을 사용하여 풀도록 하겠다.

 

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {

	public static void main(String[] args) throws IOException {
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		Queue<Integer> q = new LinkedList<>();
		
		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		
		int N = Integer.parseInt(st.nextToken());
		int K = Integer.parseInt(st.nextToken());
		
		
		for(int i = 1; i <= N; i++) {
			q.add(i);
		}
		
		StringBuilder sb = new StringBuilder();
		sb.append('<');
		
		/*
		 *  마지막 부분의 출력은 > 괄호 전에 공백이 없기 때문에
		 *  일괄적으로 출력하기 위해 마지막 원소만 남겨질 때까지만
		 *  반복하고 마지막 원소는 그대로 출력한다.
		 */
		while(q.size() > 1) {
			
			for(int i = 0; i < K - 1; i++) {
				q.offer(q.poll());
			}
			
			sb.append(q.poll()).append(", ");
		}

		// 마지막 원소 출력한 뒤 > 도 붙여준다.
		sb.append(q.poll()).append('>');
		System.out.println(sb);
	}

}

 

 

 

입력형식만 바꿔준 것이라 어렵진 않았을 것이다.

 











- 방법 4 : [BufferedReader + 알고리즘 2]

 

 

 

마찬가지로 방법 2에서 입력 방법을 Scanner 대신 BufferedReader 을 사용하여 풀이하는 방법이다. 

만약 매 번 리스트를 참조하여 사이즈를 알아내는 것이 조금 불편하다면 list.size 대신 변수 N을 써서 1씩 감소시켜도 된다. 

 

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.LinkedList;
import java.util.StringTokenizer;

public class Main {

	public static void main(String[] args) throws IOException {
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		LinkedList<Integer> list = new LinkedList<Integer>();
		
		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		
		int N = Integer.parseInt(st.nextToken());
		int K = Integer.parseInt(st.nextToken());
		
		for(int i = 1; i <= N; i++) {
			list.add(i);
		}
		
		
		StringBuilder sb = new StringBuilder();
		sb.append('<');
		
		int index = 0;	// 리스트에서 삭제할 요소를 참조할 인덱스 변수
		
		while(N > 1) {
			index = (index + (K - 1)) % N;
			
			// index위치에 있는 요소를 삭제함과 동시에 출력 
			sb.append(list.remove(index)).append(", "); 
			N--;
		}
		
		// 마지막으로 남은 요소 삭제함과 동시에 출력
		sb.append(list.remove()).append('>');
		System.out.println(sb);
	}
}

 

 

 

크게 어려울 것은 없을 것이다. 코드도 그리 길지 않고, 조금만 생각해본다면 수식을 금방 완성할 수 있었을 것이다.

 

 

 

 

 

 





  • 성능






 

채점 번호 : 25559256  -  방법 4 : BufferedReader + 알고리즘 2

채점 번호 : 25559252  -  방법 3 : BufferedReader + 알고리즘 1

채점 번호 : 25559249  -  방법 2 : Scanner + 알고리즘 2

채점 번호 : 25559246  -  방법 1 : Scanner + 알고리즘 1

 

 

보면 큐에서 매번 원소를 빼고 넣는 것 보다 특정 인덱스를 찾아나서는 것이 훨씬 빠르다는 것을 볼 수 있다.








  • 정리

 



이 번 문제 또한 어려운 점은 없었을 것이다. 

큐, 덱을 활용하는 문제지만 다른 방식으로도 더 효율적이게 짤 수 있다는 것을 알려주고자 다른 방법도 보여주었다.

 

대개 이러한 문제들은 조금만 노트에 정리해보다보면 공식이 어렵지 않게 나오기 때문에 일단 보이는대로 풀이를 해본 뒤 한 번 더 다른 방법은 없는지 고민해보는 것도 좋지 않을까 생각이 든다.

 

혹여 어렵거나 이해가 되지 않는 부분이 있다면 댓글 남겨주시면 최대한 빠르게 답변드리도록 하겠다.

 

 



저작자표시 비영리 변경금지 (새창열림)

'JAVA - 백준 [BAEK JOON] > 큐, 덱' 카테고리의 다른 글

[백준] 1021번 : 회전하는 큐 - JAVA [자바]  (0) 2021.02.27
[백준] 10866번 : 덱 - JAVA [자바]  (8) 2021.02.19
[백준] 1966번 : 프린터 큐 - JAVA [자바]  (10) 2021.02.03
[백준] 2164번 : 카드2 - JAVA [자바]  (4) 2020.12.19
[백준] 18258번 : 큐 2 - JAVA [자바]  (6) 2020.12.13

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • [백준] 10866번 : 덱 - JAVA [자바]

    [백준] 10866번 : 덱 - JAVA [자바]

    2021.02.19
  • [백준] 1966번 : 프린터 큐 - JAVA [자바]

    [백준] 1966번 : 프린터 큐 - JAVA [자바]

    2021.02.03
  • [백준] 2164번 : 카드2 - JAVA [자바]

    [백준] 2164번 : 카드2 - JAVA [자바]

    2020.12.19
  • [백준] 18258번 : 큐 2 - JAVA [자바]

    [백준] 18258번 : 큐 2 - JAVA [자바]

    2020.12.13
다른 글 더 둘러보기

정보

Stranger's LAB 블로그의 첫 페이지로 이동

Stranger's LAB

  • Stranger's LAB의 첫 페이지로 이동

검색

나의 외부 링크

  • st-github

공지사항

  • 공지 - 블로그 사용 설명서

메뉴

  • 홈
  • 방명록

카테고리

  • 전체 카테고리 (267)
    • Java (5)
    • JAVA - 백준 [BAEK JOON] (177)
      • 입출력과 사칙연산 (14)
      • 조건문 (7)
      • 반복문 (11)
      • 1차원 배열 (7)
      • 함수 (3)
      • 문자열 (10)
      • 기본 수학 1 (8)
      • 기본 수학 2 (6)
      • 2차원 배열 (0)
      • 정렬 (10)
      • 재귀 (4)
      • 브루트 포스 (5)
      • 집합과 맵 (0)
      • 기하 1 (5)
      • 정수론 및 조합론 (12)
      • 백트래킹 (8)
      • 동적 계획법 1 (15)
      • 누적 합 (0)
      • 그리디 알고리즘 (5)
      • 스택 (5)
      • 큐, 덱 (7)
      • 분할 정복 (9)
      • 이분 탐색 (7)
      • 기타 문제 (17)
      • 별 찍기 문제 모음 (2)
    • C++ - 백준 [BAEK JOON] (46)
      • 입출력과 사칙연산 (14)
      • 조건문 (7)
      • 반복문 (11)
      • 1차원 배열 (7)
      • 함수 (3)
      • 문자열 (0)
      • 기타 문제 (4)
    • 자료구조 (18)
      • Java (18)
    • 알고리즘 (11)
      • Java (11)
    • 프로그래밍 기초 (6)
    • 이모저모 (2)
    • 일상의 글 (2)

최근 글

정보

ST_의 Stranger's LAB

Stranger's LAB

ST_

블로그 구독하기

  • 구독하기
  • 네이버 이웃 맺기
  • RSS 피드

방문자

  • 전체 방문자
  • 오늘
  • 어제

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. Copyright © ST_.

티스토리툴바