String Partitioning
By: Shuo Wang
Tags: Python, Leetcode, Programming, Art, Algorithms

How do we go about creating the algorithm for partitioning the string?

I think intuitively, you’ll think that you need to loop through each characters in the string, and that is a good idea.

At each character, say, the first character ‘s’, we’ll think to ourselves: is this the end of the first partition? In order to determine that we’ll have to go through the rest of the string and check if there is another ‘s’. In this case there isn’t another s, so we’ll create our first partition.

Moving on to the next character, ‘t’, we’ll go through the rest of the string again, and find another ‘t’. In this case we’ll actually find two, one at position 9, and another one at position 11. Once we find the ‘t’ with max position, we’ll know that for this second partition, it spans at least until the max position, in this case 11.

Now moving on to ‘r’, it’s at positions 2 and 8, that’s great but we already knew partition 2 goes to at least position 11, so it doesn’t add any new information. Continuing this way, we go through ‘i’, which has a max position of 15, so we need to update partition 2 max position again to 15. Continuing yet again to ’n’ with max position 16, and then ‘g’, which can be found at the end of the string.

At this point we’ll have the two partitions we need, ‘s’ and ‘tringpartitioning’.

Translating it into Python:

def partition(s):



def find_end(c):



for idx, cc in enumerate(reversed(s)):

if cc == c:

return len(s) - idx - 1



parts = []



prev_end = 0

curr_end = 0

# loop through every character and find

# the max position of that character

for idx, c in enumerate(s):



curr_end = max(curr_end, find_end(c))



# if the current character is

# at the end of the current partition

# add the partition to the list of parts

if idx == curr_end:

parts.append(s[prev_end:curr_end + 1])

prev_end = curr_end + 1

curr_end = prev_end



return parts S = 'stringpartitioning'

print(partition(S))

# ... ['s', 'tringpartitioning']

Let’s pause for a bit and think, what is the complexity of this algorithm?

First, we are looping through each character, for a string of length n, that is O(n). Then within each iteration, we are going through every rest of the character to find the max position, that is also O(n) (we are not actually looping through the entire string each time, it’s more like max n-1, n-2, n-3, but it’s still of order n). overall, the complexity would be:

O(n * n) = O(n²)

Can we improve it? You bet, the maximum positions for each character can be found in one iteration and stored for later use, there’s no need to do it for every iteration, here is how:

def partition(s):



# iterate through each character

# and store it's max position

end_map = {}

for idx, c in enumerate(s):

end_map[c] = idx



parts = []



prev_end = 0

curr_end = 0

for idx, c in enumerate(s):

# look up the max position for c: end_map[c]

curr_end = max(curr_end, end_map[c])



if idx == curr_end:

parts.append(s[prev_end:curr_end + 1])

prev_end = curr_end + 1

curr_end = prev_end



return parts S = 'stringpartitioning'

print(partition(S))

# ... ['s', 'tringpartitioning']

In this algorithm, we loop through each character and create a dictionary of max position, assuming dictionary look up and write are constant operations (you can usually do that), then we loop through each character a second time to create the partitions, but since we have already created the dictionary of max positions, we can just look them up, saving time from finding max position of the characters at each iteration.

Now with the above enhancement, the complexity becomes:

O(2 * n) = O(n)

Amazing! But is everything better? Well, let’s see, now that you created a dictionary, it’ll take up space. How much space does it take? if every character were distinct in the string the dictionary would have n entries. so its of order:

memory usage = O(n)

Nothing can be perfect is it? But we already knew.