1781. Sum of Beauty of All Substrings
Problem Description
The problem asks us to find the sum of beauty values for all possible substrings of a given string s
.
The beauty of a string is defined as the difference between the frequency of the most frequent character and the frequency of the least frequent character in that string.
For example, in the string "abaacc"
:
- Character
'a'
appears 3 times (most frequent) - Character
'b'
appears 1 time (least frequent) - Character
'c'
appears 2 times - Beauty = 3 - 1 = 2
The task is to:
- Find all possible substrings of the input string
s
- Calculate the beauty value for each substring
- Return the sum of all these beauty values
The solution uses a nested loop approach where:
- The outer loop (variable
i
) marks the starting position of each substring - The inner loop (variable
j
) extends from positioni
to the end of the string, considering all substrings starting at positioni
- A counter tracks the frequency of each character as we extend the substring
- For each substring formed, we calculate its beauty by finding the difference between the maximum and minimum frequency values
- All beauty values are accumulated and returned as the final answer
Intuition
The key insight is that we need to examine every possible substring of the given string to calculate its beauty value. Since we need all substrings, a brute force approach makes sense here.
Think about how substrings are formed - any substring can be defined by two positions: a starting index and an ending index. This naturally leads to a nested loop structure where we fix a starting position and then explore all possible ending positions from that start.
Why do we build substrings incrementally? Instead of generating each substring from scratch and counting its character frequencies separately, we can be more efficient. When we extend a substring by one character (moving from substring s[i:j]
to s[i:j+1]
), we only need to update our frequency count for that one new character. This allows us to maintain a running frequency counter that gets updated as we extend the substring.
The beauty calculation becomes straightforward once we have the frequency counts. At each step, after adding a new character to our current substring, we can immediately calculate the beauty by finding the difference between max(cnt.values())
and min(cnt.values())
. This gives us the beauty of the current substring s[i:j+1]
.
The approach essentially answers: "For each possible starting position, what are all the substrings I can form, and what is their beauty?" By systematically exploring all starting positions (outer loop) and all ending positions from each start (inner loop), we ensure we don't miss any substring.
Solution Approach
The solution uses an enumeration approach with counting to solve the problem efficiently.
We enumerate each starting position i
in the string, treating it as the left endpoint of our substrings. For each starting position, we find all substrings that begin at this position by extending to different ending positions.
The implementation follows these steps:
-
Initialize variables: Set
ans = 0
to store the total beauty sum andn = len(s)
to get the string length. -
Outer loop - Fix starting position: Iterate through each index
i
from0
ton-1
. This represents the starting position of our substrings. -
Initialize counter for each starting position: Create a new
Counter()
object (a dictionary that tracks character frequencies) for each starting position. This counter will be reused and updated as we extend the substring. -
Inner loop - Extend substring: For each starting position
i
, iteratej
fromi
ton-1
. This represents the ending position of the substrings[i:j+1]
. -
Update frequency count: Add the character at position
j
to our counter:cnt[s[j]] += 1
. This incrementally builds the frequency map as we extend the substring. -
Calculate and accumulate beauty: After adding each new character, calculate the beauty of the current substring as
max(cnt.values()) - min(cnt.values())
and add it to our answer. -
Return result: After processing all possible substrings, return the accumulated sum.
The time complexity is O(n² × k)
where n
is the length of the string and k
is the number of unique characters in each substring (for finding max and min values). The space complexity is O(k)
for the counter, where k
is at most 26 for lowercase English letters.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a small example: s = "aab"
Step 1: Initialize
ans = 0
(to accumulate total beauty)n = 3
(length of string)
Step 2: Process starting position i = 0
Initialize cnt = Counter()
for substrings starting at index 0.
-
j = 0: Substring is
"a"
- Update:
cnt['a'] = 1
- Frequencies: {'a': 1}
- Beauty = max(1) - min(1) = 0
ans = 0 + 0 = 0
- Update:
-
j = 1: Substring is
"aa"
- Update:
cnt['a'] = 2
- Frequencies: {'a': 2}
- Beauty = max(2) - min(2) = 0
ans = 0 + 0 = 0
- Update:
-
j = 2: Substring is
"aab"
- Update:
cnt['b'] = 1
- Frequencies: {'a': 2, 'b': 1}
- Beauty = max(2, 1) - min(2, 1) = 2 - 1 = 1
ans = 0 + 1 = 1
- Update:
Step 3: Process starting position i = 1
Initialize new cnt = Counter()
for substrings starting at index 1.
-
j = 1: Substring is
"a"
- Update:
cnt['a'] = 1
- Frequencies: {'a': 1}
- Beauty = max(1) - min(1) = 0
ans = 1 + 0 = 1
- Update:
-
j = 2: Substring is
"ab"
- Update:
cnt['b'] = 1
- Frequencies: {'a': 1, 'b': 1}
- Beauty = max(1, 1) - min(1, 1) = 1 - 1 = 0
ans = 1 + 0 = 1
- Update:
Step 4: Process starting position i = 2
Initialize new cnt = Counter()
for substrings starting at index 2.
- j = 2: Substring is
"b"
- Update:
cnt['b'] = 1
- Frequencies: {'b': 1}
- Beauty = max(1) - min(1) = 0
ans = 1 + 0 = 1
- Update:
Final Result: The sum of all beauty values is 1.
The key insight demonstrated here is how we incrementally build each substring by extending it one character at a time, updating our frequency counter as we go, rather than recalculating frequencies from scratch for each substring.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def beautySum(self, s: str) -> int:
5 """
6 Calculate the sum of beauty values for all substrings of s.
7 Beauty is defined as the difference between the maximum and minimum
8 frequency of characters in a substring.
9
10 Args:
11 s: Input string
12
13 Returns:
14 Sum of beauty values for all substrings
15 """
16 total_beauty = 0
17 string_length = len(s)
18
19 # Iterate through all possible starting positions
20 for start_index in range(string_length):
21 # Character frequency counter for current substring
22 char_frequency = Counter()
23
24 # Extend substring from start_index to end of string
25 for end_index in range(start_index, string_length):
26 # Add current character to frequency counter
27 char_frequency[s[end_index]] += 1
28
29 # Calculate beauty: max frequency - min frequency
30 # Beauty represents the difference between most and least frequent characters
31 max_frequency = max(char_frequency.values())
32 min_frequency = min(char_frequency.values())
33 total_beauty += max_frequency - min_frequency
34
35 return total_beauty
36
1class Solution {
2 public int beautySum(String s) {
3 int totalBeauty = 0;
4 int stringLength = s.length();
5
6 // Iterate through all possible starting positions of substrings
7 for (int startIndex = 0; startIndex < stringLength; ++startIndex) {
8 // Frequency array to count occurrences of each character (a-z)
9 int[] charFrequency = new int[26];
10
11 // Extend the substring from startIndex to each possible ending position
12 for (int endIndex = startIndex; endIndex < stringLength; ++endIndex) {
13 // Update frequency for the current character
14 ++charFrequency[s.charAt(endIndex) - 'a'];
15
16 // Find minimum and maximum frequencies among characters that appear at least once
17 int minFrequency = 1000; // Initialize to a large value
18 int maxFrequency = 0; // Initialize to smallest possible value
19
20 // Iterate through all character frequencies
21 for (int frequency : charFrequency) {
22 // Only consider characters that appear in the current substring
23 if (frequency > 0) {
24 minFrequency = Math.min(minFrequency, frequency);
25 maxFrequency = Math.max(maxFrequency, frequency);
26 }
27 }
28
29 // Add beauty of current substring (difference between max and min frequencies)
30 totalBeauty += maxFrequency - minFrequency;
31 }
32 }
33
34 return totalBeauty;
35 }
36}
37
1class Solution {
2public:
3 int beautySum(string s) {
4 int totalBeauty = 0;
5 int stringLength = s.size();
6
7 // Iterate through all possible starting positions
8 for (int start = 0; start < stringLength; ++start) {
9 // Frequency array for 26 lowercase letters
10 int charFrequency[26] = {0};
11
12 // Iterate through all possible ending positions from current start
13 for (int end = start; end < stringLength; ++end) {
14 // Increment frequency of current character
15 ++charFrequency[s[end] - 'a'];
16
17 // Find minimum and maximum frequencies among present characters
18 int minFreq = INT_MAX;
19 int maxFreq = 0;
20
21 for (int freq : charFrequency) {
22 if (freq > 0) {
23 minFreq = min(minFreq, freq);
24 maxFreq = max(maxFreq, freq);
25 }
26 }
27
28 // Add beauty (difference between max and min frequencies) to total
29 totalBeauty += maxFreq - minFreq;
30 }
31 }
32
33 return totalBeauty;
34 }
35};
36
1/**
2 * Calculates the sum of beauty of all substrings of a given string.
3 * Beauty is defined as the difference between the most frequent and least frequent characters in a substring.
4 *
5 * @param s - The input string to process
6 * @returns The sum of beauty values for all possible substrings
7 */
8function beautySum(s: string): number {
9 let totalBeautySum: number = 0;
10
11 // Iterate through each starting position for substrings
12 for (let startIndex: number = 0; startIndex < s.length; startIndex++) {
13 // Map to store character frequency for current substring
14 const charFrequencyMap: Map<string, number> = new Map<string, number>();
15
16 // Extend substring from current starting position
17 for (let endIndex: number = startIndex; endIndex < s.length; endIndex++) {
18 // Update frequency count for current character
19 const currentChar: string = s[endIndex];
20 const currentFrequency: number = charFrequencyMap.get(currentChar) || 0;
21 charFrequencyMap.set(currentChar, currentFrequency + 1);
22
23 // Extract all frequency values from the map
24 const frequencyValues: number[] = Array.from(charFrequencyMap.values());
25
26 // Calculate beauty as difference between max and min frequencies
27 const maxFrequency: number = Math.max(...frequencyValues);
28 const minFrequency: number = Math.min(...frequencyValues);
29 const beauty: number = maxFrequency - minFrequency;
30
31 // Add current substring's beauty to total sum
32 totalBeautySum += beauty;
33 }
34 }
35
36 return totalBeautySum;
37}
38
Time and Space Complexity
The time complexity is O(n² × C)
, where n
is the length of the string and C
is the size of the character set (26 for lowercase English letters).
- The outer loop runs
n
times (fromi = 0
ton-1
) - The inner loop runs
n-i
times for each iteration of the outer loop - Total iterations:
n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 = O(n²)
- Inside the inner loop, finding
max(cnt.values())
andmin(cnt.values())
each takesO(C)
time since we need to iterate through all character counts in the Counter - Therefore, the overall time complexity is
O(n² × C)
The space complexity is O(C)
, where C = 26
.
- The Counter object
cnt
stores at mostC
distinct characters (26 lowercase English letters) - Even though we create a new Counter for each starting position
i
, we only maintain one Counter at a time - The space used by the Counter is bounded by the size of the character set, not the length of the string
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Inefficient Max/Min Calculation for Every Substring
The current solution calls max(char_frequency.values())
and min(char_frequency.values())
for every substring, which iterates through all frequency values each time. This becomes inefficient when dealing with longer strings.
Why it's a problem: For each substring s[i:j+1]
, we're iterating through up to 26 values (for lowercase English letters) to find max and min. With O(n²) substrings, this adds unnecessary overhead.
Better approach: Track max and min frequencies incrementally or use a more efficient data structure:
from collections import Counter, defaultdict
class Solution:
def beautySum(self, s: str) -> int:
total_beauty = 0
n = len(s)
for i in range(n):
char_freq = defaultdict(int)
freq_count = defaultdict(int) # Track how many chars have each frequency
max_freq = 0
for j in range(i, n):
char = s[j]
old_freq = char_freq[char]
new_freq = old_freq + 1
char_freq[char] = new_freq
# Update frequency tracking
if old_freq > 0:
freq_count[old_freq] -= 1
if freq_count[old_freq] == 0:
del freq_count[old_freq]
freq_count[new_freq] += 1
# Update max frequency
max_freq = max(max_freq, new_freq)
# Find min frequency (smallest key in freq_count)
if freq_count:
min_freq = min(freq_count.keys())
total_beauty += max_freq - min_freq
return total_beauty
2. Unnecessary Beauty Calculation for Single Character Substrings
When start_index == end_index
, the substring contains only one character, so all frequencies are 1, making beauty = 1 - 1 = 0. These calculations don't contribute to the sum but still consume resources.
Optimization: Skip beauty calculation for single-character substrings:
for end_index in range(start_index, string_length):
char_frequency[s[end_index]] += 1
# Skip single character substrings (beauty is always 0)
if end_index > start_index:
max_frequency = max(char_frequency.values())
min_frequency = min(char_frequency.values())
total_beauty += max_frequency - min_frequency
3. Memory Overhead from Counter Object Creation
Creating a new Counter()
object for each starting position can cause memory allocation overhead, especially for large strings.
Alternative: Reuse a single dictionary and reset it manually:
char_frequency = {}
for start_index in range(string_length):
char_frequency.clear() # Reset for new starting position
for end_index in range(start_index, string_length):
char = s[end_index]
char_frequency[char] = char_frequency.get(char, 0) + 1
# ... rest of logic
4. Missing Early Termination Opportunities
The algorithm doesn't leverage the fact that beauty can only increase or stay the same as we extend a substring (max can only increase or stay same, min can only decrease or stay same).
Potential optimization: Track when beauty values stabilize for certain patterns, though this is more complex to implement correctly and may not always provide significant benefits.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!