leet code - #2749 (medium)
You are given two integers num1 and num2.
In one operation, you can choose integer i in the range [0, 60] and subtract 2^i + num2 from num1.
Return the integer denoting the minimum number of operations needed to make num1 equal to 0.
If it is impossible to make num1 equal to 0, return -1.
Example 1:
Input: num1 = 3, num2 = -2Output: 3Explanation: We can make 3 equal to 0 with the following operations:
- We choose i = 2 and subtract 2^2 + (-2) from 3, 3 - (4 + (-2)) = 1.- We choose i = 2 and subtract 2^2 + (-2) from 1, 1 - (4 + (-2)) = -1.- We choose i = 0 and subtract 2^0 + (-2) from -1, (-1) - (1 + (-2)) = 0.
It can be proven, that 3 is the minimum number of operations that we need to perform.Example 2:
Input: num1 = 5, num2 = 7Output: -1Explanation: It can be proven, that it is impossible to make 5 equal to 0 with the given operation.Constraints:
1 <= num1 <= 10^9-10^9 <= num2 <= 10^9
Solution
class Solution {public: int makeTheIntegerZero(int num1, int num2) { // check if (num1 - k * num2) is possible w/ k terms of (power of 2) int k = 1; while (true) { // subtract num2 for k times (step k) long long num = num1 - (long long)num2 * k;
// if we choose i for only 0, at least k is needed (1+1+1+1.... = k) if (num < k) return -1;
// when num of set bits is <= k, we can make num w/ k terms of (power of 2) else if (bitCnt(num) <= k) return k;
// check for next step ++k; } }
// Brian Kernighan’s Algorithm int bitCnt(long long num) { int cnt = 0; while (num != 0) { ++cnt; num &= (num-1); } return cnt; }
// or use __builtin_popcountll(num) int bitCnt2(long long num) { return __builtin_popcountll(num); }};k 번 연산의 의미
연산을 k 번 수행한다는 것은 num1 에서 num2 를 k 번 빼고, 추가로 k 개의 2의 거듭제곱 ( 형태의 수 ) 을 빼는 것과 같음.
// convert this:num1_final = num1 - k * num2 - (2^i_1 + 2^i_2 + ... + 2^i_k)
// to this:num1 - k * num2 = 2^i_1 + 2^i_2 + ... + 2^i_k“x라는 숫자를 k개의 2의 거듭제곱의 합으로 표현할 수 있는가?” 가 문제의 핵심이 됨.
k 의 유효성 검증 조건
x 를 k 개의 2의 거듭제곱 합으로 나타낼 수 있는지 판별하기 위한 두 가지 조건이 있음:
k의 하한(Lower Bound):k >= popcnt(x)
x를 이진수로 표현하는 것 자체가 x를 2의 거듭제곱의 합으로 나타내는 가장 효율적인 방법임.
예를 들어 x = 13 이면 이진수로 1101이고, 이는 .
즉, 13을 표현하려면 최소 3개의 2의 거듭제곱이 필요하며, 따라서 k는 최소 popcnt(x)보다는 크거나 같아야 함.
k의 상한(Upper Bound):k <= x
k 개의 2의 거듭제곱으로 만들 수 있는 가장 작은 합은 가장 작은 2의 거듭제곱인 을 k 번 더하는 것임.
그 합은 k가 되며, 따라서 우리가 만들려는 숫자 x는 최소한 k보다는 크거나 같아야 함.
탐색 종료 조건
우리는 k 를 1부터 계속 증가시키며 탐색함. 이때 x = num1 - k * num2 의 변화를 관찰해 보면, k 가 1씩 증가할 때마다 x 는 num2 만큼 계속 감소함. 즉, k 는 계속 커지는데 x 는 계속 작아짐.
언젠가는 k > x가 되는 지점이 올 수 있으며, k 의 상한 조건(k <= x) 에 따라, 이 지점부터는 더 이상 유효한 k 를 찾을 수 없음. k 가 더 커지면 이 부등식은 계속 성립할 것이기 때문.
따라서, 탐색 중에 처음으로 k > x 가 되는 순간, 더 이상 해를 찾을 수 없다고 판단하고 즉시 -1을 반환하며 탐색을 종료할 수 있음.