728x90
반응형
문제 요약
- 알고리즘 분류: 이분탐색, 이진 탐색
- 난이도: Medium
- 문제내용:
- 가방마다 공이 들어 있고, 우리는 특정 횟수만큼 가방을 나눌 수 있습니다.
- 한 가방을 두 개로 나눌 때 각 가방에는 반드시 1개 이상 공이 있어야 한다.
- 작업을 수행한 후 가능한 최소 패널티를 반환하세요.
- 사이트 주소: https://leetcode.com/problems/minimum-limit-of-balls-in-a-bag/
문제풀이
이번 문제에는 이분탐색을 활용한 문제이다. 관련 내용은 밑에 글에서 확인 해보면 된다.
https://jih3508.tistory.com/288
[알고리즘 이론] 이진 탐색(Binary Search)
이진 탐색(Binary Search) 완벽 정리 🔍안녕하세요! 오늘은 프로그래밍에서 가장 기본적이면서도 중요한 알고리즘 중 하나인 **이진 탐색(Binary Search)**에 대해 알아보겠습니다. 이진 탐색은 "이분 탐
jih3508.tistory.com
문제의 핵심:
- 가방을 둘로 나누는 연산을 최대 maxOperations번까지 할 수 있습니다
- 목표는 "가장 많은 공이 들어있는 가방"의 공 개수를 최소화하는 것입니다
📝 예시로 이해해보기
예시 1: nums = [9], maxOperations = 2
초기 상태: [9]
1번 나누기: [9] → [6, 3]
2번 나누기: [6, 3] → [3, 3, 3]
결과: 최대 공 개수 = 3
예시 2: nums = [2,4,8,2], maxOperations = 4
초기 상태: [2, 4, 8, 2]
1번: [2, 4, 8, 2] → [2, 4, 4, 4, 2] (8을 4+4로 나누기)
2번: [2, 4, 4, 4, 2] → [2, 2, 2, 4, 4, 2] (4를 2+2로 나누기)
3번: [2, 2, 2, 4, 4, 2] → [2, 2, 2, 2, 2, 4, 2]
4번: [2, 2, 2, 2, 2, 4, 2] → [2, 2, 2, 2, 2, 2, 2, 2]
결과: 최대 공 개수 = 2
💡 해결 아이디어: 이진 탐색
이 문제는 "최소값의 최대값" 문제입니다. 이런 유형은 이진 탐색으로 해결할 수 있어요!
🎯 핵심 아이디어
- 답의 범위: 1 ~ 배열의 최댓값
- 이진 탐색: 특정 값을 최대 공 개수로 만들 수 있는지 확인
- 검증 함수: 주어진 연산 횟수로 목표를 달성할 수 있는지 계산
🔍 핵심 공식 이해하기
가방에 n개의 공이 있고, 최대 k개씩 나누고 싶다면 몇 번 나누어야 할까요?
필요한 나누기 횟수 = ceil(n / k) - 1
왜 이런 공식일까요?
- n개를 k개씩 나누면 ceil(n/k)개의 가방이 생깁니다
- 원래 1개 가방이 ceil(n/k)개가 되려면, ceil(n/k) - 1번 나누어야 합니다
📊 예시로 확인하기
- 9개 공을 최대 3개씩: ceil(9/3) - 1 = 3 - 1 = 2번 나누기
- 8개 공을 최대 2개씩: ceil(8/2) - 1 = 4 - 1 = 3번 나누기
🌟 마무리
시간 복잡도: O(n × log(max(nums)))
- 이진 탐색: O(log(max(nums)))
- 각 검증: O(n)
이 문제는 "최소값의 최대값"을 구하는 전형적인 이진 탐색 문제입니다. 핵심은 **"특정 값을 목표로 달성 가능한가?"**를 효율적으로 판단하는 것이죠.
비슷한 문제들을 더 풀어보면서 이진 탐색의 패턴을 익혀보세요! 💪
Code
Python
class Solution:
def minimumSize(self, nums: List[int], maxOperations: int) -> int:
# 주어진 balls를 최대 공 개수로 하여 목표를 달성할 수 있는지 확인하는 함수
def verify(balls : int) -> bool:
count = 0 # 필요한 연산 횟수
for num in nums:
# 예: 9개를 최대 3개씩 나누려면 -> 3개씩 3개 가방 = 2번 나누기 필요
# 공식: (전체 공 개수 / 목표 개수) + (나머지가 있으면 1) - 1
count += num // balls - 1
return count <= maxOperations
# 이진 탐색의 범위 설정
start = 1 # 최소 벌점 (가방에 최소 1개의 공은 있어야 함)
end = max(nums) # 최대 벌점 (현재 가장 큰 가방의 공 개수
result = 0
# 이진 탐색으로 최소 벌점 찾기
while start <= end:
mid = (start + end) // 2 # 중간값 (목표 최대 공 개수)
# mid를 최대 공 개수로 만들 수 있는지 확인
if verify(mid):
result = mid # 가능하면 결과 저장
end = mid -1 # 더 작은 값도 가능한지 확인 (왼쪽 탐색)
else:
start = mid + 1; # 불가능하면 더 큰 값 필요 (오른쪽 탐색)
return result
Java
class Solution {
int[] nums;
int maxOperations;
public int minimumSize(int[] nums, int maxOperations) {
this.nums = nums;
this.maxOperations = maxOperations;
// 이진 탐색의 범위 설정
int start = 1; // 최소 벌점 (가방에 최소 1개의 공은 있어야 함)
int end = Arrays.stream(nums).max().getAsInt(); // 최대 벌점 (현재 가장 큰 가방의 공 개수)
int result = 0;
// 이진 탐색으로 최소 벌점 찾기
while(start <= end){
int mid = (start + end) / 2; // 중간값 (목표 최대 공 개수)
// mid를 최대 공 개수로 만들 수 있는지 확인
if(verify(mid)){
result = mid; // 가능하면 결과 저장
end = mid - 1; // 더 작은 값도 가능한지 확인 (왼쪽 탐색)
}else{
start = mid + 1; // 불가능하면 더 큰 값 필요 (오른쪽 탐색)
}
}
return result;
}
// 주어진 balls를 최대 공 개수로 하여 목표를 달성할 수 있는지 확인하는 함수
public boolean verify(int balls){
long count = 0; // 필요한 연산 횟수
for(int num : this.nums){
if(num > balls){
// 현재 가방의 공이 목표보다 많으면 나누기 필요
// 예: 9개를 최대 3개씩 나누려면 -> 3개씩 3개 가방 = 2번 나누기 필요
// 공식: (전체 공 개수 / 목표 개수) + (나머지가 있으면 1) - 1
count += (num / balls) + (num % balls != 0 ? 1 : 0) - 1;
}
}
// 필요한 연산 횟수가 주어진 최대 연산 횟수 이하인지 확인
return count <= this.maxOperations;
}
}
Javascript
Math.max() 사용법
// ❌ 잘못된 사용법
let end = Math.max(nums); // NaN 반환
// ✅ 올바른 사용법
let end = Math.max(...nums); // 스프레드 연산자 사용
var minimumSize = function(nums, maxOperations) {
// 주어진 balls를 최대 공 개수로 하여 목표를 달성할 수 있는지 확인하는 함수
const verify = (balls) =>{
let count = 0; // 필요한 연산 횟수
nums.forEach(num=>{
// 예: 9개를 최대 3개씩 나누려면 -> 3개씩 3개 가방 = 2번 나누기 필요
// 공식: (전체 공 개수 / 목표 개수) + (나머지가 있으면 1) - 1
count += Math.ceil(num / balls) - 1;
});
// 필요한 연산 횟수가 주어진 최대 연산 횟수 이하인지 확인
return count <= maxOperations;
}
// 이진 탐색의 범위 설정
let start = 1; // 최소 벌점 (가방에 최소 1개의 공은 있어야 함)
let end = Math.max(...nums); // 최대 벌점 (현재 가장 큰 가방의 공 개수)
let result = 0;
// 이진 탐색으로 최소 벌점 찾기
while(start <= end){
const mid = Math.floor((start + end) / 2); // 중간값 (목표 최대 공 개수)
// mid를 최대 공 개수로 만들 수 있는지 확인
if(verify(mid)){
result = mid; // 가능하면 결과 저장
end = mid - 1; // 더 작은 값도 가능한지 확인 (왼쪽 탐색)
}else{
start = mid + 1; // 불가능하면 더 큰 값 필요 (오른쪽 탐색)
}
}
return result;
};
728x90
반응형
'알고리즘 > Leetcode' 카테고리의 다른 글
[Leetcode]2807. Insert Greatest Common Divisors in Linked List (0) | 2025.05.20 |
---|---|
[Leetcode]932. Beautiful Array (2) | 2025.05.14 |
[Leetcode]1306. Jump Game III (0) | 2025.05.07 |
[Leetcode] 3016. Minimum Number of Pushes to Type Word II (0) | 2025.05.06 |
[Leetcode] 881. Boats to Save People (1) | 2025.04.28 |