제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁

스프링 정리를 위한 포스팅입니다.
해당 포스팅은 @Controller, @Service, @Repository의 역할과 특징 정리입니다.

 

도입

Spring 프레임워크는 계층구조로 코드를 나누어 각 계층마다 역할을 분리하였습니다.(객체 지향적 특징)
이와 같은 계층 구조는 코드의 역할 분리를 명확히 하고 유지보수성을 높이는 데 중요한 역할을 합니다.

  • Presentation Layer: 클라이언트의 요청 처리
  • Business Layer: 비즈니스 로직 처리
  • Persistence Layer: 데이터 접근 처리

클라이언트와의 요청 처리는 @Controller, 비즈니스 로직 처리는 @Service, 데이터베이스와의 통신은 @Repository를 통해 코드를 구성합니다. 

 

이번 포스팅에서는 @Controller, @Service, @Repository의 역할과 특징을 살펴보겠습니다.

 

@Controller

@Controller는 Spring MVC에서 컨트롤러 클래스를 정의할 때 사용되는 어노테이션입니다. 일반적으로 뷰 템플릿을 반환하는 방식으로 클라이언트의 요청을 처리합니다. 이 어노테이션이 있는 클래스는 HTTP 요청을 처리하고, Model 객체를 사용하여 데이터를 뷰에 전달합니다. @Controller는 주로 웹 애플리케이션에서 서버 사이드 렌더링을 위한 컨트롤러로 사용됩니다.

@Controller 구조

  1. 클라이언트로부터 받은 요청을 통해 Handler 어댑터를 통해 해당 요청을 처리할 수 있는 controller를 찾습니다.
  2. controller는 service와 repository의 비즈니스 로직을 처리한 후 뷰 이름을 handlerAdapter에게 전달합니다.
  3. Dispatcher Servlet은 해당 뷰 네임에 해당하는 뷰를 ViewResolver를 통해서 뷰를 반환합니다.
@Controller
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping("/users")
    public String getUsers(Model model) {
        model.addAttribute("users", userService.findAll());
        return "userList"; // "userList" 뷰 반환
    }
}

userList.html

 

@Controller + @RequestBody

@RequestBody는 HTTP 요청 본문을 자바 객체로 변환하여 매핑해주는 어노테이션입니다. 이 어노테이션을 @Controller와 함께 사용하면, 클라이언트에서 전달한 JSON, XML 등의 데이터를 객체로 받을 수 있습니다. @Controller는 뷰를 반환하는 역할을 계속하고, @RequestBody는 데이터를 뷰에 매핑하는 역할을 합니다.

@Controller + @RequestBody 구조

  1. 클라이언트로부터 받은 요청을 통해 Handler 어댑터를 통해 해당 요청을 처리할 수 있는 controller를 찾습니다.
  2. controller는 service와 repository의 비즈니스 로직을 처리한 후 뷰 이름을 handlerAdapter에게 전달합니다.
  3. 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환됩니다. 이는 @ResponseBody 어노테이션으로 개발됩니다.
@Controller
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    /**
     * Handles POST requests to create a new user from JSON data and renders the "userDetail" view.
     *
     * @param user the user data from the request body
     * @param model the model to hold attributes for the view
     * @return the name of the view to be rendered
     */
    @PostMapping("/users")
    public String createUser(@RequestBody User user, Model model) {
        User savedUser = userService.save(user);
        model.addAttribute("user", savedUser);
        return "userDetail"; // Return the name of the view
    }
}

성공적인 response

 

@RestController

@RestController는 @Controller와 @ResponseBody를 결합한 어노테이션입니다. @RestController가 적용된 클래스는 RESTful 웹 서비스를 제공하며, 메서드의 반환 값이 바로 HTTP 응답 본문으로 전송됩니다. 따라서 @RestController는 일반적으로 JSON, XML과 같은 데이터를 직접 반환하는 데 사용됩니다. 뷰를 반환하지 않으며, 주로 API 서버에서 사용됩니다.

@RestController

  1. 클라이언트로부터 받은 요청을 통해 Handler 어댑터를 통해 해당 요청을 처리할 수 있는 controller를 찾습니다.
  2. controller는 service와 repository의 비즈니스 로직을 처리한 후 뷰 이름을 handlerAdapter에게 전달합니다.
  3. 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환됩니다. 이는 @RestController 어노테이션으로 개발됩니다.
@RestController
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    /**
     * Handles GET requests to retrieve the list of users as JSON.
     *
     * @return a list of users
     */
    @GetMapping("/json-users")
    public List<User> getUsers() {
        return userService.findAll();
    }
}

 

@Service

@Service 어노테이션은 서비스 계층에 사용됩니다. 서비스 계층은 비즈니스 로직을 처리하는 곳으로, 일반적으로 데이터베이스에서 데이터를 조회하고, 가공하여 컨트롤러로 전달하는 역할을 합니다. @Service는 주로 서비스 클래스를 나타내며, @Component의 특성을 그대로 가지고 있어 Spring 컨테이너에 의해 관리됩니다.

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;

    public List<User> findAll() {
        return userRepository.findAll();
    }
}

 

 

@Repository

@Repository는 데이터 접근 계층에 사용됩니다. 이 어노테이션은 주로 데이터베이스와 상호작용하는 클래스를 나타냅니다. @Repository 어노테이션을 사용하면 Spring이 해당 클래스를 자동으로 데이터 접근 객체(DAO)로 인식하고, 예외 변환 기능을 제공합니다. @Repository는 @Service와 동일하게 @Component의 특성을 그대로 가지고 있어 Spring 컨테이너에 의해 관리되며, 데이터베이스와의 상호작용을 담당합니다.

// In-memory Database
@Repository
public class ItemRepository {

    private static final Map<Long, Item> store = new HashMap<>(); //static
    private static long sequence = 0L; //static

    public Item save(Item item) {
        item.setId(++sequence);
        store.put(item.getId(), item);
        return item;
    }
}


// JPA
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findAll();
}

 

 

코드

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁

스프링 정리를 위한 포스팅입니다.
해당 포스팅은 Lombok 어노테이션 입니다.

 

Lombok이란?

Lombok은 Java 애플리케이션에서 보일러플레이트 코드(반복적인 코드)를 줄여주는 라이브러리입니다.

이를 통해 코드의 가독성을 높이고 유지보수를 쉽게 만들어 줍니다.

왜 Lombok을 사용해야 하는가?

  • Java의 기본 클래스는 자주 사용되는 메서드(생성자, getter, setter, toString 등)수동으로 작성해야 합니다.
  • Lombok은 이러한 반복적인 코드 생성을 자동화하여 개발 생산성을 크게 향상시킵니다.

Lombok 어노테이션 종류

@Getter / @Setter

  • 클래스의 모든 필드에 대해 getter와 setter 메서드를 자동으로 생성합니다.
@Getter @Setter
public class Person {
    private String name;
    private int age;
}

@ToString

  • 객체의 필드를 문자열로 변환하는 toString() 메서드를 자동으로 생성합니다.
@ToString 
public class Person { 
    private String name; 
    private int age; 
}

@NoArgsConstructor / @AllArgsConstructor

  • 기본 생성자 및 모든 필드를 초기화하는 생성자를 자동으로 생성합니다.
@NoArgsConstructor
@AllArgsConstructor
public class Animal {
    int age;
    String name;
}

@Builder

  • 빌더 패턴을 적용하여 객체 생성 시 가독성을 높입니다.
@Builder
public class Animal {
    int age;
    String name;
}

 

@Data

  • @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 모두 포함하는 어노테이션입니다. 주로 DTO 객체에 사용됩니다.
@Data
public class Plant {
    private int height;
    private String origin;

    public Plant(int height, String origin) {
        this.height = height;
        this.origin = origin;
    }
}

Lombok 설치방법

spring에서 lombok을 설치하기 위해서는 maven 또는 gradle을 사용하면 됩니다. 저는 gradle을 사용하겠습니다. 

build.gradle에서 compileOnly 'org.projectlombok:lombok'annotationProcessor 'org.projectlombok:lombok'

를 설정한 후에 gradle 프로젝트를 reload 하면 됩니다.

dependencies {
	// ...
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
}

예제 확인

public class Test {
    public static void main(String[] args) {
        // @Getter, @Setter
        Person person = new Person(1, 3, 4, 5, 8);
        System.out.println(person.getShoes()); // getShoes() automatically generated
        person.setShoes(84); // setShoes() automatically generated
        System.out.println(person.getShoes());

        // @ToString
        System.out.println("person.toString: " + person.toString()); // toString() automatically generated

        // @NoArgsConstructor / @AllArgsConstructor
        Animal tomy = new Animal(20, "tomy");
        Animal aymee = new Animal(); // Default constructor automatically generated
        System.out.println("tomy = " + tomy); // toString() automatically generated
        System.out.println("aymee = " + aymee); // toString() automatically generated

        // @Builder
        Animal hiro = Animal.builder().age(7).name("hiro").build(); // Using builder pattern
        System.out.println("hiro.toString() = " + hiro); // toString() automatically generated

        // @EqualsAndHashCode, @RequiredArgsConstructor
        Plant plant1 = new Plant(2, "korea");
        Plant plant2 = new Plant(2, "korea");

        // @Data
        System.out.println("plant1 equals plant2: " + plant1.equals(plant2)); // true, since fields are the same
        System.out.println("plant1 hashCode: " + plant1.hashCode()); // Same hashCode for equal objects
        System.out.println("plant2 hashCode: " + plant2.hashCode()); // Same hashCode for equal objects

        // @RequiredArgsConstructor automatically generates a constructor with final fields
        Plant plant3 = new Plant(3, "japan"); // Constructor with height and origin fields
        System.out.println("plant3: " + plant3); // toString() automatically generated
    }
}

결과 확인

다음과 같이 모든 어노테이션에 대해 결과를 확인할 수 있습니다.

 

코드

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁

자바 정리를 위한 포스팅입니다.
해당 포스팅은 어노테이션 정리입니다.

 

등장 배경

과거에는 소스파일은 java파일 설정 값을 XML파일에 명시하여 관리했습니다. 변경될 수 있는 데이터들을 코드가 아닌 외부 설정 파일(XML)에 분리하기 때문에, 재컴파일 없이도 쉽게 변경사항을 저장할 수 있었습니다.

하지만, 프로그램 작성을 위해 매번 많은 설정을 해야하며, 수 많은 설정파일들을 관리해야 했습니다.

 

그에 따라서 설정파일을 업데이트를 하지 않아 코드의 실행동작과 같은 부분들이 달라지거나 개발에 대해서 불편함이 많았습니다.

source code + 설정 파일

이렇듯 하나의 파일 내에서 코드와 설정을 관리할 수 있는 어노테이션이 등장했는데

 

자바 어노테이션(Java Annotation)은 자바 소스 코드에 추가하여 사용할 수 있는 메타데이터의 일종으로. @기호를 앞에 붙여서 사용하고, JDK 1.5 버전 이상에서 사용 가능합니다. 

 

어노테이션의 역할로는 컴파일러에게 코드 작성 문법 에러 체크하도록 정보제공, 런타임에 특정 기능을 실행하도록 정보 제공 등 하고 있습니다.

 

어노테이션 종류

어노테이션의 종류로는 세가지가 있는데,

  1. 표준 어노테이션
  2. 메타 어노테이션
  3. 사용자 정의 어노테이션

이 존재합니다

표준 어노테이션

표준 어노테이션은 자바가 기본적으로 제공해주는 어노테이션을 의미하는 것으로,

@Override, @Deprecated, @SuppressWarning, @FunctionIInterface 네 가지 어노테이션이 가장 많이 사용됩니다.

/**
 * {@code @Override} 어노테이션은 자식 클래스에서 부모 클래스의 메서드를 오버라이드할 때 사용됩니다.
 * 부모 클래스의 메서드와 일치하지 않으면 컴파일 에러가 발생합니다.
 */
class Parent {
    void run() {}
}

class Child extends Parent {
    @Override // 컴파일 에러! 잘못된 오버라이드
    void wrongOverride() {}
}


/**
 * {@code @Deprecated} 어노테이션은 해당 메서드, 클래스, 또는 필드가 더 이상 사용되지 않음을 나타냅니다.
 * 이 어노테이션이 붙은 요소는 향후 버전에서 제거될 수 있으므로, 사용을 피해야 합니다.
 */
@Deprecated
public int getInt() {
    return 1;
}

 

오버라이드 어노테이션은 잘못된 오버라이딩을 검사해주어 컴파일 에러를 야기하는 어노테이션이고, 

Deprecated 어노테이션은 개발자에게 사용하지 않는 것을 권장하는 필드나 메서드를 알리는 데에 사용하는 어노테이션입니다. 

메타 어노테이션

어노테이션을 위한 어노테이션으로 어노테이션을 정의할 때 사용 그 예시로 Target, Retention, Documented등이 있습니다.

 

Target은 어노테이션을 정의할 때 적용대상을 지정하는데 사용하는 어노테이션이며 Retention은 어노테이션이 유지되는 기간을 지정하는데 사용하는 어노테이션입니다.

/**
 * {@code @CustomAnnotation}은 메타 어노테이션 {@code @Retention}과 {@code @Target}을 사용한 예제입니다.
 * 이 어노테이션은 클래스에만 적용될 수 있으며, 런타임 동안 유지됩니다.
 */
@Retention(RetentionPolicy.RUNTIME)  // 이 어노테이션은 런타임까지 유지됩니다.
@Target(ElementType.TYPE)           // 이 어노테이션은 클래스, 인터페이스 또는 열거형에만 적용됩니다.
@interface CustomAnnotation {
    String value() default "Default Value";
}

@CustomAnnotation(value = "Hello, Metaannotations!")  // CustomAnnotation을 클래스에 적용
public class MetaAnnotation {
    public void display() {
        System.out.println("Sample class method.");
    }
}

Source는 소스파일에만 존재하는것. Runtime은 클래스파일에만 존재하는것. 실행시에 사용가능하고 Inherited는 어노테이션의 상속에 필요한 어노테이션입니다.

사용자 정의 어노테이션

어노테이션은 커스텀하여 사용할 수도 있는데, 다음과 같이 어노테이션을 생성하고, 사용하면됩니다

만약에 다른 폴더에 존재하는 파일이면 어노테이션을 import 하면 됩니다.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * {@code @LogExecutionTime} 어노테이션은 메서드의 실행 시간을 측정하고 로그로 출력하는 데 사용됩니다.
 * 이 어노테이션은 메서드에만 적용됩니다.
 */
@Retention(RetentionPolicy.RUNTIME)  // 이 어노테이션은 런타임 동안 유지됩니다.
@Target(ElementType.METHOD)         // 이 어노테이션은 메서드에만 적용됩니다.
public @interface LogExecutionTime {
}


// 다른 폴더
public class Sample {

    /**
     * 이 메서드는 실행 시간을 측정할 대상입니다.
     * {@code @LogExecutionTime} 어노테이션을 통해 실행 시간을 로깅합니다.
     */
    @LogExecutionTime
    public void performTask() {
        try {
            // 임의의 작업 (2초 지연)
            Thread.sleep(2000);
            System.out.println("Task performed.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

특징

  • 적용시 값을 지정하지 않으면, 사용될 수 있는 기본값을 지정할 수 있다.
  • 요소가 하나이고 이름이 value일 때는 요소의 이름 생략가능하다.
  • 요소의 타입이 배열인 경우, 괄호{}를 사용해야 한다.

규칙

  • 요소의 타입은 기본형, String, enum, 어노테이션, Class만 허용된다.
  • 괄호()안에 매개변수를 선언할 수 없다.
  • 예외를 선언할 수 없다.
  • 요소의 타입을 매개변수로 정의할 수 없다.(<T>)

코드

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

차량들의 진입점과 출구점이 주어졌을 때, 각 차량들이 겹치지 않도록 선택하는 것은 최대한 적은 공간을 차지하면서 차량들을 배치하는 문제입니다. 이를 해결하기 위해서는 먼저 고속도로를 나간 지점이 빠른 차량을 우선적으로 선택하는 것이 중요합니다.

소스코드

import java.util.*;

class Solution {
    static class Car implements Comparable<Car> {
        int src, desc;
        public Car(int src, int desc) {
            this.src = src;
            this.desc = desc;
        }
        
        @Override
        public int compareTo(Car c) {
            return this.desc - c.desc;
        }
    }
    
    public int solution(int[][] routes) {
        List<Car> cars = new ArrayList<>();
        for(int[] route: routes) {
            Car newCar = new Car(route[0], route[1]);
            cars.add(newCar);
        }
        
        Collections.sort(cars);
        
        int answer = 0;
        int last = -1;
        for(Car cur: cars) {
            if(last == -1) {
                last = cur.desc;
                answer++;
            } else if (cur.src <= last && cur.desc >= last) {
            } else {
                last = cur.desc;
                answer++;
            }
        }
        
        return answer++;
    }
}

코드 설명

  • Car 클래스
    Car 클래스는 각 차량의 진입 구간(src)과 출구 구간(desc)을 나타냅니다.
    • 이 클래스는 Comparable<Car>를 구현하여 Car 객체들을 desc (출구 구간) 기준으로 오름차순 정렬하도록 정의됩니다.
    • compareTo 메서드는 출구 구간 desc 값 기준으로 비교하여 정렬 순서를 결정합니다.
  • Car 객체 생성 및 리스트에 추가:
    • routes 배열을 순회하여 Car 객체를 생성하고, 각 차의 진입점(src)과 출구점(desc)을 Car 객체에 저장한 후 cars 리스트에 추가합니다.
  • 정렬:
    • cars 리스트를 desc 값(출구 지점)을 기준으로 오름차순 정렬합니다.
    • 정렬 기준이 desc 값이므로, 먼저 출구가 빠른 차량부터 선택하게 됩니다. 이는 그리디 알고리즘에서 가장 중요한 부분입니다.
  • 차량 선택:
    • 차량을 하나씩 순차적으로 살펴보면서, 겹치지 않도록 선택합니다.
    • last는 마지막으로 선택된 차량의 출구점을 추적하는 변수입니다. 처음에는 -1로 초기화합니다.
    • for문을 통해 각 차량의 구간을 확인하면서, 다음 조건에 맞는 차량만 선택합니다:
      • last == -1: 첫 번째 차량이므로 last를 그 차량의 출구점으로 설정하고 차량을 선택합니다.
      • cur.src <= last && cur.desc >= last: 현재 차량의 진입점이 last보다 작거나 같고, 출구점이 last보다 크거나 같은 경우는 이미 다른 차량이 선택된 구간과 겹친다는 의미입니다. 이 경우에는 차량을 선택하지 않고 넘어갑니다.
      • 그렇지 않으면, 현재 차량을 선택하고, last를 해당 차량의 출구점(desc)으로 갱신합니다.
  • 결과 반환:
    • 최종적으로 선택된 차량의 수는 answer 변수에 저장됩니다.
    • 마지막에 answer++는 answer를 증가시키는 부분인데, 이 부분은 잘못된 코드입니다. return answer로 해야 합니다. answer++는 잘못된 값이 반환될 수 있습니다.
제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

가장 최소의 코스트로 모든 다리를 연결하여야 하므로 최소 신장 트리를 구하는 문제입니다.

최소 신장 트리를 구하기 위한 크루스칼 알고리즘을 활용하여 문제를 풀어줍니다.

소스코드

import java.util.*;

class Solution {
    
    class Bridge implements Comparable<Bridge> {
        int src, dest, cost;
        
        public Bridge(int src, int dest, int cost) {
            this.src = src;
            this.dest = dest;
            this.cost = cost;
        }
        
        @Override
        public int compareTo(Bridge b) {
            if(this.cost == b.cost) {
                return this.src - b.src;
            }
            return this.cost - b.cost;
        }
    }
    int[] parents;
    
    public int findParent(int n) {
        if (parents[n] != n) {
            parents[n] = findParent(parents[n]);
        }
        return parents[n];
    }
    
    public void union(int a, int b){
        a = findParent(a);
        b = findParent(b);
        
        if (a < b) {
            parents[b] = a;
        } else {
            parents[a] = b;
        }
    }
    
    public int solution(int n, int[][] costs) {
        int answer = 0;
        int len = costs.length;
        List<Bridge> bridges = new ArrayList<>();
        
        for(int i = 0; i < len; i++){
            Bridge newBridge = new Bridge(costs[i][0], costs[i][1], costs[i][2]);
            bridges.add(newBridge);
        }
        Collections.sort(bridges);
        parents = new int[n+1];
        for(int i = 0; i <= n; i++) {
            parents[i] = i;
        }
        
        for(Bridge bridge: bridges) {
            if (findParent(bridge.src) != findParent(bridge.dest)) {
                union(bridge.src, bridge.dest);
                answer += bridge.cost;
            }
        }
        
        return answer;
    }
}

코드 설명

  • Bridge 클래스
    섬 간의 다리를 나타내는 클래스입니다.
    • src: 다리의 출발 섬.
    • dest: 다리의 도착 섬.
    • cost: 다리의 건설 비용.
    • Comparable 인터페이스를 구현하여, 비용이 낮은 순서대로 정렬되도록 합니다. 비용이 같을 경우 src의 값을 기준으로 정렬됩니다.
  • findParent 메서드
    findParent는 주어진 노드 n의 대표 부모 노드를 찾는 재귀 메서드입니다.
    • 경로 압축(Path Compression) 기법을 사용하여, 트리의 높이를 줄이고 효율성을 높입니다.
  • union 메서드
    • 두 노드 a와 b를 하나의 집합으로 합칩니다. 두 집합의 대표 부모를 찾은 후, 더 작은 번호의 노드가 부모가 되도록 합니다.
    • 이를 통해, 서로 다른 집합을 병합하면서 트리 구조를 형성합니다.
  • Solution 메서드
    • Bridge 객체 리스트 bridges에 다리 정보를 추가하고 비용 순으로 정렬합니다.
    • parents 배열을 초기화하여 각 섬의 부모를 자기 자신으로 설정합니다.
    • for 반복문을 통해 비용이 낮은 다리부터 탐색하여, 두 섬이 다른 집합에 속해 있을 경우 union 연산을 수행하고 다리 비용을 answer에 더합니다.
    • 최소 비용으로 모든 섬을 연결할 수 있는 다리들의 총 비용을 answer로 반환합니다.
제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

그리디 문제는 대부분 정렬과 조합을 이룬 문제가 많습니다. 따라서 가장 무거운 사람과 가장 무게가 작은 사람을 태워야 최대 limit를 채울 수 있습니다.

소스코드

import java.util.*;

class Solution {
    public int solution(int[] people, int limit) {
        int answer = 0;
        Arrays.sort(people);
        int index = 0;
        
        for (int i = people.length - 1; i >= index; i--) {
            if (people[i] + people[index] <= limit) {
                index++;
            }
            answer++;
        }

        return answer;
    }
}

코드 설명

  • 초기화
    • answer: 최소 보트 수를 저장하는 변수로 초기값은 0입니다.
    • index: 가장 가벼운 사람의 인덱스를 나타내는 변수로 초기값은 0입니다.
    • Arrays.sort(people): people 배열을 오름차순으로 정렬하여 무게가 가벼운 사람부터 무거운 사람 순으로 정렬합니다.
  • for (int i = people.length - 1; i >= index; i--) { if (people[i] + people[index] <= limit) { index++; } answer++; }
    • i는 가장 무거운 사람의 인덱스를 가리킵니다. i는 배열의 끝에서 시작하여 index까지 감소합니다.
    • if (people[i] + people[index] <= limit): 가장 무거운 사람(people[i])과 가장 가벼운 사람(people[index])의 합이 limit 이하인지 확인합니다.
      • 조건이 true인 경우, 두 사람을 한 보트에 태울 수 있으므로 index를 1 증가시켜 다음으로 가벼운 사람을 가리키도록 합니다.
    • answer++: 보트를 사용한 횟수를 증가시킵니다. 무거운 사람(people[i])은 항상 보트에 태워지므로 answer는 무조건 증가합니다.
  • return answer;
    • 최소 보트 수를 반환합니다.
제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

연속된 숫자들 중에 가장 큰 수를 구하는 문제였습니다. 스택을 이용하여 나왔던 숫자들 중에 가장 큰 수를 구하였습니다.

소스코드

import java.util.*;

class Solution {
    public String solution(String number, int k) {
        int len = number.length();
        Stack<Character> answer = new Stack<>();
        
        for(int i = 0; i < len; i++) {
            char cur = number.charAt(i);
            while(!answer.isEmpty() && answer.peek() < cur && k-- > 0) {
                answer.pop();
            }
            answer.push(cur);
        }
        
        StringBuilder result = new StringBuilder();
        while (!answer.isEmpty()) {
            result.append(answer.pop());
        }
        
        if (k > 0) {
            return result.reverse().toString().substring(0, result.toString().length() - k);
        }
        
        return result.reverse().toString();
    }
}

코드 설명

  • 변수 초기화
    • len: number 문자열의 길이.
    • answer: 각 문자를 저장하고, 조건에 따라 제거 및 추가하여 최종 결과를 만들기 위한 Stack.
    • cur: 현재 반복에서 number의 각 문자를 나타내는 변수.
    • result: 최종 결과를 역순으로 저장한 후, 다시 정방향으로 반환하기 위한 StringBuilder.
  • while(!answer.isEmpty() && answer.peek() < cur && k-- > 0) { answer.pop(); } answer.push(cur);
    • answer 스택이 비어있지 않고 answer의 맨 위 문자(peek())가 현재 문자 cur보다 작고 k가 0보다 크면 스택의 맨 위 문자를 제거(pop())합니다.
    • 현재 문자인 cur을 answer 스택에 추가합니다.
  • while (!answer.isEmpty()) { result.append(answer.pop()); }
    스택에 남아 있는 문자들을 result에 추가합니다. 이때 pop()을 사용하기 때문에 result는 역순으로 문자가 추가됩니다.
  • if (k > 0) { return result.reverse().toString().substring(0, result.toString().length() - k); }
    • k가 남아 있는 경우, 결과 문자열에서 k개의 문자를 제거합니다.
    • result.reverse()를 통해 정방향으로 만든 후, substring()으로 마지막 k개의 문자를 잘라 반환합니다.
  • return result.reverse().toString();
    • k가 0일 경우, result를 정방향으로 뒤집어 반환합니다.
제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

커서의 위치를 찾는 것이 포인트였습니다. 아무리해도 정답이 안되어서 서치를 진행하였는데 전체 왕복 횟수를 계산하는 로직입니다.

소스코드

class Solution {
    public int solution(String name) {
        int answer = 0;
        int len = name.length();

        int index;
        int move = len;
        
        for(int i = 0; i < len; i++){
            answer += Math.min(name.charAt(i) - 'A', 'Z' - name.charAt(i) + 1);
            
            index = i + 1;
            while(index < len && name.charAt(index) == 'A'){
                index++;
            }
            
            move = Math.min(move, i * 2 + len - index);
            move = Math.min(move, (len - index) * 2 + i);
        }
        
        return answer + move;
    }
}

코드 설명

  • for(int i = 0; i < len; i++) { answer += Math.min(name.charAt(i) - 'A', 'Z' - name.charAt(i) + 1); }
    • name.charAt(i) - 'A'는 'A'부터 현재 문자까지 위쪽 방향으로 이동할 때의 이동 횟수입니다.
    • 'Z' - name.charAt(i) + 1은 'Z'에서 반대 방향으로 이동하여 현재 문자에 도달하는 경우의 이동 횟수입니다.
    • 이 두 값 중 최소값을 취해 answer에 더합니다.
  • 좌우 이동 횟수 계산
    • index는 현재 위치 i에서 시작하여, 연속된 'A' 구간의 끝을 찾기 위해 사용됩니다.
    • index가 문자열의 끝(len)에 도달하거나 'A'가 아닌 문자를 만나면 반복문을 종료합니다.
  • move = Math.min(move, i * 2 + len - index); move = Math.min(move, (len - index) * 2 + i);
    • move는 현재까지 구한 좌우 이동의 최소값을 유지합니다.
    • i * 2 + len - index는 첫 번째 위치에서 i까지 이동한 후 다시 돌아와 남은 부분을 탐색하는 경우의 이동 거리입니다.
    • (len - index) * 2 + i는 반대 방향으로 탐색하다가 다시 i까지 돌아오는 경우의 이동 거리입니다.
    • 두 가지 방식 중 최소 이동 횟수를 move에 갱신합니다.
  • return answer + move;
    • answer는 상하 이동 횟수의 총합이고, move는 최소 좌우 이동 횟수입니다. 이 둘을 합하여 최소 조이스틱 조작 횟수를 반환합니다.

+ Recent posts