728x90

레드-블랙 트리란?

레드-블랙 트리란 BTS의 일종으로, BTS의 최악의 시간복잡도 O(n)을 방지하기 위해 균형을 유지하게 하는 균형 이진 탐색트리 중 하나입니다.

FunctionSearchInsertDelete

Function Amortized Worst Case
Select O(log⁡n) O(log⁡n)
Insert O(log⁡n) O(log⁡n)
Delete O(log⁡n) O(log⁡n)

 

레드-블랙 트리의 속성

레드-블랙 트리의 노드들은 red 또는 black색을 가지며, 아래와 같은 5가지 특징을 준수하여 균형을 이룹니다.

  1. 모든 노드는 red 혹은 black이다.
  2. 루트 노드는 black이다.
  3. 모든 리프 노드( NIL )는 black이다.
  4. red 노드의 자식 노드는 항상 black이다.
  5. 모든 노드에서 리프 노드들 까지 가는 경로에 존재하는 black노드의 수는 같다. ( 검색 시작 노드 제외 )

데이터 삽입

우선 레드-블랙 트리에서 삽입하는 노드의 색은 항상 red입니다.

 

데이터 삽입 과정 중 레드 - 블랙 트리의 속성에 위배하는 경우 Rebalancing과정을 거치게 됩니다.

Rebalancing은 색변경(Recoloring)회전(Rotation) 과정으로 이루어져 있으며, 케이스에 따라 두 가지 방법을 혼합하여 적용하면 레드 - 블랙 트리의 속성을 위반하지 않게 만들 수 있습니다.

 

아래에 삽입 시 발생할 수 있는 케이스에 대해 알아보고 또한 Rebalancing과정이 어떻게 이루어질 수 있는지 알아보도록 하겠습니다.

Case1. Root 삽입

맨 처음 Root에 데이터를 삽입하는 Case입니다.

 

데이터 삽입 시 들어오는 노드의 색은 항상 red로 고정되어 있기 때문에 삽입된 노드의 색상은 red입니다.

이때, 레드-블랙 트리 2번째 속성 'root는 black이다'를 위반하기 때문에 Recoloring과정을 통해 black으로 변경하여 줍니다.


Case2. 삽입한 노드의 부모 노드가 black인 경우

데이터 삽입 시 삽입되는 노드의 색상은 red이고, 위반하는 규칙은 존재하지 않습니다.


Case3. 부모 노드가 red이고, 한쪽으로 쏠린 경우

1을 삽입하게 되면 위 사진의 첫번째와 같이 red하위에 red가 존재하는 양상의 띄게 되며 그와 동시에 4번 속성을 위반하게 됩니다.

 

그럴경우 부모노드 색을 black으로 조부모 색을 red로 변경해줍니다.

그 다음 왼쪽으로 쏠림현상이 존재하므로 5를 기준으로 오른쪽으로 회전하여 2가 root에 위치하도록 합니다.

 

수정 후 모습을 보면 4번 속성을 위반하지 않고, 2번 속성도 위반하지 않는 것을 확인 할 수 있었습니다.


Case4. 부모노드가 red이고, 한쪽으로 몰려있으며, 오른쪽으로 꺾인 경우

3을 삽입하게 되면 오른쪽으로 꺾이게 됩니다.

이때 삽입한 노드를 기준으로 좌로 회전합니다.

 

이후 root와 삽입한 노드의 색을 바꿔주고, root를 기준으로 오른쪽으로 회전합니다.


Case5. 부모노드가 red이고, 삼촌노드도 red인 경우

6을 삽입하였습니다.

우선 4번 속성을 위반하기 때문에 부모와 삼촌 노드의 색을 black으로, 조상 노드의 색을 red로 변경합니다.

 

이후 2번 속성을 위반하였기 때문에 루트 노드의 색을 black으로 변경해줍니다.

 

이때 black 하위 black인 경우가 생기게 되는데, 이는 속성을 위반하는 사항이 아니므로 괜찮습니다.


이후 많은 다양한 상황들이 나올 수 있지만, 위 케이스들을 기반으로 비교하면 레드-블랙 트리의 속성을 지키며, 작업을 이어나갈 수 있게 됩니다.


레드-블랙 트리  VS AVL 트리

레드 블랙 트리를 왜 사용하는지, 그리고 AVL 트리와의 차이는 어떤 점들이 존재하는지 알아보도록 하겠습니다.

  레드-블랙 트리 AVL 트리
삽입/삭제 속도 더 빠름 (재조정이 덜 복잡함) 느림 (더 엄격한 균형 유지)
검색 속도 약간 느림 ( 완벽한 균형 x )  더 빠름 (더 균형 잡힘)
구현 난이도 상대적으로 단순 더 빠름(더 균형 잡힘)
적합한 용도 삽입/삭제가 잦은 경우 일기 성능이 중요한 경우
사용처 TreeMap, TreeSet,... Index, Cache,...

Reference

https://en.wikipedia.org/wiki/Red%E2%80%93black_tree

https://www.youtube.com/watch?v=2MdsebfJOyM

 

 

 

728x90
728x90

들어가기에 앞서

해당 Post는 이진 트리에 대한 기초 지식을 요구합니다.

만약 이진트리를 모른다면 이 글을 읽고 난 이후 봐주시기 바랍니다.

이진 탐색 트리 값 삽입

이진트리 중 중복 값은 허용하지 않고, 왼쪽은 부모 노드보다 작은 값을 오른쪽 자식노드는 부모노드 보다 큰 조건을 충족하는 트리를 이진 탐색 트리라고 합니다.

 

위와 같이 데이터가 삽입되며, 왼쪽은 currnet보다 작은 값, 오른쪽은 큰 값을 배치하게 됩니다.

public class BinaryTree {
    TreeNode<Integer> root;

    public void add(Integer value) {
        root = add(root, value);
    }

    private TreeNode<Integer> add(TreeNode<Integer> current, Integer value) {
        if(current == null) {
            return new TreeNode<>(value);
        }

        if(current.getValue() < value) {
            current.setRight(add(current.getRight(), value));
        } else {
            current.setLeft(add(current.getLeft(), value));
        }

        return current;
    }
}

이진 탐색 트리의 삭제

이진 탐색 트리의 삭제는 어떤 상태를 가진 노드를 삭제하느냐로 3가지로 나눠볼 수 있습니다.

Leaf Node 삭제

가장 생각할 게 없는 단순한 삭제입니다.

 

자식 노드가 존재하지 않기 때문에 균형 이진트리가 아니라면 따로 큰 작업이 이뤄지지 않는 삭제입니다.

자식이 1개인 부모 Node 삭제

Leaf Node삭제보다 살짝 작업이 추가된 삭제라고 생각합니다.

 

자식이 1개일 경우 삭제할 노드의 부모 노드에 자식 노드의 Instance정보를 연결해 주고, 본인은 참조되지 않는 방법으로 간단하게 삭제처리할 수 있습니다.

자식이 2개인 부모 Node 삭제

자식이 2개인 경우 어떤 정책을 통해 부모 노드를 대체할 것인지를 고민해야 합니다.

  • 중위 전임자(In-order Predecessor): 삭제할 노드의 왼쪽 서브트리에서 가장 큰 값을 가진 노드.
  • 중위 후속자(In-order Successor): 삭제할 노드의 오른쪽 서브트리에서 가장 작은 값을 가진 노드.

위와 같이 정책을 정하여 삭제를 진행할 경우 데이터의 쏠림 현상으로 인해 시간복잡도가 차이 날 수 있는데,

평균적으로 O(log n)의 시간복잡도를 가지며, 최악의 경우 O(n)의 시간복잡도를 가지게 됩니다.

 

중위 전임자 정책을 사용하여 삭제하였을 경우

public class BinaryTree {
    TreeNode<Integer> root;

    private TreeNode<Integer> add(TreeNode<Integer> current, TreeNode<Integer> prevNode) {
        if(current == null) {
            return prevNode;
        }

        if(current.getValue() < prevNode.getValue()) {
            current.setRight(add(current.getRight(), prevNode));
        } else {
            current.setLeft(add(current.getLeft(), prevNode));
        }

        return current;
    }

    public void remove(Integer value) {
        root = remove(root, value);
    }

    private TreeNode<Integer> remove(TreeNode<Integer> current, Integer value) {
        if(current == null) return null;
        // 중위 전임자(In-order Predecessor)
        if(Objects.equals(current.getValue(), value)) {
            TreeNode<Integer> predecessor = predecessor(current.getLeft());
            predecessor.setRight(current.getRight());
            if(current.getLeft() != null)
                add(predecessor, current.getLeft());
            return predecessor;
        }

        current.setLeft(remove(current.getLeft(), value));
        current.setRight(remove(current.getRight(), value));

        return current;
    }

    // 중위 전임자(In-order Predecessor): 삭제할 노드의 왼쪽 서브트리에서 가장 큰 값을 가진 노드.
    private TreeNode<Integer> predecessor(TreeNode<Integer> current) {
        if(current.getRight() == null)
            return current;

        TreeNode<Integer> predecessor =  predecessor(current.getRight());
        if(Objects.equals(current.getRight().getValue(), predecessor.getValue()))
            current.setRight(null);

        return predecessor;
    }
}


중위 후속자 정책을 사용하여 삭제하였을 경우

public class BinaryTree {
    TreeNode<Integer> root;

    private TreeNode<Integer> add(TreeNode<Integer> current, TreeNode<Integer> prevNode) {
        if(current == null) {
            return prevNode;
        }

        if(current.getValue() < prevNode.getValue()) {
            current.setRight(add(current.getRight(), prevNode));
        } else {
            current.setLeft(add(current.getLeft(), prevNode));
        }

        return current;
    }

    public void remove(Integer value) {
        root = remove(root, value);
    }

    private TreeNode<Integer> remove(TreeNode<Integer> current, Integer value) {
        if(current == null) return null;

        // 중위 후속자(In-order Successor)
        if(Objects.equals(current.getValue(), value)) {
            TreeNode<Integer> successor = successor(current.getRight());
            successor.setLeft(current.getLeft());
            if(current.getRight() != null)
                add(successor, current.getRight());
            return successor;
        }

        current.setLeft(remove(current.getLeft(), value));
        current.setRight(remove(current.getRight(), value));

        return current;
    }

    // 중위 후속자(In-order Successor): 삭제할 노드의 오른쪽 서브트리에서 가장 작은 값을 가진 노드.
    private TreeNode<Integer> successor(TreeNode<Integer> current) {
        if(current.getLeft() == null)
            return current;

        TreeNode<Integer> successor =  successor(current.getLeft());
        if(Objects.equals(current.getLeft().getValue(), successor.getValue()))
            current.setLeft(null);
        return successor;
    }
}


최종 코드

public class BinaryTree {
    TreeNode<Integer> root;

    public BinaryTree() {
        root = null;
    }

    public void add(Integer value) {
        root = add(root, value);
    }

    private TreeNode<Integer> add(TreeNode<Integer> current, Integer value) {
        if(current == null) {
            return new TreeNode<>(value);
        }

        if(current.getValue() < value) {
            current.setRight(add(current.getRight(), value));
        } else {
            current.setLeft(add(current.getLeft(), value));
        }

        return current;
    }

    private TreeNode<Integer> add(TreeNode<Integer> current, TreeNode<Integer> prevNode) {
        if(current == null) {
            return prevNode;
        }

        if(current.getValue() < prevNode.getValue()) {
            current.setRight(add(current.getRight(), prevNode));
        } else {
            current.setLeft(add(current.getLeft(), prevNode));
        }

        return current;
    }

    public void remove(Integer value) {
        root = remove(root, value);
    }

    private TreeNode<Integer> remove(TreeNode<Integer> current, Integer value) {
        if(current == null) return null;

        if(!Objects.equals(current.getValue(), value)) {
            current.setLeft(remove(current.getLeft(), value));
            current.setRight(remove(current.getRight(), value));
            return current;
        }

        // Leaf일 때 삭제
        if(current.getRight() == null && current.getLeft() == null)
            return null;

        if(current.getLeft() == null) return current.getRight();
        if(current.getRight() == null) return current.getLeft();

//        // 중위 전임자(In-order Predecessor)
//        TreeNode<Integer> predecessor = predecessor(current.getLeft());
//        predecessor.setRight(current.getRight());
//        if(current.getLeft() != null)
//           add(predecessor, current.getLeft());
//        return predecessor;

        // 중위 후속자(In-order Successor)
        TreeNode<Integer> successor = successor(current.getRight());
        successor.setLeft(current.getLeft());
        if(current.getRight() != null)
            add(successor, current.getRight());

        return successor;

    }

    // 중위 전임자(In-order Predecessor): 삭제할 노드의 왼쪽 서브트리에서 가장 큰 값을 가진 노드.
    private TreeNode<Integer> predecessor(TreeNode<Integer> current) {
        if(current.getRight() == null)
            return current;

        TreeNode<Integer> predecessor =  predecessor(current.getRight());
        if(Objects.equals(current.getRight().getValue(), predecessor.getValue()))
            current.setRight(null);

        return predecessor;
    }

    // 중위 후속자(In-order Successor): 삭제할 노드의 오른쪽 서브트리에서 가장 작은 값을 가진 노드.
    private TreeNode<Integer> successor(TreeNode<Integer> current) {
        if(current.getLeft() == null)
            return current;

        TreeNode<Integer> successor =  successor(current.getLeft());
        if(Objects.equals(current.getLeft().getValue(), successor.getValue()))
            current.setLeft(null);
        return successor;
    }


    // 전위 순회 (Pre-order Traversal) 출력 메서드
    public void traversePreOrder(TreeNode<Integer> node) {
        if (node != null) {
            System.out.print(" " + node.getValue());
            traversePreOrder(node.getLeft());
            traversePreOrder(node.getRight());
        }
    }

    // 중위 순회 (In-order Traversal) 출력 메서드
    public void traverseInOrder(TreeNode<Integer> node) {
        if (node != null) {
            traverseInOrder(node.getLeft());
            System.out.print(" " + node.getValue());
            traverseInOrder(node.getRight());
        }
    }

    // 후위 순회 (Post-order Traversal) 출력 메서드
    public void traversePostOrder(TreeNode node) {
        if (node != null) {
            traversePostOrder(node.getLeft());
            traversePostOrder(node.getRight());
            System.out.print(" " + node.getValue());
        }
    }

    // 레벨 순회 (Level-order Traversal) 출력 메서드
    public void traverseLevelOrder(TreeNode root) {
        if (root == null) return;

        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);

        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            System.out.print(" " + node.getValue());

            if (node.getLeft() != null) queue.add(node.getLeft());
            if (node.getRight() != null) queue.add(node.getRight());
        }
    }

    public static void main(String[] args) {
        BinaryTree bt = new BinaryTree();
        bt.add(5);
        bt.add(2);
        bt.add(3);
        bt.add(1);
        bt.add(0);
        bt.add(7);
        bt.add(8);
        bt.add(6);

        bt.remove(5);

        // 순회 결과 출력
        System.out.print("Pre-order traversal:");
        bt.traversePreOrder(bt.root);
        System.out.println();

        System.out.print("In-order traversal:");
        bt.traverseInOrder(bt.root);
        System.out.println();

        System.out.print("Post-order traversal:");
        bt.traversePostOrder(bt.root);
        System.out.println();

        System.out.print("Level-order traversal:");
        bt.traverseLevelOrder(bt.root);
        System.out.println();
    }
}
728x90

'알고리즘 문제풀이 > 자료구조' 카테고리의 다른 글

[Tree] 이진 트리 순회  (1) 2024.11.08
[Tree] 이진 트리 ( Binary Tree )  (0) 2024.11.07
[자료구조] 스택(Stack)  (0) 2024.10.21
728x90

 

바보같이 너무 많이 생각해서 어려웠던 문제였습니다.

 

문제자체는 단순합니다.

parent 값들을 주고 해당 값으로 생성된 node들 중 하나를 삭제하고, 이후 leaf의 갯수를 알아내면 되는 문제입니다.

 

parent와 index값을 저장을 하고,

index를 부모로 가지는 node들을 child로 저장,

삭제된 노드들의 정보를 가지게 된다면 간단하게 해결할 수 있는 문제입니다.

 

다만 주의할 점은 현재 삭제된 노드들만 가지고 있는 노드 또한 leaf라는 점입니다.

이점은 코드를 확인해보며 찾아보시기 바랍니다.

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int N = Integer.parseInt(br.readLine());

        int[] parents = new int[N];
        boolean[] isDeleted = new boolean[N];
        List<List<Integer>> graph = new ArrayList<>();

        for(int i = 0; i <= N; i++) {
            graph.add(new ArrayList<>());
        }

        int root = -1;
        StringTokenizer st = new StringTokenizer(br.readLine());

        for(int i = 0; i < N; i++) {
            parents[i] = Integer.parseInt(st.nextToken());
            if(parents[i] == -1) {
                root = i;
            } else {
                graph.get(parents[i]).add(i);
            }
        }
        int deleteNode = Integer.parseInt(br.readLine());
        if(parents[deleteNode] > -1) {
            delete(graph.get(parents[deleteNode]), isDeleted, deleteNode);
        } else {
            isDeleted[root] = true;
        }
        bw.write(countLeafNode(graph, isDeleted, root) + "");
        bw.flush();
        br.close();
        bw.close();
    }

    public static void delete(List<Integer> graph, boolean[] isDeleted, int node) {
        for(int child : graph) {
            if(child == node) {
                isDeleted[child] = true;
                return;
            }
        }
    }

    public static int countLeafNode(List<List<Integer>> graph, boolean[] isDeleted, int node) {
        if(isDeleted[node]) return 0;
        if(graph.get(node).isEmpty()) return 1;
        int count = 0;

        for(int child : graph.get(node)) {
            count += countLeafNode(graph, isDeleted, child);
        }

        return count == 0 ? 1 : count;
    }
}
728x90
728x90

문제를 보면 모든 노드들은 연결이 되어있고, 모든 노드들을 돌아볼 때 가장 적은 종류의 비행기( 간선 정보 )를 타고 움직이는 수를 알아내야하는 것이 문제의 의도 입니다.

 

이때 우리는 모든 노드가 연결되어있고, 최소한의 경로로 모든 노드를 방문해야한다는 조건에서 최소신장 트리를 떠올릴 수 있어야 합니다.

 

최소신장 트리의 모든 노드를 연결하면서 가중치의 합을 구하면 되겠는데, 이때 값은 N - 1로써 가장 간단하게 구할 수 있습니다.

(N은 노드 수입니다.)

 

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

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

        int T = Integer.parseInt(br.readLine());

        StringBuilder sb = new StringBuilder();
        while (T-- > 0) {
            st = new StringTokenizer(br.readLine());
            int n = Integer.parseInt(st.nextToken());
            int m = Integer.parseInt(st.nextToken());
            for (int i = 0; i < m; i++) {
                br.readLine();
            }
            sb.append(n - 1).append("\n");
        }
        bw.write(sb.toString());
        bw.flush();
        br.close();
        bw.close();
    }

}
  • 시간복잡도: O(1)
  • 공간복잡도: O(1)
728x90
728x90

 

입력 예제를 확인해보면 N개의 노드와 N - 1개의 간선 정보가 주어지는 것을 확인해 볼 수 있습니다.

그림으로 표현하면 위와 같이 표현할 수 있으며, 해당 정보를 통해 2번부터 N번 까지 노드들의 부모정보를 출력하면 되는 문제입니다.

 

즉 2번의 부모 4, 3번의 부모 6, 4번의 부모 1, 5번의 부모 3, 6번의 부모 1, 7번의 부모 4번 해서 결과는 4, 6, 1, 3, 1, 4가 나오면 되는 것 입니다.

 

여기서 주의해야하는 점은 해당 트리는 자식 노드가 최대 2개가 주어진다는 말이 없으니 일반 트리이며, 자식 노드는 N개가 될 수 있다는 말입니다.

 

문제 풀이는 생각보다 간단합니다. 

문제에서 트리의 루트를 1번으로 잡는다 하였으니, 1번의 자식들의 부모를 Bfs 탐색을 통해 확인한 다음 2번노드 부터 부모 번호를 출력해주면 됩니다.

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());
        StringTokenizer st;

        List<Integer>[] arr = new List[N + 1];
        for( int i = 1; i <= N; i++ ) {
            arr[i] = new ArrayList<>();
        }

        for (int i = 1; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            int from = Integer.parseInt(st.nextToken());
            int to = Integer.parseInt(st.nextToken());

            arr[from].add(to);
            arr[to].add(from);
        }

        int[] parent = new int[N + 1];
        Queue<Integer> queue = new LinkedList<>();
        queue.add(1);
        parent[1] = 1;

        while (!queue.isEmpty()) {
            int current = queue.poll();
            for(int child: arr[current]){
                if (parent[child] == 0) {
                    parent[child] = current;
                    queue.add(child);
                }
            }
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 2; i <= N; i++) {
            sb.append(parent[i]);
            sb.append("\n");
        }
        System.out.println(sb);
    }
}
  • 시간복잡도: O(N)
  • 공간복잡도: O(N)
728x90

'알고리즘 문제풀이' 카테고리의 다른 글

[ 백준 - 1068 ] 트리  (0) 2024.11.14
[ 백준 - 9372 ]상준이의 여행  (2) 2024.11.13
[ 백준 - 29701 ] 모스 부호  (0) 2024.11.11
[ 백준 - 25325 ] 학생 인기도 측정  (0) 2024.11.10
[ 백준 - 2358 ] 평행선  (2) 2024.11.09
728x90

모스 부호 문제입니다.

매우 간단한 문제이며, 우선 시간제한과 메모리 제한을 확인해 보겠습니다.

 

입력 예제를 보면  N은 최대 100 이하의 개수가 주어진다 합니다.

그럼 이 문제를 접할 때 최악의 경우 모스부호 100개를 문자열로 변환해야 하는 문제라고 볼 수 있습니다.

 

예제로 준 모스 부호표를 보면 41개를 명시한 표를 통해 답을 만들 수 있습니다.

최악의 경우 100개의 모스 부호를 41개 중 찾기 위해 반복문을 돌린다 하면 4100번 돌려 해결을 할 수 있습니다.

 

다만, 4100번 돌려 답을 찾는 것은 O(n)의 복잡도를 가지게 되지만 41이란 상수는 빅오표기법상 표기를 안 하니 딱히 효율적인 알고리즘은 아니라 생각합니다.

거기서 우리는 Map이라는 자료구조를 활용하여 O(1) 시간복잡도로 모스 부호를 검색할 수 있는 방법을 떠올릴 수 있습니다.

public class Problem29701 {

    public static Map<String, String> initMorseCodeMap() {
        Map<String, String> morseCodeMap = new HashMap<>();
        morseCodeMap.put(".-", "A");
        morseCodeMap.put("-...", "B");
        morseCodeMap.put("-.-.", "C");
        morseCodeMap.put("-..", "D");
        morseCodeMap.put(".", "E");
        morseCodeMap.put("..-.", "F");
        morseCodeMap.put("--.", "G");
        morseCodeMap.put("....", "H");
        morseCodeMap.put("..", "I");
        morseCodeMap.put(".---", "J");
        morseCodeMap.put("-.-", "K");
        morseCodeMap.put(".-..", "L");
        morseCodeMap.put("--", "M");
        morseCodeMap.put("-.", "N");
        morseCodeMap.put("---", "O");
        morseCodeMap.put(".--.", "P");
        morseCodeMap.put("--.-", "Q");
        morseCodeMap.put(".-.", "R");
        morseCodeMap.put("...", "S");
        morseCodeMap.put("-", "T");
        morseCodeMap.put("..-", "U");
        morseCodeMap.put("...-", "V");
        morseCodeMap.put(".--", "W");
        morseCodeMap.put("-..-", "X");
        morseCodeMap.put("-.--", "Y");
        morseCodeMap.put("--..", "Z");
        morseCodeMap.put(".----", "1");
        morseCodeMap.put("..---", "2");
        morseCodeMap.put("...--", "3");
        morseCodeMap.put("....-", "4");
        morseCodeMap.put(".....", "5");
        morseCodeMap.put("-....", "6");
        morseCodeMap.put("--...", "7");
        morseCodeMap.put("---..", "8");
        morseCodeMap.put("----.", "9");
        morseCodeMap.put("-----", "0");
        morseCodeMap.put("--..--", ",");
        morseCodeMap.put(".-.-.-", ".");
        morseCodeMap.put("..--..", "?");
        morseCodeMap.put("---...", ":");
        morseCodeMap.put("-....-", "-");
        morseCodeMap.put(".--.-.", "@");
        return morseCodeMap;
    }

    public static void main(String[] args) throws IOException {
        Map<String, String> morseCodeMap = initMorseCodeMap();
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());

        StringBuilder sb = new StringBuilder();
        while(N-- > 0) {
            String morseCode = st.nextToken();
            sb.append(morseCodeMap.get(morseCode));
        }
        System.out.println(sb);
    }
}
  • 시간 복잡도: O(n)
  • 공간 복잡도: O(n)

 

728x90
728x90

이번 문제 또한 매우 간단한 문제입니다.

N명의 학생이 존재하고, 3번째 줄부터 N개 나오는 학생들 이름을 가지고 인기도를 측정하여 출력하면 되는 문제입니다.

 

우선 여기서 봐야 할 점은 인기도 측정 방식정렬인데요.

인기도 측정 방식은 그냥 나오는 학생 이름들을 전부 인기도에 반영하면 되는 간단한 문제이고,

정렬은 주어진 조건에 맞춰 정렬하면 됩니다.

 

정렬의 경우 저는 Stream의 sorted함수를 사용하였는데요. 이때, sort조건을 커스텀함으로써 간단하게 정렬하였습니다.

이후 reduce를 활용하여 StringBuilder에 값들을 담게 했는데요, 이는 String과 달리 StringBuilder는 불변성이 아니어서 메모리 활용 측면에서 훨씬 효율적이라 판단하여 사용하였습니다.

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        Map<String, Integer> map = new HashMap<>();
        int N = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());

        for(int i = 0; i < N; i++) {
            map.put(st.nextToken(), 0);
        }

        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            while (st.hasMoreTokens()) {
                String to = st.nextToken();
                map.put(to, map.get(to) + 1);
            }
        }

        StringBuilder answer = map.entrySet().stream().sorted((prev, now) -> {
            if(prev.getValue() != now.getValue()) return now.getValue() - prev.getValue();
            return prev.getKey().compareTo(now.getKey());
        }).reduce(new StringBuilder(),(sb, entry) -> sb.append(entry.getKey()).append(" ").append(entry.getValue()).append("\n"),StringBuilder::append);
        System.out.println(answer);
    }
}
  • 시간복잡도: O(n)
  •  공간복잡도: O(n)
728x90

'알고리즘 문제풀이' 카테고리의 다른 글

[ 백준 - 11725 ] 트리의 부모 찾기  (0) 2024.11.12
[ 백준 - 29701 ] 모스 부호  (0) 2024.11.11
[ 백준 - 2358 ] 평행선  (2) 2024.11.09
[테코테코] 2주차 Stack  (0) 2024.09.23
Hackerrank 사이트 소개  (2) 2024.06.13
728x90

이번 문제는 고민 했던 것보다 꽤 쉽게 풀어져 가져와보았습니다.

 

위와 같이 문제가 주어졌습니다.

 

쉽게 생각해보면 점이 n개 주워졌을 때 x나 y에 평행이 되는 선의 갯수를 구하라는 뜻인데,

여기서 살짝 생각해보면 좋은 점은 x와 y에 평행이 되려면 어떤 조건을 가져야하는가 입니다.

 

x에 평행이 되려면 두 점의 y좌표는 동일해야할 것이고, y에 평행이 되려면 x좌표가 동일해야할 것 입니다.

 

즉, x점을 기준으로 묶은 자료와 y점을 기준으로 묶은 자료만 있다면 쉽게 문제를 풀 수 있는 것입니다.

public class Main {

    static Map<Integer, Integer> xLineMap = new HashMap<>();
    static Map<Integer, Integer> yLineMap = new HashMap<>();

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        for(int i = 0; i < n; i++) {
            String[] strs = br.readLine().split(" ");
            int x = Integer.parseInt(strs[0]);
            int y = Integer.parseInt(strs[1]);
            xLineMap.put(x, xLineMap.getOrDefault(x, 0) + 1);
            yLineMap.put(y, yLineMap.getOrDefault(y, 0) + 1);
        }
        int answer = 0;
        for (int count : xLineMap.values()) {
            if (count > 1) {
                answer++;
            }
        }
        for (int count : yLineMap.values()) {
            if (count > 1) {
                answer++;
            }
        }
        System.out.println(answer);
    }
}

 

 

위 문제를 확인해보면 2초의 시간초가 주어지고, n은 최대 100,000이라고 합니다.

이를 통해보면 알고리즘은 O(n)의 성능을 가지고 있도록 작성하는 것이 합리적이라 생각합니다.

 

위 알고리즘을 보면 n을 받아 n번 반복함으로 O(n)의 시간복잡도를 가지고 있다고 볼 수 있습니다.

또한, n을 받아 사용하는 데이터들을 보면 n개의 데이터들만을 활용하는 것을 볼 수 있어 공간복잡도 또한 O(n) 혹은 O(2n)의 복잡도를 가지고 있습니다.

  • 시간 복잡도: O(n)
  • 공간 복잡도: O(n)

 

728x90

+ Recent posts