-
[LeetCode] 15. 3Sum알고리즘 문제 풀이 2021. 10. 29. 11:13
문제 출처: https://leetcode.com/problems/3sum/
3Sum - LeetCode
Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.
leetcode.com
문제
정수 배열 nums가 주어집니다. nums[i] + nums[j] + nums[k] == 0 이고, i != j, i !=k, 그리고 j != k 인 모든 [nums[i]m nums[j], nums[k]를 찾아라.
중복된 케이스는 답에 포함되어서는 안 된다.
예제
Input: nums = [-1,0,1,2,-1,-4] Output: [[-1,-1,2],[-1,0,1]]
풀이
전형적인 세 수의 합을 계산하는 알고리즘을 작성하는 문제입니다.
세 수의 합을 알기 위해서는 반드시 세 수를 찾아야만 합니다. 만약, 세 수가 , , 그리고 라고 한다면
인 케이스를 찾고자 하는 것이고, 이는 인 것을 찾는 것과 같습니다. 여기서 x를 고정된 값으로 생각한다면 두 수의 합을 찾는 문제가 됩니다.
var threeSum = funtion(nums) { const answer = []; if (nums.length < 3) { return answer; } for (let x = 0; x < nums.length-2; x++) { // [TODO] 두 수의 합 계산(y + z = -x 인 케이스 찾기) } return answer; }
두 수를 효과적으로 찾기 위해서 투-포인터 기법을 사용할 수 있습니다.
투-포인터 기법을 통해 합이 특정 값이 되는 두 수를 찾는 방식에 대해 간략히 설명하면 다음과 같습니다.
- 정렬된 배열(arr)을 가정합니다.
- 탐색할 범위의 가장 낮은 지점에 left 포인터를, 가장 높은 지점에 right 포인터를 위치시킵니다.
- arr[left] + arr[right]와 target을 비교하여 각 케이스에 대해 다음의 행동을 취합니다.
- arr[left] + arr[right] > target : right 위치를 1 감소시킨다. (right를 그대로 두게 되면 left가 변화해도 arr[left] + arr[right] > target 유지됨)
- arr[left] + arr[right] < target : left 위치를 1 증가시킨다. (left를 그대로 두게 되면 right가 변화해도 arr[left] + arr[right] < target 유지됨)
- arr[left] + arr[right] == target의 케이스를 찾거나 left < right 조건이 유지되는 한 3의 과정을 반복합니다.
위 코드에서 비워두었던 곳을 채워보도록 하겠습니다. (left == y, right == z)
var threeSum = funtion(nums) { const answer = []; if (nums.length < 3) { return answer; } // 투 포인터 기법을 사용하기 위해 정렬 nums.sort((a, b) => a - b); for (let x = 0; x < nums.length-2; x++) { let y = x + 1; let z = nums.length - 1; while (y < z) { const sumVal = nums[x] + nums[y] + nums[z]; if (sumVal > 0) { z -= 1; } else if (sumVal < 0) { y += 1; } else { answer.push([nums[x], nums[y], nums[z]]); break; } } } return answer; }
자, 위 코드의 문제점을 찾아볼까요?
- 고정된 x에 대해 한 가지의 케이스 밖에 찾지 못함
- 예를 들어, x == 3이라 하면, -3을 만드는 케이스는 y == -2, z == -1 혹은 y == -4, z == 1과 같이 여러 케이스가 존재할 수 있는데 위 코드는 이러한 케이스를 모두 찾지 않음
- 중복 케이스에 대한 고려가 없음
- 만약 nums 배열에 -2가 두 번 등장한다고 가정하면, x로 -2가 두 번 선정될 테고 이때 동일한 케이스가 결과에 추가됨
각 문제를 하나씩 해결해보도록 하겠습니다.
첫 번째 문제는 투-포인터 기법 안에서 break 구문 때문입니다. break 구문 대신에 다음 y 혹은 z로 이동하여 다음 케이스를 찾을 수 있도록 해야 합니다. 저는 y 값을 1 증가시키는 것으로 다음 값을 찾을 수 있도록 하였습니다.
var threeSum = funtion(nums) { const answer = []; if (nums.length < 3) { return answer; } nums.sort((a, b) => a - b); for (let x = 0; x < nums.length-2; x++) { let y = x + 1; let z = nums.length - 1; while (y < z) { const sumVal = nums[x] + nums[y] + nums[z]; if (sumVal > 0) { z -= 1; } else if (sumVal < 0) { y += 1; } else { answer.push([nums[x], nums[y], nums[z]]); // y 값 1 증가 y += 1; } } } return answer; }
두 번째 문제는 x와 y(혹은 z)에 이전과 동일한 값이 사용되지 않도록 하는 것으로 문제를 해결할 수 있습니다.
var threeSum = funtion(nums) { const answer = []; if (nums.length < 3) { return answer; } nums.sort((a, b) => a - b); for (let x = 0; x < nums.length-2; x++) { // x가 동일한 값으로 설정되는 것 방지 if (x !== 0 && nums[x] === nums[x-1]) { continue; } let y = x + 1; let z = nums.length - 1; while (y < z) { const sumVal = nums[x] + nums[y] + nums[z]; if (sumVal > 0) { z -= 1; } else if (sumVal < 0) { y += 1; } else { answer.push([nums[x], nums[y], nums[z]]); // y가 동일한 값으로 설정되는 것 방지 const prevY = nums[y] while (y < z && nums[y] == prevY) { y += 1; } } } } return answer; }
모든 문제가 해결되었습니다.
이제 코드의 시간 복잡도에 대해 생각해보겠습니다. 기본적으로 정렬과 투-포인터 기법은 다음과 같은 시간 복잡도를 갖습니다.
- 배열 정렬:
- 투-포인터:
여기서 투-포인터 알고리즘은 번 실행되기 때문에 의 시간 복잡도를 생성하게 됩니다. 최종적으로 만큼의 시간이 걸리지만 빅오 표기법에 의해 의 시간 복잡도를 갖는다고 말할 수 있습니다.
Github: 15.js
GitHub - opwe37/Algorithm-Study
Contribute to opwe37/Algorithm-Study development by creating an account on GitHub.
github.com
'알고리즘 문제 풀이' 카테고리의 다른 글
[LeetCode] 980. Unique Paths Ⅲ (0) 2021.11.02 [LeetCode] 130. Surrounded Regions (0) 2021.11.01 [LeetCode] 75. Sort Colors (0) 2021.10.27 [LeetCode] 451. Sort Characters By Frequency (0) 2021.10.22 [LeetCode] 380. Insert Delete GetRandom O(1) (0) 2021.10.21