1. 概述
计算自然数之和在数学、算法设计以及实际编程中都有广泛的应用场景。例如求解等差数列、性能测试中的基准计算,甚至在某些业务逻辑中也会遇到类似需求。
本文将系统性地介绍 在 Kotlin 中计算前 N 个自然数之和的多种实现方法,涵盖从传统循环到函数式编程风格的各种写法,帮助你在不同场景下选择最合适的方式。
✅ 推荐集合,这类小问题看似简单,但面试和踩坑时经常出现!
2. 使用循环
循环是最直观的实现方式,适合理解底层逻辑或对性能有明确控制需求的场景。
2.1 for 循环
使用 for
循环遍历从 1
到 n
的每个整数并累加:
fun sumUsingForLoop(n: Int): Int {
var sum = 0
for (i in 1..n) {
sum += i
}
return sum
}
测试验证:
Assertions.assertEquals(15, sumUsingForLoop(5))
Assertions.assertEquals(5050, sumUsingForLoop(100))
✅ 正确无误。代码清晰易懂,是初学者最常采用的方式。
⚠️ 注意:1..n
是 Kotlin 中的闭区间(inclusive range),等价于数学上的 [1, n]
。
2.2 while 循环
如果你更习惯 C 风格的控制结构,可以用 while
实现等效功能:
fun sumUsingWhileLoop(n: Int): Int {
var sum = 0
var i = 1
while (i <= n) {
sum += i
i++
}
return sum
}
测试用例:
Assertions.assertEquals(15, sumUsingWhileLoop(5))
Assertions.assertEquals(5050, sumUsingWhileLoop(100))
✅ 结果一致。虽然语法稍显冗长,但在复杂条件判断中更具灵活性。
3. 使用等差数列公式
前 N 个自然数构成一个首项为 1、公差为 1 的等差数列,可以直接套用数学公式:
$$ \text{sum} = \frac{N(N+1)}{2} $$
这不仅效率最高(时间复杂度 O(1)),而且避免了循环开销,在处理大数值时优势明显。
实现如下:
fun sumUsingArithmeticProgressionFormula(n: Int): Int {
return (n * (n + 1)) / 2
}
测试验证:
Assertions.assertEquals(15, sumUsingArithmeticProgressionFormula(5))
Assertions.assertEquals(5050, sumUsingArithmeticProgressionFormula(100))
✅ 完美匹配。
⚠️ 踩坑提醒:当 n
很大时,n * (n + 1)
可能溢出 Int
范围。若需支持更大范围,建议升级为 Long
:
fun sumUsingArithmeticProgressionFormula(n: Long): Long {
return n * (n + 1) / 2
}
4. 使用 Range 扩展函数
Kotlin 提供了丰富的集合操作扩展函数,结合 1..n
区间可写出极具表达力的函数式代码。
4.1 使用 sum()
最简洁的方式之一,直接调用 sum()
扩展函数:
fun sumUsingRangeAndSum(n: Int): Int {
return (1..n).sum()
}
测试:
Assertions.assertEquals(15, sumUsingRangeAndSum(5))
Assertions.assertEquals(5050, sumUsingRangeAndSum(100))
✅ 一行搞定,推荐日常使用。
4.2 使用 sumBy()
虽然多此一举用于简单求和,但 sumBy
更适用于需要映射后再求和的场景(比如平方和)。这里只是为了展示语法完整性:
fun sumUsingRangeAndSumBy(n: Int): Int {
return (1..n).sumBy { it }
}
测试通过:
Assertions.assertEquals(15, sumUsingRangeAndSumBy(5))
Assertions.assertEquals(5050, sumUsingRangeAndSumBy(100))
✅ 功能正确,但不推荐在此类简单场景使用,略显多余。
4.3 使用 fold()
fold
允许你指定初始值并逐个累积结果,非常适合自定义聚合逻辑:
fun sumUsingRangeAndFold(n: Int): Int {
return (1..n).fold(0) { acc, num -> acc + num }
}
- ✅
acc
初始值为0
- ✅ 每次将当前数字
num
加入累加器
测试:
Assertions.assertEquals(15, sumUsingRangeAndFold(5))
Assertions.assertEquals(5050, sumUsingRangeAndFold(100))
✅ 安全且灵活,适合复杂聚合场景。
4.4 使用 reduce()
与 fold
类似,但 不接受初始值,而是以第一个元素作为起点:
fun sumUsingRangeAndReduce(n: Int): Int {
return (1..n).reduce { acc, num -> acc + num }
}
⚠️ 注意:如果传入 n = 0
,即空区间 (1..0)
,会抛出异常 —— NoSuchElementException
。
因此:
- ❌ 不适用于可能为空的输入
- ✅ 仅建议在确定区间非空时使用
测试(正常情况):
Assertions.assertEquals(15, sumUsingRangeAndReduce(5))
Assertions.assertEquals(5050, sumUsingRangeAndReduce(100))
5. 使用 Sequence
对于大数据流或惰性计算场景,Sequence
是更好的选择,它支持懒加载和链式操作优化。
实现方式如下:
fun sumUsingSequence(n: Int): Int {
return generateSequence(1) { it + 1 }
.take(n)
.sum()
}
- ✅
generateSequence(1) { it + 1 }
生成无限自然数序列 - ✅
.take(n)
截取前n
个 - ✅
.sum()
计算总和
测试:
Assertions.assertEquals(15, sumUsingSequence(5))
Assertions.assertEquals(5050, sumUsingSequence(100))
✅ 成功运行。
📌 适用场景:
- 数据量大且希望节省内存
- 后续还需进行其他中间操作(如过滤、转换)
- 希望延迟执行提升性能
6. 使用递归
递归是一种优雅的函数式解法,体现“分而治之”的思想:
fun sumUsingRecursion(n: Int): Int {
return if (n <= 1) {
n
} else {
n + sumUsingRecursion(n - 1)
}
}
测试:
Assertions.assertEquals(15, sumUsingRecursion(5))
Assertions.assertEquals(5050, sumUsingRecursion(100))
✅ 功能正确。
⚠️ 踩坑警告:
- ❌ 深度递归可能导致
StackOverflowError
(尤其是n > 几千
) - ✅ 若需安全递归,请改用尾递归(tailrec)
优化版(尾递归):
tailrec fun sumUsingTailRecursion(n: Int, acc: Int = 0): Int {
return if (n <= 0) acc else sumUsingTailRecursion(n - 1, acc + n)
}
加上 tailrec
关键字后,编译器会将其优化为循环,避免栈溢出。
7. 总结
我们系统回顾了在 Kotlin 中计算前 N 个自然数之和的 7 种实现方式,各自特点总结如下:
方法 | 时间复杂度 | 是否推荐 | 说明 |
---|---|---|---|
for / while loop |
O(n) | ✅ | 易懂,控制精细 |
等差数列公式 | O(1) | ✅✅✅ | 最高效,首选方案 |
Range.sum() |
O(n) | ✅✅ | 简洁,Kotlin 风格 |
fold / reduce |
O(n) | ✅ | 函数式编程常用 |
Sequence |
O(n) | ✅(大数据) | 惰性求值,节省资源 |
递归 | O(n) | ⚠️ | 注意栈溢出风险 |
尾递归 | O(n) | ✅(函数式偏好者) | 安全递归替代方案 |
🎯 最佳实践建议:
- 日常开发优先使用 公式法 或
Range.sum()
- 大数据流处理考虑
Sequence
- 学习函数式编程可深入研究
fold
和tailrec
所有示例代码已上传至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/kotlin-math
📌 建议动手实践每种写法,并尝试加入边界测试(如 n=0
, n=负数
),这才是真正掌握的关键。