diff --git a/longest-substring-without-repeating-characters/wogha95.js b/longest-substring-without-repeating-characters/wogha95.js new file mode 100644 index 000000000..37b1c7a38 --- /dev/null +++ b/longest-substring-without-repeating-characters/wogha95.js @@ -0,0 +1,51 @@ +/** + * TC: O(S) + * right의 S만큼 순회 + left의 S만큼 순회 + * (각 순회의 곱이 아닌 합인 이유는 right 순회 동안 left의 최대 순회가 S이기 때문입니다.) + * + * SC: O(S) + * usedCharacter에 S만큼 들어갈 수 있습니다. + * + * S: s.length + */ + +/** + * @param {string} s + * @return {number} + */ +var lengthOfLongestSubstring = function (s) { + // 1. 사용된 문자를 기록하기 위한 set + const usedCharacter = new Set(); + + // 2. 정답 제출을 위한 부분문자열 최대 길이 + let maxLength = 0; + + // 3. 순회를 위한 포인터 + 각 index에서 최대 문자열길이를 구하기 위한 변수 + let left = 0; + let right = 0; + + while (left <= right && right < s.length) { + // 4. [right] 문자가 사용되었으면 + if (usedCharacter.has(s[right])) { + // 5. 사용된 문자를 발견하기 전까지 left 이동 (+ 사용된 [left] 문자 기록 제거) + while (s[left] !== s[right]) { + usedCharacter.delete(s[left]); + left += 1; + } + + // 6. [right] 문자와 [left] 문자가 동일하므로 left만 이동 + left += 1; + } else { + // 7. [right] 문자가 미사용되었으면 기록 추가 + usedCharacter.add(s[right]); + } + + // 8. 중복없는 부분문자열 최대 길이 갱신 + maxLength = Math.max(maxLength, right - left + 1); + + // 9. 다음 문자로 이동 + right += 1; + } + + return maxLength; +}; diff --git a/number-of-islands/wogha95.js b/number-of-islands/wogha95.js new file mode 100644 index 000000000..cf5d9b075 --- /dev/null +++ b/number-of-islands/wogha95.js @@ -0,0 +1,80 @@ +/** + * TC: O(ROW * COLUMN) + * 주어진 grid 배열 전체 순회 + (최악의 경우 queue에서 grid 전체 순회) + * + * SC: O(ROW * COLUMN) + * queue에서 최대 grid만큼 순회 + * + * ROW: grid.length, COLUMN: grid[0].length + */ + +/** + * @param {character[][]} grid + * @return {number} + */ +var numIslands = function (grid) { + const LAND = "1"; + const VISITED_LAND = "#"; + const ROW = grid.length; + const COLUMN = grid[0].length; + + // 1. 상하좌우 방향키 + const DIRECTION = [ + { r: 0, c: 1 }, + { r: 1, c: 0 }, + { r: 0, c: -1 }, + { r: -1, c: 0 }, + ]; + + let numberOfIslands = 0; + + // 2. 전체 순회하면서 + for (let row = 0; row < ROW; row++) { + for (let column = 0; column < COLUMN; column++) { + // 3. LAND를 발견하면 방문한 섬으로 표시(bfs)하고 섬갯수 갱신 + if (grid[row][column] === LAND) { + bfs(row, column); + numberOfIslands += 1; + } + } + } + + return numberOfIslands; + + function bfs(startRow, startColumn) { + // 4. 시작좌표 queue에 넣고 방문 표시 + const queue = [[startRow, startColumn]]; + grid[startRow][startColumn] = VISITED_LAND; + + while (queue.length > 0) { + const [row, column] = queue.shift(); + + // 5. 상하좌우의 좌표를 가지고 + for (const direction of DIRECTION) { + const nextRow = row + direction.r; + const nextColumn = column + direction.c; + + // 6. 유효한 좌표 && 미방문 육지인지 확인 + if ( + isValidPosition(nextRow, nextColumn) && + grid[nextRow][nextColumn] === LAND + ) { + // 7. queue에 추가하고 방문 표시 + grid[nextRow][nextColumn] = VISITED_LAND; + queue.push([nextRow, nextColumn]); + } + } + } + } + + // 8. 주어진 2차원 배열의 유효한 좌표인지 확인하는 함수 + function isValidPosition(row, column) { + if (row < 0 || ROW <= row) { + return false; + } + if (column < 0 || COLUMN <= column) { + return false; + } + return true; + } +}; diff --git a/reverse-linked-list/wogha95.js b/reverse-linked-list/wogha95.js new file mode 100644 index 000000000..d294ec1ac --- /dev/null +++ b/reverse-linked-list/wogha95.js @@ -0,0 +1,69 @@ +/** + * 2차 + * Tony님 풀이 참고해서 SC 개선 + * + * TC: O(N) + * SC: O(1) + * 순회동안 노드 생성하지 않으므로 공간복잡도가 상수다. + * + * N: linked-list length + */ + +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} head + * @return {ListNode} + */ +var reverseList = function (head) { + let pointer = null; + + while (head !== null) { + let temp = head.next; + head.next = pointer; + pointer = head; + head = temp; + } + + return pointer; +}; + +/** + * 1차 + * TC: O(N) + * linked-list 길이 만큼 순회 + * + * SC: O(N) + * linked-list 길이만큼 생성 + * + * N: linked-list length + */ + +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} head + * @return {ListNode} + */ +var reverseList = function (head) { + let pointer = null; + + while (head) { + // 1. 정답 리스트의 맨 앞에 새로운 노드를 추가 + pointer = new ListNode(head.val, pointer); + // 2. head는 다음 노드로 이동 + head = head.next; + } + + return pointer; +}; diff --git a/set-matrix-zeroes/wogha95.js b/set-matrix-zeroes/wogha95.js new file mode 100644 index 000000000..2549c9454 --- /dev/null +++ b/set-matrix-zeroes/wogha95.js @@ -0,0 +1,46 @@ +/** + * TC: O(ROW * COLUMN) + * SC: O(1) + */ + +/** + * @param {number[][]} matrix + * @return {void} Do not return anything, modify matrix in-place instead. + */ +var setZeroes = function (matrix) { + const ROW = matrix.length; + const COLUMN = matrix[0].length; + const MARK = "#"; + + // 1. 0인 요소의 가로, 세로를 특정문자로 변경 + for (let row = 0; row < ROW; row++) { + for (let column = 0; column < COLUMN; column++) { + if (matrix[row][column] === 0) { + changeToMark(row, column); + } + } + } + + // 2. 특정문자를 모두 0으로 변경 + for (let row = 0; row < ROW; row++) { + for (let column = 0; column < COLUMN; column++) { + if (matrix[row][column] === MARK) { + matrix[row][column] = 0; + } + } + } + + // 3. 특정 좌표의 가로, 세로를 char문자로 변경 (대신 0인 요소는 변경하지 않음) + function changeToMark(row, column) { + for (let r = 0; r < ROW; r++) { + if (matrix[r][column] !== 0) { + matrix[r][column] = MARK; + } + } + for (let c = 0; c < COLUMN; c++) { + if (matrix[row][c] !== 0) { + matrix[row][c] = MARK; + } + } + } +}; diff --git a/unique-paths/wogha95.js b/unique-paths/wogha95.js new file mode 100644 index 000000000..9d4ca1a73 --- /dev/null +++ b/unique-paths/wogha95.js @@ -0,0 +1,127 @@ +/** + * 3차 (시간, 공간 복잡도 개선) + * 동일한 down방향, right방향들 중에서 나열하는 방법 + * 즉, ((m - 1) + (n - 1))! / ((m - 1)! * (n - 1)!) + * (+ 팩토리얼의 수는 매우 빠르게 커지므로 중간 나눗셈이 가능할때마다 나누어서 integer 범위를 넘지 않도록 방지) + * + * TC: O(M + N) + * 1부터 최대 (M - 1) + (N - 1)까지 순회 + * + * SC: O(1) + * 계산의 결과 변수가 m, n과 무관하므로 상수의 공간복잡도 + */ + +/** + * @param {number} m + * @param {number} n + * @return {number} + */ +var uniquePaths = function (m, n) { + // 1. down방향, right방향의 수 + const NUMBER_OF_DOWN = m - 1; + const NUMBER_OF_RIGHT = n - 1; + + // 2. factorial 계산을 위한 변수 + let result = 1; + let factorialOfDown = 1; + let factorialOfRight = 1; + + // 3. 'down방향 수 + right방향 수'만큼 순회하면서 + for (let number = 1; number <= NUMBER_OF_DOWN + NUMBER_OF_RIGHT; number++) { + result *= number; + + // 4. factorial 값들이 커지지 않도록 나눌수 있을때마다 나눔 (factorial of down) + if (number <= NUMBER_OF_DOWN) { + factorialOfDown *= number; + if (result % factorialOfDown === 0) { + result /= factorialOfDown; + factorialOfDown = 1; + } + } + + // 5. factorial 값들이 커지지 않도록 나눌수 있을때마다 나눔 (factorial of right) + if (number <= NUMBER_OF_RIGHT) { + factorialOfRight *= number; + if (result % factorialOfRight === 0) { + result /= factorialOfRight; + factorialOfRight = 1; + } + } + } + + return result / factorialOfDown / factorialOfRight; +}; + +/** + * 2차 (공간복잡도 개선) + * 이전 풀이에서 모든 행의 경로수를 기억할 필요가 없는 점을 활용 + * + * TC: O(M * N) + * 경로 수를 기록하기 위한 N배열 순회 * (M - 1) + * + * SC: O(N) + * 경로수 기록을 위한 1차원 배열 + */ + +/** + * @param {number} m + * @param {number} n + * @return {number} + */ +var uniquePaths = function (m, n) { + // 1. 최상단의 경로수는 모두 1 + const numberOfPaths = new Array(n).fill(1); + + for (let row = 1; row < m; row++) { + // 2. 각 좌표의 경로수는 현좌표(1차 풀이의 row-1)와 좌측좌표(1차 풀이의 column-1)의 합 + for (let column = 1; column < n; column++) { + numberOfPaths[column] += numberOfPaths[column - 1]; + } + } + + return numberOfPaths[n - 1]; +}; + +/** + * 1차 + * 각 좌표의 경로수를 기록하여 dp로 풀이 + * 현좌표까지의 경로수 = 상단좌표에서 온 경우 + 좌측좌표에서 온 경우 + * dp[row][column] = dp[row - 1][column] + dp[row][column - 1] + * + * + * TC: O(M * N) + * 경로수를 기록한 2차원 배열을 전체 순회 + * + * SC: O(M * N) + * 경로수 기록을 위한 2차원 배열 + */ + +/** + * @param {number} m + * @param {number} n + * @return {number} + */ +var uniquePaths = function (m, n) { + // 1. 각 좌료까지의 경로수를 기록하기 위한 배열 + const numberOfPaths = new Array(m).fill(new Array(n).fill(0)); + + // 2. 최좌측에 있는 좌표의 경로수는 1 + for (let row = 0; row < m; row++) { + numberOfPaths[row][0] = 1; + } + + // 3. 최상단에 있는 좌표의 경로수는 1 + for (let column = 0; column < n; column++) { + numberOfPaths[0][column] = 1; + } + + // 4. 그 외 각 좌표는 바로 위 좌표(column-1)와 바로 왼쪽 좌표(row-1)의 경로수의 합 + for (let row = 1; row < m; row++) { + for (let column = 1; column < n; column++) { + numberOfPaths[row][column] = + numberOfPaths[row - 1][column] + numberOfPaths[row][column - 1]; + } + } + + return numberOfPaths[m - 1][n - 1]; +};