문제 조건

고층 빌딩 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

수빈이는 현재 점 N(0 ≤ N ≤ 500,000)에 있고, 동생은 점 K(0 ≤ K ≤ 500,000)에 있다.

  • 수빈이가 움직이는 방법: X - 1, X + 1, 2 * X
  • 동생이 움직이는 방법: K + 1 + 2 + 3 + ... + i
  • 수빈이가 0보다 작은 좌표로, 50만보다 큰 좌표로 이동하는 것은 불가능하다.

 

수빈이가 왼쪽한번 오른쪽한번 이동하게 되면 제자리로 돌아오기 때문에, 짝수/홀수 시간마다 갈 수 있는 곳을 나누어 시간을 저장한다.

만약 동생이 지나가는 좌표값에 수빈이가 처음 도착하는 시간이 더 빠를 경우 답을 업데이트 해 주도록 한다.

import java.util.*;

public class Main {
	public static int INF = 500000;

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);

		int indexSubin = in.nextInt();
		int indexSister = in.nextInt();

		int isVisited[][] = new int[INF + 1][2];
		for (int i = 0; i <= INF; i++) {
			isVisited[i][0] = -1;
			isVisited[i][1] = -1;
		}

		moveSubin(isVisited, indexSubin);
		System.out.println(moveSister(isVisited, indexSister));
	}

	public static void moveSubin(int[][] isVisited, int start) {
		Queue<Move> queue = new LinkedList<Move>();
		queue.add(new Move(start, 0));
		isVisited[start][0] = 0;

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

			int nextTime = now.time + 1;
			checkBoundaryAndPushQueue(isVisited, queue, now.index - 1, nextTime);
			checkBoundaryAndPushQueue(isVisited, queue, now.index + 1, nextTime);
			checkBoundaryAndPushQueue(isVisited, queue, now.index * 2, nextTime);
		}
	}

	public static void checkBoundaryAndPushQueue(int[][] isVisited, Queue<Move> queue, int index, int time) {
		if (isBoundary(index) && isVisited[index][time % 2] == -1) {
			queue.add(new Move(index, time));
			isVisited[index][time % 2] = time;
		}
	}

	public static int moveSister(int[][] isVisited, int start) {
		for (int time = 0; time < INF; time++) {
			int index = start + time * (time + 1) / 2;

			if (index > INF) {
				return -1;
			}

			if (isVisited[index][time % 2] != -1 && isVisited[index][time % 2] <= time) {
				return time;
			}
		}
		return -1;
	}

	public static boolean isBoundary(int index) {
		if (index < 0) {
			return false;
		}
		if (index > INF) {
			return false;
		}
		return true;
	}
}

class Move {
	int index;
	int time;

	public Move(int index, int time) {
		this.index = index;
		this.time = time;
	}

	@Override
	public String toString() {
		return "(" + index + ": " + time + ")";
	}
}

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

boj 11812: K진 트리  (0) 2021.02.07
boj 1027: 고층 건물  (0) 2021.02.02
boj 1322: X와 K  (0) 2021.01.29
boj 1294: 문자열 장식  (0) 2021.01.29
boj 16637: 괄호 추가하기  (0) 2021.01.27

1. 똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.

특히 불변 객체(아이템 17)는 언제든 재사용할 수 있다.

 

불변 객체 String 예시를 보면 아래와 같다.

String s = new String ("effective java"); // (1)실행될 때마다 String 인스턴스를 생성
String s = "effective java"; // (2)새로운 인스턴스를 매번 만드는 대신 하나의 String 인스턴스를 사용

새로운 인스턴스를 만드는 대신 하나의 인스턴스를 재사용하는 2번 방식의 경우,

똑같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재사용함이 보장된다.

 

 

생성자 대신 정적 팩터리 매서드(아이템 1)를 제공하는 불변 클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다.

(생성자는 호출할 때마다 새로운 객체를 만들지만, 팩터리 메서드는 재사용하기 때문)

불변 객체만이 아니라 가변 객체라 해도 사용 중에 변경 되지 않을 것임을 안다면 재사용할 수 있다.

 

 

2. 생성 비용이 비싼 경우 + 반복해서 필요한 경우 객체를 캐싱하여 재사용하는 것이 좋다.

ex) Pattern: 입력받은 정규표현식에 해당하는 유한 상태머신(finite state machine)을 만들기 때문에 인스턴스 생성 비용이 높다.

따라서 필요한 정규표현식을 표현하는 불변 Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱해두고,

나중에 isRomanNumeral 메서드가 호출될 때마다 이 인스턴스를 재사용하도록 하는 것이 좋다.

public class RomanNumber {

    private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
    }

//  static boolean isRomanNumeral(String s) { 
//      // 메서드가 내부에서 만드는 정규표현식용 Pattern 인스턴스는, 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 됨
//      return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
//  }
}

 

c.f. 클래스가 초기화된 후 ROMAN 필드를 사용하지 않으면 낭비이기 때문에 지연 초기화(lazy initialization, 아이템 83) 하는 방법도 있다.

하지만 지연 초기화는 코드를 복잡하게 만들고 성능을 크게 개선시키지는 않는다고 한다(아이템 67)

 

 

객체가 불변이라면 재사용하면 되지만 불변이 아닌 상황은 어떨까?

3. Adapter Pattern에서 Adapter는 실제 작업은 뒷단 객체에 위임하고, 자신은 제2의 인터페이스 역할을 해주는 객체다.

즉, 어댑터는 뒷단 객체만 관리하면 되기 때문에, 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분하다.

 

ex) Map::keyset - Map 객체 안의 키 전부를 담은 Set 뷰를 반환한다.

keyset을 호출할 때마다 새로운 Set 인스턴스가 만들어지지 않고, 매번 같은 Set 인스턴스를 반환한다. 따라서 keySet 객체 중 하나를 수정하면 다른 모든 객체가 따라서 바뀐다.

//HashMap.java
    transient Set<K>        keySet;
    
    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }

 

 

4. 오토박싱(auto boxing): 오토박싱은 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술이다.

불필요한 박싱/언박싱은 불필요한 메모리 할당, 재할당을 반복할 수 있으며 성능상 좋지 않기 때문에 꼭 박싱 타입이 필요한 경우가 아니라면 기본타입을 사용하는 것이 좋다(아이템 61)

 

 

 

주의할 점은 방어적 복사가 필요한 상황에서 객체를 재사용했을 때의 피해가, 필요 없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다는 점이다.

즉, 이번 아이템의 주의점은 "객체 생성은 비싸니 피해야 한다" (X)

=> 아주 무거운 객체가 아닌 다음에야 단순히 객체 생성을 피하고자 객체 풀(pool)을 만드는 것이 좋은것만은 아니다. 일반적으로는 자체 객체 풀은 코드를 헷갈리게 만들고 메모리 사용량을 늘리고 성능을 떨어뜨린다.

따라서 언제 객체를 재사용하는 것이 좋고 이를 실제 코드에 적용해야할 것인지는 많은 생각을 해 봐야겠다.

 

 

용어

  • 리터럴: 소스 코드의 고정된 값을 대표하는 용어다. 리터럴과 대조적으로, 고정된 값을 가질 수 있는 변수나 변경되지 않는 상수가 있다.
  • Adapter pattern
  • 방어적 복사(아이템50): 생성자를 통해 초기화 할 때, 새로운 객체로 복사해주는 방법

 

두 자연수 X와 K가 주어진다. 그러면, 다음 식을 만족하는 K번째로 작은 자연수 Y를 구하는 문제이다.

X + Y = X | Y

 

| 가 OR 비트연산이므로 2진수로 생각해보자.

 

예제 X = 5 = 101(2)

101 + Y = 101 | Y 의 경우 X의 0이 있는 부분이 1인 경우 Y가 된다. 가능한 Y값을 크기순으로 나열하면 아래와 같다.

문제 풀이 방법은 위와 같이 X가 1, 0인 부분을 저장해 두고 1인 부분은 건너뛰면서 다음으로 가능한 수를 찾아준다.

y 값은 1을 넘어갈 수 있다고 했으니 long 범위로 설정해준다면 8byte = 64bit일 것이고, 이를 배열에 담아준다.

 

x, k를 2진수로 바꿔서 배열에 거꾸로 저장한 후 0인 비트를 해당 자리 수 만큼 쉬프트한다.

이를 구현한 코드는 아래와 같다.

import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		long x = in.nextLong();
		long k = in.nextLong();

		int binX[] = new int[65];
		int binK[] = new int[65];

		int i = 0;

		while (x != 0 || k != 0) {
			binX[i] = (int) (x % 2);
			binK[i] = (int) (k % 2);
			x /= 2;
			k /= 2;
			i++;
		}

		int xIndex = 0, kIndex = 0;
		long y = 0;

		while (kIndex < i) {
			if (binX[xIndex] == 0) {
				y |= (1L << xIndex) * binK[kIndex++];
			}
			xIndex++;
		}

		System.out.println(y);
	}
}

 

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

boj 1027: 고층 건물  (0) 2021.02.02
boj 17071: 숨바꼭질 5  (0) 2021.01.31
boj 1294: 문자열 장식  (0) 2021.01.29
boj 16637: 괄호 추가하기  (0) 2021.01.27
boj 1167: 트리의 지름  (0) 2021.01.25

문자열을 적절히 쪼갠 후 하나로 합칠 때, 사전상으로 가장 앞에 오는 단어를 구하는 문제이다.

한 문자내의 쪼갠 문자 순서는 바뀌면 안된다

 

먼저 아이디어는 각 단어를 가리키는 포인터가 각각 존재하며,

맨 앞부분만 서로 비교했을 때 가장 작은 값을 stringbuilder에 하나씩 붙인다.

 

처음에는 단순히 사전순, 길이순으로 정렬해서 풀었는데, 이 경우는 ABCCC, BACCC인 경우 ABABCCCCCC 예외케이스가 나온다.

이는 BAA, BA 테스트 케이스를 해결하기 위해 문자열의 길이를 함께 봤기 때문에 생긴 예외케이스이다.

import java.util.*;

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

		List<String> string = new ArrayList<String>();
		int index[] = new int[n];
		int length[] = new int[n];

		PriorityQueue<Alphabet> queue = new PriorityQueue<Alphabet>();
		for (int i = 0; i < n; i++) {
			string.add(in.next());
		}
		string.sort(Comparator.comparing(String::length).reversed().thenComparing(String::compareTo));

		for (int i = 0; i < n; i++) {
			queue.add(new Alphabet(i, string.get(i).charAt(index[i])));
			length[i] = string.get(i).length();
		}

		StringBuilder sb = new StringBuilder("");
		while (!queue.isEmpty()) {
			Alphabet alphabet = queue.poll();

			sb.append(alphabet.ch);

			int nextIndex = alphabet.index;
			if (index[nextIndex] < length[nextIndex] - 1) {
				index[nextIndex]++;
				queue.add(new Alphabet(nextIndex, string.get(nextIndex).charAt(index[nextIndex])));
			}
		}
		System.out.println(sb.toString());
	}
}

class Alphabet implements Comparable<Alphabet> {
	int index;
	char ch;
	int length;

	public Alphabet(int index, char ch) {
		this.index = index;
		this.ch = ch;
	}

	@Override
	public int compareTo(Alphabet o) {
		if (ch == o.ch) {
			return Integer.compare(index, o.index);
		}
		return Character.compare(ch, o.ch);
	}

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

 

그렇다면 문자열을 큐에 넣을때마다 정렬해야 한다는 소리인데

문자열 자체를 맨 앞 부분을 하나씩 제거해야하기 때문에,

연산을 할 때마다 새로운 객체를 반환하는 불변객체 String보다는 StringBuffer을 사용했다.

import java.util.*;

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

		PriorityQueue<StringBuffer> queue = new PriorityQueue<StringBuffer>(new BufferComparator());

		for (int i = 0; i < n; i++) {
			queue.add(new StringBuffer(in.next()));
		}

		while (!queue.isEmpty()) {
			//System.out.println(queue);

			StringBuffer alphabet = queue.poll();

			System.out.print(alphabet.charAt(0));
			alphabet.deleteCharAt(0);

			if (alphabet.length() > 0) {
				queue.add(alphabet);
			}
			alphabet = null;
		}
		System.out.println();
	}
}

class BufferComparator implements Comparator<StringBuffer> {

	public int compare(StringBuffer s1, StringBuffer s2) {
		int min = Math.min(s1.length(), s2.length());
		for (int i = 0; i < min; i++) {
			if (s1.charAt(i) > s2.charAt(i)) {
				return 1;
			} else if (s1.charAt(i) < s2.charAt(i)) {
				return -1;
			}
		}
		return Integer.compare(s2.length(), s1.length());
	}
}

 

BufferComparator을 직접 구현해서 정렬해주도록 했다.

결과는 53%에서 시간초과..

 

 

 

결론적으로는 로직 자체는 같으나, 커스텀 Comparator을 사용하지 않고 기본 Comparable로 정렬하여 사용하는 방법이라면 통과가 된다.

문제 조건에서 공백은 없이 알파벳 대문자로만 구성되어 있다고 했기 때문에,

문자 마지막에 'a'를 붙여주면 (ASCII - \0: 0, a: 097, A: 065)

모든 대문자는 'a'보다 작기 때문에 커스텀 Comparator를 사용하지 않고 기본 Comparable을 사용하면서 같은 동작을 하도록 만들 수 있다.

import java.util.*;

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

		PriorityQueue<StringBuffer> queue = new PriorityQueue<StringBuffer>();
		for (int i = 0; i < n; i++) {
			queue.add(new StringBuffer(in.next() + "a"));
		}

		while (!queue.isEmpty()) {
			StringBuffer alphabet = queue.poll();

			System.out.print(alphabet.charAt(0));
			alphabet.deleteCharAt(0);

			if (alphabet.length() > 1) {
				queue.add(alphabet);
			}
			alphabet = null;
		}
		System.out.println();
	}
}

 

 

이 문제에서 주의해야 하는 대표 테스트케이스는 아래와 같다

4 
CCCA 
CCCB 
CCCD 
CCCE

2
ABCCC
BACCC

2
BAA
BA

 


PriorityQueue의 정렬 방법

PriorityQueue 내부의 정렬은 아래와 같은 함수를 이용하여 정렬된다.

정렬 자체에 영향을 주는 가장 큰 요소는 정렬 알고리즘인데, 이는 머지 소트로 동일하다.

    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x, queue, comparator);
        else
            siftUpComparable(k, x, queue);
    }
    
    private static <T> void siftUpComparable(int k, T x, Object[] es) {
        Comparable<? super T> key = (Comparable<? super T>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = es[parent];
            if (key.compareTo((T) e) >= 0)
                break;
            es[k] = e;
            k = parent;
        }
        es[k] = key;
    }

    private static <T> void siftUpUsingComparator(
        int k, T x, Object[] es, Comparator<? super T> cmp) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = es[parent];
            if (cmp.compare(x, (T) e) >= 0)
                break;
            es[k] = e;
            k = parent;
        }
        es[k] = x;
    }

왠지는 모르겠지만.. 아래는 기본 Comparable과 custom Comparator이다.

어디서 차이가 발생했는지 시간날 때 compareTo 함수를 좀 더 까봐야겠다...

    int compareTo(AbstractStringBuilder another) { // StringBuffer::compareTo
        if (this == another) {
            return 0;
        }

        byte val1[] = value;
        byte val2[] = another.value;
        int count1 = this.count;
        int count2 = another.count;

        if (coder == another.coder) {
            return isLatin1() ? StringLatin1.compareTo(val1, val2, count1, count2)
                              : StringUTF16.compareTo(val1, val2, count1, count2);
        }
        return isLatin1() ? StringLatin1.compareToUTF16(val1, val2, count1, count2)
                          : StringUTF16.compareToLatin1(val1, val2, count1, count2);
    }
    
    class BufferComparator implements Comparator<StringBuffer> { // Custom BufferComparator

        public int compare(StringBuffer s1, StringBuffer s2) {
            int min = Math.min(s1.length(), s2.length());
            for (int i = 0; i < min; i++) {
                if (s1.charAt(i) > s2.charAt(i)) {
                    return 1;
                } else if (s1.charAt(i) < s2.charAt(i)) {
                    return -1;
                }
            }
            return Integer.compare(s2.length(), s1.length());
        }
    } 

 

 

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

boj 17071: 숨바꼭질 5  (0) 2021.01.31
boj 1322: X와 K  (0) 2021.01.29
boj 16637: 괄호 추가하기  (0) 2021.01.27
boj 1167: 트리의 지름  (0) 2021.01.25
boj 1939: 중량제한  (0) 2021.01.20

모든 수가 0 또는 1로 이루어진 2차원 배열 a가 주어질 때, 2차원 배열 b의 경우의 수를 (10^7 + 19)로 나눈 나머지를 구하는 문제이다.

 

문제 조건

  • b의 모든 원소는 0 또는 1이며 a와 b의 크기가 같다
  • a의 i번째 열과 b의 i번째 열에 들어 있는 1의 개수가 같다.
  • b의 각 행에 들어 있는 1의 개수가 짝수이다. (0도 짝수)

일단... 경우의 수를 ~~로 나눈 나머지를 구하라고 했으니 이 수가 엄청나게 클 것이고 그냥 구하면 시간 초과가 날 것이라는 것을 알 수 있다.

대개 이렇게 결과값의 나머지를 구하라는 문제는 dp로 푸는데, dp를 어떻게 적용하면 좋을지 생각해 봤다.

 

dp[i][j]: (0, 0) ~ (i, j)까지 조건에 맞는 b를 만들 수 있는 경우의 수라고 정의하자.

 

만약 행들 중 1의 개수가 짝수이면 1을 더하면 홀수가 될 것이고, 홀수이면 1을 더하면 짝수개가 될 것이다.

열 하나를 추가해서 새롭게 만들어지는 배열은 몇 개의 짝수행을 가지고 있을 것인가. 그리고 만들 수 있는 경우의 수는 어떻게 될까.

 

기존에 가지고 있던 짝수행의 개수는 num개 였고 k개의 행에 1을 추가한다고 했기 때문에 k개의 행이 홀수행이 될 것이고 남은 num-k개가 여전히 짝수행일 것이다. 이 경우의 수는 기존 짝수행의 개수 num개 중 k개를 넣을 것이기 때문에 조합 (num)C(k) 이다.

기존에 가지고 있던 홀수행은 n-num개(전체 배열의 행 크기 - 짝수 행 크기) 일 것이다. 조건 2에 의해 입력배열에서  column+1 열의 1의 개수를 알 수 있고 이를 oneCnt라 하자. oneCnt 중 k개는 짝수행에 넣을 것이고 남은 oneCnt - k개의 1을 홀수행에 넣을 것이다. 따라서 oneCnt-k개가 짝수행이 될 것이다. 이 경우의 수는 기존 홀수행의 개수 n-num개 중 oneCnt - k개를 넣을 것이기 때문에 조합 (n-num)C(oneCnt-k)가 될 것이다. 

 

위와 같이 짝수행, 홀수행의 경우를 독립사건으로 보고 각자의 경우의 수를 구했으면 두 경우의 수를 곱한 결과에 dp[column][num]을 곱한 결과가 우리가 구하고자 하는 dp[column+1][(num-k) + (oneCnt-k)] 값이 된다.

 

for(column을 1~m까지)
  for(num을 0에서 n까지)
    for(k를 0에서 oneCnt까지)
       // 점화식
       dp[column+1][(num-k) + (oneCnt-k)] = SUM[dp[column][num] * (num)C(k) * (n-num)C(oneCnt-k)]

설명에서는 생략했지만 dp 배열에 들어가는 계산에는 10^7 + 19 나머지 연산을 해서 변수값 범위를 넘어가지 않도록 주의한다. 

 

시간복잡도는 O(N^2 * M)


소스코드 : github.com/fpdjsns/Algorithm/blob/master/programmers/%EC%9B%94%EA%B0%84%20%EC%BD%94%EB%93%9C%20%EC%B1%8C%EB%A6%B0%EC%A7%80%20%EC%8B%9C%EC%A6%8C1/%EC%A7%9D%EC%88%98%20%ED%96%89%20%EC%84%B8%EA%B8%B0.cpp

+ Recent posts