Laymen谈面试算法题入门

535 阅读4分钟

面试算法题入门指南

作为面试门外汉,一直对大厂的算法题充满好奇和向往。前不久,一位大佬详细讲解了大厂面试中的算法题,今天我将这些宝贵的经验分享给大家。

两类编程题

在面试中,编程题通常分为两大类:

  1. 程序逻辑题:这类题目主要考察实现具体需求的能力,要求你能够写出符合功能要求的代码。
  2. 算法题:这类题目更加注重解决问题的思维能力和算法设计,考察的是你如何通过抽象和优化来提升代码的效率。

面试官为何重视算法

面试官之所以看重算法题,原因在于它们能够全面评估候选人的以下能力:

  • 思维能力和解决问题的能力:算法题不仅仅是考察编码能力,更重要的是考察你如何思考问题、分析问题并找到最优解。
  • 抽象能力:能否将复杂的问题抽象成数据结构和算法模型,是否具备清晰的思路。常见的数据结构包括数组、字符串、栈、队列、链表、哈希表、二叉树(堆)等;常见的算法包括递归、双指针、dummy 节点、滑动窗口、排序(如快速排序)、查找、分治(如二分查找)、回溯、动态规划、贪心算法等。
  • 抉择和学习能力:面试中可能会遇到一些难度较高的题目,考察你在有限时间内做出合理抉择并当场学习新知识的能力,这反映了你未来解决复杂问题的潜力。
  • 提升效率:优秀的算法可以显著降低时间和空间复杂度,提高程序的执行效率。

引入:求解 x 的 n 次方

对于“求解 x 的 n 次方”这个问题,初学者可能会想到使用简单的 for 循环来实现。然而,这样的暴力解法虽然简单,但并不是最优解。面试官更希望看到你是否有能力进一步优化,降低时间复杂度。

递归解法

递归是一种将复杂问题分解为相同类型的子问题的策略。通过递归,我们可以将 x^n 分解为 x^(n/2) 的平方,从而减少乘法操作的次数。

function fastPowRecursive(x, n) {
    if (n === 0) return 1;
    let t = fastPowRecursive(x, Math.floor(n / 2));
    if (n % 2 === 1) {
        return t * t * x;
    } else {
        return t * t;
    }
}

console.log(fastPowRecursive(2, 30));  // 输出 2^30

迭代解法

虽然递归解法简洁,但在处理非常大的 n 时,可能会导致递归深度过大,进而引发栈溢出问题。因此,我们可以通过迭代的方式实现快速幂算法,避免递归带来的性能问题。

function fastPowIterative(x, n) {
    if (n === 0) return 1;
    if (n < 0) {
        x = 1 / x;
        n = -n;
    }

    let res = 1;
    while (n > 0) {
        if (n % 2 === 1) {
            res *= x;
        }
        x *= x;
        n = Math.floor(n / 2);
    }
    return res;
}

console.log(fastPowIterative(2, 30));  // 输出 2^30

回溯算法示例:八皇后问题

回溯算法是一种通过递归尝试所有可能解的策略,常用于解决组合问题。以经典的“八皇后问题”为例,我们需要在 8×8 的棋盘上放置 8 个皇后,使得任意两个皇后都不能在同一行、同一列或同一对角线上。

回溯三部曲(karl 经典)
void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }
    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

还是卡尔讲解清晰 下面就只附带代码。面试考察分部思考,当面对这样“回溯”的棋牌题目,分部考虑在结合暴力进行解答。如果需要优化,那剪枝等可做到。回溯还是可以套用模板的。

class Solution {
private:
    // row 行,col 列 
    /**
      不同列,斜边(主对角线,次)
    */
    vector<vector<string> > result;
    int n;
    bool isVaild(int row,int col,vector<string> &cheer,int n) {
        // 列
        for(int i = 0;i < row;++i) {
            if(cheer[i][col]=='Q') {
               return false;
            }
        } 
        // 45° 主对角线
        for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--) {
            if(cheer[i][j] == 'Q'){
                return false;
            }
        }
        // 135°
        for(int i=row-1,j=col+1;i>=0&&j<n;i--,j++) {
           if(cheer[i][j] == 'Q'){
                return false;
            }
        }
        return true;
    }
    void backtracking(int n,int row,vector<string> & cheer) {
    if (row == n) {
        result.push_back(cheer);
        return;
    }
    for (int col = 0;col<n; ++col) {
        // 验证合法
        if(isVaild(row,col,cheer,n)) {
            cheer[row][col] = 'Q';//放皇后
            backtracking(n,row+1,cheer); // 递归
            cheer[row][col] = '.'; //回溯,撤销处理结果
        }
    }
}
public:
    vector<vector<string>> solveNQueens(int n) {
        result.clear();
        vector<string > cheer(n,string(n,'.'));
        backtracking(n,0,cheer);
        return result;
    }
};

总结

算法题不仅是考察你的编码能力,更是考察你如何通过抽象、优化和创新来解决问题。通过掌握常见的数据结构和算法,你可以更好地应对面试中的挑战,并在未来的工作中成为一名优秀的程序员。