4.4프로그래머스 Lv.0 #31~40
프로그래머스 Lv.0 31~40번
개요
기초 트레이닝 문제(Lv.0) 31 ~ 40번 문제 풀이 중 배울 점이 있는 풀이들에서 배운 개념들을 내 것으로 만들고자 정리해본다.
31. 수열과 구간 쿼리 4
하나의 배열과, 쿼리들이 담긴 배열이 주어지면 쿼리들이 담긴 배열을 이용해 배열의 몇번째에서 몇번째까지에 쿼리에 입력한 k의 배수번째 인덱스들에 1을 더한 배열을 반환하라는 문제다. 나는 이렇게 풀었다:
javascriptfunction solution(arr, queries) { queries.forEach(([s, e, k])=> { for(let i = s; i < e + 1; i++){ i % k === 0 && arr[i]++; } }) return arr; }
31-1. for...of
를 활용한 풀이
javascriptfunction solution(arr, queries) { for(let [s, e, k] of queries) { for(let i = s; i <= e; i++) { if(i % k === 0) arr[i]++; } } return arr; }
forEach
만 쓰던 내게 for... of
가 신선하게 보였다. 또, 새삼스럽지만 for
문에 i < e + 1
보다 i <= e
가 더 나아보이기도 했고.
31-2. 또또또 reduce
reduce
를 정형화된 패턴으로만(그놈의 (acc, cur) => {...}
) 쓰는 내게 이런 식의 풀이를 보는 것은 사고의 확장을 가져다 준다.
javascriptfunction solution(arr, queries) { return queries.reduce((bucket, [s, e, k]) => { for (let i = s; i <= e; i += 1) { if (i % k === 0) bucket[i] += 1 } return bucket }, [...arr]) }
31-3. 신기한 for
문
이 답은 처음에 보고 "이게 된다고?"했던 답이다.
javascriptfunction solution(arr, queries) { queries.forEach(([s, e, k]) => { for (; s <= e; s++) { !(s % k) && arr[s]++; } }); return arr; }
!(s % k)
로 k의 배수일때라는 조건을 나타낸 것도 흥미롭지만(k로 나누어떨어지면 0이되니 !
를 붙여 !0 = true로 만들고, 나머지는 false로 만드는게 흥미롭다. 물론 가독성은 떨어진다고 생각한다.), 더 재밌다고 생각한 부분은 역시 for
조건문.
javascriptfor (초기식; 조건식; 증감식) { // 실행할 코드 }
여기서 초기식, 조건식, 증감식 세 부분 모두 생략 가능하다고 한다. 다만, 세미콜론(;)
두 개는 항상 있어야 한다고 한다. 따라서 아래 코드 둘은 완전히 같다.
javascriptfor(; s <= e; s++){ // ... } for(let i = s; i <= e; i++){ // ... }
굳이 변수를 안 만드는 선택을 한 건 물론 이해되지만, 개인적으로 생략하는 방식은 가독성이 좋지는 않다고 생각한다.(그냥 내가 안 익숙해서 그렇기도 하겠지만) 하지만 알아둬서 나쁠건 없다.
32. 배열 만들기 2
배열 만들기 2는 문제를 풀어내는 것 자체는 단순해서 남들의 답이 더 기대되었다.
javascriptfunction solution(l, r) { const answer = []; for(let i= l; i<=r ;i++){ if(`${i}`.split("").every(value => value === "5" || value === "0")){ answer.push(i); } } if(!answer.length) return [-1]; return answer; }
32-1. RegExp
RegExp.test
가 떠오를만한 문제다.
아래 답의 /^[05]+$/
는, 각각 이런 의미다:
^
: 문자열 시작[05]
: 문자 클래스. 0 또는 5 중 하나.+
: 앞의[05]
가 하나 이상 반복$
: 문자열 끝
문자열이 0 또는 5로만 이루어져있고, 하나 이상 포함되어 있는 경우에만
true
javascriptfunction solution(l, r) { const answer = []; for (let i = l; i <= r; i++) { if (/^[05]+$/.test(i)) { answer.push(i); } } return answer.length ? answer : [-1]; }
32-2. 제너레이터 함수(generator function)
특이한 구문을 봐서 공부하고자 적는다. 참고로, toString(2)
는 숫자를 2진수 문자열로 변환한다.
javascriptfunction* gen50() { let i = 1; while(true) { yield Number(Number(i).toString(2)) * 5; i++; } } function solution(l, r) { const n = gen50(); let a = 0; const arr = []; while(a < l) { a = n.next().value; } while(a <= r) { arr.push(a); a = n.next().value; } return arr.length ? arr : [-1]; }
function*
는 제너레이터 함수, 즉 중간에 실행을 멈췄다가 다시 시작할 수 있는 함수를 정의할 때 쓰는 표현이다. 여기서 yield
는 일반함수와 달리 값을 즉시 반환하지 않고, 값을 하나씩 내보내며 실행을 일시 중단시킨다.
제너레이터 함수는 제너레이터 객체를 반환하고, next()
로 값을 하나씩 꺼낼 수 있다.
javascriptfunction* gen() { yield 1; yield 2; yield 3; } const g = gen(); console.log(g.next()); // { value: 1, done: false } console.log(g.next()); // { value: 2, done: false } console.log(g.next()); // { value: 3, done: false } console.log(g.next()); // { value: undefined, done: true }
뭐하려고 이런 문법이 존재하냐?라고 하면, 값을 순차적으로 하나씩 계산하고 싶을 때 메모리를 덜 사용할 수 있다고 한다. 즉, lazy evaluation(필요할 때만 계산)을 가능하게 해주는 것.
34. 콜라츠 수열
자연수 x에 대해 짝수이면 2로 나누고, 홀수면 3x + 1로 바꿔서 계산하면 언젠가는 x가 1이되고, 이 과정에서 거친 모든 수를 콜라츠 수열이라고 한다. x를 받아 콜라츠 수열을 반환하는 문제다. 나는 이렇게 풀었다:
javascriptfunction solution(n) { const answer = []; while(true){ if(n === 1) { answer.push(1); break; } if(n % 2 === 0){ answer.push(n) n = n / 2; } else { answer.push(n) n = 3 * n + 1; } } return answer; }
다른 풀이로 들어가기에 앞서, 한가지 중요한걸 짚고 넘어간다. 처음에 풀때는, n
을 받아 이걸 일단 보존하기 위해 let k = n
이라고 k라는 변수를 만들어 이걸 while
문으로 실행하며 변경하고 배열에 추가했는데, 그럴 필요가 없다.
함수 파라미터로 받은 n
은 값으로 전달(pass by value)된다. 자바스크립트의 원시 타입은 함수에 전달될때 값이 복사되어 함수 내부로 전달되기 때문에, 외부의 값을 변화시키지 않는다.
javascriptfunction solution(n) { n = 10; // 여기서 n을 바꿔도 } let x = 5; solution(x); console.log(x); // 여전히 5임
앞서 원시 타입이라고 제한한점에서 알 수 있껬지만, 객체는 참조(reference)로 전달되어서 함수 안에서 변경하면 원본도 바뀐다.
javascriptfunction mutate(arr) { arr.push(999); } const a = [1, 2, 3]; mutate(a); console.log(a); // [1, 2, 3, 999] → 원본도 변경됨
아무튼, 다른 사람들의 콜라츠 수열 풀이를 보자.
34-1. 재귀
하나의 함수로 완전히 재귀 로직을 구현하려면 solution함수가 전달받는 매개변수를 약간 변경해야한다. 즉, array를 추가적으로 받아 해당 array에 값을 추가하는 로직도 넣어야한다.
javascriptfunction solution(n, arr = []) { arr.push(n) if (n === 1) return arr if (n % 2 === 0) return solution(n / 2, arr) return solution(3 * n + 1, arr) }
34-2. 제너레이터 함수(generator function)
32번처럼, 이 문제도 제너레이터 함수로 푼 풀이가 있다.
javascriptfunction* collatz(num) { let value = num; yield value; while (value !== 1) { value = value % 2 ? 3 * value + 1 : value / 2; yield value; } } function solution(n) { return [...collatz(n)]; }
제너레이터 함수는 제너레이터 객체를 반환하고, next()
로 값을 하나씩 꺼낼 수 있다. 따라서 위의 답에서 [...collatz(n)]
을 반환한 것은, 모든 yield 값을 배열로 한꺼번에 펼쳐서 수집하기 위함이다.
35. 배열 만들기 4
문제 자체는 그냥 시킨대로 그대로 코드를 쓰면 풀리는 쉬운 문제다. 앞선 31번 문제에서 for
문의 조건문 생략을 보고 왔기에, 습관적으로 쓰던 i++
를 뒤로 미룰 수 있게 되어 그렇게 코드를 작성했다.
javascriptfunction solution(arr) { const stk = []; for(let i = 0; i < arr.length;){ if(!stk.length || stk[stk.length -1] < arr[i]){ stk.push(arr[i]); i++; } else { stk.pop(); } } return stk; }
35-1. i++
를 표현식 안에서 사용하기
비슷하지만 이렇게 할 수도 있다. i++에 대한 개념을 확실히 이해하고 있어야 자신있게 아래와 같이 쓸 수 있을 것이다.
javascript// 내 코드 stk.push(arr[i]); i++; // 위의 두 코드는 아래 한줄과 같다. stk.push(arr[i++]);
여기서 필요에 따라선 ++i
를 쓸 줄도 알면 좋을것 같다. 전위 연산자로 쓰게 되면, 먼저 i를 증가시키고 그 증가시킨 i가 push된다. 지금처럼 후위 연산자로 쓰면 먼저 i가 push되고, 이후 i가 증가된다.
그냥 단독으로 i++
라던가 ++i
를 쓰면 큰 차이가 없을 수도 있지만, 표현식 안에서 계산된 결과를 사용할 때 차이가 생긴다.
javascript// 후위 연산: 값을 쓰고 이후 증가시킴 let n = 5; let x = n++; // x = 5, n = 6 // 전위 연산: 먼저 증가시키고 값을 씀 let n = 5; let x = ++n; // x = 6, n = 6
35-2. continue로 루프 제어
for
문을 제어할 때, continue
키워드를 활용하고 i++
를 for
조건문에 적을 수도 있다. continue
는 말그대로 아래 코드로 계속 넘어가라는 의미가 아니고, 다음 루프로 넘어가라, 즉 증감식으로 넘어가라는 의미다.
개인적으로 내 풀이가 더 낫다고 생각하지만, continue
의 사용례를 되짚어볼만한 기회라고 생각했다.
javascriptfunction solution(arr) { const stk = []; for(let i=0; i<arr.length; i++) { if(stk.length === 0) { stk.push(arr[i]); continue; } if(stk[stk.length - 1] < arr[i]) { stk.push(arr[i]); continue; } stk.pop(); i--; } return stk; }
37. 주사위 게임 3
문제를 보고 바로 떠오른 것은 지난번에 봤던 Set
이었다. Set.size()
로 주사위의 숫자들이 어떻게 다르게 나왔는지 파악할 수 있었던 지난번 주사위 문제가 생각나서였다.
그런데 이번엔 주사위가 4개였고, 따라서 2/2나 3/1 같이, size는 2인데 서로 다른 케이스가 나오는 경우들이 있었다. 그래서 Set
을 포기해야 한다면... 자연스럽게 떠오른 것이 Map
이었다. 나는 이렇게 풀었다:
javascriptfunction solution(a, b, c, d){ const counts = new Map(); for (const num of [a, b, c, d]){ counts.set(num, (counts.get(num) || 0) + 1); } const entries = [...counts.entries()]; const length = entries.length; switch(length){ case 1: return entries[0][0] * 1111; case 2: const [[n1, c1], [n2, c2]] = entries; if(c1 === 1 || c2 === 1) { if(c1 === 1) return (10 * n2 + n1) ** 2 if(c2 === 1) return (10 * n1 + n2) ** 2 } else { return (n1 + n2) * Math.abs(n1 - n2); } case 3: const [[num1, cnt1], [num2, cnt2], [num3, cnt3]] = entries; if(cnt1 === 2) return num2 * num3; if(cnt2 === 2) return num1 * num3; if(cnt3 === 2) return num1 * num2; case 4: return Math.min(a, b, c, d) } }
37-1. Array를 이용한 풀이
javascriptfunction solution(a, b, c, d) { const dice = [a, b, c, d]; const counter = new Array(7).fill(0); for (let i = 0; i < 4; i++) counter[dice[i]]++; let result = 0; if (counter.includes(4)) { result = 1111 * counter.indexOf(4); } else if (counter.includes(3)) { const p = counter.indexOf(3); const q = counter.indexOf(1); result = (10 * p + q) ** 2; } else if (counter.includes(2) && counter.filter(val => val === 2).length === 2) { const p = counter.indexOf(2); const q = counter.lastIndexOf(2); result = (p + q) * Math.abs(p - q); } else if (counter.includes(2)) { const p = counter.indexOf(2); const q = dice.filter(num => num !== p)[0]; const r = dice.filter(num => num !== p)[1]; result = q * r; } else { result = Math.min(...dice); } return result; }
37-2. Map을 정렬해서 쓰기
javascriptfunction solution(a, b, c, d) { const map = new Map(); for (const data of [a, b, c, d]) { map.set(data, (map.get(data) || 0) + 1); } const sortedArr = [...map].sort((a, b) => {if (a[1] === b[1]) return b[0] - a[0]; else return b[1] - a[1]}); if (map.size === 1) return 1111 * sortedArr[0][0]; else if (map.size === 3) return sortedArr[1][0] * sortedArr[2][0]; else if (map.size === 4) return sortedArr[3][0]; else if (sortedArr[0][1] === 3) return (10 * sortedArr[0][0] + sortedArr[1][0]) ** 2; else return (sortedArr[0][0] + sortedArr[1][0]) * (sortedArr[0][0] - sortedArr[1][0]); }
38. 글자 이어 붙여 문자열 만들기
단순한 문제로, 얼마나 간결하고 가독성 좋게 쓰느냐가 관건인듯하다. 나는 이렇게 풀었다:
javascriptfunction solution(my_string, index_list) { const answer = []; const arr = my_string.split(''); for(let i of index_list){ answer.push(arr[i]) } return answer.join(''); }
38-1. Array.map으로 간단하게 풀이
javascriptfunction solution(my_string, index_list){ return index_list.map(i => my_string[i]).join(''); }
38-2. Array.reduce도 여전히 사용 가능
javascriptfunction solution(my_string, index_list){ return index_list.reduce((result, i) => result + my_string[i], '') }
39. 9로 나눈 나머지
사실 그냥 BigInt
수를 지정하고 9로 나눈 나머지를 구하면 될 것 같은 문제지만, 문제 설명을 따르기로 한다. 나는 일단 배열로 바꾼 다음 reduce
를 썼다. 배열로 일단 바꾸면 문자가 되는 바람에 +cur
로 숫자로 바꿨다.
javascriptfunction solution(number) { return number.split("").reduce((acc, cur)=> acc += +cur, 0) % 9; }
39-1. array로 바꾸는 다른 방법, Array.from
아래와 같이 number.split('')
대신 Array.from(number)
로도 배열로 바꿀 수 있다.
javascriptfunction solution(number) { return Array.from(number).reduce((acc, v) => acc + Number(v), 0) % 9 }
39-2. array로 바꾸는 또 다른 방법, [...string]
[...number]
도 배열로 바꾸는 방법이다. 이번 기회에 문자열을 배열로 바꾸는 방법들을 정리해보았다.
javascriptfunction solution(number) { return [...number].reduce((total, num) => +total + (+num)) % 9; }
40. 문자열 여러 번 뒤집기
문자열 여러 번 뒤집기는 앞서 본 많은 문제들처럼 쿼리를 받아 문자열에 적용하는 문제다.
javascriptfunction reverse(string){ const arr = string.split(""); const newArr = []; for(let i = 0; i< arr.length;i++){ newArr.unshift(arr[i]) } return newArr.join('') } function solution(my_string, queries) { let string = my_string queries.forEach(([s, e])=> { string = string.slice(0, s) + reverse(string.slice(s, e + 1)) + string.slice(e + 1) }) return string; }
40-1. reverse는 이미 있습니다. splice를...
Array.prototype.reverse()
는 이미 존재한다. 따라서, 직접 reverse
를 구현할 필요 없이, 배열로 바꾸고 쓰면 된다.
javascriptfunction solution(my_string, queries) { let str = my_string.split(''); queries.forEach(([start, end]) => { const changeStr = str.slice(start, end + 1); str.splice(start, changeStr.length, ...changeStr.reverse()); }); return str.join(''); }