This section covers medium-difficulty array problems commonly encountered in technical interviews and real-world software engineering. Each problem includes a detailed explanation of the core concept, step-by-step example walkthroughs, Python source code, and time/space complexity analysis.
1. Majority Element (Boyer-Moore Voting Algorithm)¶
Problem Description¶
Given an array nums of size \(N\), find the majority element. The majority element is defined as the element that appears strictly more than \(\lfloor N / 2 \rfloor\) times in the array.
If no majority element exists, return -1.
Intuition & Approach¶
1. Naive / Hash Map Approach¶
A straightforward way to solve this is using a hash map (or dictionary) to count the frequency of each element: - Traverse the array and store element frequencies in a hash map. - Iterate through the hash map to find an element with frequency \(\ge \lfloor N / 2 \rfloor\). - Complexity: Time \(\mathcal{O}(N)\), Space \(\mathcal{O}(N)\).
2. Boyer-Moore Voting Algorithm (\(\mathcal{O}(1)\) Extra Space)¶
The Boyer-Moore Voting Algorithm allows us to find the majority candidate in linear time \(\mathcal{O}(N)\) using only \(\mathcal{O}(1)\) auxiliary space.
Key Insight: If we pair up different elements in the array and cancel them out, the majority element (which appears more than half the time) will always remain at the end.
Algorithm Steps:
1. Candidate Finding Phase:
- Maintain a candidate variable and a count counter initialized to 0.
- Iterate through nums:
- If count == 0, assign candidate = current_element and reset count = 1.
- Else if current_element == candidate, increment count += 1.
- Else, decrement count -= 1.
2. Verification Phase:
- Because a majority element is not guaranteed in every input array, verify the candidate by counting its actual occurrences in nums.
- If count >= len(nums) // 2, return candidate. Otherwise, return -1.
Example Walkthrough¶
Input: nums = [7, 0, 0, 1, 7, 7, 2, 7, 7]
(\(N = 9\), threshold \(\lfloor 9/2 \rfloor = 4\))
Phase 1: Candidate Selection¶
| Step | Element | candidate |
count |
Action |
|---|---|---|---|---|
| 0 | 7 |
7 |
1 |
count was 0 \(\rightarrow\) set candidate = 7, count = 1 |
| 1 | 0 |
7 |
0 |
0 != 7 \(\rightarrow\) count decremented to 0 |
| 2 | 0 |
0 |
1 |
count was 0 \(\rightarrow\) set candidate = 0, count = 1 |
| 3 | 1 |
0 |
0 |
1 != 0 \(\rightarrow\) count decremented to 0 |
| 4 | 7 |
7 |
1 |
count was 0 \(\rightarrow\) set candidate = 7, count = 1 |
| 5 | 7 |
7 |
2 |
7 == 7 \(\rightarrow\) count incremented to 2 |
| 6 | 2 |
7 |
1 |
2 != 7 \(\rightarrow\) count decremented to 1 |
| 7 | 7 |
7 |
2 |
7 == 7 \(\rightarrow\) count incremented to 2 |
| 8 | 7 |
7 |
3 |
7 == 7 \(\rightarrow\) count incremented to 3 |
Result Candidate: 7
Phase 2: Candidate Verification¶
- Count occurrences of
7innums:5times. - Check condition:
5 >= 9 // 2(\(5 \ge 4\)) isTrue. - Output:
7
Python Solution¶
class Solution:
def majority_elements(self, nums: list) -> int:
candidate = -1
count = 0
for index, el in enumerate(nums):
if count == 0:
candidate = el
count = 1
elif candidate == el:
count += 1
else:
count -= 1
count = 0
for el in nums:
if el == candidate: count += 1
if count >= len(nums) // 2:
return candidate
return -1
# main function for the program
def main():
sol = Solution()
nums = [7, 0, 0, 1, 7, 7, 2, 7, 7]
print(sol.majority_elements(nums))
# driver code for the program
if __name__ == '__main__':
main()
Complexity Analysis¶
- Time Complexity: \(\mathcal{O}(N)\)
- Phase 1 takes \(\mathcal{O}(N)\) time for candidate selection.
- Phase 2 takes \(\mathcal{O}(N)\) time for verification.
-
Total time complexity is \(2 \times \mathcal{O}(N) = \mathcal{O}(N)\).
-
Space Complexity: \(\mathcal{O}(1)\)
- Operates in-place using constant auxiliary variables (
candidateandcount).
2. Leaders in an Array¶
Problem Description¶
Given an array nums, find all the leader elements in the array. An element is considered a leader if it is strictly greater than all elements present to its right.
Note: The rightmost element is always considered a leader because there are no elements to its right.
Intuition & Approach¶
1. Naive Nested Loop (\(\mathcal{O}(N^2)\))¶
For each element at index i, loop through all elements from i + 1 to N - 1 to check if nums[i] is greater than every element to its right.
2. Optimal Right-to-Left Scan (\(\mathcal{O}(N)\))¶
Instead of scanning left-to-right, scan the array from right to left:
1. Keep track of the maximum element seen so far from the right end, stored in right_most.
2. The last element nums[-1] is always a leader. Initialize right_most = nums[-1] and append nums[-1] to ans.
3. Loop backwards from len(nums) - 2 down to 0:
- If nums[index] > right_most: We found a new leader! Update right_most = nums[index] and append it to ans.
4. Reverse ans (ans[::-1]) to return the leaders in their original left-to-right order.
Example Walkthrough¶
Input: nums = [1, 2, 5, 3, 1, 2]
| Step | Index i |
Value nums[i] |
right_most |
Comparison | Action | ans state |
|---|---|---|---|---|---|---|
| Initial | 5 |
2 |
2 |
— | nums[-1] is leader |
[2] |
| 1 | 4 |
1 |
2 |
1 > 2 (False) |
Skip | [2] |
| 2 | 3 |
3 |
2 |
3 > 2 (True) |
Leader! right_most = 3 |
[2, 3] |
| 3 | 2 |
5 |
3 |
5 > 3 (True) |
Leader! right_most = 5 |
[2, 3, 5] |
| 4 | 1 |
2 |
5 |
2 > 5 (False) |
Skip | [2, 3, 5] |
| 5 | 0 |
1 |
5 |
1 > 5 (False) |
Skip | [2, 3, 5] |
Reversing ans \(\rightarrow\) [5, 3, 2]
Output: [5, 3, 2]
Python Solution¶
class Solution:
def leaders(self, nums: list) -> list:
ans = []
if len(nums) == 0: return ans
ans.append(nums[-1])
right_most = nums[-1]
index = len(nums) - 2
while index >= 0:
if nums[index] > right_most:
right_most = nums[index]
ans.append(right_most)
index -= 1
return ans[::-1]
# main function for the program
def main():
sol = Solution()
nums = [1, 2, 5, 3, 1, 2]
print(sol.leaders(nums))
# driver code for the program
if __name__ == '__main__':
main()
Complexity Analysis¶
- Time Complexity: \(\mathcal{O}(N)\)
- Backward scan visits each of the \(N\) elements once.
- Reversing the resulting
anslist takes \(\mathcal{O}(K)\) time (where \(K \le N\)). -
Total time complexity is \(\mathcal{O}(N)\).
-
Space Complexity: \(\mathcal{O}(1)\) auxiliary space (excluding the output array
ans).
3. Rearrange Array Elements by Sign¶
Problem Description¶
Given a 0-indexed integer array nums of even length containing an equal number of positive and negative integers, rearrange the elements of nums such that:
- Every consecutive pair of integers has alternating signs.
- For all integers with the same sign, the relative order in which they appeared in
numsis preserved. - The rearranged array begins with a positive integer (positive elements at even indices
0, 2, 4..., negative elements at odd indices1, 3, 5...).
Intuition & Two-Pointer Placement Approach¶
Since positive integers must end up at even indices (0, 2, 4...) and negative integers at odd indices (1, 3, 5...), we can populate a new result array ans of length \(N\) in a single pass:
- Initialize
pos_index = 0(points to the next available even index). - Initialize
neg_index = 1(points to the next available odd index). - Iterate through
nums: - If
el > 0: Assignans[pos_index] = eland advancepos_index += 2. - If
el < 0: Assignans[neg_index] = eland advanceneg_index += 2. - Return
ans.
Example Walkthrough¶
Input: nums = [2, 4, 5, -1, -3, -4]
| Step | Element el |
Sign | Placed at ans[idx] |
pos_index |
neg_index |
Result Array ans |
|---|---|---|---|---|---|---|
| 0 | 2 |
Positive | ans[0] = 2 |
2 |
1 |
[2, 0, 0, 0, 0, 0] |
| 1 | 4 |
Positive | ans[2] = 4 |
4 |
1 |
[2, 0, 4, 0, 0, 0] |
| 2 | 5 |
Positive | ans[4] = 5 |
6 |
1 |
[2, 0, 4, 0, 5, 0] |
| 3 | -1 |
Negative | ans[1] = -1 |
6 |
3 |
[2, -1, 4, 0, 5, 0] |
| 4 | -3 |
Negative | ans[3] = -3 |
6 |
5 |
[2, -1, 4, -3, 5, 0] |
| 5 | -4 |
Negative | ans[5] = -4 |
6 |
7 |
[2, -1, 4, -3, 5, -4] |
Output: [2, -1, 4, -3, 5, -4]
Python Solution¶
class Solution:
def rearrange_array(self, nums: list) -> list:
ans = [0] * len(nums)
pos_index, neg_index = 0, 1
for index, el in enumerate(nums):
if el < 0:
ans[neg_index] = el
neg_index += 2
else:
ans[pos_index] = el
pos_index += 2
return ans
# main function for the program
def main():
sol = Solution()
nums = [2, 4, 5, -1, -3, -4]
print(sol.rearrange_array(nums))
# driver code for the program
if __name__ == '__main__':
main()
Complexity Analysis¶
- Time Complexity: \(\mathcal{O}(N)\) — Linear scan traversing the \(N\) elements once.
- Space Complexity: \(\mathcal{O}(N)\) — Memory allocated for the output array
ansof size \(N\).
4. Sum and Target Based Problems (Two Sum & Three Sum)¶
Problem Description¶
- Two Sum: Given an array
numsand atarget, return the 0-based indices of the two numbers that sum totarget. - Two Sum Exists: Given an array
numsand atarget, returnTrueif any pair sums totarget, elseFalse. - Three Sum: Given an integer array
nums, return all unique triplets[nums[i], nums[j], nums[k]]such that \(i \neq j \neq k\) and \(\text{nums}[i] + \text{nums}[j] + \text{nums}[k] = 0\).
Intuition & Approach¶
Two Sum (Hash Map Approach)¶
Instead of a nested loop (\(\mathcal{O}(N^2)\)), use a hash map to record visited elements and their indices:
- For each element el at index, calculate complement required = target - el.
- If required exists in hash_map, return [hash_map[required], index].
- Otherwise, store hash_map[el] = index.
Three Sum (Sorting + Two Pointers)¶
- Sort
numsin non-decreasing order (\(\mathcal{O}(N \log N)\)). - Iterate
ifrom0tolen(nums) - 3: - Skip duplicate values for
nums[i]to avoid duplicate triplets. - Set two pointers:
left = i + 1,right = len(nums) - 1. - While
left < right:- Calculate
s = nums[i] + nums[left] + nums[right]. - If
s == 0: Found a triplet! Append[nums[i], nums[left], nums[right]], then incrementleftand decrementrightwhile skipping duplicate values. - Else if
s < 0: Incrementleft += 1to increase the sum. - Else (
s > 0): Decrementright -= 1to decrease the sum.
- Calculate
Example Walkthrough (Two Sum)¶
Input: nums = [1, 6, 2, 10, 3], target = 7
| Step | index |
el |
target - el |
In hash_map? |
Action | hash_map State |
|---|---|---|---|---|---|---|
| 0 | 0 |
1 |
6 |
No | Store hash_map[1] = 0 |
{1: 0} |
| 1 | 1 |
6 |
1 |
Yes! (index 0) | Return [0, 1] |
{1: 0} |
Output: [0, 1]
Python Solution¶
class Solution:
def two_sum(self, nums: list, target: int) -> list:
ans = [-1, -1]
hash_map = {}
for index, el in enumerate(nums):
if (target - el) in hash_map:
return [hash_map[target - el], index]
hash_map[el] = index
return ans
def two_sum_exists(self, nums: list, target: int) -> bool:
s = set()
for el in nums:
if target - el in s:
return True
s.add(el)
return False
def three_sum(self, nums: list) -> list:
nums.sort()
ans = []
n = len(nums)
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i - 1]:
continue
left, right = i + 1, n - 1
while left < right:
s = nums[i] + nums[left] + nums[right]
if s == 0:
ans.append([nums[i], nums[left], nums[right]])
left += 1
right -= 1
while left < right and nums[left] == nums[left - 1]:
left += 1
while right > left and nums[right] == nums[right + 1]:
right -= 1
elif s < 0:
left += 1
else:
right -= 1
return ans
# main function for the program
def main():
sol = Solution()
nums = [1, 6, 2, 10, 3]
target = 7
print(sol.two_sum(nums, target))
# driver code for the program
if __name__ == '__main__':
main()
Complexity Analysis¶
- Two Sum:
- Time Complexity: \(\mathcal{O}(N)\) — Single pass using \(\mathcal{O}(1)\) average hash map lookup.
-
Space Complexity: \(\mathcal{O}(N)\) — Space stored in
hash_map. -
Three Sum:
- Time Complexity: \(\mathcal{O}(N^2)\) — Sorting takes \(\mathcal{O}(N \log N)\) and the nested two-pointer scan takes \(\mathcal{O}(N^2)\).
- Space Complexity: \(\mathcal{O}(1)\) auxiliary space (excluding output list).
5. Pascal's Triangle Problems¶
Problem Description¶
Pascal's Triangle is a triangular array of numbers where each entry is the sum of the two numbers directly above it.
- Variation 1 (
pascal_triangle_one): Given 1-indexed rowrand columnc, return the value at element \((r, c)\). - Variation 2 (
pascal_triangle_two): Given 1-indexed rowr, return all values in the \(r\)-th row. - Variation 3 (
pascal_triangle_three): Given integern, return the firstnrows of Pascal's Triangle.
Intuition & Mathematical Formula¶
Any element at row \(r\) and column \(c\) (1-indexed) in Pascal's Triangle corresponds to the mathematical combination: $\(\text{Element}(r, c) = \binom{r - 1}{c - 1} = \frac{(r - 1)!}{(c - 1)! \times (r - c)!}\)$
Approach¶
- Compute factorials \(\text{fact}(n) = n!\).
- Compute combination \(\binom{r}{c} = \frac{r!}{c! \times (r - c)!}\).
- Use combination helper to calculate individual positions or build row vectors.
Example Walkthrough¶
Pascal's Triangle Structure:
Row 1: 1
Row 2: 1 1
Row 3: 1 2 1
Row 4: 1 3 3 1
pascal_triangle_one(4, 2)\(\rightarrow \binom{4-1}{2-1} = \binom{3}{1} = 3\)pascal_triangle_two(4)\(\rightarrow \left[\binom{3}{0}, \binom{3}{1}, \binom{3}{2}, \binom{3}{3}\right] = [1, 3, 3, 1]\)pascal_triangle_three(4)\(\rightarrow [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]\)
Python Solution¶
class Solution:
def __factorial__(self, num: int) -> int:
fact = 1
for i in range(1, num + 1):
fact = fact * i
return fact
def __combination__(self, r: int, c: int) -> int:
return (
self.__factorial__(r) // (self.__factorial__(c) * self.__factorial__(r - c))
)
"""
Given two integers r and c.
Return the value at the rth row and cth column (1-indexed) in a Pascal's Triangle.
"""
def pascal_triangle_one(self, r: int, c: int) -> int:
return self.__combination__(r - 1, c - 1)
"""
Given an integer r, return all the values in the rth row (1-indexed) in Pascal's Triangle in correct order.
"""
def pascal_triangle_two(self, r: int) -> list:
ans = []
for i in range(r):
ans.append(self.__combination__(r - 1, i))
return ans
"""
Given an integer n, return the first n (1-Indexed) rows of Pascal's triangle.
"""
def pascal_triangle_three(self, n: int) -> list:
ans = []
if n == 0: return []
if n == 1: return [1]
if n == 2: return [[1], [1,1]]
for i in range(1, n + 1):
out = []
for j in range(i):
out.append(self.__combination__(i - 1, j))
ans.append(out)
return ans
# main function for the program
def main():
sol = Solution()
r = 4
c = 2
print(sol.pascal_triangle_one(r, c))
print(sol.pascal_triangle_two(4))
print(sol.pascal_triangle_three(4))
# driver code for the program
if __name__ == '__main__':
main()
Complexity Analysis¶
- Time Complexity:
pascal_triangle_one: \(\mathcal{O}(r)\) to compute factorials.pascal_triangle_two: \(\mathcal{O}(r^2)\) computing combinations for row \(r\).-
pascal_triangle_three: \(\mathcal{O}(n^3)\) computing combinations for each entry up to row \(n\). -
Space Complexity: \(\mathcal{O}(1)\) auxiliary space (excluding result lists).
6. Sort Zeroes, Ones, and Twos (Dutch National Flag Algorithm)¶
Problem Description¶
Given an array nums containing only 0s, 1s, and 2s, sort the array in-place so that all 0s come first, followed by all 1s, and then all 2s.
You must solve this problem in-place without using built-in sort functions.
Intuition & Dutch National Flag Algorithm¶
The Dutch National Flag Algorithm (formulated by Edsger Dijkstra) uses 3 pointers (left, mid, right) to partition the array into 4 zones in a single pass:
[0 ... left-1] --> All 0s
[left ... mid-1] --> All 1s
[mid ... right] --> Unprocessed / Unknown elements
[right+1 ... N-1]--> All 2s
Algorithm Steps:¶
- Initialize
left = 0,mid = 0,right = len(nums) - 1. - Loop while
mid <= right: - If
nums[mid] == 0: Swapnums[left]andnums[mid]. Incrementleft += 1andmid += 1. - If
nums[mid] == 1: Element is already in correct middle zone. Incrementmid += 1. - If
nums[mid] == 2: Swapnums[mid]andnums[right]. Decrementright -= 1(do not incrementmidyet, as the swapped element fromrightneeds inspection).
Example Walkthrough¶
Input: nums = [1, 0, 2, 1, 0]
| Step | left |
mid |
right |
nums[mid] |
Action | Array State |
|---|---|---|---|---|---|---|
| Initial | 0 |
0 |
4 |
1 |
nums[mid] == 1 \(\rightarrow\) mid += 1 |
[1, 0, 2, 1, 0] |
| 1 | 0 |
1 |
4 |
0 |
nums[mid] == 0 \(\rightarrow\) Swap nums[0], nums[1], left=1, mid=2 |
[0, 1, 2, 1, 0] |
| 2 | 1 |
2 |
4 |
2 |
nums[mid] == 2 \(\rightarrow\) Swap nums[2], nums[4], right=3 |
[0, 1, 0, 1, 2] |
| 3 | 1 |
2 |
3 |
0 |
nums[mid] == 0 \(\rightarrow\) Swap nums[1], nums[2], left=2, mid=3 |
[0, 0, 1, 1, 2] |
| 4 | 2 |
3 |
3 |
1 |
nums[mid] == 1 \(\rightarrow\) mid += 1 (mid=4) |
[0, 0, 1, 1, 2] |
mid = 4 > right = 3 \(\rightarrow\) Loop terminates.
Output: [0, 0, 1, 1, 2]
Python Solution¶
class Solution:
def sort_zero_one_two(self, nums: list) -> None:
left, right = 0, len(nums) - 1
mid = 0
while mid <= right:
if nums[mid] == 0:
nums[left], nums[mid] = nums[mid], nums[left]
left += 1
mid += 1
elif nums[mid] == 2:
nums[mid], nums[right] = nums[right], nums[mid]
right -= 1
else:
mid += 1
# main function for the program
def main():
sol = Solution()
nums = [1, 0, 2, 1, 0]
sol.sort_zero_one_two(nums)
print(nums)
# driver code for the program
if __name__ == '__main__':
main()
7. Maximum Subarray Sum (Kadane's Algorithm)¶
Problem Description¶
Given an integer array nums, find the contiguous subarray (containing at least one element) which has the largest sum and return its maximum sum.
Intuition & Kadane's Algorithm¶
1. Naive / Brute Force Approach (\(\mathcal{O}(N^2)\))¶
Check all possible contiguous subarrays nums[i...j] using nested loops, compute their sums, and record the maximum.
2. Kadane's Algorithm (\(\mathcal{O}(N)\) Single Pass)¶
Kadane's Algorithm dynamic programming approach optimizes this to linear time:
- Key Insight: If a running sum of a contiguous subarray becomes negative, carrying it forward into future elements will only reduce their total sum. Therefore, whenever the cumulative sum
cont_sumdrops below0, we resetcont_sum = 0to start a fresh candidate subarray from the next element.
Algorithm Steps:
1. Initialize max_sum = -float("inf") (to handle all-negative arrays correctly) and cont_sum = 0.
2. Iterate through each element el in nums:
- Add el to cont_sum: cont_sum += el.
- Update max_sum: max_sum = max(max_sum, cont_sum).
- If cont_sum < 0: Reset cont_sum = 0 (discard negative prefix).
3. Return max_sum.
Example Walkthrough¶
Input: nums = [-2, -3, -7, -2, -10, -4]
(All-negative array case)
| Step | Index | Element el |
cont_sum (before reset) |
max_sum state |
cont_sum < 0 Action |
cont_sum (after) |
|---|---|---|---|---|---|---|
| 0 | 0 |
-2 |
-2 |
max(-inf, -2) = -2 |
-2 < 0 \(\rightarrow\) Reset |
0 |
| 1 | 1 |
-3 |
-3 |
max(-2, -3) = -2 |
-3 < 0 \(\rightarrow\) Reset |
0 |
| 2 | 2 |
-7 |
-7 |
max(-2, -7) = -2 |
-7 < 0 \(\rightarrow\) Reset |
0 |
| 3 | 3 |
-2 |
-2 |
max(-2, -2) = -2 |
-2 < 0 \(\rightarrow\) Reset |
0 |
| 4 | 4 |
-10 |
-10 |
max(-2, -10) = -2 |
-10 < 0 \(\rightarrow\) Reset |
0 |
| 5 | 5 |
-4 |
-4 |
max(-2, -4) = -2 |
-4 < 0 \(\rightarrow\) Reset |
0 |
Output: -2
Python Solution¶
class Solution:
def max_sub_array(self, nums: list) -> int:
max_sum = -float("inf")
cont_sum = 0
for index, el in enumerate(nums):
cont_sum += el
max_sum = max(max_sum, cont_sum)
if cont_sum < 0:
cont_sum = 0
return max_sum
# main function for the program
def main():
sol = Solution()
nums = [-2, -3, -7, -2, -10, -4]
print(sol.max_sub_array(nums))
# driver code for the program
if __name__ == '__main__':
main()
Complexity Analysis¶
- Time Complexity: \(\mathcal{O}(N)\) — Single pass linear scan traversing the \(N\) elements once.
- Space Complexity: \(\mathcal{O}(1)\) — Constant auxiliary space using scalar variables (
max_sumandcont_sum).