처음에는 단순하게 maxHeap을 이용해서 O(N)만에 풀면 되지 않을까 생각했다.

윈도우를 maxHeap에 넣고 움직이면서 맨 앞의 값은 빼고, 뒤에 새로 들어오는 값은 더해주는 방식으로 구현했다.

import java.util.*;
class Solution {
	public int[] maxSlidingWindow(int[] nums, int k) {
		int length = nums.length;
		int answer[] = new int[length - k + 1];

		PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(Collections.reverseOrder());
		for (int i = 0; i < k; i++) {
			maxHeap.add(nums[i]);
		}
		answer[0] = maxHeap.peek();

		for (int i = 1; i < length - k + 1; i++) {
			Integer remove = nums[i - 1];
			int add = nums[i + k - 1];
			maxHeap.remove(remove);
			maxHeap.add(add);

			answer[i] = maxHeap.peek();
		}

		return answer;
	}
}

 

결과는 Hard 문제 답게 TLE가 떴다...ㅠ

다시 문제 조건을 자세히 살펴봤다. 문제 조건은 아래와 같다.

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4
  • 1 <= k <= nums.length

내가 구현한 코드가 O(N)이 맞다면 nums.length<=10^5이기 때문에 시간내에 동작해야 했다.

다시 생각 해 보니 maxHeap에 넣고 빼는 동작이 logk씩 일어나기 때문에 O(NlogK)로 시간초과가 날 수 있다는 점을 생각하지 못했다.

뭔가 윈도우를 하나씩 옮기는 방법은 맞는 것 같으니, 최댓값을 구할 때 O(logK)보다 줄일 수 있는지 생각해 봤다.

 

일단은 문자열을 넣고 뺄때 힙구조가 아닌 Deque를 사용하는게 좋을 것 같았다. Deque는 맨앞과 맨뒤의 요소를 넣고 뺄 때 O(1)이 걸린다.

문제 풀이 방법은 아래와 같다.

 

1. 위의 경우와 마찬가지로 첫 k개는 Deque에 넣고 최댓값을 구한다.

2. 윈도우를 움직인다. 이 때,

  • deque에는 index를 넣어두고, 현재 윈도우 안의 요소가 될 수 있는 인덱스들만 넣어 둔다.
  • 새로 집어 넣는 값에 비해 작은 값들을 앞에서 부터 다 뺀다. 왜냐면 윈도우는 오른쪽으로 움직이고 있고, 현재 넣는 값보다 작은 기존의 deque에 있는 값은 영원히 최대값이 될 수 없기 때문이다.

 

아래는 구현한 코드이다.

import java.util.*;
class Solution {
	public void moveWindow(int i, int k, ArrayDeque<Integer> deque, int[] nums) {
		if (!deque.isEmpty() && deque.getFirst() == i - k) {
			deque.removeFirst();
		}

		while (!deque.isEmpty() && nums[i] > nums[deque.getLast()]) {
			deque.removeLast();
		}
	}

	public int[] maxSlidingWindow(int[] nums, int k) {
		ArrayDeque<Integer> deque = new ArrayDeque<Integer>();
		int length = nums.length;
		if (length * k == 0) {
			return new int[0];
		}
		if (k == 1) {
			return nums;
		}

		int maxIndex = 0;
		for (int i = 0; i < k; i++) {
			moveWindow(i, k, deque, nums);
			deque.addLast(i);

			if (nums[i] > nums[maxIndex]) {
				maxIndex = i;
			}
		}

		int[] answer = new int[length - k + 1];
		answer[0] = nums[maxIndex];

		for (int i = k; i < length; i++) {
			moveWindow(i, k, deque, nums);
			deque.addLast(i);
			answer[i - k + 1] = nums[deque.getFirst()];
		}
		return answer;
	}
}

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

leetcode: Decode String  (0) 2021.02.14
leetcode: Task Scheduler  (0) 2021.02.07
leetcode: Longest Increasing Subsequence  (0) 2021.02.07
leetcode: Jump Game II  (0) 2021.01.31
leetcode: Longest Increasing Path in a Matrix  (0) 2021.01.31

행렬 N개가 있을 때 적절히 순서를 바꿔 곱을 할 경우 연산 횟수의 최소를 구하는 문제이다.

 

예를 들어 행렬 A(a,b), B(b,c), C(c,d)가 있을 때 곱하는 경우는 두 가지가 있을 수 있다.

  • (AB)C 일 경우 연산 횟수 a*b*c + a*c*d = a*c*(b+d)
  • A(BC) 일 경우 연산 횟수 b*c*d + a*b*d = b*d*(a+c)

 

행렬 곱을 구할 때, 이전에 구한 값을 재사용 할 가능성이 있기 때문에 dp로 문제를 푸는 방향으로 생각해 봤다.

dp[i][j]: i~j까지 행렬까지의 곱 중 가장 적은 연산 횟수라고 정의한다면,

i<=k<=j인 k에 대해 dp[i][k] + dp[k][j] + i*k*j 중 최소값이 dp[i][j]가 될 것이다.

class Solution {
	public int solution(int[][] matrix_sizes) {
		int length = matrix_sizes.length;

		Matrix matrix[] = new Matrix[length + 1];
		for (int i = 1; i <= length; i++) {
			matrix[i] = new Matrix(matrix_sizes[i - 1][0], matrix_sizes[i - 1][1]);
		}
		int dp[][] = new int[length + 1][length + 1];

		for (int i = length; i > 0; i--) {
			for (int j = i + 1; j <= length; j++) {
				dp[i][j] = Integer.MAX_VALUE;
				for (int k = i; k < j; k++) {
					dp[i][j] = Math.min(dp[i][j],
							dp[i][k] + dp[k + 1][j] + (matrix[i].row * matrix[k].col * matrix[j].col));
				}
			}
		}

		return dp[1][length];
	}
}

class Matrix {
	int row, col;

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

2021/01/15 - [알고리즘 공부/boj] - boj 3745: 오름세

 

boj 3745: 오름세

이 문제에서 주의해야할 점은 아래 2가지이다. EOF 처리 최장부분증가수열을 빠르게 구하기 완전탐색 => O(2^N) 1차 시도 - `동적 계획법`을 사용하여 문제를 풀어보았다. 결과는 50%에서 시간초과. =>

sysgongbu.tistory.com

 

위 문제와 동일하다. 지난번에 푼 기억이 있어서 비교적 수월하게 풀었다.

세그먼트 트리로도 한번 구현해 봐야하는데 언제하지...

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

		lis[0] = nums[0];
		int lastIndex = 0;

		for (int i = 1; i < n; i++) {
			if (lis[lastIndex] < nums[i]) {
				lastIndex++;
				lis[lastIndex] = nums[i];
			} else {
				int index = findIndex(lis, nums[i], lastIndex);
				lis[index] = nums[i];
			}
		}
		return lastIndex + 1;
	}

	public static int findIndex(int[] lis, int target, int right) {
		int left = 0;

		while (left < right) {
			int mid = (left + right) / 2;
			if (lis[mid] == target) {
				return mid;
			}

			if (lis[mid] > target) {
				right = mid;
			} else {
				left = mid + 1;
			}
		}

		return right;
	}
}

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

leetcode: Task Scheduler  (0) 2021.02.07
leetcode: Sliding Window Maximum  (0) 2021.02.07
leetcode: Jump Game II  (0) 2021.01.31
leetcode: Longest Increasing Path in a Matrix  (0) 2021.01.31
leetcode: Target Sum  (0) 2021.01.27

1~N까지 특정한 최단 경로를 구할 때, 임의로 주어진 두 정점은 반드시 통과하도록 하는 문제이다.

일단 두 점 A, B은 무조건 통과해야 하므로 최단 경로는 `1~A~B~N` 또는 `1~B~A~N`이 될 수 있다.

 

따라서 1, A, B에서 다익스트라를 각각 돌려서 다른 점까지를 구한 다음에 두가지 최단경로를 비교하여 최솟값을 구했다.

구현 코드는 아래와 같다.

import java.util.*;

public class Main {
	public static long MAX = 800 * 200000 * 1000 + 1;

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int e = in.nextInt();
		long mat[][] = new long[n + 1][n + 1];
		for (int i = 0; i <= n; i++) {
			Arrays.fill(mat[i], MAX);
		}
		for (int i = 0; i < e; i++) {
			int start = in.nextInt();
			int end = in.nextInt();
			long weight = in.nextLong();

			mat[start][end] = Math.min(mat[start][end], weight);
			mat[end][start] = Math.min(mat[end][start], weight);
		}

		int A = in.nextInt();
		int B = in.nextInt();

		long distanceFrom1[] = dijkstra(1, n, mat);
		long distanceFromA[] = dijkstra(A, n, mat);
		long distanceFromB[] = dijkstra(B, n, mat);

		long ans = -1;

		if (distanceFrom1[A] < MAX && distanceFromA[B] < MAX && distanceFromB[n] < MAX) {
			ans = distanceFrom1[A] + distanceFromA[B] + distanceFromB[n];
		}
		if (distanceFrom1[B] < MAX && distanceFromB[A] < MAX && distanceFromA[n] < MAX) {
			ans = Math.min(ans, distanceFrom1[B] + distanceFromB[A] + distanceFromA[n]);
		}
		System.out.println(ans);
	}

	public static long[] dijkstra(int startPoint, int n, long mat[][]) {
		long[] dist = new long[n + 1];
		boolean[] isVisited = new boolean[n + 1];

		Arrays.fill(dist, MAX);

		for (int i = 1; i <= n; i++) {
			dist[i] = mat[startPoint][i];
		}
		dist[startPoint] = 0;
		isVisited[startPoint] = true;

		for (int i = 1; i < n; i++) {
			int index = -1;
			long min = MAX;
			for (int j = 1; j <= n; j++) {
				if (min > dist[j] && !isVisited[j]) {
					min = dist[j];
					index = j;
				}
			}

			if (index == -1) {
				break;
			}
			isVisited[index] = true;

			for (int j = 1; j <= n; j++) {
				if (dist[j] > dist[index] + mat[index][j]) {
					dist[j] = dist[index] + mat[index][j];
				}
			}

		}

		return dist;
	}
}

 

내가 푼 방법이 다른 스터디원보다 월등히 느려서... 나중에 코드 비교를 좀 해봐야겠다ㅠ

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

boj 2638: 치즈  (0) 2021.02.14
boj 1766: 문제집  (0) 2021.02.12
boj 11812: K진 트리  (0) 2021.02.07
boj 1027: 고층 건물  (0) 2021.02.02
boj 17071: 숨바꼭질 5  (0) 2021.01.31

k진 트리에서 두 노드 사이의 거리를 구하는 문제이다.

공통 조상문제와 유사한데, 각 노드에서 공통 조상까지의 거리를 구한 뒤 더하면 된다.

주의할 점은 노드의 수가 10^15이므로 long 타입을 사용해야 한다.

이때 k == 1인 경우는 트리의 높이가 log단위가 아니고 한 개로 쭉 나열 되어 있으므로 따로 먼저 높이 차를 리턴해주면 된다.

k진 트리를 보면서 규칙을 찾아보자

정점 long A의 부모노드가 (A-2)/k + 1임을 알 수 있다.

 

import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		long n = in.nextLong(); // 노드 수
		int k = in.nextInt(); // k진 트리
		int test = in.nextInt();

		for (int t = 0; t < test; t++) {
			long x = in.nextLong();
			long y = in.nextLong();

			if (k == 1) {
				System.out.println(Math.abs(x - y));
				continue;
			}

			long distance = 0;
			while (x != y) {
				long max = Math.max(x, y);
				y = Math.min(x, y);
				x = (max - 2) / k + 1;
				distance++;
			}
			System.out.println(distance);
		}
	}
}

 

2021/01/17 - [알고리즘 공부] - SWEA 1248: 공통조상

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

boj 1766: 문제집  (0) 2021.02.12
boj 1504:특정한 최단경로  (0) 2021.02.07
boj 1027: 고층 건물  (0) 2021.02.02
boj 17071: 숨바꼭질 5  (0) 2021.01.31
boj 1322: X와 K  (0) 2021.01.29

문제 조건

고층 빌딩 A에서 다른 고층 빌딩 B가 볼 수 있는 빌딩이 되려면,

두 지붕을 잇는 선분이 A와 B를 제외한 다른 고층 빌딩을 지나거나 접하지 않아야 한다. => 기울기를 생각해 주어야 한다.

가장 많은 고층 빌딩이 보이는 빌딩을 구하고, 거기서 보이는 빌딩의 수를 출력하는 문제이다.

 

백준에 나와있는 예시를 그려보면 아래와 같고, index=12일 때, 볼 수 있는 빌딩의 경우 선을 연결해 보면

빌딩을 기준으로 왼쪽으로 갈때나 오른쪽으로 가면서 볼 때 기울기가 커져야 한다.

15
1 5 3 2 6 3 2 6 4 2 5 7 3 1 5

import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int building[] = new int[n + 1];
		for (int i = 1; i <= n; i++) {
			building[i] = in.nextInt();
		}

		System.out.println(checkAllBuilding(building, n));
	}

	public static int checkAllBuilding(int[] building, int n) {
		int max = 0;

		for (int i = 1; i <= n; i++) {
			int sum = calcGradient(building, i, n, 1) + calcGradient(building, i, n, -1);
			max = Math.max(max, sum);
		}

		return max;
	}

	public static int calcGradient(int[] building, int index, int n, int dx) {
		if ((index == 1 && dx < 0) || (index == n && dx > 0)) {
			return 0;
		}

		int num = 1;
		int checkIndex = index + dx;
		long gradientY = building[index] - building[checkIndex];
		long gradientX = index - checkIndex;

		while (true) {
			checkIndex += dx;
			if (checkIndex <= 0 || checkIndex > n) {
				break;
			}

			long nextGradientY = building[index] - building[checkIndex];
			long nextGradientX = index - checkIndex;
			if (isPossible(gradientY, gradientX, nextGradientY, nextGradientX, dx)) {
				gradientY = nextGradientY;
				gradientX = nextGradientX;
				num++;
			}
		}
		return num;
	}

	public static boolean isPossible(long gradientY, long gradientX, long nextGradientY, long nextGradientX, int dx) {
		if (dx > 0) { // 포인터가 오른쪽으로 움직이고 있는 경우 - 기울기가 증가해야 함
			return nextGradientY * gradientX > gradientY * nextGradientX;
		}

		// 포인터가 왼쪽으로 움직이고 있는 경우 - 기울기가 감소해야 함
		return nextGradientY * gradientX < gradientY * nextGradientX;
	}
}

처음에는 double 형으로 기울기를 계산해 주었는데, double의 경우 유효숫자 개수가 정해져 있어서 이렇게 풀면 틀린댄다..

float 형은 부호 (1bit) + 지수 (8bit) + 가수 (23bit) = 32bit = 4byte로 이루어져 있다. => 유효 숫자가 6자리
double 형은 부호 (1bit) + 지수 (11bit) + 가수 (52bit) = 64 bit = 8byte로 이루어져 있다. => 유효 숫자가 15자리

 

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

boj 1504:특정한 최단경로  (0) 2021.02.07
boj 11812: K진 트리  (0) 2021.02.07
boj 17071: 숨바꼭질 5  (0) 2021.01.31
boj 1322: X와 K  (0) 2021.01.29
boj 1294: 문자열 장식  (0) 2021.01.29

문제 조건

  • num 배열 원소는 음이 아닌 정수
  • 배열의 마지막 요소까지 점프해서 무조건 도달 가능 => 0이 아닌 이상 1칸씩 가면 되니까
  • 배열의 각 요소는 해당 위치에서 최대 점프 길이를 나타낸다.
class Solution {
	public int jump(int[] nums) {
		int n = nums.length;
		if (n < 2) {
			return 0;
		}

		int maxPosition = nums[0];
		int maxLength = nums[0];

		int jumps = 1;
		for (int i = 1; i < n; ++i) {
			if (maxLength < i) {
				jumps++;
				maxLength = maxPosition;
			}
			maxPosition = Math.max(maxPosition, nums[i] + i);
		}

		return jumps;
	}
}

먼저, 브루트포스로 모든 점에 대해 DFS를 돌려 가장 긴 path를 찾는 방법이 있을 것이다. 하지만 이 방법의 경우 시간초과가 발생한다.

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

	public int longestIncreasingPath(int[][] matrix) {
		if (matrix.length == 0) {
			return 0;
		}

		int maxRow = matrix.length;
		int maxCol = matrix[0].length;

		int answer = 0;
		for (int i = 0; i < maxRow; ++i)
			for (int j = 0; j < maxCol; ++j)
				answer = Math.max(answer, dfs(matrix, i, j, maxRow, maxCol));
		return answer;
	}

	private int dfs(int[][] matrix, int row, int col, int maxRow, int maxCol) {
		int ans = 0;
		for (int i = 0; i < 4; i++) {
			int r = row + dr[i];
			int c = col + dc[i];
			if (isBoundary(r, c, maxRow, maxCol) && matrix[r][c] > matrix[row][col]) {
				ans = Math.max(ans, dfs(matrix, r, c, maxRow, maxCol));
			}
		}
		return ++ans;
	}

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

 

 

위 코드에서 시간을 줄일 수 있는 부분은 어디일까? 위 코드에서 중복되는 부분은 dfs로 계산하여 max를 업데이트 하는 부분이다.

이 부분을 줄이기 위해 모든 점에 대해 가장 긴 path를 찾는 도중에 이미 갔던 길의 경우는 저장해 두고,

다시 방문할 때 저장했던 길이를 바로 리턴해주면 더 효율적일 것이다. => 메모이제이션

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

	public int longestIncreasingPath(int[][] matrix) {
		if (matrix.length == 0) {
			return 0;
		}

		int maxRow = matrix.length;
		int maxCol = matrix[0].length;

		int[][] memo = new int[maxRow][maxCol];

		int answer = 0;
		for (int i = 0; i < matrix.length; ++i) {
			for (int j = 0; j < matrix[i].length; ++j) {
				answer = Math.max(answer, dfs(matrix, memo, i, j, maxRow, maxCol));
			}
		}
		return answer;
	}

	public int dfs(int[][] matrix, int[][] memo, int row, int col, int maxRow, int maxCol) {
		if (memo[row][col] != 0) {
			return memo[row][col];
		}

		for (int i = 0; i < 4; i++) {
			int r = row + dr[i];
			int c = col + dc[i];
			if (isBoundary(r, c, maxRow, maxCol) && matrix[r][c] > matrix[row][col]) {
				memo[row][col] = Math.max(memo[row][col], dfs(matrix, memo, r, c, maxRow, maxCol));
			}
		}

		memo[row][col]++;
		return memo[row][col];
	}

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

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

leetcode: Longest Increasing Subsequence  (0) 2021.02.07
leetcode: Jump Game II  (0) 2021.01.31
leetcode: Target Sum  (0) 2021.01.27
leetcode: Couples Holding Hands  (0) 2021.01.24
leetcode: Binary Tree Maximum Path Sum  (0) 2021.01.24

+ Recent posts