https://leetcode.com/problems/implement-trie-prefix-tree/description/?envType=study-plan-v2&envId=top-interview-150

 

Trie 자료구조를 구현하는 문제이다.

우선은 Trie에 대해서 알아보고 코드로 구현해 보자.

Trie는 검색 트리의 줄임말로 때로는 접두사 트리라고도 부른다.

기본적으로 문자열을 저장하고 효율적으로 검색하기 위한 트리 기반의 자료구조이다.

각 노드가 문자열을 나타내며 루트에서 특정 노드까지 경로가 하나의 문자열을 형성하는 것이다.

이렇게 하면 얻게 되는 장점이 뭘까?

우선 동일한 접두사를 공유할 수 있다는 점이다.

같은 접두사를 가진 문자열들은 같은 노드들을 공유하고 이는 메모리 사용을 효율적으로 만든다.

다음으로는 검색속도이다.

문자열의 존재 여부를 확인하는 데 O(M)의 시간이 걸린다. (M은 문자열의 길이)

무엇보다 접두사 검색에 최적화 돼 있다.

특정 접두사로 시작하는 모든 문자열을 쉽게 찾을 수 있다.

class Trie {

    static class TrieNode {
        Map<Character, TrieNode> children;
        boolean isEndOfWord;

        public TrieNode() {
            children = new HashMap<>();
            isEndOfWord = false;
        }

        public Map<Character, TrieNode> getChildren() {
            return children;
        }

        public boolean isEndOfWord() {
            return isEndOfWord;
        }

        public void setEndOfWord(boolean endOfWord)  {
            isEndOfWord = endOfWord;
        }
    }

    private TrieNode root;

    public Trie() {
        root = new TrieNode();    
    }
    
    public void insert(String word) {
        TrieNode current = root;
        for (char ch : word.toCharArray()) {
            current = current.getChildren().computeIfAbsent(ch, x -> new TrieNode());
        }
        current.setEndOfWord(true);
    }
    
    public boolean search(String word) {
        TrieNode node = searchNode(word);
        return node != null && node.isEndOfWord();
    }
    
    public boolean startsWith(String prefix) {
        TrieNode node = searchNode(prefix);
        return node != null;
    }

    private TrieNode searchNode(String word) {
        TrieNode current = root;
        for (char ch : word.toCharArray()) {
            TrieNode node = current.getChildren().get(ch);
            if (node == null) return null;
            current = node;
        }
        return current;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */

Letter Combinations of a Phone Number - LeetCode

dfs를 통해서 가능한 문자들을 더해나간다.

번호와 문자열을 매칭해야 하는데 어차피 8개밖에 없으니 직접 넣어주는 방식을 선택하겠다.

class Solution {

    static Map<Integer, String> letterMap = new HashMap<>();

    static {
        letterMap.put(2, "abc");
        letterMap.put(3, "def");
        letterMap.put(4, "ghi");
        letterMap.put(5, "jkl");
        letterMap.put(6, "mno");
        letterMap.put(7, "pqrs");
        letterMap.put(8, "tuv");
        letterMap.put(9, "wxyz");
    }

    public List<String> letterCombinations(String digits) {
        if (digits.equals("")) return List.of();

        List<String> result = new ArrayList<>();
        dfs(0, "", digits, result);
        return result;    
    }

    private void dfs(int depth, String curr, String digits, List<String> result) {
        if (depth == digits.length()) {
            result.add(curr);
            return;
        }

        int key = digits.charAt(depth) - '0';
        for (String s : letterMap.get(key).split("")) {
            dfs(depth + 1, curr + s, digits, result);
        }
    }
}

Binary Tree Right Side View - LeetCode

 

이진트리의 root가 주어졌을 때 자신의 우측 방향에서 바라봤을 때 보이는 노드들의 값을 위에서 아래방향으로 반환하라.

오른쪽에서 봤을 때라는 뜻은 각 레벨마다 오른쪽에 있는 값이라는 뜻이다.

그렇다면 레벨별로 탐색하는 것이 중요하다.

레벨별로 탐색은 어떻게 가능할까? BFS를 사용하면 된다.

다음의 작업을 처리하면 된다.

  1. 현재 레벨의 처리해야 할 노드 수를 기록한다.
  2. 해당 레벨의 모든 노드를 순회한다.
  3. 그 노드의 왼쪽, 오른쪽 자식이 있다면 큐에 추가한다.
  4. 해당 레벨의 마지막 노드이면 결과 리스트에 추가한다.
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) return result;
        
        Queue<TreeNode> q = new LinkedList<>();
        q.add(root);

        while (!q.isEmpty()) {
            int levelSize = q.size();
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = q.poll();
                if (i == levelSize - 1) {
                    result.add(node.val);
                }
                if (node.left != null) q.add(node.left);
                if (node.right != null) q.add(node.right);
            }

        }
        return result;
    }    
}

 

그런데 이 문제는 DFS로도 해결이 가능하다.

트리를 탐색해 나가면서 오른쪽 자식을 먼저 보면 당연히 오른쪽에서 바라보는 노드의 값을 얻을 수 있다.

문제는 이제 왼쪽 서브트리에서 레벨이 더 큰 자식이 나왔을 때이다.

이를 해결하기 위해 레벨과 찾은 오른쪽에서 바라봤을 때의 노드 개수를 연관 지어 처리해 줄 방법이 필요하다.

해당 레벨에는 하나의 정답 노드가 존재할 것이다. 따라서 저장된 노드의 개수와 레벨을 비교하면 된다.

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) return result;
        dfs(0, root, result);
        return result;
    }    

    private void dfs(int level, TreeNode node, List<Integer> result) {
        if (node == null) return;
        
        if (level == result.size()) {
            result.add(node.val);
        }

        dfs(level + 1, node.right, result);
        dfs(level + 1, node.left, result);
    }
}

Minimum Absolute Difference in BST - LeetCode

 

구해야 하는 값이 노드에 있는 값의 차이 중에 절댓값이 제일 작은 것이다.

가장 먼저 떠오르는 방법은 모든 트리를 탐색해서 값들을 저장하고 거기서 모든 값을 순회해 목푯값을 찾는 것이다.

이는 N개의 노드를 탐색하고 이중루프를 사용하기 때문에 결과적으로 O(N^2)의 시간복잡도를 가지게 될 것이다. 아주 비효율적이다.

 

다른 효율적인 방법을 생각해보자.

최소 절대 차이를 어떻게 효율적으로 찾을 수 있을까?

생각해봐야 할 점은 주어진 트리가 그냥 이진트리가 아니라 이진 탐색 트리(BST)라는 것이다.

BST는 왼쪽 노드는 부모 노드보다 작고, 오른쪽 노드는 부모 노드보다 크다는 특징을 가지고 있다.

이 말은 중위순회를 사용하게 되면 노드 값이 정렬된 순서로 나열된다는 뜻이다.

그렇다면 모든 노드를 이중루프를 통해 확인할 필요 없이 인접한 값들끼리만 비교하면 될 것이다.

해야 할 일이 다음으로 정리됐다.

  1. BST를 중위 순회하여 정렬된 노드 값을 얻는다.
  2. 순회하면서 인접한 노드 간의 차이를 계산한다.
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int getMinimumDifference(TreeNode root) {
        List<Integer> values = new ArrayList<>();
        search(root, values);
        int min = Integer.MAX_VALUE;
        int prev = values.get(0);

        for (int i = 1; i < values.size(); i++) {
            int val = values.get(i);
            min = Math.min(min, val - prev);
            prev = val;
        }

        return min;
    }

    private void search(TreeNode node, List<Integer> values) {
        if (node == null) return;

        search(node.left, values);
        values.add(node.val);
        search(node.right, values);
    }
}

Summary Ranges - LeetCode

 

다음 숫자와 차이가 1보다 크면 같은 범위에 속한 숫자가 아니게 된다.

포인터를 두고 확장해 나가며 범위를 구해 정답에 추가하자.

class Solution {
    public List<String> summaryRanges(int[] nums) {
        if (nums.length == 0) {
            return List.of();
        }
        List<String> result = new ArrayList<>();
        int s = 0;
        int prev = nums[s];
        int e;
        for (e = 0; e < nums.length; e++) {
            if (Math.abs(nums[e] - prev) > 1) {
                // 범위를 벗어난 상태 s부터 e-1까지 유효 범위
                if (s != e - 1) {
                    result.add(nums[s] + "->" + nums[e - 1]);
                } else {
                    result.add(String.valueOf(nums[s]));
                }
                s = e;
            }
            prev = nums[e];
        }

        if (s == e - 1) {
            result.add(String.valueOf(nums[s]));
        } else {
            result.add(nums[s] + "->" + nums[e - 1]);
        }

        return result;
    }
}

여기서 주의해야 할 점은 크기 비교 부분이다.

nums[e] - prev > 1 이렇게 판정을 할 시 배열의 값이 int 범위이므로 오버플로우가 발생할 수 있다.

예를 들어 2147483647와 -2147483647를 보면 알 수 있다.

2147483647 - (-2147483647)를 하면 오버플로우로 순환이 발생해 -2가 반환값으로 나오게 된다.

이를 해결하기 위해 거리개념을 도입하기 위해 Math.abs를 사용했다. 아니면 오버플로우를 방지하기 위해 long으로 변환해 계산해도 된다.

Valid Sudoku - LeetCode

9x9 크기의 스도쿠 판이 유효할 수 있는지 판단하라.

빈칸은 다음과 같은 규칙으로 채워진다.

  1. 각각 row는 중복 없는 1~9 사이 숫자로 이루어진다.
  2. 각 col은 중복 없는 1~9 사이 숫자로 이루어진다.
  3. 3x3의 9개의 서브박스는 중복 없는 1~9 사이 숫자로 이루어진다.

숫자를 채워 넣는 스도쿠가 아니라 그냥 현재 스도쿠의 상태를 판정하는 문제이다. 중복 확인만 잘하면 된다.

중복 확인은 어떻게 해야할까? 열과 행은 각각을 HashSet으로 관리하면 될 것 같다.

박스의 경우 어떻게 해야할까? 박스의 대표 인덱스를 하나 표현할 수 있으면 좋을 것 같다.

이거는 (r / 3) * 3 + (c / 3) 이면 각 박스에 속하는 값들이 모두 같은 결과를 내게 된다. 따라서 해당 결괏값을 id로 사용하겠다.

class Solution {
    public boolean isValidSudoku(char[][] board) {
        Map<Integer, Set<Character>> rowMap = new HashMap<>();
        Map<Integer, Set<Character>> colMap = new HashMap<>();
        Map<Integer, Set<Character>> boxMap = new HashMap<>();

        for (int i = 0; i < 9; i++) {
            rowMap.put(i, new HashSet<>());
            colMap.put(i, new HashSet<>());
            boxMap.put(i, new HashSet<>());
        }

        for (int r = 0; r < 9; r++) {
            for (int c = 0; c < 9; c++) {
                if (board[r][c] != '.') {
                    char value = board[r][c];
                    if (rowMap.get(r).contains(value)) return false;
                    if (colMap.get(c).contains(value)) return false;
                    int boxId = (r / 3) * 3 + (c / 3);
                    if (boxMap.get(boxId).contains(value)) return false;

                    rowMap.get(r).add(value);
                    colMap.get(c).add(value);
                    boxMap.get(boxId).add(value);
                }
            }
        }

        return true;
    }
}

15684번: 사다리 조작 (acmicpc.net)

 

H x N 크기의 격자가 있는 것과 동일하다.

현재 세로 방향은 모든 점이 연결 돼 있는 것과 다름없다. 이제 가로를 연결해야 한다.

한 점을 선택하면 왼쪽 혹은 오른쪽의 한 점도 반드시 선택해야 한다.

그런데 다른 한 점이 이미 다른 점과 인접해 있다면 그 점은 선택이 불가능하다.

각 열에서 시작해 그래프를 탐색해 나갈 것이다. 이때 도착점의 열 번호가 시작점의 열 번호와 같게 하기 위해 가로선을 최소 몇 개 추가해야 하는가.

 

우선 대전제는 시작점에서 r이 증가해 가는 방향으로 이동하는 것이다. 그리고 r이 마지막 H에 도달하는 게 종료지점이다.

이때 현재 위치에서 좌우로 움직일 수 있다면 반드시 해당 열로 이동해야 한다.

좌우로 움직일 수 있는지의 여부는 가로선의 존재 여부이다.

기존에 존재하던 가로선 이외에 새롭게 가로선을 배치할 수 있는 곳들을 파악해야 한다.

그리고 탐색을 진행해 가는데 모든 점이 자신의 점으로 도착하면 성공 하나라도 실패하면 이제 새롭게 가로선을 배치해야 한다.

필요 기능들을 생각해 보자.

  1. 탐색하는 기능
  2. 현재 지점에서 좌우로 이동이 가능한지 확인하는 기능
  3. 가로선을 추가하는 기능

가로선을 1개 놓는 경우, 2개 놓는 경우, 3개 놓는 경우를 체크해야 한다.

이렇게 했는데도 경로를 못 찾으면 -1 반환

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

public class Main {

    static FastReader scan = new FastReader();
    static int N, M, H;
    static int WIDTH;
    static int[][] width;
    static List<int[]> widthCandidate;
    static int additionalCount = -1;

    static void input() {
        N = scan.nextInt();
        M = scan.nextInt();
        H = scan.nextInt();

        WIDTH = 2 * N - 1;
        width = new int[H][WIDTH];
        for (int i = 0; i < M; i++) {
            int r = scan.nextInt() - 1;
            int c = scan.nextInt();
            width[r][c + c - 1] = 1;
        }

        for (int c = 0; c < WIDTH; c += 2) {
            for (int r = 0; r < H; r++) {
                width[r][c] = 1;
            }
        }

        widthCandidate = new ArrayList<>();
        for (int row = 0; row < H; row++) {
            for (int col = 1; col < WIDTH; col += 2) {
                if (width[row][col] == 0) {
                    widthCandidate.add(new int[]{row, col});
                }
            }
        }
    }

    static void print() {
        for (int[] row : width) {
            System.out.println(Arrays.toString(row));
        }
        System.out.println();
    }

    static boolean addWidth(int count, int depth, int next, int[][] width) {
        if (depth == count) {
            // count개의 다리를 놨다.
            if (allPathSuccess()) return true;
            return false;
        }

        for (int i = next; i < widthCandidate.size(); i++) {
            int[] cand = widthCandidate.get(i);
            int r = cand[0];
            int c = cand[1];

            if (canAdd(r, c, width)) {
                width[r][c] = 1;
                if (addWidth(count, depth + 1, i + 1, width)) {
                    return true;
                };
                width[r][c] = 0;
            }
        }
        return false;
    }

    static boolean canAdd(int r, int c, int[][] width) {
        boolean left = true;
        boolean right = true;
        if (isValidPosition(r, c - 2)) {
            left = width[r][c - 2] != 1;
        }

        if (isValidPosition(r, c + 2)) {
            right = width[r][c + 2] != 1;
        }

        return left && right;
    }

    static boolean allPathSuccess() {
        int cnt = 0;
        for (int c = 0; c <= WIDTH; c += 2) {
            if (play(c, 0, c, new boolean[H][WIDTH])) {
                cnt++;
            }
        }
        return cnt == N;
    }

    static boolean play(int startC, int r, int c, boolean[][] visited) {
        if (r == H) {
            if (startC == c) {
                return true;
            }
            return false;
        }

        visited[r][c] = true;

        if (canMoveRight(r, c) && !visited[r][c + 2]) {
            return play(startC, r, c + 2, visited);
        } else if (canMoveLeft(r, c) && !visited[r][c - 2]) {
            return play(startC, r, c - 2, visited);
        } else {
            return play(startC, r + 1, c, visited);
        }
    }

    static boolean isValidPosition(int r, int c) {
        return r >= 0 && r < H && c >= 0 && c < WIDTH;
    }

    static boolean canMoveRight(int r, int c) {
        return isValidPosition(r, c + 1) && width[r][c + 1] == 1;
    }

    static boolean canMoveLeft(int r, int c) {
        return isValidPosition(r, c - 1) && width[r][c - 1] == 1;
    }

    public static void main(String[] args) {
        input();
        if (allPathSuccess()) {
            additionalCount = 0;
        } else {
            for (int count = 1; count <= 3; count++) {
                if (addWidth(count, 0, 0, width)) {
                    additionalCount = count;
                    break;
                }
            }
        }

        System.out.println(additionalCount);
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }

}

이렇게 풀었는데 시간초과가 발생했다.

이동해서 판정하는 로직에서 재귀를 사용함으로써 스택이 깊어져 시간 효율성이 떨어지는 문제를 해결하기 위해 반복문으로 변경했다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

public class Main {

    static FastReader scan = new FastReader();
    static int N, M, H;
    static int WIDTH;
    static int[][] width;
    static List<int[]> widthCandidate;
    static int additionalCount = -1;

    static void input() {
        N = scan.nextInt();
        M = scan.nextInt();
        H = scan.nextInt();

        WIDTH = 2 * N - 1;
        width = new int[H][WIDTH];
        for (int i = 0; i < M; i++) {
            int r = scan.nextInt() - 1;
            int c = scan.nextInt();
            width[r][c + c - 1] = 1;
        }

        for (int c = 0; c < WIDTH; c += 2) {
            for (int r = 0; r < H; r++) {
                width[r][c] = 1;
            }
        }

        widthCandidate = new ArrayList<>();
        for (int row = 0; row < H; row++) {
            for (int col = 1; col < WIDTH; col += 2) {
                if (width[row][col] == 0) {
                    widthCandidate.add(new int[]{row, col});
                }
            }
        }
    }

    static void print() {
        for (int[] row : width) {
            System.out.println(Arrays.toString(row));
        }
        System.out.println();
    }

    static boolean addWidth(int count, int depth, int next, int[][] width) {
        if (depth == count) {
            // count개의 다리를 놨다.
            if (allPathSuccess()) return true;
            return false;
        }

        for (int i = next; i < widthCandidate.size(); i++) {
            int[] cand = widthCandidate.get(i);
            int r = cand[0];
            int c = cand[1];

            if (canAdd(r, c, width)) {
                width[r][c] = 1;
                if (addWidth(count, depth + 1, i + 1, width)) {
                    return true;
                };
                width[r][c] = 0;
            }
        }
        return false;
    }

    static boolean canAdd(int r, int c, int[][] width) {
        boolean left = true;
        boolean right = true;
        if (isValidPosition(r, c - 2)) {
            left = width[r][c - 2] != 1;
        }

        if (isValidPosition(r, c + 2)) {
            right = width[r][c + 2] != 1;
        }

        return left && right;
    }

    static boolean allPathSuccess() {
        for (int i = 0; i < N; i++) {
            int c = i * 2; // 세로선 위치
            int currentC = c;
            for (int r = 0; r < H; r++) {
                // 오른쪽으로 이동
                if (canMoveRight(r, currentC)) {
                    currentC += 2;
                }
                // 왼쪽으로 이동
                else if (canMoveLeft(r, currentC)) {
                    currentC -= 2;
                }
                // 아래로 이동 (별도 처리 필요 없음)
            }
            if (currentC != c) {
                return false;
            }
        }
        return true;
    }

    static boolean isValidPosition(int r, int c) {
        return r >= 0 && r < H && c >= 0 && c < WIDTH;
    }

    static boolean canMoveRight(int r, int c) {
        return isValidPosition(r, c + 1) && width[r][c + 1] == 1;
    }

    static boolean canMoveLeft(int r, int c) {
        return isValidPosition(r, c - 1) && width[r][c - 1] == 1;
    }

    public static void main(String[] args) {
        input();
        for (int i = 0; i <= 3; i++) {
            if (addWidth(i, 0, 0, width)) {
                System.out.println(i);
                return;
            }
        }
        System.out.println(-1);
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }

}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 1655 - 가운데를 말해요  (0) 2024.10.25
BOJ 1944 - 복제 로봇  (2) 2024.10.23
BOJ 14889 - 스타트와 링크  (1) 2024.10.11
BOJ 14888 - 연산자 끼워넣기  (1) 2024.10.10
BOJ 14503 - 로봇 청소기  (2) 2024.10.10

14889번: 스타트와 링크 (acmicpc.net)

 

능력치의 차이를 최소화.

분할된 팀의 능력치를 구할 수 있어야 한다.

우선 N 명을 N/2 명 씩 두 팀으로 나눠야 한다.

그 후 각 팀의 점수를 계산해서 차이를 구한 다음 갱신을 한다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {

    static FastReader scan = new FastReader();
    static int N;
    static int[][] status;
    static int minGap = Integer.MAX_VALUE;

    static void input() {
        N = scan.nextInt();
        status = new int[N][N];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                status[i][j] = scan.nextInt();
            }
        }
    }

    static void recFunc(int depth, int[] team, boolean[] visited) {
        if (depth >= N/2) {
            Set<Integer> teamA = Arrays.stream(team).boxed().collect(Collectors.toSet());
            Set<Integer> teamB = IntStream.range(0, N)
                    .boxed()
                    .collect(Collectors.toSet());
            teamB.removeAll(teamA);
            calculate(teamA, teamB);
            return;
        }

        for (int i = depth == 0 ? 0 : team[depth - 1]; i < N; i++) {
            if (visited[i]) continue;
            team[depth] = i;
            visited[i] = true;
            recFunc(depth + 1, team, visited);
            team[depth] = 0;
            visited[i] = false;
        }
    }

    static void calculate(Set<Integer> teamA, Set<Integer> teamB) {
        int teamAScore = calculateTeamScore(teamA);
        int teamBScore = calculateTeamScore(teamB);
        minGap = Math.min(minGap, Math.abs(teamAScore - teamBScore));
    }

    static int calculateTeamScore(Set<Integer> team) {
        int score = 0;

        for (int user1 : team) {
            for (int user2 : team) {
                if (user1 == user2) continue;
                score += status[user1][user2];
            }
        }
        return score;
    }

    public static void main(String[] args) {
        input();
        recFunc(0, new int[N/2], new boolean[N]);
        System.out.println(minGap);
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}

그런데 이제 보니까 teamA와 teamB의 Set은 필요가 없다. 이미 visited에서 방문한 사람들을 체크하고 있기 때문에 해당 배열이 true이면 A팀 false면 B팀으로 처리하면 된다. 그냥 팀을 나눠야 한다는 생각에 쓸모없는 시간과 공간을 낭비했다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    static FastReader scan = new FastReader();
    static int N;
    static int[][] status;
    static int minGap = Integer.MAX_VALUE;

    static void input() {
        N = scan.nextInt();
        status = new int[N][N];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                status[i][j] = scan.nextInt();
            }
        }
    }

    static void recFunc(int depth, int[] team, boolean[] visited) {
        if (depth >= N/2) {
            int scoreGap = calculateTeamScoreGap(visited);
            minGap = Math.min(minGap, scoreGap);
            return;
        }

        for (int i = depth == 0 ? 0 : team[depth - 1]; i < N; i++) {
            if (visited[i]) continue;
            team[depth] = i;
            visited[i] = true;
            recFunc(depth + 1, team, visited);
            team[depth] = 0;
            visited[i] = false;
        }
    }

    static int calculateTeamScoreGap(boolean[] visited) {
        int score = 0;

        for (int i = 0; i < N; i++) {
            for (int j = i + 1; j < N; j++) {
                if (visited[i] && visited[j]) {
                    score += status[i][j] + status[j][i];
                } else if (!visited[i] && !visited[j]) {
                    score -= status[i][j] + status[j][i];
                }
            }
        }
        return Math.abs(score);
    }

    public static void main(String[] args) {
        input();
        recFunc(0, new int[N/2], new boolean[N]);
        System.out.println(minGap);
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 1944 - 복제 로봇  (2) 2024.10.23
BOJ 15684 - 사다리 조작  (0) 2024.10.11
BOJ 14888 - 연산자 끼워넣기  (1) 2024.10.10
BOJ 14503 - 로봇 청소기  (2) 2024.10.10
BOJ 14502 - 연구소  (0) 2024.10.10

14888번: 연산자 끼워넣기 (acmicpc.net)

 

연산자 4개의 개수가 주어진다. 수가 N개 있으니 N - 1개의 위치에 연산자들을 배치하면 된다.

재귀를 통해 연산자를 선택하고 결과를 누적해 간다.

모든 연산자 선택이 끝나고 결과가 완성되면 최대, 최소를 갱신한다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    static FastReader scan = new FastReader();
    static int N;
    static int[] nums;
    static int[] ops;
    static int MIN = Integer.MAX_VALUE;
    static int MAX = Integer.MIN_VALUE;

    static void dfs(int depth, int sum) {
        if (depth == N - 1) {
            MAX = Math.max(MAX, sum);
            MIN = Math.min(MIN, sum);
            return;
        }

        for (int i = 0; i < 4; i++) {
            if (ops[i] > 0) {
                ops[i]--;
                dfs(depth + 1, calculate(sum, i, nums[depth + 1]));
                ops[i]++;
            }
        }
    }

    static int calculate(int num1, int op, int num2) {
        if (op == 0) {
            return num1 + num2;
        } else if (op == 1) {
            return num1 - num2;
        } else if (op == 2) {
            return num1 * num2;
        } else {
            if (num1 < 0) {
                num1 = -num1;
                return -num1 / num2;
            } else {
                return num1 / num2;
            }
        }
    }

    static void input() {
        N = scan.nextInt();
        nums = new int[N];
        ops = new int[4];

        for (int i = 0; i < N; i++) {
            nums[i] = scan.nextInt();
        }

        for (int i = 0; i < 4; i++) {
            ops[i] = scan.nextInt();
        }
    }

    public static void main(String[] args) {
        input();
        dfs(0, nums[0]);
        System.out.println(MAX);
        System.out.println(MIN);
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 15684 - 사다리 조작  (0) 2024.10.11
BOJ 14889 - 스타트와 링크  (1) 2024.10.11
BOJ 14503 - 로봇 청소기  (2) 2024.10.10
BOJ 14502 - 연구소  (0) 2024.10.10
BOJ 14501 - 퇴사  (1) 2024.10.09

14503번: 로봇 청소기 (acmicpc.net)

 

로봇 청소기의 이동 = 바라보는 방향이 존재

처음 빈칸은 전부 청소되지 않은 상태

  1. 현재 칸이 청소되지 않은 경우 현재 칸을 청소
  2. 현재 칸의 주변 4칸 중 청소되지 않은 빈칸이 없는 경우
    1. 바라보는 방향을 유지한 채로 한 칸 후진할 수 있다면 한 칸 후진하고 1번으로
    2. 바라보는 방향의 뒤쪽 칸이 벽이라 후진할 수 없다면 작동을 멈춘다.
  3. 현재 칸의 주변 4칸 중 청소되지 않은 빈 칸이 있는 경우
    1. 반시계 방향으로 90도 회전한다.
    2. 바라보는 방향을 기준으로 앞쪽이 청소되지 않은 빈칸인 경우 한 칸 전진한다.
    3. 1번으로 돌아간다.

 

필요한 기능을 정리해보자.

  1. 현재 위치의 청소 여부 판단
  2. 주변 4칸의 청소 여부 판단
  3. 후진
  4. 전진
  5. 반시계 90도 회전
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    static FastReader scan = new FastReader();
    static int[][] DIRECTION = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}}; // 북서남동
    static int N, M;
    static Robot robot;
    static int[][] map;

    static class Robot {
        int r;
        int c;
        int d;

        public Robot(int r, int c, int d) {
            this.r = r;
            this.c = c;
            this.d = d;
        }

        void moveForward() {
            r += DIRECTION[d][0];
            c += DIRECTION[d][1];
        }

        void moveBack() {
            r -= DIRECTION[d][0];
            c -= DIRECTION[d][1];
        }

        void rotate() {
            d = (d + 1) % 4;
        }
    }

    static class Room {
        int[][] map;
        Robot robot;
        int cleanCount;

        public Room(int[][] map, Robot robot) {
            this.map = map;
            this.robot = robot;
        }

        public void clean() {
            while (true) {
                if (map[robot.r][robot.c] == 0) {
                    map[robot.r][robot.c] = 2;
                    cleanCount++;
                }

                // 주변 4칸을 탐색
                if (existUncleanRoom(map, robot.r, robot.c)) {
                    // 4칸 중 청소되지 않은 빈 칸이 존재한다.
                    for (int i = 0; i < 4; i++) {
                        robot.rotate();
                        int nr = robot.r + DIRECTION[robot.d][0];
                        int nc = robot.c + DIRECTION[robot.d][1];
                        if (nr < 0 || nr >= N || nc < 0 || nc >= M) {
                            continue;
                        }
                        if (map[nr][nc] == 0) {
                            robot.moveForward();
                            break;
                        }
                    }

                } else {
                    // 4방향 다 봤는데 청소되지 않은 칸이 없다.
                    robot.moveBack();
                    if (map[robot.r][robot.c] == 1) {
                        // 후진했는데 벽인 경우 종료
                        return;
                    }
                }
            }
        }

        private boolean existUncleanRoom(int[][] map, int r, int c) {
            for (int d = 0; d < 4; d++) {
                if (map[r + DIRECTION[d][0]][c + DIRECTION[d][1]] == 0) {
                    return true;
                }
            }
            return false;
        }

        public int getCleanCount() {
            return cleanCount;
        }
    }

    static void input() {
        N = scan.nextInt();
        M = scan.nextInt();
        map = new int[N][M];

        // 로복 좌표, 방향
        int r = scan.nextInt();
        int c = scan.nextInt();
        int d = scan.nextInt();
        if (d == 0) {
            // 북
            d = 0;
        } else if (d == 1) {
            // 동
            d = 3;
        } else if (d == 2) {
            // 남
            d = 2;
        } else {
            //서
            d = 1;
        }

        robot = new Robot(r, c, d);
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                map[i][j] = scan.nextInt(); // 0 빈칸 1 벽
            }
        }
    }

    public static void main(String[] args) {
        input();
        Room room = new Room(map, robot);
        room.clean();
        System.out.println(room.getCleanCount());
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 14889 - 스타트와 링크  (1) 2024.10.11
BOJ 14888 - 연산자 끼워넣기  (1) 2024.10.10
BOJ 14502 - 연구소  (0) 2024.10.10
BOJ 14501 - 퇴사  (1) 2024.10.09
BOJ 14500 - 테트로미노  (1) 2024.10.09

+ Recent posts