[PS/JAVA] (배열/수학) 프로그래머스_87377_교점에 별 만들기
문제
https://school.programmers.co.kr/learn/courses/30/lessons/87377
Ax + By + C = 0으로 표현할 수 있는 n개의 직선이 주어질 때, 이 직선의 교점 중 정수 좌표에 별을 그리려 합니다.
예를 들어, 다음과 같은 직선 5개를
- 2x - y + 4 = 0
- -2x - y + 4 = 0
- -y + 1 = 0
- 5x - 8y - 12 = 0
- 5x + 8y + 12 = 0
좌표 평면 위에 그리면 아래 그림과 같습니다.
이때, 모든 교점의 좌표는 (4, 1), (4, -4), (-4, -4), (-4, 1), (0, 4), (1.5, 1.0), (2.1, -0.19), (0, -1.5), (-2.1, -0.19), (-1.5, 1.0)입니다. 이 중 정수로만 표현되는 좌표는 (4, 1), (4, -4), (-4, -4), (-4, 1), (0, 4)입니다.
만약 정수로 표현되는 교점에 별을 그리면 다음과 같습니다.
위의 그림을 문자열로 나타낼 때, 별이 그려진 부분은 *, 빈 공간(격자선이 교차하는 지점)은 .으로 표현하면 다음과 같습니다.
"..........."
".....*....."
"..........."
"..........."
".*.......*."
"..........."
"..........."
"..........."
"..........."
".*.......*."
"..........."
이때 격자판은 무한히 넓으니 모든 별을 포함하는 최소한의 크기만 나타내면 됩니다.
따라서 정답은
"....*...."
"........."
"........."
"*.......*"
"........."
"........."
"........."
"........."
"*.......*"
입니다.
직선 A, B, C에 대한 정보가 담긴 배열 line이 매개변수로 주어집니다. 이때 모든 별을 포함하는 최소 사각형을 return 하도록 solution 함수를 완성해주세요.
제한사항
- line의 세로(행) 길이는 2 이상 1,000 이하인 자연수입니다.
- line의 가로(열) 길이는 3입니다.
- line의 각 원소는 [A, B, C] 형태입니다.
- A, B, C는 -100,000 이상 100,000 이하인 정수입니다.
- 무수히 많은 교점이 생기는 직선 쌍은 주어지지 않습니다.
- A = 0이면서 B = 0인 경우는 주어지지 않습니다.
- 정답은 1,000 * 1,000 크기 이내에서 표현됩니다.
- 별이 한 개 이상 그려지는 입력만 주어집니다.
입출력 예
[[2, -1, 4], [-2, -1, 4], [0, -1, 1], [5, -8, -12], [5, 8, 12]] | ["....*....", ".........", ".........", "*.......*", ".........", ".........", ".........", ".........", "*.......*"] |
[[0, 1, -1], [1, 0, -1], [1, 0, 1]] | ["*.*"] |
[[1, -1, 0], [2, -1, 0]] | ["*"] |
[[1, -1, 0], [2, -1, 0], [4, -1, 0]] | ["*"] |
입출력 예 설명
입출력 예 #1
문제 예시와 같습니다.
입출력 예 #2
직선 y = 1, x = 1, x = -1는 다음과 같습니다.
(-1, 1), (1, 1) 에서 교점이 발생합니다.
따라서 정답은
"*.*"
입니다.
입출력 예 #3
직선 y = x, y = 2x는 다음과 같습니다.
(0, 0) 에서 교점이 발생합니다.
따라서 정답은
"*"
입니다.
입출력 예 #4
직선 y = x, y = 2x, y = 4x는 다음과 같습니다.
(0, 0) 에서 교점이 발생합니다.
따라서 정답은
"*"
입니다.
참고 사항
Ax + By + E = 0
Cx + Dy + F = 0
두 직선의 교점이 유일하게 존재할 경우, 그 교점은 다음과 같습니다.
또, AD - BC = 0인 경우 두 직선은 평행 또는 일치합니다.
문제에서 인사이트 도출
1. 문제 파악
N개의 직선이 주어지고 교점의 좌표를 문자열 형태로 표현하는 것
2. 입력 파악
직선의 입력 형태는 [X의 계수, Y의 계수, 상수항]의 2차원 배열
- [2x - y + 4 = 0, -2x - y + 4 = 0] => [[2, -1, 4], [-2, -1, 4]]
3. 제한사항 파악
- 무수히 많은 교점이 생기는 경우는 없음.
- 두 직선의 관계는 만나지 않음, 1개의 교점이 생김, 무수히 많은 교점이 생기는 경우 세가지이다. - 시간 복잡도 고려 -> 입력량은 1000 이하, O(N^3) 이상이 될 경우 1억번의 연산 -> 시간초과될 확률 있음.
- 입력 수 범위는 -100,000 ~ 100,000으로 주어짐
- 입력의 덧셈, 뺄셈, 나눗셈 연산만 하게 될 경우 int면 충분
- 입력의 곱셈 연산을 할 경우 long 자료형이 필요함
4. 참고사항 파악
Ax + By + E = 0 Cx + Dy + F = 0 두 직선의 교점이 유일하게 존재할 경우, 그 교점은 다음과 같습니다. 또, AD - BC = 0인 경우 두 직선은 평행 또는 일치합니다. |
- 두 직선이 한 개의 교점을 갖는 경우와 만나지 않는지의 여부를 확인해야 함.
- 한 개의 교점을 가지는 경우 위의 식을 통해 교점의 좌표를 얻어낼 수 있음.
- 교점을 가지지 않거나 무수히 많은 교점을 가지는 경우, x, y의 값이 INF가 될 것이다.
-> 직선이 평행하거나 일치하는 경우
5. 출력형태 파악
위의 그림을 문자열로 나타낼 때, 별이 그려진 부분은 *, 빈 공간(격자선이 교차하는 지점)은 .으로 표현하면 다음과 같습니다. |
- 출력형태는 String[]
- 모든 교점을 포함시키기 위한 최소 격자 크기를 정해야 함.
- 교점의 (x,y)좌표의 최댓값, 최소값을 이용하여 격자의 크기를 정할 수 있음.
풀이과정 도출
1. 모든 직선 쌍에 대해서 아래의 과정을 행함
- 교점 좌표 구하기 : 교점 도출 식을 통해 (x, y)좌표가 나온다면 AND x, y가 정수라면 교점 리스트에 저장
- 교점 리스트에 저장할 때, x좌표, y좌표 각각의 최대값, 최소값을 갱신함
2. 출력을 위해서 격자 문자열을 생성한 후, 출력 포멧으로 반환
- 최대값, 최소값을 이용하여 격자의 크기를 결정
- 너비 : max X - min X + 1
- 높이 : max Y - min Y + 1 - 격자에 교점 리스트에 포함된 좌표일 경우 *를, 아닐 경우 .를 차례로 갱신
- 문자열 배열로 표현 후 반환
문제풀이
1. 모든 직선 쌍에 대해서 아래의 과정을 행함
- 교점 좌표 구하기 : 교점 도출 식을 통해 (x, y)좌표가 나온다면 AND x, y가 정수라면 교점 리스트에 저장
- 교점 리스트에 저장할 때, x좌표, y좌표 각각의 최대값, 최소값을 갱신함
(1) 리팩토링 이전
아래 과정은 코드 리팩토링 하기 이전 단계이다.
1-1. 입력에 대한 순회
public String[] solution(int[][] line) {
//1. 입력에 대한 순회
for(int i = 0; i < line.length; i++){
for(int j = i + 1; j < line.length; j++){
int x1 = line[i][0];
int x2 = line[j][0];
int y1 = line[i][1];
int y2 = line[j][1];
int c1 = line[i][2];
int c2 = line[j][2];
//2. 평행, 일치여부 확인
//3, 4. X,Y 교점 구하기
//5. x, y 최대/최솟값 갱신
//6. 찾은 교점 리스트에 추가
}
}
}
- 먼저 모든 직선 쌍에 대해서 교점의 여부를 검사하기 위해서 입력에 대한 순회를 한다.
- 순서는 중요하지 않고, 자기 자신과의 쌍을 맺지 않으므로 다음과 같이 입력을 순회한다.(nC2의 조합)
- [A, B, C, D, E]의 직선이 있다면, A는 B, C, D, E와의 교점 여부를, 다음 B는 C, D, E와의 교점 여부만 검사하면 된다.
- 각 직선의 x 계수, y 계수, 상수항을 추출해 둔다.
1-2. 평행 또는 일치여부 확인하기
//2.평행 혹은 일치여부 확인하기
public boolean isParallelOrEqual(int x1, int x2, int y1, int y2){
if(((x1 * y2) - (x2 * y1)) == 0){
return true;
}
else{
return false;
}
}
public String[] solution(int[][] line) {
//1. 입력에 대한 순회
for(int i = 0; i < line.length; i++){
for(int j = i + 1; j < line.length; j++){
int x1 = line[i][0];
int x2 = line[j][0];
int y1 = line[i][1];
int y2 = line[j][1];
int c1 = line[i][2];
int c2 = line[j][2];
//2. 평행, 일치여부 확인
boolean isNotExistPoint = this.isParallelOrEqual(x1, x2, y1, y2);
if(!isNotExistPoint){
//3, 4. X,Y 교점 구하기
//5. x, y 최대/최솟값 갱신
//6. 찾은 교점 리스트에 추가
}
}
}
}
- 우선 교점이 존재하지 않는 경우를 판단한다. 만약 평행하거나 일치하는 경우라면 교점을 구하는 과정을 생략하고 넘어간다.
1-3. 두 직선의 교점 X, Y 좌표 구하기
//3. 교점 X 구하기
public Integer findX(int x1, int x2, int c1, int c2, int y1, int y2){
double result = ((double)(y1 * c2) - (c1 * y2)) / ((x1 * y2) - (y1 * x2));
return (result == (int)result) ? (int)result : null;
}
//4. 교점 Y
public Integer findY(int x1, int x2, int c1, int c2, int y1, int y2){
double result = ((double)(c1 * x2) - (x1 * c2)) / ((x1 * y2) - (y1 * x2));
return (result == (int)result) ? (int)result : null;
}
- 다음은 x 계수, y 계수, 상수항을 바탕으로 두 직선의 X, Y 좌표를 도출하는 메서드이다.
- 마지막 리턴 과정에서 정수임을 확인한 후, 정수가 아니라면 null을 반환한다.
public String[] solution(int[][] line) {
//1. 입력에 대한 순회
for(int i = 0; i < line.length; i++){
for(int j = i + 1; j < line.length; j++){
int x1 = line[i][0];
int x2 = line[j][0];
int y1 = line[i][1];
int y2 = line[j][1];
int c1 = line[i][2];
int c2 = line[j][2];
//2. 평행, 일치여부 확인
boolean isNotExistPoint = this.isParallelOrEqual(x1, x2, y1, y2);
if(!isNotExistPoint){
//3, 4. X,Y 교점 구하기
Integer findX = this.findX(x1, x2, c1, c2, y1, y2);
Integer findY = this.findY(x1, x2, c1, c2, y1, y2);
if(findX != null && findY != null){ //만약 교점이 둘 다 정수라면
//5. x, y 최대/최솟값 갱신
//6. 찾은 교점 리스트에 추가
}
}
}
}
}
- 두 직선의 교점을 구하는 로직을 순회 과정에 추가시킨다.
- 두 교점이 모두 정수라면(null이 아니라면), 좌표들의 최댓값, 최솟값을 갱신시키고, 교점 리스트에 추가시키는 과정을 거치면 된다.
1-4. 좌표 최댓값, 최솟값 갱신 및 교점 리스트에 추가하기
public int minX = Integer.MAX_VALUE;
public int minY = Integer.MAX_VALUE;
public int maxX = Integer.MIN_VALUE;
public int maxY = Integer.MIN_VALUE;
//5. 좌표 최댓값, 최소값 갱신
public void changeMaxAndMin(int x, int y){
if(x < minX){
minX = x;
}
if(x > maxX){
maxX = x;
}
if(y < minY){
minY = y;
}
if(y > maxY){
maxY = y;
}
}
//교점 리스트
List<int[]> points = new ArrayList<>();
public String[] solution(int[][] line) {
//1. 입력에 대한 순회
for(int i = 0; i < line.length; i++){
for(int j = i + 1; j < line.length; j++){
int x1 = line[i][0];
int x2 = line[j][0];
int y1 = line[i][1];
int y2 = line[j][1];
int c1 = line[i][2];
int c2 = line[j][2];
//2. 평행, 일치여부 확인
boolean isNotExistPoint = this.isParallelOrEqual(x1, x2, y1, y2);
if(!isNotExistPoint){
//3, 4. X,Y 교점 구하기
Integer findX = this.findX(x1, x2, c1, c2, y1, y2);
Integer findY = this.findY(x1, x2, c1, c2, y1, y2);
if(findX != null && findY != null){ //만약 교점이 둘 다 정수라면
this.changeMaxAndMin((int)findX, (int)findY); //5. x, y 최대/최솟값 갱신
int[] point = {(int)findX, (int)findY};
this.points.add(point); //6. 찾은 교점 리스트에 추가
}
}
}
}
}
- 찾아낸 교점들을 저장할 ArrayList를 선언한다.
- 교점의 표현은 [1, 2]와 같이 int[]형을 사용할 것이다. -> 객체지향의 특성을 살려 Class 형태를 사용하는 것이 좋으나 단순 x,y좌표이므로 배열로 나타냈다.
- 격자를 그리기 위해 필요한 교점들 리스트와 좌표의 최대값, 최소값을 순회 시 마다 갱신해준다.
현재까지의 코드
import java.util.*;
class Solution {
public int minX = Integer.MAX_VALUE;
public int minY = Integer.MAX_VALUE;
public int maxX = Integer.MIN_VALUE;
public int maxY = Integer.MIN_VALUE;
//교점
List<int[]> points = new ArrayList<>();
//2.평행 혹은 일치여부 확인하기
public boolean isParallelOrEqual(int x1, int x2, int y1, int y2){
if(((x1 * y2) - (x2 * y1)) == 0){
return true;
}
else{
return false;
}
}
//3. 교점 X 구하기
public Integer findX(int x1, int x2, int c1, int c2, int y1, int y2){
double result = ((double)(y1 * c2) - (c1 * y2)) / ((x1 * y2) - (y1 * x2));
return (result == (int)result) ? (int)result : null;
}
//4. 교점 Y
public Integer findY(int x1, int x2, int c1, int c2, int y1, int y2){
double result = ((double)(c1 * x2) - (x1 * c2)) / ((x1 * y2) - (y1 * x2));
return (result == (int)result) ? (int)result : null;
}
//5. 좌표 최댓값, 최소값 갱신
public void changeMaxAndMin(int x, int y){
if(x < minX){
minX = x;
}
if(x > maxX){
maxX = x;
}
if(y < minY){
minY = y;
}
if(y > maxY){
maxY = y;
}
}
public String[] solution(int[][] line) {
//1. 입력에 대한 순회
for(int i = 0; i < line.length; i++){
for(int j = i + 1; j < line.length; j++){
int x1 = line[i][0];
int x2 = line[j][0];
int y1 = line[i][1];
int y2 = line[j][1];
int c1 = line[i][2];
int c2 = line[j][2];
//2. 평행, 일치여부 확인
boolean isNotExistPoint = this.isParallelOrEqual(x1, x2, y1, y2);
if(!isNotExistPoint){
//3, 4. X,Y 교점 구하기
Integer findX = this.findX(x1, x2, c1, c2, y1, y2);
Integer findY = this.findY(x1, x2, c1, c2, y1, y2);
if(findX != null && findY != null){ //만약 교점이 둘 다 정수라면
this.changeMaxAndMin((int)findX, (int)findY); //5. x, y 최대/최솟값 갱신
int[] point = {(int)findX, (int)findY};
this.points.add(point); //6. 찾은 교점 리스트에 추가
}
}
}
}
//7. 좌표 문자열 만들기
}
}
(1) 리팩토링 이후
위의 코드는 일부 테스트 케이스를 제외하고는 정상적으로 돌아간다.
하지만 일부 테스트 케이스 실패가 있었으며, 과정을 간소화할 수 있다.
class Solution {
public long minX = Long.MAX_VALUE;
public long minY = Long.MAX_VALUE;
public long maxX = Long.MIN_VALUE;
public long maxY = Long.MIN_VALUE;
//교점
List<long[]> points = new ArrayList<>();
//3. 교점 X
public Long findX(long x1, long x2, long c1, long c2, long y1, long y2){
double result = ((double)(y1 * c2) - (c1 * y2)) / ((x1 * y2) - (y1 * x2));
return (result % 1 == 0) ? (long)result : null;
}
//4. 교점 Y
public Long findY(long x1, long x2, long c1, long c2, long y1, long y2){
double result = ((double)(c1 * x2) - (x1 * c2)) / ((x1 * y2) - (y1 * x2));
return (result % 1 == 0) ? (long)result : null;
}
//5. 좌표 최댓값, 최소값 갱신
public void changeMaxAndMin(long x, long y){
if(x < minX){
minX = x;
}
if(x > maxX){
maxX = x;
}
if(y < minY){
minY = y;
}
if(y > maxY){
maxY = y;
}
}
//입력 = [[x, y, 상수]]
public String[] solution(int[][] line) {
//1. 입력에 대한 순회
for(int i = 0; i < line.length; i++){
for(int j = i + 1; j < line.length; j++){
int x1 = line[i][0];
int x2 = line[j][0];
int y1 = line[i][1];
int y2 = line[j][1];
int c1 = line[i][2];
int c2 = line[j][2];
//2. 평행, 일치여부 확인(X)
//3, 4. X,Y 교점 구하기
Long findX = this.findX(x1, x2, c1, c2, y1, y2);
Long findY = this.findY(x1, x2, c1, c2, y1, y2);
if(findX != null && findY != null){ //만약 교점이 둘 다 정수라면
this.changeMaxAndMin(findX, findY); //5. x, y 최대/최솟값 갱신
long[] point = {findX, findY};
this.points.add(point); //6. 찾은 교점 리스트에 추가
}
}
}
//7. 좌표 문자열 만들기
}
}
다음은 아래의 개선사항이 이루어진 소스코드이다.
- 평행/일치 확인 여부 로직을 교점을 구할 때 함께 확인하도록 변경 -> "2. 평행, 일치여부 확인 과정"의 계산을 없애서 로직 간소화
- 교점 계산 시 오버플로우 발생 -> 계산을 위해서 자료형을 Long으로 변경
//3. 교점 X
public Long findX(long x1, long x2, long c1, long c2, long y1, long y2){
double result = ((double)(y1 * c2) - (c1 * y2)) / ((x1 * y2) - (y1 * x2));
return (result % 1 == 0) ? (long)result : null;
}
//4. 교점 Y
public Long findY(long x1, long x2, long c1, long c2, long y1, long y2){
double result = ((double)(c1 * x2) - (x1 * c2)) / ((x1 * y2) - (y1 * x2));
return (result % 1 == 0) ? (long)result : null;
}
/*
//3. 교점 X 구하기
public Integer findX(int x1, int x2, int c1, int c2, int y1, int y2){
double result = ((double)(y1 * c2) - (c1 * y2)) / ((x1 * y2) - (y1 * x2));
return (result == (int)result) ? (int)result : null;
}
//4. 교점 Y
public Integer findY(int x1, int x2, int c1, int c2, int y1, int y2){
double result = ((double)(c1 * x2) - (x1 * c2)) / ((x1 * y2) - (y1 * x2));
return (result == (int)result) ? (int)result : null;
}
*/
평행/일치 확인 여부 로직을 교점을 구할 때 함께 확인하도록 변경
- 평행/일치 확인 여부 로직을 교점을 구할 때 함께 확인하도록 변경하기 위해서 교점을 구하는 로직을 변경하였다.
- 평행/일치 여부 확인 식은 (x1 * y2) - (y1 * x2)가 0인 경우로 확인할 수 있다고 하였다. 해당 식은 교점을 구하는 식에서의 분모에 해당한다. 따라서 굳이 평행/일치 여부 확인을 하지 않고 교점을 구할 때 한번에 확인이 가능하다.
- 마지막에 정수 판별 시 분모가 0인 경우 result는 INF가 된다. 따라서, int형으로 형변환하여 확인하는 것이 불가능하다. 그래서 모듈러 연산을 통해 정수를 판별하도록 바꿔준다.
교점 계산 시 오버플로우 발생 -> 계산을 위해서 자료형을 Long으로 변경
- 문제에서 X,Y의 계수 및 상수항의 범위는 -100,000 ~ 100,000으로 주어졌다.
이는 int형으로 충분히 표현 가능한 자료형이다. 그렇지만 교점을 구하는 가정에서 두개의 계수 혹은 상수의 곱셈 연산이 이루어진다.
따라서 최댓값일 경우 10,000,000,000 > 2,147,483,647로 int로 표현이 불가능해진다. 따라서 자료형을 long으로 바꾸어 주었다.
2. 출력을 위해서 격자 문자열을 생성한 후, 출력 포멧으로 반환
- 최대값, 최소값을 이용하여 격자의 크기를 결정
- 너비 : max X - min X + 1
- 높이 : max Y - min Y + 1 - 격자에 교점 리스트에 포함된 좌표일 경우 *를, 아닐 경우 .를 차례로 갱신
- 문자열 배열로 표현 후 반환
(2) 리팩토링 이전
아래 과정은 코드 리팩토링 하기 이전 단계이다.
public int minX = Integer.MAX_VALUE;
public int minY = Integer.MAX_VALUE;
public int maxX = Integer.MIN_VALUE;
public int maxY = Integer.MIN_VALUE;
//교점
List<int[]> points = new ArrayList<>();
//7. 좌표 문자열 만들기
public String[] makeStar(){
int length = maxY - minY + 1;
String[] answer = new String[length];
boolean contains = false;
for(int j = this.maxY; j >= minY; j--){
String s = "";
for(int i = this.minX; i <= maxX; i++){
int[] point = {i, j};
contains = false;
for(int[] p : points){
if (Arrays.equals(p, point)) {
contains = true;
break;
}
}
if(contains){
s += "*";
}
else{
s += ".";
}
}
answer[this.maxY - j] = s;
}
return answer;
}
public String[] solution(int[][] line) {
//1. 입력에 대한 순회
for(int i = 0; i < line.length; i++){
for(int j = i + 1; j < line.length; j++){
//...
}
}
//7. 좌표 문자열 만들기
String[] answer = this.makeStar();
return answer;
}
- 문자열 배열의 크기(세로)는 maxY - minY + 1에 해당한다.
- 격자는 2차원 형태이다. 따라서 x좌표와 y좌표에 대한 2중 반복문 수행을 통해 출력값을 생성한다.
- 1) 열 생성 반복 -> 1행 생성 : x좌표에 대한 순회를 통해서 '.' 또는 '*'로 이루어진 문자열 하나를 생성한다.
- 2) 행 생성 반복 -> 격자 생성 : y좌표에 대한 순회를 통해서 1)의 문자열들을 반복하여 생성한다. - 격자에 좌표를 표현해야 하는 경우 다음 사항을 유의해야 한다.
- 왼쪽 위부터, x좌표는 작은 순서대로, y좌표는 큰 순서대로 표현되어야 한다.
- 따라서 y는 max값을 시작으로, x는 min값을 시작으로 2중 반복문을 수행해야 한다.
* y좌표는 큰 순서대로 표현되어야 한다고 했다.
한 행이 생성되었을 때, 결과 배열에 대입할 때 answer[j]에 대입하는 것이 아니라, answer[this.maxY - j]에 대입 - 반복문 수행 중, 해당 좌표가 point에 대해 속해 있는지를 판별하여 '*' 혹은 '.'를 문자열에 추가한다.
- 해당 경우는 최악의 경우 [X축 너비 * Y축 높이 * 교점의 개수] 만큼의 연산을 추가로 실행해야한다는 뜻이다. 연산을 줄이기 위해서 리팩토링을 실행할 것이다.
(2) 리팩토링 이후
class Solution {
public long minX = Long.MAX_VALUE;
public long minY = Long.MAX_VALUE;
public long maxX = Long.MIN_VALUE;
public long maxY = Long.MIN_VALUE;
//교점
List<long[]> points = new ArrayList<>();
public String[] makeStar(){
int width = (int)(maxX - minX + 1);
int height = (int)(maxY - minY + 1);
char[][] answer = new char[height][width];
for(char[] row: answer){
Arrays.fill(row, '.');
}
for(long[] point: points){
answer[(int)(maxY - point[1])][(int)(point[0] - minX)] = '*';
}
String[] result = new String[answer.length];
for(int i = 0; i < answer.length; i++){
result[i] = new String(answer[i]);
}
return result;
}
//입력 = [[x, y, 상수]]
public String[] solution(int[][] line) {
//1. 입력에 대한 순회
for(int i = 0; i < line.length; i++){
for(int j = i + 1; j < line.length; j++){
//...
}
}
//7. 좌표 문자열 만들기
String[] answer = this.makeStar();
return answer;
}
}
- '*' 혹은 '.'을 입력을 판단하기 위해서 실행되는 로직의 최악의 경우 [X축 너비 * Y축 높이 * 교점의 개수] 만큼의 연산을 추가로 실행해야 했던 위 코드를 갱신할 수 있다.
- 변경된 로직은 우선 모든 원소들을 '.'으로 채운 후, points의 교점 리스트의 원소에 대해서만 해당 좌표를 찾아 갱신하도록 하였다.
해당 방법은 교점의 개수만큼의 연산만 수행하면 된다. - char[][]형의 2차원 배열 대입 방법 : answer[(int)(maxY - point[1])][(int)(point[0] - minX)] = '*';
- 항상 2차원 배열은 Array[행][열]로 표현된다는 것을 유의하자. -> answer[y][x]로 이루어져야 한다.
- point의 인덱스는 말그대로 수학적 좌표이고, answer의 인덱스는 원래 좌표가 최댓값과 최소값을 기반으로 조정된 것임을 유의
* maxY와 maxX를 이용하여 해당 좌표들 간의 차이를 보정할 수 있다.
* 일반식을 처음부터 도출하기 어렵다면, 예시 케이스 두가지 정도를 생각하여, 일반식을 도출하는 방법을 활용하면 좋다.
최종 소스코드
class Solution {
public long minX = Long.MAX_VALUE;
public long minY = Long.MAX_VALUE;
public long maxX = Long.MIN_VALUE;
public long maxY = Long.MIN_VALUE;
//교점
List<long[]> points = new ArrayList<>();
//3. 교점 X
public Long findX(long x1, long x2, long c1, long c2, long y1, long y2){
double result = ((double)(y1 * c2) - (c1 * y2)) / ((x1 * y2) - (y1 * x2));
return (result % 1 == 0) ? (long)result : null;
}
//4. 교점 Y
public Long findY(long x1, long x2, long c1, long c2, long y1, long y2){
double result = ((double)(c1 * x2) - (x1 * c2)) / ((x1 * y2) - (y1 * x2));
return (result % 1 == 0) ? (long)result : null;
}
//5. 좌표 최댓값, 최소값 갱신
public void changeMaxAndMin(long x, long y){
if(x < minX){
minX = x;
}
if(x > maxX){
maxX = x;
}
if(y < minY){
minY = y;
}
if(y > maxY){
maxY = y;
}
}
public String[] makeStar(){
int width = (int)(maxX - minX + 1);
int height = (int)(maxY - minY + 1);
char[][] answer = new char[height][width];
for(char[] row: answer){
Arrays.fill(row, '.');
}
for(long[] point: points){
answer[(int)(maxY - point[1])][(int)(point[0] - minX)] = '*';
}
String[] result = new String[answer.length];
for(int i = 0; i < answer.length; i++){
result[i] = new String(answer[i]);
}
return result;
}
public String[] solution(int[][] line) {
//1. 입력에 대한 순회
for(int i = 0; i < line.length; i++){
for(int j = i + 1; j < line.length; j++){
int x1 = line[i][0];
int x2 = line[j][0];
int y1 = line[i][1];
int y2 = line[j][1];
int c1 = line[i][2];
int c2 = line[j][2];
//2. 평행, 일치여부 확인(X)
//3, 4. X,Y 교점 구하기
Long findX = this.findX(x1, x2, c1, c2, y1, y2);
Long findY = this.findY(x1, x2, c1, c2, y1, y2);
if(findX != null && findY != null){ //만약 교점이 둘 다 정수라면
this.changeMaxAndMin(findX, findY); //5. x, y 최대/최솟값 갱신
long[] point = {findX, findY};
this.points.add(point); //6. 찾은 교점 리스트에 추가
}
}
}
//7. 좌표 문자열 만들기
String[] answer = this.makeStar();
return answer;
}
}
'PS > 문제풀기' 카테고리의 다른 글
[PS/JAVA] (배열/수학) 프로그래머스_12949_행렬의 곱셈 (0) | 2023.07.28 |
---|---|
[PS/JAVA] (배열/수학) 프로그래머스_81302_거리두기 확인하기 (0) | 2023.07.28 |
[PS/JAVA] (배열/수학) 프로그래머스_68645_삼각 달팽이 (0) | 2023.07.15 |