[LeetCode] 3Sum
3Sum
Category: Array
Deadline: 1st week (12/31 - 1/7)
Difficulty: Medium
Github: https://github.com/iamseokhyun/2022-algorithm-study/tree/master/array/3Sum/
LeetCode link: https://leetcode.com/problems/3sum/
Problem
Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.
Notice that the solution set must not contain duplicate triplets.
Example 1:
Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]
Example 2:
Input: nums = []
Output: []
Example 3:
Input: nums = [0]
Output: []
Constraints:
- 0 <= nums.length <= 3000
- -10^5 <= nums[i] <= 10^5
Solution
public class Solution
{
public IList<IList<int>> ThreeSum(int[] nums)
{
IList<IList<int>> ans = new List<IList<int>>();
nums = Divide(nums);
for (int i = 0; i < nums.Length - 2; i++)
{
int key = -1 * nums[i];
if (i != 0 && nums[i] == nums[i - 1])
{
continue;
}
int l = i + 1;
int r = nums.Length - 1;
while (r > l && l < nums.Length && r > i + 1)
{
if (l > i + 1 && l < nums.Length && nums[l] == nums[l - 1])
{
l++;
continue;
}
if (r < nums.Length - 1 && r > i + 1 && nums[r] == nums[r + 1])
{
r--;
continue;
}
if (nums[l] + nums[r] == key)
{
ans.Add(new int[] { nums[i], nums[l], nums[r] });
l++;
r--;
continue;
}
else if (nums[l] + nums[r] > key)
{
r--;
continue;
}
else
{
l++;
continue;
}
}
}
return ans;
}
public int[] Divide(int[] nums)
{
if (nums.Length < 2)
{
return nums;
}
int pivot = (int)(nums.Length / 2);
int[] leftarray = nums[0..pivot];
int[] rightarray = nums[pivot..(nums.Length)];
leftarray = Divide(leftarray);
rightarray = Divide(rightarray);
return Merge(leftarray, rightarray);
}
public int[] Merge(int[] leftarray, int[] rightarray)
{
int l = leftarray.Length - 1;
int r = rightarray.Length - 1;
int[] temp = new int[leftarray.Length + rightarray.Length];
while (l >= 0 && r >= 0)
{
if (leftarray[l] > rightarray[r])
{
temp[l + r + 1] = leftarray[l];
l--;
}
else
{
temp[l + r + 1] = rightarray[r];
r--;
}
}
while (l >= 0)
{
temp[l] = leftarray[l];
l--;
}
while (r >= 0)
{
temp[r] = rightarray[r];
r--;
}
return temp;
}
}
Comment
- 먼저 정렬을 할것이다 (수열을). 오름차순으로 정렬하면 좋은점 : 왼쪽의 어떠한 수를 pivot으로 잡았을 때 pivot 오른쪽 부분에서 세 숫자의 합이 0이 되도록 하는 순서쌍을 구하기가 쉽다. Why? 2개의 pointer를 이용한 search를 하면 누락되는 경우 없이 구할 수 있으므로.
- 정렬을 해준다. (Merge Sort, Merge와 Divide는 두고두고 쓰는 것 같다. 코드길이를 줄여봐야곘다) 오름차순 정렬, 즉 작은 수가 작은 index를 부여받도록 정렬한다.
- 먼저 특정 key값에 대해서 고정을 해보자. 물론 이 key 값은 0~nums.Length-3까지 적용될 것이다. 아무리 오른쪽에 편향되어있다 해도 3개의 수열의 합이니 2개의 공간은 마련해야하므로 key 값은 nums[i]에 -1을 곱한 값으로 하자. 셋 다 더해서 0 되는거보다 둘이 더해서 key값이 되는게 계산이 편하니까.)
- 먼저 key 값은 숫자가 중복될 경우에도 의미가 없다. (중복이 되는 경우는 오른쪽 순서쌍에서 다룰 것이므로 여기서는 소거해준다. ) -> Line10, i != 0을 애초에 만족시켜야지만 뒤의 조건문을 계산하므로 i = -1이 되어 에러가 날 가능성이 없다.
- left pointer와 right pointer를 각각 i+1, nums.Length-1로 지정한다.
- right pointer가 left pointer와 닿거나 한쪽에 다른쪽 끝에 도달하기 전까지 while문을 돌린다.
- left 혹은 right 포인터에 걸린 값과 그 이전(혹은 이후) 포인터에 걸린 값이 같을 경우 continue 한다. (l++ or r–를 하고,) 이걸 하지 않으면 같은 순서쌍이 중복으로 포함되어 문제 조건에 알맞지 않다.
- left의 pointer 값과 right pointer 값 더해 key값이 나오면, 새로운 배열을 추가하고 l++, r–를 시전, continue한다.
- nums[left] + nums[right] 값이 key값보다 더 크다면, r을 한칸 왼쪽으로 옮기고 continue한다.
- 세 수의 합이 음수라면, l을 한칸 오른쪽으로 옮기고 continue한다.
- a에 대한 부가 설명 : -3 -1 -1 0 4 라는 수열에서 key가 -3이라 생각해보자. left pointer가 -1을 2번 계산하게 되면, {-3, -1(nums[1]), 4} 와 {-3, -1(nums[2]), 4}라는 2개의 동일한 수열이 생길것이다.
- -1, -1, 2와 같이 음수 2개가 합쳐지는 경우는 key가 0th index의 값 -1에 놓여있고, -1과 2가 각각 left, right pointer의 수색 범위에 들어가므로 한번 포함.
- 2, 1, 1과 같은 경우는 key가 -2일 때 1, 1이 각각 left, right이므로 한번 계산된다.
- 우리가 주의해야 할 상황은 2번째 언급된 상황으로, {-2, -1, 0, 1, 1, 2, 3, 4} 와 같은 상황에서 -2,1,1이 key, left, right가 되어 정확히 한번 겹쳐야지, left나 right에서 중복되면서 여러번 계산되는 것을 피해야 한다.
- 특히, 맨 앞의 a 부분과 3부분의 콜라보레이션으로 특정 값이 3번 이상 등장하는 경우도 모두 중복이 제거된다. (사실 특정 값이 3번 등장해서 그 합이 0인 경우는 당연히 (0,0,0)밖에 없기도 하다)
댓글을 불러오는 중입니다.