반응형

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

p131123 Level.3 GROUP BY

내가 시도한 방식

SELECT FOOD_TYPE, REST_ID, REST_NAME, MAX(FAVORITES)
FROM REST_INFO
GROUP BY FOOD_TYPE
ORDER BY FOOD_TYPE DESC;

정답

SELECT FOOD_TYPE, REST_ID, REST_NAME, FAVORITES
FROM REST_INFO
WHERE (FOOD_TYPE, FAVORITES) IN 
       (SELECT FOOD_TYPE, MAX(FAVORITES)
        FROM REST_INFO
        GROUP BY FOOD_TYPE)
ORDER BY FOOD_TYPE DESC

서브쿼리를 넣어야 하는 이유 : 두 컬럼의 조건이 일치해야 한다

처음 넣었던 MAX함수만으로 뽑게되면 식당 명과 일치하지 않게 되는 문제가 발생한다.

반응형
반응형

애플리케이션 수준 보안을 지원하는 스프링 시큐리티

스프링 시큐리티는 인프라적으로 가장 최상층에 위치한 애플리케이션 수준 보안을 구현한다

웹 애플리케이션의 일반적인 보안 취약성

  • 인증 취약성
  • 세션 고정
  • XSS(교차 사이트 스크립팅)
  • CSRF(사이트 간 요청 위조)
  • 주입
  • 기밀 데이터 노출
  • 메서드 접근 제어 부족
  • 알려진 취약성이 있는 종속성 이용

인증과 권한 부여의 취약성

  • 인증(Authentication)
    • 애플리케이션을 이용하려는 사람을 식별하는 프로세스
    • 사용자의 ID를 확인하고 나면 권한을 부여하는 프로세스가 시작
  • 권한 부여(Authorization)
    • 인증된 호출자가 특정 기능과 데이터에 대한 이용 권리가 있는지 확인하는 프로세스
    • ex) 대부분의 인증된 사용자는 본인의 계좌에서만 이체가 가능하다
  • 세션 고정(Session fixation)
    • 웹 애플리케이션의 더 구체적이고 심각한 약점
    • 이미 생성된 세션 ID를 재이용해 유효한 사용자를 가장할 수 있다.
    • 취약성 발생이유 : 인증 프로세스 중 고유한 세션ID를 할장하지 않아 기존 세션ID가 재사용될 가능성이 있을 때 발생
    • 공격방식 : 세션ID가 URL에 들어있는 방식으로 세션이 구현되어있을 때 악성 링크를 클릭하도록 유인할 수 있다. 이 때 클릭했을 때 세션 ID가 탈취된다.
      세션 값을 쿠키에 저장하는 경우 피해자의 브라우저가 스크립트를 실행하도록 할 수도 있다.
  • XSS(교차 사이트 스크립팅, Cross Site Scripting)
    • 클라이언트 쪽 스크립트를 주입해 다른 사용자가 이를 실행하도록 한다.
    • 소독 하는 과정을 통해 해결
    • 이 취약성으로 인해 발생되는 문제 : 계정 가장(세션 고정과 결합), DDOS
  • CSRF(사이트 간 요청 위조, Cross-Site Request Forger)
    • 서버가 요청의 출처를 확인하지 않을 때 발생하는 문제(모든 곳에서 요청이 실행될 수 있음)
    • 일반적으로 공격자는 CSRF를 이용해 시스템의 데이터를 변경하는 동작을 실행
  • 웹 애플리케이션의 주입(Injection) 취약성 이해
    • 주입 공격은 광범위한 공격 방식.
    • 공격자는 시스템에 특정 데이터를 주입하는 취약성을 이용
    • 공격의 목표 : 시스템의 데이터 변경, 삭제, 무단 이용을 유발
    • 주입 공격의 유형 : XSS Injection, SQL Injection, XPath Injection, OS Command Injection, LDAP Injection 등
  • 민감한 데이터의 노출 처리하기
    • 공개정보가 아닌 것은 절대 로그에 기록하지 말아야 한다
    • 로그에 기록하지 말아야 하는 예시
      • [오류] 요청의 서명이 잘못되었습니다. 사용할 올바른 키는 X입니다.
      • [경고] 사용자 이름 X와 암호 Y를 이용하여 로그인하지 못했습니다.
      • [정보] 사용자 X가 올바른 암호 Y를 이용하여 로그인했습니다.
    • 애플리케이션에 예외가 발생했을 때 서버가 클라이언트에 반환하는 정보를 주의
    • 로그인의 예시
      • 사용자의 이름이 올바르지 않음, 사용자의 암호가 올바르지 않음
        보다 사용자 이름 또는 암호가 올바르지 않음을 통해 처리하는 것이 정보를 제공하지 않기에 더 바람직함
  • 메서드 접근 제어 부족
    • 컨트롤러 단에서 구성했을 때 나중에 다른 컨트롤러에서 레포지토리 참조시 권한부여가 제대로 안될 수도 있다.
  • 알려진 취약성이 있는 종속성 이용
    • 개발하는 애플리케이션이 아닌 기능을 만들기 위해 이용하는 라이브러리나 프레임워크 같은 종속성에 취약성이 있을 수 있다.
    • 메이븐 또는 그레이들 구성에 플러그인을 추가하면 정적 분석을 수행할 수 있음

OAuth 2 흐름 이해

인증 권한 부여 방식

  • 사용자가 애플리케이션접근 시 백엔드 리소스 호출
  • 권한 부여 서버를 호출해서 토큰을 획득, 자격증명, 갱신 토큰으로 토큰 획득
  • 자격 증명, 갱신 토큰이 올바를 때 새로운 액세스 토큰을 클라이언트로 반환
  • 리소스 호출 시 서버에 요청할 때 헤더에 액세스 토큰을 담아 서비스 이용

토큰 형태로 애플리케이션이 구현되었을 때 이점

  • 클라이언트는 사용자 자격 증명을 저장할 필요 없이 액세스 토큰과 갱신 토큰만 저장하면 된다.
  • 애플리케이션은 사용자 자격 증명을 노출하지 않는다.
  • 토큰을 가로챘을 때 사용자 자격 토큰을 실격시킬 수 있다.
  • 토큰을 사용했을 때는 제삼자가 사용자를 가장하지 않고도 사용자 대신 리소스에 접근할 수 있다. 토큰의 수명은 짧기에 취약성을 악용할 기한이 제한된다.

API 키, 암호화 서명, IP 검증을 이용해 요청 보안

교환되는 메시지를 아무도 변경하지 못하게 하기 위해 백엔드 구성요소간 보안을 거는 방식이 있다.

  • 요청 및 응답 헤더에 정적 키 이용
  • 암호화 서명으로 요청 및 응답 서명
  • IP 주소에 검증 적용

통신의 신뢰성을 테스트하는 제일 좋은 방법은 암호화 서명을 이용하는 것

이 책에서 배울 내용

스프링 시큐리티를 배우는 실용적 접근법 소개

  • 스프링 시큐리티의 아키텍처와 기본 구성 요소 및 이를 이용해 애플리케이션을 보호하는 방법
  • OAuth 2 및 OpenID Connect 흐름과 시큐리티로 인증과 권한 부여를 구현하는 방법
  • 애플리케이션의 다양한 계층에서 스프링 시큐리티로 보안을 구현하는 방법
  • 다양한 구성 스타일과 프로젝트에 맞는 모범 사례
  • 리액티브 애플리케이션에 스프링 시큐리티 이용
  • 보안 구현 테스트

요약

  • 스프링 시큐리티는 스프링 애플리케이션을 보호하기 위한 가장 인기 있는 선택이며 다양한 스타일과 아키텍처에 적용할 수 있는 갖가지 대안을 제공한다
  • 시스템의 계층별로 보안을 적용해야 하며 계층별로 다른 관행을 이용해야 한다.
  • 보안은 소프트웨어 프로젝트를 시작할 때부터 고려해야 하는 공통 관심사다
  • 일반적으로 취약성을 예방하는 투자 비용보다 공격의 대가가 훨씬 크다.
  • 오픈 웹 애플리케이션 보안 프로젝트(OWASP)는 취약성과 보안 관련 사항을 참고할 수 있는 훌륭한 장소다.
  • 종종 아주 작은 실수 때문에 큰 피해가 발생한다.( 로그나 오류 메시지에 민감한 데이터 노출)
반응형
반응형

Chapter01 객체, 설계

step01. 구현

  • 수동적이지 않은 객체
    • 객체의 메서드가 모두 열려있음 - public
    • 하나의 객체(Theater)가 모든 것을 조종함 - Processing(절차지향적)
  • 변경에 취약한 코드
    • 요구사항이 변경 될 가능성
    • 구현된 클래스가 다른 클래스에 의존되어 구현된다.
    • 결합도(coupling)가 높음
public class Theater {
    private TicketSeller ticketSeller;

    public Theater(TicketSeller ticketSeller) {
        this.ticketSeller = ticketSeller;
    }

public void enter(Audience audience) {
        if (audience.getBag().hasInvitation()) {
            Ticket ticket = ticketSeller.getTicketOffice().getTicket();
            audience.getBag().setTicket(ticket);
        } else {
            Ticket ticket = ticketSeller.getTicketOffice().getTicket();
            audience.getBag().minusAmount(ticket.getFee());
            ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
            audience.getBag().setTicket(ticket);
        }
    }
}

Step02. 설계 개선

  • 자율적인 존재로 만들기
    • 기존 : 티켓 판매를 티켓 판매원이 아닌 Theater가 담당함
    • 변경 : 티켓 판매 로직을 티켓 판매원에게 양도
    • TicketSeller{ ... public void sellTo(Audience audience){ if(audienct.getBag().hasInvitation()){ Ticket ticket = ticketOffice.getTicket(); audience.getBag().setTicket(ticket); }else{ Ticket ticket = ticketOffice.getTicket(); audience.getBag().minusAmount(ticket.getFee()); ticketOffice.plusAmount(ticket.getFee90); audience.getBag().setTicket(ticket); } } ... } -> public class Theater { ... public void enter(Audience audience){ ticketSeller.sellTo(audience); } ... }
  • 기존 : bag을 getter로 불러옴
  • 변경 : bag을 private화 + getter삭제 + 공용 인터페이스인 buy()를 bag에 선언
  • public class Audience{ public Bag bag; -> private Bag bag; public Bag getBag() { return bag; } -> public Long buy(Ticket ticket){ if( bag.hasInvitation()) { bag.setTicket(ticket); return 0L; } else { bag.setTicket(ticket); bag.minusAmount(ticket.getFee()); return ticket.getFee(); } }
  • 변화점 : 각 객체들이 맡은 책임에 맞게 일을 행하도록 구현되었다.
  • Theater의 경우를 예로 들었을 때 객체는 다른 객체에게 이 일을 행해달라고 전달했을 뿐이다.

캡슐화와 응집도

  • 객체지향의 핵심은 객체 내부의 상태를 캡슐화하고 객체 간에 오직 공용 인터페이스(메시지)를 통해서만 상호작용하도록 만드는 것
  • 객체가 밀접하게 연관된 작업만을 수행하고 연관성이 없는 작업은 다른 객체에게 위임하는 것이 응집도를 높이는 방법이다.
  • 자신의 데이터를 책임지며 자신의 데이터를 가공하는 것이 객체의 응집도를 높이는 핵심

절차지향과 객체지향

  • 절차적 프로그래밍
    Step01에서는 Theater가 절차지향적인 코드였으며, Process역할을 했었다. 이렇게 Step01의 Theater처럼 별도 모듈에 위치시키는 방식
  • 객체지향 프로그래밍
    Step02에서는 프로세스가 각 객체 안에서 동작하도록 구성했다. ex)class Audience
  • 책임의 이동(shift of responsibility)
    두 방식의 근본적인 차이는 기능을 처리할 때 Theater가 직접 처리를 하는 방식과, Theater가 말을하면 Audience와 TicketSeller가 스스로 처리를 하는 방식으로 구분을 할 수 있다.
    • 책임 집중 : Theater
    • 책임 분산 : buy → Audience, SellTo → TicketSeller

Step03. 추가 개선

TicketOffice

  • 기존 : getTicket, getFee를 통해 티켓 자체를 Office에서 꺼내옴
  • 변경 : sellTicketTo를 통해 기존의 티켓을 꺼내오는 방식 변경
  • class TicketOffice{ ... public Ticket getTicket(){ ... } public void plusAmount(){ ... } ... } -> class TicketOffice{ ... public void sellTicketTo(Audience audience){ plusAmount(audience.buy(getTicket())); } private Ticket getTicket(){ ... } private void plusAmount(){ ... } ... } public TicketOffice getTicketOffice() { return ticketOffice; } -> public class TicketSeller{ public void sellTo(Audience audience){ ticketOffice.sellTicketTo(audience); } }
  • 인터페이스에만 의존하게하는 형태로 변경됨
  • but TicketSeller는 audience를 넘겨주며 서로 의존하게 만듦

트레이드오프

  • 인터페이스에만 의존하는 Audience결합도가 중요한지, audience를 넘겨주며 의존성이 생겨난 것이 더 좋을지 개발자는 트레이드 오프를 해야 할 순간이 온다.
  • 두가지 이슈를 통해 (책에서의)개발팀은 TicketOffice의 자율성을 지켜 의존성 제거를 택했다.

의인화

Theater, Bag, TicketOffice는 실세계에서 자율적인 존재가 아니다. 하지만 객체지향 패러다임에서는 생물과 동일하게 객체로 다뤘다. 이렇게 모든 객체는 능동적이고 자율적인 존재로 바뀌도록 설계하는 원칙을 의인화라고 부른다

설계

설계란 코드를 배치하는 것이다 - Metz12 -

  • 요구사항은 항상 변경된다. → 코드는 항상 변경된다.
  • 설계는 코드를 잘 배치하는 것 → 변경될 때 코드의 변경을 최소화하는 것
  • 코드의 변경을 최소화하려면 → 객체 사이의 의존성을 적절하게 관리하는 것
반응형
반응형

EnumType을 엔티티 컬럼으로 생성할 때 반드시 @Enumerated(EnumType.STRING)을 사용해야 한다.

  • 이넘 타입을 그대로 저장하게되면 ordinal() 형태로 저장된다.
  • ordinal 형태로 저장하면 데이터는 조금 아낄 수 있다. but 혹시나 나중에 추가될 enum타입이 생긴다면, 0, 1, 2, 등 숫자로 파악해야 하므로 타입 데이터가 꼬이게 된다.
  • 때문에 반드시 확실한 매핑을 할 수 있는 @Enumerated(EnumType.STRING) 스트링 형태로 컬럼을 생성하자
반응형
반응형

Chapter07 병렬 데이터 처리와 성능

자바의 병렬

자바 7 이전의 버전에서는 병렬의 처리가 어려웠다.
분할을 위한 스레드 할당 → 동기화 추가 → 결과 합치기의 과정을 통해 병렬 처리가 실행되는데, 각 병렬처리를 한 후에 스레드를 합칠 때 동기화를 적절히 이뤄줘야 교착 상태를 피할 수 있었다.
7버전 부터 지원하기 시작한 Fork/Join 프레임워크를 활용하면 해당 문제를 쉽고 효율적으로 해결할 수 있다. 이 Fork/Join 프레임워크를 활용한 스트림 API와 병렬 처리에 대해서 배워보자.


병렬 스트림

  • 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림
  • 멀티코어 프로세서가 각각의 청크를 처리하도록 한다
  • Collections.parallelStream() or Arrays.stream().parallel()
    • Pipeline에있는 parallel 상태에 true가 저장된다.
    • 이후 파이프라인 처리할 때 처리할 때 parallel 체크를 통해 해결한다.
  • 병렬 처리를 고려할 때는 성능 벤치마킹을 하는 것을 권장
  • 성능 최적화
    • 기본형(primitive) 타입의 경우 기본형 특화 스트림 권장 - 오토박싱의 이유
    • 일반적으로 순차적인 처리가 필요한 스트림의 경우 병렬 비용이 더욱 비싸 느릴 가능성이 높음
    • 순차 처리가 필요없는 경우 findAny같은 순서에 상관 없는 쇼트 서킷, unordered 같은 메서드 사용
    • 하나의 요소를 처리하는 데 드는 비용이 비싸다면 고려
    • 스트림 자료구조의 특성을 확인 ex) LinkedList vs ArrayList 일반적으로 좋음
    • 병합 과정의 비용을 생각해야 한다
    | 소스 | 분해성 |
    | --- | --- |
    | ArrayList | Excellent |
    | LinkedList | Bad |
    | IntStream.range | Excellent |
    | Stream.iterate | Bad |
    | HashSet | Good |
    | TreeSet | Good |
    | Stream.generate | iterate보단 낫다 |

parallel처리 관련 이미지

Untitled

Untitled

Untitled

포크/조인 프레임워크

  • 병렬 작업할 때 사용

  • 재귀적으로 큰 작업을 작은 작업으로 분할한 후 서브태스크의 결과를 합쳐서 전체 결과를 만듦

  • 스레드 풀(ForkJoinPool)의 작업자 스레드에 분산 할당하는 ExecutorService

    • ExecutorService를 왜 언급하는가?

      RecursiveTask를 실제로 활용했을 때 해당 추상 클래스의 부모인 ForkJoinTask에서 ForkJoinPool을 활용해서 실제 활용하고있기 때문

      RecursiveTask → ForkJoinTask에서 사용하는 ForkJoinPoolExcutorService

      Untitled

      Untitled

  • 분할 정복(divide-and-conquer) 알고리즘의 병렬화

  • compute() 메서드 오버라이딩 해서 구현

  • 병렬처리 시 주의점

    • join 호출은 두 서브태스크가 모두 시작된 다음에 해야한다.
    • ForkJoinPool의 invoke메서드는 병렬 계산 시작하는 시점에서 한번만 사용
    • 한쪽은 fork() 한쪽은 compute() ⇒ 같은 스레드 재사용 피하기 위함
    • 디버깅이 어렵다 ⇒ 스택이 아닌 다른 스레드 이기 때문
    • 병렬 처리가 무조건 빠르지 않다.
  • 작업 훔치기(work stealing) 특성을 갖고있음

  • ForkJoin을 활용한 RecursiveTask 예시

import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;

public class ForkJoinSumCalculator extends RecursiveTask<Long> {

    //THRESHOLD 이상의 값 까지만 분해
  public static final long THRESHOLD = 10_000;

  private final long[] numbers;
  private final int start;
  private final int end;

  public ForkJoinSumCalculator(long[] numbers) {
    this(numbers, 0, numbers.length);
  }

  private ForkJoinSumCalculator(long[] numbers, int start, int end) {
    this.numbers = numbers;
    this.start = start;
    this.end = end;
  }

  @Override
  protected Long compute() {
    int length = end - start; //해당 태스크에서 더할 배열의 길이
    if (length <= THRESHOLD) {
      return computeSequentially();
    }
        //각 태스크 분리 left, right
    ForkJoinSumCalculator leftTask =
        new ForkJoinSumCalculator(numbers, start, start + length / 2);
        //생성한 태스크 비동기 실행
    leftTask.fork();
    ForkJoinSumCalculator rightTask =
        new ForkJoinSumCalculator(numbers, start + length / 2, end);
        //두번째 태스크 동기 실행
    Long rightResult = rightTask.compute();
        //비동기 실행했던 left의 결과를 읽거나 처리완료 후 읽기까지 대기
    Long leftResult = leftTask.join();
    return leftResult + rightResult;
  }

    //가장 작은 단위일 때 작은 단위로 쪼갠 태스크의 결과를 계산
  private long computeSequentially() {
    long sum = 0;
    for (int i = start; i < end; i++) {
      sum += numbers[i];
    }
    return sum;
  }

    public static long forkJoinSum(long n) {
    long[] numbers = LongStream.rangeClosed(1, n).toArray();
    ForkJoinTask<Long> task = new ForkJoinSumCalculator(numbers);
    return FORK_JOIN_POOL.invoke(task);
  }
}

//호출 방법
ForkJoinSumCalculator.forkJoinSum(long n));

Spliterator 인터페이스

  • 구성 요소
    • booleaen tryAdvance(Consumer<? super T> action)
      • 요소를 하나씩 순차적으로 돌며 요소가 남아있는지 확인
    • Spliterator trySplit()
      • 일부 요소를 분할해서 새로운 Spliterator 생성
    • long estimateSize()
      • 탐색해야 할 요소 수 정보 제공(RecursiveTask의 THRESHOLD 역할로 보임)
    • int characteristics()
      • 다양한 특성 집합을 포함(ORDERED, DISTINCT 그 외 다수)

7장을 마치며

  • 내부 반복을 통해 명시적으로 스트림 병렬처리
  • 병렬처리가 무조건 빠르지 않음 특성과 병렬처리 후의 결과 등을 잘 확인하여 고려할 것
  • 병렬 처리에는 포크/조인 프레임워크를 활용한다
  • Spliterator를 통해 병렬 처리를 직접 정의할 수 있다.
반응형
반응형

도메인은 특정한 주제나 분야에 대한 지식이나 활동을 가리키는 일을 뜻하는데,
특정 도메인을 전문적으로 사용하되, 이해하기 쉽고, 사용하기 쉬울 수 있도록 개발된 언어

장점

  • 런타임 오버헤드를 발생시키지 않음
  • 외부 DSL 사용 시 플랫폼에 자유로워짐

단점

  • 개발 비용이 비쌈

⇒ 우리가 클린코드를 지향하는 이유와 DSL이 나온 이유가 같다고 생각함

DSL의 예시

  • SQL (데이터베이스 질의 언어)
  • Gradle (빌드 자동화 도구)
  • Query DSL
  • 자바의 Stream API

참고

https://www.jetbrains.com/ko-kr/mps/concepts/domain-specific-languages/

 

도메인 특화 언어(DSL)에 관한 설명 | JetBrains MPS

 

www.jetbrains.com


모던 자바 인 액션

반응형
반응형

올해 처음 접해봤는데, 실제 코딩 테스트와 유사한 방식으로 진행되어서 코테 준비할 때 도움이 많이 되는 것 같습니다!

반응형
반응형

백준 싫은데요

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

접근 방식

전형적인 투 포인터 형태로 햄스터의 몸집을 구멍을 막았을 때 구멍의 크기m에 최대한 가까운 수를 찾아 출력하는 문제


구현 방법

  • 탐색 했을 때 최대값을 찾을 max값
  • 누적합을 계산해 줄 count
  • 투 포인터에 활용할 left, right

⇒ while문 안에서 count의 값이 m보다 크면 left의 값을 count에서 뺀 후 left++, count의 값이 m보다 작거나 같으면 right의 값을 count에 더한 후 right++ 이 때 max의 값와 카운트의 값을 비교해 최댓값을 갱신


풀이

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

public class p25916 {
    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[] a = new int[n];
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i < n; i++) {
            a[i] = Integer.parseInt(st.nextToken());
        }
        //8 10
        //2 2 2 2 11 2 5 2
        //9

        int left = 0;
        int right = 0;
        int max = Integer.MIN_VALUE;
        int count = 0;
        while (right < n) {
            if (count +a[right] <= m) {
                count += a[right];
                max = Integer.max(max, count);
                right++;
            }else{
                count -= a[left];
                left++;
            }
        }

        System.out.println(max);

    }
}

후기

투 포인터에 익숙해지기 좋은 문제였다

반응형
반응형

백준 계란으로 계란치기

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

 

16987번: 계란으로 계란치기

원래 프로그래머의 기본 소양은 팔굽혀펴기를 단 한 개도 할 수 없는 것이라고 하지만 인범이는 3대 500을 넘기는 몇 안되는 프로그래머 중 한 명이다. 인범이는 BOJ에서 틀린 제출을 할 때마다 턱

www.acmicpc.net

백준 골드5 계란으로 계란치기

접근 방식

Egg 클래스를 선언해서 왼쪽부터 순서대로 들어서 해결

모든 경우의 수를 탐색해야 하기에, 백트래킹으로 진행


구현 방법

  • Egg클래스
    • isBroken : 깨져있는지 확인
    • breakEgg : 대상이 깨져있는지 확인 후 공격
    • attackEgg : 서로 공격
  • 깊은 복사를 위한 deepCopy 메서드
  • 백트래킹

알을 꺼내는 방식은 순차적
꺼낸 알을 갖고 모든 알들에 시도해보기 (모든 경우의 수 탐색)
이후 최종 깊이에 도달하면 Egg의 깨져있는 수를 카운트


풀이

package Baekjoon.gold;

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

public class p16987 {

    static int n;
    static int max = 0;

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

        n = Integer.parseInt(br.readLine());
        ArrayList<Egg> eggList = new ArrayList<>();
        StringTokenizer st;

        for (int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            eggList.add(
                    new Egg(
                            Integer.parseInt(st.nextToken()),
                            Integer.parseInt(st.nextToken())
                    )
            );
        }

        if (n == 1) {
            System.out.println(0);
            return;
        }
        boolean[] visited = new boolean[n];
        dfs(eggList, 0, 0);

        System.out.println(max);

    }

    public static void dfs(List<Egg> prevEggList, int index, int depth) {
        if (depth == n) {
            //전체 브로큰 횟수 출력
            max = Integer.max(brokenCount(prevEggList), max);
            return;
        }

        for (int i = 0; i < n; i++) {
            List<Egg> nowEggList = deepCopy(prevEggList);
            Egg nowEgg = nowEggList.get(depth);
            if (i != depth) {
                nowEgg.breakEgg(nowEggList.get(i));
                dfs(nowEggList, 0, depth + 1);
            }
        }

    }

    static int brokenCount(List<Egg> eggList) {
        int count = 0;
        for (int i = 0; i < n; i++) {
            if (eggList.get(i).isBroken()) {
                count++;
            }
        }
        return count;
    }

    static class Egg {
        int durability;
        int weight;

        Egg(int durability, int weight) {
            this.durability = durability;
            this.weight = weight;
        }

        public void breakEgg(Egg egg) {
            if (!egg.isBroken() && !this.isBroken()) {
                attackEgg(egg);
            }
        }

        private void attackEgg(Egg egg) {
            int a = this.weight;
            int b = egg.weight;
            this.durability -= b;
            egg.durability -= a;
        }

        private boolean isBroken() {
            if (durability <= 0) {
                return true;
            }
            return false;
        }

    }

    public static List<Egg> deepCopy(List<Egg> eggList) {
        ArrayList<Egg> tempList = new ArrayList<>();
        for (int i = 0; i < eggList.size(); i++) {
            Egg temp = eggList.get(i);
            tempList.add(new Egg(temp.durability, temp.weight));
        }
        return tempList;
    }
}

후기

문제 구현이 생각보다 어려웠는데 이유를 모르겠음…
굳이 이유를 꼽자면 Egg의 상태를 확인해줄 때 헷갈렸던 것 같다.

속도는 클래스를 활용해서 그런지 상당히 느림 매번 깨져있는지 확인하면서 더 돌아서 느린듯?

반응형
반응형

백준 문자열 잘라내기

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

 

2866번: 문자열 잘라내기

첫 번째 줄에는 테이블의 행의 개수와 열의 개수인 R과 C가 주어진다. (2 ≤ R, C ≤ 1000) 이후 R줄에 걸쳐서 C개의 알파벳 소문자가 주어진다. 가장 처음에 주어지는 테이블에는 열을 읽어서 문자

www.acmicpc.net

접근 방식

문제 이해가 엄청 오래 걸렸던 문제..

1000의 범위이기에 Set만 사용해서 처리해도 풀리는 문제 But 공부하기 위해 이분탐색으로 구현!
각 행을 지울 때 마다 바뀌는 세로문자열에 중복값을 있는지 없는지 찾는 문제


구현 방법

  • 세로 문자열 전체를 크게 저장할 StringBuilder[] 배열
  • 중복체크를 위한 Set자료구조
  • 중복에 걸린 것을 확인하기 위한 boolean값 flag

⇒ center는 행의 수라는 ****점을 확실하게 인지하고 진행한다면 할만할 것
행의 수를 center로 잡고 중복 값이 걸렸다면 행이 잘리기 이전 값도 똑같이 중복이란 점을 생각 →

flag 작동 시 right = center - 1;

flag 미작동 시 중복이 걸리지 않았기 때문에 left = center + 1;


풀이

package Baekjoon.gold;

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

//행을 지울때 마다 그 때의 세로문자열들 중에 중복값이 있느냐
public class p2866 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        StringTokenizer st = new StringTokenizer(br.readLine());
        int r = Integer.parseInt(st.nextToken());
        int c = Integer.parseInt(st.nextToken());

        char[][] a = new char[r][c];

        for (int i = 0; i < r; i++) {
            a[i] = br.readLine().toCharArray();
        }

        StringBuilder[] sb = new StringBuilder[c];

        for (int i = 0; i < c; i++) {
            sb[i] = new StringBuilder();
        }
        for (int i = 0; i < c; i++) {
            for (int j = 0; j < r; j++) {
                sb[i].append(a[j][i]);
            }
        }

        if (r == 2) {
            System.out.println(0);
            return;
        }

        int left = 0;
        int right = r;
        int minValue = Integer.MAX_VALUE;
        boolean flag;
        //center는 행의 수
        while (left <= right) {
            flag = false;
            int center = (left + right) / 2;
            Set<String> set = new HashSet<>();
            for (int i = 0; i < c; i++) {
                String s = sb[i].substring(center, r);
                if (!set.add(s)) {
                    minValue = center;
                    flag = true;
                }
            }

            if (flag) {
                right = center - 1;
            } else {
                left = center + 1;

            }
        }
        if (minValue == Integer.MAX_VALUE) {
            System.out.println(0);
        } else {
            System.out.println(minValue - 1);
        }
    }
}

후기

의외로 이분탐색의 아이디어보다 문제 이해하는데 더 힘들었던 문제… 이분탐색인걸 알고 풀어서 그런 것 같긴 하다. 또 Set만을 사용해도 풀리는 것을 확인했는데 효율이 매우 좋지 않았었음

반응형

+ Recent posts