Đã thấy C vẫn cần tìm về phía đầu
Với dãy không giảm, gặp A[mid]==C chưa có nghĩa mid là vị trí đầu. Tìm tiếp nửa trái; nếu nửa trái không có C thì mid mới là vị trí đầu của miền đang xét:
```python
def firstInd(A, left, right, C):
if left > right:
return -1
mid = (left + right) // 2
if A[mid] == C:
earlier = firstInd(A, left, mid - 1, C)
return earlier if earlier >= 0 else mid
if A[mid] < C:
return firstInd(A, mid + 1, right, C)
return firstInd(A, left, mid - 1, C)
```
Mỗi mức chỉ có một lời gọi nhỏ, kể cả khi có nhiều phần tử bằng C; không quét lùi qua cả vùng trùng.
Tìm về phía cuối theo cách đối xứng
```python
def lastInd(A, left, right, C):
if left > right:
return -1
mid = (left + right) // 2
if A[mid] == C:
later = lastInd(A, mid + 1, right, C)
return later if later >= 0 else mid
if A[mid] < C:
return lastInd(A, mid + 1, right, C)
return lastInd(A, left, mid - 1, C)
```
Hai hàm trả −1 nếu không có C. Nếu có, gọi trên cả dãy để nhận first,last rồi tính last−first+1. Với A=[−2,1,1,1,4,7],C=1, hai biên là 1 và 3 nên số lượng 3.
Phải kiểm first≥0 trước công thức. Khi cả hai trả −1, biểu thức −1−(−1)+1 lại bằng 1, không phải số lần xuất hiện đúng. Trường hợp không có C cần trả 0 riêng.
Đối chiếu bản quét và bản hai biên
Chạy chế độ dem của tệp thực hành. Dòng “Đếm bằng hai biên” dùng firstInd/lastInd; cần khớp bản đếm tuần tự và bản tìm rồi quét.
```text
python3 g11-cs-thuc-hanh-tim-kiem.py dem "-2 1 1 1 4 7" 1
python3 g11-cs-thuc-hanh-tim-kiem.py dem "-2 1 1 1 4 7" 3
```
Lệnh đầu đếm 3, lệnh sau đếm 0. Thử toàn dãy bằng C: first=0,last=len(A)−1 và số lượng bằng len(A). Dãy rỗng có hai biên −1 và số lượng 0.
Theo mô hình thao tác cơ bản, hai lần tìm biên đều có bậc O(log n); cộng hai lần vẫn là O(log n). Điều này đến từ việc tiếp tục chia đôi để tìm từng biên, không từ cách tìm một vị trí rồi quét hết các phần tử trùng.
Bài luyện
Khi A[mid]==C trong firstInd, vì sao còn tìm ở nửa trái?
Chưa có căn cứ loại nửa trái khi đã thấy giá trị bằng C.
Cần vị trí đầu, không chỉ một vị trí bất kì.
Điều kiện đang xét đã cho A[mid]==C.
Hàm vẫn cần trả chỉ số đầu.
Lời giải
Nếu nửa trái còn C thì vị trí đầu phải nằm trước mid. Chỉ khi lời gọi trái trả −1, mid mới là vị trí đầu phù hợp của miền này.
Bài luyện
Với first=1,last=3 đều hợp lệ, số lần xuất hiện là bao nhiêu?
Có ba vị trí, không phải bốn.
1 chỉ là chỉ số đầu.
Vùng gồm cả chỉ số 1,2,3.
last−first chưa tính đủ cả hai đầu.
Lời giải
Vùng chỉ số gồm cả first và last, nên số lượng bằng last−first+1=3−1+1=3.
Bài luyện
firstInd và lastInd đều trả −1 vì không có C. Hàm đếm cần làm gì?
Công thức chỉ dùng khi đã có vùng kết quả.
Đọc phần tử cuối không xác nhận có C.
Không được đổi dữ liệu để tạo kết quả.
−1 là tín hiệu không có biên hợp lệ.
Lời giải
Không có C thì số lượng bằng 0. Nếu áp dụng công thức với hai tín hiệu −1 sẽ ra 1 sai; cần nhánh kiểm first<0 và trả 0.
Bài luyện
Vì sao bản tìm hai biên vẫn có bậc O(log n) khi toàn dãy bằng C?
Số mức tìm từng biên vẫn tăng theo log n.
Dãy toàn trùng có thể dài tùy ý.
Hai hàm đang dùng chỉ chọn một lời gọi nhỏ ở mỗi mức.
Muốn đếm bằng vùng cần cả biên đầu và cuối.
Lời giải
firstInd đi về phía đầu và lastInd đi về phía cuối bằng các miền nhỏ hơn khoảng một nửa. Mỗi hàm có O(log n) mức, nên tổng vẫn O(log n), kể cả khi nhiều giá trị bằng nhau.
Đang học