n개의 섬 사이에 다리를 건설하는 비용(costs)이 주어질 때, 최소의 비용으로 모든 섬이 서로 통행 가능하도록 만들 때 필요한 최소 비용을 return 하는 문제이다.

=> 최소비용신장트리 중 Prim 알고리즘 사용

 

최소비용신장트리:

2020/07/19 - [알고리즘 공부/boj] - boj 1197: 최소 스패닝 트리

 

import java.util.*;
class Solution {
	public int solution(int v, int[][] costs) {
		LinkedList<Edge>[] pair = new LinkedList[v + 1];
		HashSet<Integer> vSet = new HashSet<Integer>();
		for (int i = 0; i < v; i++) {
			pair[i] = new LinkedList<Edge>();
			vSet.add(i);
		}

		for (int i = 0; i < costs.length; i++) {
			int start = costs[i][0];
			int end = costs[i][1];
			long cost = costs[i][2];

			pair[start].add(new Edge(end, cost));
			pair[end].add(new Edge(start, cost));
		}

		int currentNode = 1;
		int answer = 0;
		vSet.remove(1);

		PriorityQueue<Edge> pq = new PriorityQueue<Edge>();
		pq.addAll(pair[currentNode]);

		while (!vSet.isEmpty()) {
			Edge minEdge = pq.poll();

			if (vSet.contains(minEdge.v)) {
				answer += minEdge.cost;
				currentNode = minEdge.v;
				vSet.remove(currentNode);
				pq.addAll(pair[currentNode]);
			}
		}
		return answer;
	}
}

class Edge implements Comparable<Edge> {
	int v;
	long cost;

	public Edge(int v, long cost) {
		this.v = v;
		this.cost = cost;
	}

	@Override
	public int compareTo(Edge o) {
		return Long.compare(this.cost, o.cost);
	}
}

문제 조건

  1. 모든 방을 적어도 한 번은 방문한다.
  2. 방을 방문하기 위한 순서가 있다.
    2-1. 이는 A번 방은 방문하기 전에 반드시 B번 방을 먼저 방문해야 한다는 의미입니다.
    2-2. 어떤 방을 방문하기 위해 반드시 먼저 방문해야 하는 방은 없거나 또는 1개 입니다.
    2-3. 서로 다른 두 개 이상의 방에 대해 먼저 방문해야 하는 방이 같은 경우는 없습니다.
    2-4. 어떤 방이 먼저 방문해야 하는 방이면서 동시에 나중에 방문해야 되는 방인 경우는 없습니다. => 사이클이 없다

문제 조건을 읽으니 가장 먼저 위상 정렬을 이용해서 풀어야겠다는 생각이 들었다.

 

풀이 로직은 아래와 같다.

1. path를 이용하여 양방향 그래프를 생성한다.

2. 양방향 그래프를 bfs를 이용하여 방향 그래프로 바꾸어 준다.

3. 그래프에 order을 적용하여 위상 정렬 알고리즘을 돌린다.

class Solution {
	public static boolean solution(int n, int[][] path, int[][] order) {
		List<Integer>[] childs = new ArrayList[n];
		for (int i = 0; i < n; i++) {
			childs[i] = new ArrayList<>();
		}

		for (int i = 0; i < path.length; i++) {
			childs[path[i][0]].add(path[i][1]);
			childs[path[i][1]].add(path[i][0]);
		}

		return isPossible(createGraph(childs), order);
	}

	public static List<Integer>[] createGraph(List<Integer>[] graph) {
		Queue<Integer> q = new LinkedList<>();
		List<Integer>[] directedGraph = new ArrayList[graph.length];
		for (int i = 0; i < directedGraph.length; i++)
			directedGraph[i] = new ArrayList<>();
		boolean[] v = new boolean[directedGraph.length];

		q.offer(0);
		v[0] = true;

		while (!q.isEmpty()) {
			int cur = q.poll();

			for (int i = 0; i < graph[cur].size(); i++) {
				int next = graph[cur].get(i);
				if (v[next])
					continue;

				directedGraph[cur].add(next);
				v[next] = true;
				q.offer(next);
			}
		}

		return directedGraph;
	}

	public static boolean isPossible(List<Integer>[] childs, int[][] order) {
		Queue<Integer> queue = new LinkedList<>();
		int[] parentNum = new int[childs.length];

		for (int i = 0; i < childs.length; i++) {
			for (Integer next : childs[i]) {
				parentNum[next]++;
			}
		}

		for (int i = 0; i < order.length; i++) {
			childs[order[i][0]].add(order[i][1]);
			parentNum[order[i][1]]++;
		}

		for (int i = 0; i < childs.length; i++) {
			if (parentNum[i] == 0) {
				queue.add(i);
			}
		}

		int cnt = 0;
		while (!queue.isEmpty()) {
			int cur = queue.poll();

			cnt++;

			for (int i = 0; i < childs[cur].size(); i++) {
				int next = childs[cur].get(i);
				parentNum[next]--;

				if (parentNum[next] == 0) {
					queue.add(next);
				}
			}
		}

		return cnt == childs.length ? true : false;
	}
}

칸의 높이차가 height 보다 큰 경우 사다리를 설치해야 하는데, 사다리 설치 비용은 두 칸의 높이차이다.

최대한 적은 비용이 들도록 사다리를 설치해서 모든 칸을 방문 하도록 하는 문제이다.

 

역시나 사다리를 설치하는 모든 경우를 구하게 되면 시간초과가 날 것이므로 다른 방법을 사용해야 한다.

 

맨 처음에 든 생각은 사다리 설치 비용을 이분탐색으로 구해두고 모든 칸을 방문 가능한지 보면 되지 않나?였다.

하지만 이 경우 사다리 설치 비용을 구한다고 하더라도 사다리의 위치, 갯수 등을 정하는 것이 쉽지 않겠다는 생각이 들었다.

 

일단 문제에서 제공하는 예시를 보도록 하자.

 

 

예시 그림에서는 일단 구역 컬러링 하고 사다리를 최소한으로 배치했다.

 

따라서 예시 그림과 마찬가지로 가장 먼저 할 일은 bfs를 돌면서 사다리를 사용하지 않고 갈 수 있는 구역을 먼저 나누는 것이다. 

 

 

그 다음으로는 구역 간을 잇는 사다리를 한개씩 배치해 주면 된다.

사다리를 배치해 줄 때에는 인접한 위치 중에서,

구역간의 차이가 최소가 되는 한 개의 사다리를 골라야 한다.

 

 

 

 

 

즉, 위 문제는 구역을 나눈후 아래와 같이 최단 거리 문제로 변하게 된다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

	public int solution(int[][] land, int height) {
		int length = land.length;
		int[][] colored = new int[length][length];

		int color = 1;
		for (int i = 0; i < length; i++) {
			for (int j = 0; j < length; j++) {
				if (colored[i][j] == 0) {
					colorLand(color++, colored, i, j, length, height, land);
				}
			}
		}

		int mat[][] = new int[color][color];
		HashSet<Integer> connections[] = new HashSet[color + 1];
		for (int i = 0; i < color; i++) {
			Arrays.fill(mat[i], Integer.MAX_VALUE);
			connections[i + 1] = new HashSet<Integer>();
		}

		minLadder(mat, colored, land, connections);

		return prim(mat, color, connections);
	}

	private int prim(int[][] mat, int length, HashSet<Integer> connections[]) {
		int sum = 0;

		PriorityQueue<Node> queue = new PriorityQueue<Node>();
		boolean[] isVisited = new boolean[length];

		queue.add(new Node(1, 0));

		while (!queue.isEmpty()) {
			Node node = queue.poll();

			if (!isVisited[node.name]) {
				isVisited[node.name] = true;
				sum += node.value;

				for (int next : connections[node.name]) {
					queue.add(new Node(next, mat[node.name][next]));
				}
			}
		}

		return sum;
	}

	private void minLadder(int mat[][], int colored[][], int land[][], HashSet<Integer> connections[]) {
		int length = colored.length;

		for (int i = 0; i < length; i++) {
			for (int j = 0; j < length; j++) {
				for (int dir = 0; dir < 4; dir++) {
					int newRow = i + dr[dir];
					int newCol = j + dc[dir];

					if (isBoundary(newRow, newCol, length) && colored[i][j] != colored[newRow][newCol]) {
						int v1 = colored[i][j];
						int v2 = colored[newRow][newCol];
						int diff = Math.abs(land[i][j] - land[newRow][newCol]);

						mat[v1][v2] = Math.min(mat[v1][v2], diff);
						mat[v2][v1] = Math.min(mat[v2][v1], diff);

						connections[v1].add(v2);
						connections[v2].add(v1);
					}
				}
			}
		}
	}

	private void colorLand(int color, int colored[][], int row, int col, int length, int height, int land[][]) {
		colored[row][col] = color;

		Queue<Position> queue = new LinkedList<Position>();
		queue.add(new Position(row, col));

		while (!queue.isEmpty()) {
			Position position = queue.poll();
			colored[position.row][position.col] = color;

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

				if (isBoundary(newRow, newCol, length) && colored[newRow][newCol] == 0 && Math.abs(land[position.row][position.col] - land[newRow][newCol]) <= height) {
					queue.add(new Position(newRow, newCol));
				}
			}
		}
	}

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

class Position {
	int row;
	int col;

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

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

class Node implements Comparable<Node> {
	int name;
	int value;

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

	@Override
	public int compareTo(Node o) {
		return Integer.compare(this.value, o.value);
	}
}

정확성: 70.0

합계: 70.0 / 100.0

시간초과 & 메모리 초과 발생 -> prim말고 kruskal을 사용하도록 해보자.

kruskal은 좀 더 뭐랄까... 띄엄띄엄 이어져있는 그래프에서 유리하다고 한다.

 

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

	public int solution(int[][] land, int height) {
		int length = land.length;
		int[][] colored = new int[length][length];

		int color = 1;
		for (int i = 0; i < length; i++) {
			for (int j = 0; j < length; j++) {
				if (colored[i][j] == 0) {
					colorLand(color++, colored, i, j, length, height, land);
				}
			}
		}

		PriorityQueue<Edge> queue = minLadderList(colored, land);

		return kruskal(queue, color);
	}

	private int kruskal(PriorityQueue<Edge> pq, int length) {
		int vSet[] = new int[length + 1];

		for (int i = 1; i <= length; i++) {
			vSet[i] = i;
		}

		int answer = 0;
		if (!pq.isEmpty()) {
			Edge firstEdge = pq.poll();
			answer += firstEdge.cost;
			union(firstEdge.v1, firstEdge.v2, vSet);
		}

		while (!isFinished(vSet) && !pq.isEmpty()) {
			Edge minEdge = pq.poll();

			int v1Contains = find(minEdge.v1, vSet);
			int v2Contains = find(minEdge.v2, vSet);

			if (v1Contains != v2Contains) {
				answer += minEdge.cost;
				union(minEdge.v1, minEdge.v2, vSet);
			}
		}
		return answer;
	}

	private PriorityQueue<Edge> minLadderList(int colored[][], int land[][]) {
		PriorityQueue<Edge> queue = new PriorityQueue<>();

		int length = colored.length;

		for (int i = 0; i < length; i++) {
			for (int j = 0; j < length; j++) {
				for (int dir = 0; dir < 4; dir++) {
					int newRow = i + dr[dir];
					int newCol = j + dc[dir];

					if (isBoundary(newRow, newCol, length) && colored[i][j] != colored[newRow][newCol]) {
						int v1 = colored[i][j];
						int v2 = colored[newRow][newCol];
						int diff = Math.abs(land[i][j] - land[newRow][newCol]);

						queue.add(new Edge(v1, v2, diff));
					}
				}
			}
		}
		return queue;
	}

	private void colorLand(int color, int colored[][], int row, int col, int length, int height, int land[][]) {
		colored[row][col] = color;

		Queue<Position> queue = new LinkedList<Position>();
		queue.add(new Position(row, col));

		while (!queue.isEmpty()) {
			Position position = queue.poll();
			colored[position.row][position.col] = color;

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

				if (isBoundary(newRow, newCol, length) && colored[newRow][newCol] == 0
						&& Math.abs(land[position.row][position.col] - land[newRow][newCol]) <= height) {
					queue.add(new Position(newRow, newCol));
				}
			}
		}
	}

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

	public static void union(int x, int y, int[] vSet) {
		int ux = find(x, vSet);
		int uy = find(y, vSet);

		vSet[uy] = ux;
	}

	public static int find(int x, int[] vSet) {
		if (vSet[x] == x) {
			return x;
		}

		vSet[x] = find(vSet[x], vSet);
		return vSet[x];
	}

	public static boolean isFinished(int[] vSet) {
		int now = vSet[1];
		for (int i = 1; i < vSet.length; i++) {
			if (vSet[i] != now) {
				return false;
			}
		}
		return true;
	}
}

class Position {
	int row;
	int col;

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

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

class Edge implements Comparable<Edge> {
	int v1, v2;
	long cost;

	public Edge(int v1, int v2, long cost) {
		this.v1 = v1;
		this.v2 = v2;
		this.cost = cost;
	}

	@Override
	public int compareTo(Edge o) {
		return Long.compare(this.cost, o.cost);
	}
}

정확성: 93.3

합계: 93.3 / 100.0

결과는 메모리 초과는 넘어갔지만,, 테케 24, 25가 시간초과가 났다.

깨달은 점 - bfs를 돌 때는 Queue에 넣어줄 때 visited를 체크해야한다.

그렇지 않으면 중복되어서 들어가는 경우가 생길 수 있고, 그 때문에 시간 초과가 생길 수 있다.

 

 

최종 코드

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

	public int solution(int[][] land, int height) {
		int length = land.length;
		int[][] colored = new int[length][length];

		int color = 1;
		for (int i = 0; i < length; i++) {
			for (int j = 0; j < length; j++) {
				if (colored[i][j] == 0) {
					colorLand(color++, colored, i, j, length, height, land);
				}
			}
		}

		PriorityQueue<Edge> queue = minLadderList(colored, land);

		return kruskal(queue, color);
	}

	private int kruskal(PriorityQueue<Edge> pq, int length) {
		int vSet[] = new int[length + 1];

		for (int i = 1; i <= length; i++) {
			vSet[i] = i;
		}

		int answer = 0;
		if (!pq.isEmpty()) {
			Edge firstEdge = pq.poll();
			answer += firstEdge.cost;
			union(firstEdge.v1, firstEdge.v2, vSet);
		}

		while (!isFinished(vSet) && !pq.isEmpty()) {
			Edge minEdge = pq.poll();

			int v1Contains = find(minEdge.v1, vSet);
			int v2Contains = find(minEdge.v2, vSet);

			if (v1Contains != v2Contains) {
				answer += minEdge.cost;
				union(minEdge.v1, minEdge.v2, vSet);
			}
		}
		return answer;
	}

	private PriorityQueue<Edge> minLadderList(int colored[][], int land[][]) {
		PriorityQueue<Edge> queue = new PriorityQueue<>();

		int length = colored.length;

		for (int i = 0; i < length; i++) {
			for (int j = 0; j < length; j++) {
				for (int dir = 0; dir < 4; dir++) {
					int newRow = i + dr[dir];
					int newCol = j + dc[dir];

					if (isBoundary(newRow, newCol, length) && colored[i][j] != colored[newRow][newCol]) {
						int v1 = colored[i][j];
						int v2 = colored[newRow][newCol];
						int diff = Math.abs(land[i][j] - land[newRow][newCol]);

						queue.add(new Edge(v1, v2, diff));
					}
				}
			}
		}
		return queue;
	}

	private void colorLand(int color, int colored[][], int row, int col, int length, int height, int land[][]) {
		colored[row][col] = color;

		Queue<Position> queue = new LinkedList<Position>();
		queue.add(new Position(row, col));

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

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

				if (isBoundary(newRow, newCol, length) && colored[newRow][newCol] == 0
						&& Math.abs(land[position.row][position.col] - land[newRow][newCol]) <= height) {
					colored[newRow][newCol] = color;
					queue.add(new Position(newRow, newCol));
				}
			}
		}
	}

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

	public static void union(int x, int y, int[] vSet) {
		int ux = find(x, vSet);
		int uy = find(y, vSet);

		vSet[uy] = ux;
	}

	public static int find(int x, int[] vSet) {
		if (vSet[x] == x) {
			return x;
		}

		vSet[x] = find(vSet[x], vSet);
		return vSet[x];
	}

	public static boolean isFinished(int[] vSet) {
		int now = vSet[1];
		for (int i = 1; i < vSet.length; i++) {
			if (vSet[i] != now) {
				return false;
			}
		}
		return true;
	}
}

class Position {
	int row;
	int col;

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

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

class Edge implements Comparable<Edge> {
	int v1, v2;
	long cost;

	public Edge(int v1, int v2, long cost) {
		this.v1 = v1;
		this.v2 = v2;
		this.cost = cost;
	}

	@Override
	public int compareTo(Edge o) {
		return Long.compare(this.cost, o.cost);
	}
}

 

출발지점부터 distance 사이에 바위를 제거했을 때, 제거한 바위의 최솟값 중에 가장 큰 값을 구하는 문제이다.

이 문제는 바위를 제거하는 모든 경우를 구한 후 최솟값을 구한다면,

바위가 N개, R개를 제거할 수 있다면 O(NCR)로 시간초과가 발생하게 된다.

 

이러한 경우에는 바위를 제거하는 모든 경우의 수를 구하기보다는,

바위간의 최소 거리를 정해두고 이 조건이 가능한지 여부를 보는 쪽으로 생각하면 된다.

 

바위간의 최소 거리를 정할 때에는 이분 탐색을 사용하도록 했다. 풀이 방식은 아래와 같다.

1. 이분 탐색으로 최대 사잇값 diff를 정한다.

2. 돌을 n개 지웠을 때 최소 거리가 diff인지 확인한다.

=> 즉, 돌 사이의 거리가 diff보다 작으면 돌을 없애고,

최종적으로 없애야 하는 돌의 수가 n보다 크면 diff를 너무 크게 잡은 것이므로 왼쪽을 탐색하도록 한다.

class Solution {
	public int solution(int distance, int[] rocks, int n) {
		int length = rocks.length;
		int start = 0;
		int end = distance;

		int answer = 0;

		Arrays.sort(rocks);

		while (start <= end) {
			int mid = (start + end) / 2;
			if (!isPossible(rocks, n, length, mid, distance)) {
				// diff가 너무 크니까 줄이자
				end = mid - 1;
			} else { // 가능하니까 일단 answer에 저장하고 더 늘려보자
				answer = Math.max(answer, mid);
				start = mid + 1;
			}
		}
		return answer;
	}

	public boolean isPossible(int[] rocks, int n, int length, int diff, int distance) {
		int remove = 0;

		int before = 0;
		for (int i = 0; i < length; i++) {

			if (rocks[i] - before < diff) { // diff보다 작으니 돌을 없애자
				remove++;
			} else {
				before = rocks[i];
			}

			if (remove > n) { // 돌을 n개보다 더 없애야 하는 경우
				return false;
			}
		}

		if (distance - before < diff) {
			remove++;
		}
		return remove <= n;
	}
}

 

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

Programmers 67260: 동굴 탐험  (0) 2021.01.17
Programmers 62050: 지형 이동  (0) 2021.01.17
Programmers 67259: 경주로 건설  (0) 2021.01.05
Programmers 12938: 최고의 집합  (0) 2021.01.05
Programmers 42898: 등굣길  (0) 2021.01.05

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;
	}
}

 

 

 

 

 

최고의 집합에 해당하는 조건은 아래와 같다.

  1. 각 원소의 합이 S가 되는 자연수의 집합
  2. 위 조건을 만족하면서 각 원소의 곱 이 최대가 되는 집합
  3. 자연수는 중복 가능

1차 시도 - 최대한 유사한 값들을 곱하면 최대가 될 것이다.

동일한 값으로 먼저 나누고, 나머지를 +1씩 해 주도록 한다.

class Solution {
	public int[] solution(int n, int s) {
		LinkedList<Integer> answer = new LinkedList<Integer>();

		if (s / n == 0) {
			answer.add(-1);
		} else {
			answer.addAll(findSimilarElements(n, s));
		}

		return answer.stream().sorted().mapToInt(i -> i).toArray();
	}

	public LinkedList<Integer> findSimilarElements(int n, int s) {
		LinkedList<Integer> list = new LinkedList<Integer>();
		int quotient = s / n;
		int remainder = s % n;

		for (int i = 0; i < remainder; i++) {
			list.add(quotient + 1);
		}
		for (int i = remainder; i < n; i++) {
			list.add(quotient);
		}

		return list;
	}
}

결과 - 시간초과

 

2차 시도 - sorting 부분을 지우자

1 <= n <= 10,000 이고 1 <= s <= 100,000,000이며 위의 경우는 sorting때문에 시간 복잡도가 O(NlogN)이었을 것이다.

아래와 같은 코드로 바꾼다면 O(N)만에 동작할 수 있다.

class Solution {
	public int[] solution(int n, int s) {
		LinkedList<Integer> answer = new LinkedList<Integer>();

		if (s / n == 0) {
			answer.add(-1);
		} else {
			answer.addAll(findSimilarElements(n, s));
		}

		return answer.stream().mapToInt(i -> i).toArray();
	}

	public LinkedList<Integer> findSimilarElements(int n, int s) {
		LinkedList<Integer> list = new LinkedList<Integer>();
		int quotient = s / n;
		int remainder = s % n;

		int quotientNumber = n - remainder;

		for (int i = 0; i < quotientNumber; i++) {
			list.add(quotient);
		}
		for (int i = quotientNumber; i < n; i++) {
			list.add(quotient + 1);
		}

		return list;
	}
}

결과는 마찬가지로 시간초과...

 

3차시도 - 아무래도 stream부분과 list를 addAll하는 곳 때문에 시간 초과가 난 것 같다.

어차피 원소의 개수는 n개로 일정하기 때문에 LinkedList를 array로 바꾸는 방식이 아니라 바로 array를 구하도록 하자

class Solution {
	public int[] solution(int n, int s) {
		if (s / n == 0) {
			return new int[] { -1 };
		}

		return findSimilarElements(n, s);
	}

	public int[] findSimilarElements(int n, int s) {
		int[] answer = new int[n];
		int quotient = s / n;
		int remainder = s % n;

		int quotientNumber = n - remainder;

		for (int i = 0; i < quotientNumber; i++) {
			answer[i] = quotient;
		}
		for (int i = quotientNumber; i < n; i++) {
			answer[i] = quotient + 1;
		}

		return answer;
	}
}

최단경로를 구하는 것이므로 길을 가는 방법은 오른쪽 또는 아래로 이동하는 방법밖에 없다.

즉, 현재 위치의 가짓수는 현재 위치의 위 또는 왼쪽에서 가능한 가짓수를 더한 값이다. => DP

 

이를 식으로 표현해 보면 DP[i][j] = DP[i-1][j] + DP[i][j-1]이다.

import java.util.*;
class Solution {
	public long solution(int m, int n, int[][] puddles) {
		long mat[][] = new long[n + 1][m + 1];
		for (int i = 1; i < mat.length; i++) {
			Arrays.fill(mat[i], -100);
			mat[i][0] = 0;
		}

		for (int i = 0; i < puddles.length; i++) {
			mat[puddles[i][1]][puddles[i][0]] = -1;
		}

		mat[1][1] = 1;

		findAll(n, m, mat);
		//printMat(mat);

		return mat[n][m];
	}

	public long findAll(int row, int col, long[][] mat) {
		if (mat[row][col] != -100) {
			return mat[row][col];
		}
		long left = findAll(row, col - 1, mat);
		long upper = findAll(row - 1, col, mat);

		if (left == -1 && upper == -1) {
			mat[row][col] = 0;
			return mat[row][col];
		}
		if (left == -1) {
			mat[row][col] = upper;
			return mat[row][col];
		}
		if (upper == -1) {
			mat[row][col] = left;
			return mat[row][col];
		}

		mat[row][col] = (left + upper) % 1000000007;
		return mat[row][col];
	}

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

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

Programmers 67259: 경주로 건설  (0) 2021.01.05
Programmers 12938: 최고의 집합  (0) 2021.01.05
Programmers 17685: 자동완성  (0) 2020.12.30
Programmers 43236: 징검다리  (0) 2020.12.26
Programmers 12907: 거스름돈  (0) 2020.12.24

풀이1) Trie 자료구조

1. Trie 자료구조 구현

    - Node 필드 정의: character, 해당 prefix를 가지는 단어의 수

2. Node를 따라가면서 해당 단어의 count = 1일때 멈춘다.

 

 

 

 

 

 

 

 

 

로직을 대충 생각해보니까 굳이 Trie 자료구조가 필요없을 것 같으나...

그래도 한번 구현해 보는 것이 좋을 것 같아서 아래와 같이 짜봤다.

 

import java.util.*;
class Solution {
	public int solution(String[] words) {
		Trie trie = new Trie();
		for (String word : words) {
			trie.insert(word);
		}

		int answer = 0;
		for (String word : words) {
			answer += trie.searchWordFromFront(word);
		}

		return answer;
	}
}

class Trie {
	private Node rootNode;

	Trie() {
		rootNode = new Node();
	}

	public void insert(String word) {
		Node node = rootNode;
		for (int i = 0; i < word.length(); i++) {
			node = node.children.computeIfAbsent(word.charAt(i), c -> new Node());
			node.count++;
		}
	}

	public int searchWordFromFront(String word) {
		Node node = rootNode;
		int move = 0;
		for (int i = 0; i < word.length(); i++) {
			node = node.children.get(word.charAt(i));
			move++;

			if (node.count == 1) {
				break;
			}
		}

		return node.count == 1 ? move : word.length();
	}
}

class Node {
	Map<Character, Node> children;
	int count;

	Node() {
		this.children = new HashMap<>();
		this.count = 0;
	}
}

 

풀이2) 정렬 후 인접하는 두 단어 비교

만약 두 단어 before, word가 0 이상 길이의 동일한 prefix를 가진다고 하면, before을 찾기 위해서는

1. before과 word가 같은 서브트리에 존재

   - before.length > prefix.length : prefix+1개 입력

   - before.length == prefix.length : prefix개 입력

2. before과 word가 다른 서브트리에 존재

   - prefix+1개 입력

class Solution {
	public int solution(String[] words) {
		Arrays.sort(words);
		String before = "";
		int beforePressCount = 1;
		int beforeSameCount = 0;
		int answer = 0;

		for (String cur : words) {
			int sameCount = 0;
			boolean isSimilar = false;

			// 이전 단어와 동일한 prefix 길이 구하기
			for (int i = 0; i < before.length(); i++) {
				if (before.charAt(i) == cur.charAt(i)) {
					sameCount++;
					isSimilar = true;
				} else {
					break;
				}
			}

			if (isSimilar && sameCount >= beforeSameCount) {

				// before 단어에 대한 입력 수 업데이트
				answer -= beforePressCount;
				if (before.length() > sameCount) {
					answer += (sameCount + 1);
				} else if (before.length() == sameCount) {
					answer += sameCount;
				}

				// word 단어에 대한 입력 수 업데이트
				beforePressCount = sameCount + 1;
				answer += beforePressCount;
			} else {
				beforePressCount = sameCount + 1;
				answer += beforePressCount;
			}

			before = cur;
			beforeSameCount = sameCount;
		}

		return answer;
	}
}

 

+ Recent posts