어제는 장염에 걸려 드러누워 있느라 공부를 하지 못했다. 평일에 누워있던 건 제법 오랜만이었던 것 같다. 어쨌거나 그런 덕분에 안타깝게도 챌린지반 수업을 실시간으로 듣질 못했다. 과제도 있었는데 내용을 모르니 영 감을 잡기가 어려웠다. 그래서 원래 오늘 오전에 강의를 보려고 했는데 녹화본이 아직 올라오지를 않아서 볼수가 없었다.
그래서 오늘은 주말과 공가를 지내며 잃었던 감을 다시 잡는 시간을 갖기로 했다.
아침에 풀었던 알고리즘 문제는 크게 어렵지 않아서 나름대로 수월하게 했으나, 감도 찾을 겸 밀린 문제도 따라잡을 요량으로 풀었던 문제는 생각보다 까다로워서 쩔쩔 맸다.
첫번째는 3진법 뒤집기 문제였다.
말로는 간단해보였으나 1. n을 3진수로 표현한다. 2. 3진수의 순서를 뒤집는다. 3. 그걸 다시 10진수로 바꾼다. 라는 공정을 거쳐야했다.
여기서 코딩의 자료형 측면으로 보자면 1. n을 3진수로 표현한다. 2. 3진수의 자료형을 MutableList로 바꾼다. 3. reversed를 사용하여 순서를 바꾼다. 4. Int형으로 바꾼다. 5. 10진수로 표현한다. 라는 식으로 두 가지를 더 신경써줘야 했던 것이다.
여기까진 좋았으나 이 다음에 나의 삽질이 시작됐다. 분명, 분명 3진법 함수가 있을것 같기는 한데, 3진법 구현 그거 어렵나? 그냥 이렇게 저렇게 하면 될 것 같은데? 하는 생각이 들었던 게 큰 실수였던 것이다. 머릿속에 구상이 되는 3진수 표현법을 끝끝내 포기하지 못하고 꾸역꾸역 적은 것이 바로 아래 코드다.
import kotlin.math.*
class Solution {
fun solution(n: Int): Int {
var answer: Int = 0
var btwn: Int = n
var trit = MutableList(18) { 0 }
for (i in 0..17) {
for (j in 0..17) {
if (btwn < (3.0).pow(j)) {
if (btwn >= (2 * (3.0).pow(j - 1))) {
trit[i] = 2
} else if (btwn >= (3.0).pow(j - 1)) {
trit[i] = 1
}
btwn -= (trit[i] * (3.0).pow(j - 1).toInt())
break
}
}
}
for (i in trit.indices) {
answer += (trit[i] * (3.0.pow(i))).toInt()
}
return answer
}
}
17, 18과 같은 애매한 숫자가 들어간 것은 n 값이 최대 1억이었기에 1억의 3진수를 찾아보고 몇자리까지 있는지 확인한 다음에 정한 값이었다.
일단 3진수의 자릿수를 i라고 하면 최대 17번까지 i의 값을 정해야하므로 for문을 돌렸다. 그 다음 그 값을 정하기 위해 3.0.pow(j)의 값보다 n(코드 내에선 btwn)값이 커지는 시점을 찾으려고 for문을 또 돌렸다. 그렇게 해서 3의 j제곱이 n보다 클때, 3의 j-1제곱이 최대 2개까지 들어갈 수 있으므로 2개가 되는지, 1개가 되는지를 판별하기 위해 if를 또 썼다.
그렇게 3진수값을 채우고, 두번째 for문 안에서 다음 3진수 값을 마련하기 위해 n값에 방금 계산한 3의 j-1 제곱이 들어가는 만큼을 빼주어 다시 계산할 수 있게 만들었다. 그리고 break.
2진수 값을 다 채웠으면 10진수로 바꾸는 과정이다. 사실 중간에 reverse를 해야된다 생각했으나 index를 0부터 채워넣는 바람에 순서상으로는 완벽한 3진수를 만들었으나 내가 10진수로 다시 계산할 때 강제로 반대로 계산되는 모양새가 나왔다.
원래라면 0번째가 가장 큰 수이므로 size-1제곱을 해야할 텐데 for문으로 쉽게 계산하려면 i번째에 i제곱을 하는 것 아니겠는가. 그리고 그건 정확하게 순서가 반대인 값이므로 오히려 한 공정을 덜었다 싶었다. 그래서 만족스럽게 마무리를 했으나..
채점에서 실패했다. 이유를 도무지 알 수가 없었다. 그래서 포기했다. 너무 억울했지만 어쩌겠는가.
class Solution {
fun solution(n: Int): Int {
var answer: Int = 0
answer = n.toString(3)
.map { it }
.reversed()
.joinToString("")
.toInt(3)
return answer
}
}
이렇게 깔끔하게 끝낼 수 있었던 녀석이었다. 여기서 .toString(3)은 앞 녀석을 3진수로 바꿔주는 함수고 .toInt(3)은 앞의 녀석이 3진수로 표현되어있다고 가정하고 10진수의 값을 리턴하는 녀석이다. 좋은 함수를 알아가서.... 좋았다...
다음으로 푼 문제는 이상한 문자 만들기라는 이름처럼 정말 이상한 문제였다.
사실 처음에 봤을 때는 그리 어려워보이지 않았다. 대략적으로 금방 구상이 되었다. 예전에 사용했던 substring()이라는 녀석으로 문자열을 단어별로 나눈 다음에 나눈 각각의 문자열에서 인덱스값이 짝수인걸 대문자로 바꾸면 되겠다고 여겼다. 하지만 역시 현실은 만만하지 않았고 내가 생각한대로 흘러가지만은 않았다.
class Solution {
fun solution(s: String): String {
var answer = ""
var words = s.split(" ").toMutableList()
var word = mutableListOf<Char>()
for (str in words) {
word = str.toMutableList()
for (idx in str.indices) {
if (idx % 2 == 0) {
word[idx] = str[idx].uppercaseChar()
} else {
word[idx] = str[idx].lowercaseChar()
}
}
words[words.indexOf(str)] = word.joinToString("")
}
answer = words.joinToString(" ")
return answer
}
}
일단 substring()은 문자열을 잘라내는거지 잘라낸 부분까지 다시 리턴하지는 않는다. 여기서 알게된 것이 split이라는 녀석이었는데, 이건 특정 문자를 기준으로 문자를 나눠서 그걸 list 형태로 죄다 리턴해줬다. 옳다구나, 싶어서 substring()대신 써줬고 다음은 for문을 돌릴 차례였다.
먼저 s를 split으로 나눈 값을 넣은 word를 돌도록 했다. 그래서 단어를 하나하나 꺼내왔고, 다음은 그 각 단어의 index값을 돌도록 했다. 그러면 간단하게 for문을 돌고있는 word의 요소값의 inx값, 즉 코드 상에서는 str[idx]을 바꿔주면 되겠다고 생각했다.
하지만 역시나 그건 불가능했다. 그놈의 mutable이니 var이니 지긋지긋해 죽겠다. for문에서 요소로 사용하는것은 딱 느낌이 val 이거나 그런 라인이라는 느낌이었다. 그래서 여기서 정말 많이 헤맸다. words의 요소를 만드려니 그걸 어떻게 나눠서 만들지 싶고 여러모로 복잡했다.
어쨌거나 지금 코드를 보면 처음부터 word를 mutableList로 만들어서 for문이 시작될 때 word에 str을 mutableList로 바꾸어 할당했다. 그렇게 하니 word에 값을 바꾸면 되니 편하고 좋았다. 대신 코드가 조금 지저분해진 것 같아서 아쉬움이 남긴 한다. 더 최적화가 되지 않을까 싶은 아쉬움이 있긴하다.
아무튼 알고리즘 문제를 더 많이 풀 생각이었는데 예상치 못하게 발목이 잡혀서 시간을 많이 소모했다. 내일은 공부 위주로 열심히 해야겠다.