Giải thuật cho người mới: tiếp cận bài toán từng bước

Giải thuật cho người mới không nằm ở việc thuộc lời giải, mà ở cách suy nghĩ trước khi viết code. Cùng chúng tôi đi qua từng bước để học có nền tảng.

11 phút

Bạn đã bao giờ đọc xong một lời giải, thấy mình hiểu rồi, nhưng chỉ cần đổi đề một chút là đứng hình? Đội ngũ chúng tôi gặp chuyện này rất thường xuyên khi kèm các bạn mới bắt đầu. Giải thuật cho người mới thực ra không nằm ở việc ghi nhớ đáp án. Nó nằm ở cách bạn nghĩ trước khi đặt tay lên bàn phím. Bài viết này sẽ đi cùng bạn qua từng bước, để việc học không còn là cảm giác chạy theo lời giải của người khác.

Giải thuật là gì? Hiểu đơn giản trước khi đi sâu

Nói ngắn gọn, giải thuật là một chuỗi bước rõ ràng để đi từ dữ liệu đầu vào đến kết quả mong muốn. Bạn có thể hình dung nó giống một công thức nấu ăn. Có nguyên liệu, có bước sơ chế và xào nấu, rồi cuối cùng là món ăn hoàn chỉnh. Thiếu một bước thôi, món ăn đã khác đi rất nhiều.

Vì sao chúng tôi luôn khuyên người mới nắm tư duy giải thuật trước khi học cú pháp chi tiết? Lý do khá thực tế. Cú pháp thay đổi theo từng ngôn ngữ, còn cách chia nhỏ một bài toán thì dùng được ở mọi nơi. Khi đã quen tách vấn đề thành các bước nhỏ, bạn sẽ thấy việc viết code bằng Java, JavaScript hay C chỉ còn là diễn đạt lại một ý tưởng đã có sẵn trong đầu.

Cũng cần phân biệt giải thuật với đoạn code cụ thể. Đoạn code là cách bạn trình bày giải thuật bằng một ngôn ngữ nhất định. Cùng một ý tưởng có thể được viết thành nhiều đoạn code khác nhau, và mỗi cách có thể khác nhau về độ rõ ràng lẫn hiệu năng. Khi nhận ra điều này, bạn sẽ bớt lo cảm giác “mình viết khác bạn bè nên chắc là sai”.

Vì sao học thuộc lời giải dễ dẫn đến bế tắc?

Có thể bạn cũng từng làm đúng việc này. Tìm lời giải bài sắp xếp mảng, chép lại, chạy thử thấy kết quả đúng, rồi tự nhủ là mình đã hiểu. Nhưng khi đề bài đổi sang yêu cầu sắp xếp giảm dần, hoặc theo một tiêu chí khác, bạn lại không biết bắt đầu từ đâu.

Nguyên nhân khá đơn giản. Bạn đã ghi nhớ hình dáng của đáp án mà chưa nắm lý do đáp án lại có hình dáng đó. Trí nhớ chỉ giúp ích khi đề bài không đổi. Chỉ cần thay một điều kiện nhỏ là mọi thứ thuộc lòng trở nên vô dụng. Có một bạn học viên từng thuộc nằm lòng lời giải tìm kiếm nhị phân, nhưng khi được hỏi vì sao phải chia đôi khoảng tìm kiếm thì không trả lời được.

Có vài dấu hiệu cho thấy bạn đang học vẹt. Bạn đọc lời giải nhiều hơn là tự tay viết. Bạn không giải thích được vì sao vòng lặp phải dừng ở một vị trí nhất định. Và bạn thấy hoang mang khi gặp một bài chưa từng xuất hiện trước đó. Nếu nhận ra mình ở đây, đừng quá lo. Đó là điểm xuất phát rất bình thường của hầu hết người học.

Bốn bước tiếp cận một bài toán

Đây là quy trình đội ngũ chúng tôi vẫn dùng khi làm việc và khi hướng dẫn người mới. Bạn không cần thuộc lòng ngay, chỉ cần làm đúng thứ tự cho đến khi nó thành phản xạ tự nhiên.

  • Đọc đề, xác định đầu vào, đầu ra và các ràng buộc.
  • Thử với một ví dụ nhỏ, tính bằng tay trước khi viết bất kỳ dòng code nào.
  • Chia bài toán thành các bước con và ghi ra giấy.
  • Kiểm tra lại bằng các trường hợp biên như mảng rỗng, một phần tử, số âm.

Bước đầu tiên nghe đơn giản nhưng lại hay bị bỏ qua nhất. Đầu vào có thể là một danh sách rỗng không? Số có thể âm không? Nếu chưa trả lời được những câu này, bạn đang viết code cho một bài toán mà chính mình còn chưa hiểu rõ. Đã không ít lần đội ngũ phải quay lại sửa lời giải chỉ vì quên xét đầu vào rỗng.

Bước thử bằng tay giúp bạn nhìn thấy quy luật trước khi cần đến máy tính. Hãy lấy một mảng năm phần tử, tự đi qua từng phần tử và ghi lại điều bạn đang nghĩ ở mỗi bước. Quá trình này nghe có vẻ chậm, nhưng phần lớn lời giải hay mà chúng tôi từng thấy đều bắt đầu từ những tờ giấy nháp như vậy.

Ví dụ thực tế: tìm số lớn nhất trong một danh sách

Hãy xem một bài toán đơn giản để thấy bốn bước trên hoạt động ra sao. Đề bài: cho một danh sách số, tìm số lớn nhất. Dữ liệu mẫu là [3, 8, 2, 9, 5].

Bạn bắt đầu bằng cách giả định phần tử đầu tiên là lớn nhất tạm thời, tức số 3. Sau đó đi lần lượt qua các phần tử còn lại. Gặp 8, lớn hơn 3, nên cập nhật lớn nhất tạm thời thành 8. Gặp 2, bỏ qua. Gặp 9, cập nhật thành 9. Phần tử cuối là 5 không làm thay đổi kết quả. Vậy đáp án là 9.

Vì sao duyệt tuần tự là điểm khởi đầu hợp lý? Vì nó đơn giản, dễ kiểm tra và đảm bảo không bỏ sót phần tử nào. Với người mới, một lời giải đúng và dễ hiểu luôn quan trọng hơn lời giải ngắn gọn nhưng khó đọc. Bạn có thể tối ưu sau, khi đã nắm chắc ý tưởng gốc.

Điều đáng giá hơn nằm ở chỗ đây có thể trở thành một mẫu tư duy dùng lại nhiều lần. Ý tưởng giữ một giá trị tạm thời rồi cập nhật khi gặp giá trị tốt hơn xuất hiện trong rất nhiều bài toán khác, từ tìm giá trị nhỏ nhất, đếm tần suất cho đến tìm điểm cao nhất trong một bảng dữ liệu. Khi nhận ra mẫu này, nhiều bài toán mới sẽ bớt đáng sợ hơn hẳn.

Những nhóm giải thuật nền tảng nên nắm chắc

Bạn không cần học hết mọi thuật toán ngay từ đầu. Tuy vậy, có một số nhóm nền tảng nên được nắm vững trước, vì chúng xuất hiện rất thường xuyên trong thực tế lẫn các bài kiểm tra.

Nhóm đầu tiên là duyệt và tìm kiếm. Tìm kiếm tuyến tính đi từ đầu đến cuối cho đến khi gặp giá trị cần tìm. Tìm kiếm nhị phân nhanh hơn nhiều, nhưng đổi lại dữ liệu phải được sắp xếp sẵn. Chính vì vậy, sắp xếp thường là bước tiếp theo sau khi bạn đã làm quen với tìm kiếm.

Nhóm thứ hai là sắp xếp cơ bản, gồm sắp xếp chọn, sắp xếp chèn và sắp xếp nổi bọt. Ba thuật toán này khá chậm khi dữ liệu lớn, nhưng rất đáng học. Chúng giúp bạn hình dung rõ cách một danh sách được biến đổi từng bước, điều sẽ hữu ích khi đọc các thuật toán phức tạp hơn sau này.

Nhóm thứ ba là đệ quy và chia để trị ở mức nhập môn. Đệ quy xảy ra khi một hàm tự gọi lại chính nó với bài toán nhỏ hơn. Người mới thường thấy khái niệm này khó tiêu, nên chúng tôi khuyên bắt đầu bằng ví dụ đơn giản như tính giai thừa, rồi mới tiến tới bài phức tạp hơn. Bạn có thể tham khảo thêm vòng lặp for trong C để hiểu cách máy tính lặp lại một công việc trước khi chuyển sang đệ quy.

Vì sao không nên học hết một lúc? Mỗi thuật toán cần thời gian để thật sự thấm. Học quá nhiều cùng lúc thường chỉ dẫn đến việc nhớ được tên mà không biết áp dụng vào đâu.

So sánh độ phức tạp của các thuật toán cơ bản

Độ phức tạp là cách đo xem thời gian chạy của thuật toán tăng lên bao nhiêu khi dữ liệu đầu vào lớn dần. Nói đơn giản, đây là thước đo giúp bạn biết một lời giải có chậm đi đáng kể khi dữ liệu phình ra hay không.

Hãy hình dung thế này. Kiểm tra từng cuốn trên một kệ mười cuốn thì nhanh chóng. Nhưng nếu kệ có mười nghìn cuốn và bạn phải so sánh mỗi cuốn với mọi cuốn còn lại, thời gian sẽ bùng nổ. Đó chính là sự khác biệt giữa các ký hiệu O(n), O(n log n) và O(n²).

Với O(n), thuật toán chỉ cần đi qua mỗi phần tử một lần, như bài tìm số lớn nhất ở trên. Với O(n log n), thuật toán chia dữ liệu thành các phần nhỏ dần rồi xử lý từng phần, điển hình là sắp xếp trộn và sắp xếp nhanh. Còn O(n²) xảy ra khi thuật toán so sánh mọi cặp phần tử, như sắp xếp chọn hay nổi bọt, và đây là loại chậm nhất khi dữ liệu lớn.

Thử tưởng tượng với mười nghìn phần tử. Một lời giải O(n) cần khoảng mười nghìn bước. Lời giải O(n log n) cần khoảng một trăm ba mươi nghìn bước. Trong khi O(n²) lên tới khoảng một trăm triệu bước. Sự chênh lệch này thể hiện rõ trong thực tế: một bên chạy gần như tức thì, bên kia có thể khiến người dùng phải chờ vài giây đến vài chục giây tùy cấu hình máy.

Vậy khi nào cần để tâm đến điều này? Nếu dữ liệu chỉ vài chục phần tử, khác biệt gần như không đáng kể và bạn nên ưu tiên cách dễ hiểu nhất. Nhưng khi làm việc với dữ liệu lớn, việc chọn đúng thuật toán có thể quyết định chương trình có dùng được hay không.

Lộ trình luyện giải thuật ba tháng cho người vừa học vừa đi làm

Nếu bạn vừa đi làm vừa học, thời gian luyện tập thường rất hạn chế. Vì vậy, đều đặn quan trọng hơn học dồn. Chúng tôi đã thấy nhiều bạn tiến bộ rõ rệt chỉ nhờ duy trì vài buổi ngắn mỗi tuần.

Tháng đầu, bạn làm quen với tư duy giải thuật và các bài duyệt mảng. Mục tiêu là tự tin viết lời giải cho bài tìm kiếm, đếm và tính tổng mà không phải nhìn đáp án. Nếu muốn nền tảng vững hơn về cách tổ chức dữ liệu, bạn có thể đọc thêm về collections trong Java song song với giai đoạn này.

Sang tháng thứ hai, bạn chuyển sang tìm kiếm nhị phân, sắp xếp cơ bản và đệ quy đơn giản. Đây thường là giai đoạn dễ nản nhất. Nếu gặp khó khăn, đừng ngại quay lại các ví dụ trước đó, vì nhiều khái niệm chỉ thật sự rõ sau khi bạn thực hành thêm.

Tháng thứ ba, bạn luyện các bài tổng hợp và tự viết lời giải từ đầu. Nếu mắc kẹt quá lâu, hãy xem gợi ý, rồi đóng lại và viết lại toàn bộ. Việc tự viết lại sau khi hiểu ý tưởng giúp kiến thức nhớ lâu hơn nhiều so với chỉ đọc.

Về thời lượng, chúng tôi gợi ý khoảng năm đến bảy buổi mỗi tuần, mỗi buổi chừng bốn mươi lăm phút. Một buổi ngắn nhưng đều giúp bạn giữ mạch suy nghĩ, còn học dồn cuối tuần thường khiến kiến thức bị đứt quãng.

Chọn nơi học và tài liệu phù hợp với người mới

Tiêu chí đầu tiên khi chọn khóa học hay tài liệu là có bài tập theo từng bước, không chỉ có phần lý thuyết dài dằng dặc. Bạn cần những bài để tự làm, có gợi ý tăng dần khi bí, và có lời giải để đối chiếu sau khi đã thử hết sức.

Một lợi thế lớn khác là có người chấm và nhận xét lời giải của bạn. Khi nhận được phản hồi cụ thể về chỗ nào chạy được nhưng chưa tối ưu, chỗ nào thiếu trường hợp biên, bạn sẽ tiến bộ nhanh hơn nhiều so với tự mày mò. Nếu đang cân nhắc lộ trình tổng thể, hãy tham khảo lộ trình học lập trình cho người mới để biết giải thuật nằm ở đâu trong bức tranh chung.

Cuối cùng, hãy tự hỏi mình đang học để làm gì. Nếu mục tiêu là đi làm, nên ưu tiên các bài toán gần với công việc thực tế. Nếu chỉ muốn hiểu sâu hơn, hãy kiên nhẫn với từng nhóm thuật toán. Dù chọn hướng nào, điều quan trọng nhất vẫn là luyện đều và tự tay viết mỗi ngày.

Chuyên mục