출처
접근
- 돌려야 하는 배열을 1차원 배열로 만들어서 R만큼 이동하면 다음 배열 값을 얻을 수 있습니다.
1차원 배열과 2차원 배열을 변환하는게 조금 귀찮은데, 저는 하 -> 우 -> 상 -> 좌(반시계) 순으로 이동하도록 설계했습니다.
이 때, 1차원 배열의 크기는 2차원 배열의 (가로 + 세로) * 2 에서 네 귀퉁이(위 사진에서 (1) ~ (4))가 중복되므로 4를 빼주어야 합니다.
이렇게 구한 배열값을 R만큼 이동시키는데, 이때 R < 10^91 이므로 최적화를 위해 해당 배열의 크기로 나머지를 구합니다.
다시 그래프를 탐색하면서 이번에는 역순으로 1차원 배열의 원소로 2차원 배열을 채워넣습니다.
1 ~ 3 과정을 행 / 열 중 하나가 1보다 작아질 때까지 depth를 1씩 이동하며 수행합니다.
풀이
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int N;
static int M;
static int R;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
R = Integer.parseInt(st.nextToken());
int[][] NM = new int[N][M];
for (int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 0; j < M; j++) {
NM[i][j] = Integer.parseInt(st.nextToken());
}
}
for (int d = 0; d < Math.min(M, N) / 2; d++) { // M, N 중 최솟값의 절반만큼 수행
rotate(d, N - d, d, M - d, NM);
}
System.out.print(toStringInt2(NM)); // 정답 출력
}
/*
* [조건]
* 시간제한 : 1초 / 메모리 제한 : 512MB
* N, M < 300, R < 10^9, 1 < 원소 < 10^8
* min(N, M) % 2 = 0(M + N은 짝수)
* [풀이]
* 2(m + n) 크기의 배열에 원소들을 담고, R % 2(m + n)의 값만큼 반시계 방향으로 이동하면 해당위치 값을 구할 수 있다.
* 모든 깊이에서 해당 로직을 수행하기 위해 (n / 2 <= 1)가 될때까지 위 로직을 수행한다.
*/
static void rotate(int n0, int n1, int m0, int m1, int[][] NM) {
// 배열 크기는 전체 길이에서 네 귀퉁이를 한번씩 빼주기
int[] arr = new int[(n1 - n0 + m1 - m0) * 2 - 4];
// 배열에 반시계방향으로 원소 담기
getArrElement(n0, n1, m0, m1, arr, NM);
// 배열 값 이동시키기
int l = arr.length;
int[] newArr = new int[l];
int p = R % l; // 최적화를 위해 R을 행렬의 길이로 나눈 나머지를 구해서 이동시키기
for (int i = 0; i < l; i++) { //
int np = i - p >= 0? i - p : i - p + l;
newArr[i] = arr[np];
}
// 이동한 값 배열에 넣기
setArrElement(n0, n1, m0, m1, newArr, NM);
}
static int[] dr = {1, 0, -1, 0}; // d = 0 : 하 -> d = 1 : 상 -> d = 2 : 좌 -> d = 3 : 우
static int[] dc = {0, 1, 0, -1};
/*
* 1차원 배열로 하 -> 우 -> 상 -> 좌 순으로 2차원 배열 채우기
*/
static void setArrElement(int n0, int n1, int m0, int m1, int[] arr, int[][] NM) {
// 시작점에서부터 하 -> 우 -> 상 -> 좌 순으로 탐색
int[] p = {n0, m0};
NM[n0][m0] = arr[0]; // 1차원 배열로 2차원 배열 채우기
int l = 1;
int d = 0;
while (l < arr.length) { // 1차원 배열의 길이가 다 채워질때까지 수행
int[] np = {p[0] + dr[d], p[1] + dc[d]};
if (n0 > np[0] || np[0] >= n1 || m0 > np[1] || np[1] >= m1) { // 해당 꼭지점을 벗어날 경우 방향 전환
d++; // 방향 전환
np = new int[] {p[0] + dr[d], p[1] + dc[d]}; // 방향 전환 후 np 덮어쓰기
}
NM[np[0]][np[1]] = arr[l]; // 1차원 배열로 2차원 배열 채우기
l++; // while문 탈출조건
p = np; // 위치 다음으로 변경
}
}
/*
* 2차원 배열로 하 -> 우 -> 상 -> 좌 순으로 1차원 배열 채우기
*/
static void getArrElement(int n0, int n1, int m0, int m1, int[] arr, int[][] NM) {
// 시작점에서부터 하 -> 우 -> 상 -> 좌 순으로 탐색
int[] p = {n0, m0};
arr[0] = NM[n0][m0]; // 2차원 배열로 1차원 배열 채우기
int l = 1;
int d = 0;
while (l < arr.length) {
int[] np = {p[0] + dr[d], p[1] + dc[d]};
if (n0 > np[0] || np[0] >= n1 || m0 > np[1] || np[1] >= m1) {
d++;
np = new int[] {p[0] + dr[d], p[1] + dc[d]};
}
arr[l] = NM[np[0]][np[1]]; // 2차원 배열로 1차원 배열 채우기
l++;
p = np;
}
}
/*
* 2차원 배열 출력하기
*/
static String toStringInt2(int[][] int2) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < int2.length; i++) {
for (int j : int2[i]) sb.append(j + " ");
if (i != int2.length - 1) sb.append("\n");
}
return sb.toString();
}
}
결과
리뷰
문제 접근은 바로 떠올렸는데, 오랜만에 구현하려고 하니 생각보다 시간이 많이걸렸습니다.
그리고 2차원 행렬 탐색을 할 때 어줍짢게 for문으로 상하좌우 돌려보려고 했는데 1시간 정도 해보다가 포기했습니다.
구현방법이 직관적으로 떠오르지 않으면 바로 다른 대안을 찾아 시간을 줄여야겠습니다.
지금 생각해보니 2차원 <-> 1차원 변환하는 메서드가 중복이 많은데, 플래그를 하나 넣으면 짧게 줄일수도 있을 것 같습니다.
그리고 매개변수에 각 지점을 하나씩 넣지말고 행과 열을 묶어서 배열로 넣는게 더욱 코드를 이해하기 좋아보입니다.
- 개선 코드
/* * 1차원 배열 <-> 2차원 배열 변환 */ static void changeArrElement(int[] n, int[] m, int[] arr, int[][] NM, boolean isTwoToOne) { // 시작점에서부터 하 -> 우 -> 상 -> 좌 순으로 탐색 int[] p = {n[0], m[0]}; if (isTwoToOne) arr[0] = NM[n[0]][m[0]]; // 2차원 배열로 1차원 배열 채우기 else NM[n[0]][m[0]] = arr[0]; // 1차원 배열로 2차원 배열 채우기 int l = 1; int d = 0; while (l < arr.length) { // 1차원 배열의 길이가 다 채워질때까지 수행 int[] np = {p[0] + dr[d], p[1] + dc[d]}; if (n[0] > np[0] || np[0] >= n[1] || m[0] > np[1] || np[1] >= m[1]) { // 해당 꼭지점을 벗어날 경우 방향 전환 d++; // 방향 전환 np = new int[] {p[0] + dr[d], p[1] + dc[d]}; // 방향 전환 후 np 덮어쓰기 } if (isTwoToOne) arr[l] = NM[np[0]][np[1]]; // 2차원 배열로 1차원 배열 채우기 else NM[np[0]][np[1]] = arr[l]; // 1차원 배열로 2차원 배열 채우기 l++; // while문 탈출조건 p = np; // 위치 다음으로 변경 } }
References
URL | 게시일자 | 방문일자 | 작성자 |
---|
R = 10^9이면 원소를 순차적으로 하나씩 이동시켰을때 원소 1개의 시간복잡도가 10^9 이므로 문제에 주어진 10^8개의 원소를 이동시키는데 O(N) = 10^9 * 10^8 = 10^17 이 걸리게 됩니다. ↩︎