Given two strings s and t, where s is the original string and t is its compressed form, find if t is a valid compression of s or not.
- During compression, any sequence of consecutive characters may be replaced by the number of characters removed.
- If t contains consecutive digits, treat them as a single number. For example, "B32" represents "B" + "32", not "B" + "3" + "2".
Examples:
Input: s = "GEEKSFORGEEKS", t = "G7G3S"
Output: true
Explanation: 'G' + skip 7 characters ("EEKSFOR") + 'G' + skip 3 characters ("EEK") + 'S' matches s exactly.Input: s = "DFS", t = "D1D"
Output: false
Explanation: t is not a valid compressed string.
Table of Content
[Naive Approach] Using Parsing and Validation - O(n + m) Time and O(m) Space
The main idea is to first parse t into meaningful tokens. A character token must match the corresponding character in s, while a numeric token tells us how many characters to skip.
- Traverse the compressed string t and parse it into tokens.
- Store each character as a character token and each complete number as a number token.
- Traverse the parsed tokens using a pointer j for the original string s.
- For a number token, move j forward by that many positions; if j exceeds s.length(), return false.
- For a character token, check whether it matches s[j]; if not, return false, otherwise move j one position forward.
- After processing all tokens, return true only if j reaches exactly s.length().
#include <bits/stdc++.h>
using namespace std;
bool checkCompressed(string &s, string &t)
{
// Each token is stored as:
// {'C', character} -> character token
// {'N', number} -> number token
vector<pair<char, int>> tokens;
int n = t.size();
for (int i = 0; i < n; i++)
{
// If the current character is a digit,
// build the complete number from consecutive digits.
if (isdigit(t[i]))
{
int num = 0;
while (i < n && isdigit(t[i]))
{
num = num * 10 + (t[i] - '0');
i++;
}
// Store the number token.
tokens.push_back({'N', num});
// The for loop will increment i again,
// so move it one step back.
i--;
}
else
{
// Store the character token.
tokens.push_back({'C', t[i]});
}
}
int j = 0;
for (auto &token : tokens)
{
// If the token is a number,
// skip that many characters in s.
if (token.first == 'N')
{
j += token.second;
// We cannot skip beyond the end of s.
if (j > s.size())
return false;
}
else
{
// If the token is a character,
// it must match the current character in s.
if (j >= s.size() || s[j] != token.second)
return false;
// Move to the next character.
j++;
}
}
// The entire original string must be consumed.
return j == s.size();
}
int main()
{
string s = "GEEKSFORGEEKS";
string t = "G7G3S";
if (checkCompressed(s, t)) cout << "true\n";
else cout << "false\n";
return 0;
}
import java.util.*;
// Class used to represent a parsed token.
class Token {
char type;
long value;
Token(char type, long value)
{
this.type = type;
this.value = value;
}
}
class GFG {
static boolean checkCompressed(String s, String t)
{
// Each token is stored as:
// {'C', character} -> character token
// {'N', number} -> number token
List<Token> tokens = new ArrayList<>();
int n = t.length();
for (int i = 0; i < n; i++) {
// If the current character is a digit,
// build the complete number from consecutive
// digits.
if (Character.isDigit(t.charAt(i))) {
long num = 0;
while (i < n
&& Character.isDigit(t.charAt(i))) {
int digit = t.charAt(i) - '0';
// If num is already larger than the
// length of s, this number cannot
// possibly represent a valid skip.
if (num > s.length()) {
return false;
}
num = num * 10 + digit;
// If the number itself exceeds the
// length of s, it can never be a valid
// skip.
if (num > s.length()) {
return false;
}
i++;
}
// Store the number token.
tokens.add(new Token('N', num));
// The for loop will increment i again,
// so move it one step back.
i--;
}
else {
// Store the character token.
tokens.add(new Token('C', t.charAt(i)));
}
}
int j = 0;
// Validate the tokens against the original string.
for (Token token : tokens) {
// If the token is a number,
// skip that many characters in s.
if (token.type == 'N') {
// token.value is guaranteed to be
// at most s.length().
j += (int)token.value;
// We cannot skip beyond the end of s.
if (j > s.length()) {
return false;
}
}
else {
// If the token is a character,
// it must match the current character in s.
if (j >= s.length()
|| s.charAt(j) != (char)token.value) {
return false;
}
// Move to the next character.
j++;
}
}
// The entire original string must be consumed.
return j == s.length();
}
public static void main(String[] args)
{
String s = "GEEKSFORGEEKS";
String t = "G7G3S";
if (checkCompressed(s, t))
System.out.println("true");
else
System.out.println("false");
}
}
def checkCompressed(s, t):
# Each token is stored as:
# ('C', character) -> character token
# ('N', number) -> number token
tokens = []
n = len(t)
i = 0
while i < n:
# If the current character is a digit,
# build the complete number from consecutive digits.
if t[i].isdigit():
num = 0
while i < n and t[i].isdigit():
num = num * 10 + int(t[i])
i += 1
# Store the number token.
tokens.append(('N', num))
else:
# Store the character token.
tokens.append(('C', t[i]))
i += 1
j = 0
# Validate the tokens against the original string.
for token_type, value in tokens:
# If the token is a number,
# skip that many characters in s.
if token_type == 'N':
j += value
# We cannot skip beyond the end of s.
if j > len(s):
return False
else:
# If the token is a character,
# it must match the current character in s.
if j >= len(s) or s[j] != value:
return False
# Move to the next character.
j += 1
# The entire original string must be consumed.
return j == len(s)
# Driver Code
if __name__ == "__main__":
s = "GEEKSFORGEEKS"
t = "G7G3S"
if checkCompressed(s, t):
print("true")
else:
print("false")
using System;
using System.Collections.Generic;
// Class used to represent a parsed token.
class Token {
public char Type;
public long Value;
public Token(char type, long value)
{
Type = type;
Value = value;
}
}
class GFG {
static bool checkCompressed(string s, string t)
{
// Each token is stored as:
// {'C', character} -> character token
// {'N', number} -> number token
List<Token> tokens = new List<Token>();
int n = t.Length;
for (int i = 0; i < n; i++) {
// If the current character is a digit,
// build the complete number from consecutive
// digits.
if (char.IsDigit(t[i])) {
long num = 0;
while (i < n && char.IsDigit(t[i])) {
int digit = t[i] - '0';
// If num is already larger than the
// length of s, this number cannot
// possibly represent a valid skip.
if (num > s.Length)
return false;
num = num * 10 + digit;
// If the number exceeds the length of
// s, it can never be a valid skip.
if (num > s.Length)
return false;
i++;
}
// Store the number token.
tokens.Add(new Token('N', num));
// The for loop will increment i again,
// so move it one step back.
i--;
}
else {
// Store the character token.
tokens.Add(new Token('C', t[i]));
}
}
int j = 0;
// Validate the tokens against the original string.
foreach(Token token in tokens)
{
// If the token is a number,
// skip that many characters in s.
if (token.Type == 'N') {
j += (int)token.Value;
// We cannot skip beyond the end of s.
if (j > s.Length)
return false;
}
else {
// If the token is a character,
// it must match the current character in s.
if (j >= s.Length
|| s[j] != (char)token.Value)
return false;
// Move to the next character.
j++;
}
}
// The entire original string must be consumed.
return j == s.Length;
}
static void Main()
{
string s = "GEEKSFORGEEKS";
string t = "G7G3S";
if (checkCompressed(s, t))
Console.WriteLine("true");
else
Console.WriteLine("false");
}
}
function checkCompressed(s, t)
{
// Each token is stored as:
// ["C", character] -> character token
// ["N", number] -> number token
const tokens = [];
const n = t.length;
let i = 0;
while (i < n) {
// If the current character is a digit,
// build the complete number from consecutive
// digits.
if (t[i] >= "0" && t[i] <= "9") {
let num = 0;
while (i < n && t[i] >= "0" && t[i] <= "9") {
num = num * 10 + Number(t[i]);
i++;
}
// Store the number token.
tokens.push([ "N", num ]);
}
else {
// Store the character token.
tokens.push([ "C", t[i] ]);
i++;
}
}
let j = 0;
// Validate the tokens against the original string.
for (const [type, value] of tokens) {
// If the token is a number,
// skip that many characters in s.
if (type === "N") {
j += value;
// We cannot skip beyond the end of s.
if (j > s.length)
return false;
}
else {
// If the token is a character,
// it must match the current character in s.
if (j >= s.length || s[j] !== value)
return false;
// Move to the next character.
j++;
}
}
// The entire original string must be consumed.
return j === s.length;
}
// Driver Code
const s = "GEEKSFORGEEKS";
const t = "G7G3S";
if (checkCompressed(s, t))
console.log("true");
else
console.log("false");
Output
true
[Expected Approach] Using Two Pointer Approach - O(m) Time and O(1) Space
The main idea is to parse and validate t at the same time, so there is no need to store the tokens separately.
We maintain a pointer j in the original string s. When we encounter consecutive digits in t, we combine them into one number and skip that many characters in s.
When we encounter a character, it must match the current character of s. If any mismatch occurs or a skip goes beyond the end of s, the compression is invalid.
- Initialize a pointer j = 0 for the original string s and traverse the compressed string t.
- If t[i] is a digit, combine all consecutive digits to form the complete skip count.
- Move j forward by the skip count; if j exceeds s.length(), return false.
- If t[i] is a character, check whether it matches s[j]; if not, return false, otherwise increment j.
- Continue until the entire compressed string t is processed.
- Finally, return true only if j == s.length(), meaning the compression consumes the original string exactly.
#include <bits/stdc++.h>
using namespace std;
bool checkCompressed(string &s, string &t)
{
int j = 0; // Pointer for the original string s
int n = t.size();
for (int i = 0; i < n; i++)
{
// If the current character is a digit,
// build the complete number from consecutive digits.
if (isdigit(t[i]))
{
long long skip = 0;
while (i < n && isdigit(t[i]))
{
int digit = t[i] - '0';
// If skip is already greater than s.size(),
// this number cannot be valid.
if (skip > s.size())
return false;
skip = skip * 10 + digit;
// No valid compression can skip more
// characters than the size of s.
if (skip > s.size())
return false;
i++;
}
// Skip the required number of characters.
j += skip;
// The skip cannot go beyond the end of s.
if (j > s.size())
return false;
// The for loop will increment i again,
// so move it one step back.
i--;
}
else
{
// Current character must match s[j].
if (j >= s.size() || s[j] != t[i])
return false;
// Move to the next character in s.
j++;
}
}
// The entire original string must be consumed.
return j == s.size();
}
int main()
{
string s = "GEEKSFORGEEKS";
string t = "G7G3S";
if (checkCompressed(s, t))
cout << "true\n";
else
cout << "false\n";
return 0;
}
class GFG {
static boolean checkCompressed(String s, String t)
{
int j = 0; // Pointer for the original string s
int n = t.length();
for (int i = 0; i < n; i++) {
// If the current character is a digit,
// build the complete number from consecutive
// digits.
if (Character.isDigit(t.charAt(i))) {
long skip = 0;
while (i < n
&& Character.isDigit(t.charAt(i))) {
int digit = t.charAt(i) - '0';
// If skip is already greater than
// s.length(), this number cannot be
// valid.
if (skip > s.length())
return false;
skip = skip * 10 + digit;
// No valid compression can skip more
// characters than the size of s.
if (skip > s.length())
return false;
i++;
}
// Skip the required number of characters.
j += (int)skip;
// The skip cannot go beyond the end of s.
if (j > s.length())
return false;
// The for loop will increment i again,
// so move it one step back.
i--;
}
else {
// Current character must match s[j].
if (j >= s.length()
|| s.charAt(j) != t.charAt(i))
return false;
// Move to the next character in s.
j++;
}
}
// The entire original string must be consumed.
return j == s.length();
}
public static void main(String[] args)
{
String s = "GEEKSFORGEEKS";
String t = "G7G3S";
if (checkCompressed(s, t))
System.out.println("true");
else
System.out.println("false");
}
}
def checkCompressed(s, t):
j = 0 # Pointer for the original string s
n = len(t)
i = 0
while i < n:
# If the current character is a digit,
# build the complete number from consecutive digits.
if t[i].isdigit():
skip = 0
while i < n and t[i].isdigit():
digit = int(t[i])
# If skip is already greater than the
# length of s, this number cannot be valid.
if skip > len(s):
return False
skip = skip * 10 + digit
# No valid compression can skip more
# characters than the size of s.
if skip > len(s):
return False
i += 1
# Skip the required number of characters.
j += skip
# The skip cannot go beyond the end of s.
if j > len(s):
return False
else:
# Current character must match s[j].
if j >= len(s) or s[j] != t[i]:
return False
# Move to the next character in s.
j += 1
i += 1
# The entire original string must be consumed.
return j == len(s)
# Driver Code
if __name__ == "__main__":
s = "GEEKSFORGEEKS"
t = "G7G3S"
if checkCompressed(s, t):
print("true")
else:
print("false")
using System;
class GFG {
static bool checkCompressed(string s, string t)
{
int j = 0; // Pointer for the original string s
int n = t.Length;
for (int i = 0; i < n; i++) {
// If the current character is a digit,
// build the complete number from consecutive
// digits.
if (char.IsDigit(t[i])) {
long skip = 0;
while (i < n && char.IsDigit(t[i])) {
int digit = t[i] - '0';
// If skip is already greater than
// the length of s, this number cannot
// be valid.
if (skip > s.Length)
return false;
skip = skip * 10 + digit;
// No valid compression can skip more
// characters than the size of s.
if (skip > s.Length)
return false;
i++;
}
// Skip the required number of characters.
j += (int)skip;
// The skip cannot go beyond the end of s.
if (j > s.Length)
return false;
// The for loop will increment i again,
// so move it one step back.
i--;
}
else {
// Current character must match s[j].
if (j >= s.Length || s[j] != t[i])
return false;
// Move to the next character in s.
j++;
}
}
// The entire original string must be consumed.
return j == s.Length;
}
static void Main()
{
string s = "GEEKSFORGEEKS";
string t = "G7G3S";
if (checkCompressed(s, t))
Console.WriteLine("true");
else
Console.WriteLine("false");
}
}
function checkCompressed(s, t)
{
let j = 0; // Pointer for the original string s
let n = t.length;
for (let i = 0; i < n; i++) {
// If the current character is a digit,
// build the complete number from consecutive
// digits.
if (t[i] >= "0" && t[i] <= "9") {
let skip = 0;
while (i < n && t[i] >= "0" && t[i] <= "9") {
let digit = Number(t[i]);
// If skip is already greater than
// the length of s, this number cannot be
// valid.
if (skip > s.length)
return false;
skip = skip * 10 + digit;
// No valid compression can skip more
// characters than the size of s.
if (skip > s.length)
return false;
i++;
}
// Skip the required number of characters.
j += skip;
// The skip cannot go beyond the end of s.
if (j > s.length)
return false;
// The for loop will increment i again,
// so move it one step back.
i--;
}
else {
// Current character must match s[j].
if (j >= s.length || s[j] !== t[i])
return false;
// Move to the next character in s.
j++;
}
}
// The entire original string must be consumed.
return j === s.length;
}
// Driver Code
const s = "GEEKSFORGEEKS";
const t = "G7G3S";
if (checkCompressed(s, t))
console.log("true");
else
console.log("false");
Output
true