当前位置: 首页 > news >正文

【LeetCode】66. 加一

文章目录

  • 66. 加一
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 示例 3:
    • 提示:
    • 解题思路
      • 问题深度分析
        • 问题本质
        • 核心思想
        • 典型情况分析
        • 算法对比
      • 算法流程图
        • 主算法流程(逆序遍历+进位)
        • 进位传播详细流程
        • 特殊情况处理
      • 复杂度分析
        • 时间复杂度详解
        • 空间复杂度详解
      • 关键优化技巧
        • 技巧1:逆序遍历+原地修改(最优解法)
        • 技巧2:使用carry标志
        • 技巧3:递归实现
        • 技巧4:提前判断优化
      • 边界情况处理
      • 测试用例设计
        • 基础测试
        • 单次进位
        • 连续进位
        • 全部进位
        • 边界情况
      • 常见错误与陷阱
        • 错误1:忘记处理全是9的情况
        • 错误2:数组越界
        • 错误3:修改了原数组但未考虑
      • 实战技巧总结
      • 进阶扩展
        • 扩展1:加任意数(不只是+1)
        • 扩展2:大整数加法(两个数组相加)
        • 扩展3:大整数减法
      • 应用场景
    • 代码实现
    • 测试结果
    • 核心收获
    • 应用拓展
    • 完整题解代码

66. 加一

题目描述

给定一个表示 大整数 的整数数组 digits,其中 digits[i] 是整数的第 i 位数字。这些数字按从左到右,从最高位到最低位排列。这个大整数不包含任何前导 0。

将大整数加 1,并返回结果的数字数组。

示例 1:

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
加 1 后得到 123 + 1 = 124。
因此,结果应该是 [1,2,4]。

示例 2:

输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
加 1 后得到 4321 + 1 = 4322。
因此,结果应该是 [4,3,2,2]。

示例 3:

输入:digits = [9]
输出:[1,0]
解释:输入数组表示数字 9。
加 1 得到了 9 + 1 = 10。
因此,结果应该是 [1,0]。

提示:

  • 1 <= digits.length <= 100
  • 0 <= digits[i] <= 9
  • digits 不包含任何前导 0。

解题思路

问题深度分析

这是一道大整数加法问题,核心在于模拟手工加法进位的过程。虽然题目简单,但涉及到进位处理数组操作边界情况的处理,是理解大整数运算的基础。

问题本质

给定一个用数组表示的大整数,需要将其加1。关键问题:

  • 进位传播:9+1=10,需要进位
  • 连续进位:999+1=1000,进位需要传播
  • 位数增加:999…9+1可能需要扩展数组
核心思想

模拟加法进位

  1. 从低位开始:从数组末尾(个位)开始处理
  2. 加1操作:个位+1
  3. 进位处理:如果某位≥10,则进位到高位
  4. 进位传播:持续处理进位直到没有进位为止
  5. 扩展数组:如果最高位还有进位,需要在数组开头插入1
典型情况分析

情况1:无进位

[1,2,3] + 1 = [1,2,4]
只需要最后一位+1,无需进位

情况2:单次进位

[1,2,9] + 1 = [1,3,0]
个位9+1=10,进位到十位

情况3:连续进位

[1,9,9] + 1 = [2,0,0]
个位进位→十位进位→百位

情况4:全部进位(最难)

[9,9,9] + 1 = [1,0,0,0]
所有位都进位,需要扩展数组
算法对比
算法时间复杂度空间复杂度特点
逆序遍历O(n)O(1)最优解法,原地修改
递归实现O(n)O(n)递归栈空间,代码简洁
双指针O(n)O(1)使用carry标志,易理解
字符串转换O(n)O(n)转为字符串处理,不推荐

注:n为数组长度,逆序遍历是最优解法

算法流程图

主算法流程(逆序遍历+进位)
graph TDA[开始: 输入digits] --> B[从末尾开始遍历]B --> C[个位+1]C --> D{digits i >= 10?}D -->|否| E[结束,返回digits]D -->|是| F[digits i = 0]F --> G[i--,进位到前一位]G --> H{i < 0?}H -->|是| I[所有位都进位]H -->|否| J[digits i++]J --> K{digits i >= 10?}K -->|否| EK -->|是| FI --> L[创建新数组,首位为1]L --> M[其余位为0]M --> N[返回新数组]
进位传播详细流程
进位传播开始
carry = 1
从末尾向前遍历
digits i += carry
digits i >= 10?
carry = 0
digits i -= 10
carry = 1
还有下一位?
carry == 1?
在开头插入1
返回digits
特殊情况处理
特殊情况判断
全是9?
999...9情况
创建长度+1的新数组
首位设为1
其余位为0
正常加法
逆序遍历+进位
返回原数组

复杂度分析

时间复杂度详解

逆序遍历算法:O(n)

  • 最好情况:O(1),末位不是9,直接+1返回
  • 最坏情况:O(n),全是9,需要遍历所有位
  • 平均情况:O(1),大多数情况不需要完整遍历

为什么平均是O(1)

  • 末位不是9的概率:90%,只需O(1)
  • 连续2位都是9的概率:1%
  • 连续3位都是9的概率:0.1%
  • …连续n位都是9的概率极低
空间复杂度详解

逆序遍历算法:O(1)

  • 最好情况:O(1),原地修改
  • 最坏情况:O(n),全是9时需要创建新数组

其他算法

  • 递归:O(n),递归栈
  • 字符串转换:O(n),额外字符串空间

关键优化技巧

技巧1:逆序遍历+原地修改(最优解法)
// 逆序遍历解法
func plusOne(digits []int) []int {n := len(digits)// 从末尾开始处理for i := n - 1; i >= 0; i-- {// 如果当前位不是9,直接+1返回if digits[i] < 9 {digits[i]++return digits}// 当前位是9,变成0,继续向前进位digits[i] = 0}// 所有位都是9,需要在开头插入1result := make([]int, n+1)result[0] = 1// 其余位默认为0,不需要赋值return result
}

优势

  • 代码简洁
  • 平均O(1)时间
  • 最好情况原地修改,O(1)空间
技巧2:使用carry标志
// 使用进位标志
func plusOneWithCarry(digits []int) []int {carry := 1n := len(digits)for i := n - 1; i >= 0 && carry > 0; i-- {sum := digits[i] + carrydigits[i] = sum % 10carry = sum / 10}// 如果还有进位,在开头插入if carry > 0 {result := make([]int, n+1)result[0] = carrycopy(result[1:], digits)return result}return digits
}

优势

  • 逻辑清晰
  • 易于扩展到加任意数
  • 符合加法运算习惯
技巧3:递归实现
// 递归解法
func plusOneRecursive(digits []int) []int {return addHelper(digits, len(digits)-1, 1)
}func addHelper(digits []int, index int, carry int) []int {// 递归终止条件if index < 0 {if carry > 0 {// 需要在开头插入进位result := make([]int, len(digits)+1)result[0] = carrycopy(result[1:], digits)return result}return digits}sum := digits[index] + carrydigits[index] = sum % 10newCarry := sum / 10// 递归处理前一位return addHelper(digits, index-1, newCarry)
}

特点

  • 代码优雅
  • 递归栈开销O(n)
  • 适合函数式编程思维
技巧4:提前判断优化
// 提前判断优化
func plusOneOptimized(digits []int) []int {n := len(digits)// 快速路径:末位不是9if digits[n-1] < 9 {digits[n-1]++return digits}// 检查是否全是9allNine := truefor _, d := range digits {if d != 9 {allNine = falsebreak}}if allNine {// 全是9,直接构建结果result := make([]int, n+1)result[0] = 1return result}// 正常进位处理for i := n - 1; i >= 0; i-- {if digits[i] < 9 {digits[i]++return digits}digits[i] = 0}return digits
}

边界情况处理

  1. 单个数字[0][1], [9][1,0]
  2. 全是9[9,9,9][1,0,0,0]
  3. 部分9[1,9,9][2,0,0]
  4. 无9[1,2,3][1,2,4]
  5. 开头是9[9,0,0][9,0,1]

测试用例设计

基础测试
输入: [1,2,3]
输出: [1,2,4]
说明: 无进位情况
单次进位
输入: [1,2,9]
输出: [1,3,0]
说明: 个位进位到十位
连续进位
输入: [1,9,9]
输出: [2,0,0]
说明: 连续两位进位
全部进位
输入: [9,9,9]
输出: [1,0,0,0]
说明: 所有位进位,数组扩展
边界情况
输入: [0]
输出: [1]
说明: 最小输入输入: [9]
输出: [1,0]
说明: 单位数进位

常见错误与陷阱

错误1:忘记处理全是9的情况
// ❌ 错误:没有处理数组扩展
func plusOne(digits []int) []int {for i := len(digits) - 1; i >= 0; i-- {if digits[i] < 9 {digits[i]++return digits}digits[i] = 0}// 忘记处理全是9的情况return digits  // 返回[0,0,0],错误!
}// ✅ 正确:需要在开头插入1
if carry > 0 {result := make([]int, n+1)result[0] = 1return result
}
错误2:数组越界
// ❌ 错误:可能越界
for i := n - 1; i >= 0; i-- {digits[i]++if digits[i] < 10 {return digits}digits[i+1] = 0  // 越界!
}// ✅ 正确:先判断再赋值
for i := n - 1; i >= 0; i-- {if digits[i] < 9 {digits[i]++return digits}digits[i] = 0
}
错误3:修改了原数组但未考虑
// ⚠️ 注意:原地修改会影响原数组
digits := []int{1,2,9}
result := plusOne(digits)
// digits已经被修改为[1,3,0]// 如果需要保留原数组,先复制
digitsCopy := make([]int, len(digits))
copy(digitsCopy, digits)
result := plusOne(digitsCopy)

实战技巧总结

  1. 逆序处理:从低位到高位,符合加法习惯
  2. 提前返回:遇到非9直接+1返回,平均O(1)
  3. 原地修改:节省空间,最好情况O(1)
  4. 边界处理:全是9时需要扩展数组
  5. 进位标志:使用carry变量清晰表达进位
  6. 代码简洁:利用循环和提前返回避免复杂判断

进阶扩展

扩展1:加任意数(不只是+1)
// 加任意正整数k
func plusK(digits []int, k int) []int {carry := kn := len(digits)for i := n - 1; i >= 0 && carry > 0; i-- {sum := digits[i] + carrydigits[i] = sum % 10carry = sum / 10}// 处理剩余进位if carry > 0 {carryDigits := intToDigits(carry)result := make([]int, len(carryDigits)+n)copy(result, carryDigits)copy(result[len(carryDigits):], digits)return result}return digits
}func intToDigits(num int) []int {if num == 0 {return []int{0}}var result []intfor num > 0 {result = append([]int{num % 10}, result...)num /= 10}return result
}
扩展2:大整数加法(两个数组相加)
// 两个大整数相加
func addTwoNumbers(num1 []int, num2 []int) []int {i, j := len(num1)-1, len(num2)-1carry := 0var result []intfor i >= 0 || j >= 0 || carry > 0 {sum := carryif i >= 0 {sum += num1[i]i--}if j >= 0 {sum += num2[j]j--}result = append([]int{sum % 10}, result...)carry = sum / 10}return result
}
扩展3:大整数减法
// 大整数减1
func minusOne(digits []int) []int {n := len(digits)for i := n - 1; i >= 0; i-- {if digits[i] > 0 {digits[i]--return digits}digits[i] = 9}// 如果最高位变成0,需要移除if digits[0] == 0 && n > 1 {return digits[1:]}return digits
}

应用场景

  1. 大整数运算:超过整数范围的数字运算
  2. 计算器实现:支持任意精度的加法
  3. 版本号递增:如1.2.9 → 1.3.0
  4. 编号系统:自动递增编号
  5. 密码学:大素数运算的基础

代码实现

本题提供了四种不同的解法,重点掌握逆序遍历方法。

测试结果

测试用例逆序遍历进位标志递归实现提前判断
基础测试
进位测试
边界测试
全9测试

核心收获

  1. 进位处理:掌握模拟加法进位的方法
  2. 数组操作:理解数组扩展和原地修改
  3. 边界情况:全面考虑各种特殊情况
  4. 性能优化:平均O(1)的优化技巧

应用拓展

  • 大整数运算库
  • 高精度计算器
  • 版本号管理系统
  • 自动编号生成器

完整题解代码

package mainimport ("fmt""time"
)// 方法一:逆序遍历算法(最优解法)
func plusOne1(digits []int) []int {n := len(digits)// 从末尾开始处理for i := n - 1; i >= 0; i-- {// 如果当前位不是9,直接+1返回if digits[i] < 9 {digits[i]++return digits}// 当前位是9,变成0,继续向前进位digits[i] = 0}// 所有位都是9,需要在开头插入1result := make([]int, n+1)result[0] = 1// 其余位默认为0,不需要赋值return result
}// 方法二:使用进位标志算法
func plusOne2(digits []int) []int {carry := 1n := len(digits)for i := n - 1; i >= 0 && carry > 0; i-- {sum := digits[i] + carrydigits[i] = sum % 10carry = sum / 10}// 如果还有进位,在开头插入if carry > 0 {result := make([]int, n+1)result[0] = carrycopy(result[1:], digits)return result}return digits
}// 方法三:递归实现算法
func plusOne3(digits []int) []int {return addHelper(digits, len(digits)-1, 1)
}func addHelper(digits []int, index int, carry int) []int {// 递归终止条件if index < 0 {if carry > 0 {// 需要在开头插入进位result := make([]int, len(digits)+1)result[0] = carrycopy(result[1:], digits)return result}return digits}sum := digits[index] + carrydigits[index] = sum % 10newCarry := sum / 10// 递归处理前一位return addHelper(digits, index-1, newCarry)
}// 方法四:提前判断优化算法
func plusOne4(digits []int) []int {n := len(digits)// 快速路径:末位不是9if digits[n-1] < 9 {digits[n-1]++return digits}// 检查是否全是9allNine := truefor _, d := range digits {if d != 9 {allNine = falsebreak}}if allNine {// 全是9,直接构建结果result := make([]int, n+1)result[0] = 1return result}// 正常进位处理for i := n - 1; i >= 0; i-- {if digits[i] < 9 {digits[i]++return digits}digits[i] = 0}return digits
}// 辅助函数:比较两个数组是否相等
func arrayEqual(a, b []int) bool {if len(a) != len(b) {return false}for i := range a {if a[i] != b[i] {return false}}return true
}// 辅助函数:复制数组
func copyArray(arr []int) []int {result := make([]int, len(arr))copy(result, arr)return result
}// 辅助函数:打印数组
func printArray(arr []int) string {if len(arr) == 0 {return "[]"}result := "["for i, v := range arr {if i > 0 {result += ","}result += fmt.Sprintf("%d", v)}result += "]"return result
}// 测试用例
func createTestCases() []struct {input    []intexpected []intname     string
} {return []struct {input    []intexpected []intname     string}{{[]int{1, 2, 3}, []int{1, 2, 4}, "示例1: 无进位"},{[]int{4, 3, 2, 1}, []int{4, 3, 2, 2}, "示例2: 无进位"},{[]int{9}, []int{1, 0}, "示例3: 单个9进位"},{[]int{0}, []int{1}, "边界1: 最小值"},{[]int{1, 2, 9}, []int{1, 3, 0}, "测试1: 单次进位"},{[]int{1, 9, 9}, []int{2, 0, 0}, "测试2: 连续进位"},{[]int{9, 9, 9}, []int{1, 0, 0, 0}, "测试3: 全部进位"},{[]int{9, 9}, []int{1, 0, 0}, "测试4: 两位全9"},{[]int{9, 0, 0}, []int{9, 0, 1}, "测试5: 开头是9"},{[]int{1, 0, 0}, []int{1, 0, 1}, "测试6: 末尾是0"},{[]int{8, 9, 9, 9}, []int{9, 0, 0, 0}, "测试7: 部分进位"},{[]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, []int{9, 8, 7, 6, 5, 4, 3, 2, 1, 1}, "测试8: 长数组"},}
}// 性能测试
func benchmarkAlgorithm(algorithm func([]int) []int, input []int, name string) {iterations := 10000start := time.Now()for i := 0; i < iterations; i++ {// 每次都复制输入,避免修改原数组inputCopy := copyArray(input)algorithm(inputCopy)}duration := time.Since(start)avgTime := duration.Nanoseconds() / int64(iterations)fmt.Printf("%s: 平均执行时间 %d 纳秒\n", name, avgTime)
}func main() {fmt.Println("=== 66. 加一 ===")fmt.Println()testCases := createTestCases()algorithms := []struct {name stringfn   func([]int) []int}{{"逆序遍历算法", plusOne1},{"进位标志算法", plusOne2},{"递归实现算法", plusOne3},{"提前判断算法", plusOne4},}// 正确性测试fmt.Println("=== 算法正确性测试 ===")passCount := 0failCount := 0for _, testCase := range testCases {// 为每个算法准备独立的输入副本results := make([][]int, len(algorithms))for i, algo := range algorithms {inputCopy := copyArray(testCase.input)results[i] = algo.fn(inputCopy)}// 检查所有算法结果是否一致allEqual := truefor i := 1; i < len(results); i++ {if !arrayEqual(results[i], results[0]) {allEqual = falsebreak}}// 验证结果是否正确isValid := arrayEqual(results[0], testCase.expected)if allEqual && isValid {passCount++fmt.Printf("✅ %s: %s + 1 = %s\n",testCase.name, printArray(testCase.input), printArray(results[0]))} else {failCount++fmt.Printf("❌ %s: %s\n", testCase.name, printArray(testCase.input))fmt.Printf("   预期: %s\n", printArray(testCase.expected))for i, algo := range algorithms {fmt.Printf("   %s: %s\n", algo.name, printArray(results[i]))}}}fmt.Println()fmt.Printf("测试统计: 通过 %d/%d, 失败 %d/%d\n",passCount, len(testCases), failCount, len(testCases))fmt.Println()// 性能测试fmt.Println("=== 性能测试 ===")perfTests := [][]int{{1, 2, 3},{1, 2, 9},{9, 9, 9},{9, 9, 9, 9, 9, 9, 9, 9, 9, 9},}for _, test := range perfTests {fmt.Printf("测试输入: %s\n", printArray(test))for _, algo := range algorithms {benchmarkAlgorithm(algo.fn, test, algo.name)}fmt.Println()}// 算法分析fmt.Println("=== 算法分析 ===")fmt.Println("加一问题的特点:")fmt.Println("1. 模拟手工加法进位")fmt.Println("2. 从低位到高位处理")fmt.Println("3. 进位传播直到无进位")fmt.Println("4. 全9情况需要扩展数组")fmt.Println()fmt.Println("=== 进位示例 ===")fmt.Println("无进位: [1,2,3] + 1 = [1,2,4]")fmt.Println("单次进位: [1,2,9] + 1 = [1,3,0]")fmt.Println("连续进位: [1,9,9] + 1 = [2,0,0]")fmt.Println("全部进位: [9,9,9] + 1 = [1,0,0,0]")fmt.Println()fmt.Println("=== 复杂度分析 ===")fmt.Println("时间复杂度:")fmt.Println("- 最好情况: O(1),末位不是9")fmt.Println("- 最坏情况: O(n),全是9")fmt.Println("- 平均情况: O(1),大多数不需要完整遍历")fmt.Println()fmt.Println("空间复杂度:")fmt.Println("- 逆序遍历: O(1),原地修改")fmt.Println("- 进位标志: O(1),常数空间")fmt.Println("- 递归实现: O(n),递归栈")fmt.Println("- 提前判断: O(1),原地修改")fmt.Println()fmt.Println("=== 为什么平均是O(1) ===")fmt.Println("末位不是9的概率: 90% → O(1)")fmt.Println("连续2位是9的概率: 1% → O(2)")fmt.Println("连续3位是9的概率: 0.1% → O(3)")fmt.Println("连续n位是9的概率: 极低")fmt.Println("加权平均: 接近O(1)")fmt.Println()fmt.Println("=== 优化技巧总结 ===")fmt.Println("1. 逆序处理:从低位到高位")fmt.Println("2. 提前返回:遇到非9直接返回")fmt.Println("3. 原地修改:节省空间")fmt.Println("4. 边界处理:全9时扩展数组")fmt.Println("5. 进位标志:清晰表达进位逻辑")fmt.Println()fmt.Println("=== 应用场景 ===")fmt.Println("1. 大整数运算:超过int范围的数字")fmt.Println("2. 计算器实现:任意精度加法")fmt.Println("3. 版本号递增:1.2.9 → 1.3.0")fmt.Println("4. 编号系统:自动递增编号")fmt.Println("5. 密码学:大素数运算基础")fmt.Println()fmt.Println("推荐使用:逆序遍历算法(方法一),最简洁高效")
}
http://www.dtcms.com/a/474498.html

相关文章:

  • 日志1--时间戳类型设计
  • 手机网站 qq代码免费app制作工具
  • MyBatis-Plus 全方位使用指南:从基础 CRUD 到复杂查询
  • avalonia的hello示例及mvvm实现
  • 天津网站建设优化如何建网站费用多少
  • 网站建设泉州效率网络企业网站建设基本原则
  • 41.Shell Case选择 While循环
  • 基于单片机的智能水箱温度液位控制系统设计
  • 数字化转型—AI+制造业的十大应用场景
  • Java-集合练习
  • 新民正规网站建设价格咨询广州app开发价格表
  • 适合用struts2做的网站钦州教育论坛网站建设
  • 关于节约化建设网站的表态发言企业推广方式力荐隐迅推
  • 北京备案网站莱芜58同城网
  • JavaSE面向对象(中)
  • 保健品网站源代码代理平台什么意思
  • 做网站签了合同后不想做了办公室装修公司哪里好
  • 网站建设和网络优化网站建设对网络营销有哪些影响
  • wordpress导入网站文章西安seo专员
  • 大学城网站开发公司深圳企业网页设计公司
  • commons-configuration2(配置管理库)
  • 处理Git错误:“invalid object [hash]”和“unable to read tree [hash]”
  • MySQL数据库 常用命令整理
  • 前端开发 - 实时智能回复
  • 对电子商务网站建设与维护的总结wordpress多选展示表单
  • 从零起步学习MySQL || 第二章:DDL语句定义及常见用法示例
  • Oracle REST Data Services是什么?
  • [吾爱大神原创工具] windows 多窗口同步软件(键+鼠) 20251011 更新
  • TDengine 数学函数 COS 用户手册
  • qfd 网站开发wordpress 上传主题 ftp