Topics › Strings › Medium String Problems
Sum of Beauty of All Substrings
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
The beauty of a string is the difference between the frequency of its most frequent character and the frequency of its least frequent character, counting only characters that actually appear. For example "abaacc" has frequencies a = 3, b = 1, c = 2, so its beauty is 3 - 1 = 2. A string in which every present character occurs equally often has beauty 0.
Given a string s of lowercase letters, return the sum of the beauties of all of its substrings.
Constraints: 1 ≤ |s| ≤ 500; lowercase English letters only.
Example 1
s = "aabcb"
answer = 5 // "aab", "aabc", "aabcb", "abcb", "bcb" each have beauty 1; every other substring has beauty 0
Example 2
s = "aabcbaa"
answer = 17
Example 3
s = "aaaa"
answer = 0 // one distinct letter: max and min frequency always coincide
Approach 1: Brute force, count every substring from scratch
Intuition. Enumerate every substring s[i..j], build its 26-slot frequency table by scanning it, then find the largest and smallest non-zero entries. There is nothing clever here, but it gives us a reference implementation that the faster methods are tested against in main.
Algorithm.
- For every start
iand endj ≥ i: - Count the letters of
s[i..j]intofreq[26]. - Add
beauty(freq), the difference between the maximum and the minimum non-zero count, to the total.
static long bruteForce(String s) {
int n = s.length();
long total = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int[] freq = new int[26];
for (int p = i; p <= j; p++) freq[s.charAt(p) - 'a']++;
total += beauty(freq);
}
}
return total;
}
private static int beauty(int[] freq) {
int max = 0, min = Integer.MAX_VALUE;
for (int f : freq) {
if (f == 0) continue;
if (f > max) max = f;
if (f < min) min = f;
}
return max == 0 ? 0 : max - min;
}
Complexity. Time O(n^3): O(n^2) substrings, each scanned in O(n) plus a constant 26-slot pass. Space O(1).
Note the guard in beauty: the minimum must ignore letters with count 0, otherwise every substring that misses at least one letter of the alphabet would get a minimum of 0 and the “beauty” would just be the maximum frequency.
Approach 2: Better, extend the end and keep the table
Intuition. Fix the start i. The substrings s[i..i], s[i..i+1], s[i..i+2], and so on each differ from the previous one by exactly one appended letter. So the frequency table does not need rebuilding: increment one slot per step. The max and min are then recomputed with a pass over the 26 slots. That pass is constant work, and the O(n) rebuild disappears.
Algorithm.
- For every start
i: clearfreq[26]. - For
jfromiton - 1:freq[s[j]]++, then addbeauty(freq)to the total.
static long better(String s) {
int n = s.length();
long total = 0;
for (int i = 0; i < n; i++) {
int[] freq = new int[26];
for (int j = i; j < n; j++) {
freq[s.charAt(j) - 'a']++;
total += beauty(freq);
}
}
return total;
}
Complexity. Time O(26 · n^2), about 3.3 × 10^6 elementary steps for n = 500. Space O(1).
For the given constraints this already runs in a few milliseconds, and many accepted solutions stop here. The remaining factor of 26 is a constant, but it is a constant that depends on the alphabet, and the next approach shows how to remove it.
Approach 3: Optimal, maintain the max and min incrementally
Intuition. While the end j grows, every frequency in the table can only go up. That has two consequences.
The maximum is trivial: it is max(maxF, newCount) after each increment, because the letter we just touched is the only one that changed.
The minimum needs one extra piece of information: how many letters currently sit at each frequency. Call it lettersWith[f]. When the letter c goes from frequency f to f + 1, two things can happen to the minimum:
- If
f == 0, a brand new letter has appeared with count1, which is the smallest possible non-zero count, sominF = 1. - If
f == minFandlettersWith[f]has just dropped to0, thencwas the last letter at the minimum. Every other letter has count at leastf + 1(they were all at leastfand none is atfany more), andcnow has exactlyf + 1, sominF = f + 1. - Otherwise the minimum is unchanged: either some other letter still sits at
minF, orcwas above the minimum to begin with.
Because frequencies never decrease within the inner loop, the minimum never has to be searched for; it only ever resets to 1 or steps up by one. Each update is O(1).
Algorithm.
- For every start
i: clearfreq[26]andlettersWith[0..n+1]; setmaxF = minF = 0. - For
jfromiton - 1, withc = s[j]andf = freq[c]:- If
f > 0, decrementlettersWith[f]. Setfreq[c] = f + 1and incrementlettersWith[f + 1]. maxF = max(maxF, f + 1).- If
f == 0, setminF = 1; else iff == minFandlettersWith[f] == 0, setminF = f + 1. - Add
maxF - minFto the total.
- If
static long optimal(String s) {
int n = s.length();
long total = 0;
for (int i = 0; i < n; i++) {
int[] freq = new int[26];
int[] lettersWith = new int[n + 2]; // lettersWith[f] = how many letters occur exactly f times
int maxF = 0, minF = 0;
for (int j = i; j < n; j++) {
int c = s.charAt(j) - 'a';
int f = freq[c]; // old frequency of this letter
if (f > 0) lettersWith[f]--;
freq[c] = f + 1;
lettersWith[f + 1]++;
if (f + 1 > maxF) maxF = f + 1;
if (f == 0) minF = 1; // a letter seen for the first time is the rarest
else if (f == minF && lettersWith[f] == 0) minF = f + 1;
total += maxF - minF;
}
}
return total;
}
Dry run for the start i = 0 of s = "aabcb":
| j | letter | old f | freq after | lettersWith (non-zero) | maxF | minF | beauty |
|---|---|---|---|---|---|---|---|
| 0 | a | 0 | a=1 | [1]=1 | 1 | 1 | 0 |
| 1 | a | 1 | a=2 | [2]=1 | 2 | 2 (nothing left at 1) | 0 |
| 2 | b | 0 | a=2, b=1 | [1]=1, [2]=1 | 2 | 1 (new letter) | 1 |
| 3 | c | 0 | a=2, b=1, c=1 | [1]=2, [2]=1 | 2 | 1 | 1 |
| 4 | b | 1 | a=2, b=2, c=1 | [1]=1, [2]=2 | 2 | 1 (c still at 1) | 1 |
The start i = 0 contributes 3. The starts i = 1 ("abcb" → 1) and i = 2 ("bcb" → 1) add 2 more, and i = 3, 4 add nothing, for a total of 5.
Complexity. Time O(n^2): n(n + 1) / 2 substrings, constant work each. Space O(n) for lettersWith, which has to hold frequencies up to n.
Edge cases and pitfalls
- One distinct letter.
"aaaa"has total0: for every substring the single present letter is both the most and the least frequent. The brute force gets this right only becausebeautyskips zero counts. - Empty string. No substrings, total
0. All three loops simply do not run. - Two letters, equal counts.
"ab"has total0; the first substring with non-zero beauty needs a letter to appear at least twice while another appears once. - Overflow. With
n = 500there are125,250substrings and each beauty is below500, so the sum stays below6.3 × 10^7and fits anint. The methods still returnlong: it costs nothing, and the same code then works for the larger inputs seen in variants of this problem. - Sizing
lettersWith. A letter’s frequency can reachn - i ≤ n, so the array needs indices up ton. Allocatingn + 2avoids an off-by-one. - Updating
minFin the wrong order. ThelettersWith[f]--must happen before thelettersWith[f] == 0test; otherwise the letter that just left frequencyfis still counted there and the minimum never moves up. - Resetting per start.
freq,lettersWith,maxFandminFall belong to one start index. Reusing them across starts without clearing mixes substrings together.
Related problems
- Count Number of Substrings enumerates substrings with the same “fix the start, extend the end” loop and a running frequency table.
- Sort Characters by Frequency is built on the same 26-slot frequency table.
- Longest Repeating Character Replacement also tracks the maximum frequency inside a growing window and relies on the fact that it only needs to go up.
- Longest Substring Without Repeating Characters is the window problem where every frequency must stay at most
1.