Python/이것이코딩테스트다with파이썬

12~13강 :: 그리디 알고리즘 개요와 문제 풀이

묘걍 2023. 11. 9. 21:48

👉🏻 그리디 알고리즘

  • 그리디 알고리즘(탐욕법)은 현재 상황에서 지금 당장 좋은 것만 고르는 방법을 의미한다
  • 일반적인 그리디 알고리즘은 문제를 풀기 위한 최소한의 아이디어를 떠올릴 수 있는 능력을 요구한다
  • 그리디 해법은 그 정당성 분석이 중요하다
    • 단순히 가장 좋아 보이는 것을 반복적으로 선택해도 최적의 해를 구할 수 있는지 검토한다

- greedy: 탐욕적인

- 크루스칼 알고리즘, 다익스트라 알고리즘을 제외하고 그리디 알고리즘이 출제 되면 해당 문제를 풀 수 있는 최소한의 아이디어를 적절히 떠올릴 수 있어야 문제가 풀리도록 출제되는 경우가 많다

 

🧩 문제 상황

  • 루트 노드부터 시작하여 거쳐 가는 노드 값의 합을 최대로 만들고 싶다. 이 때 최적의 해는?

출처: 이것이코딩테스트다 유튜브

- 5 + 7 + 9 가 가장 크다

✅ 단순히 매 상황에서 가장 큰 값만 고른다면 어떻게 될까?

출처: 이것이 코딩테스트다 유튜브

- 루트노드에서 인접한 노드는 7, 10, 8이다. 이 때 최대값은 10이므로 10을 선택한다

- 그 다음 10에서 인접한 노드는 4, 3이다. 이때 최대값은 4이므로 4를 선택한다

- 그럼 최종적으로 총합 19가 나온다. 최적의 해인 21보다 작은 수이다

- 이처럼 단순히 매 상황에서 가장 큰 값만 고르는 방식

  • 일반적인 상황에서 그리디 알고리즘은 최적의 해를 보장할 수 없을 때가 많다
    • 실제 프로그램을 개발할 때는 그리디 알고리즘을 써도 충분히 최적의 해에 가까운 값을 얻을 수 있거나 최적의 해를 얻을 수 있을 때 그리디 알고리즘을 사용
  • 하지만 코딩테스트에서의 대부분의 그리디 문제는 탐욕법으로 얻은 해가 최적의 해가 되는 상황에서, 이를 추론할 수 있어야 풀리도록 출제된다
    • 출제자가 입출력값을 정해놓고 문제를 만든다 → 그리디 알고리즘을 사용해도 된다고 추론할 수 있어야 문제가 풀리도록 의도

🧩 문제

출처: 이것이 코딩테스트다 유튜브

  • 최적의 해를 빠르게 구하기 위해서는 가장 큰 화폐 단위부터 돈을 거슬러주면 된다
  • N운을 거슬러 줘야 할 때, 가장 먼저 500원으로 거슬러 줄 수 있을 만큼 거슬러준다
    • 이후에 100원, 50원, 10원짜리 동전을 차례대로 거슬러 줄 수 잇을 만큼 거슬러 주면 된다.

✅ N = 1,260일 때 예시를 확인해보자

남은 돈💵 : 1,260

출처: 이것이 코딩 테스트다 유튜브

1️⃣  500원짜리 먼저 줌

남은 돈💵 : 260

출처: 이것이 코딩 테스트다 유튜브

2️⃣  100원짜리 줌

남은 돈💵 : 60

출처: 이것이 코딩 테스트다 유튜브

3️⃣  50원짜리 줌

남은 돈💵 : 10

출처: 이것이 코딩 테스트다 유튜브

4️⃣ 10원짜리 줌

출처: 이것이 코딩 테스트다 유튜브

- 단순히 가장 큰 화폐부터 거슬러 주는 것으로 최적의 해를 보장할 수 있는 이유?

✅ 정당성 분석

  • 가장 큰 화폐 단위부터 돈을 거슬러 주는 것이 최적의 해를 보장하는 이유는 무엇일까?
    • 가지고 있는 동전 중에서 큰 단위가 항상 작은 단위의 배수이므로 작은 단위의 동전들을 종합해 다른 해가 나올 수 없기 때문이다
  • 만약 800원 거슬러 주어야 하는데 화폐 단위가 500원, 400원, 100원이라면?
    • 알고리즘에 따르면 500원 하나 + 100원 3개
    • 하지만 400원짜리 두 개 거슬러 주는 게 답
    • 즉, 큰 단위가 작은 단위의 배수가 아니라면 이 알고리즘을 통해 최적의 해를 보장할 수 없다
  • 그리디 알고리즘 문제에서는 이처럼 문제 풀이를 위한 최소한의 아이디어를 떠올리고 이것이 정당한지 검토할 수 있어야 한다.

💡 답안 예시

- 큰 단위부터 리스트에 담아줌

- 각각의 동전을 확인하면서 결과값에 n을 coin으로 나눈 몫을 담는다

     - 현재 남아 있는 돈을 현재의 동전으로 최대한 많이 거슬러주도록

- 남은 돈은 n을 coin으로 나눈 나머지

✅ 시간 복잡도 분석

  • 화폐의 종류가 K라고 할 때, 소스코드의 시간 복잡도는 O(K)이다
  • 이 알고리즘의 시간 복잡도는 거슬러줘야 하는 금액과는 무관하며, 동전의 총 종류에만 영향을 받는다

✅ 다른 언어 답안 보기

  • C++

출처: 이것이 코딩 테스트다 유튜브

- 표준 라이브러리 불러오기

- C++에서는 전역변수는 자동으로 0으로 초기화됨

- 배열에 동전 종류를 담음

- 반복문 (의미는 python과 같음)

  • Java

출처: 이것이 코딩 테스트다 유튜브

- 클래스 명을 Main으로 요구하는 경우가 많음

 


👉🏻 문제1 : 1이 될 때 까지

출처: 이것이 코딩 테스트다 유튜브

🧩 조건

출처: 이것이 코딩 테스트다 유튜브

💡 스스로 풀어보기

- 일단 답은 나왔으나 그리디 알고리즘을 제대로 활용했는지는 모르겠다

 

🧩 문제 해결 아이디어

  • 주어진 N에 대하여 최대한 많이 나누기를 수행하면 된다
  • N의 값을 줄일 때 2 이상의 수로 나누는 작업이 1을 빼는 작업보다 수를 훨씬 많이 줄일 수 있다
  • 예를 들어 N = 25, K = 3일 때 다음과 같다

출처: 이것이 코딩 테스트다 유튜브

🧩 정당성 분석

  • 가능하면 최대한 많이 나누는 작업이 최적의 해를 항상 보장할 수 있을까?
  • N이 아무리 큰 수여도, K로 계속 나눈다면 기하급수적으로 빠르게 줄일 수 있다
  • 다시 말해 K가 2 이상이기만 하면, K로 나누는 것이 1을 빼는 것 보다 항상 빠르게 N을 줄일 수 있다
    • 또한 N은 항상 1에 도달하게 된다. (최적의 해 성립)

💡 예시 답안

- 입력받은 문자열을 공백을 기준으로 나누고 map 함수를 이용해서 각각 int형으로 만들어준다

- target에 n을 k로 나눈 값에 k를 다시 곱한 값을 넣어준다.

     - n이 k로 나누어 떨어지지 않을 때 가장 가까운 k로 나누어떨어지는 수가 어떤 것인지를 찾고자 할 때 사용할 수 있다?

        (예를 들어 n이 17이고 k가 5라면 15가 됨)

- 즉, n에서 1을 빼는 과정을 몇번 반복해서 target이라는 값 까지 만들 수가 있고

- target이라는 값은 k로 나누어 떨어지는 수가 된다.

(N 안에서 나누기를 최대한 많이 이용해야하기 때문에 N보다 작은 수 중 k로 나누어떨어지는 가장 큰 수를 만드는 과정인 듯)

- 총 연산 횟수인 result에 1을 빼는 연산 횟수를 넣어준다. n에서 target을 뺀 값 만큼 1을 빼줘야 한다 (한번에)

- n이 k보다 작다면 반복문을 탈출한다

- 작지 않다면 n을 k로 나눠준다

- k로 나누는 연산을 한 번 수행하기 때문에 result에 1을 더해준다

- n이 1보다 크다면 1이 될 수 있도록 만들기 위해 남은 수에 대해 1씩 빼는 연산 수행

 

- n과 k가 10만보다 작은 정수이기 때문에 그냥 매번 n을 확인해서 n이 k로 나누어 떨어진다면 나눠주고 아니라면 1을 빼줘도 됨

- 하지만 위와 같이 작성할 경우 반복문 한 번에 n을 k로 나누는 연산이 한 번 이루어짐 반복 횟수에 다라 n이 기하급수적으로 줄어든다

- 시간복잡도가 log시간복잡도가 나올 수 있음

* C++ ver

출처: 이코테 유튜브

*Java ver

출처: 이코테 유튜브


👉🏻 문제2: 곱하기 혹은 더하기

출처: 이코테 유튜브

- 20억 이하의 정수인 이유: 일반 프로그래밍 언어에서 정수 데이터를 위해 기본 int형을 사용할 경우 약 21억까지 가능

- 사실 파이썬에서는 기본적으로 정수 데이터를 처리함에 있어서 수의 범위 제한이 없다

🧩 조건

출처: 이코테 유튜브

💡 스스로 풀어보기

- 일단 정답이 나오긴 함

🧩 문제 해결 아이디어

  • 대부분의 경우 '+'보다는 'x'가 더 값을 크게 만든다
    • 예를 들어 5 + 6 = 11, 5 × 6 = 30
  • 다만 두 수 중에서 하나라도 '0'혹은 '1'인 경우, 곱하기 보다는 더하기를 수행하는 것이 효율적이다
  • 따라서 두 수에 대하여 연산을 수행할 때, 두 수 중에서 하나라도 1 이하인 경우에는 더하고 두 수가 모두 2 이상인 경우에는 곱하면 정답이다

💡 예시 답안

- 숫자로만 구성된 하나의 문자열을 입력받는다

- 첫 번째 문자를 숫자로 변경하여 result에 대입한다

- 두 번째 숫자부터 차례로 확인하면서

- 두 수(result와 num)중 하나라도 0혹은 1이라면 더하기, 아니라면 곱하기를 수행한다

* C++ ver

출처: 이코테 유튜브

* Java ver

출처: 이코테 유튜브


👉🏻 문제3: 모험가 길드

🧩 문제 설명

출처: 이코테 유튜브
출처: 이코테 유튜브

🧩 조건

출처: 이코테 유튜브

💡 스스로 풀어보기

- 완성하지 못함

- 내림차순으로 정렬하여 인원을 많이 필요로 하는 것 부터 해결해야된다고 생각했음

    - 만약 공포도가 1인 경우 혼자 남아도 혼자 팀을 꾸릴 수 있기 때문에

🧩 문제 해결 아이디어

출처: 이코테 유튜브
출처: 이코테 유튜브

💡 예시 답안

- count는 그룹에 포함된 멤버수로 그룹이 결성이 되면 0으로 다시 돌아와야 한다

- 일단 현재 그룹에 모험가 한 명 포함시키기

- 만약 현재 그룹에 포함된 모험가의 수 >= 현재 공포도 라면 그룹 결성

- if에 해당하지 않는다면(현재 그룹에 포함된 모험가의 수 < 현재 공포도) count += 1을 계속 실행

* C++ ver

출처: 이코테 유튜브

*Java ver

출처: 이코테 유튜브

 

 

 

 

 

 

 

 

 

 

출처: https://youtu.be/5OYlS2QQMPA?si=S8aWBbiYyVJZJ3xl

https://youtu.be/_TG0hVYJ6D8?si=jouig9-Vec3QmFP6