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

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

 

프로그래머스

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

programmers.co.kr

 

n개의 퀸이 서로를 공격할 수 없도록 배치하는 방법.

퀸을 순열로 배치하면 된다. 그런데 퀸을 배치할 때 고려해야 하는 요소는 무엇일까?

그건 바로 다른 퀸들의 위치이다. 퀸을 놓을 때 다른 퀸들을 공격할 수 있는 위치라면 놓아서는 안된다.

그렇다면 관리해야 할 것은? 이전에 놓았던 퀸들의 위치를 관리해야 한다. 어차피 같은 row에는 놓을 수 없으므로 퀸들의 위치는 1차원 배열로 col만 관리하면 될 것 같다.

  1. row를 0~n까지 순회하면서 퀸을 놓을 col을 결정한다.
  2. 이때 이전 row에 위치한 퀸들과의 관계를 고려해 퀸을 놓는다.
class Solution {
    
    static int[] cols;
    static int answer = 0;
    
    public int solution(int n) {
        cols = new int[n];
        dfs(0, n);
        return answer;
    }
    
    private void dfs(int row, int n) {
        if (row == n) {
            answer++;
            return;
        }
        
        for (int col = 0; col < n; col++) {
            if (isPossible(row, col)) {
                cols[row] = col;
                dfs(row + 1, n);
                cols[row] = 0;
            }
        }
    }
    
    private boolean isPossible(int row, int col) {
        // row, col = 현재 놓을 위치
        for (int prevRow = 0; prevRow < row; prevRow++) {
            // 이전 row에 놓았던 것들
            int prevCol = cols[prevRow];
            
            if (col == prevCol) return false;
            if (row + col == prevRow + prevCol) return false;
            if (row - col == prevRow - prevCol) return false;
        }
        return true;
    }
}

주의해야 할 점은 직전 row에 대해서만 검사하는 것이 아니라 이전 모든 row들에 대해 검사를 진행해야 한다는 점이다.

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

 

프로그래머스

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

programmers.co.kr

 

최소 필요 피로도 = 해당 던전을 탐험하기 위해 가지고 있어야 하는 최소한의 피로도(입장)

소모 피로도 = 던전을 탐험한 후 소모되는 피로도

현재 피로도 k 일 때 탐험할 수 있는 최대 던전 수

예시를 살펴보자.

[[80,20], [50,40], [30,10]] 일 때 탐험 순서에 따라 결과가 달라진다는 것을 알 수 있다.

가장 단순한 방법으로는 모든 탐험 순서의 결과를 찾아보는 것이다. 하지만 이는 비효율적인 방법이다.

무작정 모든 탐험 순서를 보기보다는 가능성이 있는 탐험만 보고 가능성이 없다면 빠져나오는 방식을 사용하겠다. 즉 백트랙킹이다.

여기서 가능성을 어떻게 판단할까?

피로도를 기준으로 판단할 수 있다. 현재 피로도가 다음 던전 탐색 최소 필요 피로도보다 클 때만 탐험을 나아가면 된다.

class Solution {
    
    static int answer = -1;
    
    public int solution(int k, int[][] dungeons) {
        dfs(0, k, dungeons, new boolean[dungeons.length]);
        return answer;
    }
    
    private void dfs(int cnt, int k, int[][] dungeons, boolean[] visited) { 
        for (int i = 0; i < dungeons.length; i++) {
            if (!visited[i] && k >= dungeons[i][0]) {
                visited[i] = true;
                dfs(cnt + 1, k - dungeons[i][1], dungeons, visited);
                answer = Math.max(answer, cnt + 1);
                visited[i] = false;
            }
        }
    }
}

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

Programmers 92342 - 양궁대회  (0) 2024.10.02
Programmers 12952 - N-Queen  (0) 2024.10.01
Programmers 86971 - 전력망을 둘로 나누기  (1) 2024.09.30
Programmers 67259 - 경주로 건설  (0) 2024.09.30
Programmers 12978 - 배달  (0) 2024.09.29

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

 

프로그래머스

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

programmers.co.kr

 

n개의 송전탑이 트리 형태로 연결되어 있다. 여기서 하나의 전선을 끊어서 네트워크를 2개로 분할하려고 한다. 이때 두 전력망이 갖게 되는 송전탑의 개수를 최대한 비슷하게 맞춰야 한다.

두 전력망이 가지고 있는 송전탑 개수의 차이(절댓값)를 return하라.

 

트리 형태이기 때문에 모든 노드가 연결이 되어 있다.

브루트포스를 통해 간선을 하나 제거하고 dfs로 탐색을 하면 하나의 그룹의 송전탑의 개수를 구할 수 있고 n개에서 그 개수를 빼면 나머지 송전탑 그룹의 개수가 결정된다.

이 방법을 지워갈 간선을 하나씩 전부 선택해 가며 반복하면서 차이의 최소를 갱신해 가면 답을 구할 수 있을 것이다.

class Solution {
    
    private int[][] graph;
    
    public int solution(int n, int[][] wires) {
        int answer = Integer.MAX_VALUE;
        initGraph(n, wires);
        for (int[] wire : wires) {
            // 이번 차례 제외할 wire
            disconnectWire(wire);
            int cnt = dfs(1, new boolean[n + 1]);
            answer = Math.min(answer, Math.abs(n - 2 * cnt));
            // 다시 연결
            connectWire(wire);
        }
        return answer;
    }
    
    private int dfs(int v, boolean[] visited) {
        visited[v] = true;
        
        int cnt = 1;
        for (int u = 0; u < visited.length; u++) {
            if (graph[v][u] == 1 && !visited[u]) {
                cnt += dfs(u, visited);
            }
        }
        
        return cnt;
    }
    
    private void initGraph(int n, int[][] wires) {
        graph = new int[n + 1][n + 1];
        
        for (int[] wire : wires) {
            connectWire(wire);
        }
    }
    
    private void disconnectWire(int[] wire) {
        int v = wire[0];
        int u = wire[1];
        graph[v][u] = 0;
        graph[u][v] = 0;
    }
    
    private void connectWire(int[] wire) {
        int v = wire[0];
        int u = wire[1];
        graph[v][u] = 1;
        graph[u][v] = 1;
    }
}

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

Programmers 12952 - N-Queen  (0) 2024.10.01
Programmers 87946 - 피로도  (0) 2024.10.01
Programmers 67259 - 경주로 건설  (0) 2024.09.30
Programmers 12978 - 배달  (0) 2024.09.29
Programmers 159993 - 미로 탈출  (0) 2024.09.29

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

 

프로그래머스

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

programmers.co.kr

 

N x N 크기의 격자 형태의 board가 존재하고 각 격자의 칸은 0 또는 1로 채워져 있으며, 0은 칸이 비어있음을 1은 해당 칸이 벽으로 채워져 있음을 나타낸다.

시작점은 (0,0) 칸(좌측 상단), 도착점은 (N-1, N-1)(우측 하단)이다.

직선 도로와 코너가 존재한다. 직선 도로는 상하 또는 좌우로 연결한 경주로를 의미하고 두 직선 도로가 직각으로 만다는 지점을 코너라고 한다.

직선 도로 하나를 만들 때는 100원이 소요되며, 코너를 하나 만들 때는 500원이 추가로 든다.

경주로를 건설하는데 들어가는 최소 비용은?

 

가중치가 존재하는 최단 거리 문제이다. 최소 비용을 구하기 위해서는 추가 금액이 들어가는 코너를 최소한으로 써야지 가능할 것 같다.

코너의 판정은 어떻게 해야 할까? 코너는 결국 이동 방향이 변경됐다는 것이다. 따라서 직전 이동 방향과 이번 이동 뱡향을 알면 코너인지를 판단할 수 있다. 즉 이전과 방향이 다르면 코너다.

좌우의 경우도 방향이 다르다. 하지만 이 경우는 코너가 아니다. 그런데 어차피 이 경우는 생각하지 않아도 된다. 최단거리를 찾는데 돌아가는 경우는 없다.

풀이를 생각해보자. 우선 가중치가 있는 최단거리를 구해야 하는 문제이다. 다익스트라로 풀 수 있을까?

다익스트라의 PriorityQueue에 넣을 때 방향도 함께 넣어서 방향별로 다른 경로로 판정하면 가능할 것 같다.

import java.util.*;

class Solution {
    
    static class Node {
        int r;
        int c;
        int dir;
        int cost;
        
        public Node(int r, int c, int dir, int cost) {
            this.r = r;
            this.c = c;
            this.dir = dir;
            this.cost = cost;
        }
    }
    
    static final int INF = Integer.MAX_VALUE;
    static int[] dr = {-1, 1, 0, 0}; //상하좌우
    static int[] dc = {0, 0, -1, 1};
    
    static int[][] distances;
    
    public int solution(int[][] board) {
        int N = board.length;
        distances = new int[N][N];
        for (int[] row : distances) {
            Arrays.fill(row, INF);
        }
        
        distances[0][0] = 0;
        PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(n -> n.cost));
        pq.add(new Node(0, 0, 1, 0));
        pq.add(new Node(0, 0, 3, 0));
        
        while(!pq.isEmpty()) {
            Node curr = pq.poll();
            int r = curr.r;
            int c = curr.c;
            int dir = curr.dir;
            int cost = curr.cost;
            
            if (distances[r][c] < cost) continue;
            
            for (int d = 0; d < 4; d++) {
                int weight = 100;
                int nr = r + dr[d];
                int nc = c + dc[d];
                if (nr < 0 || nr >= N || nc < 0 || nc >= N) continue;
                if (board[nr][nc] == 1) continue;
                if (dir != d) {
                    weight += 500;
                }
                
                if (distances[nr][nc] > distances[r][c] + weight) {
                    distances[nr][nc] = distances[r][c] + weight;
                    pq.add(new Node(nr, nc, d, distances[nr][nc]));
                }
            }
        }
        
        int answer = distances[N-1][N-1];
        return answer;
    }
}

이렇게 풀었는데 오답이 됐다. 왜 틀렸는지 분석해 보자.

가장 큰 문제는 int [][] distances 배열이다.

나는 처음에는 직선으로 도착하거나 코너로 도착하거나 둘 중 하나이니까 그 두 경우를 전부 pq에 넣으면 어차피 저렴한 걸로 갱신되지 않을까 생각했다. 그런데 이렇게 풀게 되면 그 지점까지의 최솟값이 최종적인 최솟값이라고 보장이 돼야 한다. 하지만 이 문제는 그렇지 않다.

이렇게 관리하게 되면 같은 위치라도 다른 방향으로 도착했을 때의 비용 차이를 반영하지 못한다.

결과적으로, 특정 위치에 더 높은 비용으로 도착했지만, 방향이 달라서 이후 경로에서 더 낮은 총비용을 가질 수 있는 경우를 놓치게 되는 것이다.

이를 해결하기 위해 int [][][] distances로 3차원으로 방향까지 관리하도록 하겠다.

import java.util.*;

class Solution {
    
    static class Node {
        int r;
        int c;
        int dir;
        int cost;
        
        public Node(int r, int c, int dir, int cost) {
            this.r = r;
            this.c = c;
            this.dir = dir;
            this.cost = cost;
        }
    }
    
    static final int INF = Integer.MAX_VALUE;
    static int[] dr = {-1, 1, 0, 0}; //상하좌우
    static int[] dc = {0, 0, -1, 1};
    
    static int[][][] distances;
    
    public int solution(int[][] board) {
        int N = board.length;
        distances = new int[4][N][N];
        for (int d = 0; d < 4; d++) {
            for (int[] row : distances[d]) {
                Arrays.fill(row, INF);
            }
        }
        
        PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(n -> n.cost));
        for (int d = 0; d < 4; d++) {
            distances[d][0][0] = 0;
            pq.add(new Node(0, 0, d, 0));
        }
        
        while(!pq.isEmpty()) {
            Node curr = pq.poll();
            int r = curr.r;
            int c = curr.c;
            int dir = curr.dir;
            int cost = curr.cost;
            
            if (distances[dir][r][c] < cost) continue;
            
            for (int d = 0; d < 4; d++) {
                int nr = r + dr[d];
                int nc = c + dc[d];
                int weight = 100;
                if (nr < 0 || nr >= N || nc < 0 || nc >= N) continue;
                if (board[nr][nc] == 1) continue;
                
                if (dir != d) {
                    weight += 500;
                } 
                
                if (distances[d][nr][nc] > distances[dir][r][c] + weight) {
                    distances[d][nr][nc] = distances[dir][r][c] + weight;
                    pq.add(new Node(nr, nc, d, distances[d][nr][nc]));
                }
            }
        }
        
        int answer = INF;
        for (int d = 0; d < 4; d++) {
            answer = Math.min(answer, distances[d][N-1][N-1]);
        }
        return answer;
    }
}

이를 통해 문제를 해결할 수 있었다.

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

Programmers 87946 - 피로도  (0) 2024.10.01
Programmers 86971 - 전력망을 둘로 나누기  (1) 2024.09.30
Programmers 12978 - 배달  (0) 2024.09.29
Programmers 159993 - 미로 탈출  (0) 2024.09.29
Programmers 43162 - 네트워크  (0) 2024.09.29

https://leetcode.com/problems/candy/

 

n 명의 줄 서있는 아이들.

각각의 아이들에는 rating이 할당 돼 있다. 이런 아이들에게 다음 조건에 따라 사탕을 줄 것이다.

  • 각각 아이들은 최소한 하나의 사탕을 받아야만 합니다.
  • rating이 높은 아이는 인접한 아이보다 더 많은 사탕을 받습니다.

아이들에게 나눠줘야 하는 사탕의 최소 개수를 구하여라.

어차피 1개씩은 다 받아야 하니까 모든 아이에게 1개씩을 나눠주고 보자.

i 기준에서 i-1과 i+1의 값을 비교해야 한다. 양 옆의 값 중 자신보다 작은 값이 존재한다면 사탕을 양 옆보다 더 줘야 한다.

예시를 보겠다.

[1, 0, 2] 일 때 우선 가운데 1번 idx의 아이는 사탕을 1개만 받아도 된다.

0번 idx는 1번 idx보다 rating이 크므로 2개를 받아야 한다.

2번 idx는 1번 idx보다 rating이 크므로 2개를 받아야 한다.

양쪽 방향에서 중앙으로 접근해 가능 방식으로 푸는 게 좋을 것 같다.

  • 왼쪽에서 오른쪽 방향으로 훑으면서 오른쪽 이웃과 비교
  • 오른쪽에서 왼쪽 방향으로 훑으면서 왼쪽 이웃과 비교

그런데 이 두 결과를 어떻게 합쳐야 할까?

class Solution {
    public int candy(int[] ratings) {
        int n = ratings.length;
        int[] candies = new int[n];
        Arrays.fill(candies, 1);

        // left to right;
        for (int i = 1; i < n; i++) {
            if (ratings[i] > ratings[i - 1]) {
                candies[i] = candies[i - 1] + 1;
            }
        }

        // right to left;
        for (int i = n - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1]) {
                // 이미 더 많다면 추가할 필요가 없다.
                if (candies[i] > candies[i + 1]) continue;
                candies[i] = candies[i + 1] + 1;
            }
        }

        return Arrays.stream(candies).sum();
    }
}

우선 왼쪽 → 오른쪽 방향으로 진행하면서 사탕을 분배한다.

그 후 오른쪽 → 왼쪽 방향으로 진행하면서 사탕을 조건에 따라 분배해야 하는데 만약 rating이 오른쪽보다 높은데도 이미 주어진 사탕이 오른쪽 사탕보다 많다면 더 이상 추가해 줄 필요가 없다.

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

LeetCode 13 - Roman to Integer  (1) 2024.10.03
LeetCode 42 - Trapping Rain Water  (0) 2024.10.02
LeetCode 134 - Gas Station  (1) 2024.09.29
LeetCode 238 - Product of Array Except Self  (0) 2024.09.29
LeetCode 380 - Insert Delete GetRandom O(1)  (0) 2024.09.29

https://leetcode.com/problems/gas-station

 

n 개의 gas station 원형 루트. gas [i] = gas의 양

i → i + 1 까지 가는데 cost [i] 만큼의 gas 가 필요하다.

어느 지점에서 시작해야 시계방향으로 완주할 수 있을까. 만약 완주가 불가능하다면 -1을 반환한다.

즉 현재 가지고 있는 gas가 cost보다 커야만 이동이 가능하다.

순회하는 것을 표현하는 방법은 몇가지 있겠지만 대표적으로 모듈러 연산을 사용하거나 같은 배열일 이어 붙여서 사이즈를 늘리는 방법이 떠오른다.

가장 간단한 풀이는 모든 점을 시작점으로 삼아 진행해 보는 것이다.

하지만 이 방법은 O(n^2)의 시간복잡도를 갖게 되고 문제 조건에서 n은 10^5이다. 도저히 써먹을 수 없다.

생각해 보자.

만약 총 gas가 총 cost보다 작다면 이거는 어느 지점에서 시작해도 정답을 구할 수 없다.

gas - cost를 순이익이라고 했을 때 누적 순이익 < 0 이 되는 지점이 있다면 그 지점 이전은 시작점이 될 수 없다. 왜냐하면 해당 지점에서 기름이 바닥이 날 것이기 때문이다.

따라서 그다음 지점부터 다시 시작해야 한다.

특히 문제 조건을 다시 유심히 살펴보자. 해결책이 있다면 유일하다는 조건이 있다. 그 다음 지점부터 다시 시작하면 답을 구할 수 있다는 것이다. 어차피 그 이전 지점들은 이미 답에서 탈락했으니까!

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int totalGas = 0;
        int totalCost = 0;
        int currentGas = 0;
        int start = 0;

        for (int i = 0; i < gas.length; i++) {
            totalGas += gas[i];
            totalCost += cost[i];
            currentGas += gas[i] - cost[i]; // 누적 순이익

            if (currentGas < 0) {
                // 출발점 탈락. 그 이후 점부터 가능
                start = i + 1;
                currentGas = 0;
            }
        }

        return totalGas >= totalCost ? start : -1;
    }
}

최종적으로 O(n)의 시간복잡도를 갖는 풀이가 완성된다.

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

LeetCode 42 - Trapping Rain Water  (0) 2024.10.02
LeetCode 150 - Candy  (1) 2024.09.30
LeetCode 238 - Product of Array Except Self  (0) 2024.09.29
LeetCode 380 - Insert Delete GetRandom O(1)  (0) 2024.09.29
LeetCode 274 - H-Index  (0) 2024.09.29

https://leetcode.com/problems/product-of-array-except-self

 

int [ ] answer;

answer [i] = nums에서 i 위치를 제외한 모든 원소의 곱

O(n)의 시간복잡도와 나눗셈 연산 없이 해결해야 한다.

가장 쉬운 방법은 모든 원소의 곱을 구하고 순회하며 나눗셈 연산을 하는 것이다.

하지만 나눗셈은 사용하면 안 되는 조건이 있다.

그렇다면 자신을 제외한 왼쪽곱, 오른쪽곱의 배열을 만들어 두 배열의 값을 곱해서 반환하면 된다.

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int[] left = new int[nums.length];
        Arrays.fill(left, 1);
        int p = 1;
        for (int i = 1; i < nums.length; i++) {
            left[i] = p * nums[i - 1]; 
            p *= nums[i - 1];
        }
        
        p = 1;
        int[] right = new int[nums.length];
        Arrays.fill(right, 1);
        for (int i = nums.length - 2; i >= 0; i--) {
            right[i] = p * nums[i + 1];
            p *= nums[i + 1];
        }

        int[] answer = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            answer[i] = left[i] * right[i];
        }

        return answer;
    }
}

그런데 위의 풀이를 보면 left, right, answer라는 추가 공간이 많이 사용됐다.

생각해 보면 그냥 answer 배열 하나에 다 처리를 해도 될 것 같다.

또한 배열을 1로 채워주고 있는데 시작 지점만 채워주면 된다.

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] answer = new int[n];

        answer[0] = 1;
        for (int i = 1; i < n; i++) {
            answer[i] = answer[i - 1] * nums[i - 1];
        }

        int right = 1;
        for (int i = n - 1; i >= 0; i--) {
            answer[i] *= right;
            right *= nums[i];
        }

        return answer;
    }
}

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

LeetCode 150 - Candy  (1) 2024.09.30
LeetCode 134 - Gas Station  (1) 2024.09.29
LeetCode 380 - Insert Delete GetRandom O(1)  (0) 2024.09.29
LeetCode 274 - H-Index  (0) 2024.09.29
LeetCode 45 - Jump Game2  (0) 2024.09.29

+ Recent posts