문자열의 아름다움 문서의 문제 1 풀이 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""문자열의 아름다움 (프로그래머스 68938) 풀이와 검증."""
import math
import random
import time
from collections import defaultdict


def solution(s):
    n = len(s)
    total = (n + 1) * n * (n - 1) // 6        # 모든 부분 문자열의 (길이 − 1) 합
    runs = defaultdict(list)                   # 글자 → 그 글자가 연달아 나온 덩어리의 길이들
    i = 0
    while i < n:
        j = i
        while j < n and s[j] == s[i]:
            j += 1
        p = j - i
        total -= (p + 1) * p * (p - 1) // 6    # 한 덩어리 안의 부분 문자열은 아름다움 0
        runs[s[i]].append(p)
        i = j
    for lens in runs.values():
        lens.sort()
        s1 = s2 = 0                            # 앞에서 본 p들의 p(p+1)(1−p)/6 합, p(p+1)/2 합
        for q in lens:
            total -= s1 + q * s2               # 짧거나 같은 덩어리 p와의 짝: f(p, q)를 모두 뺀다
            s1 += q * (q + 1) * (1 - q) // 6
            s2 += q * (q + 1) // 2
    return total


def beauty(x):
    return max((j - i for i in range(len(x)) for j in range(i + 1, len(x)) if x[i] != x[j]), default=0)


def by_definition(s):
    return sum(beauty(s[l:r + 1]) for l in range(len(s)) for r in range(l, len(s)))


def f(p, q):
    return sum(min(a, b) for a in range(1, p + 1) for b in range(1, q + 1))


if __name__ == "__main__":
    assert solution("baby") == 9 == by_definition("baby")
    assert solution("oo") == 0 == by_definition("oo")
    # 1단계·3단계의 예
    assert beauty("abbca") == 3 and beauty("aabaaa") == 3 and beauty("aaa") == 0
    assert solution("aabaa") == by_definition("aabaa") == 20 - 2 - 5 == 13
    for p in range(1, 9):
        for q in range(p, 9):
            assert f(p, q) == p * (p + 1) * (3 * q + 1 - p) // 6 == f(q, p)
    assert (f(1, 1), f(1, 2), f(2, 2), f(2, 3)) == (1, 2, 5, 8)
    # 7단계: 짝의 수와 답의 크기
    assert math.comb(150_000, 2) > 11_000_000_000
    assert 4.4e15 < math.comb(300_001, 3) < 4.6e15 and solution("ab" * 150_000) == math.comb(300_001, 3) - 2 * math.comb(150_000, 2)
    assert solution("aabaa") == by_definition("aabaa") and beauty("aabaa") == 2 and beauty("bab") == 1
    rng = random.Random(68938)
    for _ in range(3000):
        s = "".join(rng.choice("ab" if rng.random() < 0.5 else "abc") for _ in range(rng.randint(1, 14)))
        assert solution(s) == by_definition(s), s
    # 30만 글자: 글자가 모두 다른 차례, 한 글자뿐, 덩어리 길이가 1, 2, 3, …으로 다 다른 경우
    many = []
    k = 1
    while sum(len(x) for x in many) < 300_000 - 2 * k:
        many += ["a" * k, "b"]
        k += 1
    tests = ["".join(rng.choice("abcdefghijklmnopqrstuvwxyz") for _ in range(300_000)),
             "z" * 300_000, "ab" * 150_000, "".join(many)]
    t0 = time.perf_counter()
    for s in tests:
        solution(s)
    t = time.perf_counter() - t0
    assert solution("z" * 300_000) == 0
    assert t < 5, t
    print(f"30만 글자 {len(tests)}개: {t:.2f}s")
    print("ALL CHECKS PASSED")