0% found this document useful (0 votes)
7 views106 pages

Python Interview Questions - Part 1

The document provides a comprehensive guide on various Python array interview questions and methods to manipulate arrays, including finding the largest, smallest, and second smallest elements, calculating the sum, and reversing an array. Each method is accompanied by Python code examples and explanations, showcasing both built-in functions and manual iterations. The content is aimed at candidates with 0-3 years of experience for roles such as Python Developer, Data Analyst, and Data Scientist.

Uploaded by

M.M.Prabu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views106 pages

Python Interview Questions - Part 1

The document provides a comprehensive guide on various Python array interview questions and methods to manipulate arrays, including finding the largest, smallest, and second smallest elements, calculating the sum, and reversing an array. Each method is accompanied by Python code examples and explanations, showcasing both built-in functions and manual iterations. The content is aimed at candidates with 0-3 years of experience for roles such as Python Developer, Data Analyst, and Data Scientist.

Uploaded by

M.M.Prabu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 106

PYTHON ARRAY INTERVIEW

QUESTIONS
YOE: 0-3
ROLE : Python Developer
Data Analyst
Data Scientist

1 ) Find Largest element in an array .


Method 1: Using the max() function

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:

arr: A list of numbers.

Returns:

The largest element in the array.


"""

if not arr:

return "The array is empty." # Handle empty array case

return max(arr)

# Example Usage:

my_list1 = [10, 3, 20, 5, 15]

my_list2 = [-1, -5, -2]

my_list3 = [7]

empty_list = []

print(f"Using max() function: Largest element in {my_list1} is


{find_largest_max_function(my_list1)}")

print(f"Using max() function: Largest element in {my_list2} is


{find_largest_max_function(my_list2)}")

print(f"Using max() function: Largest element in {my_list3} is


{find_largest_max_function(my_list3)}")

print(f"Using max() function: Largest element in {empty_list} is


{find_largest_max_function(empty_list)}")

Method 2: Iterating Through the Array (Looping)

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):

"""

Finds the largest element in an array by iterating through it.


Args:

arr: A list of numbers.

Returns:

The largest element in the array.

"""

if not arr:

return "The array is empty." # Handle empty array case

largest = arr[0] # Initialize largest with the first element

for element in arr:

if element > largest:

largest = element

return largest

# Example Usage:

my_list1 = [10, 3, 20, 5, 15]

my_list2 = [-1, -5, -2]

my_list3 = [7]

empty_list = []

print(f"\nUsing loop: Largest element in {my_list1} is {find_largest_loop(my_list1)}")

print(f"Using loop: Largest element in {my_list2} is {find_largest_loop(my_list2)}")

print(f"Using loop: Largest element in {my_list3} is {find_largest_loop(my_list3)}")

print(f"Using loop: Largest element in {empty_list} is {find_largest_loop(empty_list)}")


2) Find Smallest Element in an Array .
Method 1: Using the min() function

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:

arr: A list of numbers.

Returns:

The smallest element in the array, or a message if the array is empty.

"""

if not arr:

return "The array is empty." # Handle empty array case

return min(arr)

# Example Usage:

my_list1 = [10, 3, 20, 5, 15]

my_list2 = [-1, -5, -2]

my_list3 = [7]

empty_list = []
print(f"Using min() function: Smallest element in {my_list1} is
{find_smallest_min_function(my_list1)}")

print(f"Using min() function: Smallest element in {my_list2} is


{find_smallest_min_function(my_list2)}")

print(f"Using min() function: Smallest element in {my_list3} is


{find_smallest_min_function(my_list3)}")

print(f"Using min() function: Smallest element in {empty_list} is


{find_smallest_min_function(empty_list)}")

Method 2: Iterating Through the Array (Looping)

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

import math # Import math module for math.inf

def find_smallest_loop(arr):

"""

Finds the smallest element in an array by iterating through it.

Args:

arr: A list of numbers.

Returns:

The smallest element in the array, or a message if the array is empty.

"""

if not arr:

return "The array is empty." # Handle empty array case


smallest = arr[0] # Initialize smallest with the first element

# Alternatively, for robustness with any possible numbers (even very large ones):

# smallest = math.inf # Initialize with positive infinity

for element in arr:

if element < smallest:

smallest = element

return smallest

# Example Usage:

my_list1 = [10, 3, 20, 5, 15]

my_list2 = [-1, -5, -2]

my_list3 = [7]

empty_list = []

print(f"\nUsing loop: Smallest element in {my_list1} is {find_smallest_loop(my_list1)}")

print(f"Using loop: Smallest element in {my_list2} is {find_smallest_loop(my_list2)}")

print(f"Using loop: Smallest element in {my_list3} is {find_smallest_loop(my_list3)}")

print(f"Using loop: Smallest element in {empty_list} is {find_smallest_loop(empty_list)}")

3) Find the Smallest and largest element in an array .


Finding both the smallest and largest elements in an array is a common task. Here are a
couple of methods, including an efficient approach that minimizes comparisons.

Method 1: Using min() and max() functions

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:

arr: A list of numbers.

Returns:

A tuple containing (smallest_element, largest_element),

or a message if the array is empty.

"""

if not arr:

return "The array is empty."

return (min(arr), max(arr))

# Example Usage:

my_list1 = [10, 3, 20, 5, 15]

my_list2 = [-1, -5, -2, 0, 100]

my_list3 = [7]

empty_list = []

print(f"Using min() and max(): For {my_list1}, (min, max) is


{find_min_max_builtin(my_list1)}")

print(f"Using min() and max(): For {my_list2}, (min, max) is


{find_min_max_builtin(my_list2)}")
print(f"Using min() and max(): For {my_list3}, (min, max) is
{find_min_max_builtin(my_list3)}")

print(f"Using min() and max(): For {empty_list}, (min, max) is


{find_min_max_builtin(empty_list)}")

Method 2: Iterating Through the Array (Efficient Single Pass)

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):

"""

Finds the smallest and largest elements in an array in a single pass.

Args:

arr: A list of numbers.

Returns:

A tuple containing (smallest_element, largest_element),

or a message if the array is empty.

"""

if not arr:

return "The array is empty."

# Initialize min_val and max_val with the first element

min_val = arr[0]

max_val = arr[0]
# Iterate from the second element

for i in range(1, len(arr)):

if arr[i] < min_val:

min_val = arr[i]

if arr[i] > max_val:

max_val = arr[i]

return (min_val, max_val)

# Example Usage:

my_list1 = [10, 3, 20, 5, 15]

my_list2 = [-1, -5, -2, 0, 100]

my_list3 = [7]

empty_list = []

print(f"\nUsing single pass: For {my_list1}, (min, max) is


{find_min_max_single_pass(my_list1)}")

print(f"Using single pass: For {my_list2}, (min, max) is


{find_min_max_single_pass(my_list2)}")

print(f"Using single pass: For {my_list3}, (min, max) is


{find_min_max_single_pass(my_list3)}")

print(f"Using single pass: For {empty_list}, (min, max) is


{find_min_max_single_pass(empty_list)}")

4 )Find Second Smallest Element in an Array


Here are a couple of methods to find the second smallest element in an array.
Method 1: Using Sorting

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):

"""

Finds the second smallest element in an array by sorting.

Args:

arr: A list of numbers.

Returns:

The second smallest element, or a message if not enough unique elements.

"""

if not arr or len(arr) < 2:

return "Array must contain at least two elements."

# Remove duplicates and sort

unique_sorted_arr = sorted(list(set(arr)))

if len(unique_sorted_arr) < 2:

return "Not enough unique elements to find a second smallest."

else:

return unique_sorted_arr[1]

# Example Usage:
list1 = [10, 3, 20, 5, 15]

list2 = [1, 2, 3, 4, 5]

list3 = [5, 5, 5, 5, 5, 1, 1, 2] # Contains duplicates

list4 = [7, 7, 7]

list5 = [8]

empty_list = []

print(f"Using sorting: Second smallest in {list1} is {find_second_smallest_sorted(list1)}")

print(f"Using sorting: Second smallest in {list2} is {find_second_smallest_sorted(list2)}")

print(f"Using sorting: Second smallest in {list3} is {find_second_smallest_sorted(list3)}")

print(f"Using sorting: Second smallest in {list4} is {find_second_smallest_sorted(list4)}")

print(f"Using sorting: Second smallest in {list5} is {find_second_smallest_sorted(list5)}")

print(f"Using sorting: Second smallest in {empty_list} is


{find_second_smallest_sorted(empty_list)}")

Method 2: Single Pass (Efficient Approach)

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):

"""

Finds the second smallest element in an array in a single pass.

Args:

arr: A list of numbers.


Returns:

The second smallest element, or a message if not enough unique elements.

"""

if not arr or len(arr) < 2:

return "Array must contain at least two elements."

# Initialize smallest and second_smallest

# Use math.inf to ensure any number in the array will be smaller

smallest = math.inf

second_smallest = math.inf

for x in arr:

if x < smallest:

second_smallest = smallest # The previous smallest is now the second 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:

return "Not enough unique elements to find a second smallest."

else:

return second_smallest

# Example Usage:
list1 = [10, 3, 20, 5, 15]

list2 = [1, 2, 3, 4, 5]

list3 = [5, 5, 5, 5, 5, 1, 1, 2] # Contains duplicates

list4 = [7, 7, 7]

list5 = [8]

empty_list = []

list6 = [1, 1, 2, 3]

print(f"\nUsing single pass: Second smallest in {list1} is


{find_second_smallest_single_pass(list1)}")

print(f"Using single pass: Second smallest in {list2} is


{find_second_smallest_single_pass(list2)}")

print(f"Using single pass: Second smallest in {list3} is


{find_second_smallest_single_pass(list3)}")

print(f"Using single pass: Second smallest in {list4} is


{find_second_smallest_single_pass(list4)}")

print(f"Using single pass: Second smallest in {list5} is


{find_second_smallest_single_pass(list5)}")

print(f"Using single pass: Second smallest in {empty_list} is


{find_second_smallest_single_pass(empty_list)}")

print(f"Using single pass: Second smallest in {list6} is


{find_second_smallest_single_pass(list6)}")

5) Calculate the sum of elements in an array .


Method 1: Using the sum() function

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:

arr: A list of numbers.

Returns:

The sum of the elements, or 0 if the array is empty.

"""

return sum(arr)

# Example Usage:

list1 = [1, 2, 3, 4, 5]

list2 = [10, -5, 20, 0]

list3 = [7]

empty_list = []

print(f"Using sum() function: Sum of {list1} is {calculate_sum_builtin(list1)}")

print(f"Using sum() function: Sum of {list2} is {calculate_sum_builtin(list2)}")

print(f"Using sum() function: Sum of {list3} is {calculate_sum_builtin(list3)}")

print(f"Using sum() function: Sum of {empty_list} is {calculate_sum_builtin(empty_list)}")

Method 2: Iterating Through the Array (Looping)

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):

"""

Calculates the sum of elements in an array by iterating through it.

Args:

arr: A list of numbers.

Returns:

The sum of the elements.

"""

total = 0

for element in arr:

total += element

return total

# Example Usage:

list1 = [1, 2, 3, 4, 5]

list2 = [10, -5, 20, 0]

list3 = [7]

empty_list = []

print(f"\nUsing loop: Sum of {list1} is {calculate_sum_loop(list1)}")

print(f"Using loop: Sum of {list2} is {calculate_sum_loop(list2)}")

print(f"Using loop: Sum of {list3} is {calculate_sum_loop(list3)}")

print(f"Using loop: Sum of {empty_list} is {calculate_sum_loop(empty_list)}")


6) Reverse an Array
Here are a few common methods to reverse an array (Python list):

Method 1: Using Slicing

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):

"""

Reverses an array using slicing. Creates a new reversed array.

Args:

arr: The input list.

Returns:

A new list with elements in reverse order.

"""

return arr[::-1]

# Example Usage:

list1 = [1, 2, 3, 4, 5]

list2 = ['a', 'b', 'c']

list3 = [10]

empty_list = []

print(f"Original list: {list1}, Reversed using slicing: {reverse_array_slicing(list1)}")

print(f"Original list: {list2}, Reversed using slicing: {reverse_array_slicing(list2)}")


print(f"Original list: {list3}, Reversed using slicing: {reverse_array_slicing(list3)}")

print(f"Original list: {empty_list}, Reversed using slicing:


{reverse_array_slicing(empty_list)}")

Method 2: Using the reverse() method (In-place)

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: The input list (will be modified).

"""

arr.reverse()

# Example Usage:

list1_inplace = [1, 2, 3, 4, 5]

list2_inplace = ['a', 'b', 'c']

list3_inplace = [10]

empty_list_inplace = []

print(f"\nOriginal list before in-place reverse: {list1_inplace}")

reverse_array_in_place(list1_inplace)

print(f"List after in-place reverse: {list1_inplace}")

print(f"\nOriginal list before in-place reverse: {list2_inplace}")


reverse_array_in_place(list2_inplace)

print(f"List after in-place reverse: {list2_inplace}")

print(f"\nOriginal list before in-place reverse: {list3_inplace}")

reverse_array_in_place(list3_inplace)

print(f"List after in-place reverse: {list3_inplace}")

print(f"\nOriginal list before in-place reverse: {empty_list_inplace}")

reverse_array_in_place(empty_list_inplace)

print(f"List after in-place reverse: {empty_list_inplace}")

Method 3: Using a Loop (Manual Reversal)

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.

Sub-method 3a: Appending to a new list

Python

def reverse_array_loop_new_list(arr):

"""

Reverses an array by iterating and appending to a new list.

Args:

arr: The input list.

Returns:

A new list with elements in reverse order.

"""

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]

list2_loop_new = ['x', 'y', 'z']

print(f"\nOriginal list: {list1_loop_new}, Reversed using loop (new list):


{reverse_array_loop_new_list(list1_loop_new)}")

print(f"Original list: {list2_loop_new}, Reversed using loop (new list):


{reverse_array_loop_new_list(list2_loop_new)}")

Sub-method 3b: Swapping elements (in-place)

Python

def reverse_array_loop_swap(arr):

"""

Reverses an array in-place by swapping elements from ends.

Args:

arr: The input list (will be modified).

"""

left = 0

right = len(arr) - 1

while left < right:

arr[left], arr[right] = arr[right], arr[left] # Swap elements

left += 1

right -= 1
# Example Usage:

list1_loop_swap = [1, 2, 3, 4, 5]

list2_loop_swap = ['p', 'q', 'r', 's']

list3_loop_swap = [100]

empty_list_loop_swap = []

print(f"\nOriginal list before loop swap: {list1_loop_swap}")

reverse_array_loop_swap(list1_loop_swap)

print(f"List after loop swap: {list1_loop_swap}")

print(f"\nOriginal list before loop swap: {list2_loop_swap}")

reverse_array_loop_swap(list2_loop_swap)

print(f"List after loop swap: {list2_loop_swap}")

print(f"\nOriginal list before loop swap: {list3_loop_swap}")

reverse_array_loop_swap(list3_loop_swap)

print(f"List after loop swap: {list3_loop_swap}")

print(f"\nOriginal list before loop swap: {empty_list_loop_swap}")

reverse_array_loop_swap(empty_list_loop_swap)

print(f"List after loop swap: {empty_list_loop_swap}")


7 ) Sort first half in ascending order and second half in
descending

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:

arr: A list of numbers.

Returns:

A new list with the first half sorted ascending and the second half descending,

or an error message for invalid input.

"""

n = len(arr)

if n < 2:

return "Array must contain at least two elements to have distinct halves."

mid = n // 2 # Integer division to find the midpoint

first_half = arr[:mid]

second_half = arr[mid:]
# Sort the first half in ascending order

first_half.sort()

# Sort the second half in descending order

second_half.sort(reverse=True)

# Combine the sorted halves

return first_half + second_half

# Example Usage:

list1 = [10, 3, 20, 5, 15, 8, 2, 25]

list2 = [1, 2, 3, 4, 5, 6]

list3 = [9, 8, 7, 6, 5] # Odd number of elements

list4 = [1, 2]

list5 = [5] # Not enough elements

empty_list = []

print(f"Original: {list1} -> Sorted Halves: {sort_halves(list1)}")

# Expected: [3, 5, 10, 20, 25, 15, 8, 2] (incorrect in prompt example)

# Correct: [3, 5, 8, 10, 25, 20, 15, 2]

print(f"Original: {list2} -> Sorted Halves: {sort_halves(list2)}")

# Expected: [1, 2, 3, 6, 5, 4]

print(f"Original: {list3} -> Sorted Halves: {sort_halves(list3)}")

# Expected: [5, 6, 7, 9, 8]
print(f"Original: {list4} -> Sorted Halves: {sort_halves(list4)}")

# Expected: [1, 2]

print(f"Original: {list5} -> Sorted Halves: {sort_halves(list5)}")

print(f"Original: {empty_list} -> Sorted Halves: {sort_halves(empty_list)}")

Explanation:

1. Handle Edge Cases:

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:

o n = len(arr): Gets the total number of elements in the array.

o mid = n // 2: Uses integer division (//) to find the midpoint.

▪ If n is even (e.g., 6 elements), mid will be 3. The first half will be


arr[0:3] (elements at indices 0, 1, 2) and the second half will be arr[3:]
(elements at indices 3, 4, 5). Both halves will have an equal number of
elements.

▪ 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.

3. Split the Array:

o first_half = arr[:mid]: Creates a slice containing elements from the beginning


up to (but not including) the mid index.

o second_half = arr[mid:]: Creates a slice containing elements from the mid


index to the end of the array.

4. Sort the Halves:

o first_half.sort(): Sorts the first_half list in ascending order in-place.


o second_half.sort(reverse=True): Sorts the second_half list in descending
order in-place by setting the reverse parameter to True.

5. Combine the Halves:

o return first_half + second_half: Concatenates the two sorted lists and returns
the new combined list.

8 ) Sort the elements of an array


Here are the most common ways to sort the elements of an array (Python list), along with
their characteristics:

Method 1: Using the sort() method (In-place)

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

def sort_array_in_place(arr, ascending=True):

"""

Sorts an array in-place. Modifies the original array.

Args:

arr: The input list (will be modified).

ascending: If True (default), sorts in ascending order. If False, sorts in descending order.

"""

arr.sort(reverse=not ascending)

# Example Usage:

list1_inplace_asc = [10, 3, 20, 5, 15]

list2_inplace_desc = ['banana', 'apple', 'cherry', 'date']

list3_inplace = [7]
empty_list_inplace = []

print(f"Original list (ascending): {list1_inplace_asc}")

sort_array_in_place(list1_inplace_asc)

print(f"Sorted (ascending) list: {list1_inplace_asc}\n")

print(f"Original list (descending): {list2_inplace_desc}")

sort_array_in_place(list2_inplace_desc, ascending=False)

print(f"Sorted (descending) list: {list2_inplace_desc}\n")

print(f"Original list (single element): {list3_inplace}")

sort_array_in_place(list3_inplace)

print(f"Sorted (single element) list: {list3_inplace}\n")

print(f"Original list (empty): {empty_list_inplace}")

sort_array_in_place(empty_list_inplace)

print(f"Sorted (empty) list: {empty_list_inplace}\n")

Method 2: Using the sorted() function (Returns a new list)

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

def sort_array_new_list(arr, ascending=True):

"""

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:

A new list with elements in sorted order.

"""

return sorted(arr, reverse=not ascending)

# Example Usage:

list1_new_asc = [10, 3, 20, 5, 15]

list2_new_desc = ['zebra', 'cat', 'dog', 'ant']

list3_new = [7]

empty_list_new = []

print(f"Original list (ascending): {list1_new_asc}")

sorted_list1_asc = sort_array_new_list(list1_new_asc)

print(f"New sorted (ascending) list: {sorted_list1_asc}")

print(f"Original list after sorted() call: {list1_new_asc}\n") # Original is unchanged

print(f"Original list (descending): {list2_new_desc}")

sorted_list2_desc = sort_array_new_list(list2_new_desc, ascending=False)

print(f"New sorted (descending) list: {sorted_list2_desc}")

print(f"Original list after sorted() call: {list2_new_desc}\n")

print(f"Original list (single element): {list3_new}")

sorted_list3 = sort_array_new_list(list3_new)
print(f"New sorted (single element) list: {sorted_list3}\n")

print(f"Original list (empty): {empty_list_new}")

sorted_empty_list = sort_array_new_list(empty_list_new)

print(f"New sorted (empty) list: {sorted_empty_list}\n")

Key Differences and When to Use Which:

• sort() method:

o Modifies the original list (in-place).

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.

o Can only be used on lists.

• sorted() function:

o Returns a new sorted list. The original list remains unchanged.

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.

8 ) Finding the frequency of elements in an array


Finding the frequency of elements in an array (Python list) means counting how many times
each unique element appears in the array. Here are two common methods to achieve this:

Method 1: Using a Loop and a Dictionary

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:

arr: A list of elements.

Returns:

A dictionary where keys are the elements and values are their frequencies.

"""

frequency = {}

for item in arr:

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]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']

list3 = [10, 20, 30]

empty_list = []

list_mixed = [1, 'a', 1, 'b', 'a', 2]


print(f"Using loop: Frequencies in {list1} -> {find_frequency_loop(list1)}")

print(f"Using loop: Frequencies in {list2} -> {find_frequency_loop(list2)}")

print(f"Using loop: Frequencies in {list3} -> {find_frequency_loop(list3)}")

print(f"Using loop: Frequencies in {empty_list} -> {find_frequency_loop(empty_list)}")

print(f"Using loop: Frequencies in {list_mixed} -> {find_frequency_loop(list_mixed)}")

Explanation:

1. Initialize an empty dictionary: frequency = {} will store our counts.

2. Iterate through the array: For each item in the input arr.

3. Check if item exists:

o If item is already a key in the frequency dictionary, it means we've seen it


before, so we increment its count (frequency[item] += 1).

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).

Method 2: Using collections.Counter

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

from collections import Counter

def find_frequency_counter(arr):

"""

Finds the frequency of each element in an array using collections.Counter.

Args:

arr: A list of elements.


Returns:

A Counter object (which is a subclass of dictionary) representing frequencies.

"""

return Counter(arr)

# Example Usage:

list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']

list3 = [10, 20, 30]

empty_list = []

list_mixed = [1, 'a', 1, 'b', 'a', 2]

print(f"\nUsing collections.Counter: Frequencies in {list1} ->


{find_frequency_counter(list1)}")

print(f"Using collections.Counter: Frequencies in {list2} -> {find_frequency_counter(list2)}")

print(f"Using collections.Counter: Frequencies in {list3} -> {find_frequency_counter(list3)}")

print(f"Using collections.Counter: Frequencies in {empty_list} ->


{find_frequency_counter(empty_list)}")

print(f"Using collections.Counter: Frequencies in {list_mixed} ->


{find_frequency_counter(list_mixed)}")

# You can access counts like a dictionary:

counts_list1 = find_frequency_counter(list1)

print(f"Count of 2 in list1: {counts_list1[2]}")

Explanation:

1. Import Counter: from collections import Counter


2. Pass the array to Counter: Counter(arr) directly takes the iterable arr and returns a
Counter object. This Counter object behaves just like a dictionary, mapping
elements to their counts.

Which method to choose?

• For simplicity and efficiency, especially in production code, collections.Counter


is highly recommended. It's optimized in C and handles edge cases gracefully.

• 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.

9 ) Sorting elements of an array by frequency .


Method 1 :

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).

Here, we'll provide a solution that sorts:

1. Primarily by frequency in descending order (most frequent first).

2. Secondarily by the element's value in ascending order (for ties in frequency,


smaller values come first).

We'll use collections.Counter to efficiently get frequencies and then the sorted() function
with a custom key.

Python

from collections import Counter

def sort_by_frequency(arr):

"""

Sorts the elements of an array by their frequency.


Elements with higher frequency come first.

For elements with the same frequency, they are sorted by their value in ascending order.

Args:

arr: A list of elements.

Returns:

A new list with elements sorted by frequency.

"""

if not arr:

return []

# Step 1: Count the frequency of each element

# This creates a dictionary-like object: {element: count}

frequency_map = Counter(arr)

# Step 2: Define a custom sorting key

# The key for each element 'x' will be a tuple:

# (-frequency_map[x], x)

# - By negating the frequency, we achieve descending order for frequency.

# - 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.

# Step 3: Sort the original array using the custom key

# 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:

list1 = [2, 3, 2, 4, 5, 12, 2, 3, 3, 3, 12]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple', 'grape']

list3 = [1, 1, 1, 2, 2, 3]

list4 = [5, 4, 3, 2, 1] # All unique

list5 = [7, 7, 7, 7] # All same

empty_list = []

list_mixed_ties = [1, 2, 3, 1, 2, 4] # 1 and 2 have freq 2, 3 and 4 have freq 1

print(f"Original: {list1} -> Sorted by Frequency: {sort_by_frequency(list1)}")

# Expected: [3, 3, 3, 3, 2, 2, 2, 12, 12, 4, 5] (Order of 4 and 5 might vary depending on


stability, here values break ties)

# Correct output: [3, 3, 3, 3, 2, 2, 2, 12, 12, 4, 5] (3 (4x), 2 (3x), 12 (2x), 4 (1x), 5 (1x))

print(f"Original: {list2} -> Sorted by Frequency: {sort_by_frequency(list2)}")

# Correct output: ['apple', 'apple', 'apple', 'banana', 'banana', 'grape', 'orange']

print(f"Original: {list3} -> Sorted by Frequency: {sort_by_frequency(list3)}")

# Correct output: [1, 1, 1, 2, 2, 3]

print(f"Original: {list4} -> Sorted by Frequency: {sort_by_frequency(list4)}")

# Correct output: [1, 2, 3, 4, 5] (all have frequency 1, sorted by value)


print(f"Original: {list5} -> Sorted by Frequency: {sort_by_frequency(list5)}")

# Correct output: [7, 7, 7, 7]

print(f"Original: {empty_list} -> Sorted by Frequency: {sort_by_frequency(empty_list)}")

print(f"Original: {list_mixed_ties} -> Sorted by Frequency:


{sort_by_frequency(list_mixed_ties)}")

# 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:

1. from collections import Counter: We import the Counter class, which is a


specialized dictionary subclass used for counting hashable objects.

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).

3. sorted(arr, key=lambda x: (-frequency_map[x], x)):

o sorted(): This is a built-in Python function that returns a new sorted list (it
does not modify the original arr).

o key=lambda x: ...: The key argument specifies a function to be called on each


element of the list before comparisons are made. The lambda creates an
anonymous function.

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:

▪ -frequency_map[x]: We use the negative of its frequency. When


sorting tuples, Python compares the first element first. By making the
frequency negative, higher original frequencies will result in smaller
negative numbers, effectively sorting them in descending order.

▪ 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.

Method 2: Manual Frequency Counting then Sort by Item-Frequency Pairs

This method involves:

1. Manually counting frequencies using a standard dictionary.

2. Creating a list of tuples, where each tuple contains (element, 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.

Elements with higher frequency come first.

For elements with the same frequency, they are sorted by their value in ascending order.

Args:

arr: A list of elements.

Returns:

A new list with elements sorted by frequency.

"""

if not arr:
return []

# Step 1: Manually count frequencies using a dictionary

frequency_map = {}

for item in arr:

frequency_map[item] = frequency_map.get(item, 0) + 1 # Efficiently get and increment


count

# Step 2: Create a list of (element, frequency) pairs

# Example: For arr = [2, 3, 2, 4], frequency_map = {2: 2, 3: 1, 4: 1}

# items_with_freq = [(2, 2), (3, 1), (4, 1)]

items_with_freq = list(frequency_map.items())

# Step 3: Sort this list of pairs

# Sort primarily by frequency (descending: -freq) and secondarily by element (ascending:


elem)

# This makes it similar to the lambda key used with Counter, but applied to (item, freq)
pairs.

items_with_freq.sort(key=lambda x: (-x[1], x[0]))

# x[0] is the element, x[1] is the frequency

# Step 4: Construct the final sorted array

sorted_arr = []

for item, freq in items_with_freq:

sorted_arr.extend([item] * freq) # Add the item 'freq' number of times

return sorted_arr
# Example Usage:

list1 = [2, 3, 2, 4, 5, 12, 2, 3, 3, 3, 12]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple', 'grape']

list3 = [1, 1, 1, 2, 2, 3]

list4 = [5, 4, 3, 2, 1] # All unique

list5 = [7, 7, 7, 7] # All same

empty_list = []

list_mixed_ties = [1, 2, 3, 1, 2, 4]

print(f"Manual Dict & Sort: Original: {list1} -> Sorted by Frequency:


{sort_by_frequency_manual_dict(list1)}")

print(f"Manual Dict & Sort: Original: {list2} -> Sorted by Frequency:


{sort_by_frequency_manual_dict(list2)}")

print(f"Manual Dict & Sort: Original: {list3} -> Sorted by Frequency:


{sort_by_frequency_manual_dict(list3)}")

print(f"Manual Dict & Sort: Original: {list4} -> Sorted by Frequency:


{sort_by_frequency_manual_dict(list4)}")

print(f"Manual Dict & Sort: Original: {list5} -> Sorted by Frequency:


{sort_by_frequency_manual_dict(list5)}")

print(f"Manual Dict & Sort: Original: {empty_list} -> Sorted by Frequency:


{sort_by_frequency_manual_dict(empty_list)}")

print(f"Manual Dict & Sort: Original: {list_mixed_ties} -> Sorted by Frequency:


{sort_by_frequency_manual_dict(list_mixed_ties)}")

Key Differences from collections.Counter method:

• Explicit Steps: This method explicitly shows the steps of counting, creating pairs,
sorting pairs, and then rebuilding the list.

• Performance: While functionally similar, collections.Counter is implemented in C


and is highly optimized, making it generally faster for larger datasets than manual
Python dictionary operations. The sorted() function with a custom key on the original
list (Counter method) often performs fewer comparisons overall than sorting a
separate list of tuples and then extending.

• Conciseness: The collections.Counter method is significantly more concise.

This alternative method is valuable for understanding the mechanics behind frequency
sorting, even if collections.Counter is the preferred practical choice in Python.

10 ) Finding the Longest Palindrome in an Array

To find the longest palindrome in an array of numbers, we need to:

1. Define what constitutes a "palindrome" for a number (it reads the same forwards
and backward).

2. Define "length" for a number (its number of digits).

3. Iterate through the array, check each number, and keep track of the longest
palindrome found so far.

Here's the Python code to achieve this:

Python

def is_palindrome(n):

"""

Checks if a given number is a palindrome.

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:

arr: A list of integers.

Returns:

The longest palindrome (by number of digits). If multiple palindromes have the same
maximum length,

the one with the largest numerical value is returned.

Returns -1 if no palindromes are found in the array.

"""

longest_palindrome = -1 # Initialize with -1, indicating no palindrome found yet

max_length = 0 # Initialize max length found so far

for num in arr:

if is_palindrome(num):

current_length = len(str(num)) # Length of the number (number of digits)

if current_length > max_length:

# Found a new longest palindrome

max_length = current_length

longest_palindrome = num

elif current_length == max_length:

# Found a palindrome of the same maximum length


# Choose the one with the larger numerical value

longest_palindrome = max(longest_palindrome, num)

return longest_palindrome

# Example Usage:

print(f"Test Case 1: [121, 10, 202, 11]")

print(f"Longest Palindrome: {find_longest_palindrome_in_array([121, 10, 202, 11])}") #


Expected: 202 (length 3, 202 > 121)

print(f"\nTest Case 2: [1, 2, 3, 4, 5, 6, 7, 8, 9]")

print(f"Longest Palindrome: {find_longest_palindrome_in_array([1, 2, 3, 4, 5, 6, 7, 8, 9])}") #


Expected: 9 (all length 1, 9 is largest value)

print(f"\nTest Case 3: [100, 123, 454, 789789, 12321]")

print(f"Longest Palindrome: {find_longest_palindrome_in_array([100, 123, 454, 789789,


12321])}") # Expected: 12321 (length 5)

print(f"\nTest Case 4: [12, 34, 56, 78]")

print(f"Longest Palindrome: {find_longest_palindrome_in_array([12, 34, 56, 78])}") #


Expected: -1 (no palindromes)

print(f"\nTest Case 5: []")

print(f"Longest Palindrome: {find_longest_palindrome_in_array([])}") # Expected: -1 (empty


array)

print(f"\nTest Case 6: [101, 202, 303]")


print(f"Longest Palindrome: {find_longest_palindrome_in_array([101, 202, 303])}") #
Expected: 303 (all length 3, 303 is largest)

print(f"\nTest Case 7: [1001, 999]")

print(f"Longest Palindrome: {find_longest_palindrome_in_array([1001, 999])}") # Expected:


1001 (length 4 vs length 3)

print(f"\nTest Case 8: [0, 10, 00, 101]") # Note: 00 might be interpreted as 0 in Python lists,
which is a palindrome.

print(f"Longest Palindrome: {find_longest_palindrome_in_array([0, 10, 101])}") # Expected:


101

Explanation:

1. is_palindrome(n) function:

o Takes an integer n as input.

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.

o Returns True if they are equal (it's a palindrome), False otherwise.

2. find_longest_palindrome_in_array(arr) function:

o Initialization:

▪ longest_palindrome = -1: This variable will store the longest


palindrome found. It's initialized to -1 to handle cases where no
palindromes are present in the array.

▪ max_length = 0: This variable keeps track of the maximum number of


digits encountered for a palindrome so far.

o Iteration:

▪ It loops through each num in the input arr.

o Palindrome Check:
▪ if is_palindrome(num): calls the helper function to determine if the
current number is a palindrome.

o Length Comparison and Update:

▪ current_length = len(str(num)): If num is a palindrome, its length (digit


count) is determined.

▪ if current_length > max_length:: If the current palindrome's length is


greater than the max_length found so far, it means we've found a new
longest palindrome. We update max_length and set
longest_palindrome to this num.

▪ elif current_length == max_length:: If the current palindrome has the


same length as the max_length found previously, we need a tie-
breaking rule. The problem usually implies returning the numerically
largest value in such a scenario. So, longest_palindrome =
max(longest_palindrome, num) updates longest_palindrome to the
larger of the two equally long palindromes.

o Return Value: After checking all numbers in the array, the function returns
the longest_palindrome found.

11 ) Counting Distinct Elements in an Array

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):

"""

Counts the number of distinct elements in an array using a set.


Args:

arr: A list of elements.

Returns:

The count of distinct elements.

"""

return len(set(arr))

# Example Usage:

list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana', 'grape']

list3 = [10, 20, 30]

list4 = [5, 5, 5, 5, 5]

empty_list = []

list_mixed = [1, 'a', 1, 'b', 'a', 2]

print(f"Using set(): Distinct elements in {list1} -> {count_distinct_elements_set(list1)}")

print(f"Using set(): Distinct elements in {list2} -> {count_distinct_elements_set(list2)}")

print(f"Using set(): Distinct elements in {list3} -> {count_distinct_elements_set(list3)}")

print(f"Using set(): Distinct elements in {list4} -> {count_distinct_elements_set(list4)}")

print(f"Using set(): Distinct elements in {empty_list} ->


{count_distinct_elements_set(empty_list)}")

print(f"Using set(): Distinct elements in {list_mixed} ->


{count_distinct_elements_set(list_mixed)}")
Explanation: This method leverages the core property of sets: they only store unique
values. When you do set(arr), Python iterates through arr and adds each unique element to
the set. len() then simply tells you how many unique elements were added.

Method 2: Using a Loop and a Helper Set (Manual Approach)

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:

arr: A list of elements.

Returns:

The count of distinct elements.

"""

seen_elements = set() # Use a set for efficient O(1) average time lookups

distinct_count = 0

for item in arr:

if item not in seen_elements:

seen_elements.add(item)

distinct_count += 1

return distinct_count
# Example Usage:

list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana', 'grape']

list3 = [10, 20, 30]

list4 = [5, 5, 5, 5, 5]

empty_list = []

list_mixed = [1, 'a', 1, 'b', 'a', 2]

print(f"\nUsing loop: Distinct elements in {list1} -> {count_distinct_elements_loop(list1)}")

print(f"Using loop: Distinct elements in {list2} -> {count_distinct_elements_loop(list2)}")

print(f"Using loop: Distinct elements in {list3} -> {count_distinct_elements_loop(list3)}")

print(f"Using loop: Distinct elements in {list4} -> {count_distinct_elements_loop(list4)}")

print(f"Using loop: Distinct elements in {empty_list} ->


{count_distinct_elements_loop(empty_list)}")

print(f"Using loop: Distinct elements in {list_mixed} ->


{count_distinct_elements_loop(list_mixed)}")

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).

Recommendation: For most Python applications, Method 1 using len(set(arr)) is the


preferred way due to its conciseness, readability, and efficiency (as it's implemented in
highly optimized C code under the hood). Method 2 is good for understanding the
underlying algorithm.

12 ) Finding Repeating elements in an Array


To find repeating elements in an array (Python list), we need to identify all elements that
appear more than once. Here are two effective methods:

Method 1: Using collections.Counter (Most Pythonic and Efficient)

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

from collections import Counter

def find_repeating_elements_counter(arr):

"""

Finds repeating elements in an array using collections.Counter.

Args:

arr: A list of elements.

Returns:

A list of unique repeating elements.

"""

if not arr:

return []

# Count the frequency of each element

counts = Counter(arr)

repeating_elements = []

# Iterate through the (element, count) pairs


for item, count in counts.items():

if count > 1:

repeating_elements.append(item)

return repeating_elements

# Example Usage:

list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3, 1]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple', 'grape']

list3 = [10, 20, 30] # No repeating elements

list4 = [5, 5, 5, 5]

empty_list = []

list_mixed = [1, 'a', 1, 'b', 'a', 2]

print(f"Using Counter: Repeating elements in {list1} ->


{find_repeating_elements_counter(list1)}")

# Expected: [1, 2, 3] (order may vary as Counter uses dict, which is insertion order for
Python 3.7+)

print(f"Using Counter: Repeating elements in {list2} ->


{find_repeating_elements_counter(list2)}")

print(f"Using Counter: Repeating elements in {list3} ->


{find_repeating_elements_counter(list3)}")

print(f"Using Counter: Repeating elements in {list4} ->


{find_repeating_elements_counter(list4)}")

print(f"Using Counter: Repeating elements in {empty_list} ->


{find_repeating_elements_counter(empty_list)}")

print(f"Using Counter: Repeating elements in {list_mixed} ->


{find_repeating_elements_counter(list_mixed)}")
Method 2: Using a Dictionary and a Set (Single Pass)

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:

arr: A list of elements.

Returns:

A list of unique repeating elements.

"""

if not arr:

return []

counts = {}

repeating_elements_set = set() # To store unique repeating elements

for item in arr:

counts[item] = counts.get(item, 0) + 1 # Increment count

# 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]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple', 'grape']

list3 = [10, 20, 30]

list4 = [5, 5, 5, 5]

empty_list = []

list_mixed = [1, 'a', 1, 'b', 'a', 2]

print(f"\nUsing Single Pass: Repeating elements in {list1} ->


{find_repeating_elements_single_pass(list1)}")

print(f"Using Single Pass: Repeating elements in {list2} ->


{find_repeating_elements_single_pass(list2)}")

print(f"Using Single Pass: Repeating elements in {list3} ->


{find_repeating_elements_single_pass(list3)}")

print(f"Using Single Pass: Repeating elements in {list4} ->


{find_repeating_elements_single_pass(list4)}")

print(f"Using Single Pass: Repeating elements in {empty_list} ->


{find_repeating_elements_single_pass(empty_list)}")

print(f"Using Single Pass: Repeating elements in {list_mixed} ->


{find_repeating_elements_single_pass(list_mixed)}")

Which method to choose?


• collections.Counter (Method 1): This is generally the most recommended
approach in Python due to its conciseness, readability, and performance. Counter is
highly optimized for this kind of task.

• 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.

13 ) Finding Non Repeating elements in an Array

To find non-repeating (or unique) elements in an array, you're looking for elements that
appear exactly once. Here are two effective methods:

Method 1: Using collections.Counter (Most Pythonic and Efficient)

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

from collections import Counter

def find_non_repeating_elements_counter(arr):

"""

Finds non-repeating elements in an array using collections.Counter.

Args:

arr: A list of elements.

Returns:

A list of unique non-repeating elements, preserving their first appearance order.

"""
if not arr:

return []

# Count the frequency of each element

counts = Counter(arr)

non_repeating_elements = []

# Iterate through the original array to maintain order of non-repeating elements

for item in arr:

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]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana', 'grape', 'mango']

list3 = [10, 20, 30] # All are non-repeating

list4 = [5, 5, 5, 5] # No non-repeating elements

empty_list = []

list_mixed = [1, 'a', 1, 'b', 'a', 2, 'c']

print(f"Using Counter: Non-repeating elements in {list1} ->


{find_non_repeating_elements_counter(list1)}")

# Expected: [4, 5] (order preserved)

print(f"Using Counter: Non-repeating elements in {list2} ->


{find_non_repeating_elements_counter(list2)}")
print(f"Using Counter: Non-repeating elements in {list3} ->
{find_non_repeating_elements_counter(list3)}")

print(f"Using Counter: Non-repeating elements in {list4} ->


{find_non_repeating_elements_counter(list4)}")

print(f"Using Counter: Non-repeating elements in {empty_list} ->


{find_non_repeating_elements_counter(empty_list)}")

print(f"Using Counter: Non-repeating elements in {list_mixed} ->


{find_non_repeating_elements_counter(list_mixed)}")

Method 2: Using a Dictionary for Counts (Two Passes)

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):

"""

Finds non-repeating elements in an array using a dictionary (two passes).

Args:

arr: A list of elements.

Returns:

A list of unique non-repeating elements, preserving their first appearance order.

"""

if not arr:

return []

# Pass 1: Count frequencies of all elements


counts = {}

for item in arr:

counts[item] = counts.get(item, 0) + 1

non_repeating_elements = []

# Pass 2: Iterate through the original array and collect elements with count 1

for item in arr:

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]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana', 'grape', 'mango']

list3 = [10, 20, 30]

list4 = [5, 5, 5, 5]

empty_list = []

list_mixed = [1, 'a', 1, 'b', 'a', 2, 'c']

print(f"\nUsing Two-Pass Dict: Non-repeating elements in {list1} ->


{find_non_repeating_elements_two_pass_dict(list1)}")

print(f"Using Two-Pass Dict: Non-repeating elements in {list2} ->


{find_non_repeating_elements_two_pass_dict(list2)}")

print(f"Using Two-Pass Dict: Non-repeating elements in {list3} ->


{find_non_repeating_elements_two_pass_dict(list3)}")
print(f"Using Two-Pass Dict: Non-repeating elements in {list4} ->
{find_non_repeating_elements_two_pass_dict(list4)}")

print(f"Using Two-Pass Dict: Non-repeating elements in {empty_list} ->


{find_non_repeating_elements_two_pass_dict(empty_list)}")

print(f"Using Two-Pass Dict: Non-repeating elements in {list_mixed} ->


{find_non_repeating_elements_two_pass_dict(list_mixed)}")

Which method to choose?

• collections.Counter (Method 1): This is generally the preferred and most


Pythonic way. It's concise, very readable, and highly optimized for performance,
especially for large arrays.

• 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.

14 ) Removing Duplicate elements from an array

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.

Here are two common methods:

Method 1: Using set() (Does NOT Preserve Order)

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):

"""

Removes duplicate elements from an array using a set.

Note: This method does NOT preserve the original order of elements.
Args:

arr: A list of elements.

Returns:

A new list containing only the unique elements, in arbitrary order.

"""

return list(set(arr))

# Example Usage:

list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana']

list3 = [10, 20, 30] # No duplicates

list4 = [5, 5, 5, 5]

empty_list = []

list_mixed = [1, 'a', 1, 'b', 'a', 2]

print(f"Using set() (Order NOT preserved):")

print(f"Original: {list1} -> Without Duplicates: {remove_duplicates_using_set(list1)}")

print(f"Original: {list2} -> Without Duplicates: {remove_duplicates_using_set(list2)}")

print(f"Original: {list3} -> Without Duplicates: {remove_duplicates_using_set(list3)}")

print(f"Original: {list4} -> Without Duplicates: {remove_duplicates_using_set(list4)}")

print(f"Original: {empty_list} -> Without Duplicates:


{remove_duplicates_using_set(empty_list)}")

print(f"Original: {list_mixed} -> Without Duplicates:


{remove_duplicates_using_set(list_mixed)}")

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.

Method 2: Using a Loop and a Set (Preserves Order)

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:

arr: A list of elements.

Returns:

A new list containing only the unique elements, in their original order of appearance.

"""

seen = set()

unique_list = []

for item in arr:

if item not in seen:

seen.add(item)

unique_list.append(item)

return unique_list
# Example Usage:

list1 = [1, 2, 2, 3, 1, 4, 2, 5, 3]

list2 = ['apple', 'banana', 'apple', 'orange', 'banana']

list3 = [10, 20, 30]

list4 = [5, 5, 5, 5]

empty_list = []

list_mixed = [1, 'a', 1, 'b', 'a', 2]

print(f"\nUsing loop and set (Order PRESERVED):")

print(f"Original: {list1} -> Without Duplicates: {remove_duplicates_preserve_order(list1)}")

# Expected: [1, 2, 3, 4, 5]

print(f"Original: {list2} -> Without Duplicates: {remove_duplicates_preserve_order(list2)}")

# Expected: ['apple', 'banana', 'orange']

print(f"Original: {list3} -> Without Duplicates: {remove_duplicates_preserve_order(list3)}")

print(f"Original: {list4} -> Without Duplicates: {remove_duplicates_preserve_order(list4)}")

print(f"Original: {empty_list} -> Without Duplicates:


{remove_duplicates_preserve_order(empty_list)}")

print(f"Original: {list_mixed} -> Without Duplicates:


{remove_duplicates_preserve_order(list_mixed)}")

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.

3. The loop iterates through arr. For each item:


o if item not in seen:: Checks if the element has already been added to
unique_list.

o seen.add(item): If not seen, the element is added to the seen set.

o unique_list.append(item): The element is also appended to unique_list.

Which method to choose?

• If order doesn't matter: Use list(set(arr)). It's the simplest and generally fastest.

• If order must be preserved: Use the loop with a set


(remove_duplicates_preserve_order).

15 ) Finding Minimum scalar product of two vectors

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

Finding the Minimum Scalar Product

To find the minimum scalar product of two vectors, the strategy is as follows:

1. Sort one vector in ascending order.

2. Sort the other vector in descending order.

3. Calculate the scalar product of the sorted vectors.

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).

A more intuitive way: (x1−x2)(y1−y2)≥0 since x1<x2⟹(x1−x2)<0 and y1<y2⟹(y1−y2)<0. The


product of two negatives is positive. Expanding this: x1y1−x1y2−x2y1+x2y2≥0 Rearranging:
x1y1+x2y2≥x1y2+x2y1
This inequality shows that pairing the smallest with the smallest and largest with the
largest (x1y1+x2y2) gives a larger sum than pairing the smallest with the largest and largest
with the smallest (x1y2+x2y1). Therefore, to minimize the sum, we want to pair the smallest
elements of one vector with the largest elements of the other, and vice-versa. This is
achieved by sorting one ascending and the other descending.

Python Implementation

Python

def min_scalar_product(vec1, vec2):

"""

Calculates the minimum scalar product of two vectors.

To achieve the minimum scalar product, one vector is sorted in ascending order

and the other in descending order.

Args:

vec1: A list representing the first vector.

vec2: A list representing the second vector.

Returns:

The minimum scalar product (an integer or float),

or an error message if the vectors are not of equal length.

"""

if len(vec1) != len(vec2):

return "Error: Vectors must be of equal length."

n = len(vec1)
# Step 1: Sort vec1 in ascending order

sorted_vec1 = sorted(vec1)

# Step 2: Sort vec2 in descending order

sorted_vec2 = sorted(vec2, reverse=True)

# Step 3: Calculate the scalar product

min_product = 0

for i in range(n):

min_product += sorted_vec1[i] * sorted_vec2[i]

return min_product

# Example Usage:

vector_a = [1, 2, 3]

vector_b = [4, 5, 6]

print(f"Vectors: {vector_a}, {vector_b}")

print(f"Minimum scalar product: {min_scalar_product(vector_a, vector_b)}")

# Expected: (1*6) + (2*5) + (3*4) = 6 + 10 + 12 = 28

vector_c = [10, 20, 30]

vector_d = [1, 2, 3]

print(f"\nVectors: {vector_c}, {vector_d}")

print(f"Minimum scalar product: {min_scalar_product(vector_c, vector_d)}")

# Expected: (10*3) + (20*2) + (30*1) = 30 + 40 + 30 = 100


vector_e = [7, 1, 4]

vector_f = [2, 8, 5]

print(f"\nVectors: {vector_e}, {vector_f}")

print(f"Minimum scalar product: {min_scalar_product(vector_e, vector_f)}")

# Sorted vec_e (asc): [1, 4, 7]

# Sorted vec_f (desc): [8, 5, 2]

# Expected: (1*8) + (4*5) + (7*2) = 8 + 20 + 14 = 42

vector_g = [1, 2, 3, 4]

vector_h = [5, 6, 7] # Different lengths

print(f"\nVectors: {vector_g}, {vector_h}")

print(f"Minimum scalar product: {min_scalar_product(vector_g, vector_h)}")

16 ) Finding Maximum scalar product of two vectors in an


array

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

Finding the Maximum Scalar Product

To achieve the maximum scalar product:

1. Sort both vectors in the same order (either both ascending or both descending).

2. Calculate the scalar product of the sorted vectors.

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

def max_scalar_product(vec1, vec2):

"""

Calculates the maximum scalar product of two vectors.

To achieve the maximum scalar product, both vectors are sorted in the same order

(e.g., both ascending).

Args:

vec1: A list representing the first vector.

vec2: A list representing the second vector.

Returns:

The maximum scalar product (an integer or float),

or an error message if the vectors are not of equal length.

"""

if len(vec1) != len(vec2):

return "Error: Vectors must be of equal length."

n = len(vec1)

# Step 1: Sort both vectors in ascending order

# (Sorting both in descending order would yield the same result)


sorted_vec1 = sorted(vec1)

sorted_vec2 = sorted(vec2)

# Step 2: Calculate the scalar product

max_product = 0

for i in range(n):

max_product += sorted_vec1[i] * sorted_vec2[i]

return max_product

# Example Usage:

vector_a = [1, 2, 3]

vector_b = [4, 5, 6]

print(f"Vectors: {vector_a}, {vector_b}")

print(f"Maximum scalar product: {max_scalar_product(vector_a, vector_b)}")

# Sorted a: [1, 2, 3]

# Sorted b: [4, 5, 6]

# Expected: (1*4) + (2*5) + (3*6) = 4 + 10 + 18 = 32

vector_c = [10, 20, 30]

vector_d = [1, 2, 3]

print(f"\nVectors: {vector_c}, {vector_d}")

print(f"Maximum scalar product: {max_scalar_product(vector_c, vector_d)}")

# Sorted c: [10, 20, 30]

# Sorted d: [1, 2, 3]

# Expected: (10*1) + (20*2) + (30*3) = 10 + 40 + 90 = 140


vector_e = [7, 1, 4]

vector_f = [2, 8, 5]

print(f"\nVectors: {vector_e}, {vector_f}")

print(f"Maximum scalar product: {max_scalar_product(vector_e, vector_f)}")

# Sorted vec_e: [1, 4, 7]

# Sorted vec_f: [2, 5, 8]

# Expected: (1*2) + (4*5) + (7*8) = 2 + 20 + 56 = 78

vector_g = [1, 2, 3, 4]

vector_h = [5, 6, 7] # Different lengths

print(f"\nVectors: {vector_g}, {vector_h}")

print(f"Maximum scalar product: {max_scalar_product(vector_g, vector_h)}")

17 ) Counting the number of even and odd elements in an


array

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.

Here are two common methods:

Method 1: Using a Loop (Iterating and Counting)

This is a straightforward and explicit way to count.

Python

def count_even_odd_loop(arr):

"""

Counts the number of even and odd elements in an array using a loop.
Args:

arr: A list of integers.

(Note: Non-integer types might cause TypeError with modulo operator.)

Returns:

A tuple containing (even_count, odd_count).

"""

even_count = 0

odd_count = 0

for num in arr:

# We should ensure the element is an integer for modulo operation

if isinstance(num, int):

if num % 2 == 0:

even_count += 1

else:

odd_count += 1

# You could add an else-block here to handle non-integer elements

# print(f"Warning: Non-integer element '{num}' skipped.")

return even_count, odd_count

# Example Usage:

list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


list2 = [0, 2, 4, 6] # All even

list3 = [1, 3, 5, 7] # All odd

list4 = [] # Empty list

list5 = [100] # Single element

list6 = [1, 2, 3.5, 4] # Mixed types (will count 3.5 as neither, based on isinstance check)

even, odd = count_even_odd_loop(list1)

print(f"Using loop: In {list1}, Even: {even}, Odd: {odd}") # Expected: Even: 5, Odd: 5

even, odd = count_even_odd_loop(list2)

print(f"Using loop: In {list2}, Even: {even}, Odd: {odd}") # Expected: Even: 4, Odd: 0

even, odd = count_even_odd_loop(list3)

print(f"Using loop: In {list3}, Even: {even}, Odd: {odd}") # Expected: Even: 0, Odd: 4

even, odd = count_even_odd_loop(list4)

print(f"Using loop: In {list4}, Even: {even}, Odd: {odd}") # Expected: Even: 0, Odd: 0

even, odd = count_even_odd_loop(list5)

print(f"Using loop: In {list5}, Even: {even}, Odd: {odd}") # Expected: Even: 1, Odd: 0

even, odd = count_even_odd_loop(list6)

print(f"Using loop: In {list6}, Even: {even}, Odd: {odd}") # Expected: Even: 2, Odd: 1 (3.5 is
skipped)

Method 2: Using List Comprehension and len() (More Pythonic)

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:

arr: A list of integers.

Returns:

A tuple containing (even_count, odd_count).

"""

# Filter for integers first to avoid errors with modulo

integers_only = [num for num in arr if isinstance(num, int)]

even_count = len([num for num in integers_only if num % 2 == 0])

odd_count = len([num for num in integers_only if num % 2 != 0])

return even_count, odd_count

# Example Usage:

list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

list2 = [0, 2, 4, 6]

list3 = [1, 3, 5, 7]

list4 = []

list5 = [100]

list6 = [1, 2, 3.5, 4]


print(f"\nUsing list comprehension: In {list1}, Even:
{count_even_odd_comprehension(list1)[0]}, Odd:
{count_even_odd_comprehension(list1)[1]}")

print(f"Using list comprehension: In {list2}, Even:


{count_even_odd_comprehension(list2)[0]}, Odd:
{count_even_odd_comprehension(list2)[1]}")

print(f"Using list comprehension: In {list3}, Even:


{count_even_odd_comprehension(list3)[0]}, Odd:
{count_even_odd_comprehension(list3)[1]}")

print(f"Using list comprehension: In {list4}, Even:


{count_even_odd_comprehension(list4)[0]}, Odd:
{count_even_odd_comprehension(list4)[1]}")

print(f"Using list comprehension: In {list5}, Even:


{count_even_odd_comprehension(list5)[0]}, Odd:
{count_even_odd_comprehension(list5)[1]}")

print(f"Using list comprehension: In {list6}, Even:


{count_even_odd_comprehension(list6)[0]}, Odd:
{count_even_odd_comprehension(list6)[1]}")

Which method to choose?

• 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.

Here are two common methods to achieve this:

Method 1: Using a Loop (Iterating and Summing)

This is a straightforward and explicit way to calculate the sums.

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:

arr: A list of numbers.

(Note: Non-integer types are skipped as even/odd applies to integers.)

Returns:

A tuple containing (even_sum, odd_sum).

"""

even_sum = 0

odd_sum = 0

for num in arr:

# Ensure the element is an integer for even/odd check


if isinstance(num, int):

if num % 2 == 0:

even_sum += num

else:

odd_sum += num

# Optional: print a warning for non-integer elements

# else:

# print(f"Warning: Non-integer element '{num}' skipped from sum.")

return even_sum, odd_sum

# Example Usage:

list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

list2 = [0, 2, 4, 6] # All even

list3 = [1, 3, 5, 7] # All odd

list4 = [] # Empty list

list5 = [100] # Single element

list6 = [-1, -2, -3, -4] # Negative numbers

list7 = [1, 2, 3.5, 4, 'a'] # Mixed types

even_s, odd_s = sum_even_odd_loop(list1)

print(f"Using loop: In {list1}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even: 30,
Odd: 25

even_s, odd_s = sum_even_odd_loop(list2)

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

even_s, odd_s = sum_even_odd_loop(list4)

print(f"Using loop: In {list4}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even: 0,
Odd: 0

even_s, odd_s = sum_even_odd_loop(list5)

print(f"Using loop: In {list5}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even:
100, Odd: 0

even_s, odd_s = sum_even_odd_loop(list6)

print(f"Using loop: In {list6}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even: -6,
Odd: -4

even_s, odd_s = sum_even_odd_loop(list7)

print(f"Using loop: In {list7}, Even Sum: {even_s}, Odd Sum: {odd_s}") # Expected: Even: 6,
Odd: 1 (3.5 and 'a' skipped)

Method 2: Using List Comprehension and sum() (More Pythonic)

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:

arr: A list of numbers.

Returns:

A tuple containing (even_sum, odd_sum).

"""

# First, filter the array to include only integers for even/odd checks

integers_only = [num for num in arr if isinstance(num, int)]

even_sum = sum(num for num in integers_only if num % 2 == 0)

odd_sum = sum(num for num in integers_only if num % 2 != 0)

return even_sum, odd_sum

# Example Usage:

list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

list2 = [0, 2, 4, 6]

list3 = [1, 3, 5, 7]

list4 = []

list5 = [100]

list6 = [-1, -2, -3, -4]

list7 = [1, 2, 3.5, 4, 'a']

even_s, odd_s = sum_even_odd_comprehension(list1)

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}")

even_s, odd_s = sum_even_odd_comprehension(list3)

print(f"Using list comprehension: In {list3}, Even Sum: {even_s}, Odd Sum: {odd_s}")

even_s, odd_s = sum_even_odd_comprehension(list4)

print(f"Using list comprehension: In {list4}, Even Sum: {even_s}, Odd Sum: {odd_s}")

even_s, odd_s = sum_even_odd_comprehension(list5)

print(f"Using list comprehension: In {list5}, Even Sum: {even_s}, Odd Sum: {odd_s}")

even_s, odd_s = sum_even_odd_comprehension(list6)

print(f"Using list comprehension: In {list6}, Even Sum: {even_s}, Odd Sum: {odd_s}")

even_s, odd_s = sum_even_odd_comprehension(list7)

print(f"Using list comprehension: In {list7}, Even Sum: {even_s}, Odd Sum: {odd_s}")

Which method to choose?

• 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.

Method: Using a Dictionary to Track Seen Pairs

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):

"""

Finds all symmetric pairs in an array of pairs.

A pair [a, b] is symmetric with [b, a].

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,

e.g., [[pair1, pair2]].

Each unique symmetric set {[a,b], [b,a]} will be reported once.

"""

# Store pairs seen so far.

# Key: tuple(pair) for hashability (e.g., (1,2))

# Value: the original list form of the pair (e.g., [1,2])


seen_pairs = {}

symmetric_pairs_found = []

for current_pair_list in arr_of_pairs:

a, b = current_pair_list[0], current_pair_list[1]

current_pair_tuple = tuple(current_pair_list) # Convert to tuple for dictionary key

reversed_pair_tuple = (b, a)

# Check if the symmetric counterpart has been seen

if reversed_pair_tuple in seen_pairs:

# If 'a' and 'b' are the same (e.g., [7,7]), it's symmetric with itself.

# We typically only care about a != b for 'symmetric pairs' in this context.

# 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])

# Remove the processed pair from seen_pairs to avoid reporting it again

# if one of its halves appears multiple times later.

del seen_pairs[reversed_pair_tuple]
else:

# If the symmetric counterpart is not found, store the current pair.

# 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]])

# Expected: [[[1, 2], [2, 1]], [[3, 4], [4, 3]]]

print(f"Symmetric pairs: {result1}")

print(f"\nTest Case 2: [[1, 2], [2, 1], [1, 2], [3, 4]]")

result2 = find_symmetric_pairs([[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)

print(f"Symmetric pairs: {result2}")

print(f"\nTest Case 3: [[1, 1], [2, 2], [1, 1]]")

result3 = find_symmetric_pairs([[1, 1], [2, 2], [1, 1]])

# Expected: [[[1, 1], [1, 1]]] (if [1,1] is considered symmetric with itself AND appears twice)

print(f"Symmetric pairs: {result3}")

print(f"\nTest Case 4: [[10, 20], [30, 40], [50, 60]]")

result4 = find_symmetric_pairs([[10, 20], [30, 40], [50, 60]])


# Expected: []

print(f"Symmetric pairs: {result4}")

print(f"\nTest Case 5: []")

result5 = find_symmetric_pairs([])

# Expected: []

print(f"Symmetric pairs: {result5}")

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 current_pair_tuple = tuple(current_pair_list): Converts the current list-pair to


a tuple for use as a dictionary key.

o reversed_pair_tuple = (b, a): Creates the tuple for the symmetric counterpart.

5. Checking for Symmetry:

o if reversed_pair_tuple in seen_pairs:: This is the core check. If the


reversed_pair_tuple (e.g., (2, 1)) exists as a key in seen_pairs, it means we
have previously encountered the [b, a] pair. Now that we've found [a, b], they
form a symmetric pair.

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.

o Storing Unmatched Pairs:

▪ else:: If the reversed_pair_tuple is not found in seen_pairs, it means


the current current_pair_tuple has not yet found its symmetric
counterpart. So, it's stored in seen_pairs for future checks.

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.

20 ) Find maximum product sub-array in a given array


Finding the maximum product sub-array in a given array is a classic problem that requires
careful handling of negative numbers and zeros. A naive brute-force approach (checking all
possible sub-arrays) would have a time complexity of O(N2) or O(N3), which is inefficient
for large arrays.

A more efficient solution uses dynamic programming concepts, specifically tracking the
maximum and minimum products ending at the current position.

The Logic Behind the Solution

The key challenge comes from negative numbers:

• A positive number times a positive number is positive.

• A negative number times a positive number is negative.

• A negative number times a negative number is positive.

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.

If we encounter a new number num:


• The new maximum product ending at num could be num itself (starting a new sub-
array), num multiplied by the previous maximum product, or num multiplied by the
previous minimum product (if num is negative).

• 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.

Algorithm (Optimal: O(N) Time, O(1) Space)

1. Initialize current_max_product and current_min_product to the first element of the


array.

2. Initialize global_max_product to the first element of the array.

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):

"""

Finds the maximum product of a contiguous sub-array in the given array.

Args:

nums: A list of integers (can be positive, negative, or zero).


Returns:

The maximum product of any contiguous sub-array.

Returns 0 if the array is empty.

"""

if not nums:

return 0 # An empty array has no sub-arrays, so product can be considered 0.

# Alternatively, raise ValueError("Empty array has no sub-arrays").

# Initialize current max and min product for the subarray ending at the current position.

# Also initialize the overall maximum product found so far.

current_max_product = nums[0]

current_min_product = nums[0]

global_max_product = nums[0]

# Iterate from the second element

for i in range(1, len(nums)):

num = nums[i]

# Store the old current_max_product because it's needed for current_min_product


calculation

temp_current_max = current_max_product

# Calculate the new current_max_product ending at 'num'

# It can be:

# 1. num itself (starting a new subarray)


# 2. num * previous_max_product (extending the max subarray)

# 3. num * previous_min_product (if num is negative, a small negative * negative


becomes large positive)

current_max_product = max(num, temp_current_max * num, current_min_product *


num)

# Calculate the new current_min_product ending at 'num'

# It can be:

# 1. num itself

# 2. num * previous_max_product (if num is negative, a large positive * negative becomes


large negative)

# 3. num * previous_min_product

current_min_product = min(num, temp_current_max * num, current_min_product * num)

# Update the overall maximum product found so far

global_max_product = max(global_max_product, current_max_product)

return global_max_product

# Example Usage:

print(f"Array: [2, 3, -2, 4]")

print(f"Maximum Product Sub-array: {max_product_subarray([2, 3, -2, 4])}") # Expected: 6


(from [2,3])

print(f"\nArray: [-2, 0, -1]")

print(f"Maximum Product Sub-array: {max_product_subarray([-2, 0, -1])}") # Expected: 0


(from [0])
print(f"\nArray: [-2, -3, -1]")

print(f"Maximum Product Sub-array: {max_product_subarray([-2, -3, -1])}") # Expected: 6


(from [-2,-3])

print(f"\nArray: [0, 2, 4, 0, 8]")

print(f"Maximum Product Sub-array: {max_product_subarray([0, 2, 4, 0, 8])}") # Expected: 8

print(f"\nArray: [7]")

print(f"Maximum Product Sub-array: {max_product_subarray([7])}") # Expected: 7

print(f"\nArray: [-1, -2, -9, -6]")

print(f"Maximum Product Sub-array: {max_product_subarray([-1, -2, -9, -6])}") # Expected:


108 (from [-1,-2,-9,-6])

print(f"\nArray: []")

print(f"Maximum Product Sub-array: {max_product_subarray([])}") # Expected: 0

21 )Finding Arrays are disjoint or not


Show thinking

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.

Here are two effective methods:

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

def are_arrays_disjoint_set(arr1, arr2):

"""

Checks if two arrays are disjoint (have no common elements) using sets.

Args:

arr1: The first list.

arr2: The second list.

Returns:

True if the arrays are disjoint, False otherwise.

"""

set1 = set(arr1)

set2 = set(arr2)

return set1.isdisjoint(set2)

# Example Usage:

print(f"--- Method 1: Using sets and isdisjoint() ---")

list_a = [1, 2, 3, 4]

list_b = [5, 6, 7, 8]

print(f"Are {list_a} and {list_b} disjoint? {are_arrays_disjoint_set(list_a, list_b)}") # Expected:


True

list_c = [10, 20, 30]

list_d = [30, 40, 50]


print(f"Are {list_c} and {list_d} disjoint? {are_arrays_disjoint_set(list_c, list_d)}") # Expected:
False

list_e = ['apple', 'banana']

list_f = ['orange', 'grape']

print(f"Are {list_e} and {list_f} disjoint? {are_arrays_disjoint_set(list_e, list_f)}") # Expected:


True

list_g = [1, 2, 3]

list_h = [1, 2, 3] # Identical lists

print(f"Are {list_g} and {list_h} disjoint? {are_arrays_disjoint_set(list_g, list_h)}") # Expected:


False

list_i = [] # Empty list

list_j = [1, 2, 3]

print(f"Are {list_i} and {list_j} disjoint? {are_arrays_disjoint_set(list_i, list_j)}") # Expected:


True (An empty set is disjoint with any other set)

list_k = [1, 2, 1] # Duplicates in one list

list_l = [3, 4, 3]

print(f"Are {list_k} and {list_l} disjoint? {are_arrays_disjoint_set(list_k, list_l)}") # Expected:


True

Method 2: Using a Loop and a Set for Lookup (Manual Check)

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

def are_arrays_disjoint_loop(arr1, arr2):

"""
Checks if two arrays are disjoint (have no common elements) using a loop and a set for
lookup.

Args:

arr1: The first list.

arr2: The second list.

Returns:

True if the arrays are disjoint, False otherwise.

"""

# Convert one of the arrays (preferably the smaller one, if known) into a set

# for efficient O(1) average time lookups.

set_for_lookup = set(arr1)

# Iterate through the elements of the second array

for item in arr2:

if item in set_for_lookup:

return False # Found a common element, so they are not disjoint

return True # No common elements were found after checking all elements in arr2

# Example Usage:

print(f"\n--- Method 2: Using a loop and a set for lookup ---")

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_c = [10, 20, 30]

list_d = [30, 40, 50]

print(f"Are {list_c} and {list_d} disjoint? {are_arrays_disjoint_loop(list_c, list_d)}")

list_e = ['apple', 'banana']

list_f = ['orange', 'grape']

print(f"Are {list_e} and {list_f} disjoint? {are_arrays_disjoint_loop(list_e, list_f)}")

list_g = [1, 2, 3]

list_h = [1, 2, 3]

print(f"Are {list_g} and {list_h} disjoint? {are_arrays_disjoint_loop(list_g, list_h)}")

list_i = []

list_j = [1, 2, 3]

print(f"Are {list_i} and {list_j} disjoint? {are_arrays_disjoint_loop(list_i, list_j)}")

list_k = [1, 2, 1]

list_l = [3, 4, 3]

print(f"Are {list_k} and {list_l} disjoint? {are_arrays_disjoint_loop(list_k, list_l)}")

Which method to choose?

• set1.isdisjoint(set2) (Method 1) is almost always preferred. It's the most


Pythonic, readable, and highly optimized. It handles all edge cases correctly,
including empty lists or lists with duplicates.

• 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.

22) Replace each element of the array by its rank in the


array

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.

Let's go with the common interpretation:

• The smallest unique element gets rank 1.

• The next smallest unique element gets rank 2, and so on.

• Elements with the same value get the same rank.

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:

arr: A list of comparable elements (e.g., numbers, strings).

Returns:

A new list where each element is replaced by its rank.

Returns an empty list if the input array is empty.

"""

if not arr:

return []

# Step 1: Get unique sorted values

# Convert to set to get unique elements, then to list to sort

unique_sorted_values = sorted(list(set(arr)))

# Step 2: Create a mapping from value to rank

# Ranks will be 1-based

rank_map = {value: i + 1 for i, value in enumerate(unique_sorted_values)}

# Step 3: Replace elements in the original array with their ranks

ranked_array = []

for element in arr:

ranked_array.append(rank_map[element])
return ranked_array

# Example Usage:

list1 = [10, 20, 10, 30, 5]

list2 = [1, 5, 2, 8, 3, 5]

list3 = [7, 7, 7, 7] # All same elements

list4 = [50, 40, 30, 20, 10] # Already sorted (descending)

list5 = ['banana', 'apple', 'orange', 'apple'] # String elements

empty_list = []

list_single_element = [100]

print(f"Original: {list1} -> Ranked: {replace_with_rank(list1)}")

# Expected: [2, 3, 2, 4, 1] (because 5->1, 10->2, 20->3, 30->4)

print(f"Original: {list2} -> Ranked: {replace_with_rank(list2)}")

# Expected: [1, 3, 2, 4, 3] (because 1->1, 2->2, 3->3, 5->4, 8->5)

# Note: if sorted order is [1,2,3,5,8], 5 is the 4th unique element, so rank 4.

# Error in my manual trace here; 1->1, 2->2, 3->3, 5->4, 8->5.

# For list2 = [1, 5, 2, 8, 3, 5]

# Unique sorted: [1,2,3,5,8]

# Rank map: {1:1, 2:2, 3:3, 5:4, 8:5}

# Result: [1, 4, 2, 5, 3, 4]

print(f"Original: {list3} -> Ranked: {replace_with_rank(list3)}")

# Expected: [1, 1, 1, 1]
print(f"Original: {list4} -> Ranked: {replace_with_rank(list4)}")

# Expected: [5, 4, 3, 2, 1]

print(f"Original: {list5} -> Ranked: {replace_with_rank(list5)}")

# Expected: [2, 1, 3, 1] (because 'apple'->1, 'banana'->2, 'orange'->3)

print(f"Original: {empty_list} -> Ranked: {replace_with_rank(empty_list)}")

print(f"Original: {list_single_element} -> Ranked: {replace_with_rank(list_single_element)}")

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 list(...): Converts the set back into a list.

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.

2. rank_map = {value: i + 1 for i, value in enumerate(unique_sorted_values)}:

o This is a dictionary comprehension that creates the mapping from each


unique value to its rank.

o enumerate(unique_sorted_values): Provides both the index (i) and the value


from the unique_sorted_values list.

o i + 1: Since enumerate starts indexing from 0, adding 1 makes the ranks 1-


based (e.g., index 0 gets rank 1, index 1 gets rank 2, etc.).

3. for element in arr: ranked_array.append(rank_map[element]):

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:

• The sum of elements in an empty sub-array is considered to be 0.

• For an element at index 0, the sum of elements at lower indices is 0.

• For an element at the last index n-1, the sum of elements at higher indices is 0.

Algorithm (Optimal: O(N) Time Complexity)

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.

2. Iterate and Check:

o Initialize a left_sum variable to 0.

o Iterate through the array from the first element to the last (index 0 to n-1).

o For each element at the current index i:

▪ The right_sum can be easily calculated using the formula: right_sum =


total_sum - left_sum - arr[i].

▪ If left_sum is equal to right_sum, then i is an equilibrium index. Add i


to a list of results.

▪ Before moving to the next element, update left_sum by adding the


current element arr[i] to it. This prepares left_sum for the next
iteration (where arr[i] will be part of the elements before arr[i+1]).

Python Implementation

Python

def find_equilibrium_indices(arr):

"""

Finds all equilibrium indices in an array.


An equilibrium index is an index i such that the sum of elements

at lower indices is equal to the sum of elements at higher indices.

(Empty sub-arrays sum to 0).

Args:

arr: A list of numbers.

Returns:

A list of equilibrium indices. Returns an empty list if no such indices are found.

"""

n = len(arr)

# Handle empty array case

if n == 0:

return []

# Step 1: Calculate the total sum of all elements in the array

total_sum = sum(arr)

# Initialize left_sum to 0 for the first element

left_sum = 0

# List to store all found equilibrium indices

equilibrium_indices = []

# Step 2: Iterate through the array and check for equilibrium


for i in range(n):

# Calculate the right_sum for the current index 'i'

# right_sum = (sum of all elements) - (sum of elements to the left of i) - (element at i)

right_sum = total_sum - left_sum - arr[i]

# Check if the current index 'i' is an equilibrium index

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

# --- Example Usage ---

print(f"Array: [-7, 1, 5, 2, -4, 3, 0]")

print(f"Equilibrium Indices: {find_equilibrium_indices([-7, 1, 5, 2, -4, 3, 0])}")

# Expected Output: [3, 6]

# 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.

print(f"\nArray: [1, 2, 3]")

print(f"Equilibrium Indices: {find_equilibrium_indices([1, 2, 3])}")

# Expected Output: []
print(f"\nArray: [1]")

print(f"Equilibrium Indices: {find_equilibrium_indices([1])}")

# Expected Output: [0] (Left sum = 0, Right sum = 0)

print(f"\nArray: []")

print(f"Equilibrium Indices: {find_equilibrium_indices([])}")

# Expected Output: []

print(f"\nArray: [0, 0, 0, 0]")

print(f"Equilibrium Indices: {find_equilibrium_indices([0, 0, 0, 0])}")

# Expected Output: [0, 1, 2, 3] (All indices are equilibrium points)

print(f"\nArray: [1, -1, 1, -1, 1]")

print(f"Equilibrium Indices: {find_equilibrium_indices([1, -1, 1, -1, 1])}")

# Expected Output: [2]

# Explanation: For index 2 (value 1): Left sum (1 + -1 = 0), Right sum (-1 + 1 = 0). Equal.

Time and Space Complexity:

• 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.

24 ) Rotation of elements of array- left and right .


Show thinking
Array rotation involves shifting the elements of an array to the left or right by a specified
number of positions. Elements that move off one end reappear on the other end.

Let's define the two types of rotations:

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:

• Effective Rotations (k % n): If the number of rotations k is greater than or equal to


the array's length n, the array effectively rotates by k % n. For example, rotating an
array of length 5 by 7 positions is equivalent to rotating it by 7(mod5)=2 positions.

• 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

Concept: Move the first k elements to the end of the array.

Method A: Using Slicing (Most Pythonic and Concise)

Python's list slicing is very powerful for this. You can slice the array into two parts and
concatenate them.

Python

def rotate_left_slicing(arr, k):

"""

Performs a left rotation on an array using slicing.

Args:

arr: The input list (array).

k: The number of positions to rotate left.


Returns:

A new list with elements rotated left by k positions.

"""

n = len(arr)

if n == 0:

return []

effective_k = k % n # Handle k larger than array length

# Concatenate the slice from k to end, with the slice from beginning to k

return arr[effective_k:] + arr[:effective_k]

# Example Usage for Left Rotation:

list1 = [1, 2, 3, 4, 5]

k1 = 2

print(f"Original: {list1}, Left Rotate by {k1} -> {rotate_left_slicing(list1, k1)}")

# Expected: [3, 4, 5, 1, 2]

list2 = ['a', 'b', 'c', 'd']

k2 = 5 # k > len(arr)

print(f"Original: {list2}, Left Rotate by {k2} -> {rotate_left_slicing(list2, k2)}")

# Expected: ['b', 'c', 'd', 'a'] (effectively rotated by 1)

list3 = []

k3 = 3
print(f"Original: {list3}, Left Rotate by {k3} -> {rotate_left_slicing(list3, k3)}")

# Expected: []

list4 = [10]

k4 = 100

print(f"Original: {list4}, Left Rotate by {k4} -> {rotate_left_slicing(list4, k4)}")

# Expected: [10]

Method B: Using collections.deque

The deque (double-ended queue) from Python's collections module is highly optimized for
rotations.

Python

from collections import deque

def rotate_left_deque(arr, k):

"""

Performs a left rotation on an array using collections.deque.

Args:

arr: The input list (array).

k: The number of positions to rotate left.

Returns:

A new list with elements rotated left by k positions.

"""

if not arr:

return []
d = deque(arr)

d.rotate(-k) # Negative k means left rotation

return list(d)

# Example Usage for Left Rotation with deque:

list1 = [1, 2, 3, 4, 5]

k1 = 2

print(f"\nOriginal: {list1}, Left Rotate by {k1} (deque) -> {rotate_left_deque(list1, k1)}")

list2 = ['a', 'b', 'c', 'd']

k2 = 5

print(f"Original: {list2}, Left Rotate by {k2} (deque) -> {rotate_left_deque(list2, k2)}")

2. Right Rotation

Concept: Move the last k elements to the beginning of the array.

Method A: Using Slicing (Most Pythonic and Concise)

Similar to left rotation, but the slicing is adjusted.

Python

def rotate_right_slicing(arr, k):

"""

Performs a right rotation on an array using slicing.

Args:

arr: The input list (array).

k: The number of positions to rotate right.


Returns:

A new list with elements rotated right by k positions.

"""

n = len(arr)

if n == 0:

return []

effective_k = k % n # Handle k larger than array length

# Concatenate the slice from (n-k) to end, with the slice from beginning to (n-k)

return arr[n - effective_k:] + arr[:n - effective_k]

# Alternatively, using negative indices: return arr[-effective_k:] + arr[:-effective_k]

# Example Usage for Right Rotation:

list1 = [1, 2, 3, 4, 5]

k1 = 2

print(f"\nOriginal: {list1}, Right Rotate by {k1} -> {rotate_right_slicing(list1, k1)}")

# Expected: [4, 5, 1, 2, 3]

list2 = ['a', 'b', 'c', 'd']

k2 = 5 # k > len(arr)

print(f"Original: {list2}, Right Rotate by {k2} -> {rotate_right_slicing(list2, k2)}")

# Expected: ['d', 'a', 'b', 'c'] (effectively rotated by 1)

Method B: Using collections.deque

For right rotation with deque, the rotate() method takes a positive k.
Python

from collections import deque

def rotate_right_deque(arr, k):

"""

Performs a right rotation on an array using collections.deque.

Args:

arr: The input list (array).

k: The number of positions to rotate right.

Returns:

A new list with elements rotated right by k positions.

"""

if not arr:

return []

d = deque(arr)

d.rotate(k) # Positive k means right rotation

return list(d)

# Example Usage for Right Rotation with deque:

list1 = [1, 2, 3, 4, 5]

k1 = 2

print(f"\nOriginal: {list1}, Right Rotate by {k1} (deque) -> {rotate_right_deque(list1, k1)}")


list2 = ['a', 'b', 'c', 'd']

k2 = 5

print(f"Original: {list2}, Right Rotate by {k2} (deque) -> {rotate_right_deque(list2, k2)}")

Which Method to Choose?

• Slicing: Is very Pythonic, readable, and generally efficient for most practical use
cases. It's often the go-to for simple array rotations.

• collections.deque: Is highly optimized for rotations. If you are dealing with


extremely large arrays or performing a very high number of rotations repeatedly,
deque might offer better performance as its rotate() operation is O(k) or
O(min(k,n−k)) depending on implementation, rather than creating full copies of
slices like basic list slicing.

25 ) Finding Circular rotation of an array by K positions .


"Circular rotation" of an array is just another term for regular array rotation, where elements
that move off one end of the array reappear on the other end. It can be done in two
directions: left circular rotation and right circular rotation.

Key Concept: Effective Rotations

If you rotate an array of length n by k positions, the effective number of rotations is k % n.


This is because rotating n times brings the array back to its original state.

1. Left Circular Rotation (by K positions)

In a left circular rotation, elements from the beginning move to the end of the array.

Example: Array [1, 2, 3, 4, 5], rotate left by k = 2 positions.

• [1, 2, 3, 4, 5]

• Rotate by 1: [2, 3, 4, 5, 1]

• Rotate by 2: [3, 4, 5, 1, 2]

Python Implementation (using Slicing - Most Pythonic):

Python

def circular_rotate_left(arr, k):


"""

Performs a left circular rotation on an array by K positions.

Args:

arr: The input list (array).

k: The number of positions to rotate left.

Returns:

A new list with elements circularly rotated left by K positions.

"""

n = len(arr)

if n == 0:

return []

effective_k = k % n # Calculate effective rotations

# Concatenate the part from 'k' to the end with the part from the beginning to 'k'

return arr[effective_k:] + arr[:effective_k]

# Example Usage for Left Circular Rotation:

list_a = [10, 20, 30, 40, 50]

k_a = 2

print(f"Original: {list_a}, Left Circular Rotate by {k_a} -> {circular_rotate_left(list_a, k_a)}")

# Expected: [30, 40, 50, 10, 20]

list_b = ['apple', 'banana', 'cherry']


k_b = 5 # k > len(arr)

print(f"Original: {list_b}, Left Circular Rotate by {k_b} -> {circular_rotate_left(list_b, k_b)}")

# Expected: ['cherry', 'apple', 'banana'] (effectively rotated by 2, as 5 % 3 = 2)

list_c = []

k_c = 1

print(f"Original: {list_c}, Left Circular Rotate by {k_c} -> {circular_rotate_left(list_c, k_c)}")

# Expected: []

2. Right Circular Rotation (by K positions)

In a right circular rotation, elements from the end move to the beginning of the array.

Example: Array [1, 2, 3, 4, 5], rotate right by k = 2 positions.

• [1, 2, 3, 4, 5]

• Rotate by 1: [5, 1, 2, 3, 4]

• Rotate by 2: [4, 5, 1, 2, 3]

Python Implementation (using Slicing - Most Pythonic):

Python

def circular_rotate_right(arr, k):

"""

Performs a right circular rotation on an array by K positions.

Args:

arr: The input list (array).

k: The number of positions to rotate right.

Returns:
A new list with elements circularly rotated right by K positions.

"""

n = len(arr)

if n == 0:

return []

effective_k = k % n # Calculate effective rotations

# Concatenate the part from (n-k) to the end with the part from the beginning to (n-k)

return arr[n - effective_k:] + arr[:n - effective_k]

# Alternatively, using negative slicing: return arr[-effective_k:] + arr[:-effective_k]

# Example Usage for Right Circular Rotation:

list_d = [10, 20, 30, 40, 50]

k_d = 2

print(f"\nOriginal: {list_d}, Right Circular Rotate by {k_d} -> {circular_rotate_right(list_d,


k_d)}")

# Expected: [40, 50, 10, 20, 30]

list_e = ['a', 'b', 'c', 'd']

k_e = 5 # k > len(arr)

print(f"Original: {list_e}, Right Circular Rotate by {k_e} -> {circular_rotate_right(list_e, k_e)}")

# Expected: ['d', 'a', 'b', 'c'] (effectively rotated by 1, as 5 % 4 = 1)

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]

Alternative using collections.deque

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

from collections import deque

def circular_rotate_deque(arr, k, direction='left'):

"""

Performs a circular rotation on an array by K positions using deque.

Args:

arr: The input list (array).

k: The number of positions to rotate.

direction: 'left' for left rotation, 'right' for right rotation.

Returns:

A new list with elements circularly rotated.

"""

if not arr:

return []

d = deque(arr)

if direction == 'left':

d.rotate(-k) # Negative k for left rotation


elif direction == 'right':

d.rotate(k) # Positive k for right rotation

else:

raise ValueError("Direction must be 'left' or 'right'")

return list(d)

# Example Usage with deque:

list_g = [1, 2, 3, 4, 5]

print(f"\nOriginal: {list_g}, Left Circular Rotate by 2 (deque) -> {circular_rotate_deque(list_g,


2, 'left')}")

print(f"Original: {list_g}, Right Circular Rotate by 2 (deque) -> {circular_rotate_deque(lis

You might also like