6 điểm bởi GN⁺ 2025-09-13 | 2 bình luận | Chia sẻ qua WhatsApp
  • Ngay cả các bài LeetCode khó cũng có thể được giải dễ hơn nhiều nếu dùng constraint solver
  • Thay vì quy hoạch động phức tạp hay tự viết thuật toán, có thể dùng constraint solver như MiniZinc để giải các bài toán tối ưu toán học một cách đơn giản
  • Các ngôn ngữ lập trình truyền thống khó biểu đạt những bài toán tối ưu toán học kiểu này, vì vậy cách tiếp cận dựa trên ràng buộc có lợi thế rõ rệt
  • Ngay cả khi xuất hiện trường hợp ngoại lệ hoặc ràng buộc bổ sung, việc chỉnh sửa mô hình trong constraint solver cũng là tối thiểu
  • Độ phức tạp thời gian chạy có thể chậm hơn thuật toán tối ưu tự viết, nhưng xét về tính linh hoạt và năng suất phát triển thì có rất nhiều ưu điểm

Các bài LeetCode giải bằng constraint solver

Chọn đúng công cụ

  • Tác giả đã gặp bài toán “coin change” trong buổi phỏng vấn đầu tiên sau khi tốt nghiệp đại học

    • Khi đã cho các mệnh giá xu, cần tìm số lượng xu tối thiểu để trả lại đúng một số tiền nhất định
    • Tác giả dùng một thuật toán tham lam đơn giản, nhưng không đảm bảo nghiệm tối ưu trong mọi trường hợp
    • Quy hoạch động mới là đáp án đúng, nhưng vì không cài đặt được nên đã trượt phỏng vấn
  • Tuy nhiên, nếu không tự cài đặt thuật toán mà dùng constraint solver như MiniZinc thì có thể giải rất dễ

    • Mã ví dụ:

      int: total;
      array[int] of int: values = [10, 9, 1];
      array[index_set(values)] of var 0..: coins;
      
      constraint sum (c in index_set(coins)) (coins[c] * values[c]) == total;
      solve minimize sum(coins);
      
    • Có thể trực tiếp chạy thử ví dụ MiniZinc trên web

    • Solver sẽ dần dần tìm nghiệm tối ưu

Nhiều loại bài toán tối ưu/thỏa mãn khác nhau

  • Những bài toán tối ưu toán học thường gặp trên LeetCode v.v. (có hàm mục tiêu cực đại/cực tiểu và nhiều ràng buộc)
    khi giải bằng ngôn ngữ lập trình thì khó vì phải cài đặt ở mức thấp, nhưng lại rất phù hợp với constraint solver
  • Ví dụ, nhiều bài toán có tính chất như dưới đây đều thuộc nhóm này

Ví dụ 1: Bài toán lợi nhuận cổ phiếu tối đa

  • “Từ danh sách giá cổ phiếu, hãy tìm lợi nhuận lớn nhất có thể đạt được bằng cách mua một lần rồi bán vào thời điểm sau đó”
    • Theo cách truyền thống cần brute force O(n²) hoặc thuật toán tối ưu O(n)
    • Trong MiniZinc, có thể định nghĩa ngắn gọn như một bài toán ràng buộc như sau
      array[int] of int: prices = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];
      var int: buy;
      var int: sell;
      var int: profit = prices[sell] - prices[buy];
      
      constraint sell > buy;
      constraint profit > 0;
      solve maximize profit;
      

Ví dụ 2: Cộng/trừ các số để tạo thành 0 (bài toán thỏa mãn)

  • “Có thể cộng hoặc trừ ba số trong danh sách để tạo thành 0 hay không?”
    • Chỉ cần tìm một nghiệm thỏa mãn, không phải giá trị tối ưu
    • Có thể biểu diễn trong constraint solver như sau
      include "globals.mzn";
      array[int] of int: numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];
      array[index_set(numbers)] of var {0, -1, 1}: choices;
      
      constraint sum(n in index_set(numbers)) (numbers[n] * choices[n]) = 0;
      constraint count(choices, -1) + count(choices, 1) = 3;
      solve satisfy;
      

Ví dụ 3: Diện tích hình chữ nhật lớn nhất trong histogram

  • “Trong một histogram có chiều cao từng cột đã cho, hãy tìm diện tích hình chữ nhật lớn nhất có thể tạo được”
    • Theo cách truyền thống cần thuật toán phức tạp và quản lý nhiều trạng thái
    • Chỉ bằng các ràng buộc cũng có thể mô tả lời giải một cách ngắn gọn
      array[int] of int: numbers = [2,1,5,6,2,3];
      
      var 1..length(numbers): x; 
      var 1..length(numbers): dx;
      var 1..: y;
      
      constraint x + dx <= length(numbers);
      constraint forall (i in x..(x+dx)) (y <= numbers[i]);
      
      var int: area = (dx+1)*y;
      solve maximize area;
      
      output ["(\(x)->\(x+dx))*\(y) = \(area)"]
      

Cách tiếp cận bằng constraint solver có tốt hơn không?

  • Trong phỏng vấn, cách này có điểm yếu nếu bị hỏi về độ phức tạp thời gian

    • Constraint solver khó dự đoán thời gian chạy, và thường chậm hơn thuật toán tối ưu viết riêng
    • Tuy vậy, nó vẫn tốt hơn một thuật toán chất lượng thấp được cài đặt sai, và với đa số lập trình viên thì việc tự viết nghiệm tối ưu mỗi lần không hề dễ
  • Điểm mạnh thực sự là tính linh hoạt khi thêm các ràng buộc mới

    • Ví dụ, trong bài toán cổ phiếu, nếu đổi từ mua bán một lần sang mua bán nhiều lần thì độ phức tạp thuật toán sẽ tăng vọt
    • Nhưng trong mô hình ràng buộc của MiniZinc, chỉ cần chỉnh sửa mã rất ít là có thể đáp ứng các yêu cầu phức tạp hơn nhiều
      include "globals.mzn";
      int: max_sales = 3;
      int: max_hold = 2;
      array[int] of int: prices = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];
      array [1..max_sales] of var int: buy;
      array [1..max_sales] of var int: sell;
      array [index_set(prices)] of var 0..max_hold: stocks_held;
      var int: profit = sum(s in 1..max_sales) (prices[sell[s]] - prices[buy[s]]);
      
      constraint forall (s in 1..max_sales) (sell[s] > buy[s]);
      constraint profit > 0;
      
      constraint forall(i in index_set(prices)) (stocks_held[i] = (count(s in 1..max_sales) (buy[s] <= i) - count(s in 1..max_sales) (sell[s] <= i)));
      constraint alldifferent(buy ++ sell);
      solve maximize profit;
      
      output ["buy at \(buy)\n", "sell at \(sell)\n", "for \(profit)"];
      
  • Các ví dụ bài toán ràng buộc trên mạng thường tập trung vào puzzle như Sudoku, nhưng trên thực tế chúng có thể được dùng thú vị và thực dụng hơn cho logic nghiệp vụ hoặc yêu cầu tối ưu hóa

    • Ví dụ, cũng có nhiều khả năng áp dụng các kỹ thuật tối ưu nâng cao như symmetry breaking

Kết lại và tham khảo

  • Bài viết này là một phần trong newsletter hằng tuần của tác giả, bàn về lịch sử phần mềm, phương pháp hình thức, công nghệ mới và lý thuyết kỹ nghệ phần mềm
  • Nếu quan tâm, có thể đăng ký hoặc xem website chính của tác giả
  • Cuốn sách mới của tác giả, "Logic for Programmers", cũng đang được phát hành

2 bình luận

 
kohs100 2025-09-15

Thông thường các bài toán thuật toán chẳng phải đều có ràng buộc về độ phức tạp thời gian/không gian sao? Nếu bảo là có thể giải bằng constraint solver thì rốt cuộc chỉ cho thấy khả năng chuyển bài toán sang các biểu thức ràng buộc thôi mà... Tôi cũng không rõ liệu đó có thực sự là năng lực cần thiết trong công việc thực tế hay không...

 
GN⁺ 2025-09-13
Ý kiến trên Hacker News
  • Vấn đề lớn nhất của các câu hỏi phỏng vấn kiểu Leetcode là không thể yêu cầu giải thích thêm; cách tư duy của tôi khác với số đông nên Leetcode có cảm giác dựa nhiều vào việc học thuộc đáp án. Với những bài có đủ ngữ cảnh thì tôi vẫn có thể hiểu theo hướng thực tế nên không sao, nhưng phần lớn lại thiếu giải thích nên tôi không nắm đúng được bài toán. Vì vậy dạo này tôi từ chối hẳn các vòng Leetcode hay phỏng vấn tự động bằng AI. Bài tập về nhà, phỏng vấn 1:1, hoặc chia sẻ màn hình thì tôi thấy ổn. Nếu trang Leetcode có lời giải và đáp án thực sự tử tế thì việc tự học chắc cũng dễ hơn nhiều, nhưng thực tế là quá khó. Đây không đơn thuần là vấn đề năng lực, mà là cách tư duy của tôi không hợp với dạng bài này. Bị đặt vào trạng thái không được hỏi gì mà cứ phải làm mãi các câu hỏi kiểu trắc nghiệm khiến tôi cảm giác đây là một hệ thống được thiết kế để người ta thất bại, nhất là khi đang nói về các bài kiểu AI/Leetcode dùng cho vòng sơ loại trước phỏng vấn. Còn môi trường phỏng vấn có người thật, nơi có trao đổi qua lại bằng câu hỏi, thì tôi đánh giá là đủ tốt.

    • Phỏng vấn LC(Leetcode) giống như kiểm tra tốc độ chạy 100m đã được luyện trước, trong khi công việc thực tế lại giống một cuộc marathon dài phải dừng rồi quay lại nhiều lần. Dù vậy, hiện tại nếu muốn có lương cao ở các tập đoàn lớn kiểu SMEGMA thì vẫn phải chơi trò này. Ví dụ, tôi từng tự làm một engine game 2D, nhưng tôi nghĩ mình sẽ không vượt qua được kiểu phỏng vấn LC bắt phải nhào lộn với hàng loạt bài hard. Tôi đã chấp nhận điều đó rồi. Engine tôi làm

    • Không phải cứ học thuộc lời giải là xong; kể cả có thuộc vẫn có thể bí ở các câu hỏi mở rộng. Nhưng nếu đã thuộc mà vẫn xử lý tốt được phần hỏi thêm thì tôi nghĩ là không có vấn đề gì với các bài kiểu Leetcode. Khả năng giải quyết vấn đề là năng lực nhận dạng mẫu, và biết càng nhiều mẫu thì năng lực đó càng cao. Giờ thì GPT cũng giải bài và giải thích được, nên những kỹ năng này còn dễ học hơn trước. Theo tôi, bắt đầu học từ bây giờ vẫn tốt hơn.

    • Quá đồng cảm. Gần đây tôi cũng có một buổi phỏng vấn mà bài tập về nhà được chấm điểm cao nhất, các kỹ sư và cả CEO đều đánh giá tốt, nhưng rồi CTO đột ngột bắt làm một vòng live coding nên cuối cùng tôi vẫn trượt. 11 tuần đi phỏng vấn coi như công cốc. Thật bực khi quy trình ngớ ngẩn này vẫn còn tồn tại. Demo/bài tập tôi đã làm ở đây, link Monumental và sản phẩm, mã GitHub

    • Có khi việc không thể đặt ra câu hỏi rõ ràng lại chính là năng lực mà họ muốn kiểm chứng: xem ứng viên tiếp cận việc giải quyết vấn đề như thế nào. Nếu buộc mọi người chỉ được tiếp cận theo kiểu chắc nịch thì toàn bộ phần mềm sẽ càng phức tạp và rối rắm hơn. Cái khó thực sự không phải là viết ra mấy dòng code, mà là cả quá trình giải bài toán.

    • Những nơi tôi từng phỏng vấn, dù chỉ đưa một hai bài LC, hễ yêu cầu giải thích là họ lập tức chuyển sang đối thoại thời gian thực và code trực tiếp. Làm như vậy thì có một nhược điểm là áp lực tâm lý của live coding tăng rất mạnh.

  • Khi nhận những bài phỏng vấn như vậy, tôi có cảm giác họ không muốn bạn "dùng" constraint solver, mà muốn bạn "tự viết" một constraint solver phù hợp cho một bài toán bị giới hạn.

    • Đúng vậy, nên tôi nghĩ cách tiếp cận phỏng vấn kiểu này về gốc rễ đã rất vụng về. Trong bối cảnh kỹ thuật thực tế, ta có thể uống cà phê, đọc paper, xem tài liệu, đi dạo suy nghĩ, tham khảo các công cụ sẵn có như constraint solver hay LLM, rồi giải được bài toán 100%. Nhưng trong phỏng vấn thì tôi có cảm giác mình còn không giải nổi 0%. Tôi chưa từng nghĩ đến chuyện muốn vào làm ở nơi phỏng vấn kiểu đó.

    • Tôi cũng thật sự nghĩ vậy. Phần lớn các bài toán NP đều có thể chuyển thành bài toán ràng buộc. Trên thực tế, constraint solver có phải là đáp án đúng hay không còn tùy rất nhiều vào miền áp dụng. Và trong bối cảnh phỏng vấn thì gần như không phải đáp án đúng. Những solver như vậy thường chậm hơn rất nhiều so với dynamic programming đơn giản.

    • Điều này có thể đúng ở một số công ty, nhưng không phải ở tất cả. Nói chung, LC được dùng trong phỏng vấn thường chỉ vì một lý do: quy trình tuyển dụng kém hiệu quả. Ngay cả người tham gia cũng biết là hệ thống cần thay đổi, nhưng họ либо không có quyền lực, либо quá phân tán nên không đổi được. Ở công ty lớn, HR đôi khi xoay cùng một bộ câu hỏi cho nhiều team để "chuẩn hóa"; còn công ty nhỏ thì không có thời gian tự chuẩn bị bài nên lấy trên Internet. Trong những trường hợp như vậy, ngay cả người phỏng vấn kỹ thuật cũng thường có cái nhìn phê phán với phỏng vấn LC và thực tế cố tìm cách nhận ra những ứng viên nổi bật. Trong môi trường đó, chỉ cần cho thấy bạn có quan tâm hoặc có kiến thức về constraint solver thôi cũng thường đã được cộng điểm khá nhiều.

    • Nếu ai đó giải một bài LC hard bằng constraint solver mà vẫn không được tuyển, thì người ngốc là chính interviewer. Bản thân việc biết constraint solver là gì, biết cách mô hình hóa vấn đề để dùng nó, đã là điều cực kỳ hiếm. Tôi cũng từng dùng hồi năm ba, và chỉ riêng việc viết các ràng buộc thôi cũng đã là một công việc phức tạp đến đau đầu.

    • Điểm này vừa đúng vừa sai. Tôi từng dùng loại câu hỏi như vậy trong phỏng vấn, và nếu ứng viên nghĩ đến constraint solver thì tôi cho điểm tốt. Trong kỹ thuật thực tế, constraint solver bị đánh giá thấp trong khi nó lại là chỉ báo cho thấy người đó có thể nhanh chóng đưa ra câu trả lời đúng. Tất nhiên sau đó tôi vẫn sẽ hỏi thêm một vòng kiểu whiteboard để đánh giá năng lực code thực sự. Nhưng việc đưa ra constraint solver như một câu trả lời tự nó không phải là điều tệ.

  • Đây là một góc nhìn hay, nhưng tôi nghĩ nó không hợp lắm với phỏng vấn thực tế. Cốt lõi của các bài như vậy là họ muốn kiểm tra khả năng "động não" của ứng viên. Chỉ giải bằng cách viết ràng buộc thì chủ yếu cho thấy mức độ kinh nghiệm và kiến thức, chứ không bộc lộ được việc người đó có thực sự "vận não" hay không.

    • Người phỏng vấn có xu hướng hay ra các bài "Array String" trong Leetcode "Top Interview 150". Với tôi là một lập trình viên backend Python, những bài đó khá xa công việc hằng ngày. Phần lớn đòi hỏi các thuật toán thao tác mảng in-place, thứ thường chỉ thực sự cần trong các ngôn ngữ thiên về hiệu năng như C hay Rust. Ngược lại, các bài kiểu "Hashmap" lại hữu ích hơn để cho thấy cách tận dụng công cụ phù hợp với ngôn ngữ. Ngoài ra còn có nhiều bài không điều chỉnh tốt được độ khó, đến mức các bài gắn mác "easy" như Majority Element thực ra lại đòi hỏi thuật toán mang tính lịch sử như Boyer–Moore, nên rất khó gọi là "dễ".

    • Vòng cuối cùng tôi thấy gần đây ở Meta thực chất chỉ là kiểm tra xem bạn đã lặp đi lặp lại và ghi nhớ các bài rất cụ thể của họ đến mức nào. Bởi vậy nếu trả lời khác đáp án sách giáo khoa thì họ lại lúng túng. Tôi có cảm giác bản thân sự "thông minh" không phải là thứ quá quan trọng ở đó.

    • Thuật toán DP bottom-up có cần động não ở mức nào đó, nhưng phần lớn bài toán vẫn có thể giải theo kiểu top-down, tức đệ quy + memoization. Ví dụ bài đổi tiền còn có thể giải tốt hơn bằng tìm kiếm A*. Nhưng ngoài đời thực thì phần lớn lập trình viên hầu như chẳng bao giờ phải dùng đến những thuật toán như vậy. Điều thực sự quan trọng là nhận ra khi mình vô tình viết code có độ phức tạp thời gian O(n^2). Xem accidentallyquadratic.tumblr.com sẽ thấy ngay cả những người giỏi trong các dự án nổi tiếng cũng hay mắc lỗi đó. Vì thế, năng lực tạo ra thuật toán trong bài test và năng lực phát hiện sai lầm thuật toán khi làm việc thực tế là hai chuyện khác nhau.

    • Khi phỏng vấn để kiểm tra khả năng giải quyết vấn đề, tôi coi trọng quá trình tư duy, cách giao tiếp và khả năng phân rã vấn đề. Quan trọng hơn nhiều là chuẩn bị những câu hỏi có thể điều chỉnh độ khó để mọi ứng viên đều có cơ hội thể hiện năng lực. Nếu ai đó bật ra ngay đáp án có sẵn hoặc bị bí quá lâu, thì thật ra từ góc nhìn interviewer cũng không rút ra được nhiều điều. Vì vậy, câu hỏi phỏng vấn thiên về giải quyết vấn đề có thể rất hữu ích, chỉ tiếc là ngoài đời chúng lại không được dùng tốt.

    • Những bài kiểu này thực ra không hề kiểm tra "độ thông minh", mà chỉ kiểm tra bạn đã học thuộc khoảng chục mẫu khuôn sẵn đến mức nào. Gần như mọi ứng viên đều đậu hay trượt dựa trên trí nhớ ôn luyện chứ không phải năng lực giải quyết vấn đề sáng tạo. Bài LeetCode đã bị gamification quá mức, đến mức có cảm giác nó chỉ còn là công cụ kiểm tra ý chí chuẩn bị.

  • Phần lớn phỏng vấn có vẻ đặt ra bài toán theo kiểu: "Nếu bệnh nhân tiểu đường không tự tổng hợp được insulin ở nhà thì coi như gian lận trong trò chơi cuộc đời." Vợ tôi khi đường huyết tăng thì tiêm insulin; tương tự, nếu là bài toán ràng buộc thì dùng solver mới là đúng. Nếu công ty không phải đang làm phần mềm constraint solver, thì tôi không hiểu tại sao lại phải giả định rằng "loại phần mềm này không tồn tại" rồi bắt người ta xây lại từ đầu.

    • Cốt lõi là họ không kiểm tra khả năng tổng hợp insulin khi khủng hoảng, mà là một bài test năng khiếu đầu vào để xem bạn có thể học thuộc tài liệu trong một tuần rồi đọc lại trơn tru qua điện thoại hay không.

    • Nếu bạn tìm ra cách giải hiệu quả bằng constraint solver, thì việc viết vài vòng for cùng một hàm đệ quy phụ để giải mấy bài đồ chơi chắc cũng quá dễ.

    • (không có nội dung đáng kể)

    • Để bênh vực coding test một chút, phần lớn những người không giải nổi cả các bài DP đơn giản thì khi vào làm thực tế cũng thường yếu năng lực. Dĩ nhiên vẫn có ngoại lệ, nhưng theo kinh nghiệm của tôi thì thường là vậy.

  • Mỗi khi nhắc đến ngôn ngữ lập trình ràng buộc thì luôn nên nhắc Håkan Kjellerstrand. Ông ấy vận hành một trang tuyệt vời tập hợp rất nhiều ví dụ và bài toán, trong đó có cả MiniZinc. hakank minizinc

    • Và ông ấy không chỉ làm ra một website tốt, mà ngoài đời còn là một người cực kỳ tử tế.
  • Rất lâu trước đây, khi tôi còn là một tay mơ chưa từng được học về constraint solver trong chương trình khoa học máy tính ở đại học, tôi gặp loại bài toán này khi một người bạn nhờ làm ứng dụng xếp lịch cho câu lạc bộ thể thao. Ban đầu nó trông rất đơn giản, nhưng đến khi thực sự bắt tay vào làm tôi mới không nhận ra rằng mình đã đâm đầu vào một vấn đề khổng lồ. Về sau đó là một bài học rất tốt cho sự ngạo mạn của tôi, và từ trải nghiệm đó tôi đã được giúp ích rất nhiều khi thảo luận về lịch trình, deadline và kỳ vọng.

    • Có thể đây là câu hỏi sơ đẳng, nhưng tôi tò mò liệu có thể giải dễ hơn bằng "tối ưu tuyến tính" thay vì constraint solver không. Từ trải nghiệm tự làm của tôi, ưu điểm của tối ưu tuyến tính là có thể xử lý xung đột giữa các quy tắc bằng trọng số để tìm ra nghiệm theo hướng tạo ra ít "tác dụng phụ" nhất.

    • Khoảng 25 năm trước, hồi còn học cấp ba và mới bắt đầu học TI-Basic với VB6, tôi làm thêm ở một quán burger và nghe quản lý than phiền rằng việc xếp lịch nhân viên mỗi tuần thật khó. Tôi đã nói "Em biết chút về máy tính, để em viết cho anh một chương trình xếp lịch nhé!" Rồi chẳng bao lâu sau tôi nhận ra việc viết scheduler khó đến mức nào và bỏ cuộc ngay.

  • "Lập luận của tác giả là nếu đem kiểu bài này vào phỏng vấn, thì khi ứng viên hỏi 'độ phức tạp thời gian chạy của thuật toán này là bao nhiêu?' sẽ không có câu trả lời ổn thỏa"; tức là constraint solver cũng không phải đáp án cho các bài Leetcode hard nếu nó không giải đủ nhanh. Nếu yêu cầu runtime rộng rãi thì cách đơn giản cũng dùng được, nhưng việc tìm ra lời giải hiệu quả là một phần lớn của toàn bộ thử thách.

    • Trong thực tế làm việc, gần như lúc nào người ta cũng cần "nghĩ ra giải pháp để phản ứng nhanh với yêu cầu mới" hơn là "lời giải tối ưu nhất". Vì vậy tôi nghi ngờ việc phỏng vấn bằng tiêu chí hiệu năng xa rời thực tế có thật sự có ý nghĩa không, dù điều này có thể khác nhau tùy vai trò. Tôi đồng ý với tác giả rằng lời giải tối ưu nhất chưa chắc phản ánh đúng năng lực thực chiến. Đây cũng là một bối cảnh của các chỉ trích với Leetcode. Ngay cả cùng một bài, nếu nhìn theo hướng "bạn linh hoạt đáp ứng yêu cầu mới đến đâu" thì có lẽ còn khách quan hơn.
  • Constraint solver à? Ý tưởng hay, tôi có nghe qua. Nhưng trong phỏng vấn thì thực tế họ chỉ muốn bạn tự triển khai bằng Python để xem luồng suy nghĩ của bạn, (tôi cảm giác gần như không thể thuyết phục họ cho dùng constraint solver trong phỏng vấn)

    • Tôi tò mò không biết bạn đã thực sự nói điều đó với interviewer chưa, hay mới chỉ là dự đoán?

    • import z3

  • Nếu giải bằng DP (dynamic programming) trước rồi nói "giờ tôi sẽ thử lại bằng constraint solver" thì tuyển ngay.

    • +1
  • Điểm mạnh thực sự của constraint solver là nó phản ứng dễ đến đâu trước "yêu cầu mới". Tôi cũng từng trải nghiệm rất rõ lợi ích của những solver tổng quát như vậy khi làm tối ưu hóa datacenter ở Google: nó vận hành linh hoạt trước những thay đổi trong yêu cầu.