트리의 지름: 트리에서 임의의 두 점 사이의 거리 중 가장 긴 것

 

트리의 지름이라면.. 저번에 풀었던 문제를 함께 보면 좋다.

2020/12/20 - [알고리즘 공부/programmers] - Programmers 68937: 트리 트리오 중간값

 

Programmers 68937: 트리 트리오 중간값

예시 1 답: (트리의 지름) - 1 예시 2 답: (트리의 지름) 1차 시도 - 시간초과 트리의 지름을 가지는 노드가 2쌍 이상이면 2번 예시에 들어가고, 한 쌍이면 1번 예시에 들어가게 된다. 트리의 지름을

sysgongbu.tistory.com

 

어떤 트리의 지름을 구하는 방법을 예시를 통해 보자.

1. 먼저 임의의 점(1)부터 순회를 시작하여 가장 먼 거리에 있는 점 5를 찾는다.

2. 가장 먼 점 5에서 다시 순회를 하면서 가장 먼 거리에 있는 점은 2이며, 5~2=11이 트리의 지름이 된다.

이러한 풀이를 이용하면 모든 점에 대해 가장 먼 거리를 구하지 않아도 되고, 시간복잡도는 O(N)으로 줄어든다. 풀이는 아래와 같다.

import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int v = in.nextInt();
		LinkedList<Node> connections[] = new LinkedList[v + 1];
		for (int i = 1; i <= v; i++) {
			int start = in.nextInt();
			connections[start] = new LinkedList<Node>();
			
			while (true) {
				int end = in.nextInt();
				if (end == -1) {
					break;
				}
				int length = in.nextInt();

				connections[start].add(new Node(end, length));
			}
		}

		System.out.println(findTreeRadius(1, connections, true, v));
	}

	public static int findTreeRadius(int start, LinkedList<Node> connections[], boolean isFirstCycle, int n) {
		boolean[] isVisited = new boolean[n + 1];

		Queue<Node> queue = new LinkedList<Node>();
		queue.add(new Node(start, 0));
		isVisited[start] = true;

		int maxNode = start;
		int maxLength = 0;
		while (!queue.isEmpty()) {
			Node now = queue.poll();

			if (maxLength < now.length) {
				maxLength = now.length;
				maxNode = now.name;
			}

			for (Node next : connections[now.name]) {
				if (!isVisited[next.name]) {
					queue.add(new Node(next.name, now.length + next.length));
					isVisited[next.name] = true;
				}
			}
		}

		if (isFirstCycle) {
			return findTreeRadius(maxNode, connections, false, n);
		}
		return maxLength;
	}
}

class Node {
	int name;
	int length;

	public Node(int name, int length) {
		this.name = name;
		this.length = length;
	}

	@Override
	public String toString() {
		return "(" + name + ", " + length + ")";
	}
}

 

'알고리즘 공부 > boj' 카테고리의 다른 글

boj 1294: 문자열 장식  (0) 2021.01.29
boj 16637: 괄호 추가하기  (0) 2021.01.27
boj 1939: 중량제한  (0) 2021.01.20
boj 17143: 낚시왕  (0) 2021.01.20
boj 1005: ACM Craft  (0) 2021.01.20

배열의 (1, 1)~(n, n)까지 이동할 때, 이동하기 위해 거쳐 간 수들 중 최댓값과 최솟값의 차이가 가장 작아지는 경우를 구하는 문제이다.

배열에서 이동하는 모든 경우를 완전탐색 할 경우 O(4^N)이 걸릴 것이다.

 

이런 경우 최댓값과 최솟값을 차이를 무작정 구하는 방법 보다는,

최댓값과 최솟값을 차이를 정해두고 가능한지 여부를 보는 방법이 더 쉬울 수 있다.

 

 

1차 시도 - 이분탐색을 사용하자

 

처음 생각한 문제 풀이 과정은 아래와 같다.

1. 이진 탐색으로 최대-최소 차이를 정한다.

2. (1, 1) -> (n, n)로 차이 내에 갈 수 있는지 bfs로 탐색한다.

 

이 경우에는 차이를 구하더라도 이에 맞는 쌍을 구해야 하는데,

이때 해당하는 쌍을 구할 때 이중for문을 사용하기보다는 두 포인터를 이용하여 시간을 줄이도록 했다.

import java.util.*;

public class Main {
	public static int dr[] = { 0, 0, 1, -1 };
	public static int dc[] = { 1, -1, 0, 0 };

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int mat[][] = new int[n][n];
		TreeSet<Integer> set = new TreeSet<Integer>();

		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				mat[i][j] = in.nextInt();
				set.add(mat[i][j]);
			}
		}

		int[] numbers = set.stream().mapToInt(i -> i).toArray();
		System.out.println(findDifference(numbers, mat, n));
	}

	private static int findDifference(int[] numbers, int[][] mat, int n) { // 이분탐색
		int length = numbers.length;
		int start = 0;
		int end = numbers[length - 1] - numbers[0];
		int answer = Integer.MAX_VALUE;

		while (start <= end) {
			int mid = (start + end) / 2;
			if (findMaxMinFromDifference(mat, numbers, mid, n)) {
				answer = mid; // 경로가 존재하니까 일단 저장
				end = mid - 1; // 더 줄여보자
			} else {
				start = mid + 1;
			}
		}

		return answer;
	}

	private static boolean findMaxMinFromDifference(int mat[][], int[] numbers, int diff, int n) { // 투 포인터
		int length = numbers.length;

		int start = 0;
		int end = 0;

		while (start < length && end < length) {
			if (numbers[end] - numbers[start] > diff) {
				start++;
			} else {

				if (bfs(mat, numbers[end], numbers[start], n)) {
					return true;
				}
				end++;
			}
		}

		return false;
	}

	// 최댓값과 최솟값의 차이가 min 보다 클 경우 false 리턴
	public static boolean bfs(int[][] mat, int max, int min, int n) { // bfs

		if (!isValuePossible(mat[0][0], max, min)) {
			return false;
		}

		Queue<Route> queue = new LinkedList<Route>();
		queue.add(new Route(0, 0));
		boolean[][] isVisited = new boolean[n][n];
		isVisited[0][0] = true;

		while (!queue.isEmpty()) {
			Route route = queue.poll();

			if (route.row == n - 1 && route.col == n - 1) {
				return true;
			}

			for (int i = 0; i < 4; i++) {
				int newRow = route.row + dr[i];
				int newCol = route.col + dc[i];

				if (isBoundary(newRow, newCol, n) && !isVisited[newRow][newCol]
						&& isValuePossible(mat[newRow][newCol], max, min)) {
					Route newRoute = new Route(newRow, newCol);
					isVisited[newRow][newCol] = true;

					queue.add(newRoute);
				}
			}
		}
		return false;
	}

	public static boolean isValuePossible(int value, int max, int min) {
		if (value < min) {
			return false;
		}
		if (value > max) {
			return false;
		}
		return true;
	}

	public static boolean isBoundary(int row, int col, int n) {
		if (row < 0) {
			return false;
		}
		if (row >= n) {
			return false;
		}
		if (col < 0) {
			return false;
		}
		if (col >= n) {
			return false;
		}
		return true;
	}
}

class Route {
	int row;
	int col;

	public Route(int row, int col) {
		this.row = row;
		this.col = col;
	}
}

 

생각해보니 굳이 이분탐색으로 범위를 줄여서 또 두 포인터로 각각 최대/최소 원소를 구하는 것은 연산이 중복되는 느낌이 들었다..ㅋㅋㅋ

따라서 이분탐색 부분은 제외하고 두 포인터와 bfs를 이용한 풀이로 다시 고쳐보았다.

추가적으로 isVisited의 경우 함수 내에서 계속 새로 할당할 경우 시간이 더 걸릴 수 있으므로 재사용하는 부분을 추가했다.

import java.util.*;

public class Main {
	public static int dr[] = { 0, 0, 1, -1 };
	public static int dc[] = { 1, -1, 0, 0 };
	public static boolean isVisited[][];

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int mat[][] = new int[n][n];
		isVisited = new boolean[n][n];

		TreeSet<Integer> set = new TreeSet<Integer>();
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				mat[i][j] = in.nextInt();
				set.add(mat[i][j]);
			}
		}

		int[] numbers = set.stream().mapToInt(i -> i).toArray();
		System.out.println(findMaxMin(mat, numbers, n));
	}

	private static int findMaxMin(int mat[][], int[] numbers, int n) { // 투 포인터로 max, min 정하기
		int length = numbers.length;

		int start = 0;
		int end = 0;

		int answer = Integer.MAX_VALUE;

		while (start < length && end < length) {
			int diff = numbers[end] - numbers[start];
			if (bfs(mat, numbers[end], numbers[start], n)) {
				answer = Math.min(answer, diff); // 가능한 경로이므로 일단 저장

				start++; // 더 줄여보자
			} else {
				end++; // 불가능한 경로이므로 diff 범위를 늘리자
			}
		}

		return answer;
	}

	public static boolean bfs(int[][] mat, int max, int min, int n) { // bfs 가능한 경로 탐색
		if (!isPossible(0, 0, n, max, min, mat)) {
			return false;
		}

		clearMatrix(isVisited, n);

		Queue<Route> queue = new LinkedList<Route>();
		queue.add(new Route(0, 0));
		isVisited[0][0] = true;

		while (!queue.isEmpty()) {
			Route route = queue.poll();

			if (route.row == n - 1 && route.col == n - 1) {
				return true;
			}

			for (int i = 0; i < 4; i++) {
				int newRow = route.row + dr[i];
				int newCol = route.col + dc[i];

				if (isPossible(newRow, newCol, n, max, min, mat) && !isVisited[newRow][newCol]) {
					Route newRoute = new Route(newRow, newCol);
					isVisited[newRow][newCol] = true;

					queue.add(newRoute);
				}
			}
		}
		return false;
	}

	public static void clearMatrix(boolean[][] isVisited, int n) {
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				isVisited[i][j] = false;
			}
		}
	}

	public static boolean isPossible(int row, int col, int n, int max, int min, int[][] mat) {
		// 바운더리 안에 존재하며, [min, max]인 값임을 확인
		if (row < 0) {
			return false;
		}
		if (row >= n) {
			return false;
		}
		if (col < 0) {
			return false;
		}
		if (col >= n) {
			return false;
		}
		if (mat[row][col] < min) {
			return false;
		}
		if (mat[row][col] > max) {
			return false;
		}

		return true;
	}
}

class Route {
	int row;
	int col;

	public Route(int row, int col) {
		this.row = row;
		this.col = col;
	}

	@Override
	public String toString() {
		return "(" + row + ", " + col + ")";
	}
}

 

 

 

 

'알고리즘 공부 > boj' 카테고리의 다른 글

boj 1005: ACM Craft  (0) 2021.01.20
boj 1949: 우수 마을  (0) 2021.01.19
boj 3745: 오름세  (0) 2021.01.15
boj 1091: 카드 섞기  (0) 2021.01.11
boj 1153: 네 개의 소수  (0) 2021.01.10

방법1 - 각 cell에서 1인 부분을 큐에 넣고 bfs를 통해 0까지의 최소 거리를 찾는다.

import java.util.*;
class Solution {
	public static int dr[] = { 0, 0, 1, -1 };
	public static int dc[] = { 1, -1, 0, 0 };

	public int[][] updateMatrix(int[][] matrix) {
		Queue<Position> queue = new LinkedList<Position>();
		for (int i = 0; i < matrix.length; i++) {
			for (int j = 0; j < matrix[i].length; j++) {
				if (matrix[i][j] != 0) {
					queue.add(new Position(i, j, 0));
				}
			}
		}

		while (!queue.isEmpty()) {
			Position now = queue.poll();
			updateCell(now.row, now.col, matrix);
		}
		return matrix;
	}

	public void updateCell(int row, int col, int[][] matrix) {
		Queue<Position> queue = new LinkedList<Position>();
		queue.add(new Position(row, col, 0));

		while (!queue.isEmpty()) {
			Position now = queue.poll();

			if (matrix[now.row][now.col] == 0) {
				matrix[row][col] = now.value;
				return;
			}

			for (int i = 0; i < 4; i++) {
				int newRow = now.row + dr[i];
				int newCol = now.col + dc[i];

				if (isBoundary(newRow, newCol, matrix)) {
					queue.add(new Position(newRow, newCol, now.value + 1));
				}
			}
		}
	}

	public boolean isBoundary(int row, int col, int[][] matrix) {
		if (row < 0 || row >= matrix.length) {
			return false;
		}
		if (col < 0 || col >= matrix[row].length) {
			return false;
		}
		return true;
	}

	public void printMatrix(int[][] matrix) {
		for (int i = 0; i < matrix.length; i++) {
			for (int j = 0; j < matrix[i].length; j++) {
				System.out.print(matrix[i][j] + " ");
			}
			System.out.println();
		}
	}
}

class Position {
	int row, col, value;

	public Position(int row, int col, int value) {
		this.row = row;
		this.col = col;
		this.value = value;
	}
}

 

방법2 - 어차피 matrix에 거리를 계속 업데이트 하고 있는데 이 것을 이용해서 다음 값을 구할 수 없을까? => DP

class Solution {
	public int[][] updateMatrix(int[][] matrix) {
		int n = matrix.length;
		int m = matrix[0].length;
		int[][] dp = matrix;
		int INF = 10000;

		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				if (matrix[i][j] == 1) {
					dp[i][j] = INF;
				}
			}
		}

		for (int i = 1; i < n; i++) {
			dp[i][0] = Math.min(dp[i][0], dp[i - 1][0] + 1);
		}
		for (int i = 1; i < m; i++) {
			dp[0][i] = Math.min(dp[0][i], dp[0][i - 1] + 1);
		}

		for (int i = 1; i < n; i++) {
			for (int j = 1; j < m; j++) {
				dp[i][j] = Math.min(dp[i][j], Math.min(dp[i][j - 1], dp[i - 1][j]) + 1);
			}
		}

		for (int i = n - 2; i >= 0; i--) {
			dp[i][m - 1] = Math.min(dp[i + 1][m - 1] + 1, dp[i][m - 1]);
		}
		for (int i = m - 2; i >= 0; i--) {
			dp[n - 1][i] = Math.min(dp[n - 1][i + 1] + 1, dp[n - 1][i]);
		}

		for (int i = n - 2; i >= 0; i--) {
			for (int j = m - 2; j >= 0; j--) {
				dp[i][j] = Math.min(dp[i][j], Math.min(dp[i][j + 1], dp[i + 1][j]) + 1);
			}
		}

		return dp;
	}
}

'알고리즘 공부 > leetcode' 카테고리의 다른 글

leetcode: Maximum Frequency Stack  (0) 2021.01.23
leetcode: Range Sum Query - Mutable  (0) 2021.01.10
leetcode: Number of Islands  (0) 2021.01.07
leetcode: Trapping Rain Water  (0) 2020.12.27
leetcode: Median of two sorted Arrays  (0) 2020.12.26

전형적인 bfs문제이다. 

시간복잡도는 O(grid 요소의 갯수)와 같다.

import java.util.*;
class Solution {
	public static int dr[] = { 0, 1, 0, -1 };
	public static int dc[] = { 1, 0, -1, 0 };

	public int numIslands(char[][] grid) {
		int answer = 0;

		for (int i = 0; i < grid.length; i++) {
			for (int j = 0; j < grid[i].length; j++) {
				if (grid[i][j] == '1') {
					answer++;
					grid[i][j] = '0';
					bfs(i, j, grid);
				}
			}
		}
		return answer;
	}

	public static void bfs(int row, int col, char[][] grid) {
		Queue<Position> queue = new LinkedList<Position>();
		queue.add(new Position(row, col));

		while (!queue.isEmpty()) {
			Position now = queue.poll();

			for (int i = 0; i < 4; i++) {
				int newRow = now.row + dr[i];
				int newCol = now.col + dc[i];

				if (isBoundary(newRow, newCol, grid) && grid[newRow][newCol] == '1') {
					grid[newRow][newCol] = '0';
                    queue.add(new Position(newRow, newCol));
				}
			}
		}
	}

	public static boolean isBoundary(int row, int col, char[][] grid) {
		if (row < 0) {
			return false;
		}
		if (row >= grid.length) {
			return false;
		}
		if (col < 0) {
			return false;
		}
		if (col >= grid[row].length) {
			return false;
		}

		return true;
	}
}

class Position {
	int row, col;

	public Position(int row, int col) {
		this.row = row;
		this.col = col;
	}
}

N x N 크기의 정사각형 격자 형태에서 (0, 0) ~ (N-1, N-1)까지 경주로를 만드는데

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

경주로를 만드는 데 최소 비용을 구하는 문제이다.

 

1차 시도 - BFS로 모든 방향에 대한 비용을 구하면서 재방문의 경우는 제외해주도록 한다. -> O(3^N)이 걸릴 것이기 때문에 시간 초과..

심지어 정확도도 틀림

import java.util.*;
class Solution {
	public static int dr[] = { 0, 1, 0, -1 };
	public static int dc[] = { 1, 0, -1, 0 };

	public int solution(int[][] board) {
		Queue<Move> queue = new LinkedList<Move>();
		for (int i = 0; i < 4; i++) {
			queue.add(new Move(dr[i], dc[i], i, 100)); // 이 부분이 벽일 수도 있기 때문에 정확도가 틀린 것이었다.
		}

		int min = Integer.MAX_VALUE;
		int length = board.length;

		while (!queue.isEmpty()) {
			Move now = queue.poll();

			if (now.row == length - 1 && now.col == length - 1) {
				min = Math.min(min, now.value);
			}

			for (int i = 0; i < 4; i++) {
				if (Math.abs(i - now.direction) == 2) {
					continue;
				}

				int newRow = now.row + dr[i];
				int newCol = now.col + dc[i];
				int newValue = now.direction == i ? 100 : 600;
				newValue += now.value;

				if (isBoundary(newRow, newCol, length) && newValue > 0 && newValue < min
						&& board[newRow][newCol] == 0) {
					queue.add(new Move(newRow, newCol, i, newValue));
				}
			}

		}

		return min;
	}

	public boolean isBoundary(int row, int col, int N) {
		if (row < 0) {
			return false;
		}
		if (col < 0) {
			return false;
		}
		if (row >= N) {
			return false;
		}
		if (col >= N) {
			return false;
		}
		return true;
	}
}

class Move {
	int row;
	int col;
	int direction;
	int value;

	public Move(int row, int col, int direction, int value) {
		this.row = row;
		this.col = col;
		this.direction = direction;
		this.value = value;
	}
}

채점 결과

정확성: 30.4

합계: 30.4 / 100.0

 

 

 

2차시도 - 재 방문을 줄이자 => DP를 사용해볼까? 그런데 방향마다 더해지는 값이 달라서 DP에도 다르게 저장해야함

DP[i][j][k]: (0, 0) ~ (i, j)까지 k방향으로 움직였을 때 최소비용이라고 정의하도록 하자

import java.util.*;
import java.util.*;

class Solution {
	public static int dr[] = { 0, 1, 0, -1 };
	public static int dc[] = { 1, 0, -1, 0 };

	public int solution(int[][] board) {

		int min = Integer.MAX_VALUE;
		int length = board.length;

		int dp[][][] = new int[length][length][4];

		Queue<Move> queue = new LinkedList<Move>();
		for (int i = 0; i < 4; i++) {
			queue.add(new Move(0, 0, i, 0));
		}

		while (!queue.isEmpty()) {
			Move now = queue.poll();

			if (now.row == length - 1 && now.col == length - 1) {
				min = Math.min(min, dp[now.row][now.col][now.direction]);
			}

			for (int i = 0; i < 4; i++) {
				if (Math.abs(i - now.direction) != 2) { // 후진 제외
					int newRow = now.row + dr[i];
					int newCol = now.col + dc[i];
					int newValue = (now.direction == i ? 100 : 600) + now.value;

					if (isBoundary(newRow, newCol, length) && board[newRow][newCol] == 0) {
						if (dp[newRow][newCol][i] >= newValue || dp[newRow][newCol][i] == 0) {
							dp[newRow][newCol][i] = newValue;
							queue.add(new Move(newRow, newCol, i, newValue));
						}
					}
				}
			}
		}

		return min;
	}

	public boolean isBoundary(int row, int col, int N) {
		if (row < 0) {
			return false;
		}
		if (col < 0) {
			return false;
		}
		if (row >= N) {
			return false;
		}
		if (col >= N) {
			return false;
		}
		return true;
	}
}

class Move {
	int row;
	int col;
	int direction;
	int value;

	public Move(int row, int col, int direction, int value) {
		this.row = row;
		this.col = col;
		this.direction = direction;
		this.value = value;
	}
}

 

 

 

 

 

import java.util.*;

class Solution {
	static int n, m, k;
	static int[][] arr;
	static List<Pair> list;

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int test = in.nextInt();
		list = new ArrayList<>();
		for (int i = 1; i <= test; i++) {
			list.clear();

			n = in.nextInt();
			m = in.nextInt();
			k = in.nextInt();
			arr = new int[n + k][m + k];
			for (int j = (k / 2); j < (k / 2) + n; j++) {
				for (int z = (k / 2); z < (k / 2) + m; z++) {
					arr[j][z] = in.nextInt();
					if (arr[j][z] != 0)
						list.add(new Pair(j, z, arr[j][z], arr[j][z], k, 0));
				}
			}
			solve();
			
			int result = 0;
			for (int j = 0; j < arr.length; j++) {
				for (int z = 0; z < arr[0].length; z++) {
					if (arr[j][z] != 0 && arr[j][z] != -1)
						result++;
				}
			}
			System.out.println("#" + i + " " + result + "\n");
		}
	}

	static int[][] dir = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } };

	private static void solve() {
		PriorityQueue<Pair> queue = new PriorityQueue<Pair>(list);

		while (!queue.isEmpty()) {
			Pair t = queue.poll();
			if (t.state == 0 && t.flag == 1) {
				arr[t.x][t.y] = -1;
				continue;
			}
			if (t.time == 0) {
				continue;
			}
			if (t.state == 0) {
				queue.add(new Pair(t.x, t.y, t.k, t.k, t.time, 1));
			} else {
				queue.add(new Pair(t.x, t.y, t.k, t.state - 1, t.time - 1, t.flag));
				continue;
			}
			for (int i = 0; i < 4; i++) {
				int tx = t.x + dir[i][0];
				int ty = t.y + dir[i][1];
				if (tx < 0 || ty < 0 || tx >= n + k || ty >= m + k) {
					continue;
				}
				if (arr[tx][ty] != 0) {
					continue;
				}
				arr[tx][ty] = t.k;
				queue.add(new Pair(tx, ty, t.k, t.k, t.time - 1, 0));
			}
		}
	}
}

class Pair implements Comparable<Pair> {
	public int x;
	public int y;
	public int k;
	public int state; // 변하는 생명력
	public int time;
	public int flag; // 0이 되었을 때, 번식 했나 유무

	public Pair(int x, int y, int k, int state, int time, int flag) {
		this.x = x;
		this.y = y;
		this.k = k;
		this.state = state;
		this.time = time;
		this.flag = flag;
	}

	@Override
	public int compareTo(Pair o) {
		if (this.time > o.time) {
			return -1;
		} else if (this.time == o.time) {
			if (this.k > o.k) {
				return -1;
			}
			return 1;
		}
		return 1;
	}
}

'알고리즘 공부' 카테고리의 다른 글

SWEA 3462: 선표의 축구 경기 예측  (0) 2021.01.10
SWEA 1949: 등산로 조성  (0) 2021.01.07
SWEA 1267: 작업순서  (0) 2021.01.03
SWEA 5521: 상원이의 생일파티  (0) 2020.12.27
SWEA: 5215 햄버거 다이어트  (0) 2020.12.15

+ Recent posts