Tutorial on Precomputation techniques in Strings
Last Updated :
29 Dec, 2023
Precomputation in strings refers to the process of performing calculations on a given string before actual operations or queries are performed.
The goal of precomputation is to process and store certain information about the string that can be used to optimize and speed up various string-related operations or queries that might be performed later. By pre-computing certain information of the string, redundant calculations can be avoided, and faster runtime for specific tasks can be achieved. Now let's consider the following problem for better understanding.
Problem Statement:
Given a string s
of length n
and a set of q
queries, each query defined by a triplet (l, r, c)
where: For each query, you need to calculate and output the number of occurrences of the character c
within the substring s[l...r]
.
Naïve Approach:
The idea is to iterate from index l to r and count the frequency of character c for each query separately.
For each query, this approach requires iterating through the substring characters and checking if each character is equal to the given character c
. The time complexity of this approach for a single query is O(n). Since there are q
queries, the overall time complexity is O(q * n).
The naïve approach is simple to implement, but it might be inefficient for large values of q
and m
since it involves repetitive calculations for each query.
Approach (Using PreComputation):
To efficiently solve this problem, precomputation technique can be used involving the use of a 2D array that stores prefix sums of each character occurrences. Then, for each query, count of the desired character within the specified substring can be computed in O(1) time using the prefix sum.
Steps to implement the above idea:
- Initialize a 2D array
prefixSum
of size n x 26
, where n
is the length of the input string s
, and 26
represents the number of English alphabet characters ('a' to 'z'). - prefixSum[i][ch] denotes the number of times character ch occurs till i.
- For each index
i
from 0 to n - 1
:- For each character
ch
from 'a' to 'z', calculate prefixSum[i][ch - 'a']
by adding prefixSum[i - 1][ch - 'a']
to the count of character ch
at index i
in string s
.
- For each query
(l, r, c)
:- The count of character
c
is prefixSum[r][c 1="a" language="-"][/c] - prefixSum[l - 1][c 1="a" language="-"][/c]
.
Below is the implementation of above approach:
C++
// C++ code
#include <bits/stdc++.h>
using namespace std;
vector<int> preComputation(string s,
vector<vector<int> > queries,
vector<char> character)
{
int n = s.size();
// Create a 2D vector to store prefix sum of
// character occurrences
vector<vector<int> > psum(n + 1, vector<int>(26, 0));
// Calculate prefix sums
for (int i = 1; i <= n; i++) {
for (int j = 0; j < 26; j++) {
// Update prefix sum
psum[i][j] += psum[i - 1][j];
}
// Increment the count of current character
psum[i][s[i - 1] - 97]++;
}
int m = queries.size();
vector<int> ans(m);
// Calculate answers for each query
for (int i = 0; i < m; i++) {
// Calculate the count of character within the
// specified range
ans[i]
= psum[queries[i][1]][character[i] - 97]
- psum[queries[i][0] - 1][character[i] - 97];
}
return ans;
}
// Driver Code
int main()
{
string s = "baabcaba";
vector<vector<int> > queries{
{ 2, 6 }, { 4, 5 }, { 1, 6 }, { 3, 6 }
};
vector<char> character{ 'a', 'a', 'b', 'c' };
// ans[i] stores answer to the ith query
vector<int> ans = preComputation(s, queries, character);
// Output the results
for (int i = 0; i < ans.size(); i++) {
cout << "Frequency of character " << character[i]
<< " in the range " << queries[i][0] << " to "
<< queries[i][1] << " :" << ans[i] << "\n";
}
}
Java
public class PrefixSum {
static int[] preComputation(String s, int[][] queries, char[] characters) {
int n = s.length();
// Create a 2D array to store prefix sum of character occurrences
int[][] psum = new int[n + 1][26];
// Calculate prefix sums
for (int i = 1; i <= n; i++) {
for (int j = 0; j < 26; j++) {
// Update prefix sum
psum[i][j] = psum[i - 1][j];
}
// Increment the count of current character
psum[i][s.charAt(i - 1) - 'a']++;
}
int m = queries.length;
int[] ans = new int[m];
// Calculate answers for each query
for (int i = 0; i < m; i++) {
// Calculate the count of character within the specified range
ans[i] = psum[queries[i][1]][characters[i] - 'a'] - psum[queries[i][0] - 1][characters[i] - 'a'];
}
return ans;
}
public static void main(String[] args) {
String s = "baabcaba";
int[][] queries = {{2, 6}, {4, 5}, {1, 6}, {3, 6}};
char[] characters = {'a', 'a', 'b', 'c'};
// ans[i] stores the answer to the ith query
int[] ans = preComputation(s, queries, characters);
// Output the results
for (int i = 0; i < ans.length; i++) {
System.out.println("Frequency of character " + characters[i] + " in the range "
+ queries[i][0] + " to " + queries[i][1] + ": " + ans[i]);
}
}
}
Python3
def pre_computation(s, queries, characters):
n = len(s)
# Create a 2D list to store prefix sum of character occurrences
psum = [[0] * 26 for _ in range(n + 1)]
# Calculate prefix sums
for i in range(1, n + 1):
for j in range(26):
# Update prefix sum
psum[i][j] = psum[i - 1][j]
# Increment the count of current character
psum[i][ord(s[i - 1]) - ord('a')] += 1
m = len(queries)
ans = [0] * m
# Calculate answers for each query
for i in range(m):
# Calculate the count of character within the specified range
ans[i] = psum[queries[i][1]][ord(characters[i]) - ord('a')] - psum[queries[i][0] - 1][ord(characters[i]) - ord('a')]
return ans
# Driver Code
if __name__ == "__main__":
s = "baabcaba"
queries = [[2, 6], [4, 5], [1, 6], [3, 6]]
characters = ['a', 'a', 'b', 'c']
# ans[i] stores the answer to the ith query
ans = pre_computation(s, queries, characters)
# Output the results
for i in range(len(ans)):
print(f"Frequency of character {characters[i]} in the range {queries[i][0]} to {queries[i][1]}: {ans[i]}")
C#
using System;
using System.Collections.Generic;
class GFG
{
static List<int> PreComputation(string s, List<List<int>> queries, List<char> character)
{
int n = s.Length;
// Create a 2D list to store prefix sum of character occurrences
List<List<int>> psum = new List<List<int>>(n + 1);
for (int i = 0; i <= n; i++)
{
psum.Add(new List<int>(26));
for (int j = 0; j < 26; j++)
{
// Update prefix sum
psum[i].Add(0);
}
}
// Calculate prefix sums
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < 26; j++)
{
// Update prefix sum
psum[i][j] += psum[i - 1][j];
}
// Increment the count of current character
psum[i][s[i - 1] - 'a']++;
}
int m = queries.Count;
List<int> ans = new List<int>(m);
// Calculate answers for each query
for (int i = 0; i < m; i++)
{
// Calculate the count of character within the specified range
ans.Add(psum[queries[i][1]][character[i] - 'a'] - psum[queries[i][0] - 1][character[i] - 'a']);
}
return ans;
}
// Driver Code
static void Main()
{
string s = "baabcaba";
List<List<int>> queries = new List<List<int>>
{
new List<int> {2, 6},
new List<int> {4, 5},
new List<int> {1, 6},
new List<int> {3, 6}
};
List<char> character = new List<char> { 'a', 'a', 'b', 'c' };
// ans[i] stores answer to the ith query
List<int> ans = PreComputation(s, queries, character);
// Output the results
for (int i = 0; i < ans.Count; i++)
{
Console.WriteLine($"Frequency of character {character[i]} in the range {queries[i][0]} to {queries[i][1]}: {ans[i]}");
}
}
}
JavaScript
// javaScript code for the above approach
function preComputation(s, queries, character) {
const n = s.length;
// Create a 2D array to store prefix
// sum of character occurrences
const psum = new Array(n + 1).fill(0).map(() => new Array(26).fill(0));
// Calculate prefix sums
for (let i = 1; i <= n; i++) {
for (let j = 0; j < 26; j++) {
// Update prefix sum
psum[i][j] += psum[i - 1][j];
}
// Increment the count of current
// character
const charIndex = s.charCodeAt(i - 1) - 97;
psum[i][charIndex]++;
}
const m = queries.length;
const ans = new Array(m);
// Calculate answers for each query
for (let i = 0; i < m; i++) {
// Calculate the count of character
// within the specified range
const charIndex = character[i].charCodeAt(0) - 97;
ans[i] =
psum[queries[i][1]][charIndex] -
psum[queries[i][0] - 1][charIndex];
}
return ans;
}
// Driver Code
const s = "baabcaba";
const queries = [
[2, 6],
[4, 5],
[1, 6],
[3, 6]
];
const character = ['a', 'a', 'b', 'c'];
// ans[i] stores answer to the ith query
const ans = preComputation(s, queries, character);
// Output the results
for (let i = 0; i < ans.length; i++) {
console.log(
`Frequency of character ${character[i]} in the range ${
queries[i][0]
} to ${queries[i][1]} :${ans[i]}`
);
}
Time Complexity: O(n+m), where n is length of string and m is number of queries.
Auxiliary Space: O(n)
Similar Reads
String Manipulation Techniques
Recent articles on String ! Word Wrap problem ( Space optimized solution ) Form minimum number from given sequence Maximum number of characters between any two same character in a string Print shortest path to print a string on screen Minimum number of stops from given path Check whether second stri
6 min read
Precomputation Techniques for Competitive Programming
What is the Pre-Computation Technique?Precomputation refers to the process of pre-calculating and storing the results of certain computations or data structures in advance, in order to speed up the execution time of a program. This can be useful in situations where the same calculations or data stru
15+ min read
Introduction to Strings - Data Structure and Algorithm Tutorials
Strings are sequences of characters. The differences between a character array and a string are, a string is terminated with a special character â\0â and strings are typically immutable in most of the programming languages like Java, Python and JavaScript. Below are some examples of strings:"geeks"
7 min read
Top Problems on Two Pointers Technique for Interviews
The Two Pointer Approach is a powerful and efficient technique used to solve problems involving sorted arrays, searching, and optimization. By utilizing two pointers either moving towards each other or in the same direction, we can reduce the time complexity of many problems from O(n2) to O(n) or O(
1 min read
Introduction to Pattern Searching - Data Structure and Algorithm Tutorial
Pattern searching is an algorithm that involves searching for patterns such as strings, words, images, etc. We use certain algorithms to do the search process. The complexity of pattern searching varies from algorithm to algorithm. They are very useful when performing a search in a database. The Pat
15+ min read
Find the minimum operations required to type the given String
Geek is extremely punctual but today even he is not feeling like doing his homework assignment. He must start doing it immediately in order to meet the deadline. For the assignment, Geek needs to type a string s.To reduce his workload, he has decided to perform one of the following two operations ti
4 min read
Practice questions on Strings
String is an important topic from GATE exam point of view. We will discuss key points on strings as well different types of questions based on that. There are two ways to store strings as character array (char p[20]) or a pointer pointing to a string (char* s = âstringâ), both of which can be access
4 min read
String from prefix and suffix of given two strings
Given two strings a and b, form a new string of length l, from these strings by combining the prefix of string a and suffix of string b. Examples : Input : string a = remuneration string b = acquiesce length of pre/suffix(l) = 5 Output :remuniesce Input : adulation obstreperous 6 Output :adulatperou
4 min read
Print the longest common substring
Given two strings âXâ and âYâ, print the length of the longest common substring. If two or more substrings have the same value for the longest common substring, then print any one of them. Examples: Input : X = "GeeksforGeeks", Y = "GeeksQuiz" Output : Geeks Input : X = "zxabcdezy", Y = "yzabcdezx"
15+ min read
Commonly Asked Data Structure Interview Questions on Strings
Strings are essential data structures used to represent sequences of characters and are frequently encountered in coding interviews. Questions often focus on string manipulation techniques such as searching, concatenation, reversal, and substring extraction. Understanding key algorithms like pattern
4 min read