6. Failure rate

도달한 사용자 수와 실패율을 정확하게 계산하고, 이를 효율적으로 정렬하여 올바른 순서로 스테이지를 반환

algorithm test

06. failure rate

Sunhyup Lee. (2024). To be a successful candidate for the coding test. goldenrabbit. p.125.


Problem solving

입력조건:

  1. N: 스테이지의 수

  2. stages: 각 사용자가 도전 중인 스테이지 번호가 담긴 배열

예를 들어, stages = [2, 1, 2, 6, 2, 4, 3, 3] 은 8명의 사용자가 각기 다른 스테이지에 도전 중이라는 의미

  1. 각 스테이지는 1번부터 N번까지 있고, N+1번 스테이지에 있는 사용자들은 모든 스테이지를 클리어 한 사용자이다.


실패율 정의:

  • 실패율은 특정 스테이지에 도달했으나 클리어하지 못한 사용자 수를, 그 스테이지에 도달한 총 사용자 수로 나눈 값입니다.

  • 실패율이 높은 스테이지부터 내림차순으로 스테이지 번호를 반환해야 합니다.


입출력 예시:

1) N = 5, stages = [2, 1, 2, 6, 2, 4, 3, 3] - 이 경우, 각 스테이지의 실패율을 계산한 수 결과는 [3, 4, 2, 1, 5] 이다.

  1. N = 4, stages = [4, 4, 4, 4, 4]
    • 이 경우, 결과는 [4, 1, 2, 3]이다.

해결 과정 예시:

  1. 스테이지1: 8명 중 1명 실패 → 실패율 = 1/8
  2. 스테이지2: 7명 중 3명 실패 → 실패율 = 3/7
  3. 스테이지3: 4명 중 2명 실패 → 실패율 = 2/4
  4. 스테이지4: 2명 중 1명 실패 → 실패율 = 1/2
  5. 스테이지5: 0명 → 실패율 = 0/1

결과적으로 실패율이 높은 순서대로 스테이지를 정렬하면 [3, 4, 2, 1, 5]가 된다.


① The first way to solve the problem : 단계별 사용자 수 계산 후, 직접 실패율 계산

function solution(N, stages) {
	// N: 스테이지의 수, stages: 각 사용자가 도전중인 스테이지 번호가 담긴 배열

    let failureRates = [];
    // filureRates: 각 스테이지의 번허와 실패율을 저장할 배열
    
    let totalUsers = stages.length;
  	// totalUsers: 남은 사용자의 수, 초기값은 도전 중인 사용자들의 전체수(stage.length)

    for (let stage = 1; stage <= N; stage++) {

        let failedUsers = stages.filter(s => s === stage).length;
        // 해당 스테이지에 도전 중인 사용자 수
     
        let failureRate = totalUsers === 0 ? 0 : failedUsers / totalUsers;
				// 스테이지에 도달한 사용자가 없을 경우 실패율 0

        failureRates.push({ stage: stage, rate: failureRate });
        // 실패율 배열에 (스테이지 번호, 실패율) 저장
      
        totalUsers -= failedUsers;
        // 다음 스테이지로 넘어가므로 도전한 사용자를 뺌
    }

    failureRates.sort((a, b) => {
        // 실패율을 기준으로 내림차순 정렬, 실패율이 같으면 스테이지 번호 순으로 오름차순 정렬
        if (b.rate === a.rate) {
            return a.stage - b.stage;  // 실패율이 같으면 스테이지 번호 기준 오름차순
        }
        return b.rate - a.rate;  // 실패율 기준 내림차순
    });

    return failureRates.map(item => item.stage);
        // 정렬된 스테이지 번호만 반환
}

// 예시 실행
let N = 5;
let stages = [2, 1, 2, 6, 2, 4, 3, 3];
console.log(solution(N, stages));  // 출력: [3, 4, 2, 1, 5]

🧐 시간복잡도는 어떨까?

[Done] exited with code=0 in 0.063 seconds

1. 사용자 수를 계산하는 단계: O(M)

// stage 배열을 한 번 순회하며, 각 사용자가 도전 중인 스테이지를 카운트한다. 
stages.forEach(stage => {
    stageCounts[stage]++;
});

2. 실패율을 계산하는  단계: O(N)

// 1번부터 N번까지의 각 스테이지에 대해 도전한 사용자 수와 실패율을 계산
for (let stage = 1; stage <= N; stage++) {
    let failedUsers = stageCounts[stage];
    let failureRate = 0;
    
    if (totalUsers > 0) {
        failureRate = failedUsers / totalUsers;
    }

    failureRates.push({ stage: stage, rate: failureRate });

    totalUsers -= failedUsers;
}

3. 정렬  단계: O(N logN)

// 이 단계에서는 실패율을 기준으로 스테이지를 정렬
failureRates.sort((a, b) => {
    if (b.rate === a.rate) {
        return a.stage - b.stage;
    }
    return b.rate - a.rate;
});

4. 결과 배열 생성 단계: O(N)

// 정렬된 failureRates 배열에서 스테이지 번호만 추출
return failureRates.map(item => item.stage);

최종 시간 복잡도: O(N + N logN)



② The second way to solve the problem : 맵(Map)을 사용하여 실패율 계산

function solution(N, stages) {
    let stageCounts = new Map();
    
    stages.forEach(stage => {
        if (stageCounts.has(stage)) {
            stageCounts.set(stage, stageCounts.get(stage) + 1);
        } else {
            stageCounts.set(stage, 1);
        }
    });

    let totalUsers = stages.length;
    let failureRates = [];

    for (let stage = 1; stage <= N; stage++) {
        let failedUsers = stageCounts.get(stage) || 0;
        let failureRate = 0;
        
        if (totalUsers > 0) {
            failureRate = failedUsers / totalUsers;
        }

        failureRates.push({ stage: stage, rate: failureRate });

        totalUsers -= failedUsers;
    }

    failureRates.sort((a, b) => {
        if (b.rate === a.rate) {
            return a.stage - b.stage;
        }
        return b.rate - a.rate;
    });

    return failureRates.map(item => item.stage);
}

// 예시 실행
let N = 5;
let stages = [2, 1, 2, 6, 2, 4, 3, 3];
console.log(solution(N, stages));  // 출력: [3, 4, 2, 1, 5]

🧐 시간복잡도는 어떨까?

[Done] exited with code=0 in 0.064 seconds

1. 각 스테이지에 도달한 사용자 수 단계: O(M)

// stages 배열을 순회하며 각 사용자가 도전 중인 스테이지 번호를 Map객체에 저장
stages.forEach(stage => {
    if (stageCounts.has(stage)) {
        stageCounts.set(stage, stageCounts.get(stage) + 1);
    } else {
        stageCounts.set(stage, 1);
    }
});

2. 실패율 계산 단계: O(N)

// 1번부터 N번까지의 각 스테이지에 대해 실패율을 계산하고, failureRates 배열에 저장
for (let stage = 1; stage <= N; stage++) {
    let failedUsers = stageCounts.get(stage) || 0;
    let failureRate = 0;
    
    if (totalUsers > 0) {
        failureRate = failedUsers / totalUsers;
    }

    failureRates.push({ stage: stage, rate: failureRate });

    totalUsers -= failedUsers;
}

3. 실패율을 기준으로 정렬 단계: O(N logN)

// 실패율을 기준으로 failureRates 배열을 정렬
failureRates.sort((a, b) => {
    if (b.rate === a.rate) {
        return a.stage - b.stage;
    }
    return b.rate - a.rate;
});

4. 결과 배열 생성: O(N)

// 정렬된 failureRate 배열에서 스테이지 번호만 추출하여 새로운 배열 생성
return failureRates.map(item => item.stage);

최종 시간 복잡도: O(N + N logN)



③ The third way to solve the problem : 배열의 인덱스를 활용한 직접 계산

function solution(N, stages) {
    let stageUsers = new Array(N + 1).fill(0);
    
    // 각 스테이지에 도달한 사용자 수 카운트
    stages.forEach(stage => {
        if (stage <= N) {
            stageUsers[stage]++;
        }
    });

    let totalUsers = stages.length;
    let failureRates = [];

    for (let i = 1; i <= N; i++) {
        if (totalUsers > 0) {
            let failureRate = stageUsers[i] / totalUsers;
            failureRates.push({ stage: i, rate: failureRate });
            totalUsers -= stageUsers[i];
        } else {
            failureRates.push({ stage: i, rate: 0 });
        }
    }

    failureRates.sort((a, b) => b.rate - a.rate || a.stage - b.stage);

    return failureRates.map(x => x.stage);
}

// 예시 실행
let N = 5;
let stages = [2, 1, 2, 6, 2, 4, 3, 3];
console.log(solution(N, stages));  // 출력: [3, 4, 2, 1, 5]

🧐 시간복잡도는 어떨까?

[Done] exited with code=0 in 0.072 seconds

1. 각 스테이지에 도달한 사용자 수 단계: O(M)

// stages 배열을 순회하며 각 사용자가 도전 중인 스테이지 번호를 카운트하여 stageUsers 배열에 저장
stages.forEach(stage => {
    if (stage <= N) {
        stageUsers[stage]++;
    }
});

2. 실패율 계산 단계: O(N)

// 1번부터 N번까지의 각 스테이지에 대해 실패율을 게산하고, failureRate 배열에 저장
for (let i = 1; i <= N; i++) {
    if (totalUsers > 0) {
        let failureRate = stageUsers[i] / totalUsers;
        failureRates.push({ stage: i, rate: failureRate });
        totalUsers -= stageUsers[i];
    } else {
        failureRates.push({ stage: i, rate: 0 });
    }
}

3. 실패율을 기준으로 정렬 단계: O(N logN)

// 실패율을 기준으로 failureRates 배열에 정렬
failureRates.sort((a, b) => b.rate - a.rate || a.stage - b.stage);

4. 결과배열 정렬 단계: O(N logN)

// 정렬된 failureRates 배열에서 스테이지 번호만 추출하여 새로운 배열을 생성
return failureRates.map(x => x.stage);

최종 시간 복잡도: O(N + N logN)