-
Notifications
You must be signed in to change notification settings - Fork 2
feat(math, perfect squares): least number pefect squares that sum to n #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
5 changes: 4 additions & 1 deletion
5
algorithms/search/binary_search/divide_chocolate/test_divide_chocolate.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # Perfect Squares | ||
|
|
||
| Given an integer, n, return the least number of perfect square numbers that sum to n. | ||
|
|
||
| > A perfect square is an integer that is the square of an integer. In other words, it is an integer that is the result of | ||
| > multiplying a whole integer by itself. For example, 1, 4, 9 and 16 are perfect squares, but 3, 5, and 11 are not. | ||
|
|
||
| ## Constraints | ||
|
|
||
| - 1 <= `n` < 10^3 | ||
|
|
||
| ## Solution | ||
|
|
||
| One of the first solutions that comes to mind is to keep subtracting perfect squares (like 9, 16, 25, …) until we reach | ||
| zero, counting how many times it takes. But that greedy idea doesn’t always find the smallest number of squares. For | ||
| example, trying the biggest square each time may miss a better combination. Instead, this problem has a clever | ||
| mathematical shortcut that utilizes some deep results from number theory, specifically the Four-Square theorem and the | ||
| Three-Square theorem. | ||
|
|
||
| The Four-Square theorem says every number can be written as the sum of at most four perfect squares. So, the answer will | ||
| always be one of 1, 2, 3, or 4. The Three-Square theorem tells us that some numbers can’t be expressed as the sum of | ||
| three squares, and these are exactly the numbers that look like 4^a(8b+7) That means, if a number (after dividing out | ||
| all factors of 4) is of the form 8b+7, then it needs four squares. Using these ideas, we can build a simple check-and-decide | ||
| algorithm instead of trying all combinations. First, we remove all factors of 4 from the number, because multiplying or | ||
| dividing by 4 doesn’t change how many squares are needed; it just scales them. Then, we check the remainder when divided | ||
| by 8. If it’s 7, the number must have four squares. Otherwise, we check if it’s already a perfect square (then the answer | ||
| is 1). If not, we test if it can be written as the sum of two perfect squares (then the answer is 2). If none of those | ||
| conditions are true, we know from the theorems that it must be 3. So, rather than testing every combination, this | ||
| approach uses mathematical reasoning to narrow the answer step by step, making it very fast and elegant. | ||
|
|
||
| Let’s look at the algorithm steps: | ||
|
|
||
| - Keep dividing n by 4 while it is divisible by 4. This simplifies the number without changing the answer. If a number | ||
| is built from perfect squares, then four times that number is built from the same squares, just doubled. So, dividing | ||
| by 4 doesn’t affect how many squares we need; it only makes the number smaller to work with. | ||
| - If the reduced number has a remainder of 7 when divided by 8 (n % 8 == 7), return 4 immediately, because it must need | ||
| four squares. | ||
| - Check if n is a perfect square itself. If yes, return 1. | ||
| - Try to write n as a sum of two perfect squares. Iterate over all possible i from 1 to √n, and check if n - i² is also | ||
| a perfect square. If such a pair exists, return 2. | ||
| - If none of the above conditions are true, return 3. By elimination, the number can be expressed as the sum of three | ||
| squares. | ||
|
|
||
| ### Time Complexity | ||
|
|
||
| We check if the number can be decomposed into the sum of two squares, which takes O(sqrt(n)) iterations. In the remaining | ||
| cases, we perform the check in constant time. | ||
|
|
||
| ### Space Complexity | ||
|
|
||
| The algorithm consumes a constant space, regardless of the size of the input number, so O(1). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,87 @@ | ||
| from math import sqrt | ||
|
|
||
|
|
||
| # function to check if a number is a perfect square | ||
| def is_square(n): | ||
| def is_square(n: int) -> bool: | ||
| """ | ||
| Checks if a number is a perfect square. | ||
| Args: | ||
| n (int): The number to check. | ||
| Returns: | ||
| bool: True if n is a perfect square, False otherwise. | ||
| """ | ||
| if n < 0: | ||
| return False | ||
| else: | ||
| return sqrt(n).is_integer() | ||
|
|
||
|
|
||
| def is_perfect_square(n: int) -> bool: | ||
| """ | ||
| Checks if a number is a perfect square. | ||
| Args: | ||
| n (int): The number to check. | ||
| Returns: | ||
| bool: True if n is a perfect square, False otherwise. | ||
| """ | ||
| if n < 0: | ||
| return False | ||
| sqrt_num = int(sqrt(n)) | ||
| return sqrt_num * sqrt_num == n | ||
|
|
||
|
|
||
| def num_squares(n: int) -> int: | ||
| """ | ||
| Finds the least number of perfect square numbers that sum to n. | ||
| Args: | ||
| n (int): The target number to find the least number of perfect square numbers that sum to it. | ||
| Returns: | ||
| int: The least number of perfect square numbers that sum to n. | ||
| """ | ||
| if n < 0: | ||
| raise ValueError("n must be non-negative") | ||
| if n == 0: | ||
| return 0 | ||
|
|
||
| dp = [float("inf")] * (n + 1) | ||
| dp[0] = 0 | ||
|
|
||
| for i in range(1, n + 1): | ||
| j = 1 | ||
| while j * j <= i: | ||
| dp[i] = min(dp[i], dp[i - j * j] + 1) | ||
| j += 1 | ||
|
|
||
| return dp[n] | ||
BrianLusina marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| def num_squares_2(n: int) -> int: | ||
| """ | ||
| Finds the least number of perfect square numbers that sum to n. | ||
| Args: | ||
| n (int): The target number to find the least number of perfect square numbers that sum to it. | ||
| Returns: | ||
| int: The least number of perfect square numbers that sum to n. | ||
| """ | ||
| if n < 0: | ||
| raise ValueError("n must be non-negative") | ||
| if n == 0: | ||
| return 0 | ||
|
|
||
| # Apply reduction by removing factors of 4 | ||
| while n % 4 == 0: | ||
| n = n // 4 | ||
|
|
||
| # Check if n is of form (8k + 7) | ||
| if n % 8 == 7: | ||
| return 4 | ||
|
|
||
| # Check if n itself is a perfect square | ||
| if is_perfect_square(n): | ||
| return 1 | ||
|
|
||
| # Check if n is the sum of two perfect squares | ||
| for value in range(1, int(sqrt(n)) + 1): | ||
| if is_perfect_square(n - value * value): | ||
| return 2 | ||
|
|
||
| return 3 | ||
BrianLusina marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import unittest | ||
| from parameterized import parameterized | ||
| from pymath.perfect_square import num_squares, num_squares_2 | ||
|
|
||
|
|
||
| TEST_CASES = [ | ||
| (1, 1), | ||
| (12, 3), | ||
| (13, 2), | ||
| (23, 4), | ||
| (997, 2), | ||
| ] | ||
|
|
||
|
|
||
| class NumOfPerfectSquaresTestCases(unittest.TestCase): | ||
| @parameterized.expand(TEST_CASES) | ||
| def test_num_of_perfect_squares(self, n: int, expected: int): | ||
| actual = num_squares(n) | ||
| (self.assertEqual(expected, actual) @ parameterized.expand(TEST_CASES)) | ||
|
|
||
| def test_num_of_perfect_squares_2(self, n: int, expected: int): | ||
| actual = num_squares_2(n) | ||
| self.assertEqual(expected, actual) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() | ||
BrianLusina marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.