[코테] 기능개발 (스택/큐)

ekil·2026년 3월 31일

코딩테스트

목록 보기
7/11

기능개발 (스택/큐)

2026.3.31.

https://school.programmers.co.kr/learn/courses/30/lessons/42586

핵심 개념

  • 선입선출의 큐 자료구조: 배포 순서상 뒤에 있는 기능은 먼저 개발되더라도 앞에 있는 기능 배포할 때 함께 배포해야 한다는 컨셉

내 풀이

function solution(progresses, speeds) {
    const answer = [];
    
    const que = progresses.map((p, index) => {
        const days = Math.ceil((100 - p) / (speeds[index]));
        return days;
    })
    
    let count = 1;
    let target = que[0];
    
    for (let i = 1; i < que.length; i++) {
        if (target < que[i]) {
            // 배포 불가능한 경우
            answer.push(count);
            target = que[i];
            count = 1;
        } else {
            // 배포 가능한 경우
            count += 1;
        }
    }
    
    answer.push(count);
    return answer;
}

개선된 풀이

  • map 메서드 안에서 변수 선언 및 return 문 없이 화살표 함수로 구현하는 것 정도? 근데 days 라는 개념을 명확히 해두고 싶어서 일부러 변수를 썼어서 개선하지 않음.

핵심 차이

패스!

막혔던 포인트

  • 배포까지 필요한 날짜 수 구하는 건 간단한 사칙연산이라 금방 규칙을 구했다.
  • 처음 작성한 코드는 2가지 문제점이 있는데,
function solution(progresses, speeds) {
    const answer = [];
    
    const que = progresses.map((p, index) => {
        const days = Math.ceil((100 - p) / (speeds[index]));
        return days;
    })
    
    let count = 1;
    
    for (let i = 0; i < que.length; i++) {
        // console.log(que[i], que[i+1], que[i] < que[i+1]);
        
        // ⚠️ que[i]와 que[i + 1]을 비교하게 작성함
        if (i === que.length - 1 || que[i] < que[i + 1]) {
            console.log(i, true)
            answer.push(count);
            count = 0; // ⚠️ count를 1이 아닌 0으로 초기화함
        } else {
            console.log(i, false)
            count += 1;
        }
    }
    
    return answer;
}
  • 위 코드에서 count1로 초기화하도록 수정하면 문제에서 제시된 예시 케이스는 모두 통과한다.
  • 반복문 안에서 자기자신과 그 다음 요소를 비교하도록 로직을 짰더니 문제의 예시 케이스에서만 성공하고 제출하니 우수수 틀렸다!
  • 이럴 때 엣지케이스를 생각해내는 능력을 기르고 싶다. 클로드의 도움을 받아 [7, 3, 5] 케이스에 대해 더 자세히 손으로 적어보며 고민했다.
첫번째 인자 7 => count = 1
두번째 인자 3 => 7과 비교 => 7보다 작다 => count = 2
세번째 인자 5 => 7과 비교 => 7보다 작다 => count = 3
  • 이 예시에서 알 수 있는 점: 바로 앞/뒤 요소끼리 비교가 아니라 특정한 대상과 비교해야 한다! => 비교 대상을 저장할 변수가 필요하다.
  • 예시가 짧아서 문제 속 [5, 10, 1, 1, 20, 1] 케이스에 대해서도 같은 방식으로 접근해봤다.
첫번째 인자 5 => count = 1
두번째 인자 10 => 5와 비교 => 5보다 크다 => 5 먼저 나가: answer.push(count), 10을 새로운 비교 대상으로 저장, count 1로 초기화
세번째 인자 1 => 10과 비교 => 10보다 작다 => count = 2
네번째 인자 1 => 10과 비교 => 10보다 작다 => count = 3
다섯번째 인자 20 => 10과 비교 => 10보다 크다 => answer.push(count), 20을 새로운 비교 대상으로 저장, count 1로 초기화
여섯번째 인자 1 => 20과 비교 => 20보다 작다 => count = 2

더이상 인자가 없음 => answer.push(count), 종료!
  • 플로우가 명확히 정리되어 그대로 코드로 옮겼다.
function solution(progresses, speeds) {
    const answer = [];
    
    const que = progresses.map((p, index) => {
        const days = Math.ceil((100 - p) / (speeds[index]));
        return days;
    })
    
    let count = 1;
    let target = que[0];
    // ✅ target 초기값을 null로도 해봤는데, 그럴 필요가 없었다. 제일 첫 요소로 시작해서 큰 값으로 계속 바꿔주면 되는 것!
    
    // ✅ 제일 첫 요소(i=0 케이스)는 target으로 할당, count 1로 초기화 외에 하는 게 없어서 반복문 루프를 돌릴 필요가 없었다
    // 그래서 i = 1부터 시작하도록 변경
    // < 연산자라서 length - 1까지가 아닌 것도 체크
    for (let i = 1; i < que.length; i++) {
        // ✅ 인접 요소가 아닌 특정 target 값과 비교
        if (target < que[i]) {
            // 배포 불가능한 경우
            answer.push(count);
            target = que[i]; // ✅ 새로운 값으로 업데이트
            count = 1;
        } else {
            // 배포 가능한 경우
            count += 1;
        }
    }
    
    // ✅ 루프가 끝나면 그때까지 누적된 count도 answer 배열에 포함해야 함!
    answer.push(count);
    return answer;
}

풀면서 찾은 개념

  • Math.ceil()
    • 항상 숫자에 바로 적용하려 한다. (문자열, 배열 메서드 쓰듯이..) 이번에도 오류와 마주하고 아, Math 메서드였지..깨닫기
    • 올림, 반올림, 버림 메서드들은 항상 이름이 헷갈린다
    • 올림: Math.ceil
    • 반올림: Math.round
    • 버림: Math.floor

다음에 비슷한 문제 만나면

  1. 순서가 중요한 문제라면 큐(선입선출) 떠올리기: 앞에 있는 것이 처리되어야 뒤에 있는 것도 처리 가능한 구조
  2. 인접 요소끼리 비교했는데 틀렸을 경우: 비교 기준이 계속 바뀌는지 체크, 바뀌지 않는 동안은 고정된 target과 비교해야 함
  3. 반복문 밖에서 마지막 케이스를 answer 배열에 push하는 것 잊지 말기!: 루프가 끝나면 마지막 그룹이 answer에 안 담긴 상태임
    • "그룹을 만들면서 순회할 때", 루프 안에서 순회 중일 때는 조건을 만족하면 현재 그룹을 저장하고 새 그룹을 시작하니 문제 없는데, 순회가 끝나 루프에서 나왔을 때 바로 return answer 처리하면 마지막 그룹은 포함되지 않은 상태임을 기억할 것
    • 루프 끝난 뒤 마지막 처리가 필요한지 체크하는 습관 들이기
  4. 엣지케이스 의심될 때: [7, 3, 5] 같은 단순한 예시 떠올려보고 손으로 차근히 접근해보기

(비고) 다른 접근법

  • 선입선출의 자료구조가 잘 활용된 것인지 애매해서 (for문을 돌리니까 순차적으로 앞에서부터 처리하긴 하지만!) 좀더 고민해봤다.
  • 배열의 앞에서부터 요소를 제거하는 shift 메서드와 내가 잘 안 쓰는 while문을 사용한 풀이법을 적어둔다.
function solution(progresses, speeds) {
    const answer = [];
    
    const que = progresses.map((p, index) => {
        const days = Math.ceil((100 - p) / (speeds[index]));
        return days;
    })
    
    
    while (que.length > 0) {
        const target = que.shift();
        let count = 1;
        
        // target보다 작거나 같은 것들을 앞에서부터 꺼내기
        while (que.length > 0 && que[0] <= target) {
            que.shift();
            count++;
        }
        
        answer.push(count);
    }
    
    return answer;
}
  • 원본 배열을 수정해버리는 shift는 혹시 모를 버그에 대비해 거의 안쓰긴 했다. (예를 들어 같은 배열을 다른 곳에서도 쓰고 있는데 shift로 다 꺼내버리면 그 다음에 참조하는 곳에서 빈 배열을 보게 되니까)
  • 코테 수준에선 문제되지 않을테니 다시 한번 눈도장!
profile
좋아하는 일을 잘함으로써 먹고살고 싶은 프론트엔드 개발자입니다.

0개의 댓글