69. Sqrt(x)

문제

Given a non-negative integer x, return the square root of x rounded down to the nearest integer. The returned integer should be non-negative as well.

You must not use any built-in exponent function or operator.

For example, do not use pow(x, 0.5) in c++ or x ** 0.5 in python.

Example 1: Input: x = 4 Output: 2 Explanation: The square root of 4 is 2, so we return 2.

Example 2: Input: x = 8 Output: 2 Explanation: The square root of 8 is 2.82842…, and since we round it down to the nearest integer, 2 is returned.

Constraints:

0 <= x <= 2^31 - 1


문제 해석

정수 x를 입력하면 x 제곱근을 구하는 문제이다. 딱 나누어 떨어지면 바로 출력하면 되고 만약 Example 2 처럼 딱 떨어지지 않는다면 가장 가까운 정수를 내림해서 출력하면 된다.


생각

  • 먼가를 제곱해서 int x에 가장 가까운 것을 찾아야 한다.
  • 숫자들 중 최적의 해를 찾아야하는데 내림은 어떻게 고려할까?
  • 이진 탐색으로 좁혀나가서 거기서 low 값을 도출해낸다?

구현(self)

class Solution {
public:
    int mySqrt(int x) {
        int low = 0;
        int high = x;


        while(low <= high)
        {
            long long sqrt = low + (high- low) / 2;

            if(sqrt * sqrt == x)
            {
                return sqrt;
            }
            else if(sqrt * sqrt > x)
            {
                high = sqrt - 1;
            }
            else
            {
                low = sqrt + 1;
            }
        }
        return low - 1;
    }
};

코드 설명

  • binary search 를 통해 문제를 풀었다.
  • 여기서 주의할 점은 x 의 범위가 0~2^31 이기 때문에
    int sqrt = low + (high - low) / 2;
    
  • 이렇게 int 형으로 sqrt를 적게 된다면 오버플로우가 발생해버린다. 그렇기 때문에 long long 으로 적어줘야 한다.
  • 마지막 리턴을 할때 직접 계산해 본 결과 최종적으로 low값은 올림을 한 값을 가지게 된다. 그래서 return low -1;을 적어줬다.
  • 뭔가 이렇게 하는게 맞나 의구심이 들어서 정답도 공부했다.

구현(solution)

class Solution {
public:
    int mySqrt(int x) {
        // For special cases when x is 0 or 1, return x.
        if (x == 0 || x == 1)
            return x;
        
        // Initialize the search range for the square root.
        int start = 1;
        int end = x;
        int mid = -1;
        
        // Perform binary search to find the square root of x.
        while (start <= end) {
            // Calculate the middle point using "start + (end - start) / 2" to avoid integer overflow.
            mid = start + (end - start) / 2;
            
            // Convert mid to long to handle large values without overflow.
            long long square = static_cast<long long>(mid) * mid;
            
            // If the square of the middle value is greater than x, move the "end" to the left (mid - 1).
            if (square > x)
                end = mid - 1;
            else if (square == x)
                // If the square of the middle value is equal to x, we found the square root.
                return mid;
            else
                // If the square of the middle value is less than x, move the "start" to the right (mid + 1).
                start = mid + 1;
        }
        
        // The loop ends when "start" becomes greater than "end", and "end" is the integer value of the square root.
        // However, since we might have been using integer division in the calculations,
        // we round down the value of "end" to the nearest integer to get the correct square root.
        return static_cast<int>(std::round(end));
    }
};


코드 설명

  • 위 코드는 일단 x가 ‘0’ 과 ‘1’이 들어왔을 때를 따로 빼줫다(내가 짠 코드도 Test case에 0과 1을 넣어도 잘 작동한다)
  • 나머지는 비슷하게 binary search를 통해 찾았고 오버플로우를 방지하기 위해 square를 long long 으로 선언한 것을 볼 수 있다.
  • 앞 mid만 괄호 처리가 되어있는 이유는 불필요한 계산을 막기 위함이다. 앞에 mid만 long long 처리를 해주면 자동적으로 곱센 연산이 long long 으로 처리된다고한다.
return static_cast<int>(std::round(end));
  • round 는 가장 가까운 정수로 반올림하는 메소드로 double 을 반환한다.
  • 그렇기 때문에 int형으로 변환시켜줘야한다.

후기

이제 변수들의 타입과 조건으로 오버플로우가 발생할 수 있다라 생각을 하게되었다. 또한 이진 탐색은 정렬된 배열에서만 사용가능하다 이렇게 외웠었는데 최적의 해를 찾는 문제에서도 쓸 수 있다라는 걸 경험했다.