본문 바로가기
♞ | 공부일지/♝ | TIL

[Android, 내일배움캠프] 공부일지(2024-05-29)

by immgga 2024. 5. 29.
오늘 공부한 내용 정리(2024년 5월 29일)

 

 

1. 코드카타 문제풀이

A. 체육복

문제 내용

 

문제 풀이 방법

  • 학생 n명 중 체육복을 잃어버린 lost 데이터가 있을 때, 빌려올 수 있는 학생인 reserve를 통해 가장 많이 체육복을 얻을 수 있는 경우를 구하기.
  • 제한 사항 중 마지막 제한사항을 유의해서 코드를 작성하기.

 

해결 코드(스포 주의)

더보기
// 학생 n명 중 체육복을 잃어버린 lost 데이터가 있을 때, 빌려줄 수 있는 학생인 reverse를 통해 가장 많이 체육복을 입을 수 있는 경우를 구해 return.
fun solution(n: Int, lost: IntArray, reserve: IntArray): Int {
    var answer = 0
    // null: 체육복은 가지고 있지만 누구를 빌려줄 수 없음
    // true: 다른 사람한테 체육복을 빌려줄 수 있음.
    // false: 체육복이 없음.
    val own = MutableList<Boolean?>(n) { null }
    lost.forEach {
        own[it-1] = false
    }
    reserve.forEach {
        own[it-1] = true
    }

    // lost와 reserve의 중복되는 숫자를 찾아냄(교집합) => 데이터가 존재한다는 것은 여벌 체육복이 있는 사람이 여별을 도난당해 자신이 입을 것 밖에 없다는 뜻.
    val equal = lost.toSet().intersect(reserve.toSet())
    for (i in equal) {
        own[i-1] = null
    }

    // 체육복을 넘겨주기.
    for (i in 0 until own.size) {
        // 다른 사람에게 체육복을 빌려줄 수 없는 상태이거나(null), 체육복이 없는 경우(false) continue.
        // i가 0인 경우에는 i+1만, i가 마지막일 때는 i-1만 봐야 함.
        if (own[i] != true) continue
        if (i-1 >= 0 && own[i-1] == false) {
            own[i - 1] = null
            continue
        }
        if (i+1 <= own.size-1 && own[i+1] == false) {
            own[i + 1] = null
            continue
        }
    }

    // 체육복을 소지하지 못한 학생들은 제외하고 size를 구함.
    answer = own.filter { it != false }.size
    return answer
}

 

풀이 과정

  • 현재 학생들의 체육복 소지, 빌려줄 수 있는 여부를 확인할 own을 생성한다.
    null: 체육복을 가지고 있지만 다른 사람에게 빌려줄 수 없음
    true: 다른 사람한테 체육복을 빌려줄 수 있음
    false: 체육복을 도난당해 현재 없음
  • lost와 reserve를 이용해 true, false 여부를 설정해 준다. list는 0부터 시작하기 때문에 forEach에 -1해 주는 것도 잊지 말자.
  • 제한사항의 마지막 조건을 반영하기 위해 lost와 reserve를 대상으로 교집합 함수(intersect())를 이용해 같은 값이 있는지 체크한다.
    equal에 값이 있다는 것은 전에 여분의 체육복을 가지고 있었지만 도난당해 자신이 입을 체육복밖에 남지 않았다는 뜻이 된다.
  • equal에 값이 있으면 해당하는 index에 여분의 체육복이 없다는 뜻인 null을 넣어준다.
  • 이제 own을 돌면서 true인 index를 기준으로 index-1, index+1에 false인 데이터가 있을 경우, null로 변경해 주고(체육복을 빌려줌) index가 true가 아닌 경우는 continue로 넘어간다.
    null로 변경한 후에 continue를 하는 것을 잊지 말자.
    0번째 index와 n-1번째 index의 경우는 한쪽 방향으로밖에 확인을 못하기 때문에 if에 따로 조건을 추가해 준다.
    i-1이 0보다 크거나 같음, i+1이 own.size-1보다 작거나 같은가?
  • 반복문을 빠져나오면, filter를 걸어서 false인 데이터를 제외한 모든 데이터의 size를 구해줘서 return.

 

해결 과정

  • 마지막 제한 사항을 고려하지 않은 코드를 짜서 70점이 나왔었다.
    마지막 제한 사항을 체크하기 위해 두 리스트 간의 중복사항을 확인하는 방법을 찾아보았고, 교집합 함수를 찾아내어 equal 변수를 생성해 주고 equal 데이터에 해당하는 index를 null로 바꿔주는 코드를 추가해 주었다.

 

B. 문자열 나누기

문제 내용

 

문제 풀이 방법

  • 첫 글자를 x로 정의하고, x의 개수와 x가 아닌 다른 글자의 개수가 같아지면 그때까지 읽었던 문자까지 문자를 분리하고 answer를 1을 더한다.
  • 위 과정을 반복해서 s가 비거나 더 x의 개수가 x가 아닌 다른 글자의 개수가 같아질 수 없는 상황이면 answer에 1을 추가해 return.

 

해결 코드(스포 주의)

더보기
// 첫 글자를 x라고 할 때, x와 x가 아닌 다른 글자의 수가 같아지면 그때까지 읽었던 문자를 분리하고 answer+=1 이 과정 반복
fun solution(s: String): Int {
    var answer: Int = 0
    val string = StringBuilder(s)

    var xCnt = 0
    var otherCnt = 0
    while (string.isNotEmpty()) {
        val x: Char = string.first()

        // replace한 string이 1글자 이상일 수도 있나?
        if (xCnt == otherCnt) {
            xCnt = 0
            otherCnt = 0
        } else {
            answer++
            break
        }

        for (i in string.indices) {
            if (string[i] == x) xCnt++
            else otherCnt++

            if (xCnt == otherCnt) {
                string.replace(0, xCnt+otherCnt, "")
                answer++
                break
            }
        }
    }

    return answer
}

 

풀이 과정

  • x의 개수를 셀 변수(xCnt), x 이외의 문자를 셀 변수(otherCnt)를 생성해 준다.
  • string이 비어 있지 않을 때까지 반복.
  • string[i]이 x와 같으면 xCnt를 +1, 아니면 otherCnt를 +1
    반복하다가 xCnt와 otherCnt가 같아지면 string에서 지금까지 읽었던 index까지의 string을 교체(공백으로) answer를 +1 하고 break.
  • 내부 반복을 돌고 다시 외부로 들어왔을 때, xCnt와 otherCnt가 다르면 answer를 1 더하고 break. 아니면 두 개 다 0으로 초기화.

 

해결 과정

  • 시간 초과 문제가 있었다. 문자열을 다루는 문제이기 때문에 StringBuilder를 사용해 보았지만 해결되지 않아서 아마도 while 문이 있기 때문에 무한 반복되어서 시간 초과가 발생했을 것으로 생각해서 break 코드 중 문제가 있어 보였던 코드를 찾아보았다.
// 문제 코드
// if (xCnt == otherCnt) 이전에 사용했던 코드
if (string.length == 1) {
    answer++
    break
}
  • 위 코드는 string의 길이가 1인 경우 break를 하는 코드인데, 남은 string의 길이가 1개 이상인 경우가 있어서(남은 string이 "aaa"인 경우 위 코드의 조건에 부합하지 않고, 내부 반복문의 otherCnt와 xCnt가 같은 경우의 조건에도 부합하지 않아 무한히 반복됨) xCnt와 otherCnt를 반복문 외부로 빼고 if문으로 다시 두 값이 같은지 체크해서 같으면 0으로 초기화하고 아니면 answer 값을 1 더해주고 반복을 끝낼 수 있도록 코드를 작성했다.

 

2. 내일배움캠프 강연

A. TIL 작성 세션 강연

강의 내용 간단 정리

  • 취준생들이 til을 작성하는 이유는 기업에게 자신을 증명하기 위해서이다.
    하지만 til을 매일 쓴다고 해서 기업이 항상 서류에서 나를 뽑아주는 건 아니다. 아무리 열심히 적어도 기업이 내 til에 대한 신뢰가 부족하면(증명이 부족) 서류에서 떨어진다.
    til을 작성할 때는 처음에는 간단하게 작성하다가 시간이 흐를수록 더 꼼꼼하게, 제대로 작성해야 한다.
  • 간단하게 오늘 배운 개념(강의)을 정리한다고 했을 때, 예를 들어 오늘 배운 것이 무엇인지, 개념 정리, 해당 개념이 필요한 이유 같은 것들을 정리해 보자.
    하루 회고를 작성해 오늘 있었던 일들을 작성해 느낀 점, 문제 사항이 있었다면 그것을 개선했던 것들도 적어서 갈수록 더 나아지는 사람이라는 어필을 할 수 있을 것이다.
    개발하다가 문제 사항이 발생했을 경우 이를 til에 쓰는 것을 권장한다. 자신이 어떤 문제를 어떻게 해결했는지 적어서 문제 해결 능력을 어필할 수 있다.
  • 결론은 취업은 자신의 능력을 채용 담당자에게 증명하는 과정이므로, til이 증명 자료로 중요하다. 그래서 til 쓰는 데 정답은 없으므로 일단 쓰고, 쓰면서 나만의 방식으로 발전시켜 나가자.

 

3. 개인 공부

A. 그룹 단어 체커(백준, 1316번)

문제 내용

 

문제 풀이 방법

  • 각 문자가 연속적으로 나타나면 그룹 단어라고 할 때, 그룹 단어의 개수를 return.
  • 단어가 연속적으로 나타나지 않는 경우는 그룹 단어가 아님.

 

해결 코드(스포 주의)

더보기
import java.io.BufferedReader
import java.io.InputStreamReader

// 같은 단어가 연속해서 나타나야 한다.
// 이미 나타난 단어가 또 나타나면 안된다.
fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
    val case = readLine().toInt()
    var groupWord = 0

    repeat(case) {
        val word = readLine()
        val wordCntMap = mutableMapOf<Char, Int>()
        var isWordGrouped = true

        for (i in word.indices) {
            if (!wordCntMap.contains(word[i])) {
                wordCntMap[word[i]] = i
            } else {
                if (i - wordCntMap[word[i]]!! > 1) {
                    isWordGrouped = false
                    break
                }
                wordCntMap[word[i]] = i
            }
        }

        if (isWordGrouped) groupWord++
    }

    println(groupWord)
}

 

풀이 과정

  • 단어의 개수만큼 입력받기 위한 case 변수와 그룹 단어가 몇 개인지 셀 groupWord 변수를 생성한다.
  • 단어가 마지막에 몇 번째 index였는지를 확인하기 위한 wordCntMap이랑 그룹 단어인지 확인할 isWordGrouped를 생성한다.
  • word[i]가 wordCntMap에 존재하지 않으면(새로운 단어가 들어옴) map에 새롭게 key를 word[i], value를 i로 설정해 준다.
    이미 word[i]가 wordCntMap에 존재하는 경우는 연속적으로 같은 단어가 들어오고 있는 중이거나, 떨어져서 들어온 단어가 있다는 뜻이다.
  • wordCntMap의 value에는 가장 마지막에 있었던 문자의 index 정보를 담고 있으므로, i에서 wordCntMap[word[i]]를 빼서 그 값이 1보다 크면 단어들이 서로 떨어져 있다는 뜻이 되기 때문에 isWordGrouped를 false로 설정하고 반복을 끝낸다.
  • 반복이 끝난 후, isWordGrouped가 true이면 groupWord를 1 더해준다.

 

문제 해결 과정

  • 문제 이해는 빠르게 했지만, 코드로 구현하는 데 있어서 헷갈려 시간이 좀 걸렸다.
    기존에 a가 나타나고 나서 또 a가 나타났을 때를 어떻게 처리해야 하는지 고민을 많이 했었다. mutableMap을  사용하기 이전에는 mutableList를 이용하려고 했었다. 하지만 mutableList로는 구현하기 힘들겠다 해서 어떤 단어(a)가 가장 마지막에 나타난 index 위치를 저장하고, 또 같은 단어가 나타나면 연속되는 경우는 현재 index와 데이터를 바꾸기 이전의 map value랑 빼면 1이 나오기 때문에 이를 이용하기 위해 mutableMap을 사용했다.

 

B. 단어 정렬(백준, 1181번)

문제 내용

 

문제 풀이 방법

  • 길이가 짧고 사전순으로 단어를 정렬해 출력.
  • 같은 단어가 여러 개 있을 경우 하나만 출력.

 

해결 코드(스포 주의)

더보기
import java.util.Scanner

fun main() = with(Scanner(System.`in`)) {
    val case = nextInt()
    val words = mutableListOf<String>()

    repeat(case) {
        val word = next()
        if (!words.contains(word)) words.add(word)
    }

    // 사전순 정렬
    val sortDictionary = words.sorted()
    // string 길이순으로 정렬
    val sortLength = sortDictionary.sortedBy { it.length }
    sortLength.forEach {
        println(it)
    }
}

 

풀이 과정

  • 먼저 입력받은 단어들을 모두 words 리스트에 넣어준다.
  • 먼저 사전순으로 정렬한다(sorted()). 문자열 길이순으로 먼저 정렬하면 나중에 사전순으로 정렬했을 때, 길이순 정렬이 말짱 도루묵이 된다.
  • 사전순으로 정렬된 리스트를 문자열의 길이를 기준으로 정렬한다. sortedBy에서 it.length을 기준으로 정렬하면 길이순 정렬이 된다. 사전순으로 정렬한 것을 해치지 않고 길이순으로 정렬해 준다.

 

문제 해결 과정

  • 출력 형식이 string 여러 줄인데 list를 통으로 출력하려고 해서 4%에서 실패가 떴었다. 출력 형식을 잘 확인해야겠다.

 

C. 분수 찾기(백준, 1193번)

문제 내용

 

문제 풀이 방법

  • 지그재그로 분수의 순서가 주어질 때, x번째 분수를 구하기.
  • n번째 줄의 분수 형식이 a/b일 때,
    짝수인 경우에는 a가 1부터 시작해서 하나씩 커지고, b가 n에서 시작해 하나씩 작아진다.
    홀수인 경우 a가 n에서 시작해 하나씩 작아지고, b가 1부터 시작해 하나씩 커진다.
  • 무지성으로 풀면 시간 초과로 고생할 수 있다.

 

해결 코드(스포 주의)

더보기
import java.util.Scanner

fun main() = with(Scanner(System.`in`)) {
    val num = nextInt()
    var sequence = 0
    var line = 1

    // num이 몇 줄에 해당하는지 확인
    // a/b 일때, 홀수줄인 경우 a는 줄어들고, b는 커짐. 짝수는 반대.
    // 공차가 1인 등차수열의 합 공식
    // n(n-1) / 2
    while (true) {
        sequence = line * (line+1) / 2
        if (sequence >= num) break
        line++
    }

    // n번째 줄에서 몇 번째의 숫자인지 체크.
    val th = num - (line-1) * line / 2

    // 짝수인 경우 a/b가 있을 때,
    // a가 1부터 시작해 하나씩 늘어나고, b가 line으로 시작해 줄어든다.
    // 홀수인 경우
    // a가 line으로 시작해 하나씩 줄어들고, b가 1부터 시작해 늘어난다.
    if (line % 2 == 0) {
        println("${th}/${line-th+1}")
    } else {
        println("${line-th+1}/${th}")
    }
}

 

풀이 과정

  • 공차가 1인 등차수열의 합 공식 n(n+1)/2를 이용해 num이 몇 번째 줄의 번호인지 확인한다.
    num이 14인 경우 1, 3, 6, 10, 15... 에서 10보다 크고 15보다 작으므로 5번째 줄의 수가 된다.
  • num이 몇 번째 줄의 숫자인지 line에 저장했으니, 이제 line번째 줄에서 몇 번째 숫자가 num인지 확인하기 위해 line-1까지의 등차수열의 합 공식에 num을 빼주었다.
    num이 14일 때, line - 1인 4까지의 등차수열 공식을 적용 후 계산하면 14 - 10으로 5번째 줄의 4번째 번호가 되게 된다.
  • 풀이 방법에 언급한 짝수와 홀수의 경우를 따로 처리해서 반복문 없이 문제를 해결할 수 있다.

 

문제 해결 과정

  • 시간제한이 빡빡하기(0.5초) 때문에 무지성 반복문을 쓰면 안 될 것 같아서 비슷한 수학 공식을 찾다가 등차수열의 합 공식을 알게 되어서 문제에 대입해 해결할 수 있었다.

 


오늘 공부 내용 정리 및 회고

  • 프로그래머스 2문제, 백준 3문제를 풀었다. 이제 점점 알고리즘 풀이에 자신감이 붙고 있다. 백준에서 더 고난도의 문제(실버 4 ~ 2)를 도전해도 될 것 같았다.
  • til 강의를 들으면서 나는 목표가 취업인 만큼 til을 채용 담당자의 니즈에 맞게 작성해야겠다는 생각이 들었다.
728x90