0% found this document useful (0 votes)
3 views

String Problems

Uploaded by

Ayush Anand
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

String Problems

Uploaded by

Ayush Anand
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 20

1.

Anagrams:
 Anagrams are words or phrases formed by rearranging the letters of
another word or phrase.
 Best Approach: Use a hash table (e.g., unordered_map in C++ or
defaultdict in Python) to store the frequency of characters in each
string. Compare the frequency of characters in both strings. If the
frequencies match for all characters, the strings are anagrams.
#include <iostream>
#include <unordered_map>
#include <string>

bool areAnagrams(const std::string& s1, const std::string& s2) {


if (s1.length() != s2.length()) {
return false; // Anagrams must have the same length
}

std::unordered_map<char, int> freqMap;

// Count frequency of characters in s1


for (char c : s1) {
freqMap[c]++;
}

// Decrement frequency of characters in s2


for (char c : s2) {
freqMap[c]--;
}

// Check if all frequencies are zero


for (const auto& pair : freqMap) {
if (pair.second != 0) {
return false; // Frequencies don't match, not anagrams
}
}

return true; // Frequencies match, strings are anagrams


}

int main() {
std::string s1 = "listen";
std::string s2 = "silent";

if (areAnagrams(s1, s2)) {
std::cout << "Strings are anagrams\n";
} else {
std::cout << "Strings are not anagrams\n";
}

return 0;
}

Explanation:

1. The function areAnagrams takes two strings s1 and s2 as input and returns
true if they are anagrams, false otherwise.
2. First, it checks if the lengths of the strings are equal. If they are not, then the
strings cannot be anagrams, so it returns false.
3. Then, it creates an unordered_map freqMap to store the frequency of
characters in s1.
4. It iterates through each character in s1 and increments the frequency of that
character in freqMap.
5. Next, it iterates through each character in s2 and decrements the frequency of
that character in freqMap.
6. Finally, it checks if all frequencies in freqMap are zero. If any frequency is non-
zero, it means the frequencies of characters in s1 and s2 don't match, so it
returns false. Otherwise, it returns true.

In the main function, we test the areAnagrams function with two example strings
"listen" and "silent".

2. Substring Search:
Given a string and a pattern, find all occurrences of the pattern in the

string.
 Best Approach: Use efficient string matching algorithms such as
Knuth-Morris-Pratt (KMP) algorithm, Boyer-Moore algorithm, or Rabin-
Karp algorithm. These algorithms can achieve linear or sublinear time
complexity for substring search.
#include <iostream>
#include <vector>
#include <string>

std::vector<int> computeLPS(const std::string& pattern) {


int m = pattern.length();
std::vector<int> lps(m, 0);
int len = 0;
int i = 1;

while (i < m) {
if (pattern[i] == pattern[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}

return lps;
}

std::vector<int> search(const std::string& text, const std::string& pattern) {


std::vector<int> positions;
int n = text.length();
int m = pattern.length();
std::vector<int> lps = computeLPS(pattern);

int i = 0; // index for text


int j = 0; // index for pattern

while (i < n) {
if (pattern[j] == text[i]) {
j++;
i++;
}

if (j == m) {
positions.push_back(i - j);
j = lps[j - 1];
} else if (i < n && pattern[j] != text[i]) {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}

return positions;
}
int main() {
std::string text = "ABABDABACDABABCABAB";
std::string pattern = "ABABCABAB";

std::vector<int> positions = search(text, pattern);

if (positions.empty()) {
std::cout << "Pattern not found in the text.\n";
} else {
std::cout << "Pattern found at positions:";
for (int pos : positions) {
std::cout << " " << pos;
}
std::cout << "\n";
}

return 0;
}

Explanation:

1. The function computeLPS computes the Longest Prefix Suffix (LPS) array for
the given pattern. This array stores the length of the longest proper prefix
which is also a suffix for each prefix of the pattern.
2. The function search uses the computed LPS array to perform substring search
in the text using the KMP algorithm.
3. In the main function, we test the search function with an example text and
pattern. The positions where the pattern is found in the text are printed.

This implementation of the KMP algorithm efficiently finds all occurrences of the
pattern in the given text.

3. Longest Common Subsequence (LCS):


1. Given two strings, find the longest subsequence present in both strings.
2. Best Approach: Use dynamic programming to solve this problem.
Build a 2D table to store the length of the LCS for each pair of
substrings. The dynamic programming approach has a time complexity
of O(m*n), where m and n are the lengths of the two input strings.

#include <iostream>
#include <vector>
#include <string>
std::string longestCommonSubsequence(const std::string& str1, const std::string& str2) {
int m = str1.length();
int n = str2.length();

// Create a 2D table to store lengths of LCS of substrings


std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1, 0));

// Fill the dp table bottom-up


for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (str1[i - 1] == str2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = std::max(dp[i - 1][j], dp[i][j - 1]);
}
}
}

// Construct the LCS from dp table


std::string lcs;
int i = m, j = n;
while (i > 0 && j > 0) {
if (str1[i - 1] == str2[j - 1]) {
lcs = str1[i - 1] + lcs;
i--;
j--;
} else if (dp[i - 1][j] > dp[i][j - 1]) {
i--;
} else {
j--;
}
}

return lcs;
}

int main() {
std::string str1 = "AGGTAB";
std::string str2 = "GXTXAYB";

std::string lcs = longestCommonSubsequence(str1, str2);

std::cout << "Longest Common Subsequence: " << lcs << std::endl;

return 0;
}
Explanation:

1. The function longestCommonSubsequence takes two strings str1 and str2 as


input and returns the longest common subsequence (LCS) of these strings.
2. It initializes a 2D vector dp to store the lengths of LCS of substrings. dp[i][j]
represents the length of the LCS of substrings str1.substr(0, i) and
str2.substr(0, j).
3. It fills the dp table bottom-up using dynamic programming. If the characters
at indices i-1 and j-1 in str1 and str2 respectively are equal, it increments
the LCS length by 1. Otherwise, it takes the maximum LCS length from the
previous cells.
4. After filling the dp table, it constructs the LCS by backtracking from the
bottom-right cell of the table.
5. In the main function, we test the longestCommonSubsequence function with
example strings "AGGTAB" and "GXTXAYB", and print the result.

4. Pattern Matching with Wildcard Characters:


1. Given a pattern with wildcard characters (?, *), match it with a given
string.
2. Best Approach: Use dynamic programming or backtracking with
memoization to match the pattern with the string. This problem can be
solved similarly to regular expression matching.

#include <iostream>
#include <string>
#include <vector>

bool isMatch(const std::string& s, const std::string& p) {


int m = s.length();
int n = p.length();

// Create a 2D table to store the matching status


std::vector<std::vector<bool>> dp(m + 1, std::vector<bool>(n + 1, false));

// Empty pattern matches empty string


dp[0][0] = true;

// Handling patterns starting with '*'


for (int j = 1; j <= n && p[j - 1] == '*'; ++j) {
dp[0][j] = true;
}
// Fill the dp table bottom-up
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else if (p[j - 1] == '*') {
dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
}
}
}

return dp[m][n];
}

int main() {
std::string s = "adceb";
std::string p = "*a*b";

if (isMatch(s, p)) {
std::cout << "Pattern matches the string.\n";
} else {
std::cout << "Pattern does not match the string.\n";
}

return 0;
}

Explanation:

1. The function isMatch takes two strings s and p as input and returns true if the
pattern matches the string, otherwise returns false.
2. It initializes a 2D vector dp to store the matching status. dp[i][j] represents
whether the pattern p.substr(0, j) matches the string s.substr(0, i).
3. It sets dp[0][0] = true because an empty pattern matches an empty string.
4. It handles patterns starting with '*' by setting dp[0][j] = true for all j where
p[j - 1] == '*'.
5. It fills the dp table bottom-up using dynamic programming. If the characters
at indices i-1 and j-1 in s and p respectively are equal or if p[j-1] is a
wildcard character '?' (matching any single character), it updates dp[i][j]
based on dp[i-1][j-1]. If p[j-1] is a wildcard character '*' (matching any
sequence of characters), it updates dp[i][j] based on dp[i][j-1] or dp[i-
1][j].
6. Finally, it returns dp[m][n] where m and n are the lengths of the input strings
s and p respectively.

In the main function, we test the isMatch function with example strings "adceb" and
"*a*b", and print whether the pattern matches the string or not.

5. Longest Palindromic Substring:


1. Given a string, find the longest substring that is a palindrome.
2. Best Approach: As mentioned earlier, the approach of expanding
around the center is a common and efficient technique for finding the
longest palindromic substring. Another approach involves dynamic
programming, where a 2D table is used to store whether substrings are
palindromes or not.

#include <iostream>
#include <string>
#include <vector>

std::string longestPalindrome(const std::string& s) {


int n = s.length();
if (n == 0) return "";

// Create a 2D table to store whether substrings are palindromes or not


std::vector<std::vector<bool>> dp(n, std::vector<bool>(n, false));

int start = 0; // Start index of the longest palindromic substring found so far
int maxLen = 1; // Length of the longest palindromic substring found so far

// All substrings of length 1 are palindromes


for (int i = 0; i < n; ++i) {
dp[i][i] = true;
}

// Check for substrings of length 2


for (int i = 0; i < n - 1; ++i) {
if (s[i] == s[i + 1]) {
dp[i][i + 1] = true;
start = i;
maxLen = 2;
}
}

// Check for substrings of length greater than 2


for (int len = 3; len <= n; ++len) {
for (int i = 0; i <= n - len; ++i) {
int j = i + len - 1;
if (s[i] == s[j] && dp[i + 1][j - 1]) {
dp[i][j] = true;
start = i;
maxLen = len;
}
}
}

return s.substr(start, maxLen);


}

int main() {
std::string s = "babad";

std::string longestPalSubstr = longestPalindrome(s);


std::cout << "Longest Palindromic Substring: " << longestPalSubstr << std::endl;

return 0;
}

Explanation:

1. The function longestPalindrome takes a string s as input and returns the


longest palindromic substring.
2. It initializes a 2D vector dp to store whether substrings are palindromes or not.
dp[i][j] represents whether the substring s[i:j+1] is a palindrome.
3. It initializes variables start and maxLen to keep track of the start index and
length of the longest palindromic substring found so far.
4. It first marks all substrings of length 1 as palindromes ( dp[i][i] = true).
5. It then checks for substrings of length 2 and updates start and maxLen
accordingly if a palindrome of length 2 is found.
6. Finally, it iterates over all substrings of length greater than 2 and updates
start and maxLen if a longer palindrome is found.
7. It returns the longest palindromic substring found using the start index and
maxLen.
8. In the main function, we test the longestPalindrome function with an
example string "babad", and print the result.
6. String Compression:
1. Given a string, compress it by replacing consecutive repeating
characters with their count.
2. Best Approach: Iterate through the string and count consecutive
repeating characters while building the compressed string. This can be
done efficiently in a single pass through the input string.

#include <iostream>
#include <string>

std::string compressString(const std::string& s) {


if (s.empty()) {
return s;
}

std::string compressed;
int count = 1;
char prevChar = s[0];

for (int i = 1; i < s.length(); ++i) {


if (s[i] == prevChar) {
count++;
} else {
compressed += prevChar + std::to_string(count);
prevChar = s[i];
count = 1;
}
}

// Append the last character and its count


compressed += prevChar + std::to_string(count);

// Check if compressed string is shorter than original string


return compressed.length() < s.length() ? compressed : s;
}

int main() {
std::string s = "aabcccccaaa";
std::cout << "Original string: " << s << std::endl;
std::string compressed = compressString(s);
std::cout << "Compressed string: " << compressed << std::endl;
return 0;
}
Explanation:

1. The function compressString takes a string s as input and returns the


compressed string.
2. It initializes variables count to keep track of the count of consecutive
repeating characters and prevChar to store the previous character
encountered.
3. It iterates through the input string s starting from the second character.
4. If the current character s[i] is equal to the previous character prevChar, it
increments the count.
5. If the current character s[i] is different from the previous character
prevChar, it appends prevChar followed by its count to the compressed
string compressed, updates prevChar to the current character s[i], and
resets the count to 1.
6. After the loop, it appends the last character prevChar and its count to the
compressed string compressed.
7. Finally, it checks if the length of the compressed string is shorter than the
length of the original string s and returns the compressed string if true,
otherwise returns the original string s.
8. In the main function, we test the compressString function with an example
string "aabcccccaaa", and print the original and compressed strings.

7. Edit Distance:
1. Problem: Given two strings, find the minimum number of operations
(insertion, deletion, or substitution) required to convert one string into
another.
2. Approach: Use dynamic programming to calculate the edit distance
between the two strings.
3. Time Complexity: O(m * n), where m and n are the lengths of the two
input strings.

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

int minDistance(const std::string& word1, const std::string& word2) {


int m = word1.length();
int n = word2.length();

// Create a 2D vector to store the edit distance


std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1, 0));

// Initialize the first row and column of the dp table


for (int i = 0; i <= m; ++i) {
dp[i][0] = i;
}
for (int j = 0; j <= n; ++j) {
dp[0][j] = j;
}

// Fill the dp table bottom-up


for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (word1[i - 1] == word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1]; // no operation required
} else {
dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
// 1 + min(deletion, insertion, substitution)
}
}
}

return dp[m][n];
}

int main() {
std::string word1 = "horse";
std::string word2 = "ros";

int distance = minDistance(word1, word2);

std::cout << "Minimum edit distance: " << distance << std::endl;

return 0;
}

Explanation:

1. The function minDistance takes two strings word1 and word2 as input and
returns the minimum number of operations required to convert word1 into
word2.
2. It initializes a 2D vector dp to store the edit distance between substrings of
word1 and word2. dp[i][j] represents the edit distance between
word1.substr(0, i) and word2.substr(0, j).
3. It initializes the first row and column of the dp table with the indices as they
represent the cost of converting an empty string to a substring of the other
string.
4. It fills the dp table bottom-up using dynamic programming. If the characters
at indices i-1 and j-1 in word1 and word2 respectively are equal, no
operation is required (dp[i][j] = dp[i-1][j-1]). Otherwise, the edit
distance is calculated as 1 + the minimum of deletion, insertion, or
substitution operations.
5. Finally, it returns dp[m][n], where m and n are the lengths of word1 and word2
respectively.
6. In the main function, we test the minDistance function with example strings
"horse" and "ros", and print the result.

8. String Rotation:
 Problem: Given two strings, check if one string is a rotation of the
other.
 Approach: Concatenate one string with itself and check if the other
string is a substring of the concatenated string.
 Time Complexity: O(n), where n is the length of the input string.

#include <iostream>
#include <string>

bool isRotation(const std::string& s1, const std::string& s2) {


if (s1.length() != s2.length()) {
return false; // If lengths are different, s2 cannot be a rotation of s1
}

std::string concat = s1 + s1; // Concatenate s1 with itself

// Check if s2 is a substring of the concatenated string


return concat.find(s2) != std::string::npos;
}

int main() {
std::string s1 = "waterbottle";
std::string s2 = "erbottlewat";

if (isRotation(s1, s2)) {
std::cout << "s2 is a rotation of s1." << std::endl;
} else {
std::cout << "s2 is not a rotation of s1." << std::endl;
}

return 0;
}

Explanation:

1. The function isRotation takes two strings s1 and s2 as input and returns
true if s2 is a rotation of s1, otherwise returns false.
2. It first checks if the lengths of s1 and s2 are equal. If they are not equal, it
immediately returns false because s2 cannot be a rotation of s1.
3. It concatenates s1 with itself to create a string concat that contains all
possible rotations of s1.
4. It checks if s2 is a substring of concat using the find function. If s2 is found
in concat, it returns true; otherwise, it returns false.
5. In the main function, we test the isRotation function with example strings
"waterbottle" and "erbottlewat", and print the result.

9. Longest Substring Without Repeating


Characters:
 Problem: Given a string, find the length of the longest substring
without repeating characters.
 Approach: Use two pointers to maintain a sliding window and a set to
store characters in the current window. Move the right pointer and
update the set until a repeating character is found, then move the left
pointer and update the set accordingly.
 Time Complexity: O(n), where n is the length of the input string.

#include <iostream>
#include <string>
#include <unordered_set>

int lengthOfLongestSubstring(const std::string& s) {


int n = s.length();
int maxLength = 0;
std::unordered_set<char> charSet;
int left = 0, right = 0;
while (right < n) {
if (charSet.find(s[right]) == charSet.end()) {
charSet.insert(s[right]);
maxLength = std::max(maxLength, right - left + 1);
right++;
} else {
charSet.erase(s[left]);
left++;
}
}

return maxLength;
}

int main() {
std::string s = "abcabcbb";

int length = lengthOfLongestSubstring(s);


std::cout << "Length of the longest substring without repeating characters: " << length <<
std::endl;

return 0;
}

Explanation:

1. The function lengthOfLongestSubstring takes a string s as input and returns


the length of the longest substring without repeating characters.
2. It initializes variables left and right as the left and right pointers of the
sliding window, and maxLength as the maximum length found so far.
3. It uses an unordered_set charSet to store characters in the current window.
4. It iterates through the string s using the right pointer right.
5. If the character at s[right] is not in charSet, it means it's a new character.
So, it inserts the character into charSet, updates maxLength if necessary, and
moves the right pointer to the next position.
6. If the character at s[right] is already in charSet, it means it's a repeating
character. So, it removes the character at s[left] from charSet, moves the
left pointer to the next position, and continues with the next iteration.
7. After iterating through the entire string, it returns maxLength.
8. In the main function, we test the lengthOfLongestSubstring function with an
example string "abcabcbb", and print the result.
10. Word Break:
 Problem: Given a string and a dictionary of words, determine if the
string can be segmented into a space-separated sequence of one or
more dictionary words.
 Approach: Use dynamic programming to check if the string can be
segmented into words from the dictionary.
 Time Complexity: O(n^2), where n is the length of the input string.

#include <iostream>
#include <string>
#include <unordered_set>
#include <vector>

bool wordBreak(const std::string& s, const std::unordered_set<std::string>& wordDict) {


int n = s.length();
std::vector<bool> dp(n + 1, false);
dp[0] = true; // Empty string can always be segmented

for (int i = 1; i <= n; ++i) {


for (int j = 0; j < i; ++j) {
if (dp[j] && wordDict.find(s.substr(j, i - j)) != wordDict.end()) {
dp[i] = true;
break;
}
}
}

return dp[n];
}

int main() {
std::string s = "leetcode";
std::unordered_set<std::string> wordDict = {"leet", "code"};

if (wordBreak(s, wordDict)) {
std::cout << "String can be segmented into words from the dictionary." << std::endl;
} else {
std::cout << "String cannot be segmented into words from the dictionary." << std::endl;
}

return 0;
}
Explanation:

1. The function wordBreak takes a string s and a dictionary of words wordDict


as input and returns true if the string can be segmented into words from the
dictionary, otherwise returns false.
2. It initializes a boolean vector dp of size n + 1, where n is the length of the
string s. The dp[i] represents whether the substring s.substr(0, i) can be
segmented into words from the dictionary.
3. It sets dp[0] = true because the empty string can always be segmented.
4. It iterates through each character of the string s, and for each index i, it
iterates through all possible break points j before i.
5. For each break point j, it checks if dp[j] (substring s.substr(0, j)) is true
and if the substring s.substr(j, i - j) exists in the word dictionary. If both
conditions are true, it sets dp[i] = true and breaks out of the loop.
6. Finally, it returns dp[n], where n is the length of the input string s.
7. In the main function, we test the wordBreak function with an example string
"leetcode" and a dictionary containing the words "leet" and "code", and
print the result.

11. Count Palindromic Substrings:


 Problem: Given a string, find the total number of palindromic
substrings in it.
 Approach: Use the "expand around center" technique to count
palindromic substrings centered at each character and between each
pair of characters.
 Time Complexity: O(n^2), where n is the length of the input string.

#include <iostream>
#include <string>

int countPalindromicSubstrings(const std::string& s) {


int count = 0;
int n = s.length();

// Helper function to expand around center


auto expandAroundCenter = [&](int left, int right) {
while (left >= 0 && right < n && s[left] == s[right]) {
count++;
left--;
right++;
}
};
for (int i = 0; i < n; ++i) {
// Palindromes with odd length
expandAroundCenter(i, i);

// Palindromes with even length


expandAroundCenter(i, i + 1);
}

return count;
}

int main() {
std::string s = "abc";
std::cout << "Total number of palindromic substrings: " << countPalindromicSubstrings(s)
<< std::endl;

return 0;
}

Explanation:

1. The function countPalindromicSubstrings takes a string s as input and


returns the total number of palindromic substrings in it.
2. It initializes the count variable to 0 and the n variable to the length of the
string s.
3. It defines a lambda function expandAroundCenter to expand around a center
and count palindromic substrings. This function takes two indices left and
right as input and expands around the center defined by these indices while
the characters at the indices are equal.
4. It iterates through each character of the string s. For each character, it
considers two cases:
 Palindromes with odd length: Expand around the character itself
(centered at i).
 Palindromes with even length: Expand around the character and the
next character (centered between i and i + 1 ).
5. It increments the count variable whenever a palindromic substring is found.
6. Finally, it returns the count variable as the result.
7. In the main function, we test the countPalindromicSubstrings function with
an example string "abc", and print the total number of palindromic
substrings.
12. Reverse Words in a String:
 Problem: Given a string, reverse the order of words in it.
 Approach: Reverse the entire string, then reverse each word
individually.
 Time Complexity: O(n), where n is the length of the input string.

#include <iostream>
#include <string>
#include <algorithm>

void reverseWords(std::string& s) {
int n = s.length();

// Reverse the entire string


std::reverse(s.begin(), s.end());

// Reverse each word individually


int start = 0;
for (int end = 0; end < n; ++end) {
if (s[end] == ' ') {
std::reverse(s.begin() + start, s.begin() + end);
start = end + 1;
} else if (end == n - 1) {
std::reverse(s.begin() + start, s.begin() + end + 1);
}
}
}

int main() {
std::string s = "the sky is blue";
reverseWords(s);
std::cout << "Reversed string: " << s << std::endl;

return 0;
}

Explanation:

1. The function reverseWords takes a string s as input and reverses the order of
words in it.
2. It first reverses the entire string using the std::reverse function, which
reverses the elements in the range [first, last).
3. Then, it iterates through each character of the reversed string. When it
encounters a space character ' ', it means it has found the end of a word. At
this point, it reverses the characters in the range [start, end) (where start
is the start index of the current word and end is the index of the space
character) using the std::reverse function.
4. If the loop reaches the end of the string ( end == n - 1), it means the last
word in the string has been processed, so it reverses the characters in the
range [start, end + 1) to reverse the last word.
5. In the main function, we test the reverseWords function with an example
string "the sky is blue", and print the reversed string.

You might also like