본문 바로가기
JavaScript/Coding test

프로그래머스 코딩테스트 Lv.1 풀이 3

by enai 2024. 1. 11.

2023.12.31

로또의 최고 순위와 최저 순위

function solution(lottos, win_nums) {
    let miss = 0
    let rank = 1
    lottos.forEach((num, index) => {
        if (num === 0) {
            miss++
        }
        if (!win_nums.includes(num) && rank < 6) {
            rank++
        }
    })
    return [Math.max(rank - miss, 1), rank];
}

lottos에서 숫자가 0인 개수를 miss에 저장하고
민우가 적은 숫자가 틀릴 때마다
rank를 1씩 더함 (rank는 커질 수록 순위가 떨어짐, 최저 순위는 6이므로 6보다 작을 때만 더함)
miss가 다 맞았다고 가정하면 최고 순위가 되므로 최고 순위는 rank - miss이다. 단, 1이 제일 높은 순위이므로 Math.max로 1 이하가 되지 않도록 함
최저 순위는
miss가 다 틀린 경우이므로 rank 그대로 반환

숫자 짝꿍

function solution(X, Y) {
    const XCountMap = [...X].reduce((acc, curr) => {
        return {...acc, [curr]: (acc[curr] ?? 0) + 1}
    }, {})
    const YCountMap = [...Y].reduce((acc, curr) => {
        return {...acc, [curr]: (acc[curr] ?? 0) + 1}
    }, {})
    let common = []

    for (const [key, value] of Object.entries(XCountMap)) {
        const commonValue = YCountMap[key]
        if (!!commonValue) {
            const newCommonNums = new Array(Math.min(value, commonValue)).fill(key)
            common = [...common, ...newCommonNums]
        }
    }
    common = common.sort((a, b) => b - a)
    if (common.length === 0) return '-1'
    if (common[0] === '0') return '0'
    return common.join('');
}

XY의 숫자별 개수를 담은 object를 만들고 common에 둘 중 더 작은 수만큼 해당 숫자를 담음
큰 숫자를 만들어야 하기 때문에
common을 내림차순으로 정렬
common의 길이가 0이면 공통으로 오는 숫자가 없기 때문에 -1 반환
common의 첫 시작이 0이면 common이 '0' or '00' or '000' ... 이런 식이기 때문에 0 반환
그 외에는
common을 문자열로 만들어 반환
+) common을 문자열로 하고, 각 숫자 개수 비교는 for문으로 9부터 1까지로 비교해도 가능함, 이게 더 효율성 있어 보임

체육복

function solution(n, lost, reserve) {
    const lostSorted = lost.filter((num) => !reserve.includes(num)).sort((a, b) => a - b)
    const reserveSorted = reserve.filter((num) => !lost.includes(num)).sort((a, b) => a - b)
    for (let i = 0; i < lostSorted.length; i++) {
        const currLost = lostSorted[i]
        for (let j = 0; j < reserveSorted.length; j++) {
            const currReserve = reserveSorted[j]
            if (currReserve === currLost - 1 || currReserve === currLost + 1) {
                lostSorted.splice(i, 1)
                reserveSorted.splice(j, 1)
                i--
                j--
                break
            }
        }
    }
    return n - lostSorted.length
}

여벌이 있는 학생이 도둑 맞았다면 본인이 입어야 하기 때문에 빌려줄 수 없음
먼저
lostreserve에서 서로 겹치는 경우는 제외하고 같은 순서로 정렬
여벌이 있는 학생의 번호가 도둑맞은 학생의 번호보다 1 작거나 1 큰 경우가 있는지 확인하고
, 있으면 lostSortedreserveSorted 리스트에서 제거
다 확인하고 나서도
lostSorted에 남아 있으면 빌리지 못한 학생임 -> n에서 lostSorted의 길이를 빼면 체육복을 빌린 학생 수가 됨

2024.01.01

문자열 나누기

function solution(s) {
    let answer = 0
    let start = ''
    let startCount = 0
    let otherCount = 0 // count 하나로 같으면 +, 다르면 - 해서 풀어도 됨
    for (let i = 0; i < s.length; i++) {
        if (startCount === otherCount) {
            answer++
            start = s[i]
            startCount = 1
            otherCount = 0
        } else if (start === s[i]) {
            startCount++
        } else {
            otherCount++
        }
    }
    return answer;
}
start에 첫 글자를 담고, startCount는 첫 글자와 같은 글자가 나올 때마다 +1, otherCount는 다른 글자가 나올 때마다 +1 해줌
startCountotherCount가 같아지면 answer를 1 추가하고 start를 다음 글자로 바꿈

완주하지 못한 선수

// Map을 사용했으면 더 좋았을 듯!
function solution(participant, completion) {
    const participantSorted = participant.sort()
    const completionSorted = completion.sort()
    for (let i = 0; i < participantSorted.length; i++) {
        if (participantSorted[i] !== completionSorted[i]) {
            return participantSorted[i]
        }
    }
}

참가자와 완주자를 같은 순서로 정렬하고 완주자 리스트에 없는 참가자를 반환함
완주하지 못한 선수는 1명 뿐이라는 점 때문에 가능함
Map으로 풀었으면 더 좋은 코드였을 거 같음

대충 만든 자판

function solution(keymap, targets) {
    const answer = []

    const keyCountMap = new Map()
    for (const keys of keymap) {
        const keyCount = keys.length
        for (let i = 0; i < keyCount; i++) { // 여기선 reduce 써도 됐을 듯
            const currKey = keys[i]
            const currCount = i + 1
            const currKeyCount = keyCountMap.get(currKey)
            if (!currKeyCount || currKeyCount > currCount) {
                keyCountMap.set(currKey, currCount)
            }
        }
    }

    for (const target of targets) {
        const targetCount = target.length
        let count = 0
        for (let i = 0; i < targetCount; i++) {
            const currTarget = target[i]
            const keyCount = keyCountMap.get(target[i])
            if (!keyCount) {
                count = -1
                break
            } else {
                count += keyCount
            }
        }
        answer.push(count)
    }
    
    return answer;
}

각 키를 눌러야 하는 수를 가진 Map을 생성
눌러야 하는 타겟 키를
key값으로 해당 맵에서 찾아 눌러야 할 숫자들을 모두 더해 anser에 담아 반환

2024.01.02

둘만의 암호

function solution(s, skip, index) {
    let answer = '';
    const alphabets = [...new Array(26)].map((_v, i) => String.fromCharCode(97 + i)).filter((v) => !skip.includes(v))
    const avilableLength = alphabets.length
    index -= avilableLength * Math.floor(index / avilableLength)
    for (let i = 0; i < s.length; i++) {
        const currS = s[i]
        const lastAlphabetIndex = avilableLength - 1
        let alphabetIndex = alphabets.indexOf(currS) + index
        if (alphabetIndex > lastAlphabetIndex) {
            alphabetIndex -= avilableLength
        }
        answer += alphabets[alphabetIndex]
    }
    return answer;
}

알파벳 소문자 배열을 만들고 skip에 있는 알파벳을 제거함
index가 매우 커서 알파벳 리스트를 여러 바퀴 돌 수도 있으므로 뒤로 이동할 실질적인 index를 구함
s를 하나씩 탐색하며 index 만큼 뒤의 알파벳 리스트의 인덱스를 가져옴
마지막 인덱스보다 크면 알파벳 리스트 길이만큼 빼서 제일 앞 요소로부터 해당 인덱스 위치의 알파벳을 가져옴
+) 실제 알파벳 리스트의 인덱스를 (alphabets.indexOf(c) + index) % alphabets.length로 더 간단하게 가져올 수 있음

크레인 인형뽑기 게임

function solution(board, moves) {
    let answer = 0;
    const findItem = (move) => {
        for (let i = 0; i < board.length; i++) {
            const item = board[i][move]
            if (item > 0) {
                board[i][move] = 0
                return item
            }
        }
    }
    moves.reduce((acc, cur) => {
        const item = findItem(cur - 1)
        if (!item) return acc
        if (acc[acc.length - 1] === item) {
            answer += 2
            acc.pop()
            return acc
        }
        return [...acc, item]
    }, [])
    return answer;
}

board [[0,0,0,0,0],[0,0,1,0,3],[0,2,5,0,1],[4,2,4,4,2],[3,5,1,3,1]] 를
[0,0,0,0,0]
[0,0,1,0,3]
[0,2,5,0,1]
[4,2,4,4,2]
[3,5,1,3,1]
이런 형태로 생각하고 board의 각 배열을 돌며 moves의 번호 위치에 있는 0이 아닌 요소 찾기
0이 아닌 요소를 찾으면 해당 요소를 따로 저장하고 0으로 바꿈
(인형을 뺐으니까)
뽑은 요소가 이전에 저장한 요소와 같으면 answer에 2를 더해주고 이전에 저장한 요소를 제거

[카카오 인턴] 키패드 누르기

function getDistance(target, prev) {
    return Math.abs(target[0] - prev[0]) + Math.abs(target[1] - prev[1])
}

function solution(numbers, hand) {
    let left = [0, 3]
    let right = [2, 3]
    const answer = numbers.map((n) => {
        if ([1, 4, 7].includes(n)) {
            left = [0, Math.floor(n / 3)]
            return 'L'
        }
        if ([3, 6, 9].includes(n)) {
            right = [2, Math.floor(n / 3) - 1]
            return 'R'
        }

        const target = [1, n === 0 ? 3 : Math.floor(n / 3)]
        const leftDistance = getDistance(target, left)
        const rightDistance = getDistance(target, right)
        if (leftDistance < rightDistance) {
            left = target
            return 'L'
        }
        if (leftDistance > rightDistance) {
            right = target
            return 'R'
        }
        if (hand === 'left') {
            left = target
            return 'L'
        }
        right = target
        return 'R'
    })
    return answer.join('');
}

왼손 위치 좌표 left, 오른손 위치 좌표 right
휴대폰 키패드를 4x3 행렬로 보고 좌표 설정
(0부터 시작)
키패드를 누른 배열 numbers를 돌며 누른 키패드가
[1,4,7]이면 왼손 위치 좌표를 바꿔주고 'L'을 담음
누른 키패드가
[3,6,9]면 오른손 위치 좌표를 바꿔주고 'R'을 담음
그 외에는 누른 키패드의 좌표를 구해 왼손과 오른손 좌표 거리를 측정
더 짧은 쪽 손의 좌표를 바꿔주고 해당하는 텍스트를 담음
이렇게 완성한 좌우 텍스트 배열을 문자열로 만들어 반환

2024.01.03

햄버거 만들기

function solution(ingredient) {
    let answer = 0
    for (let i = 0; i < ingredient.length - 3; i++) {
        if (ingredient[i] === 1 &
        ingredient[i + 1] === 2 &
        ingredient[i + 2] === 3 &
        ingredient[i + 3] === 1) {
            // slice로 가져와도 됐음 (ingredient.slice(i, i+4))
            answer++
            ingredient.splice(i, 4)
            i = i - 3 < 0 ? -1 : i - 4
        }
    }
    return answer;
}

ingredient를 4개씩 가져와 각각 1, 2, 3, 1이 맞는지 확인해서 맞으면 anwer에 +1 하고 ingredient에서 해당 요소들을 제거함.
for문을 돌던 인덱스 값도 그만큼 줄여줌
(다음에 플러스될 것도 고려해서 줄임)

신규 아이디 추천

function solution(new_id) {
    var answer = new_id.toLowerCase().replaceAll(/[^\w\d-_.]/g, '').replaceAll(/\.{2,}/g, '.').replace(/^\./, '').replace(/\.$/, '')
    if (answer === '') {
        answer = 'a' // 빈값 체크도 정규표현식으로 가능
    }
    const newIdLength = answer.length
    if (newIdLength >= 16) {
        answer = answer.substring(0, 15).replace(/\.$/, '')
    }
    if (newIdLength <= 2) {
        answer += answer[newIdLength - 1].repeat(3 - newIdLength) // padEnd도 가능
    }
    return answer;
}

신규 아이디 규칙을 정규표현식으로 표현해 값을 바꿔줌.
체이닝으로 하기 어려운 건 따로 검사해서 처리해줌
.
+) 빈값도 정규표현식으로 찾을 수 있고, 마지막 repeat도 padEnd로 대체되니 체이닝으로 쭉 가능했음
다만
, 체이닝으로 쭉 잇는 것 보다 길이 조건에 맞는 것만 규칙 적용하도록 하는 게 더 효율적이긴 한 듯

성격 유형 검사하기

function solution(survey, choices) {
    const types = ['RT', 'CF', 'JM', 'AN']
    const scoreboard = {R: 0, T: 0, C: 0, F: 0, J: 0, M: 0, A: 0, N: 0}
    choices.forEach((choice, i) => {
        const score = choice - 4
        const [a, b] = survey[i].split('')
        scoreboard[score > 0 ? b : a] += Math.abs(score)
    })
    const answer = types.map(([a, b]) => scoreboard[a] >= scoreboard[b] ? a : b).join('')
    return answer;
}

점수판 객체를 만들어 점수 더함
문자열도 인덱스로 접근할 수 있는 점을 이용해 각 유형 중에 점수가 더 큰 걸 모아 문자열로 반환함

2024.01.04

바탕화면 정리

function solution(wallpaper) {
    const wallpaperLength = wallpaper.length
    const initialMin = Math.max(wallpaperLength, wallpaper[0].length)
    let min = [initialMin, initialMin]
    let max = [0, 0]
    for (let rowIndex = 0; rowIndex < wallpaperLength; rowIndex++) {
        const row = wallpaper[rowIndex]
        const minColIndex = row.indexOf('#')
        if (minColIndex === -1) {
            continue
        }
        min = [Math.min(rowIndex, min[0]), Math.min(minColIndex, min[1])]

        const maxColIndex = row.lastIndexOf('#')
        max = [Math.max(rowIndex + 1, max[0]), Math.max(maxColIndex + 1, max[1])]
    }

    return [...min, ...max];
}

최소 드래그 영역을 정하기 위해 바탕화면에 있는 파일 위치 최소값과 최대값을 구함
wallpaper 각 행마다 첫번째
'#' 위치(열) 확인, 각 행과 열 번호 중 제일 작은 값 저장
각 행과 마지막
'#' 위치(열)은 제일 큰 값 저장
제일 작은 위치와 제일 큰 위치 배열로 반환

개인정보 수집 유효기간

function solution(today, terms, privacies) {
    const answer = [];
    const monthDays = 28
    const yearDays = 12 * monthDays
    const [ty, tm, td] = today.split('.').map(Number)
    const todayDays = ty * yearDays + tm * monthDays + td
    const termDict = terms.reduce((acc, term) => {
        const [type, period] = term.split(' ')
        return {...acc, [type]: parseInt(period)}
    }, {})
    privacies.forEach((privacy, i) => {
        const [startDate, type] = privacy.split(' ')
        const [y, m, d] = startDate.split('.').map(Number)
        const expireDays = y * yearDays + m * monthDays + d + (termDict[type] * monthDays)
        if (todayDays >= expireDays) {
            answer.push(i + 1)
        }
    })
    return answer;
}

모든 달이 28일로 이루어져 있으므로, 단순하게 모든 날짜를 일자로 바꿔 비교

공원 산책

function findStart(park) {
    for (let i = 0; i < park.length; i++) {
        const colIndex = park[i].indexOf('S')
        if (colIndex !== -1) {
            return [i, colIndex]
        }
    }
}

function solution(park, routes) {
    const dog = findStart(park)
    const rowMax = park.length - 1
    const colMax = park[0].length - 1
    for (let i = 0; i < routes.length; i++) {
        const route = routes[i]
        const [op, n] = route.split(' ')
        const dir = op === 'E' || op === 'S' ? 1 : -1
        const horizontal = op === 'E' || op === 'W' ? 1 : 0
        const togo = dog[horizontal] + (n * dir)
        if (togo < 0 ||(!!horizontal && togo > colMax) || (!horizontal && togo > rowMax)) {
            continue
        }
        if (!!horizontal) {
            if (dog[1] > togo && park[dog[0]].substring(togo, dog[1] + 1).includes('X')) {
                continue
            } else if (park[dog[0]].substring(dog[1], togo + 1).includes('X')) {
                continue
            }
        } else {
            const parkRow = park.reduce((acc, row) => acc + row[dog[1]], '')
            if (dog[0] > togo && parkRow.substring(togo, dog[0] + 1).includes('X')) {
                continue
            } else if (parkRow.substring(dog[0], togo + 1).includes('X')) {
                continue
            }
        }
        dog[horizontal] = togo
    }
    return dog;
}

start 지점 인덱스 찾아 규칙에 따라 인덱스 번호 수정

2024.01.05

달리기 경주

function solution(players, callings) {
    const ranking = {} // 이걸 reduce로 만들었다가 시간 초과남
    for (let i = 0; i < players.length; i++) {
        ranking[players[i]] = i
    }

    for (let i = 0; i < callings.length; i++) {
        const called = callings[i]
        const currRank = ranking[called]
        const overtaken = players[currRank - 1]

        players[currRank] = players[currRank - 1]
        players[currRank - 1] = called
        
        ranking[overtaken] += 1
        ranking[called] -= 1
    }
    const answer = players
    return answer;
}

각 선수별 순위를 저장하는 ranking을 만들고
callings를 순서대로 확인하여 ranking에서 현재 순위를 확인하고 players에서 바로 앞의 선수와 순서를 바꿈

신고 결과 받기

function solution(id_list, report, k) {
    const report_count = {}
    const report_ids = {}
    for (const content of report) {
        const [reporter, reported] = content.split(' ')
        const reported_ids = report_ids[reporter]
        if (!reported_ids?.includes(reported)) {
            const count = report_count[reported]
            report_count[reported] = !!count ? count + 1 : 1
            report_ids[reporter] = !!reported_ids ? [...reported_ids, reported] : [reported]
        }
    }

    const stopped_list = Object.entries(report_count).filter(([_, count]) => count >= k).map(([reported]) => reported)

    const answer = id_list.reduce((acc, cur) => {
        let count = 0
        if (!!report_ids[cur]) {
            count = report_ids[cur].filter((reported) => stopped_list.includes(reported)).length
        }
        return [...acc, count]
    }, [])
    return answer;
}

report_count에 유저별 신고 당한 수를 저장
report_ids에는 신고한 유저 id와 신고 당한 유저의 id를 저장
이미 repoert_ids에 저장된 경우 다시 체크 x
report_count에서 신고 수가 k 이상인 경우만 거르기
본인이 신고한 유저를 report_ids에서 가져와 정지된 리스트에 포함되어 있는지 확인 후 그 개수를 모아 반환함

[PCCP 기출문제] 1번 / 붕대 감기

function startBandage(bandage, currHealth, health, recoveryCount) {
    const [time, recovery, additional] = bandage
    if (recoveryCount >= time) {
        currHealth += time * recovery + additional
        recoveryCount = recoveryCount - time
        return startBandage(bandage, currHealth, health, recoveryCount)
    } else {
        currHealth += recoveryCount * recovery
        return currHealth > health ? health : currHealth
    }
}

function solution(bandage, health, attacks) {
    let currTime = 1
    let currHealth = health
    const [time, recovery, additional] = bandage
    for (let i = 0; i < attacks.length; i++) {
        const [attackTime, attackAmount] = attacks[i]

        // 공격 받은 시간 전까지 만큼 recovery 회복
        if (currHealth < health) {
            let recoveryCount = attackTime - currTime
            currHealth = startBandage(bandage, currHealth, health, recoveryCount)
        }
  
        currHealth -= attackAmount
        if (currHealth <= 0) return -1

        currTime = attackTime + 1
    }
    return currHealth;
}

 

댓글