https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/?envType=study-plan-v2&envId=top-interview-150

 

오름차순으로 정렬된 nums 배열이 주어졌을 때 이를 height-balanced binary search tree로 변환해라.

 

균형을 맞추는 것이 중요하다.

어떻게 하면 균형을 맞출 수 있을까?

중간값을 부모로 했을 때 균형이 맞게 된다. 배열의 중간값을 현재 서브트리의 루트로 사용하자.

그리고 왼쪽 절반은 왼쪽 서브트리로, 오른쪽 절반은 오른쪽 서브트리로 재귀적으로 처리하면 될 것 같다.

  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 TreeNode sortedArrayToBST(int[] nums) {  
        return constructTree(nums, 0, nums.length - 1);
    }

    private TreeNode constructTree(int[] nums, int left, int right) {
        if (left > right) {
            return null;
        }

        int mid = left + (right - left) / 2;
        TreeNode node = new TreeNode(nums[mid]);
        node.left = constructTree(nums, left, mid - 1);
        node.right = constructTree(nums, mid + 1, right);

        return node;
    }

}

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

LeetCode 120 - Triangle  (0) 2024.10.18
LeetCode 54 - Spiral Matrix  (0) 2024.10.17
LeetCode 53 - Maximum Subarray  (2) 2024.10.16
LeetCode 35 - Search Insert Position  (2) 2024.10.16
LeetCode 208 - Implement Trie (Prefix Tree)  (0) 2024.10.16

https://leetcode.com/problems/maximum-subarray/description/?envType=study-plan-v2&envId=top-interview-150

 

가장 합이 큰 subarray를 찾아라.

 

subarray를 찾아야 하기 때문에 정렬은 안된다.

가장 단순한 방법은 모든 가능한 부분 배열을 탐색하는 것이다. 물론 이는 배열의 길이가 늘어날수록 아주 비효율적인 알고리즘이 된다.

합을 어떻게 크게 만들 수 있을까. 합은 양수를 더하면 증가하고 음수를 더하면 감소한다.

특정 범위의 합이 음수라면 다음 숫자를 더하는 것 보다 다음 숫자부터 다시 시작하는 것이 맞다는 소리다.

투포인터를 이용하겠다. left,right를 0으로 두고 right로 범위를 확장시켜 나간다.

확장시키면서 최대 합을 갱신한다.

그러다가 합이 음수가 되는 순간 시작점인 left를 변경해 다시 검사를 하는 방식을 사용하겠다.

이 문제에서는 subarray의 범위를 구할 필요는 없어 left를 관리할 필요는 없다.

class Solution {
    public int maxSubArray(int[] nums) {
        int maxSum = nums[0];
        int currentSum = 0;

        for (int right = 0; right < nums.length; right++) {
            currentSum += nums[right];
            maxSum = Math.max(maxSum, currentSum);

            if (currentSum < 0) {
                currentSum = 0;
            }
        }

        return maxSum;
    }
}

 

그런데 이 문제의 분류를 보니 카데인 알고리즘이라는 내용이 나왔다.

카데인 알고리즘이 뭘까?

카데인 알고리즘은 1차원 배열에서 최대 부분 배열 합을 O(n) 시간 복잡도로 찾는 알고리즘이다.

동적 프로그래밍을 사용한다.

핵임 아이디어는 다음과 같다.

  1. 배열을 순회하면서 각 위치에서 끝나는 최대 부분 배열의 합을 계산한다.
  2. 각 단계에서 두 가지 선택 중 하나를 한다.
    1. 현재 원소부터 새로운 부분 배열을 시작한다.
    2. 현재 원소를 이전 부분 배열에 추가한다.
  3. 이 선택은 현재까지의 합이 양수인가, 음수인가에 따라 결정된다.

설명을 보면 알 수 있듯이 이 문제 자체가 카데인 알고리즘이었다.

현재 위치에서 최대 부분합을 고려하는 부분이 핵심이다.

여기서도 나오는 핵심이 이전까지의 합이 음수이면 현재 요소부터 새로운 부분 배열을 시작하는 것이 이득이다.

class Solution {
    public int maxSubArray(int[] nums) {
        int currentSum = nums[0];
        int maxSum = nums[0];

        for (int i = 1; i < nums.length; i++) {
            currentSum = Math.max(nums[i], currentSum + nums[i]);
            maxSum = Math.max(maxSum, currentSum);
        }

        return maxSum;
    }
}

 

Divide and Conquer 방식을 사용해서 푸는 방법도 있다.

문제를 더 작은 하위 문제로 나누고, 각 하위 문제를 해결한 다음 결과를 합쳐서 전체 문제의 해결책을 얻는 방법이다. 다음과 같은 과정을 거친다.

  1. 배열을 두 부분으로 나눈다.
  2. 왼쪽 절반의 최대 부분 배열을 찾는다.
  3. 오른쪽 절반의 최대 부분 배열을 찾는다.
  4. 중간을 가로지르는 최대 부분 배열을 찾는다.
  5. 위의 세 경우 중 가장 큰 합을 반환한다.

핵심은 중간을 가로지르는 최대 부분 배열이다. 이는 다음과 같은 과정을 통한다.

  1. 중간점에서 왼쪽으로 가며 최대 합을 찾는다.
  2. 중간점에서 오른쪽으로 가며 최대 합을 찾는다.
  3. 두 합을 더한다.

위의 과정들을 재귀를 통해 배열의 크기가 1이 될 때까지 반복하면 된다.

class Solution {
    public int maxSubArray(int[] nums) {
        return findMaxSubarray(nums, 0, nums.length - 1);
    }

    private int findMaxSubarray(int[] nums, int left, int right) {
        if (left == right) {
            // 배열 크기가 1
            return nums[left];
        }

        int mid = left + (right - left) / 2;

        int leftSum = findMaxSubarray(nums,left, mid);
        int rightSum = findMaxSubarray(nums, mid + 1, right);
        int crossSum = findMaxCrossingSubarray(nums, left, mid, right);

        return Math.max(Math.max(leftSum, rightSum), crossSum);
    }

    private int findMaxCrossingSubarray(int[] nums, int left, int mid, int right) {
        int leftSum = Integer.MIN_VALUE;
        int sum = 0;
        for (int i = mid; i >= left; i--) {
            sum += nums[i];
            leftSum = Math.max(leftSum, sum);
        }

        int rightSum = Integer.MIN_VALUE;
        sum = 0;
        for (int i = mid + 1; i <= right; i++) {
            sum += nums[i];
            rightSum = Math.max(rightSum, sum);
        }

        return leftSum + rightSum;
    }
}

https://leetcode.com/problems/search-insert-position/description/?envType=study-plan-v2&envId=top-interview-150

 

정렬된 배열 nums와 숫자 target이 주어졌을 때 target이 nums에 있다면 해당 인덱스를 없다면 삽입되는 인덱스를 반환하라. 단 O(logN)의 시간복잡도로 풀이해야 한다.

 

간단하게 O(N)으로 순회하면서 파악하면 답을 구할 수 있다.

하지만 문제 조건에 O(logN)을 요구하고 있으므로 다른 방법을 찾아야 한다.

주어진 조건을 보면 nums 배열은 중복이 없고 오름차순으로 정렬된 배열이다. 따라서 이진탐색을 사용하면 O(logN)의 시간복잡도로 풀 수 있다.

사실 자바에 Collections에 있는 binarySearch 메서드가 이미 위의 조건을 구현하고 있다.

class Solution {
    public int searchInsert(int[] nums, int target) {
        List<Integer> numbers = Arrays.stream(nums)
                                    .boxed()
                                    .collect(Collectors.toList());

        int idx =  Collections.binarySearch(numbers, target);
        return idx < 0 ? -idx - 1 : idx;
    }
}

그렇지만 위의 방법은 Collection에만 사용할 수 있으므로 배열을 List로 변경하는 작업이 추가된다.

따라서 이진탐색을 직접 구현하겠다.

class Solution {
    public int searchInsert(int[] nums, int target) {
        int l = 0;
        int r = nums.length - 1;
        
        while (l <= r) {
            int mid = l - (l - r) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                l = mid + 1;
            } else if (nums[mid] > target) {
                r = mid - 1;
            }
        }
  
        return l;
    }
}

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

+ Recent posts