68. Two Sum II - Input array is sorted

아현·2021년 5월 20일
0

Algorithm

목록 보기
69/400

리트코드


  • 정렬된 배열을 받아 덧셈하여 타겟을 만들 수 있는 배열의 두 숫자 인덱스를 리턴하라.

    • 주의: 이 문제에서 배열은 0(Zero-Based)이 아닌 1부터 시작하는 것으로 한다.




1. 투 포인터


class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        left, right = 0, len(numbers) - 1
        while not left == right:
            if numbers[left] + numbers[right] < target:
                left += 1
            elif numbers[left] + numbers[right] > target:
                right -= 1
            else:
                return left + 1, right + 1 #리턴 값  +1
  • 7번 '두 수의 합' 문제는 투 포인터로 풀 수 없다고 했으나, 그 문제의 다른 버전 격인 이 문제는 입력 배열이 정렬되어 있다는 가정이 추가됐다.

    • 따라서 이 문제는 투 포인터로도 잘 풀릴 것이다.

  • 7번 문제의 코드의 일부를 가져온다.

    • 이 문제의 경우 특별히 0이 아닌 1부터 시작한다고 했으니, 리턴하는 부분의 값에 각각 +1을 한다.

    • 문제에서 입력값에 해당하는 변수명이 기존 nums에서 numbers로 바뀌었으미 이부분만 수정해주면 문제없이 잘 풀릴 것 같다.

  • 투 포인터 풀이의 경우, O(n)에 풀이할 수 있다.



2. 이진 검색


class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        for k, v in enumerate(numbers):
            left, right = k + 1, len(numbers) - 1
            expected = target - v
            
            #이진 검색으로 나머지 값 판별
            while left <= right:
                mid = left + (right - left) // 2
                if numbers[mid] < expected:
                    left = mid + 1
                elif numbers[mid] > expected:
                    right = mid - 1
                else:
                    return k + 1, mid + 1
  • 현재 값을 기준으로 나머지 값이 맞는지 확인하는 형태의 이진 검색 풀이를 시도해볼 수 있을 것 같다.

  • 이 경우 이진 검색 logn을 n번 반복하므로 시간 복잡도는 O(nlogn)이 된다.

  • 이진 검색 풀이가 투 포인터에 비해 2배 가향 늦다.

    • 그렇다면 bisect모듈을 사용하면 속도를 더 높일 수 있을까?



3. bisect 모듈 + 슬라이싱


class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        for k, v in enumerate(numbers):
            left, right = k + 1, len(numbers) - 1
            expected = target - v
            
            i = bisect.bisect_left(numbers[k + 1:], expected)
            if i < len(numbers[k + 1:]) and numbers[i + k + 1] == expected:
                return k + 1, i + k + 2
                
                
  • leftright 변수도 필요 없고, 예상대로 코드는 엄청나게 깔끔해졌다.

    • 그런데 문제가 있다. 이 풀이 속도는 무려 2184ms에 다다른다.



4. bisect 모듈 + 슬라이싱 최소화



class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        for k, v in enumerate(numbers):
            left, right = k + 1, len(numbers) - 1
            expected = target - v
            
            i = bisect.bisect_left(numbers[k + 1:], expected)
            if i < len(numbers[k + 1:]) and numbers[i + k + 1] == expected:
                return k + 1, i + k + 2




  • 파이썬 슬라이싱을 무리하게 적용한 게 원인인 듯 하여 nums 변수에 한 번만 사용해 담아두는 형태로 개선을 시도했다.

  • 이렇게 하니 1136ms로 앞서 풀이에 비해 2배가량 빨라졌다.

    • 그래도 투포인터에 비하면 많이 느리다. 조금 더 개선이 필요하다.



5. bisect 모듈 + 슬라이싱 제거


class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        for k, v in enumerate(numbers):
            left, right = k + 1, len(numbers) - 1
            expected = target - v
            
            i = bisect.bisect_left(numbers[k + 1:], expected, k + 1)
            if i < len(numbers) and numbers[i] == expected:
                return k + 1, i + 1

  • bisect 모듈의 기능을 좀 더 활용하기로 한다.

    • 기본 파라미터 외에도 여러 추가 파라미터가 있다.

bisect.bisect_left(a, x, lo = 0, hi = len(a))
  • 이를 참고하여 왼쪽 범위를 제한하는 파라미터인 lo를 찾아냈고, 이 값을 지정하는 것으로 풀이를 진행했다.

    • 더 이상 슬라이싱을 사용할 필요가 없어졌다.
  • 이 경우 68ms로 , 투 포인터 풀이와 속도가 같아졌다.

    • 슬라이싱을 사용하지 않고도 bisect 모듈만을 이용해 이진 검색의 성능을 개선하는 데 비로소 성공했다.

    • 슬라이싱은 편리하고 빠른 모듈이지만, 이처럼 생각 없이 무분별하게 남용하다보면 속도 저하의 주범이 될 수 잇다.

      • 경우에 따라서는, 꼭 필요한 곳에만 적절히 사용해야 실행 속도를 좀 더 최적화할 수 있다.



➕ 파이썬의 슬라이싱은 매우 효율적이고 빠르다고 했는데 왜 이런 일이 발생할까?


그 이유는 슬라이싱은 매번 새롭게 객체를 생성해서 할당하게 되고, 엄청나게 큰 배열의 경우 슬라이싱으로 새로운 객체를 생성하는 데 상당한 시간이 들기 때문이다.

배열의 길이를 계산하는 데도 매번 길이를 계산ㄴ하는 비용이 들기 때문에, 여기서 상당한 시간이 소요된다.

profile
For the sake of someone who studies computer science

0개의 댓글

관련 채용 정보