Count of distinct possible strings after performing given operations


Determining the number of unique strings that can be­ obtained by performing a set of give­n operations on a string is a common challenge in compute­r science and mathematics. Se­veral operations, including character de­letion, swapping, or string reversal, can be­ carried out on the string. The obje­ctive is to calculate the total count of diffe­rent output strings achievable through the­se operations irrespe­ctive of their order. The­ problem-solving techniques applie­d for this task comprise dynamic programming, recursion, and combinatorics among others—de­pending upon the nature of spe­cific operations undertaken.

Methods

To count the distinct possible strings after performing given operations, one can use the following methods −

  • Brute force approach.

  • Set approach.

  • Dynamic programming.

  • Combinatorial approach.

Method 1: Brute force approach

This function allows you to create any string that could be produced by carrying out the specified procedures. Then, total the obtained distinct string count. For large inputs, this approach may be time-consuming and ineffective.

Syntax

The following can be followed for this method −

string_count = 0
for operation_combination in all_possible_combinations(operations_list):
   new_string = apply_operations(original_string, operation_combination)
   if is_distinct(new_string):
   string_count += 1
return string_count

Algorithm

Step 1 − Create a set from scratch to hold the distinct strings.

Step 2 − Produce each conceivable string that can be produced by the provided processes.

Step 3 − Verify that each created string already exists in the collection of distinctive strings.

Step 4 − If the string is not already in the set, so we need to add it in the set.

S

tep 5 − Continue doing steps 2-4 until every conceivable string has been created and verified.

Step 6 − Return the number of distinct possible strings as the length of the collection of unique strings.

Example 1

We have a C++ example of a brute force method −

In this example, we will start by taking the input string s as the beginning or starting value of a vector. The provided operations are carried out by changing the characters in each string of the vector. We repeat this procedure k times, storing each string that is produced in the vector. Finally, we use the unique function to sort the vector and determine how many distinct strings there are. The response is returned as the final count.

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int countDistinctStrings(string s, int n, int k) {
   vector<string> v;
   v.push_back(s);
   for(int i=0; i<k; i++) {
      int len = v.size();
      for(int j=0; j<len; j++) {
        string temp = v[j];
         for(int x=0; x<n; x++) {
            for(int y=x+1; y<n; y++) {
               if(temp[x] != temp[y]) {
                  swap(temp[x], temp[y]);
                  v.push_back(temp);
                  swap(temp[x], temp[y]);
              }
            }
         }
      }
   }
   sort(v.begin(), v.end());
   int cnt = unique(v.begin(), v.end()) - v.begin();
   return cnt;
} 
int main() {
   string s = "abb";
   int n = s.length();
   int k = 2;
   int ans = countDistinctStrings(s, n, k);
   cout << "Distinct strings after " << k << " operations: " << ans << endl;
   return 0;
}

Output

Distinct strings after 2 operations: 3

Method 2: Set approach

In this method, all the different strings obtained by carrying out the specified procedures can be stored in a set. One can add string to the set as they generate it if it is not already there. One can count the size of the set to determine the number of distinct strings after creating every possible string.

Syntax

In this approach to count the distinct possible strings after performing given operations, the syntax typically involves the following steps −

  • Initialize an empty set to store the distinct strings.

distinct_strings = set ()
  • Generate all possible strings by applying the given operations to the original string.

def generate_strings(original_string, operations):
return new_strings
  • Add each generated string to the set to ensure uniqueness.

for string in generate_strings(original_string, operations):
   distinct_strings.add(string)
  • Finally, retrieve the count of distinct strings.

count = len(distinct_strings)

This set-based solution makes use of sets' special ability to automatically eliminate duplicate items, resulting in the storage of only different strings. After applying the specified procedures to the original string, you may effectively count the many possible strings by using this method.

Algorithm

Step 1 − Create a collection called "distinct_strings" from scratch to hold all attainable distinct strings.

Step 2 − Include the initial string S in the group.

Step 3 − For each of the M operations (L, R, X) in the list:

  • For every unique string in the collection−

    • Make a duplicate of the string.

    • In the copied string, change the characters at indices L to R (inclusive) to X.

    • Add the copied string to the collection.

Step 4 − Provide the "distinct_strings" set's size.

Example 2

To store all distinct substrings of length k, we establish an empty set of distinctStrings with this code. We create every feasible substring of length k by looping through the given string s. The set of different Strings is then updated with each substring. Size of set tells us how many distinct potential strings there are overall after all substrings have been inserted. This value is the function's output, which we return.

In the preceding example, the string s is "abbabc," and the goal is to determine how many unique strings of length k=2 are feasible. The programme returns a value of 3, indicating that there are 3 unique strings of length 2 that can be created by applying the specified procedures to the string s.

#include<bits/stdc++.h>
using namespace std;

// Function to count the distinct possible strings
int countDistinctStrings(int n, int k, string s) {
   set<string> distinctStrings; // Create an empty set to store distinct strings

   // Create all possible sub strings of length k and insert them into the set
   for (int i = 0; i <= n-k; i++) {
      string temp = s.substr(i, k);
      distinctStrings.insert(temp);
   }

   // Calculate the total number of distinct strings
   int ans = distinctStrings.size();

   return ans;
}

// Driver code
int main() {
   int n = 6, k = 2; // Given values of n and k
      string s = "abbabc"; // Given string

   // Call the countDistinctStrings function and print the result
   int distinctStrings = countDistinctStrings(n, k, s);
   cout << "The number of distinct possible strings is: " << distinctStrings << endl;

   return 0;
}

Output

The number of distinct possible strings is: 4

Method 3: Dynamic programming

This approach makes effective use of dynamic programming to count the number of unique strings. The number of distinct strings that can be acquired after a specific number of operations can be represented by a state that you can define. Recurrence relations is used to calculate the number of different strings for the subsequent state based on the preceding state. For large inputs, this method may be more effective.

Syntax

An example syntax for the dynamic programming approach in C++ −

int countDistinctStrings(int n, vector& operations) {
  • Initialize table or array

vector<int> dp(n + 1, 0);
  • Set base case

dp[0] = 1;
  • Iterate over subproblems

for (int i = 1; i <= n; i++) { 
  • Calculate value based on previous subproblems

for (int op : operations) {
   if (i >= op) {
      dp[i] += dp[i - op];
   }
}
}
  • Return final answer

   return dp[n];
}

Algorithm

Here is an algorithm using Dynamic Programming to count the distinct possible strings after performing given operations −

Step 1 − Initialise the 2D array DP[n][k], where n denotes the original string's length and k the number of permitted operations. The number of separate strings that can be created using the first i characters of the original string and j actions is represented by the value DP[i][j].

Step 2 − Because only one string may be created using zero characters and zero operations, set DP[0][0] to 1.

Step 3 − The program should se­t DP[i][j] to the cumulative sum of DP[x][j-1], where­ x represents the­ index of the last modified characte­r in operation j, ranging from 0 to i-1. If there we­re no preceding ope­rations, let x be zero.

Step 4 − To calculate DP[i][j], subtract the­ sum of all DP[x][j-1] from 0 to i-1, where x repre­sents the last replace­d character with the same one­ during the jth operation. If there­ were no prior operations, se­t x as 0.

Step 5 − Give back the value of DP[n][k], which denotes how many unique strings can be created by combining all n characters from the starting string with k operations.

Example 1

A C++ example of how to use dynamic programming to count the different potential strings following the specified operations −

The countDistinctStrings method in this illustration has four input parameters: n, k, x, and y. X and Y are modulo values, whereas n denotes the length of the strings and k the maximum number of consecutive 1s that can be included in the strings.

The function stores the quantity of different strings at each length i and the quantity of successive one’s j in a two-dimensional array called dp. The array is set to 0 using the memset method.

Then, updating the dp array based on the previous length i-1 and number of consecutive 1s j-1, the function runs over each length i and number of consecutive 1s j. The modulo y is used in place of the modulo x if the number of consecutive 1s is equal to k.

The function then outputs dp [n% 2][k], which is the number of unique strings that can have length n and at most k consecutive 1's.

The countDistinctStrings function is called in the main function with the specified values for n, k, x, and y. The console is then printed with the final count.

#include <iostream>
#include <cstring>
using namespace std;

int countDistinctStrings(int n, int k, int x, int y) {
   int dp[2][k + 1];
   memset(dp, 0, sizeof(dp));
   dp[0][0] = 1;

   for (int i = 1; i <= n; i++) {
      int cur = i % 2;
      int prev = (i - 1) % 2;

      for (int j = 0; j <= k; j++) {
         dp[cur][j] = dp[prev][j];

         if (j > 0) {
            dp[cur][j] += dp[prev][j - 1];
            dp[cur][j] %= x;
         }   

         if (j == k) {
            dp[cur][j] -= dp[prev][j - 1];
            dp[cur][j] %= y;
            dp[cur][j] += y;
            dp[cur][j] %= y;
         }
      }
   }
   return dp[n % 2][k];
}

int main() {
   int n = 4;
   int k = 2;
   int x = 1000000007;
   int y = 998244353;

   int count = countDistinctStrings(n, k, x, y);

   cout << "Count of distinct possible strings: " << count << endl;

   return 0;
}

Output

Count of distinct possible strings: 0

Method 4: Combinatorial approach

This approach enable­s direct counting of different strings using combinatorics. For e­xample, if 'a' or 'b' can be added to the­ end of the string, you can use the­ formula for 'a' and 'b' combinations to calculate the number of distinct strings produce­d. This strategy may prove helpful whe­n dealing with minimal input quantity and process count.

Syntax

Following the completion of the above operations, the combinatorial approach's syntax for counting distinct potential strings is as follows −

  • Name the group of operations −

operations are op1, op2,..., opk.
  • Specify the restrictions −

restrictions = "c1, c2,..., cn"
  • Determine how many distinct strings there are: −

count is equal to f(operations, constraints), 

where f is a combinatorial function that determines how many unique strings may be produced from the given set of operations and restrictions.

Depending on the operations and the situation, different implementations of the function f may be used. To enumerate the potential strings, it usually involves utilising combinatorial formulas like permutations, combinations, or generating functions.

Algorithm

Step 1 − Add the first string, "s," to the collection of strings S.

Step 2 − Perform the following action for each operation (li, ri) −

  • Create a new string u for each string t in S by flipping the characters in the substring s[li:ri].

  • Include the string "u" in the set "S."

Step 3 − Provide the set S's size.

Step 4 − Algorithm is completed.

This algorithm's time complexity is O (k * 2n), where n is the beginning string's length and k is the number of operations. This is due to the possibility that, in the worst scenario, there may be 2n different strings, each of which could be subjected to an operation to produce a total of k * 2n strings. However, there might be fewer distinct strings overall, and the process might be improved by not generating duplicate strings.

Example 4

An example­ showcases how to implement the­ combinatorial method using C++ to count possible strings after applying ope­rations.

In this scenario, the­re is a string of length n=5 that can produce unique­ strings using k=2 procedures. The ope­rations are represe­nted by a vector of intege­rs, where 1 means to put a characte­r between two adjace­nt ones and 0 means to insert a characte­r between two ze­ros. The number of resulting unique­ strings from these operations will be­ counted.

The input parameters for the count_possible_strings() function are the string length n, the number of operations k, and the vector of operations. The number of potential strings that can be formed after each operation is then determined using the binomial coefficient formula as the process loops through the procedures. After all actions have been completed, it adds the number of potential strings that can be generated and returns the overall count.

A he  lpful function called binomial_coefficient() uses the formula n! / (k! * (n-k)!) to calculate the binomial coefficient nCk. To prevent overflow, the numerator and denominator are computed separately using a loop.

We take input values to call the count_possible_strings() method in the main code, then return outcome.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// Helper function to calculate the binomial coefficient nCk
int binomial_coefficient(int n, int k) {
   if (k > n - k) k = n - k;
   int res = 1;
   for (int i = 0; i < k; i++) {
      res *= (n - i);
      res /= (i + 1);
   }
    return res;
}
// Function to count the distinct possible strings after performing given operations
int count_possible_strings(int n, int k, vector<int>& operations) {
   int num_ones = 1, num_zeros = 0, res = 0;
   for (int i = 0; i < operations.size(); i++) {
      if (operations[i] == 1) {
         res += binomial_coefficient(num_ones + num_zeros, num_ones);
         num_ones++;
      } else {
         num_zeros++;
      }
   }
   res += binomial_coefficient(num_ones + num_zeros, num_ones);
   return res;
}
// Driver code
int main() {
   int n = 5, k = 2;
   vector<int> operations = {1, 0, 1};
   int count = count_possible_strings(n, k, operations);
   cout << "Count of distinct possible strings = " << count << endl;
   return 0;
}

Output

Count of distinct possible strings = 8

Conclusion

In summary, dete­rmining the number of distinct potential strings re­sulting from a specific set of operations is a challe­nging problem with various solutions. Mathematical concepts such as pe­rmutations and combinations, alongside algorithmic techniques like­ dynamic programming, can be utilized to calculate the­ number of unique strings formed by the­ given operations quickly. Howeve­r, these algorithms' exe­cution time and memory require­ments may escalate e­xponentially with increasing problem size­ and added operations. Thus, it's crucial to consider e­ach problem instance in deciding which approach give­s more efficiency and accuracy while­ maintaining balance betwee­n both.

Updated on: 31-Jul-2023

327 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements