1679. Max Number of K-Sum Pairs
You are given an integer array nums and an integer k.
In one operation, you can pick two numbers from the array whose sum equals k and remove them from the array.
Return the maximum number of operations you can perform on the array.
Example 1:
Input: nums = [1,2,3,4], k = 5
Output: 2
Explanation: Starting with nums = [1,2,3,4]:
- Remove numbers 1 and 4, then nums = [2,3]
- Remove numbers 2 and 3, then nums = []
There are no more pairs that sum up to 5, hence a total of 2 operations.
Example 2:
Input: nums = [3,1,3,4,3], k = 6
Output: 1
Explanation: Starting with nums = [3,1,3,4,3]:
- Remove the first two 3's, then nums = [1,4,3]
There are no more pairs that sum up to 6, hence a total of 1 operation.
Constraints:
- 1 <= nums.length <= 10^5
- 1 <= nums[i] <= 10^9
- 1 <= k <= 10^9
Hint 1
The abstract problem asks to count the number of disjoint pairs with a given sum k.
Hint 2
For each possible value x, it can be paired up with k - x.
Hint 3
The number of such pairs equals to min(count(x), count(k-x)), unless that x = k / 2, where the number of such pairs will be floor(count(x) / 2).
문제 설명
이 문제는 두 숫자의 합이 k가 되는 최대 쌍(pair)을 찾는 문제입니다. 배열 nums에서 각 숫자는 한 번만 사용할 수 있습니다.
주어진 제약에 따라, 합이 k가 되는 두 숫자를 찾아 제거하는 작업을 가능한 한 많이 수행해야 합니다.
조건
- 입력:
- nums: 정수 배열 (1 ≤ nums.length ≤ 10^5).
- k: 목표 합 (1 ≤ k ≤ 10^9).
- 출력:
- 최대 쌍의 개수를 반환합니다.
- 제약:
- nums[i] 는 한 번 사용되면 다시 사용할 수 없습니다.
- 1 ≤ nums[i] ≤ 10^9.
문제 접근
이 문제는 쌍(pair) 찾기 문제로 다음과 같은 방법으로 해결할 수 있습니다.
아이디어 1: 브루트 포스
- 모든 가능한 두 숫자 쌍을 확인하여 합이 인지 확인.
- k가 되는 쌍을 찾으면 배열에서 두 숫자를 제거하고 작업을 반복.
- 문제점:
- 시간 복잡도 O(n^2)로, 입력 크기가 클 때 비효율적.
아이디어 2: 해시맵 활용
- 각 숫자에 대해:
- 해당 숫자를 사용하여 k−현재 숫자를 찾습니다.
- 이미 해시맵에 k−현재 숫자 가 존재하면 쌍을 형성할 수 있음.
- 장점:
- 시간 복잡도 O(n), 공간 복잡도 O(n) 로 효율적.
- 각 숫자의 빈도를 빠르게 추적할 수 있음.
from collections import Counter
class Solution:
def maxOperations(self, nums: List[int], k: int) -> int:
# Step 1: 숫자 빈도수를 저장하는 Counter 생성
freq = Counter(nums) # nums 배열의 각 숫자와 그 빈도수를 저장
# 예제: nums = [3, 1, 3, 4, 3], k = 6
# freq = {3: 3, 1: 1, 4: 1}
pairs = 0 # 최대 쌍의 개수를 저장할 변수 초기화
# Step 2: 해시맵의 모든 키를 순회하며 가능한 쌍을 찾음
for key in freq.keys():
a, b = key, k - key # 현재 숫자와 해당 숫자와 더해 k를 만드는 숫자
# 예제:
# 첫 번째 순회: a = 3, b = 6 - 3 = 3
# 두 번째 순회: a = 1, b = 6 - 1 = 5
# 세 번째 순회: a = 4, b = 6 - 4 = 2
# Case 1: a < b (서로 다른 숫자가 쌍을 이룸)
if a < b:
# 두 숫자의 빈도 중 작은 값만큼 쌍을 형성 가능
pairs += min(freq[a], freq[b])
# 예제: nums = [1, 2, 3, 4], k = 5
# a = 1, b = 4
# freq[1] = 1, freq[4] = 1
# pairs += min(1, 1) = 1
# Case 2: a == b (같은 숫자가 쌍을 이룸)
elif a == b:
# 같은 숫자끼리 쌍을 이루려면 빈도를 2로 나눈 몫만큼 쌍을 형성 가능
pairs += freq[a] // 2
# 예제: nums = [3, 3, 3, 4], k = 6
# a = 3, b = 6 - 3 = 3
# freq[3] = 3
# pairs += 3 // 2 = 1
# Step 3: 결과 반환
return pairs # 모든 가능한 쌍의 개수 반환
# 예제 시뮬레이션
# 입력: nums = [3, 1, 3, 4, 3], k = 6
# 1. freq = {3: 3, 1: 1, 4: 1}
# 2. 첫 번째 순회: a = 3, b = 3
# - a == b: freq[3] // 2 = 3 // 2 = 1
# - pairs = 1
# 3. 두 번째 순회: a = 1, b = 5
# - b는 freq에 없음 → 쌍 형성 불가
# 4. 세 번째 순회: a = 4, b = 2
# - b는 freq에 없음 → 쌍 형성 불가
# 출력: 1
아이디어 3: 투 포인터 접근법
- 배열을 정렬한 후, 양쪽 끝에서 시작하는 두 포인터를 사용.
- 두 포인터가 가리키는 숫자의 합이:
- k 보다 크면 오른쪽 포인터를 왼쪽으로 이동.
- k 보다 작으면 왼쪽 포인터를 오른쪽으로 이동.
- 장점:
- 시간 복잡도 전체 O(nlogn), 정렬 포함
- : 배열 정렬 비용.
- O(n): 투 포인터 순회.
- 추가 공간 사용 X -> 공간 복잡도 O(1).
- 시간 복잡도 전체 O(nlogn), 정렬 포함
class Solution:
def maxOperations(self, nums: List[int], k: int) -> int:
# Step 1: 배열을 정렬하여 두 포인터 기법을 적용 가능하게 만듦
nums.sort() # 정렬 후 작은 값부터 오른쪽으로, 큰 값은 왼쪽으로 줄어듦
left = 0 # 왼쪽 포인터, 배열의 시작
right = len(nums) - 1 # 오른쪽 포인터, 배열의 끝
operations = 0 # 최대 쌍의 개수를 저장하는 변수
# Step 2: 두 포인터를 활용하여 쌍 찾기
while left < right: # 두 포인터가 교차할 때까지 반복
total = nums[left] + nums[right] # 두 포인터가 가리키는 값의 합
if total == k: # 합이 k인 경우
operations += 1 # 쌍을 찾았으므로 연산 횟수 증가
left += 1 # 왼쪽 포인터 이동 (현재 값 사용 완료)
right -= 1 # 오른쪽 포인터 이동 (현재 값 사용 완료)
elif total < k: # 합이 k보다 작은 경우
# 왼쪽 포인터를 이동하여 더 큰 값을 포함시키려고 시도
left += 1
else: # total > k, 합이 k보다 큰 경우
# 오른쪽 포인터를 이동하여 더 작은 값을 포함시키려고 시도
right -= 1
# Step 3: 결과 반환
return operations
추천 풀이
- 입력 배열 크기가 작거나 중간 수준: 해시맵 풀이가 직관적이고 효율적.
- 배열 크기가 크고 추가 메모리를 최소화하려는 경우: 투 포인터 접근법이 적합.
시간 및 공간 복잡도 비교
방법 | 시간 복잡도 | 공간 복잡도 |
해시맵 | O(n) | O(n) |
투 포인터 | O(nlogn) | O(1) |
결론
- 빠르고 간결한 풀이: 해시맵을 활용한 방법.
- 추가 공간을 최소화하는 풀이: 투 포인터 접근법.
현재, 나는 투 포인터가 좀더 이해하기 쉽고, 이 솔루션이 속도 도 더 빠름!
'LeetCode > LeetCode75' 카테고리의 다른 글
[LeetCode 75] Medium - 2352. Equal Row and Column Pairs (0) | 2024.11.14 |
---|---|
[LeetCode 75] Easy - 643. Maximum Average Subarray I (0) | 2024.11.12 |
[LeetCode 75] Easy - 283. Move Zeroes ☆ (2) | 2024.11.12 |
[LeetCode 75] Medium - 443. String Compression ☆ (0) | 2024.11.12 |
[LeetCode 75] Medium - 334. Increasing Triplet Subsequence ☆ (3) | 2024.11.12 |