객체를 이용해 데이터 값과 값의 빈도 수를 세는 패턴이다.
2개의 배열 arr1
과 arr2
가 주어질 때, arr2
의 모든 요소가 arr1
요소의 제곱에 해당하는 값을 가지면(정렬은 상관 없음) true
를 반환하는 same
함수를 작성하십시오.
same([1, 2, 3], [4, 1, 9]) // true
same([1, 2, 3], [1, 9]) // false
same([1, 2, 1], [4, 4, 1]) // false
시간 복잡도 O(n^2)
function same(arr1, arr2) {
if(arr1.length !== arr2.length){ // 애초에 배열의 길이가 다르면 false 반환
return false;
}
for(let i = 0; i < arr1.length; i++){ // for 반복문 => O(n)
let correctIndex = arr2.indexOf(arr1[i] ** 2) // Array.indexOf() => 중첩 루프 O(n^2)
if(correctIndex === -1){ // 배열에 원하는 요소가 없으면 false 반환
return false;
}
arr2.splice(correctIndex, 1) // 배열에 해당 요소가 있으면 해당 요소를 없애기
}
return true; // 빈 배열이면 true 반환
}
시간 복잡도 O(n), 2개의 개별 루프가 중첩된 루프보다 낫다.
function same(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
let frequencyCounter1 = {}
let frequencyCounter2 = {}
for (let val of arr1) { // for 반복 => O(n)
frequencyCounter1[val] = (frequencyCounter1[val] || 0) + 1
}
for (let val of arr2) { // for 반복 => O(2n)
frequencyCounter2[val] = (frequencyCounter2[val] || 0) + 1
}
for (let key in frequencyCounter1) { // for 반복 => O(3n)
if (!(key ** 2 in frequencyCounter2)) {
return false
}
if (frequencyCounter2[key ** 2] !== frequencyCounter1[key]) {
return false
}
}
return true
}
2개의 문자열 str1
과 str2
가 주어질 때, str2
가 str1
의 애너그램이면 true
아니면 false
를 반환하는 validAnagram
함수를 작성하십시오.
validAnagram('', '') // true
validAnagram('cinema', 'iceman') // true
validAnagram('aaz', 'zza') // false
validAnagram('car', 'ar') // false
function validAnagram(str1, str2) {
let arr1 = str1.split('')
let arr2 = str2.split('')
if (arr1.length !== arr2.length) {
return false
}
const frequencyCounter = {}
for (let val of arr1) {
// 문자가 있으면 값에 1을 더하고, 없으면 값을 1로 설정
frequencyCounter[val] ? frequencyCounter[val] += 1 : frequencyCounter[val] = 1
}
for (let val of arr2) {
// 문자를 찾을 수 없거나 값이 0이면 false 반환
if (!frequencyCounter[val]) {
return false
} else {
// 문자를 찾았으면 값에 1을 빼기
frequencyCounter[val] -= 1
}
}
return true
}
인덱스나 위치에 해당하는 포인터나 값을 만든 다음, 조건에 따라 두 개의 포인터(변수)가 특정 방향으로 움직이도록 한다.
두 개의 포인터가 양 끝점에서 시작해 중간 지점을 향해 이동하도록 하거나 두 개의 포인터가 한 끝점(시작이나 끝 지점)에서 시작해 한 방향을 향해 같이 이동하게 하여 문제를 해결하는 패턴이다.
오름차순으로 정렬된 정수 배열인 arr
가 주어질 때, 배열의 두 요소 합이 0이 되는 요소를 반환하거나 0이 되는 요소가 없으면 undefined
를 반환하는 sumZero
함수를 작성하십시오.
sumZero([-3, -2, -1, 0, 1, 2, 3]) // [3, -3]
sumZero([-2, 0, 1, 3]) // undefined
sumZero([1, 2, 3]) // undefined
시간 복잡도 O(n^2), 공간 복잡도 0(1)
function sumZero(arr) {
for(let i = 0; i < arr.length; i++){ // for 반복 => O(n)
for(let j = i+1; j < arr.length; j++){ // for 중첩 반복 => O(n^2)
if(arr[i] + arr[j] === 0){
return [arr[i], arr[j]];
}
}
}
}
시간 복잡도 O(n), 공간 복잡도 O(1)
function sumZero(arr) {
// 왼쪽은 0부터 시작
let left = 0;
// 오른쪽은 마지막 인덱스(배열 길이 - 1)부터 시작
let right = arr.length - 1;
// 왼쪽이 오른쪽보다 작으면 반복 실행
while (left < right) { // while 반복 => O(n)
let sum = arr[left] + arr[right];
// 두 수의 합이 0이면 배열을 반환
if (sum === 0) {
return [arr[left], arr[right]];
}
// 합이 0보다 크면 오른쪽이 -1칸 이동
else if (sum > 0) {
right--;
}
// 합이 0보다 작으면 왼쪽이 +1칸 이동
else {
left++;
}
}
}
오름차순으로 정렬된 정수 배열인 arr
가 주어질 때, 배열의 고유한 숫자 개수를 반환하는 countUniqueValues
함수를 작성하십시오.
countUniqueValues([1, 1, 1, 1, 1, 2]) // 2
countUniqueValues([1, 2, 3, 4, 4, 4, 7, 7, 12, 12, 13]) // 7
countUniqueValues([]) // 0
countUniqueValues([-2, -1, -1, 0, 1]) // 4
function countUniqueValues(arr) {
if (arr.length === 0) return 0
let i = 0;
for (let j = 1; j < arr.length; j++) {
// 인덱스 i 위치의 수와 j 위치의 수를 비교하여 다를 경우에만 i 위치를 +1칸 이동한 후,
// j 위치의 수로 변경하고 계속 비교 진행
if (arr[i] !== arr[j]) {
i++;
arr[i] = arr[j];
}
}
return i + 1;
}
특정 방식으로 연속되는 데이터의 하위 집합을 찾는데 유용한 패턴이다.
정수 배열 arr
와 숫자 n
이 주어질 때, n
개의 연속된 요소들의 합계 중 가장 큰 합계를 찾아 반환하는 maxSubarraySum
함수를 작성하시오.
maxSubarraySum([1, 2, 5, 2, 8, 1, 5], 2) // 10
maxSubarraySum([1, 2, 5, 2, 8, 1, 5], 4) // 17
maxSubarraySum([4, 2, 1, 6], 1) // 6
maxSubarraySum([], 4) // null
시간 복잡도 O(n^2)
function maxSubarraySum(arr, num) {
if (num > arr.length) {
return null;
}
let max = -Infinity;
for (let i = 0; i < arr.length - num + 1; i++) { // for 반복 => O(n)
let temp = 0;
for (let j = 0; j < num; j++) { // for 중첩 반복 => O(n^2)
temp += arr[i + j];
}
// max보다 더 큰 합계가 나오면 max를 temp로 업데이트
if (temp > max) {
max = temp;
}
}
return max;
}
시간 복잡도 O(n)
function maxSubarraySum(arr, num) {
let maxSum = 0;
let tempSum = 0;
// 배열의 길이가 num 보다 작으면 null을 반환
if (arr.length < num) return null;
for (let i = 0; i < num; i++) {
maxSum += arr[i];
}
tempSum = maxSum;
for (let i = num; i < arr.length; i++) {
// tempSum에 이전 수를 빼주고 다음 수를 더해줘서 배열을 오른쪽으로 한 칸씩 이동하며 더해줌
tempSum = tempSum - arr[i - num] + arr[i];
// maxSum과 tempSum 중 더 큰 수를 maxSum에 업데이트
maxSum = Math.max(maxSum, tempSum);
}
return maxSum;
}
주로 배열이나 문자열 같은 큰 규모의 데이터셋을 처리하는 패턴이다. 큰 데이터 덩어리를 작은 조각으로 나누어 생각한다.
오름차순으로 정렬된 숫자 배열 arr
과 정수 val
이 주어질 때, 배열 arr
에서 val
의 인덱스를 찾아 반환하거나 배열에 val
이 없으면 -1을 반환하는 search
함수를 작성하시오.
시간 복잡도 O(n)
function search(arr, val) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === val) {
return i;
}
}
return -1;
}
시간 복잡도 O(log n), 배열을 분할하여 이진 탐색하여 탐색해야 할 요소의 범위를 줄인다.
function search(arr, val){
let min = 0;
let max = arr.length - 1;
while(min <= max){
let middle = Math.floor((min + max) / 2);
if(arr[middle] < val) {
min = middle + 1;
}
else if(arr[middle] > val){
max = middle - 1;
}
else {
return middle;
}
}
return -1;
}
✍️ <JavaScript 알고리즘 & 자료구조 마스터클래스> 강의를 들으며 알게 된 내용을 정리하였습니다.