https://leetcode.com/problems/minimum-size-subarray-sum

 

subarray의 합이 target보다 크거나 같은 subarray 중 최소 길이를 구하라.

subarray이기에 정렬이 수행되면 안 된다.

 

연속된 수를 확인할 때는 슬라이딩 윈도 기법이 좋다.

여기서 슬라이딩 윈도의 크기를 조절하며 최소 길이를 갱신하는 방법을 쓰면 될 것 같다.

 

이 슬라이딩 윈도는 두 개의 포인터로 구성이 돼있다.

두 포인터 사이에 있는 값들의 합이 target보다 크면 최소 길이를 갱신한다. 하지만 최소를 구해야 하기 때문에 범위를 줄여가며 체크를 해줘야 한다.

따라서 왼쪽 범위를 하나 줄이고 다시 체크를 하는 동작을 반복한다.

그러다가 target보다 합이 작아져 더 이상 왼쪽을 줄일 수 없을 때 오른쪽 범위를 늘려주도록 한다.

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int minLength = Integer.MAX_VALUE;
        int sum = 0;
        int left = 0;

        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];

            while (sum >= target) {
                minLength = Math.min(minLength, right - left + 1);
                sum -= nums[left];
                left++;
            }
        }

        return minLength == Integer.MAX_VALUE ? 0 : minLength;
    }
}

https://leetcode.com/problems/3sum

 

i ≠ j, i ≠ k, j ≠ k 이면서 nums [i] + nums [j] + nums [k] == 0 인 nums [i], nums [j], nums [j]의 조합을 모두 찾아라.

nums 배열을 보면 중복된 숫자가 존재할 수 있다.

int[ ] nums = [-1, 0, 1, 2, -1, -4]로 주어졌다고 가정하자.

합이 0이 되는 경우는 -1 + 0 + 1, -1 + -1 + 2가 존재한다. 하나씩 전부 확인하는 브루트포스방식을 사용하면 찾을 수는 있지만 아주 비효율적이다. 어떻게 하면 효율적으로 찾을 수 있을까?

보통 다른 문제에서는 두 수의 합이 target이 되는 경우를 찾는다. 그리고 그때 정렬과 투 포인터를 주로 사용한다.

그런 관점에서 보면 이 문제는 target이 변하는 투 포인터 문자라고도 볼 수 있다고 생각한다.

 

우선 정렬을 수행해보자. 정렬을 하면 중복을 뛰어넘기 쉽고 투 포인터 사용에도 용이하다.

nums = [-4, -1, -1, 0, 1, 2]이다. 0이 되기 위해서 하나의 숫자를 고정해 보다. 그 숫자가 -4라고 하자. 그렇다면 두 수의 합이 4가 되는 수를 찾는 것이다. 그다음은 그 숫자가 -1이 되고 합이 1이 되는 숫자를 찾는 동작이다. 이 동작을 끝까지 반복하는 것이다.

 

해야 할 일을 분리해보자.

  1. target을 정한다.
  2. 투 포인터를 사용해 target과 일치하는 조합을 찾는다.
  3. 중복을 피하기 위한 로직을 추가한다.
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> answer = new ArrayList<>();
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 2; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
                findTriplets(i, nums, answer);
        }

        return answer;
    }

    private void findTriplets(int targetIdx, int[] nums, List<List<Integer>> answer) {
        int left = targetIdx + 1;
        int right = nums.length - 1;

        while (left < right) {
            int sum = nums[left] + nums[right] + nums[targetIdx];

            if (sum == 0) {
                answer.add(Arrays.asList(nums[targetIdx], nums[left], nums[right]));

                while (left < right && nums[left] == nums[left + 1]) left++;
                while (left < right && nums[right] == nums[right - 1]) right--;

                left++;
                right--;
            } else if (sum < 0) {
                left++;
            } else {
                right--;
            }
        }
    }
}

https://leetcode.com/problems/container-with-most-water

 

가장 많은 물을 가두기 위해 어느 두 점을 선택해야 하는가.

너비라는 것이 가로 x 세로이다. 일단 가로를 가장 길게 설정해 둔 다음 가로를 좁혀가며 최댓값을 변경하면 될 것 같다.

이때 가로를 어떻게 좁히느냐가 문제인데 작은 것을 기준으로 세로가 정해지기 때문에 세로값이 작은 것을 변경시키는 방향으로 가는 것이 맞다.

class Solution {
    public int maxArea(int[] height) {
        int max = Integer.MIN_VALUE;

        int left = 0;
        int right = height.length - 1;

        while (left < right) {
            int sum = (right - left) * Math.min(height[left], height[right]);
            max = Math.max(max, sum);

            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }

        return max;
    }
}

https://leetcode.com/problems/two-sum-ii-input-array-is-sorted

 

오름차순으로 정렬된 int [] numbers가 주어졌을 때 합이 target이 되는 두 수를 찾아라.

정확히 하나의 답이 있다고 보장.

두 개의 포인터를 가지고 합을 확인하는 과정이 필요하다.

이때 정렬 돼 있다는 조건을 활용하기 위해 투 포인터를 양 쪽 끝에서 출발해 범위를 좁혀온다.

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0;
        int right = numbers.length - 1;

        while (left < right) {
            int sum = numbers[left] + numbers[right];
            if (sum > target) {
                right--;
            } else if (sum < target) {
                left++;
            } else {
                return new int[]{left + 1, right + 1};
            }
        }
        return null; // 어차피 답은 반드시 존재한다.
    }
}

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

LeetCode 15 - 3Sum  (0) 2024.10.04
LeetCode 11 - Container With Most Water  (0) 2024.10.04
LeetCode 6 - Zigzag Conversion  (1) 2024.10.03
LeetCode 151 - Reverse Words in a String  (1) 2024.10.03
LeetCode 12 - Integer to Roman  (0) 2024.10.03

https://leetcode.com/problems/zigzag-conversion

주어진 문자열 s를 numRows에 맞게 지그재그 패턴으로 만든 다음 row별로 읽어서 합친다.

예를 들어 s = “PAYPALISHIRING”, numRows = 4 이면 output은 “PINALSIGYAHRPI”가 된다.

P     I    N
A   L S  I G
Y A   H R
P     I

규칙을 살펴보자.

나는 String [] 배열이 numRows 수만큼 존재한다고 가정하겠다.

그렇다면 배열의 크기를 정하는게 중요해지는데 numRows = 1인 경우 가장 긴 길이의 배열이 필요할 것이다.

따라서 s의 길이만큼의 배열을 생성하도록 하겠다.

문자의 할당은 어떻게 할 것인가.

문자의 할당 방식은 아래쪽 방향 이동, 오른쪽 위 대각선 방향 이동 두 가지가 존재한다.

우선은 아래 방향으로 이동하다가 최대로 이동할 수 있는 곳(numRows)에 도달하면 방향을 바꿔 오른쪽 대각선 방향 이동을 하게 하면 된다.

class Solution {

    private static final int[][] DIRECTION = {{1, 0}, {-1, 1}};

    public String convert(String s, int numRows) {
        if (numRows == 1) {
            return s;
        }

        int n = s.length();
        char[][] rows = new char[numRows][n];
        for (char[] row : rows) {
            Arrays.fill(row, ' ');
        }
        // 시작 위치
        int r = 0;
        int c = 0;
        int d = 0;

        for (char ch : s.toCharArray()) {

            rows[r][c] = ch;

            r += DIRECTION[d][0];
            c += DIRECTION[d][1]; 

            if (!isValidPosition(r, c, rows)) {
                r -= DIRECTION[d][0];
                c -= DIRECTION[d][1];   
                d = (d + 1) % DIRECTION.length;
                r += DIRECTION[d][0];
                c += DIRECTION[d][1]; 
            }
        }
        
        StringBuilder sb = new StringBuilder();
        for (char[] row : rows) {
            for (char ch : row) {
                if (ch != ' ') {
                    sb.append(ch);
                }
            }
        }

        return sb.toString();
    }

    private boolean isValidPosition(int r, int c, char[][] rows) {
        return r >= 0 && r < rows.length && c >= 0 && c < rows[r].length;
    }
}

 

코드를 개선해보자. 현재 낭비되는 공간이 많다.

또한 대각선 이동도 정확한 열의 위치가 필요한 것이 아니다. 단지 어느 행에 위치하는지가 중요하다.

2차원 배열이 아니라 StringBuilder 배열로 변경하면 각 row를 처리하면서 공간 낭비를 줄일 수 있다.

class Solution {
    public String convert(String s, int numRows) {
        if (numRows == 1 || s.length() <= numRows) {
            return s;
        }

        StringBuilder[] rows = new StringBuilder[numRows];
        for (int i = 0; i < numRows; i++) {
            rows[i] = new StringBuilder();
        }
        
        int currentRow = 0;
        boolean goingDown = false;

        for (char c : s.toCharArray()) {
            rows[currentRow].append(c);

            if (currentRow == 0 || currentRow == numRows - 1) {
                // 방향 전환
                goingDown = !goingDown;
            }

            currentRow += goingDown ? 1 : -1;
        }

        StringBuilder result = new StringBuilder();
        for (StringBuilder row : rows) {
            result.append(row);
        }

        return result.toString();
    }
}

https://leetcode.com/problems/reverse-words-in-a-string

 

문자열에서 단어들을 꺼내 역순으로 반환하는 문제이다. 각 단어들은 하나의 공백을 사이에 두고

합쳐진다.

예를 들어 “ hello world “는 “world hello”로 반환된다.

보다시피 원본 문자열에 띄어쓰기가 여러 개 존재할 수도 있다.

가장 먼저 떠오른 방법은 문자열 리스트에 그냥 단어별로 파싱 해서 넣고 조작하는 것이다.

class Solution {
    public String reverseWords(String s) {
        List<String> words = new ArrayList<>();

        StringBuilder word = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == ' ') {
                if (!word.isEmpty()) {
                    words.add(word.toString());
                }
                word = new StringBuilder();
            } else {
                word.append(s.charAt(i));
            }
        }
        if (!word.isEmpty()) {
            words.add(word.toString());
        }

        StringBuilder answer = new StringBuilder();
        for (int i = words.size() - 1; i > 0; i--) {
            answer.append(words.get(i)).append(" ");
        }
        answer.append(words.get(0));

        return answer.toString();
    }
}

앞에서부터 문자를 하나씩 탐색하며 단어를 완성시켜 나간다.

그러다가 공백을 만나면 기존에 쌓아왔던 문자들을 하나의 단어로 판정하고 리스트에 넣고 쌓아오던 문자열을 초기화한다.

최종적으로는 역순으로 뒤에서부터 단어를 꺼내 결과를 생성한다.

그런데 자바에서 제공해 주는 메서드를 통해 더 간단하게 해결이 가능하다.

class Solution {
    public String reverseWords(String s) {
        
        String[] words = s.trim().split("\\\\s+");// 좌우 공백제거, 공백이 1개 이상인 것을 기준으로 분리
        StringBuilder reversed = new StringBuilder();
        
        for (int i = words.length - 1; i >= 0; i--) {
            reversed.append(words[i]);
            if (i > 0) reversed.append(" ");
        }

        return reversed.toString();
    }
}

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

LeetCode 167 - Two Sum II - Input Array Is Sorted  (0) 2024.10.03
LeetCode 6 - Zigzag Conversion  (1) 2024.10.03
LeetCode 12 - Integer to Roman  (0) 2024.10.03
LeetCode 13 - Roman to Integer  (1) 2024.10.03
LeetCode 42 - Trapping Rain Water  (0) 2024.10.02

https://leetcode.com/problems/integer-to-roman

 

Symbol 별로 숫자가 할당되어 있다.

10진수의 수를 이제 로마수로 변경해야 한다. 변경은 다음과 같은 과정을 통한다.

  1. 수가 4 혹은 9로 시작하지 않는다면 input으로부터 뺄 수 있는 최대 symbol을 선택한다. 그리고 해당 symbol을 결과에 추가한다. 그리고 해당 숫자를 뺀 나머지를 로마수로 재할당한다.
  2. 만약 수가 4 혹은 9로 시작하다면, 해당 값을 뺄샘 형태로 변환하여 로마수로 표현한다. 예를 들어 4는 5(V)에서 1(I)을 뺀 값이 되므로 IV가 된다.
  3. 10의 거듭제곱은 최대 3번까지 연속해서 붙일 수 있다. 하지만 5(V), 50(L), 500(D)은 여러 번 반복해서 사용할 수 없다. 만약 어떤 기호가 4번 반복해서 사용해야 할 경우에는 뺄셈 형태로 변환한다.

우선 하나씩 생각해보자.

주어진 숫자 num의 첫자리 숫자를 어떻게 판단 할 것인가?

4냐 400이냐에 따라 조합이 달라지기 때문에 첫 자리 숫자가 어떤 수인지를 넘어 자릿수까지 판단을 해야 한다.

문자열로 판단하는 게 낫겠다는 생각이 든다. int num = 3749라고 가정해 보자.

문자열로 바꾸면 “3749”, 길이가 4인 문자열이 된다.

첫자리는 3 * 10 ^4 그다음은 7 * 10^3, 410^2, 910^1 이렇게 된다. 매 순간 판단하는 숫자가 문자열의 길이와 chartAt(n)으로 편리하게 구할 수 있다.

class Solution {

    private static final Map<Integer, String> symbolMap = new LinkedHashMap<>();

    static {
        symbolMap.put(1000, "M");
        symbolMap.put(500, "D");
        symbolMap.put(100, "C");
        symbolMap.put(50, "L");
        symbolMap.put(10, "X");
        symbolMap.put(5, "V");
        symbolMap.put(1, "I");
    }

    public String intToRoman(int num) {
        StringBuilder sb = new StringBuilder();
        String number = Integer.toString(num);
        int length = number.length();

        for (int i = 0; i < length; i++) {
            int coefficient = number.charAt(i) - '0';
            int exponent = length - i - 1;
            
            int now = coefficient * (int)Math.pow(10, exponent);
            
            if (coefficient == 4 || coefficient == 9) {
                for (int key : symbolMap.keySet()) {
                    if (key > now && symbolMap.containsKey(key - now)) {
                        sb.append(symbolMap.get(key - now));
                        sb.append(symbolMap.get(key));
                    }
                }
            } else {
                for (int key : symbolMap.keySet()) {
                    while (now >= key) {
                        now -= key;
                        sb.append(symbolMap.get(key));
                    }
                }
            }
        }

        return sb.toString();
    }
}

일단 통과는 됐다.

간단하게 설명하자면 우선 LinkedHashMap을 사용해 데이터를 삽입된 순서대로 저장되도록 처리했다.

그 후 주어진 num을 문자로 변경하고 이 문자열을 조작해 문제를 풀었다.

그런데 매 숫자마다 일일이 확인하고 변환하는 과정 때문에 효율적이지 않다.

그러면 애초에 가능한 로마 symbol을 다 저장해 놓으면 되는 거 아닐까? 생각보다 개수가 몇 개 없으니 가능한 방식이다.

class Solution {

    private static final Map<Integer, String> SYMBOL_MAP = new LinkedHashMap<>();

    static {
        SYMBOL_MAP.put(1000, "M");
        SYMBOL_MAP.put(900, "CM");
        SYMBOL_MAP.put(500, "D");
        SYMBOL_MAP.put(400, "CD");
        SYMBOL_MAP.put(100, "C");
        SYMBOL_MAP.put(90, "XC");
        SYMBOL_MAP.put(50, "L");
        SYMBOL_MAP.put(40, "XL");
        SYMBOL_MAP.put(10, "X");
        SYMBOL_MAP.put(9, "IX");
        SYMBOL_MAP.put(5, "V");
        SYMBOL_MAP.put(4, "IV");
        SYMBOL_MAP.put(1, "I");
    }

    public String intToRoman(int num) {
        StringBuilder sb = new StringBuilder();

        for (int key : SYMBOL_MAP.keySet()) {
            while (num >= key) {
                num -= key;
                sb.append(SYMBOL_MAP.get(key));
            }
        }

        return sb.toString();
    }
}

그런데 더 개선할 점이 있는 것 같다.

데이터셋 저장을 LinkedHashMap을 사용함으로써 삽입 순서를 유지한 것은 좋았지만 각 반복에서 Map의 엔트리에 접근하는 데 추가적인 동작이 들어간다.

LinkedHashMap은 내부적으로 LinkedList를 사용하니까 메모리 접근이 배열에 비해서 덜 효율적일 수 있다.

따라서 해당 저장을 두 개의 배열로 나누어 저장해서 접근하면 조금 더 효율적인 결과를 얻을 수 있을 것이다.

class Solution {
    private static final int[] VALUES = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
    private static final String[] SYMBOLS = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};

    public String intToRoman(int num) {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < VALUES.length; i++) {
            while (num >= VALUES[i]) {
                num -= VALUES[i];
                sb.append(SYMBOLS[i]);
            }
        }

        return sb.toString();
    }
}

이 문제는 결국 공간과 시간의 트레이드오프를 보여줬던 문제였던 것 같다.

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

LeetCode 6 - Zigzag Conversion  (1) 2024.10.03
LeetCode 151 - Reverse Words in a String  (1) 2024.10.03
LeetCode 13 - Roman to Integer  (1) 2024.10.03
LeetCode 42 - Trapping Rain Water  (0) 2024.10.02
LeetCode 150 - Candy  (1) 2024.09.30

https://leetcode.com/problems/roman-to-integer

 

symbol 별로 숫자값이 할당 돼 있다.

처음 구상은 주어진 문자열 s를 char로 바꿔 하나씩 symbol을 확인해 가며 숫자로 변경하고 더하는 방식이었다.

하지만 문제에는 예외가 있었다. 큰 숫자에서 작은 숫자 순서로 나오는 로마 숫자이지만 작은 숫자 다음 큰 숫자가 나오면 뺄셈이 적용된다는 것이다.

예를 들어 문자열 s = “MCMXCIV”일 떄를 보자.

이는 [’M’, ‘C’, ‘M’, ‘X’, ‘C’, ‘I’, ‘V’] 로 바뀐다. 이를 숫자로 바꾸면 [1000, 100, 1000, 10, 100, 1, 5]가 된다.

이제 합을 누적 해 가야 한다. 단 다음 수보다 작은 수의 경우는 뺄 샘을 해준다.

1000 - 100 +1000 - 10 + 100 - 1 + 5 = 1994 가된다.

class Solution {

    static Map<Character, Integer> symbolMap = new HashMap<>();

    public int romanToInt(String s) {
        int n = s.length();
        if (n == 0) {
            return 0;
        }
        int answer = 0;
        init();

        for (int i = 0; i < n - 1; i++) {
            int symbolValue = symbolMap.get(s.charAt(i));
            int nextSymbolValue = symbolMap.get(s.charAt(i + 1));

            if (symbolValue < nextSymbolValue) {
                answer -= symbolValue;
            } else {
                answer += symbolValue;
            }

        }
        return answer + symbolMap.get(s.charAt(n - 1));
    }

    private void init() {
        symbolMap.put('I', 1);
        symbolMap.put('V', 5);
        symbolMap.put('X', 10);
        symbolMap.put('L', 50);
        symbolMap.put('C', 100);
        symbolMap.put('D', 500);
        symbolMap.put('M', 1000);
    }
}

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

LeetCode 151 - Reverse Words in a String  (1) 2024.10.03
LeetCode 12 - Integer to Roman  (0) 2024.10.03
LeetCode 42 - Trapping Rain Water  (0) 2024.10.02
LeetCode 150 - Candy  (1) 2024.09.30
LeetCode 134 - Gas Station  (1) 2024.09.29

https://leetcode.com/problems/trapping-rain-water/description

 

얼마나 많은 양의 물을 가둘 수 있을까?

주어지는 배열은 각 지점의 높이이다.

idx에 가둬진 물의 양이란 idx 높이 위에 쌓인 물과도 같다.

물이 쌓인다는 것은 양 옆에 자신보다 높이가 큰 벽이 존재해야 가능하다.

예제를 보며 파악해 보자. int [ ] heights = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]이다.

idx = 5일 때를 보자. idx = 5인 지점의 높이는 0이다. 이곳에 물이 쌓이기 위해서는 양 옆으로 0보다 큰 높이가 존재해야 한다.

여기서 물이 쌓이기 위한 조건이 결정됐다. 그건 바로 자신의 위치 양 옆 방향으로 자신보다 큰 높이가 존재해야 한다는 것이다.

그렇다면 양은 어떻게 결정이 될까?

자신을 기준으로 해서 좌우의 자신보다 높이가 높은 것 중 작은 것과 자신의 높이의 차이만큼 쌓이다.

어떻게, 얼마나 쌓이는지는 확인이 됐다.

양 끝점은 한쪽 벽이 없기에 어차피 물이 쌓이지 않는다. 그리고 양 쪽의 높이를 확인해야 하기 때문에 가장 높이가 높은 지점의 위치를 정해두면 두 지점 중 한 지점이 정해지니까 나머지의 파악이 쉬워진다.

class Solution {
    public int trap(int[] height) {
        int maxIdx = findMaxIdx(height);
        int l = 0;
        int r = height.length - 1;

        int left = height[l];
        int right = height[r];

        int answer = 0;
        // left to max
        for (int i = 1; i < maxIdx; i++) {
            left = Math.max(left, height[i - 1]);
            if (left > height[i]) {
                answer += left - height[i];
            }
        }

        // right to max;
        for (int i = r - 1; i > maxIdx; i--) {
            right = Math.max(right, height[i + 1]);
            if (right > height[i]) {
                answer += right - height[i];
            }
        }

        return answer;
    }
    
    private int findMaxIdx(int[] height) {
        int maxHeight = 0;
        int maxIdx = 0;

        for (int i = 0; i < height.length; i++) {
            if (maxHeight < height[i]) {
                maxHeight = height[i];
                maxIdx = i;
            }
        }

        return maxIdx;
    }
}

우선은 이렇게 풀었다. 그렇지만 반복문을 3번이나 순회하고 있다.

또한 배열의 길이가 0이나 1인 경우에 대한 대처가 부족하다.

class Solution {
    public int trap(int[] height) {
        // 엣지 케이스 처리
        if (height == null || height.length <= 2) return 0;
        
        int left = 0, right = height.length - 1;
        int leftMax = 0, rightMax = 0;
        int water = 0;
        
        // 배열을 한 번만 순회하며 물의 양을 계산
        while (left < right) {
            if (height[left] < height[right]) {
                // 왼쪽 벽이 더 낮은 경우
                if (height[left] >= leftMax) {
                    leftMax = height[left];
                } else {
                    water += leftMax - height[left];
                }
                left++;
            } else {
                // 오른쪽 벽이 더 낮거나 같은 경우
                if (height[right] >= rightMax) {
                    rightMax = height[right];
                } else {
                    water += rightMax - height[right];
                }
                right--;
            }
        }
        
        return water;
    }
}

이렇게 하면 한번의 반복을 수행한다.

또한 left, right 둘 중 최대 높이에 도달하면 나머지 한쪽만 계속 이동을 하면서 가둬진 물을 추가해 나간다.

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

LeetCode 12 - Integer to Roman  (0) 2024.10.03
LeetCode 13 - Roman to Integer  (1) 2024.10.03
LeetCode 150 - Candy  (1) 2024.09.30
LeetCode 134 - Gas Station  (1) 2024.09.29
LeetCode 238 - Product of Array Except Self  (0) 2024.09.29

https://school.programmers.co.kr/learn/courses/30/lessons/92342

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

라이언(우승자), 어피치(도전자)

어피치가 먼저 n발을 쏜 후 라이언이 화살 n발을 쏜다.

k(1 ~ 10)점을 어피치가 a발맞추고 라이언이 b발맞춘 경우 더 많은 화살을 k점에 맞힌 선수가 k점을 가져간다. 단, a = b 일 경우는 어피치가 k점을 가져한다.

k점을 많이 맞춘다고 k점 보다 많은 점수를 가져가는게 아니라 k점만 가져한다.

모든 결과가 나왔을 때 최종 점수가 더 높은 선수를 우승자로 결정한다. 단, 최종 점수가 같을 경우 어피치가 우승한다.

현재 상황은 어피치는 이미 n발을 다 쐈고 라이언이 n발을 쏠 차례이다.

라이언이 가장 큰 점수 차이로 이기기 위해서 n 발의 화살을 어떤 과녁 점수에 맞혀야 하는가. 만약 라이언이 우승할 수 없는 경우 [-1]을 return

라이언이 가장 큰 점수 차이로 우승할 수 있는 방법이 여러 가지 일 경우, 가장 낮은 점수를 더 많이 맞힌 경우를 return

 

점수가 10~0점 까지 있다. 라이언이 해당 점수를 가져갈지 말지를 결정하는 문제이다.

해당 점수를 가져가려면 어피치가 맞춘 개수 + 1 개를 맞춰야 한다.

그렇다면 완전탐색을 통해서 모든 경우의 수를 확인하면 된다. 여기서 변하는 값은 표적의 인덱스와 남은 화살의 개수가 될 것이다.

화살이 0개 미만이 되면 더 이상 선택을 할 수 없어진다.

그런데 가장 큰 점수 차이로 우승할 수 있는 방법이 여러 가지 일 경우의 조건을 해결해야 한다.

만약 점수 차이가 갱신이 되면 해당 배열을 저장한다. 점수 차이가 동일하다면 두 배열을 비교해 낮은 점수가 많은 배열을 선택해서 저장한다.

import java.util.*;

class Solution {
    
    static int maxScoreGap = Integer.MIN_VALUE;
    static int[] answer = {};
    
    public int[] solution(int n, int[] info) {
        int[] ryan = new int[11];
        dfs(0, n, ryan, info);
        
        return maxScoreGap <= 0 ? new int[]{-1} : answer;
    }
    
    private void dfs(int idx, int n, int[] ryan, int[] apeach) {
        if (n == 0 || idx == 11) {
            // 모든 화살을 쐈거나, 마지막 과녁까지 끝났다.
            int gap = calculateGap(ryan, apeach);
            int[] copiedRyan = ryan.clone();
            copiedRyan[10] += n; // 마지막 과녁까지 했는데 화살이 남은 경우 0점에 몰아준다.
            if (maxScoreGap < gap) {
                answer = copiedRyan;
                maxScoreGap = gap;
            } else if (maxScoreGap == gap) {
                compareResult(copiedRyan);
            }
            return;
        }
        
        // 획득 가능하며 해당 점수 획득
        if (n >= apeach[idx] + 1) {
            ryan[idx] = apeach[idx] + 1;
            dfs(idx + 1, n - ryan[idx], ryan, apeach);
            ryan[idx] = 0; 
        }
        // 해당 점수 패스
        dfs(idx + 1, n, ryan, apeach); 
    }
    
    private int calculateGap(int[] ryan, int[] apeach) {
        int ryanScore = 0;
        int apeachScore = 0;
        for (int i = 0; i < 11; i++) {
            if (ryan[i] == 0 && apeach[i] == 0) {
                continue;
            }
            
            if (ryan[i] > apeach[i]) {
                ryanScore += 10 - i;
            } else {
                apeachScore += 10 - i;
            }
        }
        return ryanScore - apeachScore;
    }
    
    private void compareResult(int[] ryan) {
        // answer와 ryan을 비교해서 낮은 점수가 더 많은 경우
        for (int i = 10; i >= 0; i--) {
            if (ryan[i] > answer[i]) {
                answer = ryan.clone();
                break;
            } else if (ryan[i] < answer[i]) {
                break;
            }
        }
    }
    
}

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

Programmers 64065 - 튜플  (3) 2024.10.23
Programmers 60062 - 외벽점검  (0) 2024.10.20
Programmers 12952 - N-Queen  (0) 2024.10.01
Programmers 87946 - 피로도  (0) 2024.10.01
Programmers 86971 - 전력망을 둘로 나누기  (1) 2024.09.30

+ Recent posts