본문 바로가기
PS/BOJ

[자바] 백준 11003 - 최솟값 찾기 (java)

by Nahwasa 2023. 3. 9.

문제 : boj11003

 

 

필요 알고리즘

  • 그리디 
    • 그리디 개념으로 풀 수 있는 문제이다.
  • 덱, 우선순위 큐
    • 생각한 그리디 로직을 구현하기 위해 필요하다.

※ 제 코드에서 왜 main 함수에 로직을 직접 작성하지 않았는지, 왜 Scanner를 쓰지 않고 BufferedReader를 사용했는지 등에 대해서는 '자바로 백준 풀 때의 팁 및 주의점' 글을 참고해주세요. 백준을 자바로 풀어보려고 시작하시는 분이나, 백준에서 자바로 풀 때의 팁을 원하시는 분들도 보시는걸 추천드립니다.

 

 

풀이

  우선 우선순위 큐를 사용한 풀이부터 얘기해보자 (코드2)

코드만 봐도 어떤 느낌인지 알 것 같다. 순서대로 입력값을 넣을 때, 입력값과 위치도 같이 넣는다. 그리고 우선순위 큐에서 최솟값을 꺼낼껀데, 이게 위치가 현재 보고 있는 위치 - L 보다 작으면 버려버리면 된다. 그럼 항상 문제에서 제시된 일정 범위 내의 최솟값이 출력됨이 보장된다.

for (int i = 0; i < n; i++) {
    pq.add(new Num(Integer.parseInt(st.nextToken()), i));	// 입력값을 위치(i)와 함께 저장
    while (pq.peek().idx <= i-l)   pq.poll();	// 최소값을 꺼내는데, 위치가 문제에서 제시된 범위를 벗어난건 버린다.
    sb.append(pq.peek().n).append(' ');	// 위치가 맞는 값 중 최소값이 출력됨이 보장된다.
}

 

  문제는 자바의 경우 자바8과 자바11에 2.6초로 시간 제한이 걸려있다는 것이다. 그래서 위 로직으로는 자바 15 이상으로 제출하면 통과되긴하는데, 자바 15이상이 나중에 백준에 추가되서 제한이 안걸려있던 것이고 의도한바는 2.6초 이내에 되야할 것 같다. 그래서 추가로 짠 코드가 코드1 이다.

 

  pq대신 리스트에 값을 넣는데, 이 리스트는 항상 최선의 상태를 유지하도록 한다. 내 경우 구현을 리스트로 했는데, 덱으로 해도 동일하다. 리스트의 앞쪽에서는 위치 범위가 지난걸 빼고, 뒤쪽으로는 입력받은걸 넣는다. 다만 이 때 리스트에 있는 값 중 가장 마지막값보다 작거나 같은 값일 경우, 어차피 위치 및 값 모두 새로 넣는게 무조건 이득이므로 마지막 값을 빼고 새로운 값을 넣는식이다. 그렇다면 언제나 리스트의 맨 앞은 이 문제에서 요구하는 최솟값임이 보장된다. 그리고 이렇게 풀면 자바 8과 자바 11로도 통과 가능하다. 차이점은 pq는 delete 연산도 O(logN)이 필요하지만 얘는 당연히 O(1)로 가능하기 때문이다.

for (int i = 0; i < n; i++) {
    Num cur = new Num(Integer.parseInt(st.nextToken()), i);
    while (!ll.isEmpty() && ll.getLast().n >= cur.n) ll.removeLast();	// 리스트의 마지막값이 현재값보다 크거나 같은 동안 빼버린다.
    ll.addLast(cur);	// 새로운값을 넣는다.
    while (!ll.isEmpty() && ll.getFirst().idx <= i-l)   ll.removeFirst();	// 위치 범위 지나간건 버린다.
    sb.append(ll.getFirst().n).append(' ');	// 항상 리스트의 가장 앞쪽값이 문제에서 요구하는 최솟값이다.
}

 

 

코드1 (pq 아닌 버전) : github

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
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));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int l = Integer.parseInt(st.nextToken());
        LinkedList<Num> ll = new LinkedList<>();
        StringBuilder sb = new StringBuilder();
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i < n; i++) {
            Num cur = new Num(Integer.parseInt(st.nextToken()), i);
            while (!ll.isEmpty() && ll.getLast().n >= cur.n) ll.removeLast();
            ll.addLast(cur);
            while (!ll.isEmpty() && ll.getFirst().idx <= i-l)   ll.removeFirst();
            sb.append(ll.getFirst().n).append(' ');
        }
        System.out.println(sb);
    }
}
class Num implements Comparable<Num> {
    int n, idx;
    public Num(int n, int idx) {this.n=n; this.idx=idx;}

    @Override
    public int compareTo(Num ano) {
        return this.n - ano.n;
    }
}

 

 

코드2 (pq 버전) : github

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

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int l = Integer.parseInt(st.nextToken());
        PriorityQueue<Num> pq = new PriorityQueue<>();
        StringBuilder sb = new StringBuilder();
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i < n; i++) {
            pq.add(new Num(Integer.parseInt(st.nextToken()), i));
            while (pq.peek().idx <= i-l)   pq.poll();
            sb.append(pq.peek().n).append(' ');
        }
        System.out.println(sb);
    }
}
class Num implements Comparable<Num> {
    int n, idx;
    public Num(int n, int idx) {this.n=n; this.idx=idx;}

    @Override
    public int compareTo(Num ano) {
        return this.n - ano.n;
    }
}