탐욕 알고리즘: 큰 동전 선택과 반례로 확인하는 최적성

·설명 약 6분·작성: 해비·블로그 소개

탐욕 알고리즘에서 큰 동전부터 고르면 항상 최소 개수가 되나요?

탐욕 알고리즘은 지금 가장 좋아 보이는 선택을 확정합니다. 큰 동전부터 고르는 규칙이 항상 동전 수를 최소로 만드는 것은 아니므로, 적용 조건의 증명과 작은 반례 확인이 필요합니다.

적용 조건·범위: 첨부 예시와 같은 500·100·50·10에서는 위 배수 관계를 확인할 수 있고, 2380은 500원 4개, 100원 3개, 50원 1개, 10원 3개로 나뉩니다. 합은 2380이고 개수는 11개입니다. 목표가 10의 배수가 아니면 이 동전들로는 정확히 만들 수 없다는 조건도 함께 따릅니다. 동전마다 사용 가능한 개수가 제한되거나 종류별 비용이 달라지면 다른 문제가 되므로 같은 증명을 그대로 적용하지 않습니다.

본문 근거: Competitive Programmer’s Handbook — 6.1 Coin problem

읽기 전에 알아두면 좋아요

아래 개념이 익숙하면 본문을 바로 읽으세요. 낯선 개념만 링크에서 복습하면 됩니다.

개념과 예제는 실행하지 않아도 읽을 수 있습니다. 내 컴퓨터·터미널에서 직접 실행하는 과정은 선택 실습입니다.

과정 위치와 선택 복습 안내

과정 위치: 그리디 선택과 반례 · 알고리즘 개념

과정 위치는 이 글이 속한 묶음입니다. 이 단계의 모든 개념을 먼저 읽을 필요는 없습니다. 위에 연결한 개념 중 낯선 내용만 복습하세요. 문제의 조건·상태·갱신 순서는 익숙한 언어로 설명하면 됩니다.

JavaScript로 문법·실행 복습하기 (선택)

개념 글의 코드는 JavaScript 예시입니다. 문제 화면에서는 JavaScript·Python·Java·C++·C# 중 익숙한 언어의 함수를 작성하세요. 본문의 Node.js 입출력 예시는 로컬 실행을 위한 별도 안내입니다.

탐욕 알고리즘: 큰 동전 선택과 반례로 확인하는 최적성 주제를 표현한 밝은 일러스트와 작은 곰 표지

탐욕 알고리즘은 지금 가장 좋아 보이는 선택을 확정합니다. 큰 동전부터 고르는 규칙이 항상 동전 수를 최소로 만드는 것은 아니므로, 적용 조건의 증명과 작은 반례 확인이 필요합니다.

선수 지식: 정수 나눗셈·나머지·배열 정렬·최솟값 비교

  • 선택 기준과 남은 상태를 분리하기
  • 동전 수 최소화의 반례 설명하기
  • 만들 수 없는 나머지를 결과에 보존하기

선택한 개수와 남은 금액을 함께 갱신합니다

동전 종류마다 개수가 무한히 있고, 목표 금액을 정확히 만드는 데 쓴 동전 수를 줄인다고 하겠습니다. 가장 큰 동전부터 가능한 만큼 고르는 규칙은 이해하기 쉽습니다. 예제 함수는 양의 안전한 정수인 동전 값을 중복 제거 후 내림차순으로 정렬합니다. 현재 동전 값이 c이고 남은 금액이 r이면 floor(r/c)개를 선택하고 그만큼의 금액을 r에서 뺍니다. 동전을 한 개씩 빼는 반복보다 상태 변화가 명확하게 드러납니다.

목표 6에서 동전 4 한 개를 고른 뒤 남은 금액 2로 갱신하는 첫 단계
본문 예제의 첫 단계입니다. 고른 금액과 남은 금액의 합은 목표 6으로 유지되지만, 이 상태만으로 최적성은 보장되지 않습니다. 원본 보기 (새 탭)

중요한 상태는 “이미 선택한 동전의 합 + 남은 금액 = 원래 목표”입니다. 각 단계에서 이 등식은 유지되며, 작은 동전으로 넘어간 뒤 큰 동전 선택을 다시 검토하지 않습니다. 이것이 탐욕적인 결정입니다. 반환값에는 종류별 개수, 전체 개수, 남은 금액을 모두 담습니다. 선택이 끝났다고 해서 목표를 정확히 만들었다는 뜻은 아니므로 나머지도 함께 읽어야 합니다.

1·3·4로 6을 만들면 큰 동전부터 고르기가 실패합니다

동전 값이 1, 3, 4이고 목표가 6이면 탐욕 선택은 4를 먼저 고릅니다. 남은 2에서는 3을 쓸 수 없으므로 1을 두 개 고릅니다. 총 세 개로 목표 금액을 정확히 만들지만, 3+3이면 두 개로 충분합니다. 따라서 “합이 맞는다”는 실행 가능성과 “개수가 가장 적다”는 최적성은 서로 다른 주장입니다. 한 가지 반례만 있어도 모든 동전 체계에 대한 일반적인 최적성 주장은 성립하지 않습니다.

동전 1, 3, 4로 6을 만들 때 탐욕 세 개와 다른 선택 두 개의 비교
4+1+1도 목표를 맞추지만 3+3이면 동전 두 개로 충분합니다. 합을 맞추는 것과 최소 개수임을 보이는 것은 다른 주장입니다. 원본 보기 (새 탭)
탐욕 선택의 상태 변화
검토 동전 선택 수 남은 금액 누적 개수
4 1 2 1
3 0 2 1
1 2 0 3

이 반례를 통해 마지막 동전을 고르는 부분이 아니라 첫 결정 자체를 살펴볼 수 있습니다. 4를 먼저 확정하면 이후에 아무리 잘 골라도 3+3으로 돌아갈 수 없습니다. 작은 입력에서 가능한 조합을 열거하거나 동적 계획법으로 최소 개수를 계산하면 탐욕 규칙의 실패를 찾는 데 도움이 됩니다. 몇 개의 입력에서 맞았다는 사실만으로 모든 금액에 대한 증명이 되지는 않습니다.

좋아 보이는 선택을 최적해로 바꿀 수 있는지 묻습니다

탐욕 규칙을 쓰려면 어떤 최적해가 현재의 선택을 포함하도록 바뀔 수 있는지 설명해야 합니다. 예를 들어 각 큰 동전이 바로 아래 동전의 정수 배수인 체계에서는 작은 동전 여러 개를 큰 동전 한 개로 교환하면 개수가 늘지 않는다는 방향으로 증명을 시작할 수 있습니다. 이런 교환을 반복하여 큰 동전부터 사용하는 최적해가 있음을 보이는 것입니다. 특정 화폐의 이름이나 익숙함 자체가 증명의 근거는 아닙니다.

작은 동전을 큰 동전으로 교환하는 관계와 목표 2380의 종류별 개수
500·100·50·10의 배수 관계를 이용한 예입니다. 목표 2380은 각 종류 4·3·1·3개로 나뉘며 총 11개입니다. 목표가 10의 배수라는 조건도 필요합니다. 원본 보기 (새 탭)

첨부 예시와 같은 500·100·50·10에서는 위 배수 관계를 확인할 수 있고, 2380은 500원 4개, 100원 3개, 50원 1개, 10원 3개로 나뉩니다. 합은 2380이고 개수는 11개입니다. 목표가 10의 배수가 아니면 이 동전들로는 정확히 만들 수 없다는 조건도 함께 따릅니다. 동전마다 사용 가능한 개수가 제한되거나 종류별 비용이 달라지면 다른 문제가 되므로 같은 증명을 그대로 적용하지 않습니다.

나머지와 입력 범위를 결과의 일부로 봅니다

동전 4와 6으로 금액 5를 처리하면 함수는 4 한 개를 고르고 나머지 1을 반환합니다. 이 결과를 성공한 거스름돈으로 출력하면 안 됩니다. 더 나아가 동전 3과 4로 금액 6을 처리하면 탐욕의 나머지는 2이지만 3+3이라는 해가 있습니다. 따라서 나머지가 남았다는 결과도 “이 탐욕 선택으로 완성하지 못했다”는 뜻이며, 모든 조합이 불가능하다는 판정은 아닙니다.

탐욕 나머지가 남는 두 예와 다른 조합으로 목표를 만들 수 있는 경우
나머지는 이 탐욕 선택의 결과입니다. 동전 [3,4]에서는 4를 고른 뒤 나머지가 남아도 3+3이라는 조합이 있으므로 문제 자체가 불가능하다는 뜻은 아닙니다. 원본 보기 (새 탭)

동전 값 0은 금액을 줄이지 못하고 음수는 의미를 뒤집으므로 거절합니다. 소수 금액도 이 정수 동전 계약에 포함되지 않습니다. 목표 0은 아무 동전도 고르지 않아 개수와 나머지가 모두 0입니다. 입력 종류 수가 K이면 중복 제거와 정렬 후 한 번씩 계산하므로 보통 비교 정렬 비용을 O(K log K), 결과와 정렬 복사 공간을 O(K)로 셉니다. 실제로 선택한 동전 수만큼 배열을 늘리지 않으므로 큰 금액을 출력 목록으로 펼치는 비용은 들지 않습니다.

전체 코드와 직접 실행

개념을 읽었다면 아래 예제를 직접 실행하며 상태 변화를 확인할 수 있습니다.

전체 JavaScript 코드와 실행 방법 펼치기

별도 패키지는 필요하지 않습니다. Node.js가 처음이라면 브라우저와 Node.js 시작 안내부터 확인하세요.

algorithm-greedy-coin-counterexample.mjs 파일을 만들고 아래 전체 코드를 저장합니다.

function greedyCoins(denominations, amount) {
  if (!Array.isArray(denominations)) throw new TypeError('동전 배열이 필요합니다.');
  for (const value of denominations) {
    if (!Number.isSafeInteger(value) || value <= 0) {
      throw new RangeError('동전 값은 양의 안전한 정수여야 합니다.');
    }
  }
  if (!Number.isSafeInteger(amount) || amount < 0) {
    throw new RangeError('금액은 0 이상의 안전한 정수여야 합니다.');
  }
  const values = [...new Set(denominations)].sort((a,b) => b-a);
  let remainder = amount;
  let count = 0;
  const coins = values.map(value => {
    const used = Math.floor(remainder/value);
    remainder -= used*value;
    count += used;
    return { value, count: used };
  });
  return { coins, count, remainder };
}

console.log(JSON.stringify(greedyCoins([1,3,4],6)));
console.log('대안: 3 + 3 = 6, 동전 2개');
console.log(JSON.stringify(greedyCoins([4,6],5)));

파일이 있는 폴더의 터미널에서 실행합니다.

node algorithm-greedy-coin-counterexample.mjs

기대 출력

{"coins":[{"value":4,"count":1},{"value":3,"count":0},{"value":1,"count":2}],"count":3,"remainder":0}
대안: 3 + 3 = 6, 동전 2개
{"coins":[{"value":6,"count":0},{"value":4,"count":1}],"count":1,"remainder":1}

비용과 적용 범위

동전 입력 종류 K개에 대해 비교 정렬을 O(K log K)로 세면 전체 시간 O(K log K), 결과와 정렬 복사 공간 O(K)입니다. 무한히 쓸 수 있는 양의 안전한 정수 동전·0 이상의 안전한 정수 금액을 다루며, 일반 동전 체계의 최적성이나 해 존재 여부를 보장하지 않습니다.

동전 종류별 정렬과 개수 계산 및 압축된 결과 기록의 비용
K는 입력 동전 종류 수이며 실제 고른 동전 개수와 다릅니다. 종류별 개수만 기록하고 동전 낱개 목록을 만들지 않는 계약에서 비교 정렬 기준 O(K log K) 시간, O(K) 공간입니다. 원본 보기 (새 탭)

직접 확인해 보기

동전 [3,4]로 6을 처리한 탐욕 결과의 나머지는 얼마일까요? 이 값으로 정확한 조합이 없다고 결론 내릴 수 있을까요?

동전 3과 4로 목표 6을 처리할 때 탐욕 나머지와 해의 존재를 묻는 문제
사용할 수 있는 동전은 3과 4입니다. 큰 동전부터 고른 결과와 그 결과로 무엇을 결론낼 수 있는지 생각해 보세요. 원본 보기 (새 탭)
답과 이유 확인

4 한 개를 선택하여 나머지는 2입니다. 하지만 3+3이면 6이므로 조합은 존재합니다. 이 규칙의 실패와 문제 자체의 불가능을 구분해야 합니다.

참고 자료

이 글이 도움이 되었나요?

조회 중

이어서 공부하기

체크포인트: 예제의 상태 변화를 손으로 추적하고 아래 문제에 적용하세요. 값이 없는 경우와 원소 하나인 경우도 설명해 보세요. 동전 선택이 실패하는 입력과 종료 시각 기준 인터벌 선택의 교환 근거를 설명하세요.

다음 추천 · 알고리즘 개념: 인터벌 스케줄링: 가장 먼저 끝나는 일정을 고르는 이유

같은 주제에서 다음 개념과 예제로 이해를 넓힙니다.

권장 순서는 학습 가이드와 같습니다. 다른 순서는 접힌 참고 목록에서 확인할 수 있습니다. 읽음 표시는 숙달이나 채점 결과가 아닙니다.

원래 과정 목록 순서 (선택)

읽음 기록

읽음 — / 전체 30개 · 필수 0개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기