Một Frontend Developer phỏng vấn thuật toán sẽ như nào ?
Hí mọi người, hôm nay mình sẽ chia sẻ về quá trình và những trải nghiệm cá nhân khi tham gia phỏng vấn tại công ty S (mình xin phép được giấu tên nhé) gồm 3 vòng (thuật toán và FE technical
)
Nghe lạ nhỉ, process phỏng vấn một Frontend Engineer nhưng lại có đến 3 vòng xử lí thuật toán. Ban đầu mình cũng khá bất ngờ và hồi hộp khi nghe về thông tin của những buổi phỏng vấn trên, khá lo lắng vì chưa từng tham gia buổi phỏng vấn nào như vậy.
Cùng mình tìm hiểu rõ hơn về quá trình của buổi phỏng vấn nhé ?
1. Các Quy Trình Phỏng Vấn
Quy trình bao gồm 4 vòng, bắt đầu là vòng gửi/duyệt CV của HR, các vòng còn lại là xử lý problems ở leetcode do interviewer chuẩn bị sẵn, sau mỗi vòng coding sẽ phỏng vấn tiếp Frontend Technical.
1.1 Gửi CV
Phần này cũng khá đơn giản, mọi người nên chuẩn bị CV 2 bản, một bản bằng tiếng Anh và một bản bằng tiếng Việt. Thông tin về CV mọi người tham ở khảo ở phía dưới nhé, mình có để kèm link:
- Nhấn vào phần
Kinh nghiệm
ở thanh điều hướng phía trên màn hình. - Hoặc có thể nhấn vào link sau: Nhấn ở đây
Nếu mọi người cần một bản PDF thì liên hệ mình nhé, mình sẽ gửi cho hen. Mình đang update thêm tính năng download PDF ở đây luôn :D mọi người chờ nhé.
1.2 Round 1 - Remove Digit From Number to Maximize Result
Đây là vòng phỏng vấn thuật toán và technical đầu tiên.
Người phỏng vấn mình vòng này là một Senior, mình có check qua Linkedin thì thấy anh này hơn 10 năm exp. Ban đầu thấy khá rén và căng thẳng, nhưng mà sau một vài phút giới thiệu bản thân và nói chuyện với anh interviewer thì mình cũng cởi mở hơn và đỡ căng thẳng hơn.
Đầu tiên sẽ hỏi về kiến thức FE trước, vòng đầu tiên mình thấy hỏi về FE khá nhẹ nhàng:
1> Về HTML/CSS:
- Mình được hỏi cơ bản về thuộc tính cơ bản như flex, grid , priority of css.
- Một vài cách xử lí layout cụ thể (interviewer chuẩn bị sẵn)
2> Về ReactJS, NextJS:
- DOM ảo là gì ? So sánh DOM ảo và DOM thật ?
- Xử lý form với những library như React Hook Form, Formik.
- Một vài câu hỏi nhỏ liên quan đến React và NextJS :D Vòng này FE Technical khá ít và không quá khó nên mình cũng không nhớ rõ chi tiết câu hỏi.
Sau khi xong vòng technical, mình được gửi cho một link Leetcode , mức độ easy, đó là bài Remove Digit From Number to Maximize Result (Easy)
Mọi người xem qua trước đề nhé, về cách mình xử lý trong buổi phỏng vấn thì mình sẽ viết ở phía bên dưới
Mình sẽ giải thích đề như sau:
Cho một chuỗi số dương interger và một con số target
Mong muốn: Trả về kết quả sau khi loại bỏ chính xác một lần xuất hiện của số target khỏi chuỗi string ban đầu sao cho cho giá trị kết quả cuối cùng là số lớn nhất trong những con số mà mình có thể loại bỏ ở trên.
""
Mình sẽ có ví dụ sau:
- Input: number =
1231
, digit =1
- Output:
231
- Explanation: Chúng ta có thể loại bỏ
1
để có được chuỗi231
hoặc là123
.
Sau khi kiểm tra, ta có 2 kết quả, có thể 231
hoặc 123
=> 231
lớn hơn 123
=> con số tối ưu mà mình nên trả về là 231
.
Đơn giản nhỉ ?
Về cơ bản thì interviewer mong muốn mình giải theo 2 cách:
- Cách cơ bản: được xài bất kì loại cấu trúc dữ liệu nào, miễn xử lý được yêu cầu của đề bài đưa ra
- Cách tối ưu: xử lý dựa trên input đầu vào, không cần tạo ra các cấu trúc dữ liệu như Array, Object etc...
Về cách cơ bản thì mình chọn tạo ra một mảng để lưu trữ những con số sau khi remove đi target:
Ví dụ ta có con số input là 232341
và target là 2
:
Sau mỗi lần loop qua từng con số => ta remove đi và push vào mảng
Cuối cùng sẽ có một mảng các phần tử như sau: [32341, 23341]
. Với kết quả như này, việc tìm ra con số lớn nhất rất dễ nhỉ ?.
Tiếp theo, là cách tối ưu hơn, mình có giải thích trong block code phía dưới nhé.
Đây là đoạn code của mình:
function removeDigit(number, digit) {
// tạo ra 1 biến lưu trữ index lưu trữ vị trí của số mà bạn sẽ xoá
let index = -1;
for (let i = 0; i < number.length; ++i) {
// Kiểm tra từng con số xem có phải là số mà chúng ta sẽ remove không ?
if (number[i] === digit) {
// Cập nhật lại index
index = i;
// Kiểm tra số hiện tại xem có lớn bé hơn số sau hay không ?
// Nếu bé hơn => thì không cần tìm index nữa => vì nếu remove tại index này sẽ ra được số lớn nhất
// Nếu lớn hơn => tiếp tục tìm
if (i + 1 < number.length && number[i] < number[i + 1]) {
break;
}}}
// Xử lí chuỗi để cho tra được con số mong muốn.
return (number.substring(0, lastOccurrenceIndex) + number.substring(lastOccurrenceIndex + 1));
}
Tuy cách triển khai cũng khá đơn giản, nhưng trong lúc interview mình cũng mất một vài phút để suy nghĩ ra được solution.
Đấy, vòng một chỉ như thế thôi, bao gồm một bài leetcode level easy và những câu hỏi về kiến thức Frontend ở mức cơ bản.
1.3 Round 2 - Roman to Integer
Người phỏng vấn mình vòng này vẫn là một Senior, vòng này mình cảm thấy thoải mái hơn vì đã có kinh nghiệm và chuẩn bị từ trước. Anh interviewer cũng khá nice nên mình không thấy áp lực trong suốt buổi phỏng vấn.
Sẽ hơi khác một tí so với vòng một, ở vòng này interviewer mong muốn mình xử lí problem ở leetcode trước, sau đó mới đến FE techincal.
Mình được gửi 1 link đề: Roman to Integer (Medium)
Mọi người xem qua trước đề nhé, về cách mình xử lý trong buổi phỏng vấn thì mình sẽ viết ở phía bên dưới
Mình sẽ giải thích đề như sau:
Cho các chữ số La Mã tương ứng như sau: I (1), V (5), X (10), L (50), C (100), D (500) và M (1000). Các số thường được biểu diễn từ lớn nhất đến nhỏ nhất từ trái sang phải
Các trường hợp duy nhất như IV (4) và IX (9) sử dụng ký hiệu trừ trong đó số nhỏ hơn đặt trước số lớn hơn biểu thị phép trừ.
Nguyên tắc này cũng áp dụng cho XL (40), XC (90), CD (400) và CM (900).
Mong muốn: Cho một số La Mã, chuyển nó thành số nguyên.
""
Mình sẽ có ví dụ sau:
- Input: s =
LVIII
- Output:
58
- Explanation:
L = 50, V= 5, III = 3.
Thêm một ví dụ nữa nhé:
- Input: s =
MCMXCIV
- Output:
1994
- Explanation:
M = 1000, CM = 900, XC = 90 and IV = 4.
Chắc mọi người cũng hiểu đề rồi nhỉ? Mình sẽ để cách xử lý của mình ở dưới, mọi người tham khảo nhé, mình có giải thích ở từng line code.
// Khởi tạo một object => dùng để chuyển đổi giá trị từ số la mã sang số nguyên
const ROMAN_NUMERALS = {
M: 1000,
D: 500,
C: 100,
L: 50,
X: 10,
V: 5,
I: 1,
};
const romanToInt = (romanInput) => {
// Tạo một số lưu kết quả trả về
let integer = 0;
for (let i = 0; i < romanInput.length; i++) {
const romanCharacter = romanInput[i]; // Chữ số la mã
// Số nguyên chuyển từ số la mã sang
const romanValue = ROMAN_NUMERALS[romanCharacter];
// Số giá trị số nguyên ở index tiếp theo
const nextRomanValue = ROMAN_NUMERALS[romanInput[i + 1]];
// Đoạn này mình sẽ so sánh giá trị hiện tại và giá trị tiếp theo:
// Nếu như số hiện tại < số tiếp theo => mình sẽ trừ đi => ngược lại
// mình sẽ cộng vào.
// => Ví dụ: XIX => 10 - 1 + 10 => được 19
if (nextRomanValue > romanValue) integer -= romanValue;
else integer += romanValue;
}
return integer;
};
Bài này ở mức độ Medium, thế nhưng thời gian và cách mình triển khai bài này lại nhanh và ổn hơn bài ở trên. Sau khi đưa ra được solution, và test function với những test case mà interviewer đưa thì function hoạt động ổn và trả về kết quả đúng.
Tiếp theo, interviewer mong muốn mình dựng một ứng dụng đơn giản với MUI, Tailwinds và NextJS 14, bao gồm một page cho user nhập input là số la mã => call api bằng NextJS => trả về kết quả và hiển thị ra màn hình.
Mình để 2 đoạn code ở dưới nhé, mọi người tham khảo:
"use client";
import { TextField } from "@mui/material";
import { useState } from "react";
export default function Home() {
const [romanValue, setRomanValue] = useState("");
const [transformedInteger, setTransformedInteger] = useState("");
const handleTransformRoman = async () => {
try {
fetch("/api/roman", {
method: "POST",
body: JSON.stringify({ input: romanValue }),
})
.then((res) => res.json())
.then((data) => setTransformedInteger(data.integer));
} catch (error) {
console.log({ error });
} finally {
console.log("end loading");
}
};
return (
<main className="flex min-h-screen flex-col p-24 bg-white">
<span className="text-[30px] text-center mt-10 font-bold text-black">
Roman to integer
</span>
<div className="flex flex-col gap-y-4 items-center justify-center mt-10">
<TextField
id="outlined-basic"
label="Outlined"
variant="outlined"
className="w-[400px]"
placeholder="Enter roman here"
value={romanValue}
onChange={(e) => setRomanValue(e.target.value)}
/>
<button
onClick={handleTransformRoman}
className="px-6 py-3 text-white border bg-black">
Chuyển đổi
</button>
<span className="text-[30px] text-center mt-10 font-bold text-black">
Result: {transformedInteger}
</span>
</div>
</main>
)}
import { getIntegerByRomanValue } from "@/utils/roman";
import type { NextApiRequest, NextApiResponse } from "next";
type ResponseData = {
integer: number;
};
export default function handler(
req: NextApiRequest,
res: NextApiResponse<ResponseData>
) {
if (req.method === "POST") {
const body = JSON.parse(req.body);
const input = body.input;
let integer = 0;
if (input) integer = getIntegerByRomanValue(input);
res.status(200).json({ integer });
} else res.status(200).json({ integer: 0 });
}}
Như vậy là đã kết thúc vòng 2 của buổi phỏng vấn, cũng khoai nhỉ?
1.4 Round 3 - Koko Eating Bananas
Đến vòng này thì mình cảm thấy không còn tâm lý và áp lực như hai vòng trên nữa. Tuy nhiên, đây là vòng mình fail nhất trong 3 vòng T.T Tuy vẫn xử lý được vấn đề nêu ra nhưng trong quá trình lên ý tưởng và triển khai thuật toán không tốt, cần sự giúp đỡ của interviewer mới đưa ra được kết quả.
Vẫn tương tự trên vòng 2, mình cần xử lý problem ở Leetcode trước khi interview FE Technical.
Mình được gửi 1 link đề như sau: Koko Eating Bananas (Medium)
Mọi người xem qua trước đề nhé, về cách mình xử lý trong buổi phỏng vấn thì mình sẽ viết ở phía bên dưới
Mình sẽ giải thích đề như sau:
Koko thích ăn chuối. Đề cho ta n cột chuối, cột thứ i có j quả chuối. Giả sử bác bảo vệ đã đi rồi và sẽ quay lại sau h giờ nữa. Koko có thể quyết định tốc độ ăn chuối mỗi giờ của mình là k. Mỗi giờ cô ấy chọn một cột chuối và ăn k quả chuối từ cột đó. Nếu cột đó có ít hơn k quả chuối, cô ấy sẽ ăn hết và sẽ không ăn thêm quả chuối nào trong giờ đó nữa. Koko cần phải ăn hết chuối khi bác bảo vệ trở về. Trả về số nguyên tối thiểu k sao cho cô ấy có thể ăn hết số chuối trong vòng h giờ.
""
Đây là mình đã dịch sang tiếng Việt , đề gốc là tiếng Anh và khá dài nên mình ban đầu mình rất hoang mang và không hiểu đề. Sau đó có đặt câu hỏi và nhờ anh interviewer giải thích một số chỗ thắc mắc.
Mình sẽ có ví dụ sau:
- Input: piles =
[3,6,7,11]
,h = 8
- Output:
4
- Explanation: Koko cần ăn 4 quả chuối mỗi giờ đối với mỗi cọc chuối => để vừa đủ 8h bác bảo vệ quay về thì Koko phải ăn hết chuối trong tất cả các cọc.
- Cụ thể hơn sẽ là: với cọc đầu tiên có 3 quả chuối => Khả năng koko ăn được là 4 quả => cột đầu tốn 1h
- Cọc thứ 2 6 quả cuối, Koko ăn mỗi lần được 4 quả => tốn 2 giờ mới ăn hết chuối trong cọc.
- Cứ như thế cho đến khi vừa đủ 8h thì Koko ăn được hết tất cả các chuối trong cọc. => Trả về số 4 (Số chuối ít nhất là Koko nên ăn để vừa kịp bác bảo về trở lại).
Đến đây mọi người đã hiểu đề rồi nhỉ?
Ta sẽ có các cách sau để xử lí được bài toán này:
1> Cách đầu tiên:
- Ta cần tìm ra số chuối nhiều nhất mà một cột có thể có, với ví dụ trên, thì ta tìm ra được cột số 4 với 11 quả chuối.
- Tiếp theo, ta sẽ thử lặp từ 1 -> 11 quả chuối, tương ứng cho mỗi giờ Koko sẽ ăn k quả chuối. Ví dụ trong 1h Koko ăn được 1 quả chuối, ta mất 3h để ăn hết cột 1, mất 6h ăn cột 2, ... và cho đến cột cuối cùng => ta có tổng số giờ mà koko sẽ ăn được chuối. Từ đó, ta so sánh và chọn số k phù hợp.
- Với cách này, ta có thể tối ưu bẳng cách giảm số lần tính tổng số giờ đối với mỗi k quả chuối mà ta thử nghiệm.
- Đây cũng là cách mình được gợi ý và triển khai theo, đoạn code của mình sẽ để phía dưới, mọi người tham khảo nhé.
2> Cách tối ưu:
- Ngoài ra mình một cách tối ưu nhất, đó chính là ta cần xác định số k phù hợp, vẫn như cách trên, ta cần xác định số chuối nhiều nhất của một cột, vẫn loop qua từng kết quả thử nghiệm, nhưng cách loop ở đây sẽ khác là ta sẽ dùng
Binary Search
để tối ưu số vòng lặp tìm kiếm k, thay vì loop hết kết quả thử nghiệm. - Mình sẽ để link solution ở đây, mọi người tham khảo nhé, sau khi phỏng vấn xong mình đã đọc và ngẫm lại: Xem ở đây nhá
Còn đây là đoạn code của mình sau vài lần refactor theo anh interviewer:
const solution = (piles, hour) => {
const maxPile = Math.max(...piles);
for (let k = 1; k <= maxPile; k++) {
let numberOfHour = 0;
for (let j = 0; j < piles.length; j++) {
numberOfHour += Math.ceil(piles[j] / k);
if (numberOfHour > hour) break;
}
if (numberOfHour === hour) return k;
}
return 0;
};
Về mặt triển khai code khá gọn, nhưng độ phức tạp thuật toán thì lại là O(n)^2
. Tối ưu nhất thì nên xài binary search nhé.
Sau khi làm bài leetcode trên, thì bắt đầu mình tâm lý lại và khá run, tuy nhiên đoạn interview FE lúc sau khá suông sẻ và anh interviewer cũng khá thân thiện, thoải mái nên mình cũng ổn.
Mình cảm thấy vòng này là khoai nhất, vì khối lượng kiến thức FE interviewer hỏi khá nhiều, từ cơ bản đến chuyên sâu của ReactJS, NextJS, ngoài ra còn những câu hỏi liên quan đến xử lý vấn đề gặp trên production, work environment, etc...
Tuy nhiên, qua những câu hỏi và tranh luận với anh interviewer, mình thấy học hỏi được khá nhiều, có những kiến thức mà trong hơn 2 năm làm việc mình chưa từng biết. Em cảm ơn anh Tuấn nhé.
Mình sẽ list lại một số câu hỏi phía dưới, tuy không đầy đủ như lúc interview, nhưng đây là những gì mình còn nhớ lại:
- SEO là gì, vì sao Next lại SEO tốt hơn React?
- React có thể SEO được không ?
- Hàm cleanup của useEffect được run khi nào?
- Các Global Store Management em hay xài là gì?
- Nếu một site cần cải thiện hiệu suất, em sẽ làm những gì?
- Nếu gặp một library không cung cấp documents hoặc documents không đầy đủ? Và trong dự án sử dụng library đó, thì em cần làm gì?
- Nếu một issues xảy ra ở production, em sẽ xử lý như nào?
- Nếu một task đã gần đến ngày deadline? Em cần làm gì
Và một loạt câu hỏi khác liên quan đến FE Technical và cách xử lý vấn đề. Mình cảm thấy mình thể hiện khá ổn ở phần Technical này.
2. Mình học được gì?
Sau các vòng interview trên thì mình nhận ra cũng khá nhiều thứ, không chỉ đơn giản là phỏng vấn Frontend hay bất kì role nào cả. Việc một Developer
, Engineer
thì suy cho cùng, công nghệ vẫn chỉ là công nghệ, ngôn ngữ vẫn là ngôn ngữ, việc ta cần cải thiện và trau dồi hằng ngày đó chính là tư duy và khả năng đúc kết, xử lý vấn đề.
Ngoài ra, việc dám thử
của bản thân cũng rất quan trọng, phải thử mới biết mình đang ở đâu, phải thử mới biết mình đang thiếu những gì? Việc mình vượt qua các vòng interview 1, interview 2 và rồi lại là interview 3, đã cho mình thấy rằng, bước ra khỏi vùng an toàn, thì lúc đấy giá trị thật sự của bản thân mới thật sự hiện hữu.
3. Feedbacks của mình
Cho đến nay thì mình được biết rằng bên phía công ty vẫn đang pending hồ sơ ứng viên để thực hiện một vài process khác, điểm này mình cảm thấy process bên phía công ty chưa thật sự chưa chuyên nghiệp lắm (không gửi mail thông báo cho ứng viên).
Dưới đây là một vài feedbacks mình nhận được từ các anh interviewer hehe.
Hí, cuối cùng cũng đến đoạn kết bài viết, những gì mình viết ở trên chính là trải nghiệm của một Frontend Developer
khi apply job Frontend Engineer
. Nếu có bất kì câu hỏi gì, thì cứ nhắn cho mình nhé.
Mình cảm ơn vì đã dành thời gian đọc bài viết của mình, se yaaa !
Algorithm
React
Typescript