Hai Tổng Trên Mảng Sắp XếpTwo Sum II - Input Array Is Sorted
Hiểu bài
Cho bạn một mảng số nguyên numbers đã được sắp xếp tăng dần (1-indexed). Tìm 2 chỉ số i, j (i < j) sao cho numbers[i] + numbers[j] === target. Trả về [i, j] theo chỉ số 1-based.
Ví dụ:
numbers = [2, 7, 11, 15], target = 9
output = [1, 2] // numbers[1-1] + numbers[2-1] = 2 + 7 = 9
numbers = [2, 3, 4], target = 6
output = [1, 3] // 2 + 4 = 6Bài này khác Two Sum gốc ở chỗ mảng đã sắp xếp — đây là "tín hiệu" rõ ràng để dùng Hai con trỏ (Two-Pointer) thay vì Hash Map.
Xem cách cơ bản (chậm hơn)
O(n²) time, O(1) spacefunction twoSumSorted(numbers: number[], target: number): [number, number] {
for (let i = 0; i < numbers.length; i++) {
for (let j = i + 1; j < numbers.length; j++) {
if (numbers[i] + numbers[j] === target) return [i + 1, j + 1]
}
}
return [-1, -1] // không thể xảy ra theo đề bài
}Hai vòng lặp lồng nhau bỏ qua hoàn toàn thông tin "mảng đã sắp xếp".
Đây là dấu hiệu nên dùng Hai con trỏ (Two-Pointer) để tận dụng việc mảng đã sắp xếp.
Lời giải tối ưu
O(n) time, O(1) spacefunction twoSumSorted(numbers: number[], target: number): [number, number] {
let left = 0
let right = numbers.length - 1
while (left < right) {
const sum = numbers[left] + numbers[right]
if (sum === target) return [left + 1, right + 1]
if (sum < target) left++ // cần tổng lớn hơn → dịch trái sang phải
else right-- // cần tổng nhỏ hơn → dịch phải sang trái
}
return [-1, -1]
}Đây là cách tối ưu khi mảng đã sắp xếp: một con trỏ ở đầu và một ở cuối: tổng quá nhỏ → dịch con trỏ trái lên, tổng quá lớn → dịch con trỏ phải xuống.
Mỗi bước loại bỏ ít nhất 1 phần tử, tổng cộng O(n) bước.
Chạy thử từng bước
- 1
left=0, right=3. sum=numbers[0]+numbers[3]=2+15=17 > target=9 → right--.
left:0right:3sum:17action:"right--" - 2
left=0, right=2. sum=2+11=13 > 9 → right--.
left:0right:2sum:13action:"right--" - 3
left=0, right=1. sum=2+7=9 === target → return [1, 2].
left:0right:1sum:9result:[1,2]
Trường hợp biên phải nhớ
Mảng 2 phần tử — left=0, right=1, chỉ 1 lần kiểm tra, đủ xử lý đúng.
Phần tử âm: numbers=[-3, -1, 0, 2, 3], target=0 → Hai con trỏ vẫn chạy đúng.
Cặp đáp án nằm sát nhau: numbers=[2, 3, 3, 5], target=6 → hai con trỏ thu hẹp dần, gặp cặp 3+3 ở giữa và trả về chỉ số của chúng.
Đề bài đảm bảo luôn có lời giải — nhưng code vẫn nên return giá trị sentinel để không lỗi TypeScript.