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 Pointers) 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 Pointers) để 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
1function twoSumSorted(numbers: number[], target: number): [number, number] {2 let left = 03 let right = numbers.length - 14 while (left < right) {5 const sum = numbers[left] + numbers[right]6 if (sum === target) return [left + 1, right + 1]7 if (sum < target) left++ // cần tổng lớn hơn → dịch trái sang phải8 else right-- // cần tổng nhỏ hơn → dịch phải sang trái9 }10 return [-1, -1]11}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.