Python Interview Questions - Part 1
Python Interview Questions - Part 1
QUESTIONS
YOE: 0-3
ROLE : Python Developer
Data Analyst
Data Scientist
Python has a built-in max() function that conveniently returns the largest item in an iterable.
Python
def find_largest_max_function(arr):
"""
Finds the largest element in an array using the built-in max() function.
Args:
Returns:
if not arr:
return max(arr)
# Example Usage:
my_list3 = [7]
empty_list = []
This method involves initializing a variable with the first element of the array (or negative
infinity for robustness) and then iterating through the rest of the array, updating the variable
if a larger element is found.
Python
def find_largest_loop(arr):
"""
Returns:
"""
if not arr:
largest = element
return largest
# Example Usage:
my_list3 = [7]
empty_list = []
Similar to max(), Python has a built-in min() function that directly returns the smallest item
in an iterable.
Python
def find_smallest_min_function(arr):
"""
Finds the smallest element in an array using the built-in min() function.
Args:
Returns:
"""
if not arr:
return min(arr)
# Example Usage:
my_list3 = [7]
empty_list = []
print(f"Using min() function: Smallest element in {my_list1} is
{find_smallest_min_function(my_list1)}")
This method involves initializing a variable with the first element of the array (or positive
infinity for robustness) and then iterating through the rest of the array, updating the variable
if a smaller element is found.
Python
def find_smallest_loop(arr):
"""
Args:
Returns:
"""
if not arr:
# Alternatively, for robustness with any possible numbers (even very large ones):
smallest = element
return smallest
# Example Usage:
my_list3 = [7]
empty_list = []
This is the simplest and most Pythonic way. Python's built-in min() and max() functions are
highly optimized and efficient.
Python
def find_min_max_builtin(arr):
"""
Finds the smallest and largest elements in an array using built-in functions.
Args:
Returns:
"""
if not arr:
# Example Usage:
my_list3 = [7]
empty_list = []
This method involves initializing both min_val and max_val and then iterating through the
array once, updating both as needed. This approach is generally more efficient than calling
min() and max() separately if you need to implement it manually, as it only requires a single
pass over the data.
Python
def find_min_max_single_pass(arr):
"""
Args:
Returns:
"""
if not arr:
min_val = arr[0]
max_val = arr[0]
# Iterate from the second element
min_val = arr[i]
max_val = arr[i]
# Example Usage:
my_list3 = [7]
empty_list = []
The simplest approach is to sort the array and then pick the element at the second index
(index 1). This works well, but sorting has a time complexity of O(nlogn).
Python
def find_second_smallest_sorted(arr):
"""
Args:
Returns:
"""
unique_sorted_arr = sorted(list(set(arr)))
if len(unique_sorted_arr) < 2:
else:
return unique_sorted_arr[1]
# Example Usage:
list1 = [10, 3, 20, 5, 15]
list2 = [1, 2, 3, 4, 5]
list4 = [7, 7, 7]
list5 = [8]
empty_list = []
This method iterates through the array only once, keeping track of the smallest and second
smallest elements found so far. This approach has a time complexity of O(n), which is more
efficient than sorting for larger arrays.
Python
import math
def find_second_smallest_single_pass(arr):
"""
Args:
"""
smallest = math.inf
second_smallest = math.inf
for x in arr:
if x < smallest:
smallest = x
elif x < second_smallest and x != smallest: # x is not smallest, but smaller than current
second_smallest
second_smallest = x
if second_smallest == math.inf:
else:
return second_smallest
# Example Usage:
list1 = [10, 3, 20, 5, 15]
list2 = [1, 2, 3, 4, 5]
list4 = [7, 7, 7]
list5 = [8]
empty_list = []
list6 = [1, 1, 2, 3]
Python provides a built-in sum() function that is designed specifically for this purpose. It's
the most concise and often the most efficient way to sum elements in a list.
Python
def calculate_sum_builtin(arr):
"""
Calculates the sum of elements in an array using the built-in sum() function.
Args:
Returns:
"""
return sum(arr)
# Example Usage:
list1 = [1, 2, 3, 4, 5]
list3 = [7]
empty_list = []
This method involves initializing a variable to 0 and then iterating through each element in
the array, adding it to the running total.
Python
def calculate_sum_loop(arr):
"""
Args:
Returns:
"""
total = 0
total += element
return total
# Example Usage:
list1 = [1, 2, 3, 4, 5]
list3 = [7]
empty_list = []
This is the most Pythonic and often the most concise way to reverse a list. It creates a new
reversed list without modifying the original.
Python
def reverse_array_slicing(arr):
"""
Args:
Returns:
"""
return arr[::-1]
# Example Usage:
list1 = [1, 2, 3, 4, 5]
list3 = [10]
empty_list = []
Python lists have a built-in reverse() method that reverses the list in-place, meaning it
modifies the original list and doesn't return a new one.
Python
def reverse_array_in_place(arr):
"""
Reverses an array in-place using the reverse() method. Modifies the original array.
Args:
"""
arr.reverse()
# Example Usage:
list1_inplace = [1, 2, 3, 4, 5]
list3_inplace = [10]
empty_list_inplace = []
reverse_array_in_place(list1_inplace)
reverse_array_in_place(list3_inplace)
reverse_array_in_place(empty_list_inplace)
You can manually reverse a list by iterating through it and appending elements to a new list
in reverse order, or by swapping elements from both ends towards the center.
Python
def reverse_array_loop_new_list(arr):
"""
Args:
Returns:
"""
reversed_arr = []
for i in range(len(arr) - 1, -1, -1):
reversed_arr.append(arr[i])
return reversed_arr
# Example Usage:
list1_loop_new = [1, 2, 3, 4, 5]
Python
def reverse_array_loop_swap(arr):
"""
Args:
"""
left = 0
right = len(arr) - 1
left += 1
right -= 1
# Example Usage:
list1_loop_swap = [1, 2, 3, 4, 5]
list3_loop_swap = [100]
empty_list_loop_swap = []
reverse_array_loop_swap(list1_loop_swap)
reverse_array_loop_swap(list2_loop_swap)
reverse_array_loop_swap(list3_loop_swap)
reverse_array_loop_swap(empty_list_loop_swap)
Here's a Python function that accomplishes this, along with explanations and examples:
Python
def sort_halves(arr):
"""
Sorts the first half of an array in ascending order and the second half in descending order.
Args:
Returns:
A new list with the first half sorted ascending and the second half descending,
"""
n = len(arr)
if n < 2:
return "Array must contain at least two elements to have distinct halves."
first_half = arr[:mid]
second_half = arr[mid:]
# Sort the first half in ascending order
first_half.sort()
second_half.sort(reverse=True)
# Example Usage:
list2 = [1, 2, 3, 4, 5, 6]
list4 = [1, 2]
empty_list = []
# Expected: [1, 2, 3, 6, 5, 4]
# Expected: [5, 6, 7, 9, 8]
print(f"Original: {list4} -> Sorted Halves: {sort_halves(list4)}")
# Expected: [1, 2]
Explanation:
o The function first checks if the array has fewer than 2 elements. If so, it's not
meaningful to talk about "halves," so it returns an appropriate message.
2. Determine Midpoint:
▪ If n is odd (e.g., 5 elements), mid will be 2. The first half will be arr[0:2]
(elements at indices 0, 1) and the second half will be arr[2:] (elements
at indices 2, 3, 4). The second half will have one more element than
the first. This is a standard way to handle "halves" in programming
when the total is odd.
o return first_half + second_half: Concatenates the two sorted lists and returns
the new combined list.
Python lists have a built-in sort() method that sorts the list in-place. This means it modifies
the original list and does not return a new one.
Python
"""
Args:
ascending: If True (default), sorts in ascending order. If False, sorts in descending order.
"""
arr.sort(reverse=not ascending)
# Example Usage:
list3_inplace = [7]
empty_list_inplace = []
sort_array_in_place(list1_inplace_asc)
sort_array_in_place(list2_inplace_desc, ascending=False)
sort_array_in_place(list3_inplace)
sort_array_in_place(empty_list_inplace)
Python also provides a built-in sorted() function. This function takes an iterable (like a list)
and returns a new sorted list, leaving the original list unchanged.
Python
"""
Sorts an array and returns a new sorted list. Does not modify the original array.
Args:
arr: The input list.
ascending: If True (default), sorts in ascending order. If False, sorts in descending order.
Returns:
"""
# Example Usage:
list3_new = [7]
empty_list_new = []
sorted_list1_asc = sort_array_new_list(list1_new_asc)
sorted_list3 = sort_array_new_list(list3_new)
print(f"New sorted (single element) list: {sorted_list3}\n")
sorted_empty_list = sort_array_new_list(empty_list_new)
• sort() method:
o Returns None.
o Generally slightly more efficient for large lists if you don't need the original
order, as it avoids creating a new list.
• sorted() function:
o Can be used on any iterable (tuples, strings, sets, etc.), not just lists.
o Useful when you need to preserve the original list or when sorting non-list
iterables.
Both sort() and sorted() use an optimized Timsort algorithm, which is highly efficient for
various data distributions.
This method involves iterating through the array and using a dictionary to store the counts
of each element. The elements of the array will be the keys in the dictionary, and their
frequencies will be the values.
Python
def find_frequency_loop(arr):
"""
Finds the frequency of each element in an array using a loop and a dictionary.
Args:
Returns:
A dictionary where keys are the elements and values are their frequencies.
"""
frequency = {}
if item in frequency:
frequency[item] += 1
else:
frequency[item] = 1
return frequency
# Example Usage:
list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]
empty_list = []
Explanation:
2. Iterate through the array: For each item in the input arr.
o If item is not yet a key, it's the first time we're seeing this element, so we add
it to the dictionary with a count of 1 (frequency[item] = 1).
The collections module in Python provides a Counter class that is specifically designed for
counting hashable objects. This is the most Pythonic, concise, and efficient way to find
frequencies.
Python
def find_frequency_counter(arr):
"""
Args:
"""
return Counter(arr)
# Example Usage:
list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]
empty_list = []
counts_list1 = find_frequency_counter(list1)
Explanation:
• If you are in a learning environment and want to understand the underlying logic of
how frequency counting works, implementing it with a loop and a dictionary is a
great exercise.
Sorting elements of an array by frequency means arranging them such that elements
appearing more often come before elements appearing less often. If two elements have
the same frequency, their relative order might depend on a secondary sorting criterion
(e.g., by their value, or by their original appearance order).
We'll use collections.Counter to efficiently get frequencies and then the sorted() function
with a custom key.
Python
def sort_by_frequency(arr):
"""
For elements with the same frequency, they are sorted by their value in ascending order.
Args:
Returns:
"""
if not arr:
return []
frequency_map = Counter(arr)
# (-frequency_map[x], x)
# - The element 'x' itself serves as the tie-breaker, sorting in ascending order.
# sorted() is stable, but for this specific tie-breaker, it's not strictly necessary.
# We use the 'key' argument of the sorted() function to provide our custom sorting logic.
sorted_arr = sorted(arr, key=lambda x: (-frequency_map[x], x))
return sorted_arr
# Example Usage:
list3 = [1, 1, 1, 2, 2, 3]
empty_list = []
# Correct output: [3, 3, 3, 3, 2, 2, 2, 12, 12, 4, 5] (3 (4x), 2 (3x), 12 (2x), 4 (1x), 5 (1x))
# Correct output: [1, 1, 2, 2, 3, 4] (1 and 2 same freq, sorted by value. 3 and 4 same freq,
sorted by value)
Explanation:
2. frequency_map = Counter(arr): This line is the most efficient way to get the
frequency of each element. It takes the input list arr and returns a Counter object
(e.g., Counter({2: 3, 3: 4, 12: 2, 4: 1, 5: 1}) for list1).
o sorted(): This is a built-in Python function that returns a new sorted list (it
does not modify the original arr).
o (-frequency_map[x], x): This is the core of the sorting logic. For each element
x from the original arr, we create a tuple:
▪ x: This is the element itself. If two elements have the same (negative)
frequency, Python moves to the second element of the tuple for
comparison. This means that if frequencies are tied, the elements will
then be sorted by their own value in ascending order.
This approach provides a robust and Pythonic solution for sorting by frequency.
3. Sorting this list of tuples based on frequency (descending) and then element value
(ascending).
4. Constructing the final sorted array by repeating each element according to its sorted
frequency.
Python
def sort_by_frequency_manual_dict(arr):
"""
Sorts the elements of an array by their frequency using manual dictionary counting.
For elements with the same frequency, they are sorted by their value in ascending order.
Args:
Returns:
"""
if not arr:
return []
frequency_map = {}
items_with_freq = list(frequency_map.items())
# This makes it similar to the lambda key used with Counter, but applied to (item, freq)
pairs.
sorted_arr = []
return sorted_arr
# Example Usage:
list3 = [1, 1, 1, 2, 2, 3]
empty_list = []
list_mixed_ties = [1, 2, 3, 1, 2, 4]
• Explicit Steps: This method explicitly shows the steps of counting, creating pairs,
sorting pairs, and then rebuilding the list.
This alternative method is valuable for understanding the mechanics behind frequency
sorting, even if collections.Counter is the preferred practical choice in Python.
1. Define what constitutes a "palindrome" for a number (it reads the same forwards
and backward).
3. Iterate through the array, check each number, and keep track of the longest
palindrome found so far.
Python
def is_palindrome(n):
"""
A number is a palindrome if its string representation reads the same forwards and
backward.
Handles both positive and negative integers (though typically palindromes refer to non-
negative).
"""
s = str(n)
return s == s[::-1]
def find_longest_palindrome_in_array(arr):
"""
Finds the numerically largest palindrome among the longest palindromes in an array.
Args:
Returns:
The longest palindrome (by number of digits). If multiple palindromes have the same
maximum length,
"""
if is_palindrome(num):
max_length = current_length
longest_palindrome = num
return longest_palindrome
# Example Usage:
print(f"\nTest Case 8: [0, 10, 00, 101]") # Note: 00 might be interpreted as 0 in Python lists,
which is a palindrome.
Explanation:
1. is_palindrome(n) function:
o Converts the integer to a string (str(n)). This is the easiest way to check for
palindromes with numbers.
o Compares the string with its reversed version (s[::-1]). Slicing with [::-1]
efficiently reverses a string.
2. find_longest_palindrome_in_array(arr) function:
o Initialization:
o Iteration:
o Palindrome Check:
▪ if is_palindrome(num): calls the helper function to determine if the
current number is a palindrome.
o Return Value: After checking all numbers in the array, the function returns
the longest_palindrome found.
To count the distinct (unique) elements in an array (Python list), you can use a couple of
straightforward methods:
Method 1: Using the set() data structure (Most Pythonic and Efficient)
Python's set data structure inherently stores only unique elements. By converting a list to a
set, all duplicate elements are automatically removed. Then, you can simply find the length
of the resulting set.
Python
def count_distinct_elements_set(arr):
"""
Returns:
"""
return len(set(arr))
# Example Usage:
list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]
list4 = [5, 5, 5, 5, 5]
empty_list = []
You can manually iterate through the array and keep track of elements you've already seen
in a separate set. If an element has not been seen before, increment a counter and add the
element to your "seen" set.
Python
def count_distinct_elements_loop(arr):
"""
Counts the number of distinct elements in an array using a loop and a helper set.
Args:
Returns:
"""
seen_elements = set() # Use a set for efficient O(1) average time lookups
distinct_count = 0
seen_elements.add(item)
distinct_count += 1
return distinct_count
# Example Usage:
list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]
list4 = [5, 5, 5, 5, 5]
empty_list = []
Explanation: This method explicitly implements the logic of identifying and counting
unique items. The seen_elements set is crucial here because checking item not in
seen_elements is very fast (average O(1) time complexity), making this approach efficient.
If you were to use a list for seen_elements and check item not in seen_elements, it would
be much slower (average O(n) for each check, leading to O(n2) overall).
The collections.Counter class is specifically designed for counting hashable objects. You
can use it to get the frequency of every element, and then easily filter for those with counts
greater than one.
Python
def find_repeating_elements_counter(arr):
"""
Args:
Returns:
"""
if not arr:
return []
counts = Counter(arr)
repeating_elements = []
if count > 1:
repeating_elements.append(item)
return repeating_elements
# Example Usage:
list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3, 1]
list4 = [5, 5, 5, 5]
empty_list = []
# Expected: [1, 2, 3] (order may vary as Counter uses dict, which is insertion order for
Python 3.7+)
This method involves a single pass through the array. We use a dictionary to keep track of
element counts and a separate set to store the elements that have been identified as
repeating. This ensures that each repeating element is added to the result only once.
Python
def find_repeating_elements_single_pass(arr):
"""
Finds repeating elements in an array in a single pass using a dictionary and a set.
Args:
Returns:
"""
if not arr:
return []
counts = {}
# If count becomes 2, it means this is the first time we've encountered it as a repeat
if counts[item] == 2:
repeating_elements_set.add(item)
# Convert the set of repeating elements to a list
return list(repeating_elements_set)
# Example Usage:
list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3, 1]
list4 = [5, 5, 5, 5]
empty_list = []
• Single Pass with Dictionary/Set (Method 2): This method is also very efficient
(O(N) time complexity) and useful if you want to avoid importing collections or
prefer a more "manual" implementation that processes the array in one go.
To find non-repeating (or unique) elements in an array, you're looking for elements that
appear exactly once. Here are two effective methods:
The collections.Counter class is ideal for this. It quickly calculates the frequency of every
element, and then you can simply filter for those with a count of 1.
Python
def find_non_repeating_elements_counter(arr):
"""
Args:
Returns:
"""
if not arr:
return []
counts = Counter(arr)
non_repeating_elements = []
if counts[item] == 1:
non_repeating_elements.append(item)
return non_repeating_elements
# Example Usage:
list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]
empty_list = []
This method manually implements the counting process using a standard dictionary,
followed by a second pass through the original array to collect elements with a count of 1.
This ensures the order of the non-repeating elements in the result matches their first
appearance in the original array.
Python
def find_non_repeating_elements_two_pass_dict(arr):
"""
Args:
Returns:
"""
if not arr:
return []
counts[item] = counts.get(item, 0) + 1
non_repeating_elements = []
# Pass 2: Iterate through the original array and collect elements with count 1
if counts[item] == 1:
non_repeating_elements.append(item)
return non_repeating_elements
# Example Usage:
list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]
list4 = [5, 5, 5, 5]
empty_list = []
• Two-Pass Dictionary (Method 2): This method is also efficient (O(N) time
complexity) and useful if you want to understand the underlying logic without using
collections or if you're restricted from using it. It also clearly demonstrates how to
preserve the original order of the non-repeating elements.
To remove duplicate elements from an array (Python list), you typically want to obtain a new
list containing only the unique elements. The approach you choose often depends on
whether you need to preserve the original order of the elements.
This is the most concise and Pythonic way to get unique elements. Python's set data
structure inherently stores only unique values.
Python
def remove_duplicates_using_set(arr):
"""
Note: This method does NOT preserve the original order of elements.
Args:
Returns:
"""
return list(set(arr))
# Example Usage:
list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]
list4 = [5, 5, 5, 5]
empty_list = []
Explanation:
1. set(arr): Converts the list arr into a set. During this conversion, any duplicate
elements are automatically discarded, as sets can only contain unique items.
2. list(...): Converts the resulting set back into a list. The order of elements in the
resulting list is not guaranteed; it will be arbitrary, depending on the internal hashing
and storage of the set.
If maintaining the original insertion order of the unique elements is crucial, you can iterate
through the array and use a separate set to keep track of elements you've already added to
your new list.
Python
def remove_duplicates_preserve_order(arr):
"""
Removes duplicate elements from an array while preserving their original order.
Args:
Returns:
A new list containing only the unique elements, in their original order of appearance.
"""
seen = set()
unique_list = []
seen.add(item)
unique_list.append(item)
return unique_list
# Example Usage:
list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]
list4 = [5, 5, 5, 5]
empty_list = []
# Expected: [1, 2, 3, 4, 5]
Explanation:
1. seen = set(): An empty set is used to quickly check if an element has already been
encountered. Set lookups (in operator) are highly efficient (average O(1) time
complexity).
2. unique_list = []: This list will store the unique elements in their order of first
appearance.
• If order doesn't matter: Use list(set(arr)). It's the simplest and generally fastest.
The scalar product (also known as the dot product) of two vectors of equal length is
calculated by multiplying corresponding elements and then summing those products.
Given two vectors, A=[a1,a2,...,an] and B=[b1,b2,...,bn], their scalar product is: A⋅B=a1b1
+a2b2+...+anbn
To find the minimum scalar product of two vectors, the strategy is as follows:
Why does this work? Consider two numbers x1<x2 and y1<y2. If we pair them as (x1,y1)
and (x2,y2), the sum of products is x1y1+x2y2. If we pair them as (x1,y2) and (x2,y1), the
sum of products is x1y2+x2y1.
Let's compare: x1y1+x2y2 versus x1y2+x2y1. Rearranging the second expression: x1y2+x2
y1=x1y1+x1y2−x1y1+x2y1=x1y1+x1(y2−y1)+x2y1. And x1y1+x2y2=x1y1+x2y1+x2y2−x2y1=x1
y1+x2y1+y2(x2−x1).
Python Implementation
Python
"""
To achieve the minimum scalar product, one vector is sorted in ascending order
Args:
Returns:
"""
if len(vec1) != len(vec2):
n = len(vec1)
# Step 1: Sort vec1 in ascending order
sorted_vec1 = sorted(vec1)
min_product = 0
for i in range(n):
return min_product
# Example Usage:
vector_a = [1, 2, 3]
vector_b = [4, 5, 6]
vector_d = [1, 2, 3]
vector_f = [2, 8, 5]
vector_g = [1, 2, 3, 4]
To find the maximum scalar product (dot product) of two vectors of equal length, the
strategy is intuitive: you want to pair the largest elements of one vector with the largest
elements of the other, and similarly for the smallest elements.
Given two vectors, A=[a1,a2,...,an] and B=[b1,b2,...,bn], their scalar product is: A⋅B=a1b1
+a2b2+...+anbn
1. Sort both vectors in the same order (either both ascending or both descending).
Why does this work? As discussed in the minimum scalar product, the property (x1−x2)(y1
−y2)≥0 implies x1y1+x2y2≥x1y2+x2y1 for x1<x2 and y1<y2. This demonstrates that pairing
elements with similar ranks (small with small, large with large) contributes more to the sum
than pairing elements with dissimilar ranks. Therefore, sorting both arrays in the same
order and multiplying corresponding elements yields the maximum scalar product.
Python Implementation
Python
"""
To achieve the maximum scalar product, both vectors are sorted in the same order
Args:
Returns:
"""
if len(vec1) != len(vec2):
n = len(vec1)
sorted_vec2 = sorted(vec2)
max_product = 0
for i in range(n):
return max_product
# Example Usage:
vector_a = [1, 2, 3]
vector_b = [4, 5, 6]
# Sorted a: [1, 2, 3]
# Sorted b: [4, 5, 6]
vector_d = [1, 2, 3]
# Sorted d: [1, 2, 3]
vector_f = [2, 8, 5]
vector_g = [1, 2, 3, 4]
To count the number of even and odd elements in an array (Python list), you can iterate
through the elements and use the modulo operator (%) to check their divisibility by 2.
Python
def count_even_odd_loop(arr):
"""
Counts the number of even and odd elements in an array using a loop.
Args:
Returns:
"""
even_count = 0
odd_count = 0
if isinstance(num, int):
if num % 2 == 0:
even_count += 1
else:
odd_count += 1
# Example Usage:
list6 = [1, 2, 3.5, 4] # Mixed types (will count 3.5 as neither, based on isinstance check)
print(f"Using loop: In {list1}, Even: {even}, Odd: {odd}") # Expected: Even: 5, Odd: 5
print(f"Using loop: In {list2}, Even: {even}, Odd: {odd}") # Expected: Even: 4, Odd: 0
print(f"Using loop: In {list3}, Even: {even}, Odd: {odd}") # Expected: Even: 0, Odd: 4
print(f"Using loop: In {list4}, Even: {even}, Odd: {odd}") # Expected: Even: 0, Odd: 0
print(f"Using loop: In {list5}, Even: {even}, Odd: {odd}") # Expected: Even: 1, Odd: 0
print(f"Using loop: In {list6}, Even: {even}, Odd: {odd}") # Expected: Even: 2, Odd: 1 (3.5 is
skipped)
This method is more concise and uses list comprehensions to create temporary lists of
even and odd numbers, then calculates their lengths.
Python
def count_even_odd_comprehension(arr):
"""
Counts the number of even and odd elements in an array using list comprehensions.
Args:
Returns:
"""
# Example Usage:
list2 = [0, 2, 4, 6]
list3 = [1, 3, 5, 7]
list4 = []
list5 = [100]
• For clarity and minimal temporary memory usage (especially with very large
arrays): The loop-based approach (Method 1) is generally preferred. It iterates
once and updates counters directly.
• For conciseness and Pythonic style: The list comprehension approach (Method
2) is elegant. However, it creates two temporary lists, which might consume more
memory for extremely large input arrays. For most typical use cases, the
performance difference is negligible.
18 ) Separating the sum of even and odd elements in an array .
To separate the sum of even and odd elements in an array (Python list), you need to
calculate the sum of all even numbers and the sum of all odd numbers independently.
Python
def sum_even_odd_loop(arr):
"""
Calculates the sum of even elements and the sum of odd elements in an array using a
loop.
Args:
Returns:
"""
even_sum = 0
odd_sum = 0
if num % 2 == 0:
even_sum += num
else:
odd_sum += num
# else:
# Example Usage:
print(f"Using loop: In {list1}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even: 30,
Odd: 25
print(f"Using loop: In {list2}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even: 12,
Odd: 0
even_s, odd_s = sum_even_odd_loop(list3)
print(f"Using loop: In {list3}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even: 0,
Odd: 16
print(f"Using loop: In {list4}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even: 0,
Odd: 0
print(f"Using loop: In {list5}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even:
100, Odd: 0
print(f"Using loop: In {list6}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even: -6,
Odd: -4
print(f"Using loop: In {list7}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even: 6,
Odd: 1 (3.5 and 'a' skipped)
This method is more concise and uses list comprehensions to create temporary lists of
even and odd numbers, then applies the built-in sum() function to these lists.
Python
def sum_even_odd_comprehension(arr):
"""
Calculates the sum of even and odd elements in an array using list comprehensions.
Args:
Returns:
"""
# First, filter the array to include only integers for even/odd checks
# Example Usage:
list2 = [0, 2, 4, 6]
list3 = [1, 3, 5, 7]
list4 = []
list5 = [100]
print(f"\nUsing list comprehension: In {list1}, Even Sum: {even_s}, Odd Sum: {odd_s}")
even_s, odd_s = sum_even_odd_comprehension(list2)
print(f"Using list comprehension: In {list2}, Even Sum: {even_s}, Odd Sum: {odd_s}")
print(f"Using list comprehension: In {list3}, Even Sum: {even_s}, Odd Sum: {odd_s}")
print(f"Using list comprehension: In {list4}, Even Sum: {even_s}, Odd Sum: {odd_s}")
print(f"Using list comprehension: In {list5}, Even Sum: {even_s}, Odd Sum: {odd_s}")
print(f"Using list comprehension: In {list6}, Even Sum: {even_s}, Odd Sum: {odd_s}")
print(f"Using list comprehension: In {list7}, Even Sum: {even_s}, Odd Sum: {odd_s}")
• For clarity and minimal memory usage (especially with very large arrays): The
loop-based approach (Method 1) is generally more efficient as it processes
elements one by one without creating intermediate lists.
• For conciseness and Pythonic style: The list comprehension approach (Method
2) is very readable and elegant. For most practical array sizes, the performance
difference is negligible, and its conciseness often makes it preferred.
• Both methods correctly handle empty arrays and correctly skip non-integer
elements if present.
19 ) Find all Symmetric pairs in an array .
To find all symmetric pairs in an array where each element is itself a pair (e.g., [[a, b], [c, d],
...]), we need to identify instances where both [a, b] and [b, a] exist in the array.
We can achieve this efficiently using a hash-based data structure (like a dictionary or a set)
to keep track of the pairs we've already encountered.
This method iterates through the input array. For each pair [a, b], it checks if its symmetric
counterpart [b, a] has already been seen.
Python
def find_symmetric_pairs(arr_of_pairs):
"""
Args:
arr_of_pairs: A list of lists, where each inner list is a pair [element1, element2].
Returns:
A list of lists, where each inner list contains two pairs that form a symmetric pair,
"""
symmetric_pairs_found = []
a, b = current_pair_list[0], current_pair_list[1]
reversed_pair_tuple = (b, a)
if reversed_pair_tuple in seen_pairs:
# If 'a' and 'b' are the same (e.g., [7,7]), it's symmetric with itself.
# If [7,7] is found and seen_pairs already has (7,7), this means it's a duplicate of itself.
# We prevent this by ensuring the original pair wasn't also (b,a) when checking.
# Or, by making sure a and b are distinct for true "symmetric" pairs.
# For simplicity, we'll include pairs like [7,7] only if there's another [7,7].
# A more robust check for (a,b) != (b,a) is implicitly handled if 'del' is used.
# Add both the previously stored reversed pair and the current pair to the result
symmetric_pairs_found.append([seen_pairs[reversed_pair_tuple], current_pair_list])
del seen_pairs[reversed_pair_tuple]
else:
# Use the tuple form as key, store the original list form as value.
seen_pairs[current_pair_tuple] = current_pair_list
return symmetric_pairs_found
# Example Usage:
print(f"Test Case 1: [[1, 2], [3, 4], [2, 1], [5, 6], [4, 3]]")
result1 = find_symmetric_pairs([[1, 2], [3, 4], [2, 1], [5, 6], [4, 3]])
print(f"\nTest Case 2: [[1, 2], [2, 1], [1, 2], [3, 4]]")
# Expected: [[[1, 2], [2, 1]]] (only reports the pair once even if [1,2] appears multiple times)
# Expected: [[[1, 1], [1, 1]]] (if [1,1] is considered symmetric with itself AND appears twice)
result5 = find_symmetric_pairs([])
# Expected: []
Explanation:
1. seen_pairs = {}: A dictionary is used to store pairs that have been encountered so
far. The key of the dictionary is a tuple representation of the pair (e.g., (1, 2)) because
lists are mutable and cannot be used as dictionary keys directly. The value stored is
the original list form of the pair (e.g., [1, 2]).
2. symmetric_pairs_found = []: This list will collect the symmetric pairs that are
found. Each entry in this list will be [original_pair, reversed_pair].
3. Iteration: The code iterates through each current_pair_list in the input arr_of_pairs.
4. Creating Tuples:
o reversed_pair_tuple = (b, a): Creates the tuple for the symmetric counterpart.
o Adding to Results:
▪ symmetric_pairs_found.append([seen_pairs[reversed_pair_tuple],
current_pair_list]): Both the previously stored [b, a] (retrieved using
seen_pairs[reversed_pair_tuple]) and the current [a, b] are added as a
sub-list to symmetric_pairs_found.
▪ del seen_pairs[reversed_pair_tuple]: This is crucial. Once a
symmetric pair is found and recorded, its reversed_pair_tuple (which
was previously stored) is removed from seen_pairs. This prevents the
same symmetric pair from being reported multiple times if, for
instance, [1, 2] appears again later in the input array after [2, 1] has
already been processed.
This approach ensures that each unique set of symmetric pairs ({[a, b], [b, a]}) is identified
and reported exactly once, regardless of how many times individual pairs appear in the
input array.
A more efficient solution uses dynamic programming concepts, specifically tracking the
maximum and minimum products ending at the current position.
This last point is crucial: a very small (large in magnitude negative) product can become a
very large positive product if multiplied by another negative number. Therefore, at each
step, we need to keep track of both the maximum product ending at the current position
and the minimum product ending at the current position.
• Similarly, the new minimum product ending at num could be num itself, num
multiplied by the previous maximum product, or num multiplied by the previous
minimum product.
We then update our overall maximum product with the largest max_product_ending_here
found so far.
3. Iterate through the array starting from the second element: a. For each number num
at the current position: b. Temporarily store the value of current_max_product
before it's updated (temp_current_max). c. Update current_max_product: It will be
the maximum of: * num itself (starting a new sub-array). * num * temp_current_max
(extending the previous max product sub-array). * num * current_min_product
(extending the previous min product sub-array - crucial for negatives). d. Update
current_min_product: It will be the minimum of: * num itself. * num *
temp_current_max. * num * current_min_product. e. Update global_max_product:
Compare global_max_product with the current_max_product and take the larger
one.
4. Return global_max_product.
Python Implementation
Python
def max_product_subarray(nums):
"""
Args:
"""
if not nums:
# Initialize current max and min product for the subarray ending at the current position.
current_max_product = nums[0]
current_min_product = nums[0]
global_max_product = nums[0]
num = nums[i]
temp_current_max = current_max_product
# It can be:
# It can be:
# 1. num itself
# 3. num * previous_min_product
return global_max_product
# Example Usage:
print(f"\nArray: [7]")
print(f"\nArray: []")
To determine if two arrays (Python lists) are disjoint means checking if they have no
common elements. If even one element is shared between them, they are not disjoint.
Method 1: Using Python Sets and isdisjoint() (Most Pythonic and Efficient)
This is the recommended approach. Python's set data structure is optimized for checking
membership and intersections. The isdisjoint() method directly checks if two sets have an
empty intersection.
Python
"""
Checks if two arrays are disjoint (have no common elements) using sets.
Args:
Returns:
"""
set1 = set(arr1)
set2 = set(arr2)
return set1.isdisjoint(set2)
# Example Usage:
list_a = [1, 2, 3, 4]
list_b = [5, 6, 7, 8]
list_g = [1, 2, 3]
list_j = [1, 2, 3]
list_l = [3, 4, 3]
This method involves converting one array into a set for efficient membership checks, then
iterating through the second array to see if any of its elements are present in the set.
Python
"""
Checks if two arrays are disjoint (have no common elements) using a loop and a set for
lookup.
Args:
Returns:
"""
# Convert one of the arrays (preferably the smaller one, if known) into a set
set_for_lookup = set(arr1)
if item in set_for_lookup:
return True # No common elements were found after checking all elements in arr2
# Example Usage:
list_a = [1, 2, 3, 4]
list_b = [5, 6, 7, 8]
print(f"Are {list_a} and {list_b} disjoint? {are_arrays_disjoint_loop(list_a, list_b)}")
list_g = [1, 2, 3]
list_h = [1, 2, 3]
list_i = []
list_j = [1, 2, 3]
list_k = [1, 2, 1]
list_l = [3, 4, 3]
• The loop-based approach (Method 2) provides more control and can be slightly
more efficient in very specific scenarios (e.g., if arr2 is extremely long and you
expect to find a common element very early, as it can "short-circuit" and return
False immediately). However, for general use, the isdisjoint() method is superior.
To replace each element of an array by its rank, we generally mean assigning a rank (usually
1-based) to each unique value in the array. If there are duplicate values, they all receive the
same rank. The ranks are consecutive for distinct values.
For example:
• [10, 20, 10, 30] -> [2, 3, 2, 4] (assuming 10 is rank 2, 20 is rank 3, etc.) or [1, 2, 1, 3] (if
ranks start from 1 for the smallest unique element). The latter (dense ranking) is the
more common interpretation.
Algorithm:
1. Get Unique Sorted Values: Extract all unique elements from the array and sort
them. This gives you the ordered set of values to determine ranks.
2. Create Rank Mapping: Create a dictionary (hash map) to store the mapping from
each unique value to its corresponding rank.
3. Replace Elements: Iterate through the original array and replace each element with
its rank using the created mapping.
Python Implementation
Python
def replace_with_rank(arr):
"""
Replaces each element of the array by its rank.
Ranks are 1-based, and elements with the same value get the same rank.
Args:
Returns:
"""
if not arr:
return []
unique_sorted_values = sorted(list(set(arr)))
ranked_array = []
ranked_array.append(rank_map[element])
return ranked_array
# Example Usage:
list2 = [1, 5, 2, 8, 3, 5]
empty_list = []
list_single_element = [100]
# Result: [1, 4, 2, 5, 3, 4]
# Expected: [1, 1, 1, 1]
print(f"Original: {list4} -> Ranked: {replace_with_rank(list4)}")
# Expected: [5, 4, 3, 2, 1]
Explanation:
1. unique_sorted_values = sorted(list(set(arr))):
o set(arr): This step converts the input list arr into a set. A set automatically
removes all duplicate elements, leaving only unique values.
o sorted(...): Sorts this list of unique values in ascending order. This sorted list
([5, 10, 20, 30] for list1) defines the order for assigning ranks.
o This loop iterates through each element in the original input arr.
o For each element, it looks up its corresponding rank in the rank_map and
appends it to the ranked_array.
This method is efficient with a time complexity dominated by the sorting step, which is
O(NlogN), where N is the number of elements in the array.
23 ) Finding equilibrium index of an array .
An equilibrium index of an array is an index i such such that the sum of elements at lower
indices is equal to the sum of elements at higher indices.
Key points:
• For an element at the last index n-1, the sum of elements at higher indices is 0.
The most efficient way to find equilibrium indices is to use a single pass through the array
after calculating the total sum.
1. Calculate Total Sum: First, compute the sum of all elements in the entire array.
o Iterate through the array from the first element to the last (index 0 to n-1).
Python Implementation
Python
def find_equilibrium_indices(arr):
"""
Args:
Returns:
A list of equilibrium indices. Returns an empty list if no such indices are found.
"""
n = len(arr)
if n == 0:
return []
total_sum = sum(arr)
left_sum = 0
equilibrium_indices = []
if left_sum == right_sum:
equilibrium_indices.append(i)
# Update left_sum for the next iteration (add current element to left_sum)
left_sum += arr[i]
return equilibrium_indices
# Explanation:
# For index 3 (value 2): Left sum (-7 + 1 + 5 = -1), Right sum (-4 + 3 + 0 = -1). Equal.
# For index 6 (value 0): Left sum (-7 + 1 + 5 + 2 + -4 + 3 = 0), Right sum (empty = 0). Equal.
# Expected Output: []
print(f"\nArray: [1]")
print(f"\nArray: []")
# Expected Output: []
# Explanation: For index 2 (value 1): Left sum (1 + -1 = 0), Right sum (-1 + 1 = 0). Equal.
• Time Complexity: O(N), where N is the number of elements in the array. This is
because we perform one pass to calculate the total sum and another single pass to
find the equilibrium indices.
• Space Complexity: O(1) (constant space), as we only use a few variables to store
sums and indices, regardless of the array size.
1. Left Rotation: Elements move from the beginning to the end of the array.
2. Right Rotation: Elements move from the end to the beginning of the array.
We'll discuss common and efficient ways to perform these rotations in Python.
Key Considerations:
• In-place vs. New Array: The examples below will return a new rotated array, which
is a common and safe practice in Python. If an in-place modification is strictly
required (common in some competitive programming contexts), different
algorithms would be used.
1. Left Rotation
Python's list slicing is very powerful for this. You can slice the array into two parts and
concatenate them.
Python
"""
Args:
"""
n = len(arr)
if n == 0:
return []
# Concatenate the slice from k to end, with the slice from beginning to k
list1 = [1, 2, 3, 4, 5]
k1 = 2
# Expected: [3, 4, 5, 1, 2]
k2 = 5 # k > len(arr)
list3 = []
k3 = 3
print(f"Original: {list3}, Left Rotate by {k3} -> {rotate_left_slicing(list3, k3)}")
# Expected: []
list4 = [10]
k4 = 100
# Expected: [10]
The deque (double-ended queue) from Python's collections module is highly optimized for
rotations.
Python
"""
Args:
Returns:
"""
if not arr:
return []
d = deque(arr)
return list(d)
list1 = [1, 2, 3, 4, 5]
k1 = 2
k2 = 5
2. Right Rotation
Python
"""
Args:
"""
n = len(arr)
if n == 0:
return []
# Concatenate the slice from (n-k) to end, with the slice from beginning to (n-k)
list1 = [1, 2, 3, 4, 5]
k1 = 2
# Expected: [4, 5, 1, 2, 3]
k2 = 5 # k > len(arr)
For right rotation with deque, the rotate() method takes a positive k.
Python
"""
Args:
Returns:
"""
if not arr:
return []
d = deque(arr)
return list(d)
list1 = [1, 2, 3, 4, 5]
k1 = 2
k2 = 5
• Slicing: Is very Pythonic, readable, and generally efficient for most practical use
cases. It's often the go-to for simple array rotations.
In a left circular rotation, elements from the beginning move to the end of the array.
• [1, 2, 3, 4, 5]
• Rotate by 1: [2, 3, 4, 5, 1]
• Rotate by 2: [3, 4, 5, 1, 2]
Python
Args:
Returns:
"""
n = len(arr)
if n == 0:
return []
# Concatenate the part from 'k' to the end with the part from the beginning to 'k'
k_a = 2
list_c = []
k_c = 1
# Expected: []
In a right circular rotation, elements from the end move to the beginning of the array.
• [1, 2, 3, 4, 5]
• Rotate by 1: [5, 1, 2, 3, 4]
• Rotate by 2: [4, 5, 1, 2, 3]
Python
"""
Args:
Returns:
A new list with elements circularly rotated right by K positions.
"""
n = len(arr)
if n == 0:
return []
# Concatenate the part from (n-k) to the end with the part from the beginning to (n-k)
k_d = 2
list_f = [100]
k_f = 7
print(f"Original: {list_f}, Right Circular Rotate by {k_f} -> {circular_rotate_right(list_f, k_f)}")
# Expected: [100]
For highly optimized and potentially faster rotations (especially with very large arrays or
many rotations), Python's collections.deque is a specialized data structure that supports
efficient rotations.
Python
"""
Args:
Returns:
"""
if not arr:
return []
d = deque(arr)
if direction == 'left':
else:
return list(d)
list_g = [1, 2, 3, 4, 5]