Problem


주어진 이진 탐색 트리에서 k 번째로 큰 수를 구하는 문제입니다.



Solution

이진 탐색 트리기 때문에 중위 순회 (Inorder) 를 사용하면 답을 구할 수 있습니다.

순서대로 순회하면서 k 번째 값을 저장하면 됩니다.

Iterative 로 구하려면 Stack 을 사용하면 됩니다.



Java Code

class Solution {
    int rank = 1;
    int res = -1;

    public int kthSmallest(TreeNode root, int k) {
        traverse(root, k);
        return res;
    }

    public void traverse(TreeNode node, int k) {
        if (node == null) return;

        traverse(node.left, k);
        if (rank++ == k) res = node.val;
        traverse(node.right, k);
    }
}

Problem


nums 배열에서 target 숫자의 범위를 구하는 문제입니다.

nums 배열은 오름차순으로 정렬되어 있으며 중복된 값도 존재합니다.


Solution

Lower Bound 와 Upper Bound 를 이용해서 답을 구하면 됩니다.

target 이상인 값을 찾으며 중복값이 있을 경우 가장 첫번째 인덱스를 구하는 Lower Bound.

target 초과인 값을 찾으며 중복값이 있을 경우 가장 첫번째 인덱스를 구하는 Upper Bound.

두 개를 이용하여 각각 start, end 인덱스를 구할 수 있습니다.

start 는 전체 범위를 대상으로 이진탐색을 하고 end 는 시작점인 start 부터 끝까지의 범위를 대상으로 탐색을 하면 됩니다.

시간복잡도는 O(log n) 입니다.



Java Code

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int start = findStartIndex(nums, target, 0, nums.length - 1);
        int end = findLastIndex(nums, target, start, nums.length - 1) - 1;

        if (start >= nums.length || start > end) {
            return new int[] {-1, -1};
        }

        return new int[] {start, end};
    }

    // lowerBound
    public int findStartIndex(int[] nums, int target, int lo, int hi) {
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (target <= nums[mid]) hi = mid - 1;
            else lo = mid + 1;
        }

        return lo;
    }

    // upperBound
    public int findLastIndex(int[] nums, int target, int lo, int hi) {
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (target < nums[mid]) hi = mid - 1;
            else lo = mid + 1;
        }

        return lo;
    }
}

Problem


문자열 s 에서 p 의 아나그램이 시작되는 모든 인덱스를 찾는 문제입니다.



Solution

Slinding Window 를 사용하여 풀 수 있습니다.

Two Pointer 와 비슷한 개념인데 s 를 순서대로 순회하면서 p 길이 만큼의 slow, fast 인덱스를 유지합니다.

문자열을 비교할 때 매번 substring 을 찾을 필요 없이 앞의 문자는 빼고 뒤에 문자는 더해주는 방식으로 시간을 절약할 수 있습니다.

아나그램 여부는 Arrays.equals 를 사용하여 문자들의 갯수가 저장된 배열을 비교하면 됩니다.

s 문자열의 길이가 n 이라고 할 때 슬라이딩 윈도우에 O(n) 이 소요되고 Arrays.equals 는 내부적으로 for 문이 사용되기 때문에 O(26) 이 소요됩니다.

총 시간복잡도는 O(n * 26) 입니다.



Java Code

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> res = new ArrayList<>();

        if (s.length() < p.length()) return res;

        int[] sCounts = new int[26];
        int[] pCounts = new int[26];

        for (int i = 0; i < p.length(); i++) {
            sCounts[s.charAt(i) - 'a']++;
            pCounts[p.charAt(i) - 'a']++;
        }

        int slow = 0, fast = p.length();

        while (true) {
            if (Arrays.equals(sCounts, pCounts)) {
                res.add(slow);
            }

            if (fast == s.length()) break;

            sCounts[s.charAt(slow++) - 'a']--;
            sCounts[s.charAt(fast++) - 'a']++;       
        }

        return res;
    }
}

Problem


자연수 배열 nums 는 각 인덱스에서 이동할 수 있는 최대 거리를 의미합니다.

마지막 인덱스에 도달 가능한 최소 이동 횟수를 구하는 문제입니다.



Solution

BFS 로 짰다가 시간초과가 나서 Discuss 참고했습니다.

Greedy 로 O(n) 에 답을 구할 수 있습니다.

간단히 변수 설명을 하자면 count 는 우리가 구하려는 답, 즉 이동한 횟수입니다.

currMax 는 현재 구간 (count) 에서 가장 멀리 점프 할 수 있는 인덱스입니다.

nextMax 는 가장 멀리 점프할 수 있는 인덱스입니다.


핵심은 count 를 구하는 방법인데 i 를 순차적으로 증가시키면서 currMax 와 만나면 count 를 증가시킵니다.

count 를 구간이라고 생각하면 됩니다.

현재 i 부터 currMax 구간에서 최대한 점프할 수 있는 거리를 구해서 nextMax 로 저장합니다.

currMax 에 도달할 때마다 점프 횟수를 한번 사용한 것이므로 count 를 하나 증가시키고 currMax 를 다음 이동가능한 구간인 nextMax 로 변경해줍니다.

문제에서 항상 마지막 인덱스에 도착할 수 있다는 조건이 있기 때문에 nextMax 는 항상 마지막 인덱스로 끝나게 됩니다.



Java Code

class Solution {
    public int jump(int[] nums) {
        int count = 0, currMax = 0, nextMax = 0;

        for (int i = 0; i < nums.length - 1; i++) {
            nextMax = Math.max(nextMax, i + nums[i]);

            if (i == currMax) {
                count++;
                currMax = nextMax;
            }
        }

        return count;
    }
}

Problem


Integer 배열이 주어졌을 때 가장 많이 나타나는 k 개의 요소를 리턴하는 문제입니다.

단, 시간복잡도가 O(n log n) 보다 빨라야 합니다.



Solution

1. Bucket Sort

첫 번째 풀이 방법은 Bucket Sort 를 활용하여 O(n) 에 구하는 방법입니다.

먼저 HashMap (number, count) 을 이용해서 각 숫자의 갯수를 전부 구합니다.

그리고 이번에는 2차원 배열 (코드에서는 List[]) 에 각 카운트 별로 해당되는 숫자를 전부 집어넣습니다.

예를 들면 [1, 1, 1, 1, 2, 2, 3, 4] 가 주어지면 아래와 같이 값이 들어갑니다.

list[0] = null
list[1] = [3, 4] // 3, 4 는 한번만 나옴
list[2] = [2]
list[3] = null
list[4] = [1]

카운트가 없는 숫자의 인덱스는 null 이 되므로 list 를 거꾸로 순회하면서 null 이 아닌 숫자들을 k 개 만큼 집어넣으면 답을 구할 수 있습니다.


2. Priority Queue

두 번째 풀이 방법은 우선순위 큐를 활용하여 O(n log k) 에 구하는 방법입니다.

1번 풀이와 마찬가지로 HashMap 에 각 숫자의 갯수를 전부 구해둡니다.

그 뒤 value 가 작은 순서대로 나오도록 우선순위 큐에 조건을 걸고, map 에 있는 값들을 전부 넣어줍니다.

대신 이 때 단순히 넣어주기만 하면 시간복잡도가 O(log n) 이지만, pq 의 사이즈가 k 를 넘지않도록 유지한다면 우선순위 큐에 삽입하는 작업은 O(log k) 가 됩니다.

어차피 k 개의 숫자 외엔 필요 없으므로 pq 의 사이즈가 k 를 초과할 때마다 가장 작은 값을 하나씩 빼주면서 진행하면 됩니다.



Java Code

// 1. Use Bucket Sort
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }

        List<Integer>[] bucket = new List[nums.length + 1];
        for (Integer key : map.keySet()) {
            int count = map.get(key);

            if (bucket[count] == null) {
                bucket[count] = new ArrayList<>();
            }

            bucket[count].add(key);
        }

        List<Integer> list = new ArrayList<>();
        for (int i = bucket.length - 1; i >= 0 && list.size() < k; i--) {
            if (bucket[i] == null) continue;
            list.addAll(bucket[i]);
        }

        // list to array
        int[] res = new int[k];
        for (int i = 0; i < k; i++) {
            res[i] = list.get(i);
        }

        return res;
    }
}

// 2. Use Priority Queue
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }

        Queue<Integer> pq = new PriorityQueue<>(
            (a, b) -> map.get(b) - map.get(a)
        );

        for (Integer num : map.keySet()) {
            pq.add(num);

            if (pq.size() > k) {
                pq.poll();
            }
        }

        // list to array
        int[] res = new int[k];
        for (int i = 0; i < k; i++) {
            res[i] = pq.poll();
        }

        return res;
    }
}

Problem


배열이 주어졌을 때, 중복되지 않는 모든 Subset, 즉 PowerSet 을 구하는 문제입니다.



Solution

Bit 를 사용해도 되고 재귀로 해도 되고 백트래킹으로 해도 되고 방법은 많습니다.

가장 구현하기 편한 백트래킹으로 했습니다.



Java Code

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        dfs(nums, res, new ArrayList<>(), 0);
        return res;
    }

    private void dfs(int[] nums, List<List<Integer>> res, List<Integer> list, int start) {
        res.add(new ArrayList<>(list));

        for (int i = start; i < nums.length; i++) {
            list.add(nums[i]);
            dfs(nums, res, list, i + 1);
            list.remove(list.size() - 1);
        }
    }
}

Problem


주어진 숫자를 뒤집으면 됩니다.



Solution

평범하게 1 의 자릿수부터 더해가면 됩니다.

한 가지 주의할 점은 res 값이 int 의 범위를 벗어날 수 있기 때문에 long 타입으로 선언 후에 나중에 바꿔주어야 합니다.



Java Code

class Solution {
    public int reverse(int x) {
        long res = 0;

        while (x != 0) {
            res *= 10;
            res += x % 10;
            x /= 10;
        }

        if (-Integer.MAX_VALUE <= res && res <= Integer.MAX_VALUE) {
            return (int) res;
        } else {
            return 0;
        }
    }
}

Problem


주어지는 숫자보다 작은 모든 소수의 개수를 구하는 문제입니다.



Solution

에라토스테네스의 체를 사용하여 소수를 미리 구해두면 O(n) 시간복잡도로 구할 수 있습니다.

2의 곱셉, 3의 곱셈, 4의 곱셉.. 전부 isNotPrime 으로 체크한 뒤에

false 인 숫자들만 카운팅하면 됩니다.


+) 2020. 01. 03

Discuss 보고 개선한 코드를 추가했습니다.

count = n / 2 로 시작하는 이유는 2 를 제외한 짝수는 소수가 될 수 없기 때문입니다.

2 의 배수는 어차피 셀 필요가 없기 때문에 처음부터 제외하고 3 부터 홀수들의 배수만 체크해서 소수가 아닌 값들을 제외합니다.



Java Code

class Solution {
    public int countPrimes(int n) {
        boolean[] isNotPrime = new boolean[n];
        int count = 0;

        for (int i = 2; i * i < n; i++) {
            if (isNotPrime[i]) continue;

            for (int j = 2; i * j < n; j++) {
                isNotPrime[i * j] = true;
            }
        }

        for (int i = 2; i < n; i++) {
            if (!isNotPrime[i]) {
                count++;
            }
        }

        return count;
    }
}

// Discuss 참고 개선
class Solution {
    public int countPrimes(int n) {
        if (n < 3) return 0;

        boolean[] isNotPrime = new boolean[n];
        int count = n / 2;

        for (int i = 3; i * i < n; i += 2) {
            for (int j = i * i; j < n; j += i * 2) {
                if (isNotPrime[j]) continue;
                count--;
                isNotPrime[j] = true;
            }
        }

        return count;
    }
}

+ Recent posts