LeetCode Guide
All examples and exercises on this site are selected from the publicly available free problems on LeetCode, with links provided to the problems so you can directly go to the official website for practice.
To assist beginners, here is a quick introduction to the problem-solving mechanism and tips for using the online judging platform.
Core Code Pattern
When solving problems on LeetCode, you are given a function framework that you need to complete with your logic. This approach is commonly referred to as the Core Code Pattern.
For example, in LeetCode's first problem, "Two Sum," you are required to implement a twoSum
function like this:
class Solution {
public int[] twoSum(int[] nums, int target) {
}
}
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
};
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
}
};
func twoSum(nums []int, target int) []int {
}
The idea is that you are given an input array nums
and a target value target
. You need to implement the algorithm logic and eventually return an array as the result.
When you submit your solution code to the backend, it will pass several predefined test cases to the twoSum
function. By comparing your return values with the correct answers, it determines whether your code is correct.
From the user's perspective, the core code mode should be the most convenient form, as you only need to focus on writing the algorithm logic. Currently, most coding platforms and technical interviews/tests use the core code mode. However, just in case, here is an introduction to the ACM mode.
ACM Mode
ACM mode involves more complexity: the problem provides you with input in a specific string format, which you must parse yourself, then print the calculation results to standard output.
After submitting your code to the backend, the system will provide each test case organized in a specific string format to your program. It then compares your program's standard output with the expected output to determine if your code is correct.
For example, platforms like "牛客网" offer this ACM mode. Here's a random screenshot, where you can see that the code editor has no initial code, and you need to write everything from scratch:

In ACM mode, one technique is to layer your code, separating the logic for handling string input from the core algorithm logic, like this:
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// this part mainly responsible for receiving
// and processing input data, constructing input
int[] nums;
int target;
int N = scanner.nextInt();
...
// call the algorithm logic to solve
System.out.println(twoSum(nums, target));
}
public static int[] twoSum(int[] nums, int target) {
// convert to core code mode, write algorithm logic here
}
}
This makes it very clear. The logic for input processing can be copied and pasted as a template and finally transformed into core code patterns, just like when you solve problems on LeetCode.
My suggestion is to use the core code form during daily learning, as it is simple and convenient. Before interviews or exams, you can familiarize yourself with the ACM pattern in just an hour or two. Nowcoder has specific practice problems for this ACM pattern, such as this problem list.
Below is an introduction to the techniques for using problem-solving platforms and how these platforms determine the correctness of your solutions.
How to Read Problems
For example, LeetCode Problem 704 "Binary Search":
704. Binary Search | LeetCode | 🟢
Given an array of integers nums
which is sorted in ascending order, and an integer target
, write a function to search target
in nums
. If target
exists, then return its index. Otherwise, return -1
.
You must write an algorithm with O(log n)
runtime complexity.
Example 1:
Input: nums = [-1,0,3,5,9,12], target = 9 Output: 4 Explanation: 9 exists in nums and its index is 4
Example 2:
Input: nums = [-1,0,3,5,9,12], target = 2 Output: -1 Explanation: 2 does not exist in nums so return -1
Constraints:
1 <= nums.length <= 104
-104 < nums[i], target < 104
- All the integers in
nums
are unique. nums
is sorted in ascending order.
The problem provides you with an empty function body, and you need to implement this search
function:
class Solution {
public int search(int[] nums, int target) {
// your code here
}
}
class Solution {
public:
int search(vector<int>& nums, int target) {
// your code here
}
};
class Solution:
def search(self, nums: List[int], target: int) -> int:
# your code here
func search(nums []int, target int) int {
// your code here
}
var search = function(nums, target) {
// your code here
};
The problem states that you are given an ascending sorted array nums
, and you need to return the index of the element target
. If the target is not found, return -1
.
Key Point: Understand the Problem Requirements
Read the problem carefully, considering all the information provided. Many readers focus only on the main question and examples, neglecting additional details about data constraints provided below the problem statement.
For instance, in this problem, there are additional notes about the range of the length of nums
, the range of its elements, and an important point: there are no duplicate elements in nums
.
This is crucial information because if there were multiple identical elements, which index should be returned? Such details are usually given in the supplementary information, so be sure to read them thoroughly.
How to Solve
This problem is intended to test the binary search algorithm, which will be discussed later. However, for now, let's write a straightforward solution:
class Solution {
public int search(int[] nums, int target) {
// traverse the array, return the index if found
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target) {
return i;
}
}
return -1;
}
}
class Solution {
public:
int search(vector<int>& nums, int target) {
// traverse the array, return the index if found
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == target) {
return i;
}
}
return -1;
}
};
class Solution:
def search(self, nums: List[int], target: int) -> int:
# traverse the array, return the index if found
for i in range(len(nums)):
if nums[i] == target:
return i
return -1
func search(nums []int, target int) int {
// traverse the array, return the index if found
for i, num := range nums {
if num == target {
return i
}
}
return -1
}
var search = function(nums, target) {
// traverse the array, return the index if found
for (let i = 0; i < nums.length; i++) {
if (nums[i] === target) {
return i;
}
}
return -1;
};
Copy this code into the LeetCode editor, click the "Run" button to execute the test cases shown at the bottom right, and click the "Submit" button to submit your code to see if it passes all the test cases.
How to Debug
The ideal way is to work directly in the local editor, which allows for setting breakpoints and debugging line by line. Detailed configuration methods can be found in the site's corresponding vscode plugin and Jetbrains IDE plugin.
There are also methods for debugging directly on the webpage. You can write print statements in the editor, and LeetCode will display your output at the bottom right. For simpler bugs, checking the output can help identify issues.
Remember to Remove Print Statements Before Submitting
Print statements involve IO operations, which can affect the execution efficiency of the code. Therefore, remember to remove them before submission to avoid significant performance degradation of the algorithm.
Judging Mechanism
At the time of writing this article, the solution code mentioned above could pass all test cases on LeetCode and could be successfully submitted.
LeetCode has many pre-defined test cases, such as:
Test case: nums = [-1,0,3,5,9,12], target = 9
Expected output: 4
Test case: nums = [-1,0,3,5,9,12], target = 2
Expected output: -1
Test case: nums = [5], target = 5
Expected output: 0
...
These test cases will be passed as parameters to the search
function you implement, and then it checks whether your function's return value matches the expected output.
If they match, the test case passes. If all test cases pass, then your code is considered correct.
The solution code provided above is not the most efficient, but it does produce the correct result and does not exceed the time limits set by the judging platform, so the platform considers it correct.
Taking Shortcuts
Based on this mechanism, the judging platform does not actually inspect your code. Your code acts as a black box; it only cares if the input and output match the expectations.
If the problem has some soft restrictions, such as prohibitions on using long
type or standard libraries, these are merely suggestions without a way to enforce them.
In a written exam, if you encounter such problems, you can ignore these restrictions. As long as your code passes all the test cases, it is deemed correct.
Of course, this is a shortcut. During your learning process, it is best to adhere to the problem constraints. Otherwise, in an interview, you cannot resort to these tricks in front of the interviewer.
Common Errors When Submitting Code
If the submitted code passes all the test cases in the backend, it is considered a successful submission, often referred to as AC (Accepted).
If the submitted code fails to pass all test cases, it is considered a failed submission. Common reasons for failure include:
Compile Error
The code cannot be compiled, usually due to syntax errors like spelling mistakes or missing semicolons. This kind of error often occurs when writing code directly on the webpage. Using our site's accompanying Jetbrains IDE plugin or vscode plugin can help prevent these errors with basic syntax checking features.
Runtime Error
The code compiles successfully but encounters errors like array out-of-bounds or null pointer exceptions during execution. These errors are usually due to improper handling of boundary conditions. Pay attention to boundary conditions and special test cases (also known as corner cases, such as empty input).
Wrong Answer
The code compiles and runs, but the results for some test cases do not match the correct answers. This error is generally due to a problem with your algorithm, requiring you to reflect on the failing test cases, and possibly rethink your entire approach.
Time Limit Exceeded (TLE)
In predefined test cases, the size of the data increases in later cases. If your algorithm's time complexity is too high, it may exceed the system's time limit when running large-scale test cases, leading to a timeout error.
To solve this problem, check the following:
Are there redundant calculations, and is there a more efficient approach to reduce time complexity?
Are there coding errors, such as incorrect boundary control leading to infinite loops, or incorrect passing of values and references causing meaningless data copying?
If you are stuck on large-scale test cases, it generally indicates your algorithm's results are correct since the smaller test cases have passed, but the time complexity needs optimization.
In written exams, scoring is usually based on the number of test cases passed. If you cannot find the optimal solution to pass all test cases, submitting a brute-force solution to pass a few test cases for partial credit is also a clever strategy.
Memory Limit Exceeded (MLE)
Similar to timeout errors, memory limit exceeded errors usually occur when the algorithm's space complexity is too high, using more memory than the system's limit when running large-scale test cases.
To solve this problem, check the following:
Is there unnecessary space allocation, and is there a more efficient approach to reduce space complexity?
Are there mistakes in using value parameters in recursive functions causing meaningless data copying?
Important Notes for Submitting Code on LeetCode
For those new to solving problems on LeetCode, I would like to highlight a few common pitfalls for beginners.
Avoid Using File-Level Global Variables
As previously mentioned, the core code pattern on LeetCode involves providing several predefined test cases as input to your algorithm code and then comparing your output with the expected answer. Therefore, it's crucial to ensure that:
Do not use file-level global variables in your code, as different test cases may interfere with each other. LeetCode's official website also addresses this issue: https://support.leetcode.cn/hc/kb/article/1194344/
Some readers may find that their solution fails when submitted, but works fine when the problematic test case is run individually. This is often due to the aforementioned reason. When you run a single test case, only that case is executed without interference, but when submitted, data from multiple test cases may interfere, causing errors.
In the tutorials on this site, especially those explaining recursive algorithms, I frequently mention "global variables." However, I refer to class-level global variables, not file-level global variables.
To illustrate this with an example, consider the problem of preorder traversal of a binary tree. The problem provides the root node of a binary tree as input and asks you to return the preorder traversal result. You might write it like this:
class Solution {
// correct example, class-level global variable
LinkedList<Integer> res = new LinkedList<>();
public List<Integer> preorderTraversal(TreeNode root) {
traverse(root);
return res;
}
void traverse(TreeNode root) {
if (root == null) {
return;
}
// other functions within the class can access res
res.add(root.val);
traverse(root.left);
traverse(root.right);
}
}
// Incorrect example, do not define global variables at the file level
// vector<int> res;
class Solution {
public:
// Correct example, class-level global variable
vector<int> res;
vector<int> preorderTraversal(TreeNode* root) {
traverse(root);
return res;
}
void traverse(TreeNode* root) {
if (!root) {
return;
}
// Other functions within the class can access res
res.push_back(root->val);
traverse(root->left);
traverse(root->right);
}
};
// Incorrect example, do not define global variables at the file level
// var res []int
func preorderTraversal(root *TreeNode) []int {
// Correct example, use closure to allow the
// traverse function to also access the res
var res []int
var traverse func(*TreeNode)
traverse = func(root *TreeNode) {
if root == nil {
return
}
// Access and modify the res variable
res = append(res, root.Val)
traverse(root.Left)
traverse(root.Right)
}
traverse(root)
return res
}
# Wrong example, do not define global variables at the file level
# res = []
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
# Correct example, class-level global variable
self.res = []
self.traverse(root)
return self.res
def traverse(self, root):
if not root:
return
# Other functions within the class can access res
self.res.append(root.val)
self.traverse(root.left)
self.traverse(root.right)
// Incorrect example, do not define global variables at the file level
// let res = [];
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root) {
// Correct example, use closure to allow the
// traverse function to access the res variable
let res = [];
var traverse = (root) => {
if (!root) {
return;
}
// Access and modify the res variable
res.push(root.val);
traverse(root.left);
traverse(root.right);
}
traverse(root);
return res;
};
For languages like Java/C++/Python, where a Solution
class is provided, you should define the variables that need shared access within the class. For languages like Go/JavaScript, which do not have classes, you can define higher-order functions and use closures to allow inner functions to access shared variables.
Alternatively, you can pass the variable as a function parameter, which is also a viable solution:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
// correct example, passed as an argument to other functions
LinkedList<Integer> res = new LinkedList<>();
traverse(root, res);
return res;
}
void traverse(TreeNode root, LinkedList<Integer> res) {
if (root == null) {
return;
}
// access and modify the res variable
res.add(root.val);
traverse(root.left, res);
traverse(root.right, res);
}
}
// Incorrect example, do not define global variables at the file level
// vector<int> res;
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
// Correct example, pass as a parameter to other functions
vector<int> res;
traverse(root, res);
return res;
}
void traverse(TreeNode* root, vector<int>& res) {
if (!root) {
return;
}
// Access and modify the res variable
res.push_back(root->val);
traverse(root->left, res);
traverse(root->right, res);
}
};
// Wrong example, do not define global variables at the file level
// var res []int
func preorderTraversal(root *TreeNode) []int {
// Correct example, pass as a parameter to other functions
var res []int
traverse(root, &res)
return res
}
func traverse(root *TreeNode, res *[]int) {
if root == nil {
return
}
// Access and modify the res variable
*res = append(*res, root.Val)
traverse(root.Left, res)
traverse(root.Right, res)
}
# Wrong example, do not define global variables at the file level
# res = []
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
# Correct example, pass as a parameter to other functions
res = []
self.traverse(root, res)
return res
def traverse(self, root, res):
if not root:
return
# Access and modify the res variable
res.append(root.val)
self.traverse(root.left, res)
self.traverse(root.right, res)
// Wrong example, do not define global variables at the file level
// let res = [];
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root) {
// Correct example, pass it as a parameter to other functions
let res = [];
traverse(root, res);
return res;
}
var traverse = (root, res) => {
if (!root) {
return;
}
// Access and modify the res variable
res.push(root.val);
traverse(root.left, res);
traverse(root.right, res);
}
Important
For this approach, pay attention to the characteristics of the programming language, specifically whether function parameters should be passed by value or by reference/pointer. Otherwise, efficiency issues or even errors may occur.
For example, in the above case, the traverse
function in C++ must use pass-by-reference for the parameter vector<int>& res
. If pass-by-value vector<int> res
is used, each recursive call will copy a new vector<int>
object, significantly reducing efficiency and potentially causing incorrect results.
Clear Standard Output Before Submission
When running test cases, LeetCode returns the standard output of your algorithm, meaning you can use print statements to debug your code. Note that you must comment out all print statements before submitting your code.
Standard output is an I/O operation, which is very time-consuming. If print statements are included, your code might be the optimal solution, but after submission, it may perform poorly, or even fail due to timeouts.
That's all I'll say about this. Next, I will guide you through some simple problems on LeetCode for practice. Theoretical knowledge can only take you so far; true understanding comes from hands-on experience!