[14223] 작은 정사각형1 - 브루트포스, 조합, 수학 (백준, Java)



▶️시도했던 과정
조합으로 N개 점 중 N - 2개를 뽑아 풀었으나 시간초과. 재귀할 때 순열처럼 계속 0부터 돌고있었다.
제외할 2개의 점을 뽑는게 키포인트였고 넓이 구하는 로직에도 문제가 있었다.
1차시도 - 시간초과
보자마자 N 개 중 N - 2개를 순서 없이, 중복 없이 뽑는 조합으로 풀면 될 것이라 생각했다.
조합 부분 & 넓이 구하는 부분 코드
private static void combination(int cnt) {
if (totalCnt == cnt) {
getWidth();
return;
}
for (int i = 0; i < N; i++) {
if (flagArr[i] == 0) {
flagArr[i] = 1;
cnt++;
combination(cnt);
cnt--;
flagArr[i] = 0;
}
}
}
private static void getWidth() {
long minX = Long.MAX_VALUE;
long minY = Long.MAX_VALUE;
long maxX = Long.MIN_VALUE;
long maxY = Long.MIN_VALUE;
for (int i = 0 ; i < N; i++) {
if (flagArr[i] == 1) {
Point cur = pointArr[i];
if (cur.x < minX) minX = cur.x;
if (cur.y < minY) minY = cur.y;
if (cur.x > maxX) maxX = cur.x;
if (cur.y > maxY) maxY = cur.y;
}
}
long x = Math.abs((minX - 1) - (maxX + 1));
long y = Math.abs((minY - 1) - (maxY + 1));
if (answer > x * y) answer = x * y;
}
totalCnt를 N - 2개로 설정해두고 cnt가 해당 수와 같아지면 그 N - 2 개의 점들의 각 x,y 최소 최대 값들을 뽑아 최소 넓이를 구한다.
→ 시간초과
N이 최대 50이니까 50개에서 48개를 뽑는 경우의 수는 1225가지가 나온다.

다만 이미 선택했거나 제외한 점에 대한 정보를 flagArr[i]로 체크하고 있음에도 활용하지 못 하고 매번 반복문에서 0부터 탐색한다.
이러면 순열처럼 N!의 시간 복잡도를 갖게 된다.
재귀호출 시 다음 인덱스부터 탐색하도록 제한해야 O(N^2) 내에서 효율적인 동작을 할 수 있다.
2차 시도 - startIdx로 재귀 시 처음부터 탐색하는 것을 막았지만 시간초과
따라서 이 코드로 조합 부분을 변경하였다.
private static void combination(int cnt, int start){
if(cnt == totalCnt){
getWidth();
return;
}
for(int i = start; i < N; i++){
if(flagArr[i] == 0){
flagArr[i] = 1;
combination(cnt+1, i+1);
flagArr[i] = 0;
}
}
}
다만 이것도 시간 초과가 나게 되는데 수학적으로는 NC2와 NCN-1가 같지만
이 로직같은 경우에 N 50이면 40번째부터 요소 1개를 뽑아도 48개가 당연히 안 되는데 끝까지 시도하게 된다. 이런 불필요한 시도로직에 시간이 많이 소요가 된다.
따라서 totalCnt (N-2)가 아닌 제외할 점 2개를 뽑는 로직으로 변경해준다.
▶️Solution Code1 - 조합로직
import java.io.*;
import java.util.*;
public class Main {
static int totalCnt;
static int N;
static Point[] pointArr;
static int[] flagArr;
static long answer = Long.MAX_VALUE;
static class Point {
long x;
long y;
public Point(long x, long y) {
this.x = x;
this.y = y;
}
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine());
pointArr = new Point[N];
flagArr = new int[N];
for (int i = 0; i < N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
pointArr[i] = new Point(Long.parseLong(st.nextToken()), Long.parseLong(st.nextToken()));
}
combination(0, 0);
System.out.println(answer);
}
private static void combination(int cnt, int startIdx) {
if (2 == cnt) {
getWidth();
return;
}
for (int i = startIdx; i < N; i++) {
if (flagArr[i] == 0) {
flagArr[i] = 1;
combination(cnt + 1, i + 1);
flagArr[i] = 0;
}
}
}
private static void getWidth() {
long minX = Long.MAX_VALUE;
long minY = Long.MAX_VALUE;
long maxX = Long.MIN_VALUE;
long maxY = Long.MIN_VALUE;
for (int i = 0; i < N; i++) {
if (flagArr[i] == 1) continue;
Point cur = pointArr[i];
if (cur.x < minX) minX = cur.x;
if (cur.y < minY) minY = cur.y;
if (cur.x > maxX) maxX = cur.x;
if (cur.y > maxY) maxY = cur.y;
}
long dx = maxX - minX + 2;
long dy = maxY - minY + 2;
long side = Math.max(dx, dy);
long curArea = side * side;
if (answer > curArea) answer = curArea;
}
}
근데 이것을 굳이 조합로직이 아니라 어차피 제외할 2개의 점을 뽑는 거라 이중 반복문으로도 가능하다.
▶️Solution Code2 - 이중 반복문 로직
import java.io.*;
import java.util.*;
public class Main {
static int N;
static Point[] pointArr;
static long answer = Long.MAX_VALUE;
static class Point {
long x;
long y;
public Point(long x, long y) {
this.x = x;
this.y = y;
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine());
pointArr = new Point[N];
for (int i = 0; i < N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
pointArr[i] = new Point(Long.parseLong(st.nextToken()), Long.parseLong(st.nextToken()));
}
for (int i = 0; i < N; i++) {
for (int j = i + 1; j < N; j++) {
getWidth(i, j);
}
}
System.out.println(answer);
}
private static void getWidth(int exclude1, int exclude2) {
long minX = Long.MAX_VALUE;
long minY = Long.MAX_VALUE;
long maxX = Long.MIN_VALUE;
long maxY = Long.MIN_VALUE;
for (int i = 0; i < N; i++) {
if (i == exclude1 || i == exclude2) continue;
Point cur = pointArr[i];
if (cur.x < minX) minX = cur.x;
if (cur.y < minY) minY = cur.y;
if (cur.x > maxX) maxX = cur.x;
if (cur.y > maxY) maxY = cur.y;
}
long dx = maxX - minX + 2;
long dy = maxY - minY + 2;
long side = Math.max(dx, dy);
long curArea = side * side;
if (answer > curArea) answer = curArea;
}
}
여기에 넓이 구하는 getWidth()로직도 처음엔 다음과 같이 작성해서 틀렸었다.
private static void getWidth(int exclude1, int exclude2) {
long minX = Long.MAX_VALUE;
long minY = Long.MAX_VALUE;
long maxX = Long.MIN_VALUE;
long maxY = Long.MIN_VALUE;
for (int i = 0; i < N; i++) {
if (i == exclude1 || i == exclude2) continue;
Point cur = pointArr[i];
if (cur.x < minX) minX = cur.x;
if (cur.y < minY) minY = cur.y;
if (cur.x > maxX) maxX = cur.x;
if (cur.y > maxY) maxY = cur.y;
}
long x = Math.abs((minX - 1) - (maxX + 1));
long y = Math.abs((minY - 1) - (maxY + 1));
if (answer > x * y) answer = x * y;
}
x, y 각각 최대, 최소 값들의 절댓값을 곱해서 계산했는데 이러면 직사각형이 된다.
x와 y 값 중 더 큰 값을 택해서 (그래야 모든 점이 덮어지니까) 그 값으로 제곱을 해 정사각형의 넓이를 구해야한다.
따라서 올바른 getWidth()는 다음과 같다.
private static void getWidth() {
long minX = Long.MAX_VALUE;
long minY = Long.MAX_VALUE;
long maxX = Long.MIN_VALUE;
long maxY = Long.MIN_VALUE;
for (int i = 0; i < N; i++) {
if (flagArr[i] == 1) continue;
Point cur = pointArr[i];
if (cur.x < minX) minX = cur.x;
if (cur.y < minY) minY = cur.y;
if (cur.x > maxX) maxX = cur.x;
if (cur.y > maxY) maxY = cur.y;
}
long dx = maxX - minX + 2;
long dy = maxY - minY + 2;
long side = Math.max(dx, dy);
long curArea = side * side;
if (answer > curArea) answer = curArea;
}
고민의 흔적들..
