LeetCode 阿姆斯特朗数(Armstrong Number)判定:三种数位长度 k 的计算方案与多语言实现
【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode
本篇文章围绕 LeetCode 1134 号题目 Armstrong Number(阿姆斯特朗数)展开,完整讲解"数位提取 + 按位求幂求和"的核心思路,并给出字符串转换、对数运算、纯算术循环三种计算数位长度k的实现方案。文中代码覆盖 Python、Java、C++、JavaScript、C#、Go、Kotlin、Swift、Rust 九种语言,全部继承自本仓库的 armstrong-number.md 技术文档,读者读完后既能透彻理解阿姆斯特朗数的判定原理,也能直接对照任意主流语言落地可运行的isArmstrong实现。
问题定义与数学背景
阿姆斯特朗数(又称水仙花数推广)的定义是:一个整数等于其每一位数字分别按"该数总位数"作为指数求幂后再求和的结果。形式化表达为:
设正整数
n有k位数字,且十进制表示为d₁d₂...dₖ,若d₁^k + d₂^k + ... + dₖ^k = n,则n是阿姆斯特朗数。
典型例子:
153:3 位数,1³ + 5³ + 3³ = 1 + 125 + 27 = 153,成立。1634:4 位数,1⁴ + 6⁴ + 3⁴ + 4⁴ = 1 + 1296 + 81 + 256 = 1634,成立。
注意指数必须等于该数自身的位数,而不是某个固定常数(后文"常见陷阱"会专门强调这一点)。整个判定流程可以拆解为三步:统计位数 k → 逐位提取数字 → 累加 digit^k 并与原数比较。三种解法唯一的分歧点只在于"如何得到 k"。
本仓库将本题的完整讲解收录于 articles/armstrong-number.md,与 duplicate-integer.md 等文章同属articles/目录,遵循 articles/README.md 中"至少提供一种与 NeetCode 视频一致的多语言解法、给出时间与空间复杂度、尽量覆盖全部相关解法"的写作规范。仓库 README.md 中列出的 Python、Java、JavaScript、C++、Go、Swift、C#、TypeScript、Rust、Kotlin、Ruby、C、Scala、Dart 多语言体系,也正是本文三套方案所覆盖的代码生态。
前置知识(Prerequisites)
原文档要求读者在动手前掌握三项基础技能:
- 数位提取(Digit Extraction):利用
n % 10取出最低位、n // 10去掉最低位,反复迭代即可逐一拿到每一位数字,这是所有方案共用的核心操作。 - 幂运算(Exponentiation):需要能够计算
digit^k。各语言实现分别借助**(Python)、Math.pow(Java/Kotlin/C#)、pow(C++)、Math.pow(JavaScript)、pow(Rust)等内置能力,或用循环连乘自行实现。 - 基础数学(Basic Math):理解数字的十进制性质与位数统计方法,包括"除以 10 缩位"的计数思想,以及"
floor(log10(n)) + 1等于正整数的位数"这一对数恒等式。
方法一:通过字符串转换计算 k
直觉(Intuition)
最直观的做法是先把数字转成字符串:字符串的长度天然就是位数k,随后再按"取模取位、整除缩位"的方式逐个提取数字,累加digit^k。之所以先求k再求和,是因为 Armstrong 数的指数依赖于位数——例如153用 3 次方、1634用 4 次方,二者不能混用。
算法步骤
- 将
n转为字符串并取其长度,得到k。 - 循环执行
n % 10提取最低位数字,随后n //= 10去掉该位,直到n变为 0。 - 对每一位数字累加
digit^k到总和result。 - 若
result == n(比较时使用最初保存的原值),返回true,否则返回false。
多语言实现
class Solution: def isArmstrong(self, n: int) -> bool: def getSumOfKthPowerOfDigits(num, k): result = 0 while num != 0: result += (num % 10) ** k num //= 10 return result length = len(str(n)) return getSumOfKthPowerOfDigits(n, length) == nclass Solution { public int getSumOfKthPowerOfDigits(int n, int k) { int result = 0; while (n != 0) { result += Math.pow(n % 10, k); n /= 10; } return result; } public boolean isArmstrong(int n) { int length = String.valueOf(n).length(); return getSumOfKthPowerOfDigits(n, length) == n; } }class Solution { public: int getSumOfKthPowerOfDigits(int n, int k) { int result = 0; while (n != 0) { result += pow(n % 10, k); n /= 10; } return result; } bool isArmstrong(int n) { int length = to_string(n).length(); return getSumOfKthPowerOfDigits(n, length) == n; } };class Solution { /** * @param {number} n * @return {boolean} */ isArmstrong(n) { const getSumOfKthPowerOfDigits = (num, k) => { let result = 0; while (num !== 0) { result += Math.pow(num % 10, k); num = Math.floor(num / 10); } return result; }; const length = String(n).length; return getSumOfKthPowerOfDigits(n, length) === n; } }public class Solution { private int GetSumOfKthPowerOfDigits(int n, int k) { int result = 0; while (n != 0) { result += (int)Math.Pow(n % 10, k); n /= 10; } return result; } public bool IsArmstrong(int n) { int length = n.ToString().Length; return GetSumOfKthPowerOfDigits(n, length) == n; } }func isArmstrong(n int) bool { getSumOfKthPowerOfDigits := func(num, k int) int { result := 0 for num != 0 { digit := num % 10 power := 1 for i := 0; i < k; i++ { power *= digit } result += power num /= 10 } return result } length := len(fmt.Sprintf("%d", n)) return getSumOfKthPowerOfDigits(n, length) == n }class Solution { private fun getSumOfKthPowerOfDigits(n: Int, k: Int): Int { var num = n var result = 0 while (num != 0) { result += Math.pow((num % 10).toDouble(), k.toDouble()).toInt() num /= 10 } return result } fun isArmstrong(n: Int): Boolean { val length = n.toString().length return getSumOfKthPowerOfDigits(n, length) == n } }class Solution { func isArmstrong(_ n: Int) -> Bool { func getSumOfKthPowerOfDigits(_ num: Int, _ k: Int) -> Int { var num = num var result = 0 while num != 0 { var power = 1 for _ in 0..<k { power *= num % 10 } result += power num /= 10 } return result } let length = String(n).count return getSumOfKthPowerOfDigits(n, length) == n } }impl Solution { pub fn is_armstrong(n: i32) -> bool { fn get_sum_of_kth_power_of_digits(mut num: i32, k: u32) -> i32 { let mut result = 0; while num != 0 { result += (num % 10).pow(k); num /= 10; } result } let length = n.to_string().len() as u32; get_sum_of_kth_power_of_digits(n, length) == n } }复杂度分析
- 时间复杂度:O(M),其中
M是整数n的位数。字符串转换与逐位循环都只需要线性扫描一遍数字。 - 空间复杂度:O(1),除极少量中间变量外不依赖额外空间(各语言内部字符串/格式化开销不计入算法空间)。
方法二:通过对数运算计算 k
直觉(Intuition)
不借助字符串,改用对数恒等式:正整数n的位数等于floor(log10(n)) + 1。例如log10(153) ≈ 2.184,向下取整得 2,再加 1 得到位数 3。这样做可以省去字符串分配的开销,在部分语言中更轻量。
算法步骤
- 计算
k = floor(log10(n)) + 1得到位数。 - 用取模与整除循环提取每一位数字。
- 累加每一位的
digit^k。 - 若总和等于
n则返回true。
多语言实现
class Solution: def isArmstrong(self, n: int) -> bool: def getSumOfKthPowerOfDigits(num, k): result = 0 while num != 0: result += (num % 10) ** k num //= 10 return result length = int(math.log10(n)) + 1 return getSumOfKthPowerOfDigits(n, length) == nclass Solution { public int getSumOfKthPowerOfDigits(int n, int k) { int result = 0; while (n != 0) { result += Math.pow(n % 10, k); n /= 10; } return result; } public boolean isArmstrong(int n) { int length = (int) Math.log10(n) + 1; return getSumOfKthPowerOfDigits(n, length) == n; } }class Solution { public: int getSumOfKthPowerOfDigits(int n, int k) { int result = 0; while (n != 0) { result += pow(n % 10, k); n /= 10; } return result; } bool isArmstrong(int n) { int length = log10(n) + 1; return getSumOfKthPowerOfDigits(n, length) == n; } };class Solution { /** * @param {number} n * @return {boolean} */ isArmstrong(n) { const getSumOfKthPowerOfDigits = (num, k) => { let result = 0; while (num !== 0) { result += Math.pow(num % 10, k); num = Math.floor(num / 10); } return result; }; const length = Math.floor(Math.log10(n)) + 1; return getSumOfKthPowerOfDigits(n, length) === n; } }public class Solution { private int GetSumOfKthPowerOfDigits(int n, int k) { int result = 0; while (n != 0) { result += (int)Math.Pow(n % 10, k); n /= 10; } return result; } public bool IsArmstrong(int n) { int length = (int)Math.Log10(n) + 1; return GetSumOfKthPowerOfDigits(n, length) == n; } }func isArmstrong(n int) bool { getSumOfKthPowerOfDigits := func(num, k int) int { result := 0 for num != 0 { digit := num % 10 power := 1 for i := 0; i < k; i++ { power *= digit } result += power num /= 10 } return result } length := int(math.Log10(float64(n))) + 1 return getSumOfKthPowerOfDigits(n, length) == n }class Solution { private fun getSumOfKthPowerOfDigits(n: Int, k: Int): Int { var num = n var result = 0 while (num != 0) { result += Math.pow((num % 10).toDouble(), k.toDouble()).toInt() num /= 10 } return result } fun isArmstrong(n: Int): Boolean { val length = (Math.log10(n.toDouble()) + 1).toInt() return getSumOfKthPowerOfDigits(n, length) == n } }class Solution { func isArmstrong(_ n: Int) -> Bool { func getSumOfKthPowerOfDigits(_ num: Int, _ k: Int) -> Int { var num = num var result = 0 while num != 0 { var power = 1 for _ in 0..<k { power *= num % 10 } result += power num /= 10 } return result } let length = Int(log10(Double(n))) + 1 return getSumOfKthPowerOfDigits(n, length) == n } }impl Solution { pub fn is_armstrong(n: i32) -> bool { fn get_sum_of_kth_power_of_digits(mut num: i32, k: u32) -> i32 { let mut result = 0; while num != 0 { result += (num % 10).pow(k); num /= 10; } result } let length = (n as f64).log10() as u32 + 1; get_sum_of_kth_power_of_digits(n, length) == n } }复杂度分析
- 时间复杂度:O(M),其中
M为位数。对数计算是 O(1) 的数学函数调用,真正耗时的是位数为M的逐位循环。 - 空间复杂度:O(1),无需字符串缓冲区。
使用对数方案时需注意浮点精度:
log10返回浮点数,必须向下取整后加 1。对本题目给定的正整数输入范围,该恒等式是精确的。
方法三:不使用任何内置方法计算 k
直觉(Intuition)
如果环境不允许调用字符串、对数等内置函数(例如某些嵌入式或面试限定场景),可以只用基础算术统计位数:反复把数字除以 10 并计数,直到数字变为 0,循环次数就是位数k。这一方案不依赖任何语言库,可移植性最强。
算法步骤
- 将
n复制到临时变量tempN。 - 循环
tempN /= 10并计数length++,直到tempN为 0,得到位数k。 - 从原始数
n中逐位提取数字,累加digit^k。 - 若总和等于
n返回true。
多语言实现
class Solution: def isArmstrong(self, n: int) -> bool: def getSumOfKthPowerOfDigits(num, k): result = 0 while num != 0: result += (num % 10) ** k num //= 10 return result length = 0 temp_n = n while temp_n != 0: length += 1 temp_n //= 10 return getSumOfKthPowerOfDigits(n, length) == nclass Solution { public int getSumOfKthPowerOfDigits(int n, int k) { int result = 0; while(n != 0) { result += Math.pow(n % 10, k); n /= 10; } return result; } public boolean isArmstrong(int n) { int length = 0; int tempN = n; while (tempN != 0) { length++; tempN /= 10; } return getSumOfKthPowerOfDigits(n, length) == n; } }class Solution { public: int getSumOfKthPowerOfDigits(int n, int k) { int result = 0; while(n != 0) { result += pow(n % 10, k); n /= 10; } return result; } bool isArmstrong(int n) { int length = 0; int tempN = n; while (tempN) { length++; tempN /= 10; } return getSumOfKthPowerOfDigits(n, length) == n; } };class Solution { /** * @param {number} n * @return {boolean} */ isArmstrong(n) { const getSumOfKthPowerOfDigits = (num, k) => { let result = 0; while (num !== 0) { result += Math.pow(num % 10, k); num = Math.floor(num / 10); } return result; }; let length = 0; let tempN = n; while (tempN !== 0) { length++; tempN = Math.floor(tempN / 10); } return getSumOfKthPowerOfDigits(n, length) === n; } }public class Solution { private int GetSumOfKthPowerOfDigits(int n, int k) { int result = 0; while (n != 0) { result += (int)Math.Pow(n % 10, k); n /= 10; } return result; } public bool IsArmstrong(int n) { int length = 0; int tempN = n; while (tempN != 0) { length++; tempN /= 10; } return GetSumOfKthPowerOfDigits(n, length) == n; } }func isArmstrong(n int) bool { getSumOfKthPowerOfDigits := func(num, k int) int { result := 0 for num != 0 { digit := num % 10 power := 1 for i := 0; i < k; i++ { power *= digit } result += power num /= 10 } return result } length := 0 tempN := n for tempN != 0 { length++ tempN /= 10 } return getSumOfKthPowerOfDigits(n, length) == n }class Solution { private fun getSumOfKthPowerOfDigits(n: Int, k: Int): Int { var num = n var result = 0 while (num != 0) { result += Math.pow((num % 10).toDouble(), k.toDouble()).toInt() num /= 10 } return result } fun isArmstrong(n: Int): Boolean { var length = 0 var tempN = n while (tempN != 0) { length++ tempN /= 10 } return getSumOfKthPowerOfDigits(n, length) == n } }class Solution { func isArmstrong(_ n: Int) -> Bool { func getSumOfKthPowerOfDigits(_ num: Int, _ k: Int) -> Int { var num = num var result = 0 while num != 0 { var power = 1 for _ in 0..<k { power *= num % 10 } result += power num /= 10 } return result } var length = 0 var tempN = n while tempN != 0 { length += 1 tempN /= 10 } return getSumOfKthPowerOfDigits(n, length) == n } }impl Solution { pub fn is_armstrong(n: i32) -> bool { fn get_sum_of_kth_power_of_digits(mut num: i32, k: u32) -> i32 { let mut result = 0; while num != 0 { result += (num % 10).pow(k); num /= 10; } result } let mut length = 0u32; let mut temp_n = n; while temp_n != 0 { length += 1; temp_n /= 10; } get_sum_of_kth_power_of_digits(n, length) == n } }复杂度分析
- 时间复杂度:O(M),其中
M为位数。统计位数的循环与求和循环各执行M次,总体仍为线性。 - 空间复杂度:O(1),仅使用几个临时整型变量。
从源码结构看,Go 与 Swift 版本在求幂时没有依赖内置
pow,而是用嵌套循环power *= digit连乘k次实现幂运算,因此这三种解法在所有语言中都不依赖pow/**之外的库函数,进一步印证了"纯算术可移植"的定位。
三种方案横向对比
| 方案 | 求 k 的方式 | 依赖的内置能力 | 适用场景 |
|---|---|---|---|
| 方法一:字符串转换 | str(n)取长度 | 字符串/格式化 API | 代码最直观,适合日常刷题快速实现 |
| 方法二:对数运算 | floor(log10(n)) + 1 | log10数学库 | 无字符串分配,实现更紧凑 |
| 方法三:纯算术循环 | 反复除以 10 计数 | 无(仅基础算术) | 受限环境或追求最大可移植性 |
三种方案共享同一个判定核心——getSumOfKthPowerOfDigits(num, k)子过程,唯一区别仅在位数k的获取方式;三者时间复杂度均为O(M)、空间复杂度均为O(1)(M为位数)。选择哪种方案取决于代码可读性偏好与实际运行环境约束。
常见陷阱(Common Pitfalls)
原文档专门列出两个高频踩坑点,这里逐一展开说明。
陷阱一:把指数写死为固定值
阿姆斯特朗数要求指数等于该数的实际位数。新手最容易把指数硬编码成 3(因为常接触水仙花数153),但这会直接导致错误:1634是 4 位数,必须使用 4 次方;固定用 3 次方会算出1+216+27+64 = 308 ≠ 1634。
# Wrong: always using power 3 result += digit ** 3正确做法是动态计算位数k后再用于幂运算,这也是三种解法都先求k的原因。
陷阱二:在提取数字时破坏了原数
逐位提取的循环会把n不断整除到 0。如果在进入循环前没有保存原始值,循环结束后n已经是 0,再也无法与求和结果比较,判定必然错误。
# Wrong: n is modified and can't be compared later while n != 0: result += (n % 10) ** k n //= 10 return result == n # n is now 0!修复方式:要么像方法三那样先用tempN副本统计位数、再对原始n求和,要么在函数内部先把n复制给局部变量num,保证比较时仍有原始值可用。观察上文九种语言的实现可以发现,所有正确版本都遵守了这一约定(如 Go 版闭包参数num、Rust 版mut num均是对传入值的副本操作)。
总结
阿姆斯特朗数判定是一个"数学定义 → 数位分解 → 幂求和"的标准入门题,核心考点有二:一是理解指数与位数的动态绑定关系,二是在逐位循环中保护好原始值。本文基于 articles/armstrong-number.md 完整呈现了字符串、对数、纯算术三种求位数方案,并提供 Python、Java、C++、JavaScript、C#、Go、Kotlin、Swift、Rust 九个语言的对照实现。这套"同题多解 + 多语言对照"的组织方式与本仓库整体风格一致——仓库在 README.md 中即按 Arrays & Hashing、Two Pointers、Sliding Window 等分类维护数百道题的解法,每篇 articles 都要求给出复杂度分析与覆盖全部相关解法,方便读者跨语言对照、按需取用。
【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考