【透过算法了解编程】之加一

Hello,大家晚上好,我是猿码。夜深人静宿自宽,不写博客枉疯癫!

今天为大家带来的是一道 super easy 的算法题,由于它不涉及到分治(Divide and Conquer)、动态规划(DP)、递归(Recursive call)以及二分(Binary search)等算法,因此算不上经典,但作为了解 JVM 的 Happend-before 或程序执行流程优化可见一斑,前者可能有些牵强。


📕 题目:加一(Plus one)

📝 题目介绍:

给定一个由 整数 组成的 非空 数组 所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

tip:

  • 1 <= digits.length <= 100
  • 0 <= digits[i] <= 9

来源:力扣(LeetCode)

💡 题目分析

关于两个数字相加的程序模拟需要注意的相关细节,我在前几日的两数之和中有介绍,朋友们可以前往阅读。此处,就不赘述了。但,一个数字加 1 可能需要向高位进 1 位是这类题的核心,稍后我们会通过代码看看如何处理这个过程。

原题中要求以 int 类型的数组返回结果,大家都知道数组是固定大小的数据结构,一旦定义并指定长度,就无法直接改变其长度,区别于可变数组 ArrayList,而 ArrayList 除了有特殊的扩容机制外,只是在数组的基础上使用了 System.arrayCopy 来实现原始数组与扩容之后数组的元素交换。考虑到最高位可能会进 1 会改变原数字的初始长度,进而需要考虑:若在原数组中存储两数之和,必然会与数组的特性相冲突。因此,此处我们需要先创建一个长度为 n + 1 的数组,作为备用,n 为原始数组的长度。这样,“两全” 的情况就可以解决了。

  • 999 + 1
  • 888 + 1

接着,就是如何不留痕迹地在处理第一种“最高位不进1”的情况的同时,将第二种情况也一并处理掉,那么留在最后的一步,就是解决最高位补 1 的操作了。

🖊 代码实现

  public int[] plusOne(int[] digits) {
      int n = digits.length, r = 1;
      // 若最高位需进 1 的备用数组
      int[] nD = new int[n + 1];
      // 倒叙遍历,低位算起
      for (int j = n - 1; j >= 0; j--) {
          // 若任何位满足与 1 相加不超过 9,则直接返回参数 digits
          // 注意这里使用的是 +=
          if((digits[j] += r) <= 9)
              return digits;
          else {
              // 反之,由于刚刚对 digits[j] 进行判断时已经赋了值,若能跳入这个 if 子句
              // 说明 digits[j] 一定为 9,在原数组 i 位置在减去 10,留下余数,
              // 同时将原数的 j 位置的值赋值给备用数组  j + 1 位置
              nD[j + 1] = (digits[j] -= 10);
              // 当然也可以这么写 nD[j + 1] = digits[j] = 0;
          }
      }
      // 如果程序执行到这里,说明 digits 一定是 999
      nD[0] = 1;
      return nD;
  }

时间复杂度:O(n)n 为输入数组的长度

空间复杂度:O(n)这里如果细看,其实是 O(n + 1) 的空间复杂度,但大O标记法是标记算法复杂度最坏情形也就是性能上界的标准,因此需要去掉常数项 O(1)。

✂ 代码分析

该算法中,第一个 if 判断条件,大家如果细看,是先对 digits[j] 进行与 1 相加并赋值,在判断其是否超出 9,若满足条件,就直接返回结果,那么下图中代码标记为序号 1 的地方的作用就很明显,或者可以理解为:为 else 子句做铺垫。

序号 3,原代码是 nD[j + 1] = (digits[j] -= 10); 其实可以走下图序号 3 和 4 的写法。但前者,能突出 JVM 中 Happen-before 的执行顺序。Happen-before 可以理解为:某个已执行的指令是另外一个待执行指令的前驱,

int a = 10; 

int b = a + 1;

如果前者不执行,后者也不会执行。JVM 在编译这段代码时,不会对其进行重排序。

🎓 总结

  • 麻雀虽小,五脏俱全。每一段程序都有其背后的执行逻辑支撑,掌握细节,等于掌握大局
  • 代码的优化除了结构就是细节
  • 算法是赤裸的数学思想,语言则是它华丽的衣裳

好了,祝大家明日有一个好的开始,谢谢大家的阅读!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿码叔叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值