Count Substrings Starting and Ending with Given Character
MedPrompt
You are given a string s and a character c. Return the total number of substrings of s that start and end with c.
Example 1:
Input: s = "abada", c = "a"
Output: 6
Explanation: Substrings starting and ending with "a" are: "abada", "abada", "abada", "abada", "abada", "abada".
Example 2:
Input: s = "zzz", c = "z"
Output: 6
Explanation: There are a total of 6 substrings in s and all start and end with "z".
Constraints:
1 <= s.length <= 105sandcconsist only of lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves iterating through all possible substrings of the given string s. For each substring, we check if it starts and ends with the specified character c. If it does, we increment a counter.
Algorithm
- Initialize a counter variable
countto 0. - Use a nested loop structure. The outer loop with index
iiterates from0ton-1(wherenis the string length) to select a starting character. - The inner loop with index
jiterates fromiton-1to select an ending character. - Inside the loops, check if
s.charAt(i)is equal to the target charactercANDs.charAt(j)is also equal toc. - If both conditions are true, it means we have found a valid substring. Increment the
count. - After both loops have finished, return the total
count.
Walkthrough
The algorithm uses two nested loops to define the start and end points of every substring. The outer loop, with index i, iterates from the beginning to the end of the string, marking the potential start of a substring. The inner loop, with index j, iterates from i to the end of the string, marking the potential end of a substring. A substring is valid if the characters at both the start index i and the end index j are equal to the target character c. We maintain a counter that is incremented for every such valid pair of indices (i, j).
class Solution { public long countSubstrings(String s, char c) { long count = 0; int n = s.length(); for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { if (s.charAt(i) == c && s.charAt(j) == c) { count++; } } } return count; }}Complexity
Time
O(N^2), where N is the length of the string `s`. The two nested loops cause the algorithm to check every possible pair of start and end indices, leading to a quadratic runtime.
Space
O(1), as we only use a few variables to store the count and loop indices, which does not depend on the input string's size.
Trade-offs
Pros
Simple to understand and implement.
It directly translates the problem definition into code.
Cons
Highly inefficient due to its quadratic time complexity.
For the given constraints (s.length up to 10^5), this approach will be too slow and result in a 'Time Limit Exceeded' (TLE) error on most platforms.
A much more efficient approach is to reframe the problem from a combinatorial perspective. A substring that starts and ends with c is formed by choosing two occurrences of c in the string, one for the start and one for the end (the start and end can be the same occurrence). If the character c appears k times, the problem reduces to finding the number of ways to choose two positions from these k occurrences, where the order doesn't matter and repetition is allowed.
Algorithm
- Initialize a counter
kto 0. It's important to use a 64-bit integer type (likelongin Java) for this counter to avoid potential overflow. - Iterate through the input string
sjust once, from the first character to the last. - In each iteration, check if the current character is equal to the target character
c. - If it is, increment the counter
k. - After the loop finishes,
kwill hold the total number of occurrences ofcin the string. - The total number of valid substrings is the sum of integers from 1 to
k. This can be calculated directly using the formula for the sum of an arithmetic series:(k * (k + 1)) / 2. - Return this calculated value.
Walkthrough
The core idea is that any substring starting and ending with c is uniquely defined by the indices of its starting and ending c. Let's say the character c appears k times in the string. The first occurrence of c can form a valid substring with itself and the k-1 other occurrences of c that appear after it, resulting in k substrings. The second occurrence of c can form a valid substring with itself and the k-2 occurrences after it, giving k-1 substrings. This pattern continues until the last occurrence of c, which can only form a substring with itself (1 substring). The total count is the sum k + (k-1) + ... + 1. This is the sum of the first k positive integers, which has a well-known formula: k * (k + 1) / 2. The algorithm is thus to first count occurrences of c and then apply this formula. Note that the result can be large, so a 64-bit integer type (long) should be used for the count and the final result to prevent overflow.
class Solution { public long countSubstrings(String s, char c) { long countOfC = 0; for (char ch : s.toCharArray()) { if (ch == c) { countOfC++; } } // The number of substrings is the sum of 1 + 2 + ... + countOfC // which is the formula for the k-th triangular number: k * (k + 1) / 2 return countOfC * (countOfC + 1) / 2; }}Complexity
Time
O(N), where N is the length of the string `s`. We only need to perform a single pass over the string to count the occurrences of `c`. The final calculation is a constant time operation.
Space
O(1), as we only need a single variable to store the count of `c`, regardless of the input string's length.
Trade-offs
Pros
Highly efficient with a linear time complexity, making it suitable for large inputs.
Optimal solution for this problem.
Simple to implement once the combinatorial logic is understood.
Cons
The underlying mathematical insight might not be immediately obvious to everyone.
Solutions
Solution
class Solution {public long countSubstrings(String s, char c) { long cnt = s.chars().filter(ch->ch == c).count(); return cnt + cnt * (cnt - 1) / 2; }}class Solution {public: long long countSubstrings(string s, char c) { long long cnt = ranges ::count(s, c); return cnt + cnt * (cnt - 1) / 2; }};class Solution: def countSubstrings(self, s: str, c: str) -> int: cnt = s . count(c) return cnt + cnt * (cnt - 1) // 2Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.