Problem overview
Given a positive number S, find the largest n for which 1*1 + 2*2 + ... + n*n is less than or equal to S. The function is find_largest_number; it takes an i32 and returns an i32.
For S = 35 the sums grow like this: 1, 5, 14, 30, 55. The sum for n = 4 is 30, which fits under 35, and adding 25 for n = 5 gives 55, which doesn't. So the answer is 4.
Approach: keep adding until the next square doesn't fit
The sums only grow as n grows, so the answer is the point where the next addition would cross S. That suggests a simulation: keep a running total, try the next square, and stop just before it would push the total past S.
The number of iterations is the answer itself, and the sum of the first n squares is about n³ / 3, so n is roughly the cube root of 3 * S. The loop stays short even for very large S.
Step by step
- Declare
totalSoFarwithlet mutand start it at 0. Nothing has been added yet, andmutis needed because the value changes. - Declare
numberwithlet mutand start it at 1, the first square to add. - Write the
whilecondition. It asks whether addingnumber * numberto the total still leaves a value at mostS. Looking one square ahead means the total itself never exceedsS. - Add the square to
totalSoFarwith+=inside the loop. - Increase
numberby 1. Rust has no++operator, so usenumber += 1. Without this line the condition would never change and the loop would never end. - Return
number - 1. When the loop stops,numberhas already moved on to the first square that did not fit, so the answer is the number before it.
Tracing S = 35: the loop adds 1, 4, 9 and 16 and number becomes 5. The check at 5 computes 30 + 25 = 55, which is more than 35, so the loop ends and the function returns 4.
The solution in Rust
impl Solution {
fn find_largest_number(S: i32) -> i32 {
let mut totalSoFar = 0;
let mut number = 1;
while totalSoFar + number * number <= S {
totalSoFar += number * number;
number += 1;
}
return number - 1;
}
}
Rust details worth knowing:
- Inferred types. Neither variable has a type annotation. Both are compared with or added to
S, which is ani32, so the compiler makes themi32as well. - Naming. The names
SandtotalSoFarcome from the lesson. Rust style prefers snake case such assandtotal_so_far, and the compiler prints a warning about it. The code still builds and runs. - The loop condition is a plain expression. Unlike C-family languages, Rust takes no parentheses around the
whilecondition, and the condition must be abool; a number is never treated as true or false. - Compound assignment.
+=changes the variable in place, which is why both variables needmut.
Complexity
Time: O(S^(1/3)). Each iteration adds one square, and the loop runs once per term that fits. Since the sum of the first n squares is about n³ / 3, the number of iterations grows with the cube root of S. For S = 1,000,000 the answer is 143, so the loop runs 143 times.
Space: O(1). Two integers, no matter how large S is.
Edge cases and common mistakes
- Exactly on the limit. For
S = 30the answer is 4, because the condition uses<=. With<the function would return 3. - Just under a sum. For
S = 29the answer is 3. - Small values.
S = 1returns 1.S = 0returns 0, since even 1 doesn't fit and the loop never runs. - Checking after adding. A loop that adds the square first and tests afterwards overshoots by one term, so the result would be off by one.
- Returning
numberinstead ofnumber - 1. The counter is always one past the answer when the loop ends. - Overflow with very large
S. The lessons build Rust with optimizations, wherei32arithmetic wraps silently on overflow; a debug build would panic instead. The sum forn = 1860is 2,146,682,110, and the next addition, of 3,463,321, goes past the largesti32, 2,147,483,647. WhenSis at least 2,146,682,110, the wrapped sum becomes a negative number, which passes the test<= S, and the loop keeps going. WithS = 2147000000this version returns 8548 instead of 1860. Doing the arithmetic ini64avoids the problem:
fn find_largest_number(s: i32) -> i32 {
let s = s as i64;
let mut total_so_far: i64 = 0;
let mut number: i64 = 1;
while total_so_far + number * number <= s {
total_so_far += number * number;
number += 1;
}
(number - 1) as i32
}
This version returns 1860 for S = 2147000000 and for S = 2147483647. Inputs of that size are far beyond the lesson's examples, but the same loop on a smaller integer type would run into the problem much sooner.
Testing
S = 35gives4.S = 30gives4.S = 29gives3.S = 55gives5.S = 100gives6.S = 1gives1.
