Skip to content
Related Articles

Related Articles

Check if subsequences formed by given characters are same for Q queries

View Discussion
Improve Article
Save Article
  • Difficulty Level : Hard
  • Last Updated : 13 Jul, 2022

Given an array arr[] of N strings, and Q queries where each of the queries contains some characters, the task is to check if for each query the subsequences from all the strings only made up of all the occurrences of the characters of that query are the same or not. 

A subsequence is a sequence that can be derived from the given sequence by deleting some or no elements without changing the order of the remaining elements. 

Examples:

Input: arr[] = {“accbad”, “abcacd”, “cacbda”}, queries[] = {{‘a’}, {‘a’, ‘b’}, {‘a’, ‘d’}, {‘b’, ‘c’, ‘d’}, {‘a’, ‘b’, ‘d’}
Output: 
True
True
False
True
False
False
Explanation: All strings generate the subsequence “aa” using only the character ‘a’.
All strings generate subsequence “aba” using only the characters ‘a’ and ‘b’.
arr[1] and arr[2] generate subsequence “aad”, but arr[3] generates subsequence “ada” using only the characters ‘a’ and ‘d’, and since subsequences don’t match, false is printed.
All strings generate the subsequence “bd” using only the characters ‘b’ and ‘d’
arr[1] and arr[3] generate subsequence “ccbd”, but arr[2] generates subsequence “bccd” using only the characters ‘b’, ‘c’, and ‘d’, so print ‘False’.
arr[1] and arr[2] generate subsequence “abad”, but arr[3] generates subsequence “abda” using only the characters ‘a’, ‘b’, and ‘d’

Input: arr[] = {“adacb”, “aacbd”}, queries[] = {{‘a’, ‘b’, ‘c’}}
Output: True.
Explanation: The subsequences are ‘aacb’ for both the strings

 

Naive Approach: For each query generate the subsequences containing all the occurrences of all the characters of that query and check if they are the same or not.

Time complexity: O(Q * N * M), where M is the average length of each string.
Auxiliary Space: O(1)

Efficient Approach using Memoization: This problem can be solved efficiently based on the following idea:

If the relative positions of all the characters for a query are the same in all the strings then the subsequence is common in all of them.

The relative positions of any character (say character ch), can be find out by finding the frequencies of all the characters of a query upto the index of ch. If they are the same then their relative position are also same.

Follow the steps mentioned below to implement the idea:

  • Create a 3-dimensional (say elements[])array to store the frequency of each character of the strings upto each index.
  • Traverse all the strings and find the frequency and store them
  • Now check for the relative positions of the characters of a query.
  • To implement this efficiently, iterate through the array of strings, compare the adjacent strings and do the following:
    • Find the relative position of each character with respect to every other character in the English alphabet (relative position is checked by checking the frequencies of all other characters till that index).
    • If they are not the same for a character (say ch) in two strings, add the characters which are causing the mismatch in a set. (mismatch means frequency counts are not the same)
    • Check if the mismatches found are the same as the characters of the query.
    • If so then the subsequences formed are not the same. So return false.
  • After the iteration is over and a “false” is not returned, then subsequences formed are the same.

Follow the illustration below for a better understanding.

Illustration:

Say arr[] = {“adacb”, “aacbd”}, queries[] = {{‘a’, ‘b’, ‘c’}}

The frequencies of all the characters for arr[0] and arr[1] are shown below

arr[0] = 

 

‘a’

‘d’ ‘a’ ‘c’ ‘b’
a 1 1 2 2 2
b 0 0 0 0 1
c 0 0 0 1 1
d 0 1 1 1 1

arr[1] = 

 

‘a’

‘a’ ‘c’ ‘b’ ‘d’
a 1 2 2 2 2
b 0 0 0 1 1
c 0 0 1 1 1
d 0 0 0 0 1

For ‘a’:
        => The relative positions of the second ‘a’ are not same.
        => In the first string there is an extra ‘d’ before the 2nd ‘a’.
        => So the mismatch set is { ‘d’ }.

For ‘b’:
        => The relative positions of ‘b’ are not same.
        => In the first string there is an extra ‘d’ before it.
        => So the mismatch set is { ‘d’ }.

For ‘c’:
        => The relative positions of ‘c’ are not same.
        => In the first string there is an extra ‘d’ before it.
        => So the mismatch set is { ‘d’ }.

Now no mismatch set contains the same character as the ones present in the query. So the subsequence formed is the same for the strings.

Below is the implementation of the above approach.

Java




// Java code to implement the approach
 
import java.io.*;
import java.util.*;
 
public class SubsequenceQueries {
 
    // Function to check if
    // the subsequences formed are the same
    public static ArrayList<Boolean>
    isPossible(int N, ArrayList<String> arr, int Q,
               String queries[])
    {
        // For 26 letters
        final int numElements = 26;
 
        // Generate prefix sums (for each letter)
        // for each string
        ArrayList<int[][]> elements
            = new ArrayList<>();
        for (int i = 0; i < N; i++) {
 
            // Create a new prefix sum array
            // and add it to the array list
            elements.add(new int[arr.get(i).length()]
                                [numElements]);
            int[][] tmp = elements.get(i);
 
            // Build the prefix sum
            // at each position in the string
            for (int j = 0; j < arr.get(i).length();
                 j++) {
                for (int k = 0; k < numElements;
                     k++) {
                    if (j != 0)
                        tmp[j][k] = tmp[j - 1][k];
                }
 
                // ASCII to int conversion
                // for lowercase letters
                tmp[j][arr.get(i).charAt(j) - 97]++;
            }
        }
 
        // Generate the set of characters
        // which are necessary to remove.
        // Each mapping is the set
        // corresponding of characters
        // which need to be removed.
        // for each letter in order for a
        // subsequence to be generated.
        HashMap<Integer, Set<Integer> > requiredRemovals
            = new HashMap<>();
        for (int i = 0; i < numElements; i++)
            requiredRemovals.put(i, new HashSet<Integer>());
 
        // Iterate over all the characters
        // (in the alphabet in this case)
        for (int i = 0; i < numElements; i++) {
 
            // For each character,
            // go through all M strings
            // to generate prefix sums
            for (int j = 1; j < N; j++) {
 
                // String a stores
                // the previous string (j-1)
                // string b stores
                // the current string (j)
                String a = arr.get(j - 1);
                String b = arr.get(j);
 
                // Store the prefix sums
                // for strings a and b
                int[][] elements1
                    = elements.get(j - 1);
                int[][] elements2
                    = elements.get(j);
 
                int p1 = 0;
                int p2 = 0;
 
                // Check if the lengths of characters
                // differ; if they do, then
                // no valid subsequence
                // with that character can be generated.
                // So for all other letters,
                // add that character to its Set.
                // Otherwise, check the count
                // of each character
                // at every position where
                // letter i appears in both strings
                if (elements1[a.length() - 1][i]
                    != elements2[b.length() - 1][i]) {
                    for (int key :
                         requiredRemovals.keySet())
                        requiredRemovals.get(key).add(i);
                }
                else {
                    // Iterate through both strings
                    // using p1 for all characters
                    // in the first string
                    // and p2 for all characters
                    // in the second string
                    while (p1 < a.length()
                           && p2 < b.length()) {
 
                        // Skip to the next occurrence of
                        // character i in string a
                        while (p1 < a.length()
                               && a.charAt(p1)
                                          - 97
                                      != i) {
                            p1++;
                        }
 
                        // Skip to the next occurrence of
                        // character i in string b
                        while (p2 < b.length()
                               && b.charAt(p2)
                                          - 97
                                      != i) {
                            p2++;
                        }
 
                        // Compare the count of each
                        // character to check if they match
                        // in both strings
                        if (p1 < a.length()
                            && p2 < b.length()) {
 
                            // Iterate over
                            // their prefix sums
                            for (int k = 0;
                                 k < numElements;
                                 k++) {
                                if (elements1[p1][k]
                                    != elements2[p2][k])
                                    requiredRemovals.get(i)
                                        .add(k);
                            }
                        }
                        p1++;
                        p2++;
                    }
                }
            }
        }
 
        ArrayList<Boolean> res
            = new ArrayList<Boolean>();
 
        // Read in Q queries
        for (int i = 0; i < Q; i++) {
 
            // st = new StringTokenizer(br.readLine());
            // String q = st.nextToken();
            Set<Integer> union
                = new HashSet<Integer>();
 
            // generate a combined set of all characters
            // which must be removed for a valid subsequence
            // to be created with the query string
            for (char c : queries[i].toCharArray())
                union.addAll(requiredRemovals.get(c - 97));
            boolean ans = true;
 
            // if there are any contradictions in the query,
            // then the answer will be false
            for (char c : queries[i].toCharArray()) {
                if (union.contains(c - 97))
                    ans = false;
            }
 
            res.add(ans);
        }
        return res;
    }
 
    // Driver code
    public static void main(String[] args)
        throws IOException
    {
        int N = 3;
        ArrayList<String> arr
            = new ArrayList<String>();
        arr.add("accbad");
        arr.add("abcacd");
        arr.add("cacbda");
        int Q = 6;
        String queries[]
            = new String[6];
        queries[0] = "a";
        queries[1] = "ab";
        queries[2] = "ad";
        queries[3] = "bd";
        queries[4] = "bcd";
        queries[5] = "abd";
 
        // Function call
        ArrayList<Boolean> ans
            = isPossible(N, arr, Q, queries);
        for (boolean val : ans)
            System.out.println(val);
    }
}


C#




// C# program to implement above approach
using System;
using System.Collections.Generic;
 
class GFG
{
 
    // For 26 letters
    readonly public static int numElements = 26;
 
    // Function to check if
    // the subsequences formed are the same
    public static List<bool> isPossible(int N, List<String> arr, int Q, String[] queries)
    {
 
        // Generate prefix sums (for each letter)
        // for each string
        List<int[, ]> elements = new List<int[, ]>();
        for (int i = 0 ; i < N ; i++) {
 
            // Create a new prefix sum array
            // and add it to the array list
            elements.Add(new int[arr[i].Length, numElements]);
 
            // Build the prefix sum
            // at each position in the string
            for (int j = 0 ; j < arr[i].Length ; j++) {
                for (int k = 0 ; k < numElements ; k++) {
                    if(j!=0){
                        elements[i][j, k] = elements[i][j - 1, k];
                    }
                }
 
                // ASCII to int conversion
                // for lowercase letters
                elements[i][j, ((int)arr[i][j] - 97)]++;
            }
        }
 
        // Generate the set of characters
        // which are necessary to remove.
        // Each mapping is the set
        // corresponding of characters
        // which need to be removed.
        // for each letter in order for a
        // subsequence to be generated.
        SortedDictionary<int, SortedSet<int>> requiredRemovals = new SortedDictionary<int, SortedSet<int>>();
        for (int i = 0 ; i < numElements ; i++){
            requiredRemovals.Add(i, new SortedSet<int>());
        }
 
        // Iterate over all the characters
        // (in the alphabet in this case)
        for (int i = 0 ; i < numElements ; i++) {
 
            // For each character,
            // go through all M strings
            // to generate prefix sums
            for (int j = 1 ; j < N ; j++) {
 
                // String a stores
                // the previous string (j-1)
                // string b stores
                // the current string (j)
                String a = arr[j - 1];
                String b = arr[j];
 
                // Store the prefix sums
                // for strings a and b
                int[, ] elements1 = elements[j - 1];
                int[, ] elements2 = elements[j];
 
                int p1 = 0;
                int p2 = 0;
 
                // Check if the lengths of characters
                // differ; if they do, then
                // no valid subsequence
                // with that character can be generated.
                // So for all other letters,
                // add that character to its Set.
                // Otherwise, check the count
                // of each character
                // at every position where
                // letter i appears in both strings
                if (elements1[a.Length - 1, i] != elements2[b.Length - 1, i]) {
                    foreach( KeyValuePair<int, SortedSet<int>> pr in requiredRemovals){
                        requiredRemovals[pr.Key].Add(i);
                    }
                }
                else {
                    // Iterate through both strings
                    // using p1 for all characters
                    // in the first string
                    // and p2 for all characters
                    // in the second string
                    while (p1 < a.Length && p2 < b.Length) {
 
                        // Skip to the next occurrence of
                        // character i in string a
                        while (p1 < a.Length && ((int)a[p1] - 97) != i) {
                            p1++;
                        }
 
                        // Skip to the next occurrence of
                        // character i in string b
                        while (p2 < b.Length && ((int)b[p2] - 97) != i) {
                            p2++;
                        }
 
                        // Compare the count of each
                        // character to check if they match
                        // in both strings
                        if (p1 < a.Length && p2 < b.Length) {
 
                            // Iterate over
                            // their prefix sums
                            for (int k = 0 ; k < numElements; k++) {
                                if (elements1[p1, k] != elements2[p2, k]){
                                    requiredRemovals[i].Add(k);
                                }
                            }
                        }
                        p1++;
                        p2++;
                    }
                }
            }
        }
 
        List<bool> res = new List<bool>();
 
        // Read in Q queries
        for (int i = 0; i < Q; i++) {
 
            // st = new StringTokenizer(br.readLine());
            // String q = st.nextToken();
            SortedSet<int> union = new SortedSet<int>();
 
            // generate a combined set of all characters
            // which must be removed for a valid subsequence
            // to be created with the query string
            foreach (char c in queries[i].ToCharArray()){
                foreach(int x in requiredRemovals[((int)c - 97)]){
                    union.Add(x);
                }
            }
            bool ans = true;
 
            // if there are any contradictions in the query,
            // then the answer will be false
            foreach (char c in queries[i].ToCharArray()) {
                if (union.Contains((int)c - 97)){
                    ans = false;
                }
            }
 
            res.Add(ans);
        }
        return res;
    }
 
 
    // Driver Code
    public static void Main(string[] args){
         
        int N = 3;
        List<String> arr = new List<String>();
        arr.Add("accbad");
        arr.Add("abcacd");
        arr.Add("cacbda");
 
        int Q = 6;
        String[] queries = new String[6];
        queries[0] = "a";
        queries[1] = "ab";
        queries[2] = "ad";
        queries[3] = "bd";
        queries[4] = "bcd";
        queries[5] = "abd";
 
        // Function call
        List<bool> ans = isPossible(N, arr, Q, queries);
        foreach(bool val in ans){
            Console.Write(val + "\n");
        }
 
    }
}
 
// This code is contributed by subhamgoyal2014.


Output

true
true
false
true
false
false

Time Complexity: O(C2 * N + Q * N)
Auxiliary Space: O(C2 * N) where C = 26


My Personal Notes arrow_drop_up
Recommended Articles
Page :

Start Your Coding Journey Now!