算法面试题及答案

2025年3月17日 | 阅读16分钟
Algorithm Interview Questions

算法是任何流程中不可或缺的一部分,因此面试官会问你很多与算法相关的问题。

这里列出了一些最常问的算法面试题及答案。这些问题对于学术和竞赛考试也很有益。

1) 什么是算法?算法的必要性是什么?

算法是一种定义明确的计算过程,它接收一些值或一组值作为输入,并产生一组值或一些值作为输出。

算法的必要性

算法提供了问题的基本思想和解决它的方法。使用算法的一些原因如下:

  • 算法提高了现有技术的效率。
  • 比较算法与其他技术的性能。
  • 算法为设计者提供了对问题需求和目标的清晰描述。
  • 算法提供了对程序流程的合理理解。
  • 算法测量不同情况下的方法性能(最佳情况、最坏情况、平均情况)。
  • 算法识别算法所需的资源(输入/输出、内存)周期。
  • 借助算法,我们可以测量和分析问题的复杂度和空间。
  • 算法还降低了设计成本。

2) 什么是算法的复杂度?

算法复杂度是一种对算法相对于其他算法的效率进行分类的方式。它关注执行时间随着要处理的数据集的增加而增加的程度。算法的计算复杂度在计算中很重要。

根据算法所需的时间或空间的相对量对算法进行分类,并指定时间/空间需求作为输入大小函数的增长,这是非常合适的。

时间复杂度

时间复杂度是程序的运行时间作为输入大小的函数。

空间复杂度

空间复杂度基于算法完成任务所需的空间来分析算法。在早期计算(当计算机存储空间有限时)中,空间复杂度分析至关重要。

如今,空间问题很少发生,因为计算机的空间非常充足。

我们对复杂度进行以下类型的分析:

最坏情况:f(n)

它由大小为 n 的任何实例上采取的最大步骤数定义。

最佳情况:f(n)

它由大小为 n 的任何实例上采取的最小步骤数定义。

平均情况:f(n)

它由大小为 n 的任何实例上采取的平均步骤数定义。


3) 编写一个反转字符串的算法。例如,如果我的字符串是“uhsnamiH”,那么我的结果将是“Himanshu”。

反转字符串的算法。

步骤1:开始

步骤2:取两个变量 i 和 j

步骤3:执行 length (string)-1,将 J 设置在最后一个位置

步骤4:执行 string [0],将 i 设置在第一个字符。

步骤5:将 string [i] 与 string[j] 交换

步骤6: i 增加 1

步骤7: j 增加 1

步骤8:如果 i>j,则转到步骤 3

步骤9:停止


4) 编写一个在排序链表中插入节点的算法。

在排序链表中插入节点的算法。

情况 1

检查链表是否为空,然后将节点设置为头并返回。

情况 2

在中间插入新节点

情况 3

在末尾插入节点


5) 什么是渐近符号?

渐近分析用于衡量算法的效率,它不依赖于特定机器的常数,并防止算法比较耗时的算法。渐近符号是一种数学工具,用于为渐近分析表示算法的时间复杂度。

最常用的三种渐近符号如下:

θ 符号

θ 符号定义了精确的渐近行为。为了定义行为,它从上方和下方约束函数。获得表达式的 Theta 符号的便捷方法是删除低阶项并忽略前导常数。

Algorithm Interview Questions

大 O 符号

大 O 符号从上方约束函数,它定义了算法的上界。让我们考虑插入排序的情况;它在最佳情况下需要线性时间,在最坏情况下需要二次时间。插入排序的时间复杂度为 O(n2)。当算法的时间复杂度只有上界时,它很有用。

Algorithm Interview Questions

Ω 符号

就像大 O 符号提供渐近上界一样,Ω 符号为函数提供渐近下界。当算法的时间复杂度有下界时,它很有用。

Algorithm Interview Questions

6) 解释冒泡排序算法?

冒泡排序是所有排序算法中最简单的。它通过重复地交换相邻的元素(如果它们顺序错误)来工作。

例如:

(72538) 我们有这个数组要排序。

第一遍

(72538) -> (27538) 交换 7 和 2。
(27538) -> (25738) 交换 7 和 5。
(25738) -> (25378) 交换 7 和 3。
(25378) -> (25378) 算法不交换 7 和 8,因为 7<8。

第二遍

(25378) -> (25378) 算法不交换 2 和 5,因为 2<5。
(25378) -> (23578) 交换 3 和 5。
(23578) -> (23578) 算法不交换 5 和 7,因为 5<7。
(23578) -> (23578) 算法不交换 7 和 8,因为 7<8。

这里,排序后的元素是 (23578)。


7) 如何在 Java 中不使用临时变量交换两个整数?

这是一个非常常见的陷阱题。解决这个问题有很多方法。

但必要条件是我们必须在不使用临时变量的情况下解决它。

如果我们考虑整数溢出并考虑其解决方案,那么它会在面试官眼中留下深刻的印象。

假设我们有两个整数 i 和 j,i=7 且 j=8,那么如何在不使用第三个变量的情况下交换它们?这是一个常见问题。

我们需要使用 Java 编程构造来完成此操作。我们可以通过执行一些数学运算(如加法、减法、乘法和除法)来交换数字。但这可能会导致整数溢出问题。

使用加法和减法

这是一个很好的技巧。但在此技巧中,如果加法值超过 `Integer.MAX_VALUE` 定义的 int 原语的最大值,或者减法值小于最小值(即 `Integer.MIN_VALUE`),则整数将溢出。

使用 XOR 技巧

交换两个整数而不使用第三个变量(临时变量)的另一种解决方案被广泛认为是最佳解决方案,因为它也适用于不支持整数溢出的语言,例如 Java,示例 C、C++。Java 支持多种按位运算符。其中之一是 XOR(表示为 ^)。


8) 什么是哈希表?如何使用这种结构来查找字典中的所有变位词?

哈希表是一种用于存储任意类型键值的数据结构。哈希表通过使用哈希函数将数组的索引作为存储值。索引用于存储元素。我们使用哈希函数将每个可能的元素分配给一个桶。多个键可以分配给同一个桶,因此所有键值对都存储在各自桶内的列表中。正确的哈希函数对性能有很大影响。

要查找字典中的所有变位词,我们需要将包含相同字母集合的所有单词分组。因此,如果我们把单词映射到表示它们排序后字母的字符串,那么我们就可以使用它们的排序后字母作为键来将单词分组到列表中。

哈希表包含映射到字符串的列表。对于每个单词,我们将其添加到相应键的列表中,或者创建一个新列表并将其添加到其中。


9) 什么是分治算法?

分治不是一种算法;它是一种算法模式。它的设计方式是通过处理一个巨大的输入,将输入分解成更小的部分,并为每个小部分解决问题。然后将所有分块解决方案合并成一个全局解决方案。这种策略称为分治。

分治使用以下步骤来解决算法上的争议。

划分:在此部分,算法将原始问题分解为一组子问题。

征服:在此部分,算法单独解决每个子问题。

合并:在此部分,算法将子问题的解决方案组合起来得到整个问题的解决方案。

10) 解释 BFS 算法?

BFS(广度优先搜索)是一种图遍历算法。它从根节点开始遍历图,并探索所有相邻的节点。它选择最近的节点并访问所有未探索的节点。直到达到目标状态,算法对每个最近的节点都遵循相同的过程。

算法

步骤1:将状态设置为 1(就绪状态)

步骤2:将起始节点 A 入队,并将其状态设置为 2,即(等待状态)

步骤3:重复步骤 4 和 5,直到队列为空。

步骤4:出队一个节点 N 并处理它,将其状态设置为 3,即(已处理状态)

步骤5:将所有处于就绪状态(状态=1)的 N 的邻居入队,并将其状态设置为 2(等待状态)
[停止循环]

步骤6:退出


11) 什么是 Dijkstra 的最短路径算法?

Dijkstra 算法是一种在加权图中查找从起始节点到目标节点最短路径的算法。该算法以起始顶点为源顶点,为图中所有其他节点生成最短路径树。

假设你想以最短的可能方式从家到办公室。你知道有些道路非常拥堵且难以使用,这意味着这些边具有较大的权重。在 Dijkstra 算法中,算法找到的最短路径树将尝试避免具有较大权重的边。


12) 给出分治算法的一些例子?

以下是一些使用分治算法解决问题的例子。

  • 合并排序
  • 快速排序
  • 二分搜索
  • Strassen 矩阵乘法
  • 最近点对(点)

13) 什么是贪心算法?给出一些例子?

贪心算法是一种算法策略,它在每个子阶段都做出最佳选择,目的是最终导致全局最优解。这意味着算法在当前选择最佳解决方案,而不考虑后果。

换句话说,算法在寻找答案时总是选择最佳的即时或局部解决方案。

贪心算法可以为一些理想化的问题找到整体的理想解决方案,但可能为其他问题的一些实例找到次优解决方案。

以下是使用贪心算法解决的算法列表。

  • Travelling Salesman Problem
  • Prim 最小生成树算法
  • Kruskal 最小生成树算法
  • Dijkstra 最小生成树算法
  • 图 - 地图着色
  • 图 - 顶点覆盖
  • 背包问题
  • 作业调度问题

14) 什么是线性搜索?

线性搜索用于一组项目。它依靠遍历列表从开始到结束的技术,访问沿途找到的所有元素的属性。

例如,假设有一个包含一些整数元素的数组。您应该查找并打印所有具有其值的元素的其位置。在这里,线性搜索的工作流程就像从列表的开头到列表的结尾匹配每个元素与整数,如果条件为 `True`,则打印元素的位置。

实现线性搜索

实现线性搜索需要以下步骤:

步骤1:使用 **for 循环**遍历数组。

步骤2:在每次迭代中,将目标值与数组的当前值进行比较

步骤3:如果值匹配,则返回数组的当前索引

步骤4:如果不匹配,则移动到下一个数组元素。

步骤5:如果未找到匹配项,则返回 -1


15) 什么是二叉搜索树?

二叉搜索树是一种特殊类型的数据结构,它具有以下属性:

  • 小于根的节点将在左子树中。
  • 大于根的节点(即包含更多值)将在右子树中。
  • 二叉搜索树不应包含重复节点。
  • 左右子树(即左子树和右子树)也应为二叉搜索树。
Algorithm Interview Questions

16) 编写一个在二叉搜索树中插入节点的算法?

插入节点操作是一个平滑的操作。您需要将其与根节点进行比较,并根据要插入的节点的值向左(如果较小)或向右(如果较大)遍历。

算法

  • 将根节点作为当前节点
  • 如果要插入的节点 < 根
    • 如果它有左子节点,则向左遍历
    • 如果它没有左子节点,则在此处插入节点
  • 如果要插入的节点 > 根
    • 如果它有右子节点,则向右遍历
    • 如果它没有右子节点,则在此处插入节点。

17) 如何计算二叉树的叶节点数?

算法-

计算叶节点数的步骤如下:

  • 如果节点为 null(包含 null 值),则返回 0。
  • 如果遇到叶节点。左为 null 且节点右为 null,则返回 1。
  • 使用以下方法递归计算叶节点的数量:

叶节点数 = 左子树中的叶节点数 + 右子树中的叶节点数。


18) 如何找到字符棋盘(Boggle 游戏)上的所有可能单词?

在给定的词典中,通过一个在词典中查找的流程,以及一个每个单元格只有一个字符的 M x N 棋盘。识别可以按相邻字符顺序形成的所有可能单词。考虑我们可以移动到任何一个可用的 8 个相邻字符,但一个单词不应包含同一个单元格的多个实例。

示例

输出

Following words of the dictionary are present
JAVA

19) 编写一个在链表中插入节点的算法?

算法

  • 检查链表是否没有值,然后将节点设为头并返回
  • 检查要插入的节点的值是否小于头节点的值,然后将节点插入开头并将其设为头。
  • 在循环中,找到要插入输入节点之后的合适节点。要找到最近的节点,从头开始,持续向前移动,直到到达一个值大于输入节点的值的节点。紧前面的节点就是合适的节点。
  • 在步骤 3 中找到的合适节点之后插入节点。

20) 如何删除给定链表中的节点?编写一个算法和一个程序?

编写一个函数来从单向链表中删除给定节点。该函数必须遵循以下约束:

  • 该函数必须接受指向起始节点的指针作为第一个参数,并将要删除的节点作为第二个参数,即头节点的指针不是全局的。
  • 该函数不应返回指向头节点的指针。
  • 该函数不应接受指向头节点指针的指针。

我们可以假设链表永远不会变空。

假设函数名为 `delNode()`。在直接实现中,当要删除的节点是第一个节点时,该函数需要调整头指针。

C 语言链表节点删除程序

我们将处理要删除的第一个节点的情况,然后我们将下一个节点的数据复制到头节点并删除下一个节点。在其他情况下,当删除的节点不是头节点时,可以通过查找前一个节点来一般处理。

输出

Available Link List: 12 15 10 11 5 6 2 3 
Delete node 10:
Updated Linked List: 12 15 11 5 6 2 3
Delete first node
Updated Linked list: 15 11 5 6 2 3 

21) 编写一个 C 程序将一个链表合并到另一个链表中,交替插入位置?

我们有两个链表,将第二个链表的节点插入到第一个链表中,交替插入第一个链表的位置。

示例

如果第一个链表是 1->2->3,第二个链表是 12->10->2->4->6,那么第一个链表应该变成 1->12->2->10->17->3->2->4->6,第二个链表应该变为空。第二个链表的节点只有在有可用位置时才应插入。

不允许使用额外的空间,即必须在原地插入。可预测的时间复杂度为 O(n),其中 n 是第一个链表中的节点数。

输出

I Linked List:        
1 2 3
II Linked List:      
4 5 6 7 8                
Updated I Linked List:         
1 4 2 5 3 6           
Updated II Linked List:          
7 8                                                                      

22) 解释加密算法是如何工作的?

加密是将明文转换为秘密代码格式的技术,也称为“密文”。为了转换文本,算法使用称为“密钥”的一串比特进行计算。密钥越大,加密的潜在模式越多。大多数算法使用固定长度约为 64 到 128 位的输入块,而一些算法则使用流方法进行加密。


23) 算法分析的标准是什么?

算法通常通过两个因素进行分析。

  • 时间复杂度
  • 空间复杂度

时间复杂度涉及量化代码集或算法运行以处理或运行所需的时间量,作为输入量的函数。换句话说,时间复杂度是效率,或者一个程序函数处理给定输入所需的时间。

空间复杂度是算法执行和产生结果所需的内存量。


24) 堆栈和队列之间有什么区别?

堆栈和队列都是非原始数据结构,用于存储数据元素,并且基于某些现实世界的等价物。

让我们根据以下参数看看关键区别。

工作原理

堆栈和队列之间的一个显著区别是,堆栈使用 LIFO(后进先出)方法来访问和添加数据元素,而队列使用 FIFO(先进先出)方法来获取数据成员。

结构

在堆栈中,存储和删除元素使用相同的末端,但在队列中,一个末端用于插入(即队尾),另一个末端用于删除元素(即队头)。

使用的指针数量

堆栈使用一个指针,而队列使用两个指针(在简单情况下)。

执行的操作

堆栈的操作是 Push 和 Pop,而队列的操作是 Enqueue 和 Dequeue。

变体

堆栈没有变体,而队列有变体,如循环队列、优先队列、双端队列。

实施

堆栈更简单,而队列相对复杂。


25) 单向链表和双向链表数据结构之间有什么区别?

这是一个关于数据结构的传统面试问题。单向链表和双向链表之间的主要区别在于遍历能力。

您无法在单向链表中向后遍历,因为其中的节点仅指向下一个节点,而没有指向前一个节点的指针。

另一方面,双向链表允许您在任何链表中双向导航,因为它维护指向下一个和前一个节点的两个指针。