반응형
반응형
반응형

스프링 시큐리티 공식문서를 참고해서 정리해본 시큐리티 관련 내용

인증(비밀번호)

Password Storage :: Spring Security

스프링 시큐리티에 있는 passwordEncoder인터페이스는 비밀번호의 단방향 변환의 기능을 갖고있다.

유저데이터의 비밀번호 보안 역사

제일 처음 유저데이터의 정보는 평문으로 저장했었다. why? : 유저데이터는 DB에 들어가있으니 DB 어드민이 털리지 않는 이상 문제 없을거라 판단.

이후 악의적인 사용자는 SQL Injection같은 공격을 통해 사용자 이름과 비밀번호가 포함된 데이터를 얻을 방법을 찾아냈고, 이러한 방식으로 데이터의 보안이 뚫리기 시작하자 새로운 암호화 방식인 단방향해시 SHA-256을 통해 정보를 해시화하고 해당 해시를 저장하도록 권장되었다.

이러한 방식은 사용자가 인증을 시도하면 해시된 비밀번호가 입력한 비밀번호의 해시와 비교해서 일치한다면 로그인이 되는데, 이러한 암호화를 뚫기 위해 Rainbow Tables라는 조회 테이블이 생겼고 해당 테이블은 비밀번호의 입력값을 미리 해시화해서 테이블에 넣어두고 역인코딩으로 값을 받아온다

때문에 해당 방식으로도 값을 뚫리게 되고 이를 막기위해 개발자는 소금을 치기 시작한다(특정하지 않은 랜덤한 문자열을 해시문에 포함시킴) 이러한 형식으로 Rainbow Tables 조회테이블에 당하지 않는게 현재 상황이다.

이러한 보안상의 이슈를 스프링 시큐리티에서 자체적으로 지원을 해주는데, 추가로 생각해야 하는 것이 비밀번호 검증을 할 때 의도적으로 리소스(CPU, 메모리, 등등)를 많이 사용하는것이다.

비밀번호를 확인하는데 1초의 지연시간을 주는 이유는 공격자가 암호를 무차별확인을 하면서 해독할 수 있는 시간을 주지 않는것이 주요 목표이다.

때문에 스프링 시큐리티 인증 설정을 할 때 해당 부분을 잘 신경써서 같이 구현해야 할 것이다.

또 위와같이 데이터를 찾는데 시간을 주지않기 위해 장기자격증명이 아닌 단기자격증명으로 지속체크를 하는 것이 좋을 것이다.

 

장기자격증명 → 사용자 이름 및 비밀번호

단기자격증명 → 세션, Oauth토큰

User.UserBuilder users = User.withDefaultPasswordEncoder();

or 

UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
  .username("user")
  .password("password")
  .roles("USER")
  .build();
UserDetails admin = users
  .username("admin")
  .password("password")
  .roles("USER","ADMIN")
  .build();

기본 Deprecated되어있는데 이유는 메모리와 컴파일된 소스코드에 비밀번호가 노출되어있기 때문에 해당 비밀번호를 외부에서 해시를 해야 안전해짐

때문에 사용x

 

위의 사진은 스프링 시큐리티 클래스의 User클래스 내부 코드중 일부인데 이런식으로 생성자를 통해서도 가입이 가능한듯?

패스워드 인코더는 자바 자체에서 지원하는데,

  • bcrypt
  • ldap
  • MD4
  • MD5
  • noop
  • pbkdf2
  • scrypt
  • SHA-1
  • SHA-256
  • sha256

그냥 이런게 있구나하고 인지하면될듯

주요한 것은 스프링시큐리티에 있는

  • BCryptPasswordEncoder
  • Argon2PasswordEncoder
  • Pbkdf2PasswordEncoder
  • SCryptPasswordEncoder

이 네가지 인코더인데 상황이나 여건에 따라서 네가지중 하나를 사용하면서 비밀번호를 확인하는 시간을 약 1초가 걸리도록 지연하게 하는것이 안전하다

이유는 레인보우테이블 공격을 통해 무차별 인코딩확인을 막기 위해.

PasswordEncoder 또한 지원을 하긴 하는데 현재는 안전하지 않다는 것이 검증되었다. 지원하는 이유는 이전 버전에서 만들어진 코드는 해당 인코더를 사용했었기 때문에

만들어진 비밀번호를 변경하는 방법은

http
    .passwordManagement(Customizer.withDefaults())
http
    .passwordManagement((management) -> management
        .changePasswordPage("/update-password")
    )

위의 방식으로 사용하면 될 것 같음

 

추가로

약관에 동의하더라도 개인정보는 평문으로 저장하면 안된다

이 때문에 스프링 시큐리티의 도움을 반드시 받는것이 중요할 것 같다.

혹시나 서비스할 때 항상 잊지않고 생각해야하는 부분

 

비밀번호 해시 관련 지식참조 사이트

https://starplatina.tistory.com/entry/비밀번호-해시에-소금치기-바르게-쓰기

반응형
반응형

빌더는 Capacity와 count를 갖고있다. 빌더 안에서 문자열을 합쳐도 StringBuilder의 capacity를 넘지 않는다면 new 연산을 하지 않음

이유는 아래 설명을 통해 풀어가보겠다

StringBuilder : 기존의 capacity일 때는 새로운 메모리에 문자열을 추가하는 것이 아닌 기존의 메모리에 담기 때문에.

String은 불변하고, 불변성의 특징을 갖기 때문에 문자열을 추가할 때는 두개를 서로 붙여서 새로운 메모리를 할당하는 new 연산이 들어감

추가로 StringBuilder와 기능은 동일한 StringBuffer를 사용한다면 성능이 약간 낮아지는 대신에 Thread-Safe하다는 것까지 체크하면 좋을 것 같다.

문자열 합칠 때 비교

  • String : 무조건 new String(a + b);
  • StringBuilder : a.length+b.length < capacity ? StringBuilder(a + b) : new StringBuilder(a + b);

String클래스의 배열 final byte[]

 

StringBuilder는 추상클래스AbstractStringBuilder를 상속받고, 해당 클래스에서 char[]배열은 final선언이 안되어있음

 

때문에 동작은 아래와 같음

 

결론 : 문자열을 더할 때는 String 대신 StringBuilder나 StringBuffer를 사용하자.

참고 사이트 : https://cjh5414.github.io/why-StringBuffer-and-StringBuilder-are-better-than-String/

https://readystory.tistory.com/141

반응형
반응형

https://school.programmers.co.kr/learn/courses/30/lessons/181914

해당 문제를 람다식 형식과 스트림 방식을 통해 구현해보았다.

이번에 자바의정석을 통해 람다와 스트림을 처음 배워봤는데, 한번에 이해될거라 생각하지 못했지만, 완벽히 이해하지 못해서 공부해보고자 프로그래머스에서 가장 쉬운 난이도인 lv0문제를 대상으로 풀어봤다.

람다식으로 풀었을 때

public int solution(String number) {
        Function<String, Integer> counter = string -> {

        int count = 0;
        for (int i = 0; i < number.length(); i++) {
            count += number.charAt(i) - '0';
        }

        return count % 9;
    };

        return counter.apply(number);
    }

 

스트림으로 풀 때의 다양한 형식

public int solution2(String number) {
        //mapToInt는 10만의 제한때문에 할 수 없음
        return IntStream.range(0, number.toCharArray().length)
                .mapToObj(i -> Character.getNumericValue(number.charAt(i)))
                .reduce(0, Integer::sum) % 9;

        number.chars()
                .map(Character::getNumericValue)
                .reduce(0, Integer::sum);
        return sum % 9;

        return number.chars()
                .map(ch -> Character.getNumericValue(ch))
                .reduce(0, (x, y) -> x + y) % 9;

        return number.chars()
                .map(Character::getNumericValue)
                .reduce(0,(x, y) -> x + y) % 9;

    }

해당 문제는 number라는 String형태로 길이가 10만까지 주어지기에 int형이나 long형태를 사용하지 못하기때문에, mapToInt, mapToLong을 사용하지 못했다

그래서 char배열을 활용해야한다고 생각까지만 하고 해결하지 못하고 gpt한테 물어봐서 해결했다

알고 보니 String에는 chars라는 스트림을 지원해서 해당 스트림을 활용하면 풀 수 있는 문제였다

한동안은 계속 헤맬듯…

반응형
반응형

https://school.programmers.co.kr/learn/courses/30/lessons/120817

stream으로 풀 수 있는 가벼운 문제

이 문제때문에 0레벨을 만만하게 봐버렸다….

import java.util.stream.IntStream;
import java.util.Arrays;

class Solution {
    public double solution(int[] numbers) {
        IntStream intStream = Arrays.stream(numbers);

        return (double)intStream.average().orElseThrow();
    }
}

IntStream의 최종연산인 average를 통해 평균값을 구하고 doubleOptional을 orElseThrows()로 반환했다.

반응형
반응형

공부하며 가볍게 정리

람다


람다식은 함수(메서드)를 간단한 식으로 표현하는 것

람다 작성 방법 : 메서드의 이름과 반환타입을 제거하고 →를 블록{} 앞에 추가한다

ex)

int max(int a, int b){
	return a > b ? a : b;
}
(int a, int b) -> {
	return a > b ? a : b;
}

람다식을 활용하여 사용할 때는 함수형 인터페이스를 통해서 람다를 구현할 수 있어야한다.

interface를 자체적으로 선언해서 활용해도 되고, 기존에 있는 Functions 패키지에 들어있는 여러 클래스의 의미를 명확히 아는 것이 좋을 것 같다. 그 이유는 자바에서 지원하는 내부 클래스에는 함수형 인터페이스를 매개로 동작하는 메서드들이 있는데 그 메서드를 사용하기 위해서는 함수형 인터페이스가 어떻게 활용되는지 의미라도 파악을 해야 사용할 수 있을 것이기 때문이다.

함수형 인터페이스로는 대표적으로 네가지가 있고 바로 아래 쓴 메서드가 해당 함수를 실행하는 메서드이다.

  • Supplier<T> - 공급자 : 매개변수는 없고 반환값만 있다
    • get()
  • Consumer<T> - 사용자 : 매개변수는 있고, 반환값이 없다
    • accept()
  • Function<T, R> - 함수 : 일반적인 함수형태, 하나의 매개변수를 받아 결과를 반환
    • apply()
  • Predicate<T> - 매개변수 하나를 받아서 boolean타입으로 반환한다
    • test()

T는 제네릭타입을 뜻하고, R은 리턴타입을 뜻한다.

추가로 Bi가 붙은 형태가 있는데 다른 방법은 똑같고 매개변수가 두개 들어간다는 의미이다.

 

스트림


스트림은 jdk1.8버전부터 도입이 되었는데, 도입 이유는 Collection 인터페이스의 자식인 List, Set, Map과 배열등은 이전 버전까지는 같은 메서드여도 동작하는 형태가 달랐다. 때문에 이를 통일된 방식으로 처리를 하기 위해 표준화해서 등장한 것이다.

ex) 배열, List, Set, Map의 정렬방식이 서로 다름

장점으로는 데이터 소스를 추상화하여 표준화 된 방식으로 사용이 가능하고, 또 원본의 코드를 수정하지 않기 때문에 코드의 재사용성이 높아진다. 또한 간결하게 표현이 가능해 가독성이 높아진다.

단점으로는 단순한 로직에서 데이터의 양이 적다면 순환하는데 드는 비용이 기본형의 래퍼클래스로 인해 더 크다는 것을 인지할 수 있다.(박싱 언박싱 반복)

스트림은 중간연산, 최종연산이 있고, 유의해야 할 점으로는 스트림으로 변환하면 1회만 사용되고 버려지는데, 이러한 특징으로 인해 최종연산을 실행하면 해당 스트림이 사라지는 것을 인지하고 데이터 처리를 해야한다.

중간연산 : 순차적이지 않고 상황에 맞게 커스텀된다.(스트림의 지연연산특징)

최종연산 : 중간연산을 상황에 맞게 활용 이후 출력된 데이터를 최종처리할 연산을 작성

 

Optional<T> - T타입 객체의 래퍼클래스

  • 해당 객체가 null인지 판단을 하기 때문에 nullSafe하다
  • null체크를 하는데 드는 비용이 줄어든다(if문, try catch 사용필요x)

 

 

반응형
반응형

14494번 다이나믹이 뭐예요?

https://www.acmicpc.net/problem/14494

 

14494번: 다이나믹이 뭐예요?

(1, 1)에서 (n, m)에 도달하는 경우의 수를 구하여라. 단, 경우의 수가 엄청 커질 수 있으므로 경우의 수를 1,000,000,007(=109+7)로 나눈 나머지를 출력한다.

www.acmicpc.net


Comment

DP문제의 기초격 문제

DP는 기초라고해도 왜이리 생각하기 어려운지 모르겠다..


hint

dp배열의 저장값 : 해당 지점에 도달하는 경우의 수

점화식 : 우측, 하단, 우하단 세가지 경우의 수를 활용


Solution

DP의 기초격 문제라 그런가 DP를 조금 맛본 상태에서 접했더니 점화식이 생각났던 얼마안되는 문제

먼저 dp배열을 만들고 해당 dp배열의 상단(우측으로만 진행하는 경우의 수) 하단(아래로만 진행하는 경우의 수)의 값은 도달하는 방법이 한가지 뿐이다. 때문에 먼저 초기화해서 쭉 1로 채워준 후에 해당 값을 가지고 남은 dp배열 모두를 채워나가면 되는데, 점화식은 위에 hint에 적었던 우측, 하단, 우하단 을 받는 이전dp배열을 가져오면 된다.

즉, dp[i][j] = dp[i - 1][j] + dp[i][j-1] + dp[i-1][j-1]; 로 해결이 된다

그림예시

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.*;
import java.util.*;
 
/**
 * dp 다차원배열 도달할 수 있는 경우의 수
 */
public class Main {
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
 
        int n, m;
 
        StringTokenizer st =new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
 
        int[][] dp = new int[1001][1001];
 
        for(int i = 1; i <= n; i++){
            dp[i][1= 1;
        }
 
        for(int i = 1; i <= m; i++){
            dp[1][i] = 1;
        }
        //값 초기화 끝
 
        for(int i = 2; i <= n; i++){
            for(int j = 2; j <= m; j++){
                dp[i][j] = (((dp[i-1][j] + dp[i][j-1]) % 1000000007+ dp[i-1][j-1]) % 1000000007;
            }
        }
 
        System.out.println(dp[n][m]);
        
    }
}
cs

 

반응형
반응형

이번 프로젝트에서 리더보드를 구현하는 부분을 맡게 되었다.

리더보드에서는 해당 유저의 엔티티를 통해 하나의 값을 기준으로 내림차순으로 나열하는 방식이였는데, 때문에 JPA를 통해서 findAll로 받고 java에서 Arrays.sort형식으로 구현하려 했었는데 막상 구현하려고 하니 DB에서 데이터를 findAll로 받아서 Java로 구현한다면

"자바를 통해 다시한번 해당 데이터를 처리해야하기에 메모리에 손해가 있지 않을까?"

라는 의문을 시작으로 그렇다면 DB를 사용해서 정렬을 활용하면 더 빠를까?라는 생각을 통해 기본적으로 pk값인 id는 인덱스가 걸려있어서 속도걱정을 안해도 되겠지만 다른 특정한 값에 인덱스를 넣지 않고 orderByDesc를 해야하는 상황이였기에 이게 빠른지 저게 빠른지 모르는 상태였다.

현재는 프로젝트가 대규모 데이터를 갖고있지는 않기에 얼마나 성능적인 면에서 차이가 있을지는 모르겠어서 내가 할 수 있는 최대한의 방법인 검색을 통해 데이터를 수집해 보았고, 정리를 해 보았다.

  • 해당 데이터가 DB측에서 정리가 가능게 되어있는 가공된 데이터들 orderByDesc, Asc 등등 예외사항이 나올 수 없는 경우에 일반적으로 DB에서 처리하는 것이 빠르고 안정적
  • 위에서 언급했던 자바에서 해당 데이터를 한번 더 처리해야하기에 O(2n)의 시간복잡도를 가지게 될 것이라 생각한다.

 

GPT를 통한 답변도 비슷하게 나왔다.

JPA로 정렬하고 가져오는 것과 JPA의 findAll을 통해서 리스트를 가져온 다음 Java로 메서드를 작성해서 정렬하는것 어느게 더 빨라?
JPA
(Java Persistence API
)를 이용한 데이터베이스 쿼리와 Java의 메서드를 이용한 데이터 정렬은 각각 장단점이 있습니다
다음은 주요 차이점과 고려사항입니다

JPA로 정렬하고 가져오는 방법:
성능 : 데이터베이스에서 최적화된 정렬 알고리즘을 이용하기 때문에 대체로 빠릅니다
네트워크 부하 : 필요한 데이터만 가져올 수 있어서 네트워크 트래픽이 줄어듭니다
메모리 사용량 : 필요한 데이터만 메모리에 로드되므로 효율적입니다

Java로 메서드를 작성해서 정렬하는 방법
성능 : Java에서 처리하는 정렬은 데이터베이스의 최적화된 알고리즘보다 일반적으로 느립니다
네트워크 부하 : 모든 데이터를 가져와야 하므로 네트워크 트래픽이 증가할 수 있습니다
메모리 사용량 : 모든 데이터를 메모리에 로드해야 하므로 메모리 사용량이 늘어납니다

이러한 차이점을 고려하면 일반적으로는 JPA를 이용해서 데이터베이스에서 정렬하고 가져오는 것이 더 효율적입니다
그러나 상황에 따라 다를 수 있으므로
성능 테스트를 통해 가장 적합한 방법을 선택하는 것이 좋습니다

결론은 데이터를 추가로 가공할 것이 아니면 자바에서 findAll로 받아올 필요가 없을 것이라 정리가 되었다.

다음에 정렬할 일이 있다면 더 신중하고 정확하게 어느걸 고를지 판단할 수 있을듯 한 느낌😊

반응형

'호기심 천국 > Spring' 카테고리의 다른 글

스프링 시큐리티 1 (인증)  (0) 2023.09.26
스프링부트 final로 클래스 생성자주입  (0) 2023.07.27
반응형

1680번 쓰레기 수거

https://www.acmicpc.net/problem/1680

 

1680번: 쓰레기 수거

쓰레기장에서 출발한 쓰레기차가 여러 지점들을 방문하며 쓰레기를 모으고 있다. 쓰레기차는 쓰레기장에서 가까운 지점부터 방문하며, 쓰레기를 모으다가 다음과 같은 경우에 쓰레기장으로 돌

www.acmicpc.net


Comment

일반 시뮬레이션 구현문제라서 평소처럼 풀까하다가 자바스럽게 풀어보고싶어서 객체지향(?)적으로 설계를 하기 위해 노력하면서 구현해보았다.

알고리즘을 풀 때 클래스를 잘 활용하지 않아서 생각보다 많이 오래걸렸던 문제


hint

문제를 잘 읽고 테스트케이스 또한 해당 지문에 맞춰서 잘 이해해야한다.


Solution

메모리나 시간이나 상관없이 구현만 잘하면 되는 문제였다. 때문에 클래스를 만들어서 써보기에 더 좋았던 것 같다.

일단 해당 문제의 1, 2, 3번 조건을 항상 잘 생각해서 염두에 두고 조건을 넣는다면 쉽게 풀리는 문제일 것이다.

1번의 시점은 다음 지점으로 이동하기 전이고, 2번의 시점은 쓰레기를 싣기 전, 3번은 가장 마지막. 이렇게 해당 분기를 잘 확인해서 조건처리만 잘 해준다면 문제 해결은 쉽게 될 것이다.

클래스 활용해서 구현하면 속도가 안나온다....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import java.io.*;
import java.util.*;
 
public class p1680 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
 
        int t = Integer.parseInt(br.readLine());
        int weight, point;
        StringTokenizer st;
        StringBuilder sb = new StringBuilder();
 
        while (t-- > 0) {
            st = new StringTokenizer(br.readLine());
            weight = Integer.parseInt(st.nextToken());
            point = Integer.parseInt(st.nextToken());
            p1680TrashCar garbageTruck = new p1680TrashCar(weight, 0);
 
            for (int i = 0; i < point; i++) {
                st = new StringTokenizer(br.readLine());
                int pointDistance = Integer.parseInt(st.nextToken());   //해당 위치의 거리값(원점기준)
                int pointWeight = Integer.parseInt(st.nextToken());     //해당 위치의 쓰레기양
 
                garbageTruck.movePoint(pointDistance); // 해당 포인트로 이동
 
                while (pointWeight > 0) {
 
                    if (!garbageTruck.possibleCollect(pointWeight)) { //용량을 넘게될때
                        garbageTruck.movePoint(0);
                        garbageTruck.movePoint(pointDistance);
                    }
 
                    if (garbageTruck.possibleCollect(pointWeight)) { // 용량이 넘지않으면 True반환
                        pointWeight = garbageTruck.collectTrash(pointWeight); // 쓰레기 수집 -> 해당 위치의 쓰레기는 0이됨
                    }
 
                    if (garbageTruck.isFull() && i != point - 1) { // 마지막 지점이 아닐 때 쓰레기가 꽉차면 수거장에 가서 비우고 와야함
                        garbageTruck.movePoint(0);
                        garbageTruck.movePoint(pointDistance);
                    }
                }
                if (i == point - 1) {
                    garbageTruck.movePoint(0);
                }
            }
            sb.append(garbageTruck.getMeter()).append("\n");
            System.out.println(garbageTruck.getPoints()); // 쓰레기를 담기위해 문제제출할 땐 주석처리해야함
        }
        System.out.println(sb);
    }
}
 
/**
 * 기본 차 인터페이스
 * recentDestination : 목적지에 이동할 때 이동한 거리를 매 순간 담음
 */
interface p1680Car {
 
    public int getMeter();
 
    public void movePoint(int distance);
 
    ArrayList<Integer> recentDestination = new ArrayList<>();
 
    default public void pointAdd(int n) {
        recentDestination.add(n);
    }
 
    default public ArrayList<Integer> getPoints() {
        return recentDestination;
    }
 
    default public void resetDestList() {
        recentDestination.clear();
    }
 
}
 
/**
 * TrashCar는 p15979Car의 상속을 받아 Meter와 movePoint를 반드시 사용해야한다.
 * 추가로 쓰레기를 담을 capacity, 쓰레기의 용량상태확인이 필요한 trashWeight이 필요
 */
class p1680TrashCar implements p1680Car {
    private int capacity;
    private int trashWeight;
    private int meter;
    private int nowLocation;
 
    public p1680TrashCar(int capacity, int nowLocation) {
        this.capacity = capacity;
        this.nowLocation = nowLocation;
        resetDestList();
    }
 
    @Override
    public int getMeter() {
        return meter;
    }
 
    @Override
    public void movePoint(int point) {
        if (point == 0) {
            pointAdd(nowLocation);
            meter += nowLocation;
            nowLocation = 0;
            trashWeight = 0;
            return;
        }
        pointAdd(point - nowLocation);
        meter += point - nowLocation;
        nowLocation += point - nowLocation;
 
    }
 
    public boolean possibleCollect(int weight) {
        if (capacity < trashWeight + weight) {
            return false;
        }
        return true;
    }
 
    public boolean isFull() { // 비었을 때는 꽉차는지 체크
        if (trashWeight >= capacity) {
            return true;
        }
        return false;
    }
 
    public int collectTrash(int weight) {
        trashWeight += weight;
        return 0;
    }
}
cs
반응형
반응형

1417번 국회의원 선거

https://www.acmicpc.net/problem/1417

 

1417번: 국회의원 선거

첫째 줄에 후보의 수 N이 주어진다. 둘째 줄부터 차례대로 기호 1번을 찍으려고 하는 사람의 수, 기호 2번을 찍으려고 하는 수, 이렇게 총 N개의 줄에 걸쳐 입력이 들어온다. N은 50보다 작거나 같

www.acmicpc.net


Comment

자료구조를 활용하는 문제중에서 난이도가 쉬웠기에 여러가지 시도해보기 좋았던 문제같다.

처음엔 그냥 자료구조 클래스만 활용할까 하다가 객체도 같이 써보면 재밌지 않을까 해서 같이 사용해봤다


hint

우선순위 큐를 활용했다.

객체를 사용한다면 해당 객체에 Comparable를 상속받아야함


Solution

전체 입력값 자체가 적어서 시간은 볼 필요 없긴 할듯하다

우선순위 큐를 써봤는데 객체에서는 자동정렬을 계속 해주지 않는건지 처음 입력된 우선순위 큐 객체에서만 투표수를 가져가고 있었다. 때문에 이를 해결하기 위해 우선순위 큐에서 득표 수에 변경이 있을 때마다 큐에서 꺼냈다가 다시 넣어주는 형식으로 반복을 했다.

그래도 객체를 기준으로 우선순위 큐를 다루는게 은근 재밌었던 문제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import java.io.*;
import java.util.PriorityQueue;
import java.util.Queue;
 
public class p1417 {
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
 
        int count = 0;
        int n = Integer.parseInt(br.readLine());
        Queue<Candidate> candiQ = new PriorityQueue<>(n+8);
 
        Candidate dasom = new Candidate(1, Integer.parseInt(br.readLine()));
 
        for(int i = 2; i < n+1;i++){
            int receveVote = Integer.parseInt(br.readLine());
            candiQ.offer(new Candidate(i, receveVote));
        }
 
        if(candiQ.isEmpty()){
            System.out.println(0);
            return;
        }
 
        //기호1번을 1등으로
        while (dasom.getReceveVote() <= candiQ.peek().getReceveVote()){
            candiQ.peek().voteControl(0);
            Candidate temp = candiQ.poll();
            candiQ.offer(temp);
            dasom.voteControl(1);
            count++;
        }
 
        System.out.println(count);
    }
}
 
 
/**
 * 후보자번호, 받은 득표수가 들어있는 후보객체
 * 우선순위큐를 위해 Comparable을 상속받음
 */
class Candidate implements Comparable<Candidate>{
    private final int number;
 
    private int receveVote = 0;
 
    Candidate(int number) {
        this.number = number;
    }
 
    Candidate(int number, int receveVote) {
        this.number = number;
        this.receveVote = receveVote;
    }
 
    @Override
    public int compareTo(Candidate o) {
        //receveVote 내림차순정렬
        return o.receveVote - receveVote;
    }
 
    public int getReceveVote() {
        return receveVote;
    }
 
    public int getNumber() {
        return number;
    }
 
    public void voteControl(int mode){
        if(mode == 0){
            receveVote--;
            return;
        }
        receveVote++;
 
    }
}
 
cs
 
반응형

+ Recent posts