算法面试:从基础到优化,掌握x的n次方计算

167 阅读6分钟

引言

在现代软件开发中,算法不仅是编程技能的核心组成部分,更是衡量程序员综合素质的重要指标。无论是应对复杂的业务逻辑,还是解决高性能计算问题,优秀的算法能力都是不可或缺的。因此,在技术面试中,面试官常常通过算法题来评估候选人的思维能力、解决问题的能力以及代码质量。

一、面试官为何考算法?

面试官之所以重视算法,是因为它不仅是编程技能的一部分,更是衡量候选人综合素质的重要指标。

算法考察的是什么?

image.png

算法考察面试者的思维能力和解决问题的能力,而非单纯的代码能力。
  1. 思维能力和解决问题的能力:逻辑思维、创造性思维
  2. 抽象能力和数据结构的理解:数据结构的选择、抽象层次
  3. 应对复杂问题的能力:面对难题的态度、寻求帮助
为什么要写算法?

提高效率

  1. 减少时间复杂度:高效的算法可以在更短的时间内完成任务,这对于处理大数据量或实时性要求高的应用尤其重要。例如搜索引擎、在线交易平台等都需要快速响应用户请求。

  2. 降低空间复杂度:优化算法可以减少内存使用,这在资源有限的环境中(如嵌入式系统、移动设备)是非常重要的。此外,降低空间复杂度也可以间接地提高性能,因为更少的内存占用通常意味着更快的数据访问速度。

掌握算法知识可以帮助程序员更好地理解和解决复杂的计算问题。面对一个新问题时,能够设计出有效的解决方案是编程技能的重要体现。优秀的算法能力是一位优秀程序员的必要条件。

二、算法题之x的n次方

看见这个题目,相信你立马就想到了暴力法:

function fun1(x,n){
     let result = 1;
     for(let i = 0;i<n;i++){
         result *= x;
     }
     return result;
 }

显然这段代码使用了一个for循环来重复将x乘以自身n次。虽然这种方法可以工作,但因为它的时间复杂度是O(n),当n非常大的时候,意味着随着n的增长,执行时间会线性增加。

这时面试官就会问你:你认为你的代码有什么缺陷或者能不能优化一下?

然后你思考后想到了递归(递归本身不是一种算法,而是一种编程技术和问题解决策略):

function fun1(x,n){
    if(n==0){        //递归终止条件
        return 1;
    }
    //把问题分解为规模更小的子问题
    return fun1(x,n-1)*x;
}

递归步骤: 函数调用自身,每次将指数n减1,并将结果乘以x。这实际上是在重复执行x * x * ... * x(共n次)的操作。

这时面试官又说:这段代码的时间复杂度是仍然是O(n),对于较大的n,函数会进行大量的递归调用,可能导致栈溢出错误。还能优化一下吗?

这时你犯了难,不知如何是好?殊不知,这也是面试官对你的考验。 这是考验你碰到难题会怎么办(测试你的抗压能力)以及未来解决复杂问题的能力,(放弃还是坚持),其实这时你可以向面试官寻求帮助,请他点拨一二,这也是考验你的学习能力和理解能力。

于是,面试官告诉你可以把规模分的更小。

你恍然大悟,想到了快速幂递归,写下来以下代码:

function fun1(x,n){
    if(n===0){
        return 1;
    }
    // 自顶向下 
    let t = fun1(x,Math.floor(n/2));// 把x的n次方分解为x平方的n/2次方
    if(n%2===0){
        return t*t;// 偶数
    }
    else{
        return t*t*x;// 奇数
    }
}

这段代码首先计算xMath.floor(n / 2)次幂,并将其结果存储在变量t中。 然后根据n是否为偶数奇数,决定最终返回的结果: 如果n偶数,则返回t * t。 如果n奇数,则返回t * t * x,因为在奇数情况下还需要额外乘以一次x

由于每次递归都将n减半,因此时间复杂度为O(log n),这比简单的线性递归(O(n))要快得多,尤其对于较大的n。

快速幂递归有些抽象,下面以x=2,n=5来举例:

初始状态

在开始之前,栈是空的Stack[]。

第一次调用 fun1(2, 5)

  • 调用fun1(2, 5),将参数x=2n=5压入栈。
  • n是奇数,所以需要计算t = fun1(2, Math.floor(5 / 2)),即fun1(2, 2)
Stack: [fun1(2, 5)]

第二次调用 fun1(2, 2)

  • 调用fun1(2, 2),将参数x=2n=2压入栈。
  • n是偶数,所以需要计算t = fun1(2, Math.floor(2 / 2)),即fun1(2, 1)
Stack: [fun1(2, 5), fun1(2, 2)]

第三次调用 fun1(2, 1)

  • 调用fun1(2, 1),将参数x=2n=1压入栈。
  • n是奇数,所以需要计算t = fun1(2, Math.floor(1 / 2)),即fun1(2, 0)
Stack: [fun1(2, 5), fun1(2, 2), fun1(2, 1)]

第四次调用 fun1(2, 0)

  • 调用fun1(2, 0),将参数x=2n=0压入栈。
  • n是0,根据递归终止条件,返回1。
Stack: [fun1(2, 5), fun1(2, 2), fun1(2, 1), fun1(2, 0)]
  • 弹出fun1(2, 0),返回值为1。
Stack: [fun1(2, 5), fun1(2, 2), fun1(2, 1)]

回溯到 fun1(2, 1)

  • t = 1(来自fun1(2, 0)
  • n是1(奇数),所以返回t * t * x = 1 * 1 * 2 = 2
Stack: [fun1(2, 5), fun1(2, 2)]
  • 弹出fun1(2, 1),返回值为2。
Stack: [fun1(2, 5), fun1(2, 2)]

回溯到 fun1(2, 2)

  • t = 2(来自fun1(2, 1)
  • n是2(偶数),所以返回t * t = 2 * 2 = 4
Stack: [fun1(2, 5)]
  • 弹出fun1(2, 2),返回值为4。
Stack: [fun1(2, 5)]

回溯到 fun1(2, 5)

  • t = 4(来自fun1(2, 2)
  • n是5(奇数),所以返回t * t * x = 4 * 4 * 2 = 32
Stack: []
  • 弹出fun1(2, 5),最终结果为32。

图解

image.png

三、总结

对于追求职业成长和希望在软件开发领域有所建树的程序员来说,深入学习和掌握算法是至关重要的。通过不断练习和挑战各种算法问题,不仅可以提高自身的编程技艺,还能够培养出更为严谨的逻辑思维和创新精神。此外,在准备技术面试的过程中,熟练掌握常见的算法与数据结构,并能够灵活运用它们来设计高效、优雅的解决方案,将使候选人脱颖而出。因此,无论是为了提升个人的技术实力,还是为了成功通过竞争激烈的技术面试,投入时间和精力去钻研算法都是值得的。这不仅是对现有知识体系的巩固和完善,更是为未来的职业发展打开了一扇更宽广的大门。