# Top 30 Divide & Conquer Interview Questions

## Introduction

Divide and Conquer is a powerful problem-solving technique used in computer science and algorithms. It involves breaking down a complex problem into smaller, more manageable subproblems, solving them individually, and then combining the solutions to solve the original problem. This approach is often used in interviews to assess a candidate’s problem-solving and analytical skills. Divide and Conquer questions typically require understanding the problem, identifying the base case and recursive steps, and analyzing the time and space complexity. These questions challenge candidates to think critically, design efficient algorithms, and demonstrate their ability to break down complex problems into simpler ones.

## Questions

### 1. What is the Divide and Conquer algorithm?

The Divide and Conquer algorithm is a problem-solving technique that involves breaking down a complex problem into smaller subproblems, solving each subproblem independently, and then combining their solutions to obtain the final result. The process of dividing, solving, and combining is repeated recursively until the problem is small enough to be directly solved.

Example: Finding the maximum element in an array using Divide and Conquer

```
#include <iostream>
#include <vector>
int findMax(const std::vector<int>& arr, int left, int right) {
if (left == right) {
return arr[left];
} else {
int mid = (left + right) / 2;
int leftMax = findMax(arr, left, mid);
int rightMax = findMax(arr, mid + 1, right);
return std::max(leftMax, rightMax);
}
}
int main() {
std::vector<int> arr = {12, 56, 27, 8, 45, 93};
int n = arr.size();
int maxElement = findMax(arr, 0, n - 1);
std::cout << "The maximum element in the array is: " << maxElement << std::endl;
return 0;
}
```

### 2. What are the main steps in a Divide and Conquer algorithm?

The main steps in a Divide and Conquer algorithm are as follows:

**Divide**: Break the original problem into smaller, manageable subproblems.**Conquer**: Solve each subproblem independently (typically recursively).**Combine**: Merge the solutions of the subproblems to obtain the final result.

Example: Calculating the factorial of a number using Divide and Conquer

```
#include <iostream>
int factorial(int n) {
if (n == 0 || n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
int main() {
int num = 5;
int result = factorial(num);
std::cout << "Factorial of " << num << " is: " << result << std::endl;
return 0;
}
```

### 3. Can you give some examples of problems that are solved using the Divide and Conquer strategy?

Some examples of problems that are solved using the Divide and Conquer strategy:

**Binary Search**: Searching for an element in a sorted array by dividing the array into halves.

```
#include <iostream>
#include <vector>
int binarySearch(const std::vector<int>& arr, int target, int left, int right) {
if (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] > target) {
return binarySearch(arr, target, left, mid - 1);
} else {
return binarySearch(arr, target, mid + 1, right);
}
}
return -1; // Element not found
}
int main() {
std::vector<int> arr = {2, 5, 8, 12, 16, 23, 38};
int target = 12;
int index = binarySearch(arr, target, 0, arr.size() - 1);
if (index != -1) {
std::cout << "Element " << target << " found at index " << index << std::endl;
} else {
std::cout << "Element not found in the array." << std::endl;
}
return 0;
}
```

**Merge Sort**: Sorting an array by dividing it into smaller subarrays, sorting them, and then merging the sorted subarrays.

```
#include <iostream>
#include <vector>
void merge(std::vector<int>& arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
std::vector<int> leftArr(n1);
std::vector<int> rightArr(n2);
for (int i = 0; i < n1; ++i)
leftArr[i] = arr[left + i];
for (int j = 0; j < n2; ++j)
rightArr[j] = arr[mid + 1 + j];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i];
++i;
} else {
arr[k] = rightArr[j];
++j;
}
++k;
}
while (i < n1) {
arr[k] = leftArr[i];
++i;
++k;
}
while (j < n2) {
arr[k] = rightArr[j];
++j;
++k;
}
}
void mergeSort(std::vector<int>& arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
int main() {
std::vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
int n = arr.size();
mergeSort(arr, 0, n - 1);
std::cout << "Sorted array: ";
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
```

### 4. What is the time complexity of Divide and Conquer algorithms? How do we calculate it?

To calculate the time complexity of a Divide and Conquer algorithm, we usually express it using a recurrence relation, which describes the time complexity in terms of the time complexity of the subproblems. Solving the recurrence relation will give us the overall time complexity of the algorithm.

Let’s look at a classic example of the Divide and Conquer algorithm: Binary Search.

**Binary Search:**

Binary Search is used to find the position of a target element in a sorted array. The algorithm repeatedly divides the search space in half until the target element is found or the search space is exhausted.

**Algorithm:**

- Compare the target element with the middle element of the array.
- If the target is equal to the middle element, the search is successful, and we return the index.
- If the target is less than the middle element, search in the left half of the array.
- If the target is greater than the middle element, search in the right half of the array.
- Repeat the process until the element is found or the search space is empty.

**C++ Code for Binary Search:**

```
#include <iostream>
#include <vector>
int binarySearch(const std::vector<int>& arr, int target, int left, int right) {
if (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
return binarySearch(arr, target, mid + 1, right);
else
return binarySearch(arr, target, left, mid - 1);
}
return -1; // Element not found
}
int main() {
std::vector<int> sortedArray = {1, 3, 5, 7, 9, 11, 13};
int target = 7;
int index = binarySearch(sortedArray, target, 0, sortedArray.size() - 1);
if (index != -1)
std::cout << "Element found at index " << index << std::endl;
else
std::cout << "Element not found in the array." << std::endl;
return 0;
}
```

**Time Complexity Analysis:**

In Binary Search, the search space is halved in each recursive call, making it a Divide and Conquer algorithm. The time complexity can be expressed using the recurrence relation:

T(n) = T(n/2) + O(1)

Where:

- T(n) is the time taken to search in an array of size n.
- T(n/2) is the time taken to search in a subarray of size n/2 (as the array is halved).
- O(1) represents the time taken for the comparison operation (constant time).

To solve the recurrence relation, we can use the Master Theorem, which gives us the solution for many Divide and Conquer algorithms. In this case, the Master Theorem gives us the time complexity:

T(n) = O(log n)

So, the time complexity of the Binary Search algorithm is O(log n). The algorithm’s efficiency lies in its ability to quickly narrow down the search space, making it very efficient for large sorted arrays.

### 5. What are some advantages and disadvantages of using a Divide and Conquer strategy?

**Advantages**:

- Efficient for solving large problems by breaking them into smaller parts.
- Utilizes parallelism for improved performance.
- Often leads to simpler and cleaner code.
- Well-suited for problems that exhibit overlapping subproblems.

**Disadvantages**:

- May incur overhead due to the recursive nature.
- Not always the best approach for all problems.
- Some problems may have a more straightforward linear solution.
- May require additional memory for storing subproblems.

### 6. How does the Merge Sort algorithm apply the Divide and Conquer principle? Explain with examples and code

Merge Sort is a sorting algorithm that efficiently applies the Divide and Conquer principle to sort an array. It breaks down the sorting process into smaller, more manageable subproblems, sorts those subproblems independently, and then merges them to obtain the final sorted array. The key steps of Merge Sort are:

**Divide**: The unsorted array is divided into two halves, roughly of equal size.**Conquer**: Each half is recursively sorted using Merge Sort until the base case is reached, which is when the size of the array becomes 1 or 0 (a single element or an empty array, by definition, is already sorted).**Merge**: The sorted halves are then merged together to produce the final sorted array. The merging process involves comparing elements from both halves and putting them in order.

Let’s go through a step-by-step explanation of Merge Sort with an example and then provide a code implementation in C++.

**Example of Merge Sort:**

Consider the unsorted array: [38, 27, 43, 3, 9, 82, 10]We’ll apply the Divide and Conquer principle to sort this array.

**Divide**:

Split the array into two halves: [38, 27, 43] and [3, 9, 82, 10]**Conquer**:

Recursively sort both halves.

Sort the first half: [27, 38, 43]Sort the second half: [3, 9, 10, 82]**Merge**:

Merge the two sorted halves to obtain the final sorted array.

Compare elements from both halves and arrange them in ascending order.

Result: [3, 9, 10, 27, 38, 43, 82]

**C++ Code for Merge Sort:**

```
#include <iostream>
#include <vector>
void merge(std::vector<int>& arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
// Create temporary arrays to hold the two halves
std::vector<int> L(n1), R(n2);
// Copy data to the temporary arrays
for (int i = 0; i < n1; ++i)
L[i] = arr[left + i];
for (int j = 0; j < n2; ++j)
R[j] = arr[mid + 1 + j];
// Merge the two temporary arrays back into arr
int i = 0; // Index for the left subarray
int j = 0; // Index for the right subarray
int k = left; // Index for the merged array
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
++i;
} else {
arr[k] = R[j];
++j;
}
++k;
}
// Copy any remaining elements of L[] and R[] if there are any
while (i < n1) {
arr[k] = L[i];
++i;
++k;
}
while (j < n2) {
arr[k] = R[j];
++j;
++k;
}
}
void mergeSort(std::vector<int>& arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
// Sort the first and second halves
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// Merge the sorted halves
merge(arr, left, mid, right);
}
}
int main() {
std::vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
int n = arr.size();
std::cout << "Unsorted array: ";
for (int num : arr)
std::cout << num << " ";
std::cout << std::endl;
mergeSort(arr, 0, n - 1);
std::cout << "Sorted array: ";
for (int num : arr)
std::cout << num << " ";
std::cout << std::endl;
return 0;
}
```

**Output:**

```
Unsorted array: 38 27 43 3 9 82 10
Sorted array: 3 9 10 27 38 43 82
```

The code demonstrates the Merge Sort algorithm using the Divide and Conquer principle. The `mergeSort`

function recursively divides the array into two halves until the base case is reached. The `merge`

function merges the sorted halves back together.

Merge Sort has a time complexity of O(n log n) in all cases, which makes it an efficient sorting algorithm for large data sets.

### 7. What is the QuickSort algorithm? How does it use the Divide and Conquer strategy?

QuickSort is a widely used sorting algorithm that applies the Divide and Conquer strategy to efficiently sort an array. It selects a “pivot” element from the array and partitions the other elements into two subarrays: elements less than the pivot and elements greater than the pivot. It then recursively applies QuickSort to these two subarrays.

**Algorithm:**

- Choose a pivot element from the array.
- Partition the array into two subarrays: elements less than the pivot and elements greater than the pivot.
- Recursively apply QuickSort to the two subarrays.
- Merge the sorted subarrays to obtain the final sorted array.

**Example of QuickSort:** Consider the unsorted array: [38, 27, 43, 3, 9, 82, 10]

We’ll apply QuickSort to sort this array.

- Choose pivot: Let’s choose the pivot as the last element, i.e., 10.
- Partition the array: The partitioning process will rearrange the array as follows: [3, 9, 10, 27, 43, 82, 38] All elements less than or equal to the pivot (10) are on the left, and all elements greater than the pivot are on the right.
- Recursively apply QuickSort to the left and right subarrays: [3, 9] and [27, 43, 82, 38].
- Merge the sorted subarrays: [3, 9, 10, 27, 38, 43, 82]

**C++ Code for QuickSort:**

```
#include <iostream>
#include <vector>
int partition(std::vector<int>& arr, int low, int high) {
int pivot = arr[high]; // Choose the last element as the pivot
int i = low - 1;
for (int j = low; j < high; ++j) {
if (arr[j] <= pivot) {
++i;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[high]);
return i + 1;
}
void quickSort(std::vector<int>& arr, int low, int high) {
if (low < high) {
int pivotIndex = partition(arr, low, high);
quickSort(arr, low, pivotIndex - 1); // Sort left subarray
quickSort(arr, pivotIndex + 1, high); // Sort right subarray
}
}
int main() {
std::vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
int n = arr.size();
std::cout << "Unsorted array: ";
for (int num : arr)
std::cout << num << " ";
std::cout << std::endl;
quickSort(arr, 0, n - 1);
std::cout << "Sorted array: ";
for (int num : arr)
std::cout << num << " ";
std::cout << std::endl;
return 0;
}
```

**Output:**

```
Unsorted array: 38 27 43 3 9 82 10
Sorted array: 3 9 10 27 38 43 82
```

### 8. How does the Binary Search algorithm work? How is it an example of Divide and Conquer?

Binary Search is a search algorithm used to find the position of a target element in a sorted array. It employs the Divide and Conquer strategy by repeatedly dividing the search space in half until the target element is found or the search space is exhausted.

**Algorithm:**

- Compare the target element with the middle element of the array.
- If the target is equal to the middle element, the search is successful, and we return the index.
- If the target is less than the middle element, search in the left half of the array.
- If the target is greater than the middle element, search in the right half of the array.
- Repeat the process until the element is found or the search space is empty.

**Example of Binary Search:** Consider the sorted array: [3, 9, 10, 27, 38, 43, 82]. We’ll use Binary Search to find the position of the target element 27.

- Start with the entire array [3, 9, 10, 27, 38, 43, 82].
- Compare the target (27) with the middle element (10). 27 > 10, so the target must be in the right half.
- Search the right half [27, 38, 43, 82].
- Compare the target (27) with the middle element (38). 27 < 38, so the target must be in the left half.
- Search the left half [27].
- The target is found at index 3.

**C++ Code for Binary Search:**

```
<code>#include <iostream>
#include <vector>
int binarySearch(const std::vector<int>& arr, int target) {
int left = 0;
int right = arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1; // Element not found
}
int main() {
std::vector<int> sortedArray = {3, 9, 10, 27, 38, 43, 82};
int target = 27;
int index = binarySearch(sortedArray, target);
if (index != -1)
std::cout << "Element found at index " << index << std::endl;
else
std::cout << "Element not found in the array." << std::endl;
return 0;
}
</code>
```

**Output:**

`Element found at index 3`

### 9. What is the relationship between recursion and Divide and Conquer strategy?

Recursion and Divide and Conquer are closely related concepts in computer science, and often, a Divide and Conquer algorithm is implemented using recursion. The Divide and Conquer strategy breaks down a problem into smaller subproblems, solves these subproblems independently (the “Conquer” step), and then combines their solutions to solve the original problem (the “Merge” or “Combine” step).

Recursion is a programming technique where a function calls itself to solve a smaller instance of the same problem. In the context of Divide and Conquer, recursion is used to handle the subdivision of the problem into smaller subproblems. The base case of the recursion is when the problem becomes trivial and can be solved directly without further subdivision.

**Example with Code:** Let’s consider the example of Merge Sort, which we discussed earlier. Merge Sort is a classic example of a Divide and Conquer algorithm implemented using recursion. In Merge Sort, the array is divided into two halves, each of which is recursively sorted using Merge Sort. The sorted halves are then merged back together to obtain the final sorted array.

The `mergeSort`

function in the code implementation of Merge Sort uses recursion to sort the left and right halves of the array:

```
<code>void mergeSort(std::vector<int>& arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
// Sort the first and second halves
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// Merge the sorted halves
merge(arr, left, mid, right);
}
}
</code>
```

The recursive calls to `mergeSort`

on lines 4 and 5 handle the “Divide” step of the Merge Sort algorithm by sorting the left and right subarrays.

### 10. Can you explain how the Strassen’s Matrix Multiplication algorithm uses Divide and Conquer?

Strassen’s Matrix Multiplication algorithm uses the Divide and Conquer strategy to efficiently multiply two matrices. It breaks down the matrix multiplication into smaller subproblems, solves these subproblems independently, and then combines their results to obtain the final product matrix. The key steps of Strassen’s algorithm are as follows:

**Algorithm:**

**Divide**: Divide the input matrices A and B into four equal-sized submatrices (quadrants). This step involves dividing the matrices into smaller pieces.**Conquer**: Recursively calculate seven matrix products using these submatrices. The seven products are derived from the submatrices using addition and subtraction operations.**Combine**: Combine these products to obtain the final product matrix. The result is a combination of the products obtained from the previous step.

The Divide and Conquer strategy in Strassen’s algorithm can be illustrated as follows:

Suppose we want to multiply two 2×2 matrices, A and B:

```
| A11 A12 | | B11 B12 |
| | | |
| A21 A22 | | B21 B22 |
```

Where A11, A12, A21, and A22 are the submatrices of matrix A, and B11, B12, B21, and B22 are the submatrices of matrix B.

**Step 1 (Divide):**

Divide each matrix A and B into four equal-sized submatrices (quadrants):

```
| A11 A12 | | B11 B12 |
| | | |
| A21 A22 | | B21 B22 |
```

**Step 2 (Conquer):**

Calculate seven products recursively:

```
P1 = A11 * (B12 - B22)
P2 = (A11 + A12) * B22
P3 = (A21 + A22) * B11
P4 = A22 * (B21 - B11)
P5 = (A11 + A22) * (B11 + B22)
P6 = (A12 - A22) * (B21 + B22)
P7 = (A11 - A21) * (B11 + B12)
```

**Step 3 (Combine):**

Combine the products to obtain the final product matrix C:

```
| C11 C12 | | P5 + P4 - P2 + P6 P1 + P2 |
| | | |
| C21 C22 | | P3 + P4 P1 + P5 - P3 - P7 |
```

Where C11, C12, C21, and C22 are the submatrices of the resulting product matrix C.

The final product matrix C is obtained by combining the seven products calculated in the Conquer step using addition and subtraction.

Strassen’s Matrix Multiplication algorithm reduces the number of multiplications from 8 (in the standard matrix multiplication algorithm) to 7, making it more efficient for large matrix sizes. However, it introduces additional overhead due to the recursive calls and the need to combine the results. As a result, Strassen’s algorithm is generally most beneficial for large matrix sizes, and for smaller matrices, the standard matrix multiplication algorithm may be more efficient.

## Coding Questions

### 1. Implement the binary search algorithm.

```
#include <iostream>
#include <vector>
int binarySearch(const std::vector<int>& arr, int target) {
int left = 0;
int right = arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // Target not found
}
int main() {
std::vector<int> arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int target = 5;
int index = binarySearch(arr, target);
if (index != -1) {
std::cout << "Target found at index: " << index << std::endl;
} else {
std::cout << "Target not found." << std::endl;
}
return 0;
}
```

### 2. Implement the merge sort algorithm.

```
#include <iostream>
#include <vector>
void merge(std::vector<int>& arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
std::vector<int> leftArr(n1);
std::vector<int> rightArr(n2);
for (int i = 0; i < n1; i++) {
leftArr[i] = arr[left + i];
}
for (int j = 0; j < n2; j++) {
rightArr[j] = arr[mid + 1 + j];
}
int i = 0;
int j = 0;
int k = left;
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i];
i++;
} else {
arr[k] = rightArr[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = leftArr[i];
i++;
k++;
}
while (j < n2) {
arr[k] = rightArr[j];
j++;
k++;
}
}
void mergeSort(std::vector<int>& arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
int main() {
std::vector<int> arr = {5, 2, 7, 1, 3, 6, 4, 8};
mergeSort(arr, 0, arr.size() - 1);
for (int num : arr) {
std::cout << num << " ";
}
return 0;
}
```

### 3. Implement the quicksort algorithm.

```
#include <iostream>
#include <vector>
int partition(std::vector<int>& arr, int low, int
high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[high]);
return i + 1;
}
void quickSort(std::vector<int>& arr, int low, int high) {
if (low < high) {
int pivotIndex = partition(arr, low, high);
quickSort(arr, low, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, high);
}
}
int main() {
std::vector<int> arr = {5, 2, 7, 1, 3, 6, 4, 8};
quickSort(arr, 0, arr.size() - 1);
for (int num : arr) {
std::cout << num << " ";
}
return 0;
}
```

### 4. Write a program that finds the peak element in an array.

```
#include <iostream>
#include <vector>
int findPeakElement(const std::vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < nums[mid + 1]) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
int main() {
std::vector<int> nums = {1, 2, 3, 1};
int peakIndex = findPeakElement(nums);
std::cout << "Peak element is: " << nums[peakIndex] << std::endl;
return 0;
}
```

### 5. Write a program to perform polynomial multiplication using the divide and conquer strategy.

```
#include <iostream>
#include <vector>
std::vector<int> multiplyPolynomials(const std::vector<int>& A, const std::vector<int>& B) {
int m = A.size();
int n = B.size();
int size = m + n - 1;
std::vector<int> result(size, 0);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
result[i + j] += A[i] * B[j];
}
}
return result;
}
int main() {
std::vector<int> A = {1, 2, 1};
std::vector<int> B = {2, -1};
std::vector<int> product = multiplyPolynomials(A, B);
std::cout << "Product of polynomials: ";
for (int coeff : product) {
std::cout << coeff << " ";
}
std::cout << std::endl;
return 0;
}
```

### 6. Write a program to perform Strassen’s Matrix Multiplication.

```
#include <iostream>
#include <vector>
std::vector<std::vector<int>> matrixMultiply(const std::vector<std::vector<int>>& A, const std::vector<std::vector<int>>& B) {
int n = A.size();
if (n == 1) {
std::vector<std::vector<int>> C(1, std::vector<int>(1));
C[0][0] = A[0][0] * B[0][0];
return C;
}
int halfSize = n / 2;
std::vector<std::vector<int>> A11(halfSize, std::vector<int>(halfSize));
std::vector<std::vector<int>> A12(halfSize, std::vector<int>(halfSize));
std::vector<std::vector<int>> A21(halfSize, std::vector<int>(halfSize));
std::vector<std::vector<int>> A22(halfSize, std::vector<int>(halfSize));
std::vector<std::vector<int>> B11(halfSize, std::vector<int>(halfSize));
std::vector<std::vector<int>> B12(halfSize, std::vector<int>(halfSize));
std::vector<std::vector<int>> B21(halfSize, std::vector<int>(
halfSize));
std::vector<std::vector<int>> B22(halfSize, std::vector<int>(halfSize));
for (int i = 0; i < halfSize; i++) {
for (int j = 0; j < halfSize; j++) {
A11[i][j] = A[i][j];
A12[i][j] = A[i][j + halfSize];
A21[i][j] = A[i + halfSize][j];
A22[i][j] = A[i + halfSize][j + halfSize];
B11[i][j] = B[i][j];
B12[i][j] = B[i][j + halfSize];
B21[i][j] = B[i + halfSize][j];
B22[i][j] = B[i + halfSize][j + halfSize];
}
}
std::vector<std::vector<int>> P1 = matrixMultiply(A11, B12 - B22);
std::vector<std::vector<int>> P2 = matrixMultiply(A11 + A12, B22);
std::vector<std::vector<int>> P3 = matrixMultiply(A21 + A22, B11);
std::vector<std::vector<int>> P4 = matrixMultiply(A22, B21 - B11);
std::vector<std::vector<int>> P5 = matrixMultiply(A11 + A22, B11 + B22);
std::vector<std::vector<int>> P6 = matrixMultiply(A12 - A22, B21 + B22);
std::vector<std::vector<int>> P7 = matrixMultiply(A11 - A21, B11 + B12);
std::vector<std::vector<int>> C11 = P5 + P4 - P2 + P6;
std::vector<std::vector<int>> C12 = P1 + P2;
std::vector<std::vector<int>> C21 = P3 + P4;
std::vector<std::vector<int>> C22 = P5 + P1 - P3 - P7;
std::vector<std::vector<int>> C(n, std::vector<int>(n));
for (int i = 0; i < halfSize; i++) {
for (int j = 0; j < halfSize; j++) {
C[i][j] = C11[i][j];
C[i][j + halfSize] = C12[i][j];
C[i + halfSize][j] = C21[i][j];
C[i + halfSize][j + halfSize] = C22[i][j];
}
}
return C;
}
int main() {
std::vector<std::vector<int>> A = {
{1, 2},
{3, 4}
};
std::vector<std::vector<int>> B = {
{5, 6},
{7, 8}
};
std::vector<std::vector<int>> C = matrixMultiply(A, B);
std::cout << "Matrix Multiplication Result:" << std::endl;
for (const auto& row : C) {
for (int element : row) {
std::cout << element << " ";
}
std::cout << std::endl;
}
return 0;
}
```

### 7. Write a program to perform the Karatsuba algorithm for fast multiplication.

```
#include <iostream>
#include <string>
#include <cmath>
std::string add(const
std::string& a, const std::string& b) {
std::string sum;
int carry = 0;
int i = a.length() - 1;
int j = b.length() - 1;
while (i >= 0 || j >= 0 || carry) {
int digitA = i >= 0 ? a[i] - '0' : 0;
int digitB = j >= 0 ? b[j] - '0' : 0;
int digitSum = digitA + digitB + carry;
carry = digitSum / 10;
digitSum %= 10;
sum = std::to_string(digitSum) + sum;
i--;
j--;
}
return sum;
}
std::string subtract(const std::string& a, const std::string& b) {
std::string diff;
int borrow = 0;
int i = a.length() - 1;
int j = b.length() - 1;
while (i >= 0 || j >= 0) {
int digitA = i >= 0 ? a[i] - '0' : 0;
int digitB = j >= 0 ? b[j] - '0' : 0;
int digitDiff = digitA - digitB - borrow;
if (digitDiff < 0) {
digitDiff += 10;
borrow = 1;
} else {
borrow = 0;
}
diff = std::to_string(digitDiff) + diff;
i--;
j--;
}
// Remove leading zeros
while (diff.length() > 1 && diff[0] == '0') {
diff = diff.substr(1);
}
return diff;
}
std::string multiply(const std::string& x, const std::string& y) {
int n = std::max(x.length(), y.length());
if (n == 1) {
int product = (x[0] - '0') * (y[0] - '0');
return std::to_string(product);
}
std::string a = x.substr(0, n / 2);
std::string b = x.substr(n / 2);
std::string c = y.substr(0, n / 2);
std::string d = y.substr(n / 2);
std::string ac = multiply(a, c);
std::string bd = multiply(b, d);
std::string abcd = multiply(add(a, b), add(c, d));
std::string adbc = subtract(abcd, add(ac, bd));
std::string result = add(add(ac + std::string(n, '0'), adbc + std::string(n / 2, '0')), bd);
// Remove leading zeros
while (result.length() > 1 && result[0] == '0') {
result = result.substr(1);
}
return result;
}
int main() {
std::string x = "123456789";
std::string y = "987654321";
std::string product = multiply(x, y);
std::cout << "Product: " << product << std::endl;
return 0;
}
```

### 8. Implement the power function using divide and conquer.

```
#include <iostream>
double power(double x, int n) {
if (n ==
0) {
return 1.0;
}
double half = power(x, n / 2);
if (n % 2 == 0) {
return half * half;
} else if (n > 0) {
return x * half * half;
} else {
return (half * half) / x;
}
}
int main() {
double x = 2.0;
int n = 10;
double result = power(x, n);
std::cout << "Result: " << result << std::endl;
return 0;
}
```

### 9. Write a program that solves the closest pair of points problem.

```
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <limits>
struct Point {
int x, y;
};
bool sortByX(const Point& p1, const Point& p2) {
return p1.x < p2.x;
}
bool sortByY(const Point& p1, const Point& p2) {
return p1.y < p2.y;
}
double findDistance(const Point& p1, const Point& p2) {
int dx = p1.x - p2.x;
int dy = p1.y - p2.y;
return std::sqrt(dx * dx + dy * dy);
}
double findClosestPairDistance(std::vector<Point>& points, int low, int high) {
if (high - low + 1 <= 3) {
double minDistance = std::numeric_limits<double>::max();
for (int i = low; i <= high; i++) {
for (int j = i + 1; j <= high; j++) {
minDistance = std::min(minDistance, findDistance(points[i], points[j]));
}
}
return minDistance;
}
int mid = (low + high) / 2;
double leftMinDistance = findClosestPairDistance(points, low, mid);
double rightMinDistance = findClosestPairDistance(points, mid + 1, high);
double minDistance = std::min(leftMinDistance, rightMinDistance);
std::vector<Point> strip;
for (int i = low; i <= high; i++) {
if (std::abs(points[i].x - points[mid].x) < minDistance) {
strip.push_back(points[i]);
}
}
std::sort(strip.begin(), strip.end(), sortByY);
int stripSize = strip.size();
for (int i = 0; i < stripSize; i++) {
for (int j = i + 1; j < stripSize && strip[j].y - strip[i].y < minDistance; j++) {
minDistance = std::min(minDistance, findDistance(strip[i], strip[j]));
}
}
return minDistance;
}
int main() {
std::vector<Point> points = {
{2, 3},
{12, 30},
{40, 50},
{5, 1},
{12, 10},
{3, 4}
};
std::sort(points.begin(), points.end(), sortByX);
double closestDistance = findClosestPairDistance(points, 0, points.size() - 1);
std::cout << "Closest pair distance: " << closestDistance << std::endl;
return 0;
}
```

### 10. Implement a program to find the majority element in an array.

```
#include <iostream>
#include <vector>
#include <unordered_map>
int findMajorityElement(const std::vector<int>& nums) {
std::unordered_map<int, int> count;
for (int num : nums) {
count[num]++;
if (count[num] > nums.size() / 2) {
return num;
}
}
return -1; // No majority element
}
int main() {
std::vector<int> nums = {2,
2, 1, 1, 1, 2, 2};
int majorityElement = findMajorityElement(nums);
if (majorityElement != -1) {
std::cout << "Majority element: " << majorityElement << std::endl;
} else {
std::cout << "No majority element found." << std::endl;
}
return 0;
}
```

### 11. Write a program that calculates the number of inversions in an array.

```
#include <iostream>
#include <vector>
int merge(std::vector<int>& nums, int left, int mid, int right) {
int inversions = 0;
std::vector<int> temp(right - left + 1);
int i = left;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= right) {
if (nums[i] <= nums[j]) {
temp[k++] = nums[i++];
} else {
temp[k++] = nums[j++];
inversions += mid - i + 1;
}
}
while (i <= mid) {
temp[k++] = nums[i++];
}
while (j <= right) {
temp[k++] = nums[j++];
}
for (int p = 0; p < temp.size(); p++) {
nums[left + p] = temp[p];
}
return inversions;
}
int mergeSort(std::vector<int>& nums, int left, int right) {
int inversions = 0;
if (left < right) {
int mid = left + (right - left) / 2;
inversions += mergeSort(nums, left, mid);
inversions += mergeSort(nums, mid + 1, right);
inversions += merge(nums, left, mid, right);
}
return inversions;
}
int countInversions(std::vector<int>& nums) {
return mergeSort(nums, 0, nums.size() - 1);
}
int main() {
std::vector<int> nums = {2, 4, 1, 3, 5};
int inversions = countInversions(nums);
std::cout << "Number of inversions: " << inversions << std::endl;
return 0;
}
```

### 12. Write a program that finds the maximum subarray sum.

```
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
int maxSubarraySum(const std::vector<int>& nums) {
int maxSum = INT_MIN;
int currentSum = 0;
for (int num : nums) {
currentSum = std::max(num, currentSum + num);
maxSum = std::max(maxSum, currentSum);
}
return maxSum;
}
int main() {
std::vector<int> nums = {-2, -3, 4, -1, -2, 1, 5, -3};
int maxSum = maxSubarraySum(nums);
std::cout << "Maximum subarray sum: " << maxSum << std::endl;
return 0;
}
```

### 13. Implement a program to find the median of two sorted arrays.

```
#include <iostream>
#include <vector>
double findMedian(const std::vector<int>& nums1, const std::vector<int>& nums2) {
int m = nums1
.size();
int n = nums2.size();
if (m > n) {
return findMedian(nums2, nums1);
}
int left = 0;
int right = m;
int halfLength = (m + n + 1) / 2;
while (left <= right) {
int partition1 = (left + right) / 2;
int partition2 = halfLength - partition1;
int maxLeft1 = (partition1 == 0) ? INT_MIN : nums1[partition1 - 1];
int maxLeft2 = (partition2 == 0) ? INT_MIN : nums2[partition2 - 1];
int minRight1 = (partition1 == m) ? INT_MAX : nums1[partition1];
int minRight2 = (partition2 == n) ? INT_MAX : nums2[partition2];
if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
if ((m + n) % 2 == 0) {
return (std::max(maxLeft1, maxLeft2) + std::min(minRight1, minRight2)) / 2.0;
} else {
return std::max(maxLeft1, maxLeft2);
}
} else if (maxLeft1 > minRight2) {
right = partition1 - 1;
} else {
left = partition1 + 1;
}
}
return 0.0;
}
int main() {
std::vector<int> nums1 = {1, 3};
std::vector<int> nums2 = {2};
double median = findMedian(nums1, nums2);
std::cout << "Median: " << median << std::endl;
return 0;
}
```

### 14. Implement a program to find the k-th smallest element in an array.

```
#include <iostream>
#include <vector>
#include <algorithm>
int findKthSmallest(std::vector<int>& nums, int k) {
std::sort(nums.begin(), nums.end());
return nums[k - 1];
}
int main() {
std::vector<int> nums = {7, 10, 4, 3, 20, 15};
int k = 3;
int kthSmallest = findKthSmallest(nums, k);
std::cout << "K-th smallest element: " << kthSmallest << std::endl;
return 0;
}
```

### 15. Write a program that finds the largest rectangular area under a histogram.

```
#include <iostream>
#include <stack>
#include <vector>
int largestRectangleArea(const std::vector<int>& heights) {
std::stack<int> stack;
int maxArea = 0;
int i = 0;
while (i < heights.size()) {
if (stack.empty() || heights[i] >= heights[stack.top()]) {
stack.push(i++);
} else {
int topIndex = stack.top();
stack.pop();
int area = heights[topIndex] * (stack.empty() ? i : i - stack.top() - 1);
maxArea = std::max(maxArea, area);
}
}
while (!stack.empty()) {
int topIndex = stack.top();
stack.pop();
int area = heights[topIndex] * (stack.empty() ? i : i - stack.top() - 1);
max
Area = std::max(maxArea, area);
}
return maxArea;
}
int main() {
std::vector<int> heights = {6, 2, 5, 4, 5, 1, 6};
int largestArea = largestRectangleArea(heights);
std::cout << "Largest rectangular area: " << largestArea << std::endl;
return 0;
}
```

### 16. Implement a program to solve the Skyline Problem.

```
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
struct Building {
int left;
int height;
int right;
Building(int l, int h, int r) : left(l), height(h), right(r) {}
};
std::vector<std::pair<int, int>> getSkyline(const std::vector<Building>& buildings) {
std::vector<std::pair<int, int>> skyline;
std::multimap<int, int> corners;
for (const Building& building : buildings) {
corners.emplace(building.left, -building.height);
corners.emplace(building.right, building.height);
}
std::multiset<int> heights = {0};
int prevHeight = 0;
for (const auto& corner : corners) {
if (corner.second < 0) {
heights.insert(-corner.second);
} else {
heights.erase(heights.find(corner.second));
}
int currHeight = *heights.rbegin();
if (currHeight != prevHeight) {
skyline.emplace_back(corner.first, currHeight);
prevHeight = currHeight;
}
}
return skyline;
}
int main() {
std::vector<Building> buildings = {
{2, 9, 10},
{3, 7, 15},
{5, 12, 12},
{15, 20, 10},
{19, 24, 8}
};
std::vector<std::pair<int, int>> skyline = getSkyline(buildings);
std::cout << "Skyline: " << std::endl;
for (const auto& point : skyline) {
std::cout << "(" << point.first << ", " << point.second << ")" << std::endl;
}
return 0;
}
```

### 17. Write a program to solve the job scheduling problem.

```
#include <iostream>
#include <vector>
#include <algorithm>
struct Job {
int id;
int deadline;
int profit;
Job(int i, int d, int p) : id(i), deadline(d), profit(p) {}
};
bool sortByProfit(const Job& job1, const Job& job2) {
return job1.profit > job2.profit;
}
std::vector<int> scheduleJobs(const std::vector<Job>& jobs) {
int maxDeadline = 0;
for (const Job& job : jobs) {
maxDeadline = std::max(maxDeadline, job.deadline);
}
std::vector<int> schedule(maxDeadline, -1);
int totalProfit = 0;
std::sort(jobs.begin(), jobs.end(), sortByProfit);
for (const Job& job : jobs) {
for (int i = job.deadline - 1; i >= 0; i--) {
if (schedule[i] == -1) {
schedule[i] = job.id;
totalProfit += job.profit;
break;
}
}
}
return schedule;
}
int main() {
std::vector<Job> jobs = {
{1, 4, 20},
{2, 1, 10},
{3, 1, 40},
{4, 1, 30}
};
std::vector<int
> schedule = scheduleJobs(jobs);
std::cout << "Job schedule: ";
for (int jobId : schedule) {
std::cout << jobId << " ";
}
std::cout << std::endl;
return 0;
}
```

### 18. Implement a program to find the longest common prefix among a set of strings.

```
#include <iostream>
#include <vector>
#include <algorithm>
std::string longestCommonPrefix(const std::vector<std::string>& strs) {
if (strs.empty()) {
return "";
}
std::string prefix = strs[0];
for (int i = 1; i < strs.size(); i++) {
while (strs[i].find(prefix) != 0) {
prefix = prefix.substr(0, prefix.length() - 1);
if (prefix.empty()) {
return "";
}
}
}
return prefix;
}
int main() {
std::vector<std::string> strs = {"flower", "flow", "flight"};
std::string commonPrefix = longestCommonPrefix(strs);
std::cout << "Longest common prefix: " << commonPrefix << std::endl;
return 0;
}
```

### 19. Implement a program to solve the Painter’s Partition problem.

```
#include <iostream>
#include <vector>
#include <algorithm>
bool canPartition(const std::vector<int>& boards, int numPainters, int maxLength) {
int numPaintersUsed = 1;
int currentLength = 0;
for (int board : boards) {
if (currentLength + board > maxLength) {
numPaintersUsed++;
currentLength = board;
if (numPaintersUsed > numPainters) {
return false;
}
} else {
currentLength += board;
}
}
return true;
}
int findMinTime(const std::vector<int>& boards, int numPainters) {
int totalLength = 0;
int maxLength = 0;
for (int board : boards) {
totalLength += board;
maxLength = std::max(maxLength, board);
}
int left = maxLength;
int right = totalLength;
while (left < right) {
int mid = left + (right - left) / 2;
if (canPartition(boards, numPainters, mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
int main() {
std::vector<int> boards = {10, 20, 30, 40};
int numPainters = 2;
int minTime = findMinTime(boards, numPainters);
std::cout << "Minimum time: " << minTime << std::endl;
return 0;
}
```

### 20. Write a program to solve the optimal matrix chain multiplication problem.

```
#include <iostream>
#include <vector>
#include <climits>
int matrixChainMultiplication(const std::vector<int>& dimensions) {
int n = dimensions.size() - 1;
std::vector<std::vector<int>> dp(n, std::vector<int>(n, 0));
for (int len = 2; len <= n; len++) {
for (int i = 0; i < n - len + 1; i++) {
int j = i + len - 1
;
dp[i][j] = INT_MAX;
for (int k = i; k < j; k++) {
int cost = dp[i][k] + dp[k + 1][j] + dimensions[i] * dimensions[k + 1] * dimensions[j + 1];
dp[i][j] = std::min(dp[i][j], cost);
}
}
}
return dp[0][n - 1];
}
int main() {
std::vector<int> dimensions = {10, 30, 5, 60};
int minMultiplications = matrixChainMultiplication(dimensions);
std::cout << "Minimum number of multiplications: " << minMultiplications << std::endl;
return 0;
}
```

## MCQ Questions

### 1. What is the main principle behind the Divide and Conquer algorithm?

a) Breaking a problem into smaller subproblems

b) Combining multiple problems into a single solution

c) Repeating a process until the problem is solved

d) Iterating through all possible solutions

Answer: a) Breaking a problem into smaller subproblems

### 2. Which of the following algorithms uses the Divide and Conquer approach?

a) Depth-First Search (DFS)

b) Breadth-First Search (BFS)

c) Quick Sort

d) Dijkstra’s Algorithm

Answer: c) Quick Sort

### 3. In the Divide and Conquer paradigm, the divide step involves:

a) Combining smaller subproblems into a solution

b) Breaking the problem into smaller subproblems

c) Repeating a process until the problem is solved

d) Selecting the best solution among all possibilities

Answer: b) Breaking the problem into smaller subproblems

### 4. Which of the following is NOT a typical step in the Divide and Conquer algorithm?

a) Divide

b) Conquer

c) Combine

d) Merge

Answer: d) Merge

### 5. Which algorithm uses the Divide and Conquer approach to find the maximum subarray sum?

a) Kadane’s Algorithm

b) Merge Sort

c) Binary Search

d) Prim’s Algorithm

Answer: a) Kadane’s Algorithm

### 6. The time complexity of the Divide and Conquer algorithm is often expressed using:

a) Big O notation

b) Binary notation

c) Decimal notation

d) Exponential notation

Answer: a) Big O notation

### 7. Which sorting algorithm is based on the Divide and Conquer approach?

a) Bubble Sort

b) Selection Sort

c) Insertion Sort

d) Merge Sort

Answer: d) Merge Sort

### 8. Which data structure is commonly used in the Divide and Conquer algorithm to store intermediate results?

a) Stack

b) Queue

c) Heap

d) Array

Answer: a) Stack

### 9. Which of the following problems can be solved using the Divide and Conquer approach?

a) Finding the shortest path in a graph

b) Sorting a linked list

c) Calculating the Fibonacci sequence

d) Searching for an element in an array

Answer: b) Sorting a linked list

### 10. Which algorithm uses the Divide and Conquer approach to find the closest pair of points in a plane?

a) Quick Sort

b) Merge Sort

c) Convex Hull

d) Binary Search

Answer: c) Convex Hull

### 11. The Divide and Conquer approach is based on the principle of:

a) Divide, Combine, Conquer

b) Divide, Conquer, Repeat

c) Divide, Merge, Conquer

d) Divide, Sort, Merge

Answer: b) Divide, Conquer, Repeat

### 12. Which algorithm uses the Divide and Conquer approach to find the kth smallest element in an array?

a) Quick Sort

b) Merge Sort

c) Binary Search

d) Heap Sort

Answer: c) Binary Search

### 13. Which of the following is NOT a characteristic of the Divide and Conquer algorithm?

a) Recursion

b) Dynamic programming

c) Optimal substructure

d) Overlapping subproblems

Answer: b) Dynamic programming

### 14. Which algorithm uses the Divide and Conquer approach to solve the Tower of Hanoi problem?

a) Depth-First Search (DFS)

b) Breadth-First Search (BFS)

c) Quick Sort

d) Backtracking

Answer: d) Backtracking

### 15. Which algorithm uses the Divide and Conquer approach to find the median of two sorted arrays?

a) Quick Sort

b) Merge Sort

c) Binary Search

d) Selection Sort

Answer: c) Binary Search

### 16. The Combine step in the Divide and Conquer algorithm involves:

a) Breaking the problem into smaller subproblems

b) Repeating a process until the problem is solved

c) Combining the solutions of the subproblems into a single solution

d) Selecting the best solution among all possibilities

Answer: c) Combining the solutions of the subproblems into a single solution

### 17. Which algorithm uses the Divide and Conquer approach to find the maximum value in an array?

a) Quick Sort

b) Merge Sort

c) Binary Search

d) Maximum Subarray Sum

Answer: d) Maximum Subarray Sum

### 18. In the Divide and Conquer approach, the conquer step involves:

a) Combining smaller subproblems into a solution

b) Breaking the problem into smaller subproblems

c) Repeating a process until the problem is solved

d) Selecting the best solution among all possibilities

Answer: a) Combining smaller subproblems into a solution

### 19. Which algorithm uses the Divide and Conquer approach to find the inverse of a matrix?

a) Quick Sort

b) Merge Sort

c) Binary Search

d) Strassen’s Algorithm

Answer: d) Strassen’s Algorithm

### 20. The Divide and Conquer approach is commonly used in:

a) Numerical analysis

b) Image processing

c) Cryptography

d) All of the above

Answer: d) All of the above

### 21. Which algorithm uses the Divide and Conquer approach to find the maximum subarray product?

a) Kadane’s Algorithm

b) Merge Sort

c) Quick Sort

d) Strassen’s Algorithm

Answer: b) Merge Sort

### 22. In the Divide and Conquer algorithm, the divide step involves:

a) Combining smaller subproblems into a solution

b) Breaking the problem into smaller subproblems

c) Repeating a process until the problem is solved

d) Selecting the best solution among all possibilities

Answer: b) Breaking the problem into smaller subproblems

### 23. Which algorithm uses the Divide and Conquer approach to solve the maximum subarray sum problem in linear time?

a) Kadane’s Algorithm

b) Merge Sort

c) Quick Sort

d) Strassen’s Algorithm

Answer: a) Kadane’s Algorithm

### 24. The time complexity of the Divide and Conquer algorithm for sorting is typically:

a) O(n)

b) O(log n)

c) O(n log n)

d) O(n^2)

Answer: c) O(n log n)

### 25. Which algorithm uses the Divide and Conquer approach to find the closest pair of points in a plane efficiently?

a) Quick Sort

b) Merge Sort

c) Convex Hull

d) Binary Search

Answer: c) Convex Hull

### 26. Which of the following is NOT a step in the Divide and Conquer algorithm?

a) Divide

b) Conquer

c) Combine

d) Iterate

Answer: d) Iterate

### 27. The Divide and Conquer approach can be applied to solve problems in which of the following domains?

a) Mathematics

b) Computer Science

c) Physics

d) All of the above

Answer: d) All of the above

### 28. Which algorithm uses the Divide and Conquer approach to solve the matrix multiplication problem efficiently?

a) Quick Sort

b) Merge Sort

c) Binary Search

d) Strassen’s Algorithm

Answer: d) Strassen’s Algorithm

### 29. In the Divide and Conquer algorithm, the conquer step involves:

b) Breaking the problem into smaller subproblems

c) Repeating a process until the problem is solved

d) Selecting the best solution among all possibilities

Answer: a) Combining smaller subproblems into a solution

### 30. Which algorithm uses the Divide and Conquer approach to find the majority element in an array?

a) Quick Sort

b) Merge Sort

c) Binary Search

d) Boyer-Moore Majority Vote Algorithm

Answer: d) Boyer-Moore Majority Vote Algorithm