Medium
You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).
You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.
Example 1:
Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output: [[7,4,1],[8,5,2],[9,6,3]]
Plain Text
복사
Example 2:
Input: matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
Output: [[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
Plain Text
복사
Example 3:
Input: matrix = [[1]]
Output: [[1]]
Plain Text
복사
Example 4:
Input: matrix = [[1,2],[3,4]]
Output: [[3,1],[4,2]]
Plain Text
복사
Constraints:
•
matrix.length == n
•
matrix[i].length == n
•
1 <= n <= 20
•
1000 <= matrix[i][j] <= 1000
My solution
in-place ?
생각을 전환해야 한다. 알고리즘을 어떻게 짜야 공간을 아낄 수 있을까?
intro sort에서 자주 이용되는, 복잡도가 n^2짜리인 정렬 알고리즘인 insertion sort는 어떨까요? 이것 역시 추가적인 공간을 그리 많이 소모하지 않습니다.
로직을 잘 생각해 봅시다. 이미 정렬된 것들은 노란색으로 표시했습니다. 그리고 현재 턴에 올바른 자리에 들어가야 할 원소는 군청색으로 표시해 두었는데요. 2가 들어갈 자리는 1과 4 사이입니다.
그러면 노란색 뒤에, 주황색 앞에 들어가야 할 겁니다. 따라서, 주황색으로 표시된 것들을 뒤로 1칸 밀어버립니다.
다음에 군청색으로 표시된 것이 주황색 앞에 옵니다. 이 과정은 추가적인 메모리가 필요하지 않습니다. 단지, 군청색이 어떤 원소였는지만 저장해 두고, 주황색 부분만 뒤에 있는 원소부터 1칸씩 뒤로 밀어버리기만 하면 되기 때문입니다. 배열의 크기가 500만이 되도, 끽 해봐야 현재 턴에 위치를 찾아야 할 수를 저장할 공간하고, 배열의 특정 위치를 가리키는 포인터 몇 개만 관리하면 됩니다. 이는 500만보다 한참 작은 수치입니다. 따라서 insertion sort는 in place 하다고 할 수 있어요.
어쨌든 딱 90도만큼 시계방향으로 돌린 배열을 구하면 된다. 1~2개 정도의 변수만 있으면 돌릴 수 있을 거 같다.
변수는 네 개의 원소를 가진 배열이어야 할듯. 한방에 하나의 배열씩 바꿔줘야지
돌릴 것 이전에 원래 위치인 [5, 1, 9, 11]을 저장해두고 있어야 된다. 4번은 돌려야 가장자리가 다바뀜.
const rotate = function (matrix) {
const n = matrix.length;
console.log(matrix);
for (let i = 0; i < n; i++) {
const temp = matrix[i].slice(i, n - i);
let t = [];
for (const x of matrix) {
t = [...temp2, x[n - i - 1]];
}
temp2 = t.slice(i, n - i);
let temp2 = [];
for (const x of matrix) {
temp2 = [...temp2, x[n - i - 1]];
}
temp2 = temp2.slice(i, n - i);
const temp3 = matrix[n - i - 1].slice(i, n - i);
if (temp.length === 1) return;
console.log(temp, temp2, temp3);
}
};
JavaScript
복사
이렇게 복잡하게 생각하면 안될 거 같다.
아래의 경우는 위에서 str을 중앙 기준으로 양옆을 교환하면서 앞뒤를 바꾸는 것과 같은 방식이다.
j는 0부터 시작해서 N/2(중앙)까지만 도착하면 양옆이 모두 변경된 것이다. 따라서 N/2보다 작을 때까지를 범위로 둔다.
for (let i = 0; i < N; ++i) {
for (let j = 0; j < N / 2; ++j) {
JavaScript
복사
이렇게 앞뒤를 바꾸는 방식은 알아두면 굉장히 좋을 것 같다. 마지막에 0,2와 0,2를 바꾸게 된다.
i, j i, N - j - 1 (이건 N-1 그러니까 최대 인덱스에서 j만큼 뺀만큼이다.)
이걸 서로 바꿔준 것이다.
결과적으로 matrix가 reverse horizontally된다.
위의 방식이 더 편하지만 훨씬 더 빠른 방식이 있긴 하다. 2중 for문을 한번만에 끝내는 방식이다. 이게 가장 빠른 것 같다.
for (let k = 0; k < matrix.length / 2; k++) {
for (let i = k; i < matrix.length - 1 - k; i++) {
const temp = matrix[k][i];
matrix[k][i] = matrix[matrix.length - 1 - i][k];
matrix[matrix.length - 1 - i][k] = matrix[matrix.length - 1 - k][matrix.length - 1 - i];
matrix[matrix.length - 1 - k][matrix.length - 1 - i] = matrix[i][matrix.length - 1 - k];
matrix[i][matrix.length - 1 - k] = temp;
}
}
return matrix;
JavaScript
복사
그리고 가독성이나 성능이 조금 더 높은게 있는데 이건 먼저 전치행렬화를 하는데 행과 열을 뒤바꾸는 게 아니라
이렇게 이동시킨다. 그리고 for문 하나로 위 아래로 뒤바꾼다.
// # : 3
const N = matrix.length;
for (let i = 0; i < N; i++) {
for (let j = 0; j < N - i; j++) {
[matrix[i][j], matrix[N - 1 - j][N - 1 - i]]
= [matrix[N - j - 1][N - i - 1], matrix[i][j]];
}
}
for (let i = 0; i < N / 2; i++) {
[matrix[i], matrix[N - 1 - i]] = [matrix[N - 1 - i], matrix[i]];
}
JavaScript
복사
소스코드
/**
* @param {number[][]} matrix
* @return {void} Do not return anything, modify matrix in-place instead.
*/
const rotate = function (matrix) {
// # : 1
// for (let k = 0; k < matrix.length / 2; k++) {
// for (let i = k; i < matrix.length - 1 - k; i++) {
// const temp = matrix[k][i];
// matrix[k][i] = matrix[matrix.length - 1 - i][k];
// matrix[matrix.length - 1 - i][k] = matrix[matrix.length - 1 - k][matrix.length - 1 - i];
// matrix[matrix.length - 1 - k][matrix.length - 1 - i] = matrix[i][matrix.length - 1 - k];
// matrix[i][matrix.length - 1 - k] = temp;
// }
// }
// return matrix;
// # : 2
const N = matrix.length;
// & : i <-> j : 전치행렬화, 행과 열을 swap
for (let i = 0; i < N; ++i) {
for (let j = i; j < N; ++j) {
[matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
}
}
console.log(matrix);
for (let i = 0; i < N; ++i) {
for (let j = 0; j < N / 2; ++j) {
console.log(i, j, '/', i, N - j - 1, '/', matrix[i][j], matrix[i][N - j - 1]);
[matrix[i][j], matrix[i][N - j - 1]] = [matrix[i][N - j - 1], matrix[i][j]];
}
}
console.log(matrix);
return matrix;
};
// test('TC1', () => {
// expect(
// rotate([
// [5, 1, 9, 11],
// [2, 4, 8, 10],
// [13, 3, 6, 7],
// [15, 14, 12, 16],
// ])
// ).toStrictEqual([
// [15, 13, 2, 5],
// [14, 3, 4, 1],
// [12, 6, 8, 9],
// [16, 7, 10, 11],
// ]);
// });
test('TC2', () => {
expect(
rotate([
[5, 1, 9, 11, 17],
[2, 4, 8, 10, 18],
[13, 3, 6, 7, 19],
[21, 22, 23, 24, 25],
[15, 14, 12, 16, 20],
])
).toStrictEqual([
[15, 21, 13, 2, 5],
[14, 22, 3, 4, 1],
[12, 23, 6, 8, 9],
[16, 24, 7, 10, 11],
[20, 25, 19, 18, 17],
]);
});
// test('TC3', () => {
// expect(rotate([[1]])).toStrictEqual([[1]]);
// });
// test('TC4', () => {
// expect(
// rotate([
// [1, 2],
// [3, 4],
// ])
// ).toStrictEqual([
// [3, 1],
// [4, 2],
// ]);
// });
JavaScript
복사