Hai con trỏ

Cấu trúc dữ liệu và giải thuật

5.0 (1 đánh giá)
Tạo bởi Katsu Cập nhật lần cuối 3 năm trước 8.799 lượt xem 1 bình luận
Tác giả/Dịch giả: huulam3011 K9
Học nhanh

Danh sách bài học

Hai con trỏ

Dẫn nhập

Trong ngày hôm nay, chúng ta sẽ cùng nhau đi tìm hiểu về một kĩ thuật được sử dụng khá phổ biến trong lập trình giúp giảm độ phức tạp của bài toán đi rất nhiều. Hãy cùng tìm hiểu xem kỹ thuật đó là gì nhé!


Nội dung

Để có thể tiếp thu bài học này một cách tốt nhất, các bạn nên có những kiến thức cơ bản về:

Trong bài học ngày hôm nay, chúng ta sẽ cùng nhau tìm hiểu về:

  • Ví dụ về sử dụng hai con trỏ
  • Nhận biết sử dụng hai con trỏ

Bài toán đặt ra

Cho hai dãy số nguyên a b lần lượt gồm n và m phần tử được sắp xếp không giảm (n, m\leq 10^{5}; |a_{i}|, |b_{i}|\leq 10^{9}). Hãy ghép hai dãy trên thành một dãy mới c sao cho các phần tử trong dãy c được sắp xếp không giảm

Ví dụ:

Input Output

4

2 5 7 9

5

1 3 5 6 8

1 2 3 5 5 6 7 8 9


Lời giải ban đầu

Theo một suy nghĩ thông thường, ta sẽ đọc đồng thời cả hai dãy a và b vào cùng một mảng sau đó dùng hàm sort để thu được kết quả.

Từ suy nghĩ trên, ta có thể triển khai code như sau:

Tuy nhiên, độ phức tạp của chương trình trên sẽ là O((n+m)^{\ast} \log (n+m)) do ta cần sort một mảng có kích thước n+m. Vậy liệu có cách làm nào tối ưu hơn không?


Nhận xét

Hãy cùng phân tích tính chất của dãy c trong ví dụ trên một chút.

Ta có a=2, 5, 7, 9, b=[1,3, 5, 6, 8]

Ta thấy, do dãy c là dãy không giảm, các phần tử trong dãy c là của dãy a và b nên phần tử đầu tiên trong dãy c phải là phần tử nhỏ nhất trong dãy a và b.

Mặc khác, dãy a và b cũng được sắp xếp không giảm nên hai phần tử nhỏ nhất trong dãy a và b là a_{0} và b_{0}. Khi này, ta sẽ so sánh a_{0} và b_{0} rồi thêm phần tử nhỏ hơn vào dãy c.

Tiếp theo, ta sẽ xét phần tử nhỏ nhất trong dãy a và b chưa được thêm vào dãy c. Do b đã được đưa vào dãy c nên ta sẽ so sánh phần tử nhỏ kế tiếp trong dãy b là b_{1} với a_{0}.

Sau đó, ta lại sẽ xét phần tử nhỏ nhất trong dãy a và b chưa được thêm vào dãy c. Do a_{0} đã được đưa vào dãy c nên ta sẽ so sánh phần tử nhỏ kế tiếp trong dãy a là a_{1} với b_{1} và quá trình lặp lại đến khi tất cả các phần tử của hai dãy được đưa vào dãy c

Từ những gì mình vừa nêu ở trên, ta có thể rút ra một số nhận xét sau:

  • Phần tử tiếp theo được đưa vào dãy c luôn là phần tử nhỏ nhất chưa được chọn của dãy a và b
  • Khi phần tử a_{i} được chọn thì phần tử tiếp theo được xét để đưa vào dãy c là a_{i+1}
  • Khi phần tử b_{i} được chọn thì phần tử tiếp theo được xét để đưa vào dãy c là b_{i+1}
  • Hai giá trị ban đầu được xét là a_{0} và b_{0}

Vậy thì những nhận xét trên sẽ giúp ta giải quyết bài toán này như thế nào?


Lời giải cải tiến

Dựa vào các nhận xét ở trên, ta có thể đưa ra cách làm như sau:

  • Ta có một con trỏ i ở mảng a thể hiện cho phần tử nhỏ nhất trong dãy a chưa được chọn
  • Ta có một con trỏ j ở mảng b thể hiện cho phần tử nhỏ nhất trong dãy b chưa được chọn
  • Ta sẽ lặp lại tiến trình sau cho đến khi tất cả phần tử hai dãy a và b được đưa vào dãy c
    • Nếu như các phần tử của một trong hai dãy a và b đều được đưa vào dãy c (i=n hoặc j=m) thì ta đưa tất cả các phần tử của dãy còn lại vào
    • Nếu không:
      • Xét hai phần tử ở vị trí i trong dãy a và j trong dãy b
      • Chọn phần tử nhỏ hơn vào dãy c, nếu bằng nhau thì chọn bất kì
      • Tăng con trỏ của mảng có phần tử được đưa vào dãy c

Khi triển khai ý tưởng trên ra code ta có

Trong ví dụ trên ta sử dụng hai biến i và j như hai con trỏ trỏ đến hai phần tử nên kĩ thuật này được gọi là Hai con trỏ.

Vậy độ phức tạp của thuật toán trên là bao nhiêu? Ta thấy vòng lặp while lặp lại n+m lần, mỗi thao tác trong vòng lặp là O(1) nên độ phức tạp của chương trình sẽ chỉ là O(n+m).


Nhận biết sử dụng hai con trỏ

Trên thực tế, không có một hướng dẫn chính xác là khi nào ta nên sử dụng hai con trỏ hay bài toán dạng nào thì sẽ sử dụng hai con trỏ. Những gì mình sắp chia sẻ là từ những kinh nghiệm cá nhân của mình. Nếu các bạn có thêm bất cứ đóng góp gì cho phần này, hãy chia sẻ ở phần bình luận để mọi người cùng tham khảo.

Một bài toán có thể sử dụng hai con trỏ nếu như:

  • Ta có hai biến để thể hiện cho hai phần tử đang xét hoặc một đoạn đang xét
  • Sự tăng giảm của các biến là hoàn toàn độc lập với nhau
  • Chiều tăng hoặc giảm của hai biến phải là cố định, có nghĩa là nếu biến đó tăng thì sẽ tăng liên tục, nếu giảm thì phải giảm liên tục. Trên thực tế vẫn có một số thuật toán dùng hai con trỏ mà các biến có thể vừa tăng vừa giảm. Tuy nhiên, khi làm như vậy thì các bạn phải có ràng buộc về quãng tăng giảm của biến và đánh giá được thật chính xác về độ phức tạp thời gian.

Ví dụ mở rộng

Để các bạn có thể hiểu hơn về cách sử dụng hai con trỏ, mình sẽ bổ sung ví dụ sau

Cho một dãy số nguyên gồm n phần tử a_{i} (n\leq 10^{5},|a_{i}|\leq 10^{9}) được sắp xếp không giảm. Hãy in ra hai vị trí thuộc dãy a sao cho tổng của hai phần tử ở vị trí đó bằng số nguyên k cho trước. Nếu không có hai vị trí nào thỏa mãn in ra “-1 -1”.

Ví dụ:

Input Output

7 10

1 1 2 4 7 8 11

3 6

Giải thích: Phần tử ở vị trí thứ 3 là 2 và phần tử ở vị trí thứ 6 là 8, tổng của chúng là 10

Với những kiến thức về hai con trỏ mà mình đã nêu ở trên, hãy thử tạm ngưng tại đây rồi suy nghĩ để tự tìm cách giải cho bài toán này nhé!

Các bạn đã nghĩ ra lời giải chưa? Hãy cùng xem lời giải của mình sẽ là gì nhé!


Nhận xét

Ta thấy rằng a_{6}+a_{0}=12>k mà a_{6}>a_{5}>a_{4}>a_{3}>a_{2}>a_{1}>a_{0} do đó a_{6}+a_{5}>a_{6}+a_{4}>a_{6}+a_{3}>a_{6}+a_{2}>a_{6}+a_{1}>a_{6}+a_{0}>k. Có nghĩa là a_{6} cộng với bất cứ phần tử nào cũng lớn hơn k. Do vậy, a_{6} sẽ không bao giờ là một phần tử được chọn nên ta sẽ không xét a_{6} nữa mà sẽ xét a_{5}.

Lại có a_{0}+a_{5} = 9 < k = 10. Tương tự lập luận bên trên thì k>a_{0}+a_{5}>a_{0}+a_{4}>a_{0}+a_{3}>a_{0}+a_{2}>a_{0}+a_{1}. Có nghĩa là a_{0} cộng với bất cứ phần tử nào cũng nhỏ hơn k. Do vậy, a_{0} sẽ không bao giờ là một phần tử được chọn nên ta sẽ không xét a_{0} nữa mà sẽ xét a_{1}.

Từ những nhận xét mình vừa nêu, các bạn đã tìm được lời giải chưa?


Lời giải

Từ các nhận xét trên ta có lời giải sau:

  • Ta có hai con trỏ i và j, i ở đầu mảng và j ở cuối mảng
  • Ta tiến hành quá trình sau đến khi hai con trỏ gặp nhau, có nghĩa là đến khi i=j
  • Nếu tổng hai phần tử ở vị trí i và j là k thì in ra kết quả và kết thúc quá trình
  • Nếu tổng hai phần tử ở vị trí i và j nhỏ hơn k thì tăng i lên 1 đơn vị
  • Nếu tổng hai phần tử ở vị trí i và j lớn hơn k thì giảm j đi 1 đơn vị
  • Khi hai con trỏ gặp nhau có nghĩa là không có hai phần tử nào thỏa mãn và in ra  “-1 -1”

Code

Độ phức tạp của chương trình trên là O(n)do ta chỉ cần duyệt hết một lần dãy đã cho.


Kết luận

  • Qua bài này chúng ta đã nắm được về Hai con trỏ
  • Bài sau chúng ta sẽ tiếp tục tìm hiểu về Tìm kiếm max, min trên đoạn tịnh tiến
  • Cảm ơn các bạn đã theo dõi bài viết. Hãy để lại bình luận hoặc góp ý của mình để phát triển bài viết tốt hơn. Đừng quên “Luyện tập – Thử thách – Không ngại khó”.

Tải xuống

Tài liệu

Nhằm phục vụ mục đích học tập Offline của cộng đồng, Kteam hỗ trợ tính năng lưu trữ nội dung bài học Hai con trỏ dưới dạng file PDF trong link bên dưới.

Ngoài ra, bạn cũng có thể tìm thấy các tài liệu được đóng góp từ cộng đồng ở mục TÀI LIỆU trên thư viện Howkteam.com

Đừng quên likeshare để ủng hộ Kteam và tác giả nhé!


Thảo luận

Nếu bạn có bất kỳ khó khăn hay thắc mắc gì về khóa học, đừng ngần ngại đặt câu hỏi trong phần bên dưới hoặc trong mục HỎI & ĐÁP trên thư viện Howkteam.com để nhận được sự hỗ trợ từ cộng đồng.

Tác giả/Dịch giả

Mình là Nguyễn Hữu Lâm, một người có niềm đam mê rất lớn đối với lập trình. Hiện tại, mình đang là sinh viên Khoa học máy tính của Đại học Bách Khoa Hà Nội. Mong muốn của mình là có thể chia sẻ những kiến thức mà bản thân có cho mọi người, học hỏi, kết bạn với tất cả những người có cùng đam mê với mình.


K9

Nhà sáng lập Howkteam.com, KQuiz.vn & tác giả các khóa học C#, Auto, Unity3D, Python....

Với mong muốn mang đến kiến thức chất lượng, miễn phí cho mọi người, với tâm huyết phá bỏ rào cản kiến thức từ việc giáo dục thu phí. Tôi đã cùng đội ngũ Kteam đã lập nên trang website này để thế giới phẳng hơn.
Hãy cùng chúng tôi lan tỏa kiến thức đến cộng đồng! 

Khóa học

Cấu trúc dữ liệu và giải thuật

Bạn đã từng đau đầu với các cấu trúc stack, queue,.. hoặc cảm thấy cực kỳ khó khăn với các thuật toán sắp xếp, tìm kiếm được sử dụng trong lập trình. Đừng lo lắng! Trong khoá học này, chúng ta sẽ cùng nhau tìm hiểu một cách đơn giản nhất về cấu trúc dữ liệu và giải thuật, cũng như giúp bạn nắm rõ hơn về các kiến thức này.

Hãy cùng xem cấu trúc dữ liệu và giải thuật có gì đáng sợ không nhé!

Đánh giá

congthanh45 đã đánh giá 12 tháng trước

Bình luận

Để bình luận, bạn cần đăng nhập bằng tài khoản Howkteam.

Đăng nhập
quangan2k đã bình luận 6 tháng trước

hay quá

Không có video.