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

 

1991번: 트리 순회

첫째 줄에는 이진 트리의 노드의 개수 N(1 ≤ N ≤ 26)이 주어진다. 둘째 줄부터 N개의 줄에 걸쳐 각 노드와 그의 왼쪽 자식 노드, 오른쪽 자식 노드가 주어진다. 노드의 이름은 A부터 차례대로 알파

www.acmicpc.net

 

 

 

[정답 코드]

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

public class Main {
    static Node head = new Node('A', null, null); // 트리는 A부터 시작
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");

        int N = Integer.parseInt(st.nextToken());

        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine(), " ");
            char root = st.nextToken().charAt(0);
            char left = st.nextToken().charAt(0);
            char right = st.nextToken().charAt(0);

            insertNode(head, root, left, right);
        }

        preOrder(head);
        System.out.println();
        inOrder(head);
        System.out.println();
        postOrder(head);
        System.out.println();
    }


    static class Node {
        char value;
        Node left;
        Node right;

        Node(char value, Node left, Node right) {
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

    public static void insertNode(Node tmp, char root, char left, char right) {
        if (tmp.value == root) {
            tmp.left = (left == '.' ? null : new Node(left, null, null));
            tmp.right = (right == '.' ? null : new Node(right, null, null));
        } else {
            if (tmp.left != null) {
                insertNode(tmp.left, root, left, right);
            }
            if (tmp.right != null) {
                insertNode(tmp.right, root, left, right);
            }
        }
    }

    private static void preOrder(Node node) {
        if (node == null) { // 더 이상 값이 없을 때 까지
            return;
        }
        System.out.print(node.value); // 출력
        preOrder(node.left); // 왼쪽 노드 탐색
        preOrder(node.right); // 오른쪽 노드 탐색
    }

    private static void inOrder(Node node) {
        if (node == null) { // 더 이상 값이 없을 때 까지
            return;
        }
        inOrder(node.left); // 왼쪽 노드 탐색
        System.out.print(node.value); // 출력
        inOrder(node.right); // 오른쪽 노드 탐색
    }

    private static void postOrder(Node node) {
        if (node == null) { // 더 이상 값이 없을 때 까지
            return;
        }
        postOrder(node.left); // 왼쪽 노드 탐색
        postOrder(node.right); // 오른쪽 노드 탐색
        System.out.print(node.value); // 출력
    }
}

 

[설명]

트리를 순회하는 문제이다. 트리 탐색의 아주 기초문제이지만 너무 오랜만에 풀어서 도저히 풀 수가 없어 다른 글을 참고했다.

먼저 해결 방법은 다음과 같다.

 

1. Node 클래스를만든다.

  • Node 클래스는char value, Node left, Node right 구조를 가진 클래스이다.

2. Node 클래스에 insertNode메서드를 활용해 값을 입력한다.

  • insertNode는 (Node tmp, char root, char left, char right) 매개변수를 가지는데
    맨 처음 선언한 Node head 를 기준으로 null 이 아닌 노드를 계속 탐색한다.
    입력한 char root 값이 어떠한 Node의 value라면 그 노드의 왼쪽 노드와 오른쪽 노드에 
    값을 집어 넣는다.

3. 전위 순회 ,중위 순회 ,후위 순회 메서드를 각 각 사용해 출력한다.

  • 전위 순회 : 루트 -> 왼쪽  노드 -> 오른쪽 노드 순서
    중위 순회 : 왼쪽 노드 -> 루트 -> 오른쪽 노드 순서
    후위 순회 : 왼쪽 노드  -> 오른쪽 노드 -> 루트 순서

디버깅모드로 차근차근 이해해보면 금방 이해할 수 있다.

'코딩테스트' 카테고리의 다른 글

백준 1260번: DFS와 BFS[JAVA]  (0) 2024.03.20
백준 1068번: 트리[JAVA]  (1) 2024.03.15
백준 16234번: 인구 이동[JAVA]  (1) 2024.03.13
백준 2493번: 탑[JAVA]  (0) 2024.03.12
백준 7682번: 틱택토[JAVA]  (0) 2024.03.11

Comparable 인터페이스는 객체들을 순서대로 정렬할 수 있도록 한다. 이 인터페이스를 구현하는 클래스는 compareTo 메서드를 오버라이드 해야하며, 이 메서드는 객체 자신과 다른 객체를비교하여 정렬 순서를 결정하는데사용된다.

 

compareTo 메서드의 일반 규약

 


1. 호출 결과의 부호는 첫 번째 객체가 두 번째 객체보다 작은지, 같은지, 큰지를 나타낸다.

  • compareTo가 반환하는 값이 0보다 작으면, 첫 번째 객체가 두 번째 객체보다 작다는 것을 의미합니다.
  • compareTo가 반환하는 값이 0이면, 두 객체는 동일하다는 것을 의미합니다.
  • compareTo가 반환하는 값이 0보다 크면, 첫 번째 객체가 두 번째 객체보다 크다는 것을 의미합니다.


2. 두 객체 참조의 순서를 바꿔 비교해도 예상한 결과가 나와야 한다.

  • x<y 면 y>x 다
  • x==y 면 y==x다
  • x>y 면 y<x다

3. 첫 번째가 두 번째보다 크고 두 번째가 세 번째보다 크면, 첫 번째는 세 번째보다 커야한다.

  • x>y이고 y>z 면 x>z다

4. 크기가 같은 객체들끼리는 어떤 객체와 비교하더라도 항상 같아야 한다.

5. 두 객체가 compareTo로 0을 반환할 경우 equals 메서드에서 true를  반환해야 한다.

  • 그러나 equals가 true를 반환할 경우에 compareTo가 0을 반환할 필요는 없다.( compareTo는 equals보다 더 세밀한 비교를 제공할 수 있음)

compareTo 메서드를 재정의시 관계 연산자 '<'와 '>'를 사용하면 안된다.

이따금 '값의 차'를 기준으로 첫 번째 값이 두 번째 값보다 작으면 음수를, 두 값이 같으면 0을, 첫 번째 값이 크면 양수를 반환하는 compareTo나 comapre 메서드를 마주치는데 이 방식을 사용해서는 안된다. 정수 오버플로나 부동소수점 계산 방식에따른 오류가 발생할 수 있기 때문이다.

해시코드 값의 차를 기준으로 하는 비교자 - 추이성에 위배되므로 권장되지 않음!
static Comparator<Object> hashCodeOrder = new Comparator<>() {
	public int compare(Obejct o1, Object o2) {
    	return o1.hashCode() - o2.hashCode();
    }
};

권장되는 정적 compare 메서드를 활용한 비교자 예시
static Comparator<Object> hashCodeOrder = new Comparator<>() {
	public int compare(Obejct o1, Object o2) {
    	return Integer.compare(o1.hashCode(), o2.hashCode());
    }
};

비교자 생성 메서드를 활용한 비교자
static Comparator<Object> hashCodeOrder = 
		Comparator.comparingInt(o -> o.hashCode());

 

 

 

결론 : 순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현하여, 그 인스턴스들을 쉽게 정렬하고, 검색하고, 비교 기능을 제공하는 컬렉션과 어우러지도록 해야 한다. compareTo 메서드에서 필드의 값을 피교할 때는 박싱된 기본 타입 클래스가 제공하는 '정적 compare 메서드나 Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자.

 

clone() 메서드 : 객체의 복사본을 생성하기 위해 사용되는 메서드. 객체의 얕은 복사(shallow copy)를 수행한다.

얕은 복사는 객체 내부의 복합 객체에 대한 참조만을 복사하므로, 복제된 객체와 원본 객체가 같은 데이터를 가리키게 될 수 있다. 이로 인해 복제된 객체를 변경할 때 원본 객체도 영향을 받을 수 있다.

 

clone 메서드를 사용할 때 주의할 점은 얕은 복사가 되다는 점이다. 이로 인해  클래스가 가변 객체를 참조하는 순간 오류가 발생할 수 있다.

import java.util.Arrays;
import java.util.EmptyStackException;

public class Stack implements Cloneable {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // 다 쓴 참조 해제
        return result;
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }

    @Override
    protected Stack clone() {
        try {
            Stack result = (Stack) super.clone();
            result.elements = elements.clone(); // 배열을 깊은 복사한다.
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); // 이 예외가 발생할 일은 없다.
        }
    }
}

저번 아이템 7에서 사용한 예제에 가변상태를 참조하는 clone 메서드를 추가해주었다. 만약 가변상태를 참조하는 clone 메서드를 없다고 가정하고 clone 메서드가 Stack 클래스를 복제한다면 Stack인스턴스의 size 필드는 올바른 값을 갖겠지만, elements 필드는 원본 Stack 인스턴스와 똑같은 배열을 참조할것이다. 원본이나 복제본 중 하나를 수정하면 다른 하나도 수정되어 불변식이 해친다는 것이다. 이를 해결하기 위해 가변상태를 참조하는 clone 메서드 추가한다면 불변식을 해치지 않는다.

 @Override
    protected Stack clone() {
        try {
            Stack result = (Stack) super.clone();
            result.elements = elements.clone(); // 배열을 깊은 복사한다.
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); // 이 예외가 발생할 일은 없다.
        }

clone 메서드는 사실상 생성자와 같은 효과를 낸다. 즉, clone은 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야 한다. 이처럼 배열을 복제할 때는 배열의 clone 메서드를 사용하라고 권장한다. 사실, 배열은 clone 기능을 제대로 사용하는 유일한 예라 할 수 있다.

 

새로운 인터페이스를 만들 때는 절대 Cloneable을 확장해서는 안 되며, 새로운 클래스도 이를 구현해서는 안된다. final 클래스라면 Cloneable을 확장해서는 안되며, 새로운 클래스도 이를 구현해서는 안된다. final 클래스라면 위험이 크지는 않지만 권장하지는 않는다.

 

결론 : 복제기능의 기본원칙은 '복제기능은 복사 생성자와 복사 팩터리 메서드 를 이용하는게 BEST ' 라는 것이다. 단, 배열만은 clone 메서드 방식이 가장 깔끔하다.

 

 

추가 설명 - 복사 생성자와 복사 팩토리 메서드란?

복사 생성자 :
복사 생성자는 해당 클래스의 인스턴스를 인자로 받는 생성자다.
이 생성자는 전달받은 인스턴스의 상태를 복사하여 새로운 인스턴스를 초기화한다.
복사 생성자는 객체의 깊은 복사를 명시적으로 제어할 수 있게 해준다.

public class MyClass {
    private int data;

    public MyClass(MyClass source) {
        this.data = source.data;
    }
}

복사 팩토리 메서드 : 
복사 팩토리 메서드는 복사 생성자와 유사한 기능을 제공하지만, 메서드를 통해 구현된다. 
이 방식은 클래스의 인스턴스를 인자로 받아, 그 인스턴스의 상태를 복사하여 새로운 인스턴스를 반환한다.
public class MyClass {
    private int data;

    public static MyClass newInstance(MyClass source) {
        return new MyClass(source.data);
    }
}

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

 

16234번: 인구 이동

N×N크기의 땅이 있고, 땅은 1×1개의 칸으로 나누어져 있다. 각각의 땅에는 나라가 하나씩 존재하며, r행 c열에 있는 나라에는 A[r][c]명이 살고 있다. 인접한 나라 사이에는 국경선이 존재한다. 모

www.acmicpc.net

 

[정답 코드]

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

public class Main {
    static int[][] graph;
    static boolean[][] visited;
    static int[] dx = {-1, 1, 0, 0};
    static int[] dy = {0, 0, -1, 1}; // 상하좌우순서
    static int N, L ,R;
    static ArrayList<int[]> list;
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");

        N = Integer.parseInt(st.nextToken());
        L = Integer.parseInt(st.nextToken());
        R = Integer.parseInt(st.nextToken());

        graph = new int[N][N];
        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine(), " ");
            for (int j = 0; j < N; j++) {
                graph[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        System.out.println(move());
    }

    public static int move() { // 더이상 인구 이동이 일어나지 않을때 까지 반복
        int result = 0;
        while (true) {
            boolean isMove = false;
            visited = new boolean[N][N];
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    if (!visited[i][j]) {
                        int sum = dfs(i, j); // 방문하지 않은 곳 dfs 탐색
                        if (list.size() > 1) {
                            changePopulation(sum); // 국경이 열린 노드끼리 인구 이동
                            isMove = true;
                        }
                    }
                }
            }
            if (!isMove) {
                return result;
            }
            result++;
        }
    }

    public static void changePopulation(int sum) {
        int avg = sum / list.size();
        for (int i = 0; i < list.size(); i++) {
            int x = list.get(i)[0];
            int y = list.get(i)[1];
            graph[x][y] = avg;
        }
    }

    public static int dfs(int x, int y) {
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{x, y});

        list = new ArrayList<>();
        list.add(new int[]{x, y});

        visited[x][y] = true;
        int sum = graph[x][y];

        while (!queue.isEmpty()) {
            int tmp[] = queue.poll();
            for (int i = 0; i < 4; i++) { // 4방탐색
                int nx = tmp[0] + dx[i];
                int ny = tmp[1] + dy[i];
                if (nx >= 0 && nx < N && ny >= 0 && ny < N) { // graph 범위 내
                    // 방문한적 없고, L과 R 사이일 때
                    if (!visited[nx][ny] && L <= Math.abs(graph[tmp[0]][tmp[1]] - graph[nx][ny]) && Math.abs(graph[tmp[0]][tmp[1]] - graph[nx][ny]) <= R) {
                        visited[nx][ny] = true; // 방문한곳으로 변경
                        queue.offer(new int[]{nx, ny}); // 새로 큐에 담아줌
                        list.add(new int[]{nx, ny}); // 연결된 나라끼리는 따로 좌표를 담아줌
                        sum += graph[nx][ny];
                    }
                }
            }
        }
        return sum;
    }
}

 

[설명]

dfs와 구현을 동시에 해야하는 문제이다. 

문제를 세분화해서 풀어야 한다.

1. 순회를하며 방문하지 않은 노드를방문한다. 모든 노드를방문할 때 까지 반복된다.
2. 방문한 노드에서 dfs 알고리즘을 구현. 

  • dfs 알고리즘 : 연결된 노드들의 좌표를 list에 담아줌. 연결된 노드들의 값을 모두 더함.

3. 연결된 것 노드의 체크가 끝나면 인구 이동을 시작.

  • 인구이동 로직 : 연결된 노드들의 좌표를 담았던 list를 하나씩 꺼내며 연결된 노드들의 값을 모드 더했던것을 적절히 나눠 값을 배분

4. 모든 노드들을 방문했다면 1일 증가

5. 1 ~ 5를 반복, 인구이동이 더이상 일어나지 않을 때 까지 반복

'코딩테스트' 카테고리의 다른 글

백준 1068번: 트리[JAVA]  (1) 2024.03.15
백준 1991번: 트리 순회[JAVA]  (1) 2024.03.15
백준 2493번: 탑[JAVA]  (0) 2024.03.12
백준 7682번: 틱택토[JAVA]  (0) 2024.03.11
백준 9372번: 상근이의 여행[JAVA]  (0) 2024.03.09

toString() 메서드객체를 문자열 표현으로 반환해주는 메서드이다.

toString() 메서드는 기본적으로 '클래스이름@hashcode' 문자열을 반환해준다. 예를들면 PhoneNumber@abbaa 이런식이다.

이런 형태는 객체의 상태나 값에 대한 유용한 정보를 제공하지 않기때문에 재정의해 의미있게 표현해야한다.

 

예를들어 이러한 클래스가 있다고 예시를 들어보자 : 

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 객체의 문자열 표현을 반환합니다.
     * 이 문자열 표현은 객체의 주요 정보를 요약하여 제공하며,
     * 디버깅과 로깅 목적으로 유용합니다.
     * 
     * 반환되는 문자열의 포맷은 "Person{name='이름', age=나이}" 입니다.
     * 여기서, '이름'과 '나이'는 각각 객체의 name과 age 필드의 현재 값을 나타냅니다.
     * 
     * @return 객체의 문자열 표현
     */
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

// 사용 예:
Person person = new Person("John Doe", 30);
System.out.println(person.toString()); // 출력: Person{name='John Doe', age=30}

 

예시처럼 toString()을 재정의 했을 경우 Person{name='John Doe', age=30} 같이 반환된다.

Person 객체에 담긴 name, age같은 정보가 출력되어 디버깅, 로깅시 유용하다.

만약 이를 재정의하지 않았을경우 출력값은 Person@b58a 이런식으로 출력됬을것이다.

 

그리고 예시처럼 반드시 반환값의 포맷을 명시하든 아니든 재정의 하려는 의도를 반드시 명확하게 밝혀야 한다.

구글의 AutoValue같은 프레임워크로 toString 재정의시 각 필드의 내용등을 설명해주긴 한다. 하지만 개발자가 원하는 클래스의 '의미'까지는 알지는 못. 개발자가 원하는 의도에 맞게 작성해주자.

 

결론 : toString() 메서드는 재정의해주자. 재정의시 의도를 명확하게 작성해주자. 재정의해 디버깅시 유용하게 사용하자. 재저

hashcode : 객체를 대표하는 정수 값. hashCode() 메서드를 통해 이 값을 얻는다. 객체의 hashcode는 객체를 저장하거나 검색할때 hash table같은 데이터 구조의 로 사용된다. 

 

equals를 재정의한 클래스 모두에서 hashCode도 재정의해야 한다.

그렇지 않으면 hashCode 일반 규약을 어기게 되어 오류가 발생할 수 있다.

 

1. 프로그램 실행 중 동일한 객체에 대해 여러 번 hashCode() 메서드를 호출하면, 객체가 수정되지 않았다면 메서드는 항상 동일한 정수를 반환해야 한다. 이 값은 객체가 수정되지 않는 한 프로그램 재실행 간에도 일관되어야 한다.

 

2. equals(Object) 메서드가 두 객체를 동등하게 판단한다면, 두 객체의 hashCode() 메서드는 동일한 정수 값을 반환해야 한다.

 

3. equals(Object) 메서드가 두 객체를 다르게 판단한다 하더라도, 두 객체의 hashCode() 메서드가 서로 다른 정수 값을 반환할 필요는 없다. 그러나, 다른 객체에 대해 다른 해시 코드를 생성하는 것이 해시 테이블의 성능을 향상시킨다.

 

 

결론 : equals를 재정의할 때는 hashcode도 반드시 재정의해야 한다.

하지만 AutoValue 프레임워크를 사용하면 equals와 hashCode를 자동으로 만들어주니 hashCode를 직접 재정의하는 방법을 외울 필요는 없는것 같다.

equals 메서드는 Java의 Object 클래스에 정의된 메서드로, 두 객체가 "같음(equal)"을 결정하기 위해 사용된다. 모든 클래스에서 상속받으므로, 필요에 따라 오버라이드(재정의)하여 객체 간의 동등성 비교 방식을 커스텀할 수 있다.

 

기본 동작 : 

public boolean equals(Object obj) {
    return (this == obj);
}

기본적으로 Object 클래스의 equals 메서드는 두 객체의 참조가 같은지 확인한다. 즉, 두 참조가 메모리 상에서 같은 객체를 가리키는 경우에만 true를 반환한다.

 

equals를 재정의할 때 다음 중 하나라도 해당한다면 재정의하지 않는것이 좋다.

1. 각 인스턴스가 본질적으로 고유하다.

  • 예를들어 Thread 클래스가 있다. Thread 클래스의 인스턴스는 각각 고유한 실행 스레드를 대표한다. 따라서, equals 메서드를 재정의하여 스레드 간의 '동등성'을 비교하는것은 의미가 없다.

2. 인스턴스의 논리적 동치성을 검사할 일이 없다.

  • 해당 인스턴스가 대표하는 값이나 상태가 같은 다른 인스턴스와 "동등"하다고 판단할 필요가 없다는 의미이다. 즉, 객체의 동일성(identity)이 중요할 뿐, 두 개의 서로 다른 객체가 같은 값을 가진다는 개념이 의미가 없거나 관련이 없는 경우를 말합니다.
public class Session {
    private final String sessionId;

    public Session(String sessionId) {
        this.sessionId = sessionId;
    }

    // 세션 ID에 기반한 동치성 비교가 의미가 없는 경우, equals 메서드를 재정의하지 않음
}
  • 이처럼 세션ID가 같다고해서 두 세션의 인스턴스가 논리적으로 동등하다고 판단할 필요 자체가 없을경우 이다.

3. 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.

  • 상위 클래스에서 재정의한 equals 메서드가 하위 클래스에서도 모든 면에서 적절하게 동작한다면, 하위 클래스에서 equals 메서드를 다시 재정의할 필요가 없다. 이 경우는 일반적으로 하위 클래스가 상위 클래스의 "동등성" 개념을 변경하지 않고, 추가적인 상태 정보 없이 상위 클래스의 동작을 확장할 때 발생한다.

4. 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다.

  • 클래스가 private이거나 package-private이고 그 인스턴스 간에 equals 메서드를 호출할 일이 없다면, 이는 해당 클래스가 매우 특정한 용도로 사용되며, 그 범위와 목적 내에서 인스턴스들의 동등성 비교가 로직적으로 의미가 없거나 필요하지 않다는 것을 의미할 경우 equals 메서드를 호출할 필요 자체가 없기때문에 재정의할 필요도 없는것이다.

 

이런 상황에 해당하지 않고 eqauls를 재정의 해야할 경우에는 반드시! 일반 규약을 따라야 한다.

 

1. 반사성(Reflexivity): 객체는 자기 자신과 동등해야 한다.

  • 어떤 객체 x에 대해, x.equals(x)true를 반환해야 한다.

2. 대칭성(Symmetry): 두 객체의 동등성 검사는 방향에 무관해야 한다.

  • 어떤 객체 x와 y에 대해, x.equals(y)가 true를 반환한다면, y.equals(x)도 true를 반환해야 한다.

3. 추이성(Transitivity): 첫 번째 객체가 두 번째 객체와 동등하고, 두 번째 객체가 세 번째 객체와 동등하면, 첫 번째 객체도 세 번째 객체와 동등해야 한다.

  • 어떤 객체 x, y, z에 대해, x.equals(y)와 y.equals(z)가 true를 반환한다면, x.equals(z)도 true를 반환해야 한다.

4. 일관성(Consistency): 두 객체의 상태가 변하지 않는 한, equals 메서드의 호출 결과는 변경되지 않아야 한다.

  • 어떤 객체 x와 y에 대해, 여러 번 x.equals(y)를 호출하더라도 항상 true를 반환하거나 항상 false를 반환해야 한다.

5. null에 대한 비동등성: 모든 객체는 null과 비교했을 때 false를 반환해야 한다.

  • 어떤 객체 x에 대해, x.equals(null)은 false를 반환해야 한다.

어려워 보이지만 하나하나 읽어보면 당연한 말이다.

 

eqauls를 재정의를 해야만 하는 경우에는 5가지 일반규약을 따르고 검색해가며 단계별로 재정의를 하자. 하지만 일반 규약을 따르며 재정의해도 오류가 발생할 수 있다. equals 재정의를 했다면 대칭적인지, 추이성이 있는지, 일관적인지 단위 테스트를 작성해보는것이 좋다.

 

결론 : 정말 꼭 필요한 경우가 아니라면 equals를 재정의하지 말자. 대부분 equals가 개발자가 원하는 비교를 정확히 준다.

그냥 재정의 하지말자.

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

 

2493번: 탑

첫째 줄에 탑의 수를 나타내는 정수 N이 주어진다. N은 1 이상 500,000 이하이다. 둘째 줄에는 N개의 탑들의 높이가 직선상에 놓인 순서대로 하나의 빈칸을 사이에 두고 주어진다. 탑들의 높이는 1

www.acmicpc.net

[정답 코드]

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

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");

        int N = Integer.parseInt(st.nextToken());
        Stack<int[]> stack = new Stack<>();
        st = new StringTokenizer(br.readLine(), " ");
        for (int i = 1; i <= N; i++) { // 탑은 0번부터가 아닌 1번부터 시작
            int top = Integer.parseInt(st.nextToken()); // 현재 확인중인 탑
            while (!stack.isEmpty()) {
                if (stack.peek()[1] >= top) {
                    bw.write(stack.peek()[0] + " ");
                    break;
                }
                stack.pop();
            }
            if (stack.isEmpty()) {
                bw.write("0 ");
            }
            stack.push(new int[]{i, top});
        }
        bw.flush();
        bw.close();
        br.close();
    }
}

 

[설명]

제일 왼쪽 탑부터 스택에 담긴 값들과 비교해가면 로직을 반복한다.

스택에는 높이가 작은 순서부터 큰순으로 오름차순으로값이 담기게 된다.

 

높은 6탑은 첫번째 탑이기 때문에 비교할 스택들이 없어 0을 bw.write 한 후 {탑의 위치, 탑의 높이}를 스택에 담는다.

 

높이 9탑은 스택에 담긴 6보다 높이가 높다. 그 뜻은 앞으로 확인할 탑들은 모두 높이 9 탑 때문에 스택에 담겼던 높이 6 탑을 만날 수 없다. 그러므로 높이 6 스택은 pop해준다. 높이 9 탑을 스택에 담는다.

 

높이 5탑과 스택에 담긴 높이9 탑과 비교해준다. 높이9 탑에 신호가 닿기 때문에 높이9의 위치를 출력해준 후 스택에 높이 5 스택을 담아준다.

 

높이 7탑과 스택에 담긴값과 비교해준다. 스택의 가장 위의 값인 높이 5와 비교를 해본다. 스택에 담겼던 스택 5탑은 현재 확인중인 높이 7탑 때문에 앞으로 모든신호를 못받게된다. 그러므로 스택에서 pop 해준다. 그 다음 스택에 담긴 값 높이 9랑 비교 하면 신호가 9에 닿는다. 9의 위치를 출력후 높이7 탑을 스택에 담는다.

 

높이 4탑과 스택에 담긴 값을 비교해본다. 스택의 top에 있는값 높이 7인탑과 비교해보니 바로 신호가 닿는다. 높이 7탑의 위치를 출력해준다.

+ Recent posts