Problem overview
The function countPairs(n: Int, sum: Int): Int receives two positive integers. It must count the pairs (x, y) with 1 <= x, y <= n whose sum is exactly sum. The order inside a pair doesn't matter: (1, 5) and (5, 1) are the same pair and count once.
In the example, n = 7 and sum = 6. The valid pairs are (1, 5), (2, 4) and (3, 3), so the answer is 3. A pair may use the same number twice, as (3, 3) does.
The statement asks for O(n^2) time and O(1) extra space, which is the cost of trying every pair while keeping only a few variables.
Approach: try every candidate pair
A brute-force solution generates every candidate and tests each one. Here a candidate is a pair of numbers from 1 to n, and the test is x + y == sum.
Taking all ordered pairs would count (1, 5) and (5, 1) separately. To avoid that, the pairs are generated with x <= y. The first number runs from 1 to n, and the second runs from the first number up to n. Each unordered pair then appears exactly once, with its smaller number first. Starting the second number at the first number, not after it, keeps pairs such as (3, 3).
Two nested loops do this. The outer loop picks the first number, the inner loop picks the second, and an if counts the matches.
Step by step
- Create the counter. Declare
resultwithvar, because its value changes, and start it at 0. If no pair matches, 0 is the right answer. - Loop over the first number. Use
for (i in 1..n). The range1..nincludesn, which is what the statement needs becausenis an allowed value. - Loop over the second number. Inside it, use
for (j in i..n). Starting atiis what makes each pair count once. - Test and count. If
i + j == sum, add 1 withresult++. Every pair reaching this test is distinct, so each match adds exactly 1. - Return the count after both loops have finished.
For n = 7 and sum = 6, the pairs that match are (1, 5), (2, 4) and (3, 3). When i is 4 and j is 2, the combination isn't even generated, because j starts at 4.
The solution in Kotlin
class Solution {
fun countPairs(n: Int, sum: Int): Int {
var result = 0
for (i in 1..n) {
for (j in i..n) {
if (i + j == sum) {
result++
}
}
}
return result
}
}
The Kotlin details worth knowing:
varandval. Avalcan't be reassigned. Sinceresult++changes the variable, it must be avar.- Inclusive ranges.
1..nandi..ninclude both ends.1 until nwould stop atn - 1and miss pairs that usen, such as(1, 7). - Empty ranges are safe. If the start is greater than the end, as with
1..0, the loop body never runs. Forn = 0the function returns 0. - The loop variables are fresh each pass. You can't assign to
iorj, and you don't need to. Intis 32 bits. The sumi + jwraps silently past 2147483647. The loops would take far too long before that matters, so it doesn't affect this lesson's sizes.result++as a statement. It adds 1 toresult. Kotlin has no need for a separate statement terminator, so no semicolon follows.
Complexity
Time: O(n^2). For the first number i, the inner loop makes n - i + 1 passes. Adding these for i from 1 to n gives n + (n - 1) + ... + 1 = n(n + 1) / 2 passes, which is of the order of n^2. Each pass does a constant amount of work.
Space: O(1). The function stores result and the two loop variables, no matter how large n is.
The statement asks for exactly this brute force. The condition x + y == sum also means that for each x there is at most one possible y, namely sum - x. With that observation you could drop the inner loop and get O(n). The lesson practises nested loops, so it uses the plain version.
Beyond the brute force
Because y = sum - x is fixed once x is chosen, the answer is the number of values of x that satisfy two conditions. First, x >= 1 and y = sum - x <= n, which means x >= sum - n. Second, x <= y, which means x <= sum / 2 with integer division. So x runs from maxOf(1, sum - n) to sum / 2, and the count is the length of that range, or 0 when it is empty:
fun countPairsFast(n: Int, sum: Int): Int {
val lo = maxOf(1, sum - n)
val hi = sum / 2
return if (hi >= lo) hi - lo + 1 else 0
}
For n = 7 and sum = 6, lo is 1 and hi is 3, which gives 3. For n = 7 and sum = 14, both are 7, which gives 1. For n = 7 and sum = 15, lo is 8 and hi is 7, so the result is 0. It agrees with the nested loops on all the examples below, and it runs in constant time. The lesson asks for the nested loops, though, because they are the pattern you will reuse when no formula exists.
Edge cases and common mistakes
- No pair exists. If
sumis larger than2 * n, as withn = 7andsum = 15, the answer is 0. The same holds forsum = 1, since the smallest possible sum is 2. - Equal numbers. With
n = 5andsum = 2the only pair is(1, 1), and the answer is 1. Starting the inner loop ati + 1would miss it. - The largest sum. With
n = 7andsum = 14, the only pair is(7, 7), so the answer is 1. This one needs the inclusive endnin both loops. - Counting both orders. Starting the inner loop at 1 gives 5 for
n = 7andsum = 6, because it also counts(5, 1),(4, 2)and so on. The correct answer is 3. - Using
until. Excludingnfrom either range loses pairs. - Declaring
resultwithval. The compiler rejectsresult++on a read-only variable.
Testing
n = 7,sum = 6gives3.n = 7,sum = 14gives1.n = 7,sum = 15gives0.n = 5,sum = 2gives1.n = 10,sum = 11gives5.n = 4,sum = 8gives1.