LeetCode-Problems / 0279. Perfect Squares / 0279. Perfect Squares.py
0279. Perfect Squares.py
Raw
# 11-21-2022 LeetCode 279. Perfect Squares
# https://leetcode.com/problems/perfect-squares/

# Interesting. A compositions thing. Noting first that
# n is max 1000, so thats only a handful of perfect squares
# to search from. Hell, we could even precompute the sums
# of x copies of the ith square through 10000 directly, so
# there is less multiplication.

# This point I am a little stuck. Thats... a lot of combinations
# we could run through.

# Oh, note that there is ALWAYS a combination of n copies of 1**2,
# which is just n. So much so that we can remove it from the list?
# This ALSO means ANY perfect square less than n CAN be included in
# some sum, as we can always fill out the remainder with at least 1's.

# Ok. Given this, can we just start with the largest square below n,
# subtract it and repeat to get our starting point. Its almost like leaving
# behind j pointers...

# hmm, the biggest gulf between numbers is 61 (between 900-961). We can
# quickly reduce these gaps by precomputing some inbetweeners. For instance
# the gap between 841-900 is bisected by 441*2 = 882, cutting the gap
# from 59 down to 41.

# ONCE AGAIN I've had to look this up. Getting pretty frustrated.
# This is a DP question, and I started to build a chart to try to figure it out
# Its very akin to the change making problem, noting if we build UP from a
# base case of zero, and check the current index of our dp array, we can
# either add a ONE to the last result, or if a "larger" coin would fit,
# add one to the value for the current index minus this larger "coin". If
# TWO coppies of said larger coin could be used to get closer, then the
# current index will be somewhat after the index of the sum OF the two coins,
# which would be one higher than the value of the single coin...
# It IS straightforwad DP, but tricky to note what the base case is. We need
# to check the current index value agains ALL "coins" that are equal to or
# less than the current value/index

# There are some AWESOME better answers after this, including math based theories
# showing its NEVER more than 4, and there are formulas that can kind of tell you
# if its 3, assuming its not 1 or two. 1 is easy. Is it Two? No way to know but
# to check.

# That being said, I'd never be able to remember or explain those on the fly.
# Better to try for the DP solution. Having seen it, Ill now just attempt to
# recreate it blind as a basic reinforcement challenge. I was MAYBE getting
# somewhere with noting we could just subtract the last largest square and use
# a precomputed table for the sum of composites for THAT number. I just "forgot"
# that you must do this starting from the bottom. I then DID exactly that, by
# listing in excel the composites, but I didnt note the similarity to the
# coin proiblem and missed the fundamental step of what we were comparing.


class Solution:
    def numSquares(self, n: int) -> int:
        # list of square numbers that are less than `n`
        square_nums = [i * i for i in range(1, int(n**0.5) + 1)]

        level = 0
        queue = {n}
        while queue:
            level += 1
            #! Important: use set() instead of list() to eliminate the redundancy,
            # which would even provide a 5-times speedup, 200ms vs. 1000ms.
            next_queue = set()
            # construct the queue for the next level
            for remainder in queue:
                for square_num in square_nums:
                    if remainder == square_num:
                        return level  # find the node!
                    elif remainder < square_num:
                        break
                    else:
                        next_queue.add(remainder - square_num)
            queue = next_queue
        return level