설치 조건

  • 기둥
    • 바닥 위에 있거나 (y=0)
    • 보의 한쪽 끝 부분 위에 있거나 다른 기둥 위에 있어야 한다 (아래와 이어져있다)
    • 한쪽 끝 부분이 기둥 위에 있거나 또는 양쪽 끝 부분이 다른 보와 동시에 연결되어 있어야 한다. (왼쪽이나 오른쪽과 이어져있다)
  • 기둥과 보는 길이가 1인 선분으로 표현된다.

 

예시

https://programmers.co.kr/learn/courses/30/lessons/60061

  1. (1, 0)에서 위쪽으로 기둥을 하나 설치 후, (1, 1)에서 오른쪽으로 보를 하나 만듭니다.
  2. (2, 1)에서 위쪽으로 기둥을 하나 설치 후, (2, 2)에서 오른쪽으로 보를 하나 만듭니다.
  3. (5, 0)에서 위쪽으로 기둥을 하나 설치 후, (5, 1)에서 위쪽으로 기둥을 하나 더 설치합니다.
  4. (4, 2)에서 오른쪽으로 보를 설치 후, (3, 2)에서 오른쪽으로 보를 설치합니다.

만약 (4, 2)에서 오른쪽으로 보를 먼저 설치하지 않고, (3, 2)에서 오른쪽으로 보를 설치하려 한다면 2번 규칙에 맞지 않으므로 설치가 되지 않습니다.

기둥과 보를 삭제하는 기능도 있는데 기둥과 보를 삭제한 후에 남은 기둥과 보들 또한 위 규칙을 만족해야 한다.

만약, 작업을 수행한 결과가 조건을 만족하지 않는다면 해당 작업은 무시된다.

 

 

어떻게 풀지 생각해 보다가 구조물을 추가할 수 있는지 여부를 보는 함수 addValid을 만들어 두고,

구조물을 제거할 때에는 구조물을 일단 제거한 뒤, 주위에 있는 기둥과 보를 다시 추가할 때 조건에 맞는지 여부를 addValid를 이용해서 보도록 했다.

이렇게 풀면 조건에 대해 세세하게 생각할 필요 없이 addValid 함수만 호출하면 되어서 쉽게 문제를 풀 수 있다.

import java.util.*;
class Solution {
	public static int HORIZONTAL = 1;
	public static int VERTICAL = 0;

	public static int ADD = 1;
	public static int DELETE = 0;

	public int[][] solution(int n, int[][] build_frame) {
		TreeSet<Structure> set = new TreeSet<Structure>();

		for (int i = 0; i < build_frame.length; i++) {
			int x = build_frame[i][0];
			int y = build_frame[i][1];
			boolean isVertical = build_frame[i][2] == VERTICAL ? true : false;
			boolean isAdd = build_frame[i][3] == ADD ? true : false;

			if (!isAdd) {
				removeValid(x, y, isVertical, set);
			} else {
				addValid(x, y, isVertical, set);
			}
		}

		int[][] answer = new int[set.size()][3];
		int index = 0;
		for (Structure s : set) {
			answer[index][0] = s.x;
			answer[index][1] = s.y;
			answer[index][2] = s.structure;
			index++;
		}
		return answer;
	}

	public void removeValid(int x, int y, boolean isVertical, TreeSet<Structure> set) {
		if (isVertical) { // 기둥
			if (set.contains(new Structure(x, y + 1, VERTICAL))) {
				// 제거할 기둥 위에 기둥이 있는 경우
				set.remove(new Structure(x, y, VERTICAL));
				if (!addValid(x, y + 1, true, set)) {
					set.add(new Structure(x, y, VERTICAL));
					return;
				}
			}

			if (set.contains(new Structure(x, y + 1, HORIZONTAL))) {
				// 기둥 위에 오른쪽 보가 지탱이 안되는 경우
				set.remove(new Structure(x, y, VERTICAL));
				if (!addValid(x, y + 1, false, set)) {
					set.add(new Structure(x, y, VERTICAL));
					return;
				}
			}

			if (set.contains(new Structure(x - 1, y + 1, HORIZONTAL))) {
				// 기둥 위에 왼쪽 보가 지탱이 안되는 경우
				set.remove(new Structure(x, y, VERTICAL));
				if (!addValid(x - 1, y + 1, false, set)) {
					set.add(new Structure(x, y, VERTICAL));
					return;
				}
			}

			set.remove(new Structure(x, y, VERTICAL));
			return;
		}

		// 보
		if (set.contains(new Structure(x, y, VERTICAL))) {
			// 보 위에 기둥이 있는 경우
			set.remove(new Structure(x, y, HORIZONTAL));
			if (!addValid(x, y, true, set)) {
				set.add(new Structure(x, y, HORIZONTAL));
				return;
			}
		}
		if (set.contains(new Structure(x + 1, y, VERTICAL))) {
			// 보 오른쪽 위에 기둥이 있는 경우
			set.remove(new Structure(x, y, HORIZONTAL));
			if (!addValid(x + 1, y, true, set)) {
				set.add(new Structure(x, y, HORIZONTAL));
				return;
			}
		}

		if (set.contains(new Structure(x + 1, y, HORIZONTAL))) {
			// 오른쪽 보와 연결되어있는 보에 기둥이 없는 경우
			set.remove(new Structure(x, y, HORIZONTAL));
			if (!addValid(x + 1, y, false, set)) {
				set.add(new Structure(x, y, HORIZONTAL));
				return;
			}
		}

		if (set.contains(new Structure(x - 1, y, HORIZONTAL))) {
			// 왼쪽 보와 연결되어있는 보에 기둥이 없는 경우
			set.remove(new Structure(x, y, HORIZONTAL));
			if (!addValid(x - 1, y, false, set)) {
				set.add(new Structure(x, y, HORIZONTAL));
				return;
			}
		}

		set.remove(new Structure(x, y, HORIZONTAL));
		return;
	}

	public boolean addValid(int x, int y, boolean isVertical, TreeSet<Structure> set) {
		if (isVertical) { // 기둥
			if (y == 0) {
				// 바닥 위에 있는 경우
				set.add(new Structure(x, y, VERTICAL));
				return true;
			}

			if (set.contains(new Structure(x - 1, y, HORIZONTAL)) || set.contains(new Structure(x, y, HORIZONTAL))) {
				// 보의 한쪽 끝 부분 위에 있는 경우
				set.add(new Structure(x, y, VERTICAL));
				return true;
			}

			if (set.contains(new Structure(x, y - 1, VERTICAL))) {
				// 다른 기둥 위에 있는 경우
				set.add(new Structure(x, y, VERTICAL));
				return true;
			}

			return false;
		}

		// 보
		if (set.contains(new Structure(x + 1, y, HORIZONTAL)) && set.contains(new Structure(x - 1, y, HORIZONTAL))) {
			// 양쪽 끝 부분이 다른 보와 동시에 연결
			set.add(new Structure(x, y, HORIZONTAL));
			return true;
		}
		if (set.contains(new Structure(x, y - 1, VERTICAL)) || set.contains(new Structure(x + 1, y - 1, VERTICAL))) {
			// 한쪽 끝 부분이 기둥 위에 있는 경우
			set.add(new Structure(x, y, HORIZONTAL));
			return true;
		}

		return false;
	}
}

class Structure implements Comparable<Structure> {
	int x, y;
	int structure;

	public Structure(int x, int y, int structure) {
		this.x = x;
		this.y = y;
		this.structure = structure;
	}

	@Override
	public int compareTo(Structure o) {
		if (x == o.x) {
			if (y == o.y) {
				return Integer.compare(structure, o.structure);
			}
			return Integer.compare(y, o.y);
		}
		return Integer.compare(x, o.x);
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (!(o instanceof Structure)) {
			return false;
		}
		Structure s = (Structure) o;
		return x == s.x && y == s.y && structure == s.structure;
	}

	@Override
	public int hashCode() {
		return Objects.hash(x, y, structure);
	}

	@Override
	public String toString() {
		if (structure == 1) {
			return "(" + x + ", " + y + ") 보";
		}
		return "(" + x + ", " + y + ") 기둥";
	}
}

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

 

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

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단계에서는 음높이가 세 배가 되며, 2단계와 3단계에서 음높이가 각각 1씩 증가할 경우 3단고음 가능, 즉 *++ 형태만 가능하다.
    • 즉, 문자열은 항상 ++로 끝난다
  • 3단 점프를 한 후에는 음계를 한 단계씩 두 번을 더 높게 내야한다. 하지만 굳이 연속으로 1단 점프를 할 필요는 없다.
    • 예) *+*+++

 

 

n= 41일 때 answer=2이다. 예시를 보자

41 = (~~~)++

39 = (~~~)

 

39를 만드는 방법은 아래와 같다.

38+

37++

36+++

12*+++, 35+++

11+*++, 34++++, (4**+++의 경우는 불가)

...

class Solution {
	public int solution(int n) {
		return check(n - 2, 2);
	}

	private int check(int n, int p) { // n이 현재 값 , p가 plus의 개수
		if (n == 3 && p == 2) {
			return 1;
		}
		if (n < 3 || Math.log(n) / Math.log(3) * 2 < p) {
			return 0;
		}
		if (n == 3 && p == 3) {
			return 0;
		}

		return check(n - 1, p + 1) + (n % 3 == 0 && p > 1 ? check(n / 3, p - 2) : 0);
	}
}

커플 (2N-2, 2N-1)가 인접해서 앉고 싶은데, 그렇지 못하다면 최소로 swap하여 인접하게 앉을 수 있도록 하는 문제이다.

 

커플은 (2N-2, 2N-1)으로 (0,1), (2,3), (4,5), ...이므로 x의 파트너는 x가 짝수인 경우 x+1, 홀수인 경우 x-1이다.

먼저 array를 0부터 2자리씩 보기 시작하면서 해당 파트너와 인접하면 넘어가고,

인접하지 않으면 짝은 옆으로 데려와서 앉힌다.

 

즉, 소파에 첫번째 앉아있는 사람은 고정시키고 두번째 사람만 옮김으로써 커플을 완성하도록 한다. 

시간복잡도는 O(N^2)이고 코드는 아래와 같다.

class Solution {
	public int minSwapsCouples(int[] row) {
		int ans = 0;

		for (int i = 0; i < row.length; i += 2) {
			int x = row[i];

			if (row[i + 1] == findPartner(x)) {
				continue;
			}

			ans++;

			for (int j = i + 1; j < row.length; ++j) {
				if (row[j] == findPartner(x)) {
					row[j] = row[i + 1];
					row[i + 1] = findPartner(x);
					break;
				}
			}
		}
		return ans;
	}

	public int findPartner(int x) {
		if (x % 2 == 0) {
			return x + 1;
		}
		return x - 1;
	}
}

이 문제는 트리를 어떤 방식으로든 순회했을 때, 그 합의 최대를 구하는 문제이다.

https://leetcode.com/problems/binary-tree-maximum-path-sum/

예를 들어 위 예제의 경우 최적의 순회 방법중 하나는 15 -> 20 -> 7 이고 그 합은 15 + 20 + 7 = 42이다.

 

먼저 저번에 공부했던 tree dp처럼 어떤 노드를 기준으로 해당 서브 트리의 최대값을 구할 수 있도록 하자고 생각했다.

즉, 리프 노드의 경우는 해당 노드에 적혀있는 수를 리턴하고,

그 이외는 아래에서부터 올라오면서 최대 값을 해당 노드에 저장하는 식이다.

 

이러한 경우 시간복잡도는 O(N)으로 볼 수 있으며 풀이는 아래와 같다.

class Solution {
	int answer = Integer.MIN_VALUE;

	public int findMaxNode(TreeNode node) {
		if (node == null) {
			return 0;
		}

		int leftValue = Math.max(findMaxNode(node.left), 0);
		int rightValue = Math.max(findMaxNode(node.right), 0);

		int newValue = node.val + leftValue + rightValue;

		answer = Math.max(answer, newValue);

		return node.val + Math.max(leftValue, rightValue);
	}

	public int maxPathSum(TreeNode root) {
		findMaxNode(root);
		return answer;
	}
}

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

leetcode: Target Sum  (0) 2021.01.27
leetcode: Couples Holding Hands  (0) 2021.01.24
leetcode: Maximum Frequency Stack  (0) 2021.01.23
leetcode: Range Sum Query - Mutable  (0) 2021.01.10
leetcode: 01 Matrix  (0) 2021.01.08

문제 조건

회전판에 먹어야 할 1~N의 음식이 있다. 각 음식을 섭취하는데 일정 시간이 소요된다.

  • 무지는 1번 음식부터 먹기 시작하며, 회전판은 번호가 증가하는 순서대로 음식을 무지 앞으로 가져다 놓는다.
  • 마지막 번호의 음식을 섭취한 후에는 회전판에 의해 다시 1번 음식이 무지 앞으로 온다. => 원형 큐
  • 무지는 음식 하나를 1초 동안 섭취한 후 남은 음식은 그대로 두고, 다음 음식을 섭취한다.

무지가 먹방을 시작한 지 K 초 후에 방송이 잠시 중단되었는데, 다시 방송을 이어갈 때 섭취할 음식의 번호를 구하는 문제이다.

 

먼저 k가 음식의 갯수보다 큰지 작은지 생각해 보아야 한다.

  • k < food_times.length: (k+1) 번째 음식을 무조건 리턴
  • k > food_times.length: 맨 처음 들어있는 음식의 시간만큼 사이클을 돌 수 있는 경우
    • 한 cycle을 돌 수 있는 경우: k 시간에서 사이클을 도는 시간만큼 바로 빼준다
    • 한 cycle 전에 끝나는 경우: 큐에 있는 index를 크기순으로 정렬했을 때 몇번째 위치를 리턴해야 하는지 생각해본다

 

음.. 글로 설명하니까 좀 어려워 보이는데,,,,

프로그래머스 질문하기에 나와있는 예시를 한 번 시뮬레이션 해 보면

food_times=[4,2,3,6,7,1,5,8] k=27 => answer = 5

 

food = [(0, 4), (1, 2), (2, 3), (3, 6), (4, 7), (5, 1), (6, 5), (7, 8)] 이고 가장 작은 값을 가진 1만큼의 사이클을 돈다고 하면 1*8=8 => k-8=19

food = [(0, 4), (1, 2), (2, 3), (3, 6), (4, 7), (6, 5), (7, 8)] 그 다음 가장 작은 2-1 = 1만큼의 사이클을 돌면 1*7=7 => 19-7=12

food = [(0, 4), (2, 3), (3, 6), (4, 7), (6, 5), (7, 8)] 그 다음 가장 작은 3-2 = 1만큼의 사이클을 돌면 1*6=6 => 12-6=6

food = [(0, 4), (3, 6), (4, 7), (6, 5), (7, 8)] 그 다음 가장 작은 4-3 = 1만큼의 사이클을 돌면 1*5=5 => 6-5=1

food = [(3, 6), (4, 7), (6, 5), (7, 8)] 마지막으로 사이클을 온전히 다 돌지 못하기 때문에 1+1번째 요소를 찾으면 (4,7)이고 해당 answer=5이다.

 

즉, 한 개의 사이클을 온전히 돌 수 있다면 그 요소는 결국 리스트에서 사라지기 때문에 위치가 영향을 주지 않고,

food_times가 가장 작은 순으로 사이클을 돌아야 하기 때문에 자료구조는 priority queue가 적합하다.

 

한 개의 사이클을 온전히 돌 수 없다면 맨 처음 들어왔던 food_times의 인덱스 순으로 순회한다.

 

아래는 그 풀이이다.

import java.util.*;
import java.util.stream.Collectors;
class Solution {
	public int solution(int[] food_times, long k) {
		int length = food_times.length;

		if (k < length) {
			return (int) (k % length) + 1;
		}

		PriorityQueue<Food> queue = new PriorityQueue<Food>();
		for (int i = 0; i < food_times.length; i++) {
			queue.add(new Food(i, food_times[i]));
		}

		long minus = 0;
		while (!queue.isEmpty()) {
			Food now = queue.peek();
			long cycle = (long) (now.left - minus) * queue.size();
			if (k - cycle >= 0) { // 한바퀴
				k -= cycle;
				minus = now.left;
				queue.poll();

			} else { // 중간에 멈춤
				k %= queue.size();
				return queue.stream().mapToInt(food -> food.index).sorted().skip(k).findFirst().getAsInt() + 1;
			}
		}

		return -1;
	}
}

class Food implements Comparable<Food> {
	int index;
	int left;

	public Food(int index, int left) {
		this.index = index;
		this.left = left;
	}

	@Override
	public int compareTo(Food o) {
		return Integer.compare(left, o.left);
	}

	@Override
	public String toString() {
		return "(" + index + ", " + left + ")";
	}
}

  

 

FreqStack has two functions:

  • push(int x), which pushes an integer x onto the stack.
  • pop(), which removes and returns the most frequent element in the stack.
    • If there is a tie for most frequent element, the element closest to the top of the stack is removed and returned.

두가지 자료구조를 사용한다.

  • frequency: (key, value) -> 값 key에 대한 빈도수 value를 저장한 자료구조
  • frequencyValueStack: (key, stack) -> 빈도수 key에 대해 값 stack을 저장한 자료구조
  • maxfrequency: 가장 많은 빈도수를 저장
import java.util.*;
class FreqStack {
	Map<Integer, Integer> frequency;
	Map<Integer, Stack<Integer>> frequencyValueStack;
	int maxfreq;

	public FreqStack() {
		frequency = new HashMap<Integer, Integer>();
		frequencyValueStack = new HashMap<Integer, Stack<Integer>>();
		maxfreq = 0;
	}

	public void push(int x) {
		int xFrequency = frequency.getOrDefault(x, 0) + 1;
		frequency.put(x, xFrequency);
		if (xFrequency > maxfreq) {
			maxfreq = xFrequency;
		}

		/*
		 * implement a multi-value map, Map<K,Collection<V>>, 
		 * supporting multiple values per key: 
		 * map.computeIfAbsent(key, k -> new HashSet<V>()).add(v);
		 */
		frequencyValueStack.computeIfAbsent(xFrequency, stack -> new Stack<Integer>()).push(x);
	}

	public int pop() {
		int x = frequencyValueStack.get(maxfreq).pop();
		
		frequency.put(x, frequency.get(x) - 1);
		if (frequencyValueStack.get(maxfreq).size() == 0) {
			maxfreq--;
		}
		return x;
	}
}

 

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

leetcode: Couples Holding Hands  (0) 2021.01.24
leetcode: Binary Tree Maximum Path Sum  (0) 2021.01.24
leetcode: Range Sum Query - Mutable  (0) 2021.01.10
leetcode: 01 Matrix  (0) 2021.01.08
leetcode: Number of Islands  (0) 2021.01.07

문제 조건 

  1. 진열된 모든 종류의 보석을 적어도 1개 이상 포함하는 가장 짧은 연속된 구간을 찾아서 구매
  2. 가장 짧은 구간이 여러 개라면 시작 진열대 번호가 가장 작은 구간을 리턴

 

 

연속된 구간을 보는 것이기 때문에 투 포인터를 사용하여 풀면 된다고 생각했다.

진열대 위치를 쭉 훑으면서 맵에 저장되어있는 보석의 수가 1보다 크면 시작 위치를 하나씩 땡기는 방법으로 생각하고 문제를 풀었다. 

import java.util.*;
class Solution {
	public int[] solution(String[] gems) {
		int[] answer = new int[2];

		HashMap<String, Integer> selects = new HashMap<String, Integer>();
		HashSet<String> allGems = new HashSet<String>();

		int length = gems.length;
		answer[0] = 1;
		answer[1] = length;

		for (int i = 0; i < length; i++) {
			allGems.add(gems[i]);
		}
		int size = allGems.size();

		int start = 0;
		for (int end = 0; end < length; end++) {
			selects.put(gems[end], selects.getOrDefault(gems[end], 0) + 1);

			while (selects.getOrDefault(gems[start], 0) > 1) {
				selects.replace(gems[start], selects.get(gems[start]) - 1);
				start++;
			}

			if (selects.size() == size && answer[1] - answer[0] + 1 > end - start + 1) {
				answer[1] = end + 1;
				answer[0] = start + 1;
			}
		}

		return answer;
	}
}

+ Recent posts