Two Sum
Bạn nhận được một mảng số nguyên nums và một số nguyên target. Chính xác hai phần tử của nums ở hai vị trí khác nhau có tổng bằng target. Trả về chỉ số của hai phần tử đó, với chỉ số nhỏ hơn đứng trước.
Hàm
- numsinteger-array
- mảng số nguyên cần tìm kiếm
- targetinteger
- tổng của hai mục phải đạt đến
- Trả vềinteger-array
- hai chỉ số, chỉ số nhỏ hơn đứng trước
Ràng buộc
2 ≤ nums.length ≤ 104-109 ≤ nums[i] ≤ 109-109 ≤ target ≤ 109- Chỉ có đúng một cặp vị trí khác nhau có tổng bằng
target.
Ví dụ
- Đầu vào
- nums = [3, 8, 12, 5]target = 17
- Đầu ra
- [2, 3]
- Giải thích
- 12 nằm ở chỉ số 2 và 5 nằm ở chỉ số 3, còn 12 + 5 = 17. Không có cặp nào khác có tổng bằng 17.
- Đầu vào
- nums = [6, 1, 4, 10]target = 7
- Đầu ra
- [0, 1]
- Giải thích
- 6 ở chỉ số 0 và 1 ở chỉ số 1 cộng lại bằng 7: cặp này có thể là hai phần tử đầu tiên.
- Đầu vào
- nums = [2, -6, 9, 4, 13]target = 3
- Đầu ra
- [1, 2]
- Giải thích
- -6 ở chỉ số 1 và 9 ở chỉ số 2 cộng lại bằng 3. Số âm ghép cặp giống như số dương.
Câu hỏi mở rộng
Nếu nums đã được sắp xếp, bạn có thể tìm cặp số trong thời gian O(n) với chỉ O(1) bộ nhớ bổ sung không?
Gợi ý
Mở từng gợi ý một. Mỗi gợi ý tiết lộ thêm một chút.
Dùng hai vòng lặp lồng nhau để thử mọi cặp là đúng, nhưng với 10.000 số, cách này cần khoảng 50 triệu lần kiểm tra. Bạn có thể tìm số cần ghép với từng số mà không cần duyệt lại danh sách không?
Khi bạn đứng tại giá trị
x, bạn đã biết giá trị nào sẽ hoàn thành cặp này: target trừx. Câu hỏi duy nhất là bạn đã đi qua giá trị đó trước đây chưa và giá trị đó nằm ở chỉ số nào.Duyệt danh sách một lần và lưu một bản đồ băm ánh xạ mỗi giá trị bạn đã đi qua với chỉ số của nó. Ở mỗi vị trí, hãy tìm giá trị còn thiếu để ghép cặp trước; nếu giá trị đó có trong bản đồ, bạn đã có cả hai chỉ số. Nếu không, hãy lưu giá trị hiện tại rồi tiếp tục. Tìm trước khi lưu sẽ ngăn một số ghép cặp với chính nó.
Lời giải
Kiểm tra từng cặp đều hiệu quả, nhưng lượng công việc tăng theo bình phương độ dài. Hãy đổi cách đặt câu hỏi: với số x đang xét, bạn đã biết số cần ghép với nó phải là target - x. Một bảng băm cho bạn biết trong thời gian hằng số liệu bạn đã gặp giá trị đó chưa, vì vậy chỉ cần duyệt một lượt.
Kiểm tra từng cặp
Đúng, nhưng không chạy xong với các test lớn nhất
Ý tưởng
Cố định chỉ số đầu tiên i rồi thử từng chỉ số j ở bên phải cho đến khi nums[i] + nums[j] bằng target. Bắt đầu j từ i + 1 giúp một vị trí không ghép với chính nó, thử mỗi cặp một lần và đặt chỉ số nhỏ hơn lên trước. Cặp bạn cần là một trong những cặp được thử, nên bạn sẽ tìm thấy nó.
n số tạo thành khoảng n²/2 cặp: n = 10^4 có nghĩa là gần 5 × 10^7 phép cộng.
Thuật toán
- Lặp
iqua mọi chỉ số củanums. - Với mỗi
i, lặpjtừi + 1đến chỉ số cuối cùng. - Nếu
nums[i] + nums[j]bằngtarget, trả về[i, j]. - Nếu cả hai vòng lặp kết thúc, trả về một mảng rỗng. Đề bài đảm bảo trường hợp này không bao giờ xảy ra.
def twoSum(nums, target):
n = len(nums)
for i in range(n):
# Only look at partners to the right, so each pair is checked once.
for j in range(i + 1, n):
if nums[i] + nums[j] == target:
return [i, j]
return []Một lượt duyệt với bảng băm
Ý tưởng
Với một nums[i] cố định, vòng lặp bên trong của cách vét cạn tìm một giá trị đã biết trước: target - nums[i]. Việc quét đó thực chất là một thao tác tra cứu, còn từ điển băm ánh xạ giá trị sang chỉ số có thể tra cứu trong thời gian hằng số trung bình.
Tra cứu trước, lưu sau. Khi đó, một số chỉ có thể ghép cặp với một số đứng trước nó, không bao giờ với chính nó. Vì số ghép cặp luôn đứng trước nên chỉ số của số đó cũng nhỏ hơn. Mỗi số được duyệt một lần, với các thao tác trên từ điển có thời gian hằng số, nên việc tìm kiếm có độ phức tạp O(n); đổi lại, cần một từ điển có tối đa n mục.
Thuật toán
- Tạo một ánh xạ rỗng từ giá trị đến chỉ số.
- Với mỗi chỉ số
i, tínhneed, giá trị cần để hoàn thành cặp:targettrừnums[i]. - Nếu
needcó trong ánh xạ, trả về chỉ số đã lưu của nó, theo sau lài. - Nếu không, lưu
nums[i]cùng chỉ sốirồi tiếp tục.
def twoSum(nums, target):
seen = {}
for i, num in enumerate(nums):
need = target - num
if need in seen:
return [seen[need], i]
seen[num] = i
return []
Lỗi thường gặp và trường hợp biên
- Lưu một số trước khi tìm đối số của nó. Với
[4, 2, 6]vàtarget = 8, số 4 tìm thấy chính nó và bạn trả về[0, 0]. - Trả về các chỉ số không đúng thứ tự. Khi tra cứu trước khi lưu, chỉ số trong bản đồ nhỏ hơn và đứng trước.
- Sắp xếp mảng để dùng hai con trỏ, rồi trả về vị trí trong bản sao đã sắp xếp. Giữ chỉ số ban đầu bên cạnh từng giá trị.
- Cho rằng các giá trị luôn dương hoặc khác nhau.
[0, 7, -3, 0]vớitarget = 0ghép đôi hai số 0:[0, 3]. - Trong Lua và R, trả về các chỉ số bắt đầu từ 1. Trừ 1 khỏi mỗi chỉ số.
Câu hỏi thường gặp3
Độ phức tạp thời gian của Two Sum là gì?
Lời giải bằng bảng băm chạy trong thời gian O(n), vì nó duyệt qua mỗi số một lần và mỗi lần tra cứu hoặc thêm phần tử mất thời gian hằng số trung bình. Lời giải này dùng thêm O(n) dung lượng cho bảng. Kiểm tra từng cặp mất thời gian O(n²) và dung lượng O(1).
Có thể giải bài toán Two Sum mà không cần thêm bộ nhớ không?
Không thể thực hiện trong thời gian tuyến tính trên một mảng chưa được sắp xếp. Với bộ nhớ bổ sung O(1), bạn phải kiểm tra từng cặp, việc này có độ phức tạp O(n²). Bản đồ băm đánh đổi bộ nhớ O(n) để lấy thời gian tuyến tính.
Cách Two Sum xử lý các số trùng lặp như thế nào?
Các giá trị trùng lặp vẫn hoạt động khi bạn tìm số cần ghép trước khi lưu số hiện tại. Với [7, 3, 7] và target = 14, số 7 đầu tiên được lưu ở chỉ số 0, còn số 7 thứ hai tìm thấy nó, cho kết quả [0, 2]. Một số 7 đơn lẻ không bao giờ ghép với chính nó: số đó chưa có trong map khi bạn tìm.
Bài tập tương tự
Các bài tập dùng cùng ý tưởng. Giải hai hoặc ba bài là bạn sẽ nhớ được mẫu này.
def twoSum(nums, target):
# Viết code ở đâyKiểm thử #1
Kiểm thử #2
Kiểm thử #3
Đối số
nums = [3, 8, 12, 5] target = 17
Đầu ra mong đợi
[2, 3]