← Back to LeetCode POTD | Portfolio Home

2472. Maximum Number of Non-overlapping Palindrome Substrings

Difficulty: Hard | Published on 2026-09-15

This is a genuinely hard problem that combines three ideas: palindrome-checking, dynamic programming over string positions, and a clever greedy "shrink to the smallest valid piece" trick. Let's build it up from zero.


1. Problem in Very Simple Language

You have a string INLINECODE0 and a number INLINECODE1. You want to pick out several pieces (substrings) of INLINECODE2, such that:

Each piece is a palindrome (reads the same forwards and backwards). Each piece has length at least INLINECODE3. The pieces don't overlap — once you use some characters for one piece, you can't reuse any of those same characters in another piece (though pieces don't need to be adjacent — there can be gaps of unused characters between them).

Goal: pick as many such non-overlapping pieces as possible.

What's given: the string INLINECODE4, and the minimum length INLINECODE5. What to find: the largest possible count of non-overlapping, length-≥k palindromic substrings you can select. What to return: that count.


2. Real-Life Analogy

Imagine a long string of Christmas lights, where you're looking for "symmetric" light patterns — stretches that look the same read left-to-right as right-to-left, using at least INLINECODE6 bulbs. You want to cut out as many separate symmetric stretches as possible from the string, where "separate" means no light bulb belongs to two different stretches at once. You want to know the maximum number of these symmetric snippets you can carve out.


3. Important Programming Concepts I Need First

Palindrome (recap)

A string that reads the same forwards and backwards. INLINECODE7 is a palindrome if INLINECODE8 and the "inside" (INLINECODE9) is also a palindrome.

2D "is this a palindrome" table

Concept: Build a table INLINECODE10 = true if INLINECODE11 is a palindrome. We fill it using the recursive relationship above: INLINECODE12. Fill it by increasing substring length, so smaller answers are ready when needed by bigger ones. Why we need it: Checking "is this a palindrome" naively (comparing characters from both ends) takes $O(\text{length})$ time each time; precomputing this table once lets us answer any "is s[l..r] a palindrome?" question in $O(1)$ afterward.

Dynamic Programming over string prefixes

Concept: INLINECODE13 = the maximum number of valid non-overlapping palindromic pieces we can select from just the first INLINECODE14 characters of INLINECODE15 (i.e., INLINECODE16). We build this up left to right: INLINECODE17 is either "same as INLINECODE18" (don't use position INLINECODE19 in any new piece) or "1 + dp[j]" for some INLINECODE20 where INLINECODE21 is a valid piece (ending right at position INLINECODE22, starting at INLINECODE23). Why we need it: This naturally captures "process the string left to right, deciding where to place pieces, while respecting the non-overlapping rule" — a classic 1D DP over string position.

The key greedy trick: shrinking a palindrome from the outside

Concept: If INLINECODE24 is a palindrome of length $\ge k+2$, then peeling off its outermost matching character pair (INLINECODE25 and INLINECODE26, which we know are equal, since it's a palindrome) leaves INLINECODE27, which is also a palindrome, just 2 characters shorter. You can keep peeling like this, layer by layer, like an onion, until you reach a palindrome of length exactly INLINECODE28 or INLINECODE29 (whichever matches the original length's parity), without ever moving the right endpoint INLINECODE30. Why we need it: This means: if any valid (length $\ge k$) palindrome ends at position INLINECODE31, there's also a valid palindrome of length exactly INLINECODE32 or INLINECODE33 ending at that same position INLINECODE34. And using the smaller one is never worse — it uses fewer characters, leaving more room (to the left) for other pieces — so we only ever need to check two fixed lengths (INLINECODE35 and INLINECODE36) when looking for a piece ending at any given position, instead of checking every possible longer length too.

4. Understand the Input

Take Example 1: CODEBLOCK0

Indices: INLINECODE37.

We want to select non-overlapping substrings, each a palindrome, each length $\ge 3$. Candidates include INLINECODE38 (indices 0-2, a palindrome of length 3) and INLINECODE39 (indices 5-8, a palindrome of length 4). We want the maximum count of such pieces we can pick without any shared characters.


5. Understand the Output

Output: INLINECODE40

INLINECODE41 (0-2) and INLINECODE42 (5-8) don't overlap (indices 0-2 vs 5-8, no shared positions) — both are palindromes of length $\ge 3$. Could we possibly fit 3 non-overlapping valid pieces? The string only has 9 characters, and each piece needs at least 3, so at most $\lfloor 9/3 \rfloor = 3$ pieces could theoretically fit — but as the explanation states, no arrangement actually achieves 3 pieces here (there just aren't enough non-overlapping valid palindromes available), so INLINECODE43 is the true maximum.


6. Solve the Example Manually

Let's manually find every palindromic substring of length $\ge 3$ in INLINECODE44, then reason about the best non-overlapping selection.

Scanning for palindromes of length $\ge 3$: INLINECODE45 at indices 0-2 (length 3) — palindrome. INLINECODE46 — length 2, too short, skip (need $\ge 3$). INLINECODE47 at 6-7 — length 2, too short. INLINECODE48 at 5-8 (length 4) — check: INLINECODE49 (ends), inside is INLINECODE50 which is a palindrome $\rightarrow$ yes, INLINECODE51 is a palindrome of length 4. No other length-$\ge 3$ palindromic substrings exist in this particular string.

So our only two usable pieces are INLINECODE52 (0-2) and INLINECODE53 (5-8) — they don't overlap, so we take both: 2 pieces. This matches, and also confirms there's no way to squeeze out a third piece, since there simply isn't a third valid palindrome available anywhere else in the string.


7. Think Like a Programmer

What do I know? For a piece "ending at position INLINECODE54," I need to know if some substring ending there, of length $\ge k$, is a palindrome — and if so, what's the best count achievable by using it. What do I need to find? The overall maximum count, built up as we scan left to right through the string. What can I try? Define INLINECODE55 = best count using only INLINECODE56. For each INLINECODE57, either don't place a piece ending exactly at INLINECODE58 (INLINECODE59), or place one that ends there: for every possible starting point INLINECODE60 such that INLINECODE61 is a valid (length $\ge k$) palindrome, try INLINECODE62. What happens if I try every possibility (every INLINECODE63) for every INLINECODE64? That's $O(n)$ choices of INLINECODE65 for each of $O(n)$ positions INLINECODE66, giving $O(n^2)$ dp transitions — each needing an $O(1)$ palindrome lookup (from our precomputed table) — so $O(n^2)$ total, which for $n=2000$ is $4,000,000$ operations — perfectly fast!

Actually, thanks to the "shrink to length k or k+1" insight, we only ever need to check two specific candidate pieces ending at position INLINECODE67: the one of length exactly INLINECODE68, and the one of length exactly INLINECODE69. If either is a valid palindrome, using it is at least as good as using any longer palindrome ending at the same spot (since a longer one could always be shrunk down to one of these two without losing validity, and shrinking only ever frees up more room for other pieces, never using it up).

What information should I remember? The INLINECODE70 table (built once, $O(n^2)$), and the INLINECODE71 array ($O(n)$), built left to right. What pattern do I notice? This is a "DP with a greedy-pruned transition" — instead of trying all possible previous cut points, we prove that only two specific lengths ever need checking, cutting the DP transition down to $O(1)$ per position instead of $O(n)$.


8. Start With the Brute Force Solution

Brute force idea: Precompute the INLINECODE72 table ($O(n^2)$), then run the DP checking every possible starting point INLINECODE73 for every ending point INLINECODE74 (not just the two "shrunk" lengths).

CODEBLOCK1

Why it works: It directly implements the DP recurrence from Section 7, trying every legitimate placement.

Time complexity: Building INLINECODE75 is $O(n^2)$. The DP itself, trying every INLINECODE76 for every INLINECODE77, is also $O(n^2)$ (for each of the INLINECODE78 values of INLINECODE79, up to INLINECODE80 values of INLINECODE81). Total: $O(n^2)$.


9. Explain the Brute Force Code Line by Line

The INLINECODE82 construction loop — builds up palindrome-checking results by increasing length, so that whenever we need INLINECODE83 (a shorter substring) to determine INLINECODE84, it's already been computed in an earlier iteration of the outer INLINECODE85 loop. INLINECODE86 — a single character is trivially a palindrome. INLINECODE87 — a 2-character substring is a palindrome exactly when both characters match. INLINECODE88 — for longer substrings, it's a palindrome exactly when the outer characters match AND the inside (already computed, since it's shorter) is a palindrome. INLINECODE89 — baseline: whatever the best count was using one fewer character, we can always match that (simply don't place anything new ending exactly here). The inner INLINECODE90 loop — tries every starting position INLINECODE91 such that the resulting substring INLINECODE92 has length at least INLINECODE93 (INLINECODE94, rearranged to INLINECODE95). INLINECODE96 — only consider this INLINECODE97 if the substring is actually a palindrome. INLINECODE98 — if valid, this represents "place a piece from INLINECODE99 to INLINECODE100, plus whatever the best count was using everything strictly before INLINECODE101."


10. Why Do We Still Want the Sharper Version?

Even though $O(n^2)$ is already fast enough here, checking every INLINECODE102 per INLINECODE103 is doing more work than strictly necessary — most of those INLINECODE104 checks either fail the palindrome test or, even when they succeed, would never actually be the optimal choice (since a longer valid palindrome ending at INLINECODE105 can always be replaced by a shorter one ending at the same spot without hurting the final answer). Understanding why only two lengths ever need checking is the real "hard problem" insight here, and turns the DP transition into a clean $O(1)$ step.


11. Find the Better Approach

"Do we really need to check every possible starting point INLINECODE106?"

No — using the "shrinking" insight from Section 3:

CODEBLOCK2


⭐ Key Insight

Before the insight

It seems like we need to consider every possible palindrome length ending at each position, to be sure we're not missing a better option.

The problem

Checking every length is more work than needed, and it obscures a beautiful structural fact about palindromes.

The insight

If INLINECODE107 is a palindrome with length $\ge k+2$, then INLINECODE108 is automatically also a palindrome (2 shorter), still ending at the exact same right endpoint INLINECODE109. Repeating this peeling process, any valid (length $\ge k$) palindrome ending at INLINECODE110 can always be shrunk down — still ending at INLINECODE111 — to a palindrome of length exactly INLINECODE112 (if the original length has the same parity as INLINECODE113) or INLINECODE114 (if the parities differ by one). Using this shrunk version instead of the original never makes our answer worse — it uses strictly fewer characters, potentially freeing up more room for additional pieces, never less.

After the insight

For every ending position, we only ever need to check two fixed-length candidates (length INLINECODE115 and length INLINECODE116) rather than searching through all possible lengths — collapsing the DP's inner search into a constant number of checks.

13. Dry Run the Optimized Solution

Let's dry-run on Example 1: INLINECODE117, INLINECODE118. INLINECODE119.

We build INLINECODE120 (details as in Section 8), then run the DP with the sharper transition: for each INLINECODE121, check only the length-INLINECODE122(3) piece ending at INLINECODE123, and the length-INLINECODE124(4) piece ending at INLINECODE125.

iPosition i-1 (char)Length-3 piece ending herePalindrome?Length-4 piece ending herePalindrome?dp[i]
0dp[0]=0
1a(0)(too short)(too short)dp[1]=0
2b(1)(too short)(too short)dp[2]=0
3a(2)s[0..2]="aba"Yes! dp[3]=max(dp[2], 1+dp[0])=1(too short)dp[3]=1
4c(3)s[1..3]="bac"Nos[0..3]="abac"Nodp[4]=dp[3]=1
5c(4)s[2..4]="acc"Nos[1..4]="bacc"Nodp[5]=dp[4]=1
6d(5)s[3..5]="ccd"Nos[2..5]="accd"Nodp[6]=dp[5]=1
7b(6)s[4..6]="cdb"Nos[3..6]="ccdb"Nodp[7]=dp[6]=1
8b(7)s[5..7]="dbb"Nos[4..7]="cdbb"Nodp[8]=dp[7]=1
9d(8)s[6..8]="bbd"Nos[5..8]="dbbd"Yes! dp[9]=max(dp[8], 1+dp[5])=2dp[9]=2
Final: INLINECODE126. ✔️ matches!

14. Optimized Code

CODEBLOCK3

CODEBLOCK4


15. Explain Optimized Code Line by Line

The INLINECODE127 table construction — identical to Section 9's explanation. INLINECODE128INLINECODE129 = max valid pieces achievable using only INLINECODE130. INLINECODE131 (empty prefix, no pieces possible), left at its default. INLINECODE132 — baseline: skip using position INLINECODE133 in any new piece. INLINECODE134 — check the length-INLINECODE135 candidate ending at INLINECODE136 (starting at INLINECODE137); the INLINECODE138 guard ensures this starting index is valid (non-negative). INLINECODE139 — if that candidate is a valid palindrome, consider using it: 1 new piece, plus the best achievable using everything strictly before it. INLINECODE140 — check the length-INLINECODE141 candidate similarly. INLINECODE142 — same update logic for this second candidate. INLINECODE143 — the final answer, using the entire string.

Why checking just these two lengths suffices: by the Key Insight, any valid (length $\ge k$) palindrome ending at INLINECODE144 can be shrunk (same ending point) to length INLINECODE145 or INLINECODE146, and using that shrunk version is never worse for the DP's purposes — so the maximum achievable by "placing some valid piece ending at i-1" is always captured by checking just these two minimal lengths.


16. Test With Multiple Examples

Example 1 — Normal Case

INLINECODE147 $\rightarrow$ dry-ran fully in Section 13 $\rightarrow$ Output: INLINECODE148 ✔️

Example 2 — Different Case

INLINECODE149. Checking every position for length-2 and length-3 palindromic pieces ending there: none of the possible 2-character or 3-character windows in INLINECODE150 turn out to be palindromes. So INLINECODE151 throughout, and the final INLINECODE152. Output: INLINECODE153 ✔️

17. Edge Cases

No valid palindrome exists anywhere (Example 2) $\rightarrow$ INLINECODE154 never increases, final answer INLINECODE155. INLINECODE156 equal to INLINECODE157 $\rightarrow$ only the entire string itself could ever qualify (if it's a palindrome); answer is INLINECODE158 or INLINECODE159 accordingly. INLINECODE160 $\rightarrow$ every single character (length-1 substring) is trivially a palindrome, so at minimum, you could always pick every other single character as its own piece — this problem still works correctly, since checking "length k(1)" candidates would find single characters, and "length k+1(2)" candidates would find adjacent-matching-pairs, both valid inputs to the same shrink-based logic. Overlap-avoidance forcing a trade-off $\rightarrow$ correctly handled by the DP's INLINECODE161 comparisons at every step, naturally exploring "use this piece" vs. "don't use it" at every position. INLINECODE162 entirely made of the same repeated character (e.g., INLINECODE163, INLINECODE164) $\rightarrow$ many overlapping candidate palindromes exist; the DP correctly finds the maximum non-overlapping count (which would greedily chop the string into as many length-INLINECODE165 or length-INLINECODE166 pieces as fit, back to back).


18. Time Complexity

CODEBLOCK5

Why: The palindrome table construction inherently requires checking $O(n^2)$ substrings (all possible INLINECODE167 pairs), each in $O(1)$ given the smaller sub-results are already computed. The DP loop itself, thanks to the "only check 2 lengths" insight, does a fixed, small amount of work per position, contributing only $O(n)$. The table construction dominates, giving $O(n^2)$ overall — for $n=2000$, that's $4,000,000$ operations, comfortably fast.


19. Space Complexity

INLINECODE168 is an $n \times n$ boolean table $\rightarrow O(n^2)$ space. INLINECODE169 is $O(n)$. Overall: $O(n^2)$ — for $n=2000$, that's $4,000,000$ booleans (about 4MB) — comfortably within memory limits.


20. Common Mistakes Beginners Make

❌ "I need to check every possible length ending at each position, not just k and k+1." ✅ The shrinking insight proves checking just these two lengths is always sufficient — any longer valid palindrome can be shrunk to one of these two without losing validity or hurting the final answer.

❌ "I should build the palindrome table by length increasing from the OUTSIDE in, rather than shortest-to-longest." ✅ You must process by increasing length, since INLINECODE170 depends on the shorter INLINECODE171 — building longer substrings before their shorter "insides" are ready would reference uncomputed (default INLINECODE172) values, giving wrong answers.

❌ "dp[i] should represent 'best count ending exactly at position i-1', not 'best count using the whole prefix'." ✅ Defining it as "best using the whole prefix s[0..i-1]" is what correctly allows the simple INLINECODE173 baseline (skip this position) — a "must end exactly here" definition would need extra handling to combine with earlier, unused stretches of string.

❌ "The two lengths to check should be INLINECODE174 and INLINECODE175, not INLINECODE176 and INLINECODE177." ✅ We need lengths at least INLINECODE178 — checking INLINECODE179 would violate the minimum-length requirement. We check INLINECODE180 and INLINECODE181 because between these two, one of them always matches the parity of any longer valid palindrome, allowing the full shrink-down argument to apply correctly regardless of whether the "true" palindrome's length is even or odd.


21. How to Recognize This Pattern in Other Problems

CODEBLOCK6

Whenever you see "maximize non-overlapping pieces satisfying some property, each with a minimum length," think prefix DP, and separately ask whether the "some property" (like palindrome-ness) has a structural shrinking argument that limits how many candidate lengths you actually need to check per position.


22. Interview Thinking

CODEBLOCK7


23. Mini Challenge

1. Why does peeling the outer characters off a palindrome (removing INLINECODE182 and INLINECODE183) always leave another valid palindrome behind? 2. If a palindrome has length 7 and INLINECODE184, would we shrink it down to length 3 or length 4? Why? 3. Why is INLINECODE185 always a safe "baseline" option to include in the max, even when a valid piece does exist ending at position INLINECODE186?


Answer to Mini Challenge

1. Because a palindrome's defining property is symmetric: INLINECODE187, INLINECODE188, and so on, inward. Removing the matched outermost pair leaves all the remaining* symmetric pairs intact — the inside was already forced to be a palindrome by the original palindrome's own definition. 2. Length 7, shrinking by 2 at a time (7 $\rightarrow$ 5 $\rightarrow$ 3), reaches length 3 — matching INLINECODE189 exactly, since 7 and 3 have the same parity (both odd). We'd never need to reach for length $k+1=4$ in this specific case. 3. Because using a piece ending at INLINECODE190 is only beneficial if it actually improves the count — sometimes skipping is just as good or better. Including INLINECODE191 as a baseline ensures we never accidentally force a piece placement that turns out to be suboptimal; INLINECODE192 naturally picks whichever option is actually best.


24. Final Revision

🧠 Problem in One Sentence

Find the maximum number of non-overlapping, length-$\ge k$ palindromic substrings selectable from INLINECODE193.

🔑 Main Idea

Any valid (length $\ge k$) palindrome ending at some position can be shrunk, same ending point, down to length exactly INLINECODE194 or INLINECODE195 — so a prefix DP only ever needs to check these two fixed lengths at each position, not every possible length.

⚙️ Algorithm

1. Build INLINECODE196 via $O(n^2)$ DP over increasing substring length. 2. INLINECODE197 = best count using INLINECODE198; base INLINECODE199. 3. If INLINECODE200 is a palindrome, try INLINECODE201. 4. If INLINECODE202 is a palindrome, try INLINECODE203. 5. Return INLINECODE204.

⏱️ Complexity

Time: $O(n^2)$ Space: $O(n^2)$

🎯 Pattern to Remember

"Max non-overlapping pieces, min length, DP over prefix" combined with a structural "shrink to a minimal representative" argument that limits transition checks to a constant number of candidate lengths.

25. Beginner Quiz

1. (Understanding) Why must selected substrings not overlap, and what does "overlap" mean precisely here? 2. (Basic concept) What does INLINECODE205 represent, and why must it be built in order of increasing length? 3. (Logic) Why does checking only lengths INLINECODE206 and INLINECODE207 at each ending position suffice, instead of checking every length from INLINECODE208 up to the maximum possible? 4. (Dry run) For INLINECODE209, INLINECODE210, build the relevant palindrome checks ending at each position and compute the final INLINECODE211 array. 5. (Complexity/pattern) Why is this problem's time complexity $O(n^2)$ rather than $O(n^3)$ or worse, and what specific structural property of palindromes (from the Key Insight) is what enables collapsing the DP transition down to $O(1)$ per position?