난이도: lv 1 / 걸린 시간: 60분
내 첫 접근: 주어진 7단계를 이용하기
막혔던 지점:
1~3단계는 순차적으로 해놓고, 4~7단계는 한 번에 처리하려했기에 코드가 꼬였다
내 코드
class Solution {
fun solution(new_id: String): String {
val regex = "[^a-z0-9\\-_.]".toRegex()
val dot = ".."
var answer: String = new_id.lowercase().replace(regex, "")
while (true) {
if (dot in answer) answer = answer.replace(dot, ".")
else break
}
if (answer.first() == '.') answer = answer.substring(1 .. answer.lastIndex)
if (answer.isNotEmpty() && answer.last() == '.') answer = answer.substring(0 until answer.lastIndex)
if (answer.isEmpty()) answer = "a"
if (answer.length > 15) {
answer = answer.substring(0 until 15)
if (answer.last() == '.') answer = answer.substring(0, answer.lastIndex)
} else if (answer.length < 3) {
while (answer.length < 3) {
answer += answer.last()
}
}
return answer
}
}
지문에 이미 순서와 조건이 명확히 적혀있었는데, 코드 구조가 그 순서를 그대로 안 따라갔고,
"단계가 여러 개 있는 문제"를 만나면, 각 단계를 지문 순서 그대로 하나씩 독립적으로 실행되게 짜는 습관을 가지기
해당 문제에 처리 단계인 4단계를 보면 "new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거합니다." 인데, 여기에서
무의식적으로 if - else if 를 이용해서 한 가지만 처리해서 넘겨버린 것이 문제였다.
(ex: .age. 가 테스트 케이스로 들어왔다면, age.로 됐을 것이다 .
"두 조건이 동시에 참일 수 있는가?"를 항상 먼저 의심하는 습관을 갖도록 하자
substring의 "빈 범위" 성질을 우연히 발견 .. 너무 늦게 발견
substring(1..0)처럼 범위 자체가 거꾸로(시작>끝)면, 코틀린이 알아서 빈 문자열로 처리해준 것을 기억하자
난이도: lv 1 / 걸린 시간: 60분
내 첫 접근: startday부터 늘려가면서 토요일, 일요일을 걸러내고 시각 비교하기
막혔던 지점: 855(8시 55분)처럼 분이 50~59 사이일 때 10분을 더하면 시가 넘어가는 부분에서 생각을 못하고 +10 만 해버림
생각 후 문자열리스트로 바꿔서 해볼까도 했는데 아닌거같고..HHMM 형식이라 /100 , %100으로 분리 -> 각각 연산 -> 다시 합치기
실제 사용한 자료구조/패턴: 특별한 자료구조는 없어도 됐고, 시/분을 분리해서 계산하는 산술 처리 + 요일 순환 카운터 조합으로 풀리는 문제
class Solution {
fun solution(schedules: IntArray, timelogs: Array<IntArray>, startday: Int): Int {
var answer: Int = 0
for(i in 0 until schedules.size) {
var hour = schedules[i] / 100
var minute = schedules[i] % 100 + 10
if(minute >= 60) {
hour += 1
minute -= 60
}
val time = hour * 100 + minute
var dayCnt = startday
var cnt = 0
for(j in timelogs[i]) {
if(dayCnt % 8 == 6) {
dayCnt++
continue
} else if(dayCnt % 8 == 7) {
dayCnt = 1
continue
}
dayCnt++
if(j > time) cnt++
}
if(cnt == 0) answer++
}
return answer
}
}
/, % 연산으로 단위별로 분리하는게 더 직관적이다는 것을 배움if(minute+10>60) {...} else {...}
처럼 나눠서 쓰다가 실수했던 것
minute = 원래 분 + 10 먼저 계산해두고 넘침만 체크
난이도: lv 1 / 걸린 시간: 60분
문제 한 줄 요약:
유저들이 서로를 신고하는데, k번 이상 신고당한 사람은 "정지" 되고, 그 사람을 신고했던 사람들 전원에게 메일이 1통씩 간다. 각 유저가 받는 메일 개수를 구하는 문제.
내 첫 접근:
1000x1000 2차원 배열로 관리하려다가, 크기 부담을 느끼고 다른 방법을 고민함실제 사용한 자료구조/패턴:
자료구조: Map<String, MutableSet<String>> (신고자 -> 신고 대상 목록) +
Map<String, Int> (유저 -> 신고당한 횟수
패턴:
1. 방향성 있는 관계는 "누가 누구를"의 방향을 명확히 구분해서 저장 - 트리거 신호: "A가 B를 ~했다"처럼 관계에 방향이 있는 문제 -> Map<신고자, Set<대상>>처럼 key를 누구로 삼을지부터 정하기
2. "같은 원소를 여러 번 넣어도 하나로 취급"이 필요하면 Set
3. 키가 없을 수도 있는 Map에 원소를 추가할 땐 getOrPut - getOrDefault는 "없으면 기본값을 반환만" 하고 Map에 안 남기지만, getOrPut은 "없으면 실제로 만들어서 저장까지" 해줌
4. 두 집합의 관계를 확인할 땐, 집계 순서가 결과를 좌우한다.
contains()의 방향을 반대로 씀if (reportedBy.contains(j)) // j는 범인 후보
reportedBy는 신고자 -> 그 사람이 신고한 대상들인데,
contains(J)는 "j가 key로 존재하는가", 즉 이 범인이 누군가를 신고한 적이 있는가 를 묻는 거라 엉뚱한 질문이였음
-> 필요했던 건 "지금 보고 있는 신고자(name)가 이 범인(j)을 신고했는가?"
if( j in reportedBy.getOrDefault(name, mutableSetOf()) )
"A가 B를 신고했다"는 관계를 다룰 때, 지금 확인하려는 게 "A 기준으로 B를 찾는 건지, B 기준으로 A를 찾는건지" 코드 짜기 전에 말로 먼저 정리하고 들어가야 함.
2차 실수: "몇 번 신고당했는지" 어디서 세지?
ex) ["con", "ryan"], report = ["ryan con", "ryan con", "ryan con", "ryan con"] (같은 사람이 같은 사람을 4번 신고), k = 3
for (i in report) {
val people = i.split(" ")
reportedBy.getOrPut(people[0]) { mutableSetOf() }.add(people[1])
map[people[1]] = map.getOrDefault(people[1], 0) + 1 // <- 바로 여기서 카운트
}
report가 4줄이니까 이 줄이 4번 실행돼서 map["con"] = 4가 됨
정답 - reportedBy를 완전히 다 채운 뒤, report 루프 밖에서 딱 한 번만 카운트:
// 1단계: report 다 훑으면서 reportedBy만 채움
for (i in report) {
val people = i.split(" ")
reportedBy.getOrPut(people[0]) { mutableSetOf() }.add(people[1])
}
// 2단계: report 루프가 완전히 끝난 뒤, 여기서 딱 한 번만
for ((user, suspects) in reportedBy) {
for (s in suspects) {
map[s] = map.getOrDefault(s, 0) + 1
}
}
왜 이 구조가 맞는지 (메커니즘 이해)
핵심은 "바깥 루프를 무엇 기준으로 도느냐"임.
report 원본 줄 기준으로 돌면: 같은 사람이 같은 사람을 여러 번 신고한 게 줄 개수만큼 그대로 다 세짐 (중복 제거 효과 없음)
reportedBy의 "신고자 한 명씩" 기준으로 돌면: 같은 신고자의 Set엔 같은 대상이 한 번만 들어있으니, 그 신고자 차례에서 그 대상은 딱 1번만 카운트됨
즉 이 구조가 자동으로 구분해주는 것:
같은 사람의 중복 신고 → Set 덕분에 자동으로 1번
서로 다른 사람들의 각자 신고 → 바깥 루프가 사람 수만큼 도니까 자동으로 다 셈
"A가 B에게 ~했다" 는 방향성 있는 관계 → Map<A, Set<B>> 형태로, 그리고
"A 기준으로 찾을지 B 기준으로 찾을지" 먼저 말로 정리
"집계(카운트)는 원본 로그가 아니라, 이미 중복 제거된 구조를 기준으로" — 원본 데이터를 그대로 세면 중복이 다시 살아날 수 있음. 중복 제거가 필요한 카운트는 항상 "이미 걸러진 자료구조를 순회하면서" 세야 함
중첩 루프가 있을 때, 안쪽 로직이 "몇 번 반복되어야 정확한지" 손으로 먼저 계산해보기 — 바깥 루프 위치를 report 원본으로 할지, 정제된 Map으로 할지에 따라 결과가 완전히 달라짐을 이번에 직접 경험함