LeetCode Guide
All the example problems and exercises on this site are selected from LeetCode's free public problems. We also provide links to the problems, so you can go to the official website to practice directly.
To help beginners, here is a quick introduction to how the online judge works and some tips for using it.
Core Code Mode
When you solve problems on LeetCode, you are given a function template. You need to write the logic inside this function. This is usually called Core Code Mode.
For example, the first LeetCode problem "Two Sum" asks you to complete a function like twoSum
:
class Solution {
public int[] twoSum(int[] nums, int target) {
}
}
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
}
};
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
func twoSum(nums []int, target int) []int {
}
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
};
This means you are given an array nums
and a target value target
. You need to write the algorithm, and finally return an array.
When you submit your solution, the system will test your twoSum
function with several predefined test cases. It compares your output with the correct answer to check if your code works.
For users, Core Code Mode is the easiest way to practice. You only need to focus on writing the algorithm. Most coding platforms and technical interviews use this mode. But just in case, we will also introduce ACM Mode.
ACM Mode
ACM mode is a bit more complicated. The input is given as a string in a specific format. You need to parse this string yourself and print the result to standard output.
When you submit your code, the system will send each test case to your program as a formatted string. It will then compare your program's output with the expected output to check if your solution is correct.
For example, Nowcoder uses this ACM mode. Here is a screenshot. The code editor has no starter code, so you have to write everything from scratch:

For ACM mode, a useful tip is to separate your code into layers. Handle the input parsing separately from the core algorithm logic, like this:
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// This part handles the input and builds the test case
int[] nums;
int target;
int N = scanner.nextInt();
...
// Call the algorithm function to solve the problem
System.out.println(twoSum(nums, target));
}
public static int[] twoSum(int[] nums, int target) {
// Write your core algorithm here
}
}
This way, your code is clear. You can copy and paste the input handling part as a template. The core logic is the same as what you write on LeetCode.
My suggestion is to use the core code style when learning. It's simple and easy. Before interviews or written tests, spend one or two hours to get used to ACM mode. Nowcoder has special practice problems for ACM mode, such as this problem list.
Next, I will introduce some tips for using problem-solving platforms and explain how these platforms check if your solution is correct.
How to Read the Problem
For example, let's look at LeetCode 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.
You are given an empty function. You need to implement the 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 gives you a sorted array nums
in ascending order. You need to return the index of the element target
in the array. If the target is not found, return -1
.
Key Point: Read the Problem Carefully
Read the problem carefully. Do not miss any information. Many people only look at the main text and example, but ignore extra information like the data range below the problem.
For this problem, the extra info tells you the length of nums
, the range of its elements, and one very important point: there are no duplicate elements in nums
.
This is important. If there were duplicates, which index should you return? Such information is usually given in the extra notes, so make sure you read everything.
How to Solve the Problem
This problem is meant to test the binary search algorithm, which we will talk about later. But here, let's just write a simple 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 test it with the cases at the bottom right. Click "Submit" to see if it passes all test cases.
How to Debug
The best way is to solve problems in your local editor. This allows you to use breakpoints and debug line by line. See the guide for the vscode plugin and Jetbrains IDE plugin.
You can also use print statements when working on the web. LeetCode will show your output at the bottom right. For simple bugs, printing values can help you find the problem.
Remember to Remove Print Statements Before Submitting
Print statements (IO operations) will affect your code's performance. So remember to delete them before submitting. Otherwise, your code will run much slower.
How the Judge Works
At the time of writing, the code above can pass all test cases on LeetCode and can be submitted successfully.
LeetCode has many 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 are passed as input to your search
function. The platform checks if your result matches the expected output.
If it matches, you pass that test case. If you pass all test cases, your code is considered correct.
The solution above is not the most efficient, but it gives the correct results and does not exceed the time limit. So the judge thinks it is correct.
A Little Trick
The judge only checks if your output matches the expected answers. Your code is like a black box, and the platform only cares about input and output.
If a problem says you cannot use long
type, or you cannot use the standard library, usually it is just a suggestion. The platform cannot really stop you.
If you run into these rules in a written test, you can ignore them. As long as your code passes all test cases, it is correct.
But, this is just a trick. When you are learning, you should follow the rules. In an interview, you should not do such things 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?
Notes for Submitting Code on LeetCode
If you are new to solving problems on LeetCode, here are some common mistakes beginners make.
Do Not Use File-Level Global Variables
LeetCode will run your code with several predefined test cases, and check if your return values are correct. So, there is an important rule:
Do not use file-level global variables in your code. Otherwise, data from one test case may affect another. LeetCode also explains this here: https://support.leetcode.cn/hc/kb/article/1194344/
Some readers say their code works for one test case, but fails when they submit. This usually happens because the code only works for a single test case, but when submitted, different test cases share the same global variable, causing errors.
In this tutorial, when I talk about "global variables", I mean class-level variables, not file-level global variables.
Here is an example. For the problem of preorder traversal of a binary tree, you are given the root of a binary tree and asked to return the preorder traversal result. You can write:
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);
}
};
# 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
// var res []int
func preorderTraversal(root *TreeNode) []int {
// Correct example, use closure to allow the
// traverse function to also access the res variable
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
}
// 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 internally
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, which use a Solution
class, you can put shared variables inside the class. For Go/JavaScript, which do not use classes, you can define higher-order functions and use closures to let inner functions access shared variables.
Or, you can pass the variable as a function parameter. This is also a good 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
# 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
// 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
// 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 solution, you must pay attention to how your programming language passes function parameters: by value or by reference/pointer. If you do not handle this correctly, your code may be slow or even wrong.
For example, in the code above, in C++, the traverse
function must use reference parameters like vector<int>& res
. If you use vector<int> res
, each recursion will copy the entire vector, making the code slow and possibly giving the wrong answer.
Remove Print Statements Before Submitting
When running test cases, LeetCode will show your code's standard output. You can use print statements to debug. But, before you submit your code, always comment out all print statements.
Standard output is an IO operation, which is slow. If you keep print statements, your code may be optimal, but it will run slowly or even fail because of timeouts.
That's all for now. Next, I will guide you through some easy LeetCode problems for practice. Reading is not enough—you need to try it yourself!