10 Classic Coding Challenges
10 Classic Coding Challenges
Release 1.2
Contents
3 Single Number 11
4 Majority Element 15
6 Valid Parentheses 25
9 Spiral Matrix II 39
10 Unique Paths 44
11 Thank you! 51
Index 52
1
1 Maximum Depth of Binary Tree
Example 1
Example 2
Constraints
2
1.2 Solution
You have the following recursive relationship between the root and its children.
Code
#include <iostream>
using namespace std;
struct TreeNode
{
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr)
{
}
TreeNode(int x) : val(x), left(nullptr), right(nullptr)
{
}
TreeNode(int x, TreeNode *left, TreeNode *right)
: val(x), left(left), right(right)
{
}
};
3
(continued from previous page)
TreeNode three(3, &nine, &twenty);
cout << maxDepth(&three) << endl;
TreeNode two(2);
TreeNode one(1, nullptr, &two);
cout << maxDepth(&one) << endl;
}
Output:
3
2
Code explanation
1. At the beginning of the function, there is a base case check. This condition
checks if the current node, represented by root, is a null pointer. If it is, it
means that the tree is empty (or a subtree is empty), and the depth at this
point is zero. So, the function returns 0 to indicate that the depth of the current
subtree is zero.
2. If the current node is not null (i.e., it’s a valid node in the tree), the code
proceeds to calculate the maximum depth of the left and right subtrees.
3. maxDepth(root->left) and maxDepth(root->right) are recursive function calls
that calculate the maximum depth of the left and right subtrees, respectively.
4. The std::max function is used to find the maximum depth among the left and
right subtrees. Adding 1 to this maximum depth represents the depth of the
current node in the tree.
5. This recursive calculation repeats for each node in the tree until it reaches the
base case of a null node, and the recursion unwinds, returning depth values for
each subtree.
6. Ultimately, the function returns the maximum depth of the entire binary tree
rooted at the given root node.
4
Complexity
This solution uses a recursive algorithm to calculate the maximum depth of a binary
tree. It starts from the root node and recursively calculates the maximum depth of
the left and right subtrees, returning the maximum depth among them. This process
continues until it reaches the leaf nodes of the tree.
• Runtime: O(n), where n is the number of nodes.
• Extra space: O(h), where h is the height of the tree.
You are given an integer array height of length n. There are n vertical lines drawn
such that the two endpoints of the i-th line are (i, 0) and (i, height[i]).
Find two lines that together with the x-axis form a container, such that the container
contains the most water.
Return the maximum amount of water a container can store.
Notice that you may not slant the container.
Example 1
5
(continued from previous page)
˓→5,4,8,3,7]. In this case, the max area of water (blue section) the␣
˓→container can contain is 49.
Example 2
Constraints
• n == height.length.
• 2 <= n <= 10^5.
• 0 <= height[i] <= 10^4.
For each line i, find the line j > i such that it gives the maximum amount of water
the container (i, j) can store.
Code
#include <iostream>
#include <vector>
using namespace std;
int maxArea(vector<int>& height)
{
int maxA = 0;
for (int i = 0; i < height.size() - 1; i++)
{
for (int j = i + 1; j < height.size(); j++)
{
maxA = max(maxA, min(height[i], height[j]) * (j - i));
}
}
return maxA;
(continues on next page)
6
(continued from previous page)
}
int main()
{
vector<int> height{1,8,6,2,5,4,8,3,7};
cout << maxArea(height) << endl;
height = {1,1};
cout << maxArea(height) << endl;
}
Output:
49
1
Code explanation
1. The code initializes a variable maxA to keep track of the maximum area found.
Initially, maxA is set to 0.
2. The solution employs two nested loops to iterate through pairs of vertical lines.
The outer loop (i) iterates from the first vertical line to the second-to-last ver-
tical line (up to height.size() - 1), and the inner loop (j) iterates from the
second vertical line to the last vertical line.
3. For each pair of vertical lines at indices i and j, it calculates the area formed
by these lines. The area is determined by taking the minimum height between
height[i] and height[j] (as the shorter line limits the height of the rectangle)
and multiplying it by the distance between the lines, which is (j - i).
4. In each iteration of the inner loop, it updates maxA to store the maximum area
encountered so far by comparing it with the calculated area. This ensures that
maxA always holds the maximum area among all possible pairs of vertical lines.
5. After both loops have completed, the function returns the final value of maxA,
which represents the maximum area that can be enclosed by two vertical lines
from the height vector.
7
Complexity
This solution has a time complexity of O(n^2), where n is the number of elements
in the height vector. This is because it checks all possible pairs of vertical lines,
resulting in a quadratic time complexity. The space complexity is O(1), as it only uses
a constant amount of extra space to store the maxA variable.
• Runtime: O(n^2), where n = height.length.
• Extra space: O(1).
Any container has left line i and right line j satisfying 0 <= i < j < height.length.
The biggest container you want to find satisfies that condition too.
You can start from the broadest container with the left line i = 0 and the right line j
= height.length - 1. Then by moving i forward and j backward, you can narrow
down the container to find which one will give the maximum amount of water it can
store.
Depending on which line is higher, you can decide which one to move next. Since
you want a bigger container, you should move the shorter line.
Example 1
maxArea = 8.
8
• So on and so on. Final maxArea = 49.
Code
#include <iostream>
#include <vector>
using namespace std;
int maxArea(vector<int>& height)
{
int maxA = 0;
int i = 0;
int j = height.size() - 1;
while (i < j)
{
if (height[i] < height[j])
{
maxA = max(maxA, height[i] * (j - i) );
i++;
}
else
{
maxA = max(maxA, height[j] * (j - i) );
j--;
}
}
return maxA;
}
int main()
{
vector<int> height{1,8,6,2,5,4,8,3,7};
cout << maxArea(height) << endl;
height = {1,1};
cout << maxArea(height) << endl;
}
Output:
49
1
9
Code explanation
1. The code initializes a variable maxA to keep track of the maximum area found.
Initially, maxA is set to 0.
2. Two pointers, i and j, are initialized at the beginning and end of the height
vector, respectively. These pointers represent the left and right vertical lines
currently under consideration.
3. While i is less than j, the algorithm continues to evaluate pairs of vertical lines.
This is because moving the pointers closer together will reduce the width of the
rectangle and can potentially increase the height of the lines.
4. In each iteration, the algorithm compares the heights of the lines at indices i
and j. It calculates the area formed by these lines, considering the height of
the shorter line (as the shorter line limits the height of the rectangle) and the
width between the lines, which is (j - i).
5. The algorithm updates maxA to store the maximum area encountered so far by
comparing it with the calculated area. This ensures that maxA always holds the
maximum area achievable with the current positions of the pointers.
6. Depending on the comparison of heights at indices i and j, one of the pointers
(i or j) is moved towards the other pointer. The pointer corresponding to the
shorter line is moved because moving it may lead to a higher line in the future,
potentially increasing the area.
7. The loop continues until i is no longer less than j, meaning that all possible
pairs of vertical lines have been evaluated.
8. After the loop completes, the function returns the final value of maxA, which
represents the maximum area that can be enclosed by two vertical lines from
the height vector.
Complexity
This solution has a time complexity of O(n), where n is the number of elements in the
height vector. It iterates through the elements once using the two-pointer approach,
resulting in a linear time complexity. The space complexity is O(1), as it only uses a
constant amount of extra space to store the maxA, i, and j variables.
• Runtime: O(n), where n = height.length.
• Extra space: O(1).
10
3 Single Number
You’re provided with a non-empty array of integers called nums. In this array, every
element occurs twice except for one element that appears only once. Your task is to
identify and find that unique element.
To solve this problem, your solution needs to have a linear runtime complexity and
utilize only a constant amount of extra space.
Example 1
Example 2
Example 3
Constraints
11
3.2 Solution 1: Counting the appearances
Count how many times each element appears in the array. Then return the one
appearing only once.
Code
#include <vector>
#include <iostream>
#include <unordered_map>
using namespace std;
int singleNumber(vector<int>& nums)
{
unordered_map<int, int> count;
for (int n : nums) {
count[n]++;
}
int single;
for (auto& pair : count)
{
if (pair.second == 1)
{
single = pair.first;
break;
}
}
return single;
}
int main()
{
vector<int> nums{2,2,1};
cout << singleNumber(nums) << endl;
nums = {4,1,2,1,2};
cout << singleNumber(nums) << endl;
nums = {1};
cout << singleNumber(nums) << endl;
}
Output:
1
(continues on next page)
12
(continued from previous page)
4
1
Code explanation
1. The code initializes an unordered map count where the keys are integers from
nums, and the values represent the count of each integer’s occurrence in the
nums array.
2. It then iterates through the nums array. For each element n in nums, it increments
the count for that element in the count map.
3. Next, it initializes an integer variable single which will store the single number
we are looking for.
4. The loop goes through the count map. For each key-value pair in the map it
checks if the count (the value in the pair) is equal to 1. If so, it means this
is the number that appears only once, so it assigns that number to the single
variable and breaks out of the loop.
5. Finally, it returns the single variable, which contains the single number that
appears only once in the nums array.
Complexity
This solution effectively finds the single number by counting the occurrences of each
element in the array and selecting the one with a count of 1. It has a time complexity
of O(N) because it iterates through the nums array once, and it uses additional memory
to store the counts in the unordered map, resulting in a space complexity of O(N) in
the worst case, where N is the number of elements in the nums array.
• Runtime: O(N).
• Extra space: O(N).
13
3.3 Solution 2: Bitwise exclusive OR
You can also use the bitwise XOR operator to cancel out the duplicated elements in
the array. The remain element is the single one.
a XOR a = 0.
a XOR 0 = a.
Code
#include <vector>
#include <iostream>
using namespace std;
int singleNumber(vector<int>& nums)
{
int single = 0;
for (int n : nums)
{
single ^= n;
}
return single;
}
int main()
{
vector<int> nums{2,2,1};
cout << singleNumber(nums) << endl;
nums = {4,1,2,1,2};
cout << singleNumber(nums) << endl;
nums = {1};
cout << singleNumber(nums) << endl;
}
Output:
1
4
1
14
Code explanation
1. The code initializes an integer variable single to 0. This variable will be used
to keep track of the single number we are looking for.
2. The loop then iterates through the nums array. For each element n in nums, it
performs the XOR (^) operation on single and n. The XOR operation returns 1 if
the bits being compared are different and 0 if they are the same.
3. The key insight here is that XORing the same number twice results in 0 (A ^
A = 0), so when XORing all elements in the array, the numbers that appear
twice will effectively cancel each other out, leaving only the single number that
appears once in single.
4. After iterating through all elements in nums, single will hold the value of the
single number.
5. Finally, it returns the single variable, which contains the single number that
appears only once in the nums array.
Complexity
This solution is highly efficient and has a time complexity of O(N), where N is the
number of elements in the nums array, because it iterates through the array once.
Additionally, it has a space complexity of O(1) because it uses a constant amount of
extra space to store the single variable, regardless of the size of the input array.
• Runtime: O(N).
• Extra space: O(1).
4 Majority Element
You’re given an array nums with a total of n elements. Your task is to find and return
the majority element.
The majority element is the element that appears more frequently in the array than
any other element, specifically, it appears more than n / 2 times.
You can assume that the majority element always exists in the given array.
4
https://leetcode.com/problems/majority-element/
15
Example 1
Example 2
Constraints
• n == nums.length.
• 1 <= n <= 5 * 10^4.
• -2^31 <= nums[i] <= 2^31 - 1.
Follow-up:
Could you solve the problem in linear time and in O(1) space?
Code
#include <vector>
#include <iostream>
#include <unordered_map>
using namespace std;
int majorityElement(vector<int>& nums)
{
unordered_map<int,int> freq;
const int HALF = nums.size() / 2;
for (int a : nums)
{
freq[a]++;
if (freq[a] > HALF)
(continues on next page)
16
(continued from previous page)
{
return a;
}
}
return nums[0];
}
int main()
{
vector<int> nums{3,2,3};
cout << majorityElement(nums) << endl;
nums = {2,2,1,1,1,2,2};
cout << majorityElement(nums) << endl;
}
Output:
3
2
Code explanation
1. The code initializes an unordered map freq to keep track of the frequency of
each unique integer in the nums array. Also, it defines a constant HALF as half
the size of the nums array. This value represents the threshold for an element to
be considered a majority element.
2. It iterates through the nums array using a for-each loop, processing each integer
a:
• It increments the frequency count of integer a in the freq map by 1.
• It checks if the frequency count of a in the freq map is greater than HALF.
If it is, this means a appears more than n/2 times, and it returns a as the
majority element.
3. If the loop completes without finding a majority element, it returns the first el-
ement of the nums array (assuming there is always a majority element present).
The code effectively counts the occurrences of each integer in the array and checks if
any integer appears more than n/2 times. If so, it returns that integer as the majority
element; otherwise, it defaults to the first element of the array.
17
Complexity
Code
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int majorityElement(vector<int>& nums)
{
sort(nums.begin(), nums.end());
return nums[nums.size()/2];
}
int main()
{
vector<int> nums{3,2,3};
cout << majorityElement(nums) << endl;
nums = {2,2,1,1,1,2,2};
cout << majorityElement(nums) << endl;
}
Output:
3
2
Code explanation
1. The code sorts the nums vector in ascending order using the sort function. This
step rearranges the elements so that identical elements are adjacent to each
other.
2. After sorting, the majority element (if it exists) will be in the middle of the
sorted vector because it appears more than n/2 times, where n is the size of the
vector.
18
3. It returns the element at the middle index of the sorted vector, which is
nums[nums.size() / 2]. This element is the majority element.
Complexity
This code leverages the property of a majority element, which guarantees that it
occupies the middle position in the sorted list of elements. Sorting the array allows
us to easily access this middle element.
• Runtime: O(n*logn), where n = nums.length.
• Extra space: O(1).
Since you are interested in only the middle element after sorting, the partial sorting
algorithm std::nth_element5 can be used in this case to reduce the cost of the full
sorting.
Code
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int majorityElement(vector<int>& nums)
{
const int mid = nums.size() / 2;
nth_element(nums.begin(), nums.begin() + mid, nums.end());
return nums[mid];
}
int main()
{
vector<int> nums{3,2,3};
cout << majorityElement(nums) << endl; // 3
nums = {2,2,1,1,1,2,2};
cout << majorityElement(nums) << endl; // 2
}
5
https://en.cppreference.com/w/cpp/algorithm/nth_element
19
Output:
3
2
Code explanation
1. The code calculates the index mid, which represents the approximate middle
index of the nums vector. This is done by dividing the size of the vector by 2.
2. It uses the std::nth_element function to rearrange the elements in the nums
vector such that the element at index mid will be in its correct sorted position,
and all elements before it will be less than or equal to it, while all elements
after it will be greater than or equal to it.
3. It returns the element at index mid. This element is the majority element.
Complexity
By using std::nth_element, the code avoids fully sorting the entire vector, which
makes it more efficient.
• Runtime: O(n), where n = nums.length.
• Extra space: O(1).
In other words, nums[mid] divides the array nums into two groups: all elements that
are less than or equal to nums[mid] and the ones that are greater than or equal to
nums[mid].
Those two groups are unsorted. That is why the algorithm is called partial sorting.
6
https://en.cppreference.com/w/cpp/algorithm/nth_element
20
5 Excel Sheet Column Number
Given a string columnTitle that represents the column title as appears in an Excel
sheet, return its corresponding column number.
For example:
A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28
...
Example 1
Example 2
Example 3
21
Constraints
Let us write down some other columnTitle strings and its value.
"A" = 1
"Z" = 26
"AA" = 27
"AZ" = 52
"ZZ" = 702
"AAA" = 703
"A" = 1 = 1
"Z" = 26 = 26
"AA" = 27 = 26 + 1
"AZ" = 52 = 26 + 26
"ZZ" = 702 = 26*26 + 26
"AAA" = 703 = 26*26 + 26 + 1
If you map 'A' = 1, ..., 'Z' = 26, the values can be rewritten as
"A" = 1 = 'A'
"Z" = 26 = 'Z'
"AA" = 27 = 26*'A' + 'A'
"AZ" = 52 = 26*'A' + 'Z'
"ZZ" = 702 = 26*'Z' + 'Z'
"AAA" = 703 = 26*26*'A' + 26*'A' + 'A'
22
Code
#include <iostream>
using namespace std;
int titleToNumber(string columnTitle)
{
int column = 0;
for (char c : columnTitle)
{
// The ASCII value of 'A' is 65.
column = 26*column + (c - 64);
}
return column;
}
int main()
{
cout << titleToNumber("A") << endl;
cout << titleToNumber("AB") << endl;
cout << titleToNumber("ZY") << endl;
}
Output:
1
28
701
Code explanation
1. The code initializes an integer variable column to store the column number.
This variable starts at 0 and will be updated as we process each character in
columnTitle.
2. It iterates through each character c in the columnTitle string using a range-
based for loop.
3. Inside the loop, it calculates the numeric value associated with the current
character c. This is done using the formula (c - 64). Here’s why:
• The ASCII value of 'A' is 65.
• To convert 'A' to 1, 'B' to 2, and so on, you need to subtract 64 from the
ASCII value of the character.
23
4. The code multiplies the current value of column by 26 and then adds the cal-
culated numeric value of the character. This step effectively accumulates the
values for each character from left to right.
5. Once the loop finishes, the column variable will contain the numeric represen-
tation of the entire column title.
6. The code returns the column value, which represents the corresponding column
number.
This algorithm works because Excel column titles follow a base-26 numbering sys-
tem, where 'A' corresponds to 1, 'B' to 2, . . . , 'Z' to 26, 'AA' to 27, 'AB' to 28, and
so on.
Complexity
The code efficiently calculates the decimal representation of the Excel column title by
processing each character and updating the result. It has a time complexity of O(N),
where N is the length of columnTitle, as it iterates through the characters once, and
a space complexity of O(1) since it uses a constant amount of extra space.
• Runtime: O(N), where N = columnTitle.length.
• Extra space: O(1).
Implementation notes
If you write it as
26*(26*(26*(0 + a) + b) + c) + d,
24
6 Valid Parentheses
You are given a string s containing only the characters '(', ')', '{', '}', '[', and
']'. Your task is to check if the input string is valid.
A string is considered valid if the following conditions are satisfied:
1. Opening brackets must be closed by the same type of brackets.
2. Opening brackets must be closed in the correct order, meaning that the inner-
most opening bracket should be closed before its surrounding brackets.
Example 1
Input: s = "()"
Output: true
Example 2
Input: s = "()[]{}"
Output: true
Example 3
Input: s = "(]"
Output: false
Constraints
25
6.2 Solution: Using a stack
Code
#include <iostream>
#include <stack>
using namespace std;
bool isValid(string s)
{
stack<char> stk;
for (char c : s)
{
if (c == '(' || c == '[' || c == '{')
{
stk.push(c);
}
else if (stk.empty())
{
return false;
}
else if (c == ')' && stk.top() != '('
|| c == ']' && stk.top() != '['
|| c == '}' && stk.top() != '{')
{
return false;
}
else
{
stk.pop();
}
(continues on next page)
26
(continued from previous page)
}
return stk.empty();
}
int main()
{
cout << isValid("()") << endl;
cout << isValid("(){}[]") << endl;
cout << isValid("(]") << endl;
cout << isValid("([)]") << endl;
}
Output:
1
1
0
0
Code explanation
1. The code uses a stack data structure, represented by stack<char> stk, to keep
track of the opening brackets as they are encountered.
2. The code iterates through each character in the input string s.
• If the current character c is an opening bracket ('(', '[', or '{'), the code
pushes it onto the stack stk. This indicates that an opening bracket has
been encountered and needs to be matched later with a closing bracket.
• If the current character c is a closing bracket (')', ']', or '}'), the code
checks the stack for a matching opening bracket.
• If the stack is empty, meaning there is no corresponding opening bracket,
the code immediately returns false, as this is an invalid sequence.
• If the stack is not empty, the code compares the current closing bracket
with the top element of the stack (stk.top()) to check if they match. If
they match, the opening bracket is popped from the stack because it has
been successfully matched with a closing bracket.
• If the current closing bracket does not match the top element of the stack,
the code returns false because the sequence is invalid.
3. After processing all characters in the input string, the code checks if the stack
27
is empty. If the stack is empty, it means that all opening brackets have been
successfully matched with closing brackets, and the input string is considered
valid. If the stack is not empty, there are unmatched opening brackets remain-
ing, and the input string is considered invalid.
Complexity:
This solution efficiently checks the validity of a string of parentheses, brackets, and
curly braces by using a stack to ensure that each opening bracket is correctly matched
with its corresponding closing bracket.
• Runtime: O(N), where N = s.length.
• Extra space: O(N).
Given an integer array nums, return an array answer such that answer[i] is equal to
the product of all the elements of nums except nums[i].
The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.
You must write an algorithm that runs in O(n) time and without using the division
operation.
Example 1
28
Example 2
Constraints
To avoid division operation, you can compute the prefix product and the suffix one
of nums[i].
Code
#include <vector>
#include <iostream>
using namespace std;
vector<int> productExceptSelf(vector<int>& nums)
{
const int n = nums.size();
vector<int> prefix(n);
prefix[0] = 1;
for (int i = 1; i < n; i++)
{
prefix[i] = prefix[i - 1] * nums[i - 1];
}
vector<int> suffix(n);
suffix[n - 1] = 1;
for (int i = n - 2; i >= 0; i--)
{
suffix[i] = suffix[i + 1] * nums[i + 1];
(continues on next page)
29
(continued from previous page)
}
vector<int> answer(n);
for (int i = 0; i < n; i++)
{
answer[i] = prefix[i] * suffix[i];
}
return answer;
}
void print(vector<int>& nums)
{
for (auto& v : nums)
{
cout << v << " ";
}
cout << endl;
}
int main()
{
vector<int> nums = {1, 2, 3, 4};
auto answer = productExceptSelf(nums);
print(answer);
nums = {-1, 1, 0, -3, 3};
answer = productExceptSelf(nums);
print(answer);
}
Output:
24 12 8 6
0 0 9 0 0
Code explanation
1. The code stores the length of the input vector nums in the variable n.
2. It initializes an empty vector called prefix of length n. The prefix vector will
store the product of all elements to the left of the current element in nums.
3. It initializes the first element of the prefix vector at index 0 to be 1. Since
there are no elements to the left of the first element, its product is defined as 1.
4. For each index i in the loop, it calculates the product of all elements to the
30
left of i in nums and stores it in the prefix vector at index i. This is done by
multiplying the previous element in the prefix vector with the corresponding
element in nums.
5. The code initializes another empty vector called suffix of the same length n.
The suffix vector will store the product of all elements to the right of the
current element in nums.
6. It initializes the last element of the suffix vector at index n - 1 to be 1. Since
there are no elements to the right of the last element, its product is defined as
1.
7. The code loops through nums in reverse order, starting from index n - 2 and go-
ing down to index 0. For each index i, it calculates the product of all elements
to the right of i in nums and store it in the suffix vector at index i.
8. It initializes an empty vector called answer of the same length n. This vector
will store the final result.
9. For each index i in the loop, it calculates the final answer at that index by
multiplying the corresponding elements from the prefix and suffix vectors
and store it in the answer vector.
10. The code returns the answer vector, which contains the desired products of all
elements in nums except for the element at each respective index.
Complexity
The time complexity of this code is O(n) because it iterates through the nums vector
three times in separate loops, and each loop has a linear time complexity with respect
to the length of nums. The space complexity is O(n) as well because it uses additional
vectors (prefix and suffix) of the same length as nums.
• Runtime: O(n), where n = nums.length.
• Extra space: O(n).
7.3 Solution 2: Use directly vector answer to store the prefix product
In the solution above you can use directly vector answer for prefix and merge the
last two loops into one.
31
Code
#include <vector>
#include <iostream>
using namespace std;
vector<int> productExceptSelf(vector<int>& nums)
{
const int n = nums.size();
vector<int> answer(n);
answer[0] = 1;
for (int i = 1; i < n; i++)
{
answer[i] = answer[i - 1] * nums[i - 1];
}
int suffix = 1;
for (int i = n - 2; i >= 0; i--)
{
suffix *= nums[i + 1];
answer[i] *= suffix;
}
return answer;
}
void print(vector<int>& nums)
{
for (auto& v : nums)
{
cout << v << " ";
}
cout << endl;
}
int main()
{
vector<int> nums = {1, 2, 3, 4};
auto answer = productExceptSelf(nums);
print(answer);
nums = {-1, 1, 0, -3, 3};
answer = productExceptSelf(nums);
print(answer);
}
Output:
(continues on next page)
32
(continued from previous page)
24 12 8 6
0 0 9 0 0
Code explanation
1. An integer variable n is set to the size (number of elements) of the input vector
nums.
2. The code creates a new integer vector called answer of the same size as nums.
This vector will store the final results.
3. It initializes the first element of the answer vector at index 0 to be 1. This is
because the product of all elements to the left of the first element is defined as
1.
4. For each element at index i in the loop, it calculates the product of all elements
to the left of it and store it in the answer vector at index i. This is done by
multiplying the previous element in the answer vector with the corresponding
element in nums.
5. It initializes an integer variable suffix to 1. This variable will be used to keep
track of the product of all elements to the right of the current element in nums.
6. For each element at index i in the reverse-order loop, it calculates the product
of all elements to the right of it and multiply it with the corresponding element
in the answer vector at index i. It updates the suffix by multiplying it with the
next element to the right in nums.
7. The code returns the answer vector, which now contains the desired products
of all elements in nums except for the element at each respective index.
Complexity
This code efficiently calculates the products of all elements in the nums vector except
for the element at each index using two passes through the array. The first pass calcu-
lates products to the left of each element, and the second pass calculates products to
the right of each element. The time complexity is O(n), and the space complexity is
O(1) as it only uses a single additional integer (suffix) and reuses the answer vector
to store the final results.
• Runtime: O(n), where n = nums.length.
• Extra space: O(1).
33
8 Find the Duplicate Number
You have an array of integers called nums that contains n + 1 integers. Each integer
in the array falls within the range [1, n] inclusive.
Within this array, there is only one number that appears more than once. Your task
is to find and return this repeated number.
Importantly, you must solve this problem without making any modifications to the
original array nums, and you are only allowed to use a constant amount of extra
space.
Example 1
Example 2
Constraints
34
8.2 Solution 1: Sorting
Code
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int findDuplicate(vector<int>& nums)
{
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 1; i++)
{
if (nums[i] == nums[i + 1])
{
return nums[i];
}
}
return 0;
}
int main()
{
vector<int> nums{1,3,4,2,2};
cout << findDuplicate(nums) << endl;
nums = {3,1,3,4,2};
cout << findDuplicate(nums) << endl;
}
Output:
2
3
Code explanation
35
4. If no duplicate is found during the loop, it returns 0 as a default value.
Complexity
The code relies on sorting to bring duplicate elements together, making it easy to
identify them during the linear pass. The time complexity is O(n*logn) due to the
sorting operation.
• Runtime: O(n*logn).
• Extra space: O(1).
8.3 Follow up
How can we prove that at least one duplicate number must exist in nums?
Code
#include <vector>
#include <iostream>
using namespace std;
int findDuplicate(vector<int>& nums)
{
vector<bool> visited(nums.size());
for (int a : nums)
{
if (visited.at(a))
(continues on next page)
11
https://en.wikipedia.org/wiki/Pigeonhole_principle
36
(continued from previous page)
{
return a;
}
visited[a] = true;
}
return 0;
}
int main()
{
vector<int> nums{1,3,4,2,2};
cout << findDuplicate(nums) << endl;
nums = {3,1,3,4,2};
cout << findDuplicate(nums) << endl;
}
Output:
2
3
Code explanation
1. The code creates a Boolean vector visited with the same size as the nums vector
to keep track of visited elements.
2. For each element a in the nums vector, it checks if the corresponding index in
the visited vector is already marked as true. If it is, it returns a as it is the
duplicate.
3. If the element is not marked as visited, it sets the corresponding index in the
visited vector to true to mark it as visited.
4. If no duplicates are found during the loop, it returns 0 as a default value.
Complexity
• Runtime: O(n).
• Extra space: much less than O(n). std::vector12 is optimized for space-efficient.
12
https://en.cppreference.com/w/cpp/container/vector_bool
37
8.5 Solution 3: Marking with std::bitset
Since n <= 10^5, you can use this size for a std::bitset13 to do the marking.
Code
#include <vector>
#include <iostream>
#include <bitset>
using namespace std;
int findDuplicate(vector<int>& nums)
{
bitset<100001> visited;
for (int a : nums)
{
if (visited[a])
{
return a;
}
visited[a] = 1;
}
return 0;
}
int main()
{
vector<int> nums{1,3,4,2,2};
cout << findDuplicate(nums) << endl;
nums = {3,1,3,4,2};
cout << findDuplicate(nums) << endl;
}
Output:
2
3
13
https://en.cppreference.com/w/cpp/utility/bitset
38
Code explanation
1. The code creates a bitset named visited with a size of 100001, which is large
enough to accommodate the possible values in the nums vector.
2. For each element a in nums, it checks if the corresponding bit in the visited
bitset is set (1). If it is, the code returns a as it is the duplicate.
3. If the bit is not set, the code sets the corresponding bit in the visited bitset
to 1 to mark it as visited.
4. If no duplicates are found during the loop, it returns 0 as a default value.
Complexity
This code efficiently uses a bitset to keep track of visited elements and quickly detects
any duplicate element encountered during the iteration. The time complexity is O(n)
because it makes a single pass through the input vector.
• Runtime: O(n).
• Extra space: O(1).
9 Spiral Matrix II
Example 1
14
https://leetcode.com/problems/spiral-matrix-ii/
39
Input: n = 3
Output: [[1,2,3],[8,9,4],[7,6,5]]
Example 2
Input: n = 1
Output: [[1]]
Constraints
9.2 Solution
Code
#include <vector>
#include <iostream>
using namespace std;
enum Direction {RIGHT, DOWN, LEFT, UP};
vector<vector<int>> generateMatrix(int n)
{
vector<vector<int>> m(n, vector<int>(n));
int bottom = n - 1;
int right = n - 1;
int top = 0;
int left = 0;
int row = 0;
int col = 0;
Direction d = RIGHT;
int a = 1;
while (top <= bottom && left <= right)
(continues on next page)
40
(continued from previous page)
{
m[row][col] = a++;
switch (d)
{
case RIGHT:
if (col == right)
{
top++;
d = DOWN;
row++;
}
else
{
col++;
}
break;
case DOWN:
if (row == bottom)
{
right--;
d = LEFT;
col--;
}
else
{
row++;
}
break;
case LEFT:
if (col == left)
{
bottom--;
d = UP;
row--;
}
else
{
col--;
}
break;
41
(continued from previous page)
case UP:
if (row == top)
{
left++;
d = RIGHT;
col++;
}
else
{
row--;
}
break;
}
}
return m;
}
void printResult(vector<vector<int>>& m)
{
cout << "[";
for (auto& r : m)
{
cout << "[";
for (int a : r)
{
cout << a << ",";
}
cout << "]";
}
cout << "]\n";
}
int main()
{
auto m = generateMatrix(3);
printResult(m);
m = generateMatrix(1);
printResult(m);
}
Output:
[[1,2,3,][8,9,4,][7,6,5,]]
[[1,]]
42
Code explanation
Complexity
43
10 Unique Paths
A robot starts at the top-left corner of a grid with dimensions m x n. It can move
either down or right at each step. The robot’s goal is to reach the bottom-right corner
of the grid.
The problem is to determine the number of unique paths the robot can take to reach
the bottom-right corner.
Example 1
Input: m = 3, n = 7
Output: 28
Example 2
Input: m = 3, n = 2
Output: 3
Explanation:
From the top-left corner, there are a total of 3 ways to reach the␣
˓→bottom-right corner:
44
Example 3
Input: m = 7, n = 3
Output: 28
Example 4
Input: m = 3, n = 3
Output: 6
Constraints
At each point, the robot has two ways of moving: right or down. Let P(m,n) is the
wanted result. Then you have a recursive relationship:
If the grid has only one row or only one column, then there is only one possible path.
P(1, n) = P(m, 1) = 1.
Code
#include <iostream>
#include <vector>
using namespace std;
int uniquePaths(int m, int n)
{
if (m == 1 || n == 1)
{
(continues on next page)
45
(continued from previous page)
return 1;
}
return uniquePaths(m - 1, n) + uniquePaths(m, n - 1);
}
int main()
{
std::cout << uniquePaths(3,7) << std::endl;
std::cout << uniquePaths(7,3) << std::endl;
std::cout << uniquePaths(3,2) << std::endl;
std::cout << uniquePaths(3,3) << std::endl;
}
Output:
28
28
3
6
Code explanation
This recursive solution calculates the number of unique paths from the top-left corner
of an m x n grid to the bottom-right corner.
• The base cases are when either m or n is equal to 1, in which case there is
only one path (either moving only horizontally or only vertically) to reach the
destination.
• For all other cases, the function recursively calculates the number of unique
paths by summing two possibilities:
1. Moving one step down (decreasing m by 1) and staying at the same column
(n remains the same).
2. Moving one step right (decreasing n by 1) and staying at the same row (m
remains the same).
By adding these two possibilities, you account for all the valid paths to reach the
destination.
46
Complexity
This is a recursive solution that breaks down the problem into two subproblems:
• uniquePaths(m-1, n)
• uniquePaths(m, n-1)
Each recursive call reduces either the m or n value by 1.
The base case is when m == 1 or n == 1, where there is only 1 unique path.
To calculate the complexity, let’s look at the recursion tree:
• uniquePaths(m,n) calls:
– uniquePaths(m-1,n)
– uniquePaths(m, n-1)
• Each of those calls two more calls and so on.
The height of the tree will be max(m,n). At each level, there are 2 branches.
So the total number of nodes in the tree will be 2^max(m,n).
Since each node represents a function call, the runtime complexity is O(2^max(m,n)).
The space complexity is O(max(m,n)) due to the call stack.
In summary, the complexities are:
• Time complexity: O(2^max(m,n))
• Space complexity: O(max(m,n))
• Runtime: O(2^max(m,n), where m x n is the size of the grid.
• Extra space: O(max(m,n).
47
Code
#include <iostream>
#include <vector>
using namespace std;
int uniquePaths(int m, int n)
{
vector<vector<int> > dp(m, vector<int>(n,1));
for (int i = 1; i < m; i++)
{
for (int j = 1; j < n; j++)
{
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
int main()
{
std::cout << uniquePaths(3,7) << std::endl;
std::cout << uniquePaths(7,3) << std::endl;
std::cout << uniquePaths(3,2) << std::endl;
std::cout << uniquePaths(3,3) << std::endl;
}
Output:
28
28
3
6
Code explanation
48
and the cell to the left of it, as these are the only two possible directions to reach the
current cell.
Finally, the value at dp[m-1][n-1] contains the total number of unique paths to reach
the bottom-right corner of the grid, which is returned as the result.
Complexity
The time complexity of this solution is O(m*n), as it iterates through all cells of the
grid once to compute the unique paths efficiently, making it a much more efficient
approach compared to the recursive solution.
• Runtime: O(m*n), where m x n is the size of the grid.
• Extra space: O(m*n).
You can rephrase the relationship inside the loop like this:
“new value” = “old value” + “previous value”;
Then you do not have to store all values of all rows.
Code
#include <iostream>
#include <vector>
using namespace std;
int uniquePaths(int m, int n)
{
vector<int> dp(n, 1);
for (int i = 1; i < m; i++)
{
for (int j = 1; j < n; j++)
{
dp[j] += dp[j - 1];
}
}
return dp[n - 1];
}
int main()
(continues on next page)
49
(continued from previous page)
{
std::cout << uniquePaths(3,7) << std::endl;
std::cout << uniquePaths(7,3) << std::endl;
std::cout << uniquePaths(3,2) << std::endl;
std::cout << uniquePaths(3,3) << std::endl;
}
Output:
28
28
3
6
Code explanation
This solution efficiently computes the number of unique paths in an m x n grid using
dynamic programming. It uses a 1D vector dp of size n to store the number of unique
paths for each column.
First, it initializes all elements of dp to 1, as there’s exactly one way to reach any cell
in the first row or first column.
Then, it iterates through the grid, starting from the second row and second column
(i.e., indices (1, 1)). For each cell, it updates the value in dp by adding the value
from the cell directly above it and the value from the cell to the left of it. This step
efficiently accumulates the number of unique paths to reach the current cell.
Finally, the value at dp[n-1] contains the total number of unique paths to reach the
bottom-right corner of the grid, which is returned as the result.
Complexity
The time complexity of this solution is O(m*n), where m and n are the dimensions of
the grid, as it iterates through all cells of the grid once to compute the unique paths
efficiently. It’s an optimized version of the previous solution that uses a 1D array
instead of a 2D array.
• Runtime O(m*n).
• Memory O(n).
50
10.5 Final thought
I am wondering if there is some mathematics behind this problem. Please share your
finding if you find a formula for the solution to this problem.
11 Thank you!
Thank you for taking the time to read this book. I hope it has been a valuable
experience and that you are excited to continue your coding journey. Best of
luck with your coding challenges, and remember to have fun along the way!
Nhut Nguyen is a seasoned software engineer and career coach with nearly a decade
of experience in the tech industry.
He was born and grew up in Ho Chi Minh City, Vietnam. In 2012, he moved to
Denmark for a Ph.D. in mathematics at the Technical University of Denmark. After
his study, Nhut Nguyen switched to the industry in 2016 and has worked at various
tech companies in Copenhagen, Denmark.
Nhut’s passion for helping aspiring software engineers succeed led him to write sev-
eral articles and books where he shares his expertise and practical strategies to help
readers navigate their dream job and work efficiently.
With a strong background in mathematics and computer science and a deep under-
standing of the industry, Nhut is dedicated to empowering individuals to unlock their
full potential, land their dream jobs and live happy lives.
Learn more at https://nhutnguyen.com.
51
Index
A
ASCII, 23
B
binary tree, 2
bitwise XOR, 14
bool, 37
D
Dynamic programming, 47
P
Partial sort, 19
R
recursive, 3, 45
S
Sorting, 35
stack, 26
std::bitset, 38
std::nth_element, 20
switch, 43
T
Two pointers, 8
U
unordered map, 13
52