Showing posts with label DP. Show all posts
Showing posts with label DP. Show all posts

Monday, August 19, 2024

[LeetCode] 2 Keys Keyboard

Problem: There is only one character 'A' on the screen of a notepad. You can perform one of two operations on this notepad for each step:

  • Copy All: You can copy all the characters present on the screen (a partial copy is not allowed).
  • Paste: You can paste the characters which are copied last time.

Given an integer n, return the minimum number of operations to get the character 'A' exactly n times on the screen.

Example:

Input: n = 3
Output: 3
Explanation: Initially, we have one character 'A'.
In step 1, we use Copy All operation.
In step 2, we use Paste operation to get 'AA'.
In step 3, we use Paste operation to get 'AAA'.
Input: n = 1
Output: 0


Approach: We are going to use bottom-up DP here. We can define a 1D array where table[i] tells the minimum number of steps required to display i A's. Obviously table[n] will be our answer.

Now the main logic here is to get the relations between table[i] and table[i-1]...table[1], right?  Let's understand it; let's say we had j A's on the screen where 1 <= j < i. Now we can say we can copy these j A's and paste it exactly (i - j) / j times to display i A's so total number of operations will be:

f(i) = f(j) + 1 + (i - j) / j

Why? Let's see:

  • f(j) - Number of steps to display j A's
  • 1 - Copy All
  • (i - j) / j - Number of pastes.

Now let's simplify this equation:

f(i) = f(j) + 1 + (i - j) / j

=> f(i) = f(j) + 1 + (i/j) - (j/j)

=> f(i) = f(j) + 1 + (i/j) - 1

=> f(i) = f(j) + i / j

So here is our final relation:

f(i) = f(j) + i / j

We also need to make sure that i should be divisible by j as it's kind of repeated pastes. Now we just need to get the minimum of all such j's where 1 <= j < i and i is divisible by j.

That's all!


Implementation in C#:

    public int MinSteps(int n)
    {
        if (n <= 1)
        {
            return 0;
        }
        int[] table = new int[n + 1];
        for (int i = 2; i <= n; ++i)
        {
            table[i] = int.MaxValue;
            for (int j = 1; j <= i / 2; ++j)
            {
                if (i % j != 0)
                {
                    continue;
                }
                table[i] = Math.Min(table[i], table[j] + i / j);
            }
        }
        return table[n];
    }


Complexity: O(n^2)

Friday, May 17, 2024

[LeetCode] Maximum Sum Circular Subarray

Problem: Given a circular integer array nums of length n, return the maximum possible sum of a non-empty subarray of nums.

A circular array means the end of the array connects to the beginning of the array. Formally, the next element of nums[i] is nums[(i + 1) % n] and the previous element of nums[i] is nums[(i - 1 + n) % n].

A subarray may only include each element of the fixed buffer nums at most once. Formally, for a subarray nums[i], nums[i + 1], ..., nums[j], there does not exist i <= k1, k2 <= j with k1 % n == k2 % n.

Example:

Input: nums = [1,-2,3,-2]
Output: 3
Explanation: Subarray [3] has maximum sum 3.
Input: nums = [5,-3,5]
Output: 10
Explanation: Subarray [5,5] has maximum sum 5 + 5 = 10.
Input: nums = [-3,-2,-3]
Output: -2
Explanation: Subarray [-2] has maximum sum -2.


Approach: This problem is an extension of Kadanes max sum problem. If you see if the max sum exist in the middle than Kadanes approach is sufficient but if the max sum exist in circular subarray then we need to come up with a different approach.

Let's say the other special sum in case of circular array is called circular sum. How does it look like:


So if you see above array, you will understand

circular sum = sum of prefix of array + sum of suffix of array.

Now we just need to get max of all such calculated circular sums. To do it efficiently, we can prepopulate a rightmax array where rightmax[i] is the max sum of subarray nums[i...n-1]. With this we can easily calculate the max sum which is:

max_sum = MAX(kadanes_max_sum, max_circular_sum)

This is time efficient but if you see the above approach is iterating the input array twice and is using extra space in the form of rightmax array. Let's try to do better.

If you see max_circular_sum is equal to total sum of array - kadanes_min_sum. Right! We can calculate it using kadanes min sum with same approach by which we get kadanes max sum. It's just that we need to use Min instead of Max in the algo.

The only exception will be when all the numbers are negative which is easy to handle as in that case we just need to return kadanes_max_sum.

That's all!


Implementation in C#:

Approach 1: Using extra space:

    public int MaxSubarraySumCircular(int[] nums)
    {
        int length = nums?.Length ?? 0;
        if (length == 0)
        {
            return 0;
        }
        if (length == 1)
        {
            return nums[0];
        }
        int[] rightMax = new int[length];
        rightMax[length - 1] = nums[length - 1];
        int suffixSum = nums[length - 1];
        for (int i = length - 2; i >= 0; --i)
        {
            suffixSum += nums[i];
            rightMax[i] = Math.Max(suffixSum, rightMax[i + 1]);
        }
        int maxSum = int.MinValue;
        int circularSum = int.MinValue;
        int currSum = 0;
        int prefixSum = 0;
        for (int i = 0; i < length; ++i)
        {
            currSum = Math.Max(currSum, 0) + nums[i];
            maxSum = Math.Max(maxSum, currSum);
            prefixSum += nums[i];
            if (i < length - 1)
            {
                circularSum = Math.Max(circularSum, prefixSum + rightMax[i + 1]);
            }
        }
        return Math.Max(maxSum, circularSum);
    }

Approach 2: No extra space:

       public int MaxSubarraySumCircular(int[] nums)
    {
        int length = nums?.Length ?? 0;
        if (length == 0)
        {
            return 0;
        }
        if (length == 1)
        {
            return nums[0];
        }
        int maxSum = int.MinValue;
        int currMaxSum = 0;
        int currMinSum = 0;
        int minSum = int.MaxValue;
        int totalSum = 0;
        for (int i = 0; i < length; ++i)
        {
            currMaxSum = Math.Max(currMaxSum, 0) + nums[i];
            maxSum = Math.Max(maxSum, currMaxSum);
            currMinSum = Math.Min(currMinSum, 0) + nums[i];
            minSum = Math.Min(currMinSum, minSum);
            totalSum += nums[i];
        }
        return totalSum == minSum ? maxSum : Math.Max(maxSum, totalSum - minSum);
    }

Complexity: O(n)

Sunday, March 31, 2024

[Amazon][LeetCode] Best Time to Buy and Sell Stock

Problem: You are given an array prices where prices[i] is the price of a given stock on the ith day.

You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock.

Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0.

Example:

Input: prices = [7,1,5,3,6,4]
Output: 5
Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell.
Input: prices = [7,6,4,3,1]
Output: 0
Explanation: In this case, no transactions are done and the max profit = 0.


Approach: One way to do it is we can maintain an array leftMin where leftMin[i] will give the minimum value in the prices[0...i]. Similarly we can maintain an array rightMax where rightMax[i] will give the maximum of prices[i...n-1]. It is obvious that maximum of all the rightMax[i] - leftMin[i] where i = 0...n-1 will be our answer.

The above approach is a big optimiztion over brute force approach but it's still doing three iterations and moreover the space is 2*n. Let's try to do better.

Instead of maintaining rightMax array we can have a variable maxElement which gives at any ith index the maximum of prices[i...n-1] and then we can just return the max of maxElement - leftMin[i] for i = 0...n-1. See now we have just save n space and also we are saving one iteration.

If we see like maxElement, we can maintain a minElement which gives at ith index the min of pices[0...i], so now do we need to actually need to create any addition array? No right. Actually we don't need maxElement from right. At any i (i = 0...n-1), if we have Min(prices[0...i]) which is our minElement, we can say the max profit using prices[i] will be prices[i] - minElement so if we take max of all prices[i] - minElement for every i = 0...n-1 then we will have our answer.

That's all!


Implementation in C#:

Using left min array:

    public int MaxProfit(int[] prices)
    {
        int length = prices?.Length ?? 0;
        if (length <= 1)
        {
            return 0;
        }
        int[] leftMinArr = new int[length];
        leftMinArr[0] = prices[0];
        for (int i = 1; i < length; ++i)
        {
            leftMinArr[i] = Math.Min(leftMinArr[i - 1], prices[i]);
        }
        int maxElement = prices[length - 1];
        int maxProfit = Math.Max(0, maxElement - leftMinArr[length - 1]);
        for (int i = length - 2; i >= 0; --i)
        {
            maxElement = Math.Max(maxElement, prices[i]);
            maxProfit = Math.Max(maxProfit, maxElement - leftMinArr[i]);
        }
        return maxProfit;
    }

Without using extra space:

    public int MaxProfit(int[] prices)
    {
        int length = prices?.Length ?? 0;
        if (length <= 1)
        {
            return 0;
        }
        int minElement = prices[0];
        int maxProfit = 0;
        for (int i = 0; i < length; ++i)
        {
            minElement = Math.Min(minElement, prices[i]);
            maxProfit = Math.Max(maxProfit, prices[i] - minElement);
        }
        return maxProfit;
    }

Complexity: O(n)    

Wednesday, January 4, 2023

[LeetCode] Minimum Rounds to Complete All Tasks

Problem: You are given a 0-indexed integer array tasks, where tasks[i] represents the difficulty level of a task. In each round, you can complete either 2 or 3 tasks of the same difficulty level.

Return the minimum rounds required to complete all the tasks, or -1 if it is not possible to complete all the tasks.

Example:

Input: tasks = [2,2,3,3,2,4,4,4,4,4]
Output: 4
Explanation: To complete all the tasks, a possible plan is:
- In the first round, you complete 3 tasks of difficulty level 2. 
- In the second round, you complete 2 tasks of difficulty level 3. 
- In the third round, you complete 3 tasks of difficulty level 4. 
- In the fourth round, you complete 2 tasks of difficulty level 4.  
It can be shown that all the tasks cannot be completed in fewer than 4 rounds, so the answer is 4.
Input: tasks = [2,3,3]
Output: -1
Explanation: There is only 1 task of difficulty level 2, but in each round, you can only complete either 2 or 3 tasks of the same difficulty level. Hence, you cannot complete all the tasks, and the answer is -1.


Approach: The first approach that came to my mind is that it is similar to Coin Change problem. Basically we use a map to count the frequency of each task difficulty. Now for each frequency, the frequency becomes the value and the available coins are of value 2 and 3. We can get the minimum number of coins to make this value(frequency). We can then sum up this for all tasks and this sum will be our answer. We can optimize it further a bit, if we fill our DP table for the maximum frequency then we don't have to repeat our calculation for other frequencies as these are already filled.

Can we do better? The above solution is taking O(n) time and space (max frequency can't be more than number of tasks). The above solution is doing 3 passes; one for frequency calculation, second for filling the DP table and third one is for final calculation of rounds. It is also taking 2 * n space; Map and DP table. Let's try to do reduce the number of passes and space.

If you remember except 1 any natural number can be written in the form of 3x + 2y. In order to minimize the (x + y) which is our answer (number of rounds), we need to maximize x. With this intuition/greed, Let's see how we can solve this question.

Given that we want maximize x, we can say the number could be of 3 types:

  • 3 * x (multiple of 3)
  • 3 * x + 1 (Remainder 1)
  • 3 * x + 2 (Remainder 2)

In the first case answer is straight forward i.e. x but what to do in the next two cases. First let's take the easy case which is the third case; 3 * x + 2, we can see that here y is 1 (3 * x + 2 * y), so the answer will be x + 1.

Now let's look at remaining case which is 3 * x + 1. We need it in the form of 3 * x + 2 * y so we can do the following:

3 * x + 1

= > 3 * ( x - 1 + 1) + 1;

=> 3 * x - 3 + 3 + 1

=> 3 * x - 3 + 4

=> 3 * (x - 1) + 4

=> 3 * (x - 1) + 2 * 2

=> 3 * z + 2 * 2 where z = x - 1

so here y is 2 and x is z. The sum of x + y = z + 2 = (x - 1) + 2 = x + 1.

So we can see that in both cases 2 and 3 the answer is x + 1. Where x is N / 3.

That's all about the approach!

 

Implementation in C#:

Approach 1: DP:

    public int MinimumRounds(int[] tasks)
    {
        Dictionary<int, int> taskFreqMap = new Dictionary<int, int>();
        int maxFreq = 0;
        foreach (int task in tasks)
        {
            if (!taskFreqMap.ContainsKey(task))
            {
                taskFreqMap[task] = 0;
            }
            ++taskFreqMap[task];
            if (maxFreq < taskFreqMap[task])
            {
                maxFreq = taskFreqMap[task];
            }
        }
        if (maxFreq < 2)
        {
            return -1;
        }
        int rounds = 0;
        int[] table = new int[maxFreq + 1];
        table[2] = 1;
        if (maxFreq >= 3)
        {
            table[3] = 1;
        }
        this.DistributeTasks(maxFreq, table);
        foreach (var taskItem in taskFreqMap)
        {
            int currFreq = taskItem.Value;
            if (table[currFreq] == 0)
            {
                return -1;
            }
            rounds += table[currFreq];
        }
        return rounds;
    }

    private void DistributeTasks(int value, int[] table)
    {
        if (value < 2)
        {
            return;
        }

        for (int i = 4; i <= value; ++i)
        {
            if (table[i - 2] == 0 && table[i - 3] == 0)
            {
                table[i] = 0;
                continue;
            }
            if (table[i - 2] == 0)
            {
                table[i] = table[i - 3] + 1;
            }
            else if (table[i - 3] == 0)
            {
                table[i] = table[i - 2] + 1;
            }
            else
            {
                table[i] = Math.Min(table[i - 2], table[i - 3]) + 1;
            }
        }
    }

Approach 2: Math:

    public int MinimumRounds(int[] tasks)
    {
        Dictionary<int, int> taskFreqMap = new Dictionary<int, int>();
        foreach (int task in tasks)
        {
            if (!taskFreqMap.ContainsKey(task))
            {
                taskFreqMap[task] = 0;
            }
            ++taskFreqMap[task];
        }
        int rounds = 0;
        foreach (var taskItem in taskFreqMap)
        {
            int currFreq = taskItem.Value;
            if (currFreq < 2)
            {
                return -1;
            }
            if (currFreq % 3 == 0)
            {
                rounds += (currFreq / 3);
            }
            else
            {
                rounds += (currFreq / 3 + 1);
            }
        }
        return rounds;
    }

Complexity: O(n) in both approaches.

Monday, December 26, 2022

[LeetCode] Jump Game II

Problem: 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] and
  • 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:

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.

Approach: My observation about the problem is if we can reach an index i and then we can definitely reach index i - 1. The proof is simple:
  • If the length of jump is 1 then we are already at i - 1.
  • Otherwise we can take length - 1 jump to reach i - 1.
In this way we can prove that we can reach any j where j = 0 to i - 1. Now we can maintain a table say jump where jump[i]  tells the farthest point it can reach. We can fill this table in following way:
  • jump[0] = nums[0]
  • jump[i] = MAX(jump[i - 1], i + jump[i]
Now we can simply use this table to get our answer. 

The above solution works and it works efficiently but it's taking extra space. Let's try to reduce the space and also let's see the problem in different way. We can be greedy here too. Idea is simple let's say the jump range at any index is (currStart, currEnd). We know we need to make a jump here at a particular index then being greedy we want to go till the currEnd and then take a jump. 

But now what's the jump we want to take is the maximum reachable point from currStart...currEnd and when we take a jump the next currEnd will become this maximum reachable point.

Implementation in C#:

Apporach 1: DP:

    public int Jump(int[] nums)
    {
        int length = nums?.Length ?? 0;
        if(length <= 1)
        {
            return 0;
        }
        int[] jumps = new int[length];
        jumps[0] = nums[0];
        for (int i = 1; i < length; ++i)
        {
            jumps[i] = Math.Max(jumps[i - 1], i + nums[i]);
        }
        int numOfJumps = 0;
        for (int i = 0; i < length - 1; i = jumps[i])
        {
            ++numOfJumps;
        }
        return numOfJumps;
    }


Apporach 2: Greedy:

    public int Jump(int[] nums)
    {
        int length = nums?.Length ?? 0;
        if (length <= 1)
        {
            return 0;
        }
        int jumps = 0, currEnd = 0, longestJumpIndex = 0;
        for (int i = 0; i < length; ++i)
        {
            longestJumpIndex = Math.Max(longestJumpIndex, i + nums[i]);
            if (i == currEnd)
            {
                ++jumps;
                currEnd = longestJumpIndex;
                if (currEnd == length - 1)
                {
                    break;
                }
            }
        }
        return jumps;
    }


Complexity: O(n)

Tuesday, December 13, 2022

[LeetCode] Stone Game III

Problem: Alice and Bob continue their games with piles of stones. There are several stones arranged in a row, and each stone has an associated value which is an integer given in the array stoneValue.

Alice and Bob take turns, with Alice starting first. On each player's turn, that player can take 1, 2, or 3 stones from the first remaining stones in the row.

The score of each player is the sum of the values of the stones taken. The score of each player is 0 initially.

The objective of the game is to end with the highest score, and the winner is the player with the highest score and there could be a tie. The game continues until all the stones have been taken.

Assume Alice and Bob play optimally.

Return "Alice" if Alice will win, "Bob" if Bob will win, or "Tie" if they will end the game with the same score.

Example:

Input: values = [1,2,3,7]
Output: "Bob"
Explanation: Alice will always lose. Her best move will be to take three piles and the score become 6. Now the score of Bob is 7 and Bob wins.
Input: values = [1,2,3,-9]
Output: "Alice"
Explanation: Alice must choose all the three piles at the first move to win and leave Bob with negative score.
If Alice chooses one pile her score will be 1 and the next move Bob's score becomes 5. In the next move, Alice will take the pile with value = -9 and lose.
If Alice chooses two piles her score will be 3 and the next move Bob's score becomes 3. In the next move, Alice will take the pile with value = -9 and also lose.
Remember that both play optimally so here Alice will choose the scenario that makes her win.
Input: values = [1,2,3,6]
Output: "Tie"
Explanation: Alice cannot win this game. She can end the game in a draw if she decided to choose all the first three piles, otherwise she will lose.


Approach: Every time Alice make a move, Bob is going to make his move in such a manner that Alice get the least points from the remaining.

For example- if Alice pick stone[i] and stone[i+1], Alice can pick stone[i +2] or stone[i + 2] + stone[i + 3] or stone[i + 2] + stone[i + 3] + stone[i + 4]. His strategy of picking one of the three combinations would be such that Alice will be able to make the minimum score out of the remaining stones. 

Let's maintain a table of size n where table[i] will tell the max score of Alice for stones[i...n-1]. We can fill the table in following manner:

  • table[n - 1] = stones[n- 1]
  • table[n - 2] = MAX(stones[n - 1], stones[n - 2] + stones[n -1])
  • table[n - 3] = MAX(MIN(stones[n - 3], stones[n - 3] + stones[n - 1]), stones[n - 3] + stones[n - 2], stones[n - 3] + stones[n - 2] + stones[n -1]) // We are choosing Min of stones[n - 3] and  stones[n - 3] + stones[n - 1] because in case of only 3 stones, if Alice chooses 1st stone, Bob can just choose 2nd stone then Alice has to take 3rd stone too. Example: [-1,-2,-3]

For rest of the i: n - 4 to 0

table[i] = MAX of the following

  1. stones[i] + MIN(table[i + 2], table[i + 3], table[i + 4]), // Case when Alice just chooses ith stone, Bob has to choose at least stones[i + 1] so table[i + 1] is not in the picture
  2. stones[i] + stones[i + 1] + MIN(table[i + 3], table[i + 4], table[i + 5]), // Case when Alice chooses ith and (i + 1)th stones
  3. stones[i] + stones[i + 1]  stones[i + 2] + MIN(table[i + 4], table[i + 5], table[i + 6]) // Case when Alice chooses  ith, (i + 1)th (i + 2)th stones

In the end we can check if Alice's max score (table[0]) is more than the half of the sum of all stone values. If yes then Alice will win otherwise Bob will win.

 

Implementation in C#:

    public string StoneGameIII(int[] stoneValue)
    {
int length = stoneValue.Length;

int sum = stoneValue.Sum();
int[] table = new int[length + 3];
for (int i = length - 1; i >= 0; --i)
{
if (i < length - 3)
{
table[i] = this.Max(stoneValue[i] +
                                    this.Min(
    table[i + 2],
table[i + 3],
table[i + 4]),
stoneValue[i] + stoneValue[i + 1] +
                                    this.Min(
    table[i + 3],
table[i + 4],
table[i + 5]),
stoneValue[i] +
                                    stoneValue[i + 1] +
                                    stoneValue[i + 2] +
                                    this.Min(
    table[i + 4],
table[i + 5],
table[i + 6]));
}
else if (i == length - 3)
{
int valAtChoosingFirst = this.Min(stoneValue[i],
                                            stoneValue[i] + stoneValue[i + 2]);
table[length - 3] = this.Max(valAtChoosingFirst,
stoneValue[i] + stoneValue[i + 1],
stoneValue[i] +
                                            stoneValue[i + 1] +
                                            stoneValue[i + 2]);
}
else if (i == length - 2)
{
table[length - 2] = this.Max(stoneValue[i],
                                            stoneValue[i] + stoneValue[i + 1]);
}
else
{
table[length - 1] = stoneValue[i];
}
}

if ((float)table[0] > sum / 2.0)
{
return "Alice";
}
else if ((float)table[0] == sum / 2.0)
{
return "Tie";
}

return "Bob";
}

private int Max(params int[] values)
{
return Enumerable.Max(values);
}
private int Min(params int[] values)
{
return Enumerable.Min(values);
}


Complexity: O(n)