Friday, December 30, 2022

[Google][GeeksForGeeks] Longest substring with k unique characters

Problem: Given a string you need to return longest possible substring that has exactly K unique characters. If there is more than one substring of longest possible length, then print any one of them. If there are no such substring, return empty string.

Example:

Input: str = "aabbcc", k = 2
Output: "aabb"
Explanation: Can return "bbcc"


Approach: This is a straight forward sliding window problem. We can use a map to maintain the characters and their count in the current window. Whenever the number of keys in the map are equal to the k, we can check if current window length is more than max length then we can update the max length.

if length of current window is more than k then we need to slide the window from the left. That's all!


Implementation in C#:

    private static string MaxSubstringWithKUniqueChars(string str, int k)

    {

        int length = str?.Length ?? 0;

        if (length < k)

        {

            return "";

        }     

        Dictionary<char, int> freqMap = new Dictionary<char, int>(); 

        int start = 0, maxLength = 0, startIndex = -1;

        for (int end = 0; end < length; ++end)

        {

            if (!freqMap.ContainsKey(str[end]))

            {

                freqMap[str[end]] = 0;

            }

            ++freqMap[str[end]];

            if (freqMap.Count == k)

            {

                int currLength = end - start + 1;

                if (maxLength < currLength)

                {

                    maxLength = currLength;

                    startIndex = start;

                }

            }

            while (freqMap.Count > k)

            {

                --freqMap[str[start]];

                if (freqMap[str[start]] == 0)

                {

                    freqMap.Remove(str[start]);

                }

                ++start;

            }

        }

        return startIndex != -1 ? str.Substring(startIndex, maxLength) : "";

    }


Complexity: O(n)

Monday, December 26, 2022

[LeetCode] Jump Game IV

Problem: Given an array of integers arr, you are initially positioned at the first index of the array.

In one step you can jump from index i to index:

  • i + 1 where: i + 1 < arr.length.
  • i - 1 where: i - 1 >= 0.
  • j where: arr[i] == arr[j] and i != j.

Return the minimum number of steps to reach the last index of the array.

Notice that you can not jump outside of the array at any time.

Example:

Input: arr = [100,-23,-23,404,100,23,23,23,3,404]
Output: 3
Explanation: You need three jumps from index 0 --> 4 --> 3 --> 9. Note that index 9 is the last index of the array.
Input: arr = [7]
Output: 0
Explanation: Start index is the last index. You do not need to jump.
Input: arr = [7,6,9,6,9,6,9,7]
Output: 1
Explanation: You can jump directly from index 0 to index 7 which is last index of the array.


Approach: This is straight forward BFS problem in a given graph where nodes (indices) are connected using the conditions given in the problem statement. We can optimize it using HashMap where we can maintain for value to indices mapping so that we don't have to traverse the array every time.


Implementation in C#:

    public int MinJumps(int[] arr)
    {
        int length = arr?.Length ?? 0;
        if (length <= 1)
        {
            return 0;
        }
        if (length == 2)
        {
            return 1;
        }
        var valToIndexMap = this.GetValToIndexMap(arr);
        bool[] visited = new bool[length];
        Queue<int> queue = new Queue<int>();
        queue.Enqueue(0);
        valToIndexMap[arr[0]].Remove(0);
        visited[0] = true;
        int steps = 0;
        while (queue.Count > 0)
        {
            int size = queue.Count;
           
            for (int i = 0; i < size; ++i)
            {
                int currIndex = queue.Dequeue();
                if (currIndex == length - 1)
                {
                    return steps;
                }
                foreach (int index in valToIndexMap[arr[currIndex]])
                {
                    visited[index] = true;
                    queue.Enqueue(index);
                }
                valToIndexMap[arr[currIndex]].Clear();             
                this.EnqueueValidAndUnvisitedIndex(
                                            queue,
                                            currIndex - 1,
                                            visited,
                                            arr,
                                            valToIndexMap);
                this.EnqueueValidAndUnvisitedIndex(
                                            queue,
                                            currIndex + 1,
                                            visited,
                                            arr,
                                            valToIndexMap);
            }
            ++steps;
        }
        return -1;
    }

    private void EnqueueValidAndUnvisitedIndex(
                                Queue<int> queue,
                                int index,
                                bool[] visited,
                                int[] arr,
                                Dictionary<int, HashSet<int>> valToIndexMap)
    {
        if (isValidAndUnvisitedIndex(index, arr.Length, visited))
        {
            visited[index] = true;
            valToIndexMap[arr[index]].Remove(index);
            queue.Enqueue(index);
        }
    }

    private bool isValidAndUnvisitedIndex(int index, int length, bool[] visited)
    {
        return index >= 0 &&
            index < length &&
            !visited[index];
    }

    private Dictionary<int, HashSet<int>> GetValToIndexMap(int[] arr)
    {
        var valToIndexMap = new Dictionary<int, HashSet<int>>();
        for (int i = 0; i < arr.Length; ++i)
        {
            if (!valToIndexMap.ContainsKey(arr[i]))
            {
                valToIndexMap[arr[i]] = new HashSet<int>();
            }
            valToIndexMap[arr[i]].Add(i);
        }
        return valToIndexMap;
    }

Complexity: O(n)

[LeetCode] Jump Game III

Problem: Given an array of non-negative integers arr, you are initially positioned at start index of the array. When you are at index i, you can jump to i + arr[i] or i - arr[i], check if you can reach to any index with value 0.

Notice that you can not jump outside of the array at any time.

Example:

Input: arr = [4,2,3,0,3,1,2], start = 5
Output: true
Explanation: 
All possible ways to reach at index 3 with value 0 are: 
index 5 -> index 4 -> index 1 -> index 3 
index 5 -> index 6 -> index 4 -> index 1 -> index 3 
Input: arr = [4,2,3,0,3,1,2], start = 0
Output: true 
Explanation: 
One possible way to reach at index 3 with value 0 is: 
index 0 -> index 4 -> index 1 -> index 3


Approach: Just by looking at it you can think of this problem of reaching from a source node to destination node in a Graph. Here the nodes are connected using "i + arr[i] or i - arr[i]" condition. We can used BFS or DFS to solve this problem. I am using DFS in my implementation.


Implementation in C#:

    public bool CanReach(int[] arr, int start)
    {
        if (arr[start] == 0)
        {
            return true;
        }
        int jump = arr[start];
        // Marking it as visited.
        arr[start] = -1;
        if (start + jump < arr.Length && arr[start + jump] != -1)
        {
            if (this.CanReach(arr, start + jump))
            {
                return true;
            }
        }
        if (start - jump >= 0 && arr[start - jump] != -1)
        {
            if (this.CanReach(arr, start - jump))
            {
                return true;
            }
        }
        return false;
    }

Complexity: O(n)

[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)

Thursday, December 22, 2022

[LeetCode] Combination Sum II

Problem: Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sum to target.

Each number in candidates may only be used once in the combination.

Note: The solution set must not contain duplicate combinations.

Example:

Input: candidates = [10,1,2,7,6,1,5], target = 8
Output: 
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
Input: candidates = [2,5,2,1,2], target = 5
Output: 
[
[1,2,2],
[5]
]


Approach: Its a typical backtracking problem except we need to take care of the duplicates too. We can take care of duplicates by sorting the array "candidates".


Implementation in C#:

    public IList<IList<int>> CombinationSum2(int[] candidates, int target)
    {
        List<IList<int>> result = new List<IList<int>>();
        Array.Sort(candidates);
        List<int> currCandidates = new List<int>();
        this.CombinationSum2Rec(candidates,
                            0,
                            target,
                            0,
                            currCandidates,
                            result);
        return result;
    }

    private void CombinationSum2Rec(int[] candidates,
                                int currIndex,
                                int target,
                                int currSum,
                                List<int> currCandidates,
                                List<IList<int>> result)
    {
        if (currSum == target)
        {
            result.Add(new List<int>(currCandidates));
            return;
        }
        for (int i = currIndex; i < candidates.Length; ++i)
        {
            if (currSum + candidates[i] > target)
            {
                break;
            }
            if (i > currIndex && candidates[i] ==  candidates[i - 1])
            {
                continue;
            }
            currCandidates.Add(candidates[i]);
            this.CombinationSum2Rec(candidates,
                                i + 1,
                                target,
                                currSum + candidates[i],
                                currCandidates,
                                result);
            currCandidates.RemoveAt(currCandidates.Count - 1);
        }
    }

Complexity: O(2^n)

Wednesday, December 21, 2022

[LeetCode] Possible Bipartition

Problem: We want to split a group of n people (labeled from 1 to n) into two groups of any size. Each person may dislike some other people, and they should not go into the same group.

Given the integer n and the array dislikes where dislikes[i] = [ai, bi] indicates that the person labeled ai does not like the person labeled bi, return true if it is possible to split everyone into two groups in this way.

Example:

Input: n = 4, dislikes = [[1,2],[1,3],[2,4]]
Output: true
Explanation: group1 [1,4] and group2 [2,3].
Input: n = 3, dislikes = [[1,2],[1,3],[2,3]]
Output: false
Input: n = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]]
Output: false


Approach: This is clearly a bipartite graph problem. We can use coloring approach to solve it.


Implementation in C#:

    public bool PossibleBipartition(int n, int[][] dislikes)
    {
        var dislikeGraph = new Dictionary<int, List<int>>();
        foreach(int[] dislike in dislikes)
        {
            if (!dislikeGraph.ContainsKey(dislike[0]))
            {
                dislikeGraph[dislike[0]] = new List<int>();
            }
            if (!dislikeGraph.ContainsKey(dislike[1]))
            {
                dislikeGraph[dislike[1]] = new List<int>();
            }
            dislikeGraph[dislike[0]].Add(dislike[1]);
            dislikeGraph[dislike[1]].Add(dislike[0]);
        }
        int[] color = new int[n + 1];
        for (int i = 1; i <= n; ++i)
        {
            if (color[i] == 0)
            {
                if (!this.DFS(i, 1, dislikeGraph, color))
                {
                    return false;
                }
            }
        }
        return true;
    }

    private bool DFS(int node,
                int currColor,
                Dictionary<int, List<int>> dislikeGraph,
                int[] color)
    {
        color[node] = currColor;
        if (dislikeGraph.ContainsKey(node))
        {
            foreach(int neighbour in dislikeGraph[node])
            {
                if (color[neighbour] == color[node])
                {
                    return false;
                }
                if (color[neighbour] == 0)
                {
                    DFS(neighbour, -color[node], dislikeGraph, color);
                }
            }
        }
        return true;
    }

Complexity: O(n)

Monday, December 19, 2022

[LeetCode] Max Chunks To Make Sorted II

Problem: You are given an integer array arr.

We split arr into some number of chunks (i.e., partitions), and individually sort each chunk. After concatenating them, the result should equal the sorted array.

Return the largest number of chunks we can make to sort the array.

Example:

Input: arr = [5,4,3,2,1]
Output: 1
Explanation:
Splitting into two or more chunks will not return the required result.
For example, splitting into [5, 4], [3, 2, 1] will result in [4, 5, 1, 2, 3], which isn't sorted.
Input: arr = [2,1,3,4,4]
Output: 4
Explanation:
We can split into two chunks, such as [2, 1], [3, 4, 4].
However, splitting into [2, 1], [3], [4], [4] is the highest number of chunks possible.


Approach: Here we can use chaining technique. We can have one array which stores the max from left and one array which stores the min from the right. At any index 'i' if we find that 

leftMax[i] <= rightMin[i + 1] 

then we can safely consider it as a chunk. Right? Confusing, isn't it? Let's taken an example and see. 

Let's take an array arr =  [30, 20, 10, 40, 60, 50, 70]. It's leftMax and rightMin will be:

lmax = [30, 30, 30, 40, 60, 60, 70]

rmin = [10 ,10, 10, ,40, 50, 50, 70]

if lmax[i] is greater than rightMin[i + 1] then it means they can be in the same chunk because that means till now the array has the right elements till index 'i'.


Implementation in C#:

    public int MaxChunksToSorted(int[] arr)
    {
        int length = arr?.Length ?? 0;
        if (length <= 1)
        {
            return length;
        }
        int[] rightMin = new int[length];
        rightMin[length - 1] = arr[length - 1];
        for (int i = length - 2; i >= 0; --i)
        {
            rightMin[i] = Math.Min(rightMin[i + 1], arr[i]);
        }
        int chunksRequired = 1;
        int lmax = -1;
        for (int i = 0; i < length - 1; ++i)
        {
            lmax = Math.Max(lmax, arr[i]);
            if (lmax <= rightMin[i + 1])
            {
                ++chunksRequired;
            }
        }
        return chunksRequired;
    }

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)