(13)

[알고리즘] LCS (Longest Common Subsequence, Longest Common Substring)

LCSLongest Common Subsequence는 최장 공통 부분 문자열로, Substring의 값을 구하는 것이 아니라연속되지 않은 부분 문자열 중 가장 긴 공통 문자열을 찾는 알고리즘이다. 반대로, Longest Common Substring은 비슷하지만 부분 문자열이 아닌, substring이 되는 문자열이다. 예를들어 ABCDEF, BCDFQQ라는 문자열이 주어지면Longest Common Subsequence는 BCDF가 되고Longest Common Substring은 BCD가 된다.  LCS의 길이를 구할 때는 DP(Dynamic Programming)를 통해 메모제이션으로 효율적인 문제 해결이 가능하다.   점화식char[] w1 = word1.toCharArray();char[] w..

[백준 1504번 / Java] 특정한 최단 경로 - 다익스트라(Dijkstra)

문제 링크 Gold 4 https://www.acmicpc.net/problem/1504 1504번: 특정한 최단 경로 첫째 줄에 정점의 개수 N과 간선의 개수 E가 주어진다. (2 ≤ N ≤ 800, 0 ≤ E ≤ 200,000) 둘째 줄부터 E개의 줄에 걸쳐서 세 개의 정수 a, b, c가 주어지는데, a번 정점에서 b번 정점까지 양방향 길이 존 www.acmicpc.net https://mag1c.tistory.com/452 다익스트라(Dijkstra) 알고리즘 다익스트라(Dijkstra) 알고리즘 그래프의 최단 경로를 구하는 알고리즘으로 하나의 정점에서 출발하여 최단 거리를 구하는 알고리즘이다. 탐욕법(Greedy)과 동적 계획법을 사용하는 알고리즘으로, mag1c.tistory.com 풀이 방향..

[알고리즘] 다익스트라(Dijkstra) 알고리즘

다익스트라(Dijkstra) 알고리즘그래프의 최단 경로를 구하는 알고리즘으로 하나의 정점에서 출발하여 최단 거리를 구하는 알고리즘이다. 탐욕법(Greedy)과 동적 계획법을 사용하는 알고리즘으로, 음의 가중치(음의 값)가 없어야한다.음의 가중치가 존재하게 되면, 최소 비용의 음의 무한대값이 생성될 수 있으며 그리디 알고리즘을 적용할 수 없다. 인접 행렬로 표현된 그래프의 경우 O(n^2)의 복잡도를 가지며, 우선순위 큐를 사용하여 시간복잡도를 O(m logn)까지 낮출 수 있다.  매커니즘(1) 방문하지 않은 노드 중 가장 비용이 적은 노드를 선택한다 (그리디)(2) 해당 노드로부터 갈 수 있는 노드들의 비용을 갱신한다 (DP)  예시https://mag1c.tistory.com/451 [백준 1916번..

[백준 1916번 / Java] 최소비용 구하기 / 다익스트라(Dijkstra)

문제 링크 Gold 5 https://www.acmicpc.net/problem/1916 1916번: 최소비용 구하기 첫째 줄에 도시의 개수 N(1 ≤ N ≤ 1,000)이 주어지고 둘째 줄에는 버스의 개수 M(1 ≤ M ≤ 100,000)이 주어진다. 그리고 셋째 줄부터 M+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그 www.acmicpc.net 첫 풀이 단순 BFS로 구해보려했던 것 같다. import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; public class Main { private static int N; private st..

[백준 9465번 / Java] 스티커 - DP

문제 링크 Silver 1 https://www.acmicpc.net/problem/9465 9465번: 스티커 첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스의 첫째 줄에는 n (1 ≤ n ≤ 100,000)이 주어진다. 다음 두 줄에는 n개의 정수가 주어지며, 각 정수는 그 위치에 해당하는 스티커의 www.acmicpc.net 풀이 문제의 조건은 선택한 스티커의 상하좌우 스티커는 선택할 수 없다. 라고 하였다. 아래 문제 예시를 보자. 50 10 100 20 40 30 50 70 10 60 위의 예시에서, dp를 사용하여 최대값을 어떻게 구할 수 있을까 왼쪽부터 시작해서, 하나씩 선택해보았다. 50 10 30 50 50 10 30 50 첫 번째 idx의 값을 선택할 수 있다. 두 번..

[백준 1149번 / Java] RGB거리 - DP

문제 링크 Silver 1 https://www.acmicpc.net/problem/1149 1149번: RGB거리 첫째 줄에 집의 수 N(2 ≤ N ≤ 1,000)이 주어진다. 둘째 줄부터 N개의 줄에는 각 집을 빨강, 초록, 파랑으로 칠하는 비용이 1번 집부터 한 줄에 하나씩 주어진다. 집을 칠하는 비용은 1,000보다 작거나 www.acmicpc.net 풀이 문제에서 N은 N-1과 N+1과 색상을 중복해서는 안된다. 라고 하는 부분에서 조금 헷갈렸는데 결론은 크게 신경쓰지 않아도 된다. N일 때 N-1에서 중복만 체크한다면 어떤 상황에서도 중복이 될 일은 없다. for (int i = 0; i < N; i ++) { st = new StringTokenizer(br.readLine(), " "); ..

[백준 17626번 / Java] Four Squares - DP

문제 링크 Silver 3 https://www.acmicpc.net/problem/17626 17626번: Four Squares 라그랑주는 1770년에 모든 자연수는 넷 혹은 그 이하의 제곱수의 합으로 표현할 수 있다고 증명하였다. 어떤 자연수는 복수의 방법으로 표현된다. 예를 들면, 26은 52과 12의 합이다; 또한 42 + 32 + 1 www.acmicpc.net 풀이 문제 티어는 낮지만 DP문제라서 냉큼 공부 처음에는 아래처럼 해당 idx의 제곱값을 구하여 greedy처럼 풀어내려고 했으나 int[] arr = new int[(int) Math.sqrt(n) + 1]; for (int i = 0; i < arr.length; i ++) { arr[i] = i * i; } 모든 상황에 만족하지 ..

[백준 1463번 / Java] 1로 만들기 - DP

문제 링크 https://www.acmicpc.net/problem/1463 1463번: 1로 만들기 첫째 줄에 1보다 크거나 같고, 106보다 작거나 같은 정수 N이 주어진다. www.acmicpc.net 풀이 자기 전에 문제 하나 풀고 자야지~~ 하면서 풀었던 문제 실버 3 문제라 DP로의 접근은 쉬웠으나, 뜻하지 않게 ArrayIndexOutOfBounds Exception으로 고생했음.. 반 졸린 상태에서 풀어서~~ 라고 핑계를 대보지만. 실수를 줄이기 위해 항상 신경써야겠다고 다짐한 문제로. 상기시키기 위해 포스팅.... import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; publi..

[Java] 2466. Count Ways To Build Good Strings - LeetCode Daily Challenge / Dynamic Programing(DP)

Count Ways To Build Good Strings - LeetCode Can you solve this real interview question? Count Ways To Build Good Strings - Given the integers zero, one, low, and high, we can construct a string by starting with an empty string, and then at each step perform either of the following: * Append the char leetcode.com 풀이 DP배열을 생성해, Bottom Up방식으로 풀이. dp[0]=1이 왜 1인가에 대해 고민을 좀 많이 했던 문제였다. 문자열의 길이가 0인게 없지..

[알고리즘] LCS (Longest Common Subsequence, Longest Common Substring)

Tech/알고리즘 2024. 5. 26. 13:06
728x90
728x90

 

 

LCS

Longest Common Subsequence는 최장 공통 부분 문자열로, Substring의 값을 구하는 것이 아니라

연속되지 않은 부분 문자열 중 가장 긴 공통 문자열을 찾는 알고리즘이다.

 

반대로, Longest Common Substring은 비슷하지만 부분 문자열이 아닌, substring이 되는 문자열이다.

 


예를들어 ABCDEF, BCDFQQ라는 문자열이 주어지면
Longest Common Subsequence는 BCDF가 되고
Longest Common Substring은 BCD가 된다.

 

 

LCS의 길이를 구할 때DP(Dynamic Programming)를 통해 메모제이션으로 효율적인 문제 해결이 가능하다.

 

 

 

점화식

char[] w1 = word1.toCharArray();
char[] w2 = word2.toCharArray();

 

 

위와 같이, 문자열을 배열로 변환했다고 가정하고, 아래와 같이 점화식을 작성할 수 있다.

Substring과 Subsequence의 차이는 공통 문자열이 이어지느냐 이어지지 않느냐의 차이이다.

//Longest Common Substring
if (w1[i - 1] == w2[j - 1]) {
    dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
    //공통 문자열이 끝났기 때문에 초기화
    dp[i][j] = 0;
}

//Longest Common Subsequence
if (w1[i - 1] == w2[j - 1]) {
    dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}

 

 

 

점화식의 이해를 돕기위해 각각의 상황을 위의 예시인 ABCDEF / BCDEFQQ를 가지고 그림으로 그려보았다.

 

 

Longest Common Substring

 

 

 

 

 

 

 

Longest Common Subsquence

부분 문자열에서는, substring과 다르게 계속해서 이전 메모된 값들 중 Max값을 가져가야한다.

(연속된 문자열이 아니기 때문)

 

 

 

 

 

 

 

 

 

예제 - 백준 9251번: LCS

https://www.acmicpc.net/problem/9251

 

 

위에서 설명했던 것 처럼 점화식은 아래와 같다.

if (w1[i - 1] == w2[j - 1]) {
    dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}

 

 

단순 최대 길이를 출력하는 것이기 때문에 dp배열의 가장 끝 idx에 최대길이가 저장되게 된다.

 

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String word1 = br.readLine();
        String word2 = br.readLine();
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];

        char[] w1 = word1.toCharArray();
        char[] w2 = word2.toCharArray();
        for (int i = 1; i <= word1.length(); i ++) {
            for (int j = 1; j <= word2.length(); j ++) {
                if (w1[i - 1] == w2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }

        System.out.println(dp[word1.length()][word2.length()]);
    }

}

 

 

 

 

 

 

관련 문제

https://leetcode.com/problems/longest-common-subsequence/description/

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1504번 / Java] 특정한 최단 경로 - 다익스트라(Dijkstra)

P.S./백준 2023. 8. 14. 06:21
728x90
728x90

문제 링크

Gold 4

 

https://www.acmicpc.net/problem/1504

 

1504번: 특정한 최단 경로

첫째 줄에 정점의 개수 N과 간선의 개수 E가 주어진다. (2 ≤ N ≤ 800, 0 ≤ E ≤ 200,000) 둘째 줄부터 E개의 줄에 걸쳐서 세 개의 정수 a, b, c가 주어지는데, a번 정점에서 b번 정점까지 양방향 길이 존

www.acmicpc.net

 

https://mag1c.tistory.com/452

 

다익스트라(Dijkstra) 알고리즘

다익스트라(Dijkstra) 알고리즘 그래프의 최단 경로를 구하는 알고리즘으로 하나의 정점에서 출발하여 최단 거리를 구하는 알고리즘이다. 탐욕법(Greedy)과 동적 계획법을 사용하는 알고리즘으로,

mag1c.tistory.com

 

 

풀이

방향성이 없는 그래프가 주어졌으며, 이는 양방향으로 구현해야 함

또한 주어진 두 가지의 경로 v1, v2를 모두 통과한 후 최종 지점으로 도착할 수 있는 경로를 구해야한다는 점이다.

 

1번에서 출발해서 v1, v2를 거쳐 최종 목적지로 도착하는 방법은 두 가지가 있으며 이를 쪼개면 다음과 같이 표현할 수 있다.

1 → v1 → v2 → goal
// 1 → v1
// v1 → v2
// v2 → goal

1 → v2 → v1 → goal
// 1 → v2
// v2 → v1
// v1 → goal

 

위에서 종합한 조건들을 바탕으로 기본 다익스트라 알고리즘을 구현한 코드 위에 아래 세 가지만 고려하여 작성하면 된다.

 

1. 양방향으로 구현하기 때문에 반대의 경우도 노드 리스트에 추가

for(int i = 0; i < E; i ++) {
    st = new StringTokenizer(br.readLine(), " ");
    int before = Integer.parseInt(st.nextToken());
    int after = Integer.parseInt(st.nextToken());
    int weight = Integer.parseInt(st.nextToken());

    //단방향
    nodeList.get(before).add(new Node(after, weight));
    
    //양방향
    nodeList.get(after).add(new Node(before, weight));
}

 

2. 파라미터로 각 조건을 분할해서 수행하면 코드의 재사용성을 극대화할 수 있음

int distV1 = 0;
distV1 += dist(1, v1);
distV1 += dist(v1, v2);
distV1 += dist(v2, N);

int distV2 = 0;
distV2 += dist(1, v2);
distV2 += dist(v2, v1);
distV2 += dist(v1, N);

private static int dist(int start, int end) {
	//코드
}

 

3. Integer.MAX_VALUE로 초기화한 기존 dist배열을 문제 조건에 맞게. 간선 개수(200,000) * 거리(1000)의 최대 값인 2억개로 셋팅해준다.

private static int INF = 200000000;
Arrays.fill(dist, INF);

 

 

전체 코드

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

public class Main {

    private static int N;
    private static int E;
    private static int v1;
    private static int v2;
    private static int[] dist;
    private static boolean[] visited;
    private static List<List<Node>> nodeList = new ArrayList<>();
    private static int INF = 200000000;

    static class Node implements Comparable<Node> {
        int node;
        int cost;

        public Node(int node, int cost) {
            this.node = node;
            this.cost = cost;
        }

        @Override
        public int compareTo(Node o) {
            return cost - o.cost;
        }
    }

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");

        N = Integer.parseInt(st.nextToken());
        E = Integer.parseInt(st.nextToken());

        for(int i = 0; i <= N; i ++) {
            nodeList.add(new ArrayList<>());
        }

        for(int i = 0; i < E; i ++) {
            st = new StringTokenizer(br.readLine(), " ");
            int before = Integer.parseInt(st.nextToken());
            int after = Integer.parseInt(st.nextToken());
            int weight = Integer.parseInt(st.nextToken());

            nodeList.get(before).add(new Node(after, weight));
            nodeList.get(after).add(new Node(before, weight));
        }

        st = new StringTokenizer(br.readLine(), " ");
        v1 = Integer.parseInt(st.nextToken());
        v2 = Integer.parseInt(st.nextToken());

        dist = new int[N + 1];
        visited = new boolean[N + 1];

        int distV1 = 0;
        distV1 += dist(1, v1);
        distV1 += dist(v1, v2);
        distV1 += dist(v2, N);

        int distV2 = 0;
        distV2 += dist(1, v2);
        distV2 += dist(v2, v1);
        distV2 += dist(v1, N);

        System.out.println(distV1 >= INF && distV2 >= INF ? -1 : Math.min(distV1, distV2));
    }

    private static int dist(int start, int end) {
        Arrays.fill(dist, INF);
        Arrays.fill(visited, false);
        dist[start] = 0;

        PriorityQueue<Node> nodeQueue = new PriorityQueue<>();
        nodeQueue.offer(new Node(start, 0));

        while (!nodeQueue.isEmpty()) {
            Node curNode = nodeQueue.poll();
            int cur = curNode.node;

            if(!visited[cur]) {
                visited[cur] = true;

                for(Node node : nodeList.get(cur)) {
                    if(!visited[node.node] && dist[node.node] > dist[cur] + node.cost) {
                        dist[node.node] = dist[cur] + node.cost;
                        nodeQueue.offer(new Node(node.node, dist[node.node]));
                    }
                }
            }
        }
        return dist[end];
    }
}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[알고리즘] 다익스트라(Dijkstra) 알고리즘

Tech/알고리즘 2023. 8. 13. 08:36
728x90
728x90

다익스트라(Dijkstra) 알고리즘

그래프의 최단 경로를 구하는 알고리즘으로 하나의 정점에서 출발하여 최단 거리를 구하는 알고리즘이다.

 

탐욕법(Greedy)과 동적 계획법을 사용하는 알고리즘으로, 음의 가중치(음의 값)가 없어야한다.

음의 가중치가 존재하게 되면, 최소 비용의 음의 무한대값이 생성될 수 있으며 그리디 알고리즘을 적용할 수 없다.

 

인접 행렬로 표현된 그래프의 경우 O(n^2)의 복잡도를 가지며, 우선순위 큐를 사용하여 시간복잡도를 O(m logn)까지 낮출 수 있다.

 

 

매커니즘

(1) 방문하지 않은 노드 중 가장 비용이 적은 노드를 선택한다 (그리디)
(2) 해당 노드로부터 갈 수 있는 노드들의 비용을 갱신한다 (DP)

 

 

예시

https://mag1c.tistory.com/451

 

[백준 1916번 / Java] 최소비용 구하기 / 다익스트라(Dijkstra) - DP

문제 링크 Gold 5 https://www.acmicpc.net/problem/1916 1916번: 최소비용 구하기 첫째 줄에 도시의 개수 N(1 ≤ N ≤ 1,000)이 주어지고 둘째 줄에는 버스의 개수 M(1 ≤ M ≤ 100,000)이 주어진다. 그리고 셋째 줄부

mag1c.tistory.com

 

위 포스팅의 백준 1916번 문제를 예시로 들어 다익스트라 알고리즘을 구현해보자.

 

아래는 문제의 입력 예시 부분이며, 직접 그림으로 그려서 표현해보았다.

 

 

 또한 다익스트라 알고리즘 구현을 위해 필요한 우선순위 큐(선택)와 방문 체크 및 최단 경로를 기록할 수 있는 배열 두 개를 준비한다.

 

우선순위 큐에는, 정점과 출발지에서 정점까지 가는 최소거리를 저장한다.

또한 최단거리를 비교하여 입력할 수 있게, dist배열에는 최대로 들어갈 수 있는 길이의 값보다 큰 수를 입력하는 것이 좋다.

PriorityQueue = []
check = [F, F, F, F, F]
dist = [max, max, max, max, max]

 

구현할 때, 위에서 언급한 매커니즘대로 적용만 시키면 된다. 시작해보자

 

 

 

출발지인 1번 노드의 경우에서 보자. (참고 : 1번에서 1번까지의 길이는 출발지이기 때문에 0이다.)

출발지를 우선순위 큐에 입력한 후

PriorityQueue = [[1, 0]]
check = [T, F, F, F, F]
dist = [0, max, max, max, max]

 

방문한 노드에서 또 갈 수 있는 인접 노드들의 비용을 갱신하며, 방문하지 않은 노드의 경우 우선순위 큐에 갱신한다.

이 때, 우선순위 큐를 통해 비용이 가장 작은 순으로 먼저 입력될 것이다.

PriorityQueue = [[4, 1], [2,2], [3,3], [5,10]]
check = [T, F, F, F, F]
dist = [0, 2, 3, 1, 10]

 

방문하지 않은 노드 중에서 가장 비용이 작은 노드를 선택하여 또 방문한다.

여기서부터는 모든 노드를 탐색할 때 까지 무한반복이다.

방문 여부를 확인하고 나서 더 적은 비용을 갱신하고, 큐에 저장하고, 또 가장 작은노드를 먼저 탐색할 것이다.

이제 설명없이 쭉쭉 나열해보겠다.

PriorityQueue = [[2,2], [3,3], [5,10], [5,4]]
check = [T, F, F, T, F]
dist = [0, 2, 3, 1, 4]
PriorityQueue = [[3,3], [5,4], [5,10]]
check = [T, T, F, T, F]
dist = [0, 2, 3, 1, 4]
PriorityQueue = [[5,4], [5,10]]
check = [T, T, T, T, F]
dist = [0, 2, 3, 1, 4]
PriorityQueue = [[5,10]]
check = [T, T, T, T, T]
dist = [0, 2, 3, 1, 4]
PriorityQueue = []
check = [T, T, T, T, T]
dist = [0, 2, 3, 1, 4]

 

이렇게 방문이 끝나게되면, 결국 출발지점부터 도착지점까지의 최단경로가 dist[도착지점] 배열 안에 들어있을 것이다.

 

 

아래 주석을 잘 해놓은 예시 코드가 있어, 출처를 남기고 공유한다.

https://sskl660.tistory.com/59

 

[Java]다익스트라 알고리즘(Dijkstra Algorithm)

*다익스트라 알고리즘(Dijkstra Algorithm) -> 다익스트라 알고리즘은 음의 가중치(음의 간선, 음의 값)가 없는 그래프의 한 노드에서 각 모든 노드까지의 최단거리를 구하는 알고리즘을 말한다. -> 초

sskl660.tistory.com

package Graph;

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

/*
sample input
5 6
1
5 1 1
1 2 2
1 3 3
2 3 4
2 4 5
3 4 6
 */

public class Dijkstra2 {
	static int V, E, start;
	static ArrayList<ArrayList<Node>> graph;

	static class Node {// 다음 노드의 인덱스와, 그 노드로 가는데 필요한 비용을 담고 있다.
		int idx, cost;

		Node(int idx, int cost) {
			this.idx = idx;
			this.cost = cost;
		}
	}

	public static void main(String[] args) throws IOException {
		// 초기화
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		V = Integer.parseInt(st.nextToken());
		E = Integer.parseInt(st.nextToken());
		start = Integer.parseInt(br.readLine());
		graph = new ArrayList<ArrayList<Node>>();
		for (int i = 0; i < V + 1; i++) {
			graph.add(new ArrayList<Node>());
		}
		for (int i = 0; i < E; i++) {
			st = new StringTokenizer(br.readLine());
			int s = Integer.parseInt(st.nextToken());
			int e = Integer.parseInt(st.nextToken());
			int c = Integer.parseInt(st.nextToken());
			// 문제에서는 유향 그래프에서의 다익스트라 알고리즘(이 조건도 문제에 따라 중요하다!).
			graph.get(s).add(new Node(e, c));
		}

		// 다익스트라 알고리즘 초기화
		int[] dist = new int[V + 1]; // 최소 비용을 저장할 배열
		for (int i = 0; i < V + 1; i++) {
			dist[i] = Integer.MAX_VALUE;
		}

		// 주의점 1. 다익스트라 알고리즘의 최소비용을 기준으로 추출해야 한다. 최대 비용을 기준으로 하는 경우 최악의 경우 지수시간 만큼의 연산을
		// 해야한다!
		PriorityQueue<Node> q = new PriorityQueue<Node>((o1, o2) -> Integer.compare(o1.cost, o2.cost));
		// 시작 노드에서, 시작 노드로 가는 값이 초기에 가장 짧은 비용을 갖는 노드이다.
		// 즉, 도착 정점은 start, 비용은 0인 노드를 가장 먼저 선택할 것이다.
		q.offer(new Node(start, 0));
		// 해당 노드를 선택한 것이나 마찬가지 이므로, dist[start] = 0으로 갱신.
		dist[start] = 0;
		while (!q.isEmpty()) {
			Node curNode = q.poll();

			// 목표 정점이 꺼내 졌다면, 해당 노드까지는 최솟값 갱신이 완료 되었다는 것이 확정이다(다익스트라 알고리즘).
			// 따라서, 반복문을 종료해도 되지만, 해당 코드는 시작 정점에 대하여 모든 정점으로의 최단 경로를 구하는 것을 가정한다.
			// 아래 주석된 코드는 목표 정점이 구해졌다면 빠르게 탈출할 수 있는 조건이다.
//			if (curNode.idx == end) {
//				System.out.println(dist[curNode.idx]);
//				return;
//			}

			// 꺼낸 노드 = 현재 최소 비용을 갖는 노드.
			// 즉, 해당 노드의 비용이 현재 dist배열에 기록된 내용보다 크다면 고려할 필요가 없으므로 스킵한다.
			// 주의점 2 : 중복노드 방지1 : 만일, 이 코드를 생략한다면, 언급한 내용대로 이미 방문한 정점을 '중복하여 방문'하게 된다.
			// 만일 그렇다면, 큐에 있는 모든 다음 노드에대하여 인접노드에 대한 탐색을 다시 진행하게 된다.
			// 그래프 입력이 만일 완전 그래프의 형태로 주어진다면, 이 조건을 생략한 것 만으로 시간 복잡도가 E^2에 수렴할 가능성이 생긴다.
			if (dist[curNode.idx] < curNode.cost) {
				continue;
			}

			// 선택된 노드의 모든 주변 노드를 고려한다.
			for (int i = 0; i < graph.get(curNode.idx).size(); i++) {
				Node nxtNode = graph.get(curNode.idx).get(i);
				// 만일, 주변 노드까지의 현재 dist값(비용)과 현재 선택된 노드로부터 주변 노드로 가는 비용을 비교하고, 더 작은 값을 선택한다.
				// 주의점 3 : 중복노드 방지 2 : 만일, 조건문 없이 조건문의 내용을 수행한다면 역시 중복 노드가 발생한다.
				// 간선에 연결된 노드를 모두 넣어준다면 앞서 언급한 정점의 시간 복잡도 VlogV를 보장할 수 없다.
				// 마찬가지로 E^2에 수렴할 가능성이 생긴다. 따라서 이 조건 안에서 로직을 진행해야만 한다.
				if (dist[nxtNode.idx] > curNode.cost + nxtNode.cost) {
					dist[nxtNode.idx] = curNode.cost + nxtNode.cost;
					// 갱신된 경우에만 큐에 넣는다.
					q.offer(new Node(nxtNode.idx, dist[nxtNode.idx]));
				}
			}
		}

		// 결과 출력
		System.out.println(Arrays.toString(dist));
	}
}

 

 

 

관련 문제풀이

https://mag1c.tistory.com/451

 

[백준 1916번 / Java] 최소비용 구하기 / 다익스트라(Dijkstra) - DP

문제 링크 Gold 5 https://www.acmicpc.net/problem/1916 1916번: 최소비용 구하기 첫째 줄에 도시의 개수 N(1 ≤ N ≤ 1,000)이 주어지고 둘째 줄에는 버스의 개수 M(1 ≤ M ≤ 100,000)이 주어진다. 그리고 셋째 줄부

mag1c.tistory.com

https://mag1c.tistory.com/453

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1916번 / Java] 최소비용 구하기 / 다익스트라(Dijkstra)

P.S./백준 2023. 8. 13. 06:38
728x90
728x90

문제 링크

Gold 5

 

https://www.acmicpc.net/problem/1916

 

1916번: 최소비용 구하기

첫째 줄에 도시의 개수 N(1 ≤ N ≤ 1,000)이 주어지고 둘째 줄에는 버스의 개수 M(1 ≤ M ≤ 100,000)이 주어진다. 그리고 셋째 줄부터 M+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그

www.acmicpc.net

 

 

 

 

 

첫 풀이

단순 BFS로 구해보려했던 것 같다.

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

public class Main {

    private static int N;
    private static int M;
    private static int[][] arr;
    private static int start;
    private static int end;
    private static List<Integer> answer = new ArrayList<>();

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        N = Integer.parseInt(br.readLine());
        M = Integer.parseInt(br.readLine());

        arr = new int[M][3];

        StringTokenizer st;

        for(int i = 0; i < M; i ++) {
            st = new StringTokenizer(br.readLine(), " ");

            for(int j = 0; j < 3; j ++) {
                arr[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        st = new StringTokenizer(br.readLine(), " ");
        start = Integer.parseInt(st.nextToken());
        end = Integer.parseInt(st.nextToken());

        Queue<int []> queue = new LinkedList<>();
        queue.add(new int[]{start, 0});

        bfs(queue);

        Collections.sort(answer);

        System.out.println(answer.get(0));

    }

    private static void bfs(Queue<int[]> queue) {
        while(!queue.isEmpty()) {
            int size = queue.size();

            for(int i = 0; i < size; i ++) {
                int[] tmp = queue.poll();
                int current = tmp[0];
                int cost = tmp[1];

                if(current == end) {
                    answer.add(cost);
                    continue;
                }

                for(int j = 0; j < M; j ++) {
                    if(arr[j][0] == current) {
                        queue.add(new int[] {arr[j][1], cost + arr[j][2]});
                    }
                }
            }
        }
    }
}

 

버스의 대수가 최대 10만대라는 조건이 있었으므로 당연히 시간초과가 발생했다.

 

 

 

두번째 풀이

최단거리 경로를 구할 때 활용하는 DP의 대표적인 최단 경로 탐색 알고리즘인 다익스트라(Dijkstra)를 활용했다.

 

다익스트라 알고리즘에 대한 자세한 설명은 아래 포스팅을 참고하기 바란다.

https://mag1c.tistory.com/452

 

bfs와의 차이는 아무래도, 현재 방문하고있는 노드에 대한 체크를 해주지 못해서 시간초과가 발생한 것 같다.

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

public class Main {

    private static int N;
    private static int M;
    private static int start;
    private static int end;
    private static int[] dist;
    private static boolean[] visited;
    private static List<List<Node>> nodeList = new ArrayList<>();

    static class Node implements Comparable<Node> {
        int node;
        int cost;

        public Node(int node, int cost) {
            this.node = node;
            this.cost = cost;
        }

        @Override
        public int compareTo(Node o) {
            return cost - o.cost;
        }
    }

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        N = Integer.parseInt(br.readLine());
        M = Integer.parseInt(br.readLine());
        StringTokenizer st;

        for(int i = 0; i <= N; i ++) {
            nodeList.add(new ArrayList<>());
        }

        for(int i = 0; i < M; i ++) {
            st = new StringTokenizer(br.readLine(), " ");
            int before = Integer.parseInt(st.nextToken());
            int after = Integer.parseInt(st.nextToken());
            int weight = Integer.parseInt(st.nextToken());

            nodeList.get(before).add(new Node(after, weight));
        }

        st = new StringTokenizer(br.readLine(), " ");
        start = Integer.parseInt(st.nextToken());
        end = Integer.parseInt(st.nextToken());

        dist = new int[N + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[start] = 0;
        visited = new boolean[N + 1];

        System.out.println(dist());
    }

    private static int dist() {
        PriorityQueue<Node> nodeQueue = new PriorityQueue<>();
        nodeQueue.offer(new Node(start, 0));

        while (!nodeQueue.isEmpty()) {
            Node curNode = nodeQueue.poll();
            int cur = curNode.node;

            if(!visited[cur]) {
                visited[cur] = true;

                for(Node node : nodeList.get(cur)) {
                    if(!visited[node.node] && dist[node.node] > dist[cur] + node.cost) {
                        dist[node.node] = dist[cur] + node.cost;
                        nodeQueue.offer(new Node(node.node, dist[node.node]));
                    }
                }
            }
        }

        return dist[end];
    }
}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 9465번 / Java] 스티커 - DP

P.S./백준 2023. 8. 11. 11:55
728x90
728x90

문제 링크

Silver 1

 

https://www.acmicpc.net/problem/9465

 

9465번: 스티커

첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스의 첫째 줄에는 n (1 ≤ n ≤ 100,000)이 주어진다. 다음 두 줄에는 n개의 정수가 주어지며, 각 정수는 그 위치에 해당하는 스티커의

www.acmicpc.net

 

 

 

 

 

풀이

문제의 조건은 선택한 스티커의 상하좌우 스티커는 선택할 수 없다. 라고 하였다. 아래 문제 예시를 보자.

50 10 100 20 40
30 50 70 10 60

위의 예시에서, dp를 사용하여 최대값을 어떻게 구할 수 있을까

 

 

왼쪽부터 시작해서, 하나씩 선택해보았다.

50 10
30 50
50 10
30 50

첫 번째 idx의 값을 선택할 수 있다.

두 번째 idx까지는 문제 없이 선택할 수 있다. 자연스럽게 가로, 세로의 값은 선택할 수 없기 때문에

(50,50) = 100 / (30,10) = 40의 결과가 나올 것이다.

 

 

만약 세 번째 idx에서부터는, 이전 값의 최대값을 더한 값을 표현하려면 어떻게 해야 할까?

50 40
30 100

위의 두 번째 idx에는 이미 두 번째 idx까지의 최대값이 담겨있다. 그럼 이전처럼 idx - 1의 값을 더해가면 되는 것일까?

그렇지 않다. 아래의 예시처럼 만약 idx - 2의 값이 더 큰 경우까지 고려해주어야 한다.

50 130
120 100

 

이러한 상황들을 고려한 점화식은 다음과 같다.

dp[0][j] += j == 1 ? dp[1][j - 1] : Math.max(dp[1][j - 2], dp[1][j - 1]);
dp[1][j] += j == 1 ? dp[0][j - 1] : Math.max(dp[0][j - 2], dp[0][j - 1]);

 

 

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

public class Main {

    private static int N;
    private static int T;

    private static int[][] dp;

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        N = Integer.parseInt(br.readLine());

        for(int i = 0; i < N; i ++) {
            T = Integer.parseInt(br.readLine());
            dp = new int[2][T];

            for(int j = 0; j < 2; j ++) {
                st = new StringTokenizer(br.readLine(), " ");
                for(int k = 0; k < T; k ++) {
                    dp[j][k] = Integer.parseInt(st.nextToken());
                }
            }

            for(int j = 1; j < T; j ++) {
                dp[0][j] += j == 1 ? dp[1][j - 1] : Math.max(dp[1][j - 2], dp[1][j - 1]);
                dp[1][j] += j == 1 ? dp[0][j - 1] : Math.max(dp[0][j - 2], dp[0][j - 1]);
            }
            System.out.println(Math.max(dp[0][T - 1], dp[1][T - 1]));
        }

    }
}

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1149번 / Java] RGB거리 - DP

P.S./백준 2023. 7. 18. 07:03
728x90
728x90

문제 링크

Silver 1

 

https://www.acmicpc.net/problem/1149

 

1149번: RGB거리

첫째 줄에 집의 수 N(2 ≤ N ≤ 1,000)이 주어진다. 둘째 줄부터 N개의 줄에는 각 집을 빨강, 초록, 파랑으로 칠하는 비용이 1번 집부터 한 줄에 하나씩 주어진다. 집을 칠하는 비용은 1,000보다 작거나

www.acmicpc.net

 

 

 

풀이

문제에서 N은 N-1과 N+1과 색상을 중복해서는 안된다. 라고 하는 부분에서 조금 헷갈렸는데 결론은 크게 신경쓰지 않아도 된다.

 

N일 때 N-1에서 중복만 체크한다면 어떤 상황에서도 중복이 될 일은 없다.

for (int i = 0; i < N; i ++) {
    st = new StringTokenizer(br.readLine(), " ");
    int R = Integer.parseInt(st.nextToken());
    int G = Integer.parseInt(st.nextToken());
    int B = Integer.parseInt(st.nextToken());

    if(i > 0) {
        arr[i][0] = Math.min(arr[i - 1][1], arr[i - 1][2]) + R;
        arr[i][1] = Math.min(arr[i - 1][0], arr[i - 1][2]) + G;
        arr[i][2] = Math.min(arr[i - 1][0], arr[i - 1][1]) + B;
        continue;
    }
    arr[i][0] = R;
    arr[i][1] = G;
    arr[i][2] = B;
}

N일 때 겹치지 않는 색상 중 N-1의 값에서 최소값을 더하기만 하면 된다.

 

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

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        int N = Integer.parseInt(br.readLine());
        int[][] arr = new int[N][3];

        for (int i = 0; i < N; i ++) {
            st = new StringTokenizer(br.readLine(), " ");
            int R = Integer.parseInt(st.nextToken());
            int G = Integer.parseInt(st.nextToken());
            int B = Integer.parseInt(st.nextToken());

            if(i > 0) {
                arr[i][0] = Math.min(arr[i - 1][1], arr[i - 1][2]) + R;
                arr[i][1] = Math.min(arr[i - 1][0], arr[i - 1][2]) + G;
                arr[i][2] = Math.min(arr[i - 1][0], arr[i - 1][1]) + B;
                continue;
            }
            arr[i][0] = R;
            arr[i][1] = G;
            arr[i][2] = B;
        }

        System.out.println(Math.min(arr[N - 1][0], Math.min(arr[N - 1][1], arr[N - 1][2])));
    }
}
728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 17626번 / Java] Four Squares - DP

P.S./백준 2023. 7. 17. 06:02
728x90
728x90

문제 링크

Silver 3

 

https://www.acmicpc.net/problem/17626

 

17626번: Four Squares

라그랑주는 1770년에 모든 자연수는 넷 혹은 그 이하의 제곱수의 합으로 표현할 수 있다고 증명하였다. 어떤 자연수는 복수의 방법으로 표현된다. 예를 들면, 26은 52과 12의 합이다; 또한 42 + 32 + 1

www.acmicpc.net

 

 

 

 

풀이

문제 티어는 낮지만 DP문제라서 냉큼 공부

 

처음에는 아래처럼 해당 idx의 제곱값을 구하여 greedy처럼 풀어내려고 했으나

int[] arr = new int[(int) Math.sqrt(n) + 1];
for (int i = 0; i < arr.length; i ++) {
    arr[i] = i * i;
}

 

모든 상황에 만족하지 못했다.

 

n을 줄여가며 제곱 수가 1밖에 존재하지 않는 4 미만의 수가 됐을 때까지 반복해주는 식을 작성했었는데

내가 세운 식을 바탕으로 12를 풀어내면 3^2 + 3의 값인 4가 나오는데

최적의 해는 2^2를 세 번 더한 3이다.

 

dp배열의 값을 바꾸어주어야만 했고, dp배열의 idx값의 제곱 수를 더하는 최소값을 작성하기로 했다.

 

나열해보면 다음과 같다.

dp[0] = 0, dp[1] = 1, dp[2] = 2, dp[3] = 3, dp[4] = 1 .......

 

생각해낸 규칙은 제곱수가 갱신될 때까지는 이전 dp에서 +1을 해 주는 것이었는데

갱신될 때는 무조건 1이다. (4 = 2^2, 9 = 3^2 .....)

어떻게 규칙을 줄 수 있을까 하다가 dp[0]이 0이기 때문에, 거기서 +1을 해주면 어떨까라는 생각을 했고 적용시켜보았다.

 

arr[0] = 0; arr[1] = 1;
for (int i = 2; i < arr.length; i ++) {
    int min = 50001;
    for (int j = 1; j * j <= i; j ++) {
        int idx = i - j * j;
        min = Math.min(min, arr[idx]);
    }
    arr[i] = min + 1;
}

 

작성한 전체 코드는 아래와 같다.

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

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(br.readLine());
        int[] arr = new int[n + 1];

        arr[0] = 0; arr[1] = 1;
        for (int i = 2; i < arr.length; i ++) {
            int min = 50001;
            for (int j = 1; j * j <= i; j ++) {
                int idx = i - j * j;
                min = Math.min(min, arr[idx]);
            }
            arr[i] = min + 1;
        }

        System.out.println(arr[arr.length - 1]);

    }
}

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 1463번 / Java] 1로 만들기 - DP

P.S./백준 2023. 7. 1. 03:38
728x90
728x90

문제 링크

https://www.acmicpc.net/problem/1463

 

1463번: 1로 만들기

첫째 줄에 1보다 크거나 같고, 106보다 작거나 같은 정수 N이 주어진다.

www.acmicpc.net


 

 

 

 

풀이

자기 전에 문제 하나 풀고 자야지~~ 하면서 풀었던 문제

실버 3 문제라 DP로의 접근은 쉬웠으나, 뜻하지 않게 ArrayIndexOutOfBounds Exception으로 고생했음..

반 졸린 상태에서 풀어서~~ 라고 핑계를 대보지만.

실수를 줄이기 위해 항상 신경써야겠다고 다짐한 문제로. 상기시키기 위해 포스팅....

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

public class BaekJoon1463 {

    public static void main(String[] args) throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int X = Integer.parseInt(br.readLine());

        int[] dp = new int[X+1];

        dp[1] = 0;

        for(int i = 2; i <= X; i++){
            dp[i] = dp[i-1] + 1;
            if(i % 6 == 0) dp[i] = Math.min(dp[i/3] + 1, Math.min(dp[i/2] + 1, dp[i]));
            else if(i % 3 == 0) dp[i] = Math.min(dp[i], dp[i/3] + 1);
            else if(i % 2 == 0) dp[i] = Math.min(dp[i], dp[i/2] + 1);
        }

        System.out.println(dp[X]);
    }
}

 

계속 오답을 제출했던 이유는, 처음 dp배열 초기화에 있었는데,

2일 때도 1이고, 3일 때도 1이기 때문에, dp[2]와 dp[3]의 값 모두 1로 설정해놓아서 X가 1일 때, 2일 때 예외상황이 발생했음;;;;;;;;;

 

졸릴 땐 자는걸로 하고.. 좋은 컨디션으로 문제를 풀어나가야겠다.. 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[Java] 2466. Count Ways To Build Good Strings - LeetCode Daily Challenge / Dynamic Programing(DP)

P.S./leetcode 2023. 5. 13. 21:44
728x90
728x90
 

Count Ways To Build Good Strings - LeetCode

Can you solve this real interview question? Count Ways To Build Good Strings - Given the integers zero, one, low, and high, we can construct a string by starting with an empty string, and then at each step perform either of the following: * Append the char

leetcode.com

 

 

 

풀이


DP배열을 생성해, Bottom Up방식으로 풀이.

 

dp[0]=1이 왜 1인가에 대해 고민을 좀 많이 했던 문제였다.

문자열의 길이가 0인게 없지않나?? 라는 생각을 많이했다.

 

1. 문자열의 길이가 0인 경우가 ""의 경우가 존재할 수도 있겠구나라는 생각

2. dp[0]을 1로 설정해주지 않으면 문제 풀이자체가 되지않겠구나. dp[i]=0일테니까.

 

고민을해서 위와 같은 결론을 내렸다.

 

 

 

 

위의 그림은, 해당 문제의 Solution 탭의 그림의 일부인데.

문제에서 말하는 좋은 문자열이 되기 위해서는

 

1. 마지막에 해당 숫자만큼 붙였을때 알맞은 길이의 숫자가 되는것

2. 그러기 위해서는 i가 zero, one보다 크거나 같을때 : 해당 dp[i]가 ++될 것이다.

 

까지 생각을 할 수 있었다.

 

 

이게 무슨말인고? 하니

위의 그림에서 자세하게 idx = 5까지가는 과정을 살펴보자.

 

 

 

모든 dp[idx]배열의 값에, 들어갈 수 있는 경우의 수는

dp[idx-zero]나, dp[idx-one] 두 경우에서 만족하는 경우의 배열들을 추가해준 값이 해당 dp[idx]에 들어갈 수 있는 총 문자열의 개수가 될 수밖에 없다.

 

class Solution {
    public int countGoodStrings(int low, int high, int zero, int one) {
    	int answer = 0;    	    	
    	int[] dp = new int[high+1];
    	dp[0] = 1;

    	for(int i=1; i<=high; ++i) {
    		if(i>=zero) dp[i] += dp[i-zero];
    		if(i>=one) dp[i] += dp[i-one];
    		
    		dp[i] %= 1_000_000_007;
    	}
    	
    	for(int i=low; i<=high; i++) {
    		answer += dp[i];
    		answer %= 1_000_000_007;
    	}
    	
        return answer;
    }
}

 

 

O(n) (n=high)의 시간복잡도를 가지는 코드가 완성됐다.

 

 

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

방명록