Welcome to the Algorithm Design Exercises section. This part of the course is designed to help you apply the concepts and strategies you've learned in the previous modules. You'll find a series of exercises that will challenge your understanding and help you develop your algorithm design skills.

Exercise 1: Divide and Conquer

Problem Statement

Implement a function to find the maximum element in an array using the Divide and Conquer strategy.

Steps to Solve

  1. Divide: Split the array into two halves.
  2. Conquer: Recursively find the maximum element in each half.
  3. Combine: Compare the maximum elements from both halves to find the overall maximum.

Example

def find_maximum(arr, low, high):
    # Base case: Only one element
    if low == high:
        return arr[low]

    # Find the middle point
    mid = (low + high) // 2

    # Recursively find the maximum in both halves
    max1 = find_maximum(arr, low, mid)
    max2 = find_maximum(arr, mid + 1, high)

    # Return the maximum of the two halves
    return max(max1, max2)

# Test the function
array = [1, 5, 3, 9, 2, 8, 4]
print("Maximum element:", find_maximum(array, 0, len(array) - 1))

Solution Explanation

  • The function find_maximum is called recursively to divide the array into smaller subarrays.
  • The base case is when the subarray has only one element, which is the maximum by default.
  • The middle point is calculated to split the array.
  • The maximum elements of the left and right subarrays are found recursively.
  • Finally, the maximum of these two elements is returned.

Exercise

Implement a function to find the minimum element in an array using the Divide and Conquer strategy.

Exercise 2: Greedy Algorithms

Problem Statement

Given a set of activities with their start and finish times, select the maximum number of activities that can be performed by a single person, assuming that a person can only work on a single activity at a time.

Steps to Solve

  1. Sort: Sort the activities based on their finish times.
  2. Select: Select the first activity and then select the next activity that starts after the previous one finishes.

Example

def activity_selection(activities):
    # Sort activities based on finish time
    activities.sort(key=lambda x: x[1])

    # The first activity always gets selected
    selected_activities = [activities[0]]

    # Consider the rest of the activities
    for i in range(1, len(activities)):
        if activities[i][0] >= selected_activities[-1][1]:
            selected_activities.append(activities[i])

    return selected_activities

# Test the function
activities = [(1, 3), (2, 5), (4, 6), (6, 7), (5, 8), (8, 9)]
print("Selected activities:", activity_selection(activities))

Solution Explanation

  • The activities are sorted based on their finish times.
  • The first activity is always selected.
  • For each subsequent activity, it is selected if its start time is greater than or equal to the finish time of the last selected activity.

Exercise

Implement a function to find the minimum number of coins needed to make a given amount using a greedy algorithm.

Exercise 3: Dynamic Programming

Problem Statement

Implement a function to find the length of the longest common subsequence (LCS) of two strings using Dynamic Programming.

Steps to Solve

  1. Define Subproblems: Define the subproblems in terms of the indices of the two strings.
  2. Recurrence Relation: Use the recurrence relation to build the solution.
  3. Memoization: Store the results of subproblems to avoid redundant calculations.

Example

def lcs(X, Y):
    m = len(X)
    k = len(Y)
    # Create a 2D array to store the lengths of LCS
    L = [[0] * (k + 1) for _ in range(m + 1)]

    # Build the LCS table in bottom-up fashion
    for i in range(m + 1):
        for j in range(k + 1):
            if i == 0 or j == 0:
                L[i][j] = 0
            elif X[i - 1] == Y[j - 1]:
                L[i][j] = L[i - 1][j - 1] + 1
            else:
                L[i][j] = max(L[i - 1][j], L[i][j - 1])

    return L[m][k]

# Test the function
X = "AGGTAB"
Y = "GXTXAYB"
print("Length of LCS:", lcs(X, Y))

Solution Explanation

  • A 2D array L is created to store the lengths of LCS for different subproblems.
  • The table is filled in a bottom-up manner using the recurrence relation.
  • The final value in L[m][k] gives the length of the LCS.

Exercise

Implement a function to find the minimum number of operations required to convert one string into another using Dynamic Programming.

Exercise 4: Backtracking

Problem Statement

Implement a function to solve the N-Queens problem using Backtracking.

Steps to Solve

  1. Place Queens: Place queens one by one in different columns.
  2. Check Safety: Check if placing a queen in a particular cell is safe.
  3. Backtrack: If placing the queen leads to a solution, return true. If not, backtrack and try the next position.

Example

def is_safe(board, row, col):
    for i in range(col):
        if board[row][i] == 1:
            return False
    for i, j in zip(range(row, -1, -1), range(col, -1, -1)):
        if board[i][j] == 1:
            return False
    for i, j in zip(range(row, len(board), 1), range(col, -1, -1)):
        if board[i][j] == 1:
            return False
    return True

def solve_n_queens_util(board, col):
    if col >= len(board):
        return True
    for i in range(len(board)):
        if is_safe(board, i, col):
            board[i][col] = 1
            if solve_n_queens_util(board, col + 1):
                return True
            board[i][col] = 0
    return False

def solve_n_queens(n):
    board = [[0] * n for _ in range(n)]
    if not solve_n_queens_util(board, 0):
        return "No solution exists"
    return board

# Test the function
n = 4
solution = solve_n_queens(n)
for row in solution:
    print(row)

Solution Explanation

  • The is_safe function checks if placing a queen in a particular cell is safe.
  • The solve_n_queens_util function places queens one by one and backtracks if a solution is not found.
  • The solve_n_queens function initializes the board and calls the utility function to solve the problem.

Exercise

Implement a function to solve the Sudoku puzzle using Backtracking.

Conclusion

In this section, you have practiced various algorithm design strategies through hands-on exercises. These exercises are designed to reinforce your understanding and help you apply the concepts in practical scenarios. Make sure to attempt the exercises on your own before referring to the solutions. Happy coding!

© Copyright 2024. All rights reserved