---
# System prepended metadata

title: DSA 4 - Interview Problems

---

# DSA 4 - Interview Problems

---
### Problem 1 Target Sum

You are given a set of non-negative integers and a target sum. The task is to determine whether there exists a subset of the given set whose sum is equal to the target sum.


**Examples**
**Input**: set = {3, 34, 4, 12, 5, 2}, sum = 9
**Output**: True
**Explanation**: There is a subset (4, 5) with a sum of 9.

:::warning
Please take some time to think about the brute force approach on your own before reading further.....
:::

#### Approach: Brute Force
The brute force approach involves exploring all possible subsets of the given set. This is achieved through a recursive function named isSubsetSum(set, n, sum). The function explores two possibilities for each element: including it in the subset or excluding it. The base cases check if the sum is zero or if there are no more elements to consider.


#### Code
```java
boolean isSubsetSum(int[] set, int n, int sum) {
    if (sum == 0) return true;
    if (n == 0 && sum != 0) return false;

    // Explore two possibilities: include the current element or exclude it
    return isSubsetSum(set, n - 1, sum) || isSubsetSum(set, n - 1, sum - set[n - 1]);
}

```

#### Complexity Analysis
* **Time complexity**: O(2^n) - Exponential
* **Space complexity**: O(1)

---
### Target Sum Dynamic Programming Approach
#### Approach: 
To optimize the solution, dynamic programming is employed. A 2D array `dp` is used to store results of subproblems. The value `dp[i][j]` represents whether it's possible to obtain a sum of `j` using the first `i` elements of the set.

#### Code
```java
boolean isSubsetSum(int[] set, int n, int sum) {
    boolean[][] dp = new boolean[n + 1][sum + 1];

    // Initialize the first column as true, as sum 0 is always possible
    for (int i = 0; i <= n; i++) dp[i][0] = true;

    // Fill the dp array using a bottom-up approach
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= sum; j++) {
            dp[i][j] = dp[i - 1][j];
            if (j >= set[i - 1]) dp[i][j] = dp[i][j] || dp[i - 1][j - set[i - 1]];
        }
    }

    return dp[n][sum];
}

```

#### Complexity Analysis
* **Time complexity**: O(n * sum)
* **Space complexity**: O(n * sum)

---
### Problem 2 Minimum Jumps to Reach End

You are given a 0-indexed array of integers nums of length n. You are initially positioned at nums[0].

Each element nums[i] represents the maximum length of a forward jump from index i. In other words, if you are at nums[i], you can jump to any nums[i + j] where:
* 0 <= j <= nums[i]
* i + j < n

Return the minimum number of jumps to reach nums[n - 1]. The test cases are generated such that you can reach nums[n - 1].


#### Example 1
**Input**: nums = [2,3,1,1,4]
**Output**: 2
**Explanation**: The minimum number of jumps to reach the last index is 2. Jump 1 step from index 0 to 1, then 3 steps to the last index.

#### Example 2
**Input**: nums = [2,3,0,1,4]
**Output**: 2

:::warning
Please take some time to think about the solution approach on your own before reading further.....
:::

#### Intuition

The code utilizes a greedy approach, aiming to minimize the number of jumps by selecting the jump that maximizes the range of reachable positions at each step.

#### Approach
* Initialize a counter to keep track of the number of jumps (counter).
* Use a while loop to iterate through the array until the end is reached.
* In each iteration:
    * Calculate the range of positions that can be reached from the current position (range).
    * If the calculated range includes the last position or exceeds it, exit the loop.
    * Find the position within the current range that maximizes the next reachable position (temp).
    * Update the current position to the selected position (temp).
    * Increment the jump counter (counter).
* Repeat the process until the end is reached.

#### Code
```java
class Solution {
    public int jump(int[] nums) {
        int i = 0;
        int counter = 0;
        
        // If the array has only one element, no jumps are needed
        if (nums.length == 1)
            return 0;
        
        while (i < nums.length) {
            counter++;
            int range = i + nums[i];
            
            // If the range includes the last position or exceeds it, exit the loop
            if (range >= nums.length - 1)
                break;
            
            int max = 0;
            int temp = 0;
            
            // Find the position within the current range that maximizes the next reachable position
            for (int k = i + 1; k <= range; k++) {
                if (nums[k] + k >= max) {
                    max = nums[k] + k;
                    temp = k;
                }
            }
            i = temp;
        }
        return counter;
    }
}

```

#### Complexity Analysis
**Time complexity**: O(n)
* The algorithm iterates through each element of the array once.
* The inner loop within each iteration also traverses a limited number of positions.

**Space complexity**: O(1)
* The algorithm uses a constant amount of extra space.
* The space requirements do not depend on the size of the input array.

---
### Problem 3 N digit numbers

Find out the number of A digit positive numbers, whose digits on being added equals to a given number B.

Note that a valid number starts from digits 1-9 except the number 0 itself. i.e. leading zeroes are not allowed.

Since the answer can be large, output answer modulo 1000000007

:::warning
Please take some time to think about the brute force approach on your own before reading further.....
:::

### Brute Force Approach 
#### Objective 
Count the number of A-digit positive numbers whose digit sum equals a given number B.

#### Idea
* Generate all possible A-digit numbers 
* For each number, check if the sum of its digits equals B.
* Keep track of the count of valid numbers.


#### Code
```cpp
int bruteForceCount(int A, int B) {
    int count = 0;
    for (int num = pow(10, A - 1); num < pow(10, A); ++num) {
        int digitSum = 0;
        int temp = num;
        while (temp > 0) {
            digitSum += temp % 10;
            temp /= 10;
        }
        if (digitSum == B) {
            count++;
        }
    }
    return count;
}
```

---
### N digit numbers optimization using Recursion
#### Observation
The brute force approach involves iterating through all A digit numbers and checking the count of numbers whose digit sum equals B.

#### Optimized Recursive Approach

**Recursive Function Definition**

* The recursive function `countWays(id, sum)` is defined to represent the count of A-digit numbers with a digit sum equal to sum.
* The base cases are as follows:
    * If sum becomes negative, it implies that the digit sum has exceeded the target, so the count is 0.
    * If id becomes 0, meaning all digits have been considered, the count is 1 if sum is 0 (indicating the target sum has been achieved), and 0 otherwise.
    * Memoization is used to store and retrieve previously calculated values, preventing redundant computations.

**Memoization Table**

* The memo vector is a 2D table of size (A + 1) x (B + 1) initialized with -1 to represent uncalculated states.
* The value `memo[id][sum]` stores the count of A-digit numbers with a digit sum of sum that has already been calculated.


**Recursive Part**

* The function explores all possible digits from 0 to 9 in a loop.
* For each digit, it recursively calls `countWays(id - 1, sum - digit, memo)` to calculate the count of (A-1)-digit numbers with the updated digit sum.
* The results are summed up, and the modulus operation is applied to avoid integer overflow.


#### Optimized Recursive Code

```cpp
int memoizationCount(int A, int B) {
    vector<vector<int>> memo(A + 1, vector<int>(B + 1, -1));
    return countWays(A, B, memo);
}

int countWays(int id, int sum, vector<vector<int>>& memo) {
    if (sum < 0) return 0;
    if (id == 0) return (sum == 0) ? 1 : 0;
    if (memo[id][sum] != -1) return memo[id][sum];

    int ways = 0;
    for (int digit = 0; digit <= 9; ++digit) {
        ways += countWays(id - 1, sum - digit, memo);
        ways %= 1000000007;
    }
    return memo[id][sum] = ways;
}
```

#### Optimized Iterative Code
```cpp
int iterativeCount(int A, int B) {
    vector<vector<int>> dp(A + 1, vector<int>(B + 1, 0));

    // Base case
    for (int digit = 1; digit <= 9; ++digit) {
        if (digit <= B) dp[1][digit] = 1;
    }

    // Build DP table
    for (int id = 2; id <= A; ++id) {
        for (int sum = 1; sum <= B; ++sum) {
            for (int digit = 1; digit <= 9; ++digit) {
                if (sum - digit >= 0) {
                    dp[id][sum] += dp[id - 1][sum - digit];
                    dp[id][sum] %= 1000000007;
                }
            }
        }
    }

    return dp[A][B];
}
```

**Time Complexity** : O(A * B)
**Space Complexity** : O(A * B)


---
### Problem 4 Maximum Profit from Stock Prices

Given an array A where the i-th element represents the price of a stock on day i, the objective is to find the maximum profit. We're allowed to complete as many transactions as desired, but engaging in multiple transactions simultaneously is not allowed.

:::warning
Please take some time to think about the solution approach on your own before reading further.....
:::

#### Approach
Let's start with some key observations:

**Note 1**: It's never beneficial to buy a stock and sell it at a loss. This intuitive insight guides our decision-making process.

**Note 2**: If the stock price on day i is less than the price on day i+1, it's always advantageous to buy on day i and sell on day i+1.

Now, let's delve into the rationale behind Note 2:

**Proof**: If the price on day i+1 is higher than the price on day i, buying on day i and selling on day i+1 ensures a profit. If we didn't sell on day i+1 and waited for day i+2 to sell, the profit would still be the same. Thus, it's optimal to sell whenever there's a price increase.

#### DP-Based Solution
Now, let's transition to a dynamic programming solution based on the following recurrence relation:

Let Dp[i] represent the maximum profit you can gain in the region (i, i+1, ..., n).

**Recurrence Relation**: `Dp[i] = max(Dp[i+1], -A[i] + max(A[j] + Dp[j] for j > i))`

Here, Dp[i] considers either continuing with the profit from the next day (Dp[i+1]) or selling on day i and adding the profit from subsequent days.

#### Base Cases

When i is the last day (i == n-1), Dp[i] = 0 since there are no more future days.
When i is not the last day, Dp[i] needs to be computed using the recurrence relation.

#### Direction of Computation

We start computing Dp[i] from the last day and move towards the first day.

#### Code
```cpp
int max_profit(vector<int>& A) {
    int n = A.size();
    vector<int> dp(n, 0);

    for (int i = n - 2; i >= 0; --i) {
        dp[i] = dp[i + 1];

        for (int j = i + 1; j < n; ++j) {
            if (j + 1 < n) {
                dp[i] = max(dp[i], -A[i] + A[j] +  dp[j + 1]);
            } else {
                dp[i] = max(dp[i], -A[i] + A[j]);
            }
        }
    }

    return dp[0];
}
```


#### Optimized Code
The provided code snippet in C++ contains this observation-based solution. It iterates through the array, checks for price increases, and accumulates the profits accordingly.
```cpp
int Solution::maxProfit(const vector<int> &A) {
    int total = 0, sz = A.size();
    for (int i = 0; i < sz - 1; i++) {
        if (A[i + 1] > A[i]) 
            total += A[i + 1] - A[i];
    }
    return total;
}
```

**Time Complexity** : O(|A|)
**Space Complexity** : O(1)
