알고리즘 - 두 수의 합

리트코드 1번 문제

덧셈을 하여 타겟을 만들 수 있는 배열의 두 숫자 인덱스를 리턴하라.Permalink

Input Output
[2,7,11,15], 9 [0,1]
[3,2,4], 6 [1,2]
[3,3], 6 [0,1]


Soultion1 (브루트 포스로 계산)Permalink

def twoSum(nums: List[int], target: int) -> List[int]:
    for i in range(len(nums)):
        for j in range(i+1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i,j]

가장 간단하게 풀 수 있지만 모든 요소를 모두 차례대로 비교 시간복잡도는 O(n²)



Solution2 (in을 이용한 탐색)Permalink

def twoSum(nums: List[int], target: int) -> List[int]:
    for i in enumerate(nums):
        complement = target - n

        if complement in nums[i + 1:]:
            return [nums.index(n), nums[i + 1:].index(complement) + (i + 1)]

시간복잡도는 solution1과 동일하지만 in 연산 쪽이 훨씬 가볍고 빠르다.
파이썬의 내부 함수로 구현된 in은 파이썬 레벨에서 매번 값을 비교하는 것에 비해 훨씬 더 빨리 실행된다.



Solution3 (첫 번째 수를 뺀 결과 키 조회)Permalink

def twoSum(nums: List[int], target: int) -> List[int]:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        nums_map = {}
        # 키와 값을 바꿔서 딕셔너리로 저장
        for i, num in enumerate(nums):
            nums_map[num] = i

        # 타겟에서 첫 번째 수를 뺀 경과를 키로 조회
        for i, num in enumerate(nums):
            if target - num in nums_map and i != nums_map[target - num]:
                return [i, nums_map[target - num]]

i != nums_map[target - num] 을 검사해주는 이유는 자기 자신의 인덱스를 두 번 더한 값일 경우를 제외하기 위해서이다.
[3, 2, 4] , 6 일 경우 [2, 4]가 나와야 하지만 이 검사를 해주지 않으면 [3, 3]이 나온다.



Solution4 (조회 구조 개선)Permalink

def twoSum(nums: List[int], target: int) -> List[int]:
    nums_map = {}
    for i, num in enumerate(nums):
        if target - num in nums_map:
            return [nums_map[target - num], i]
        nums_map[num] = i

딕셔너리 저장과 조회를 2개의 for문으로 처리 한 것을 하나의 for로 합쳤지만 두 번째 값을 찾기 위해 매번 비교해야 하기때문에 성능상의 큰 이점은 없다.


Solution5 (투 포인터 이용) -> 풀이 불가Permalink

인덱스 처음과 끝을 가리키고 합이 타겟보다 작으면 왼쪽 포인터를 오른쪽으로 이동시키고 합이 타겟보다 크면 오른쪽 포인터를 왼쪽으로 이동시킨다.


    def twoSum(self, nums: List[int], target: int) -> List[int]:
        left, right = 0, len(nums) - 1
        while not left == right:
            # 합이 타겟보다 작으면 왼쪽 포인터를 오른쪽으로
            if nums[left] + nums[right] < target:
                left += 1
            # 합이 타겟보다 크면 오른쪽 포인터를 왼쪽으로
            elif nums[left] + nums[right] > target:
                right -= 1
            else:
                return [left, right]

코드도 간결하고 보기 쉽다. 또한 시간 복잡도도 O(n)으로 불필요한 추가 계산도 없어 빠를 것으로 기대하지만 이 문제는 투 포인터로 풀 수 없다.
왜냐하면 입력이 정렬된 숫자가 아니고 sort를 통해 정렬한다고 하더라고 인덱스 위치가 꼬이기 때문이다.
인덱스 위치가 아니라 값을 찾아내는 문제라면 얼마든지 정렬하고 투 포인터로 풀이 할 수 있다.

댓글남기기