---
# System prepended metadata

title: DSA 4.2 - Interview Problems

---

# DSA 4.2 - Interview Problems

---
title: Problem 1 Partition to K Equal Sum Subsets
description:
duration: 900
card_type: cue_card
---

### Description
Given an integer array nums and an integer k, return true if it is possible to divide this array into k non-empty subsets whose sums are all equal.

### Example 
**Input**: nums = [4,3,2,3,5,2,1], k = 4
**Output**: true
**Explanation**: It is possible to divide it into 4 subsets (5), (1, 4), (2,3), (2,3) with equal sums.

### Brute Force Approach
#### Objective
Determine if it is possible to divide the array into k non-empty subsets with equal sums.

#### Idea
* Generate all possible combinations of subsets.
* Check if each subset has an equal sum.
* Keep track of the count of subsets with equal sums.

#### Brute Force Code
```cpp
bool canPartitionKSubsets(vector<int>& nums, int k) {
    int sum = accumulate(nums.begin(), nums.end(), 0);
    if (sum % k != 0) return false; // If total sum is not divisible by k, return false
    vector<bool> visited(nums.size(), false);
    return canPartition(nums, k, 0, 0, sum / k, visited);
}

bool canPartition(vector<int>& nums, int k, int start, int currentSum, int targetSum, vector<bool>& visited) {
    if (k == 0) return true; // All subsets are formed
    if (currentSum == targetSum) return canPartition(nums, k - 1, 0, 0, targetSum, visited);

    for (int i = start; i < nums.size(); ++i) {
        if (!visited[i]) {
            visited[i] = true;
            if (canPartition(nums, k, i + 1, currentSum + nums[i], targetSum, visited)) return true;
            visited[i] = false;
        }
    }
    return false;
}
```

---
title: Partition to K Equal Sum Subsets optimization using DP
description:
duration: 900
card_type: cue_card
---

### Observation
The backtracking approach still has overlapping subproblems, suggesting a dynamic programming solution.

### Approach
* Create a DP table (dp) with size `1 << nums.size()`. Each bit of the mask represents whether an element is included in the subset.
* Iterate through all possible states of the DP table (i represents the current state).
* For each state, iterate through the elements of the array (nums[j]).
* Check if the j-th element is not already included in the current state (`(i & (1 << j)) == 0`).
* Check if the previous state (`dp[i ^ (1 << j)]`) is valid, and adding the j-th element does not exceed the target sum.
* If conditions are met, update the current state (dp[i]) by adding the j-th element.
* Break the inner loop if a valid subset is found.
* If the last state of the DP table (dp.back()) is equal to 0, return true; otherwise, return false.

#### Optimized DP Code
```cpp
bool canPartitionKSubsets(vector<int>& nums, int k) {
    int sum = accumulate(nums.begin(), nums.end(), 0);
    if (sum % k != 0) return false;
    int targetSum = sum / k;
    sort(nums.rbegin(), nums.rend());
    vector<int> dp(1 << nums.size(), -1);
    dp[0] = 0;

    for (int i = 1; i < (1 << nums.size()); ++i) {
        for (int j = 0; j < nums.size(); ++j) {
            if ((i & (1 << j)) && dp[i ^ (1 << j)] != -1 && (dp[i ^ (1 << j)] + nums[j] <= targetSum)) {
                dp[i] = (dp[i ^ (1 << j)] + nums[j]) % targetSum;
                break;
            }
        }
    }

    return dp.back() == 0;
}
```

#### Complexity Analysis
**Time Complexity**: O(2^n * n), where n is the size of the input array.
**Space Complexity**: O(2^n).

---
title: Problem 2 Flatten Binary Tree to Linked List
description:
duration: 900
card_type: cue_card
---

### Description
Given the root of a binary tree, the task is to flatten the tree into a "linked list" using the same TreeNode class. The flattened "linked list" should follow the same order as a pre-order traversal of the binary tree.

### Example

Consider the binary tree:
```md
    1
   / \
  2   5
 / \   \
3   4   6

```

After applying the flatten function, the tree is transformed into:
```md
1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

```

### Approach 1: Brute Force

In a brute force approach, we can perform a pre-order traversal of the binary tree and construct a new linked list using the elements in the order of traversal. This can be done by traversing the tree and creating a new node for each encountered element, linking the nodes in pre-order.

#### Code
```java
public class Solution {
    public TreeNode flatten(TreeNode root) {
        if (root == null) {
            return null;
        }

        List<Integer> preorderList = new ArrayList<>();
        preOrderTraversal(root, preorderList);

        TreeNode dummy = new TreeNode(0);
        TreeNode current = dummy;

        for (int val : preorderList) {
            current.right = new TreeNode(val);
            current = current.right;
        }

        return dummy.right;
    }

    private void preOrderTraversal(TreeNode node, List<Integer> list) {
        if (node != null) {
            list.add(node.val);
            preOrderTraversal(node.left, list);
            preOrderTraversal(node.right, list);
        }
    }
}
```

### Complexity Analysis

**Time complexity**: O(n)
The pre-order traversal visits each node once.

**Space complexity**: O(n)
The space required for the pre-order traversal list is proportional to the number of nodes in the tree.

---
title: Flatten Binary Tree to Linked List Optimized Approach
description:
duration: 900
card_type: cue_card
---
 
The strategy is to recursively flatten the right subtree first, and then handle the left subtree. If a node has a left child, it means there is a subtree on the left that needs to be flattened. To preserve the structure, the right child of the current node is linked to the rightmost node of the left subtree before making the left child the new right child. The left child is set to null after this operation.

#### Code
```java
public class Solution {
    public TreeNode flatten(TreeNode root) {
        if (root != null) {
            // Flatten the right subtree first
            flatten(root.right);
            
            // Check if there is a left child
            if (root.left != null) {
                // Flatten the left subtree
                flatten(root.left);
                
                // Find the rightmost node in the left subtree
                TreeNode node = root.left;
                while (node.right != null) {
                    node = node.right;
                }
                
                // Attach the right subtree to the rightmost node of the left subtree
                node.right = root.right;
                
                // Make the left child the new right child
                root.right = root.left;
                
                // Set the left child to null
                root.left = null;
            }
        }
        return root;
    }
}
```

#### Complexity Analysis

**Time complexity**: O(n) - The function visits each node once.
**Space complexity**: O(h) - The space complexity is dominated by the recursion stack, which is proportional to the height of the binary tree (h). In the worst case, for a skewed tree, it could be O(n), but in balanced trees, it is typically O(log n). The in-place transformation is done without using additional space proportional to the number of nodes in the tree.

---
title: Problem 3 Insert Delete GetRandom O(1)Approach
description:
duration: 900
card_type: cue_card
---

### Problem Description
Implement the `RandomizedSet` class:

* `RandomizedSet()` Initializes the RandomizedSet object.
* `bool insert(int val)` Inserts an item `val` into the set if not present. Returns `true` if the item was not present, `false` otherwise.
* `bool remove(int val)` Removes an item `val` from the set if present. Returns `true` if the item was present, `false` otherwise.
* `int getRandom()` Returns a random element from the current set of elements (it's guaranteed that at least one element exists when this method is called). Each element must have the same probability of being returned.

You must implement the functions of the class such that each function works in average O(1) time complexity.

### Approach
For implementing the RandomizedSet class, we can use both an unordered map (`mymap`) and a vector (`list`). This combination allows for constant-time average complexity for the insert, remove, and getRandom operations.

vector (list):
* Stores the elements of the set.
* Indices of elements in this vector correspond to their positions in the set.

unordered_map (mymap):
* Used for mapping elements to their indices in the vector.
* Key: Element in the set.
* Value: Index of the element in the vector.

**Insert Operation**:
* Check if the value exists in the set using `mymap`.
* If the value exists, return `false`.
* If the value doesn't exist:
    * Add the value to `mymap` with its index in `list`.
    * Append the value to the end of `list`.
    * Return `true` to indicate a successful insertion.

**Remove Operation**:

* Check if the value exists in the set using `mymap`.
* If the value exists:
    * Retrieve the index of the value in list from `mymap`.
    * Swap the value to be removed with the last value in `list`.
    * Update `mymap` to reflect the new index of the last value.
    * Remove the last element from `list`.
    * Erase the value from `mymap`.
    * Return `true` to indicate a successful removal.
* If the value doesn't exist, return `false`.

**GetRandom Operation**:
* Generate a random index within the current size of `list`.
* Return the element at the randomly selected index.


### Code
```cpp
#include <unordered_map>
#include <vector>
#include <cstdlib>

class RandomizedSet {
private:
    std::unordered_map<int, int> mymap;
    std::vector<int> list;

public:
    RandomizedSet() {
        // Constructor: Initialize member variables.
    }

    bool insert(int val) {
        // If the value already exists, return false.
        if (mymap.count(val) != 0)
            return false;

        // Insert the value to the map with its index in the list.
        mymap[val] = list.size();

        // Add the value to the list as well.
        list.push_back(val);
        return true;
    }

    bool remove(int val) {
        // Check if the value exists in the map.
        if (mymap.count(val) != 0) {
            // Get the index of the value in the list.
            int index = mymap[val];

            // Swap the value to be removed with the last value in the list.
            int lastValue = list.back();

            // Replace the value at the given index with the last value.
            list[index] = lastValue;

            // Remove the last element from the list.
            list.pop_back();

            // Update the index of the last value in the map to reflect its new position.
            mymap[lastValue] = index;

            // Remove the value from the map.
            mymap.erase(val);
            return true;
        }
        return false;
    }

    int getRandom() {
        // Generate a random index within the current size of the list.
        int randomIndex = std::rand() % list.size();
        return list[randomIndex];
    }
};

```

### Complexity Analysis
**Insert and Remove**
* Average time complexity is O(1) due to constant-time operations of `mymap` and `list`.

**GetRandom**
* Time complexity is O(1) since it involves random index generation and retrieval from `list`.

**Space Complexity**
* O(n) where n is the number of elements in the set, as both `mymap` and `list` grow with the size of the set.

---
title: Problem 4 Best Time to Buy and Sell Stocks III
description:
duration: 900
card_type: cue_card
---

### Problem Description
You are given an array A, where the ith element is the price of a given stock on day i. Design an algorithm to find the maximum profit you can achieve by completing at most 2 transactions. Note that you cannot engage in multiple transactions at the same time, meaning you must sell the stock before buying again.

### Example
**Input**: [3, 3, 5, 0, 0, 3, 1, 4]

**Output**: 6

**Explanation**: Buy on day 4 (price = 0) and sell on day 6 (price = 3), then buy on day 7 (price = 1) and sell on day 8 (price = 4). Total profit = 3 + 3 = 6.

### Brute Force Solution
One way to approach this problem is to consider every possible pair of transactions and calculate the profit for each pair. Keep track of the maximum profit obtained. However, this approach would have a time complexity of O(n^4), making it impractical for large inputs.

### Optimal Solution Approach
To optimize the solution, we can observe that we need to keep track of four variables for each day:

**FirstBuy**: The minimum price when buying for the first time.
**FirstSell**: The maximum profit when selling for the first time.
**SecondBuy**: The minimum price when buying for the second time.
**SecondSell**: The maximum profit when selling for the second time.
For each day, we simultaneously update these variables based on the current stock price.

### Optimal Solution Code
```cpp
int maxProfit(const vector<int> &A) {
    int FirstBuy = INT_MAX, FirstSell = 0, SecondBuy = INT_MAX, SecondSell = 0;

    for (int i = 0; i < A.size(); i++) {
        int b1 = FirstBuy, s1 = FirstSell, b2 = SecondBuy, s2 = SecondSell;

        FirstBuy = min(b1, A[i]);
        FirstSell = max(s1, A[i] - b1);
        SecondBuy = min(b2, A[i] - s1);
        SecondSell = max(s2, A[i] - b2);
    }

    return max(FirstSell, SecondSell);
}
```