Java编程面试题

2025年3月31日 | 阅读15分钟

在软件工程师招聘和选拔过程的所有阶段中,编程面试被认为是重要的。这些面试不仅考察候选人编写清晰、简洁代码的能力,还考察候选人的解决问题能力、算法知识以及数据结构知识。在基于Java的面试中,候选人需要解决各种与数组、链表、哈希映射、动态规划等核心问题相关的难题。

Java在这些面试中被使用,因为它功能强大,拥有标准库,并且是面向对象的。在回答这些问题时,应注意解决方案、方法、合理的解释、性能以及可能的错误。

在本节中,我们将通过Java中的分步说明和示例,介绍一些典型的编程面试题。详细介绍了每种提出的解决方案,包括所有代码示例和效率方面的考虑。如果您正计划参加编码面试,或者希望提高Java的解决问题能力,您可以在这里找到所需的所有工具和信息。

1. 两数之和问题

问题

您有一个整数数组,任务是返回相加得到目标值的两个数字的索引。

问题概述

问题描述包含一个整数数组nums和一个表示目标值的整数。我们需要找到一对i和j,并确保nums[i] + nums[j] = target。我们必须确保解决方案足够快;最好至少与输入数组的大小成比例。

解决方案方法

暴力破解法

这包括遍历整个矩阵,将每个元素与其他所有元素进行比较,以找到数组中相加等于目标值的两个值。然而,由于有两个嵌套循环,这种方法需要很长时间,时间复杂度为 O(n²)。

优化方法(使用HashMap)

我们使用HashMap遍历数组一次并将每个元素及其索引存储起来。在迭代过程中,我们验证补数是否存在于映射中。否则,这意味着当前元素及其补数具有相同的频率,因此我们返回当前元素和补数的索引。这种方法更有效,因为它具有 O(n) 的时间复杂度。

文件名:TwoSum.java

输出

 
Indices: 0, 1   

注意事项和边缘情况

  • 负数:它也能很好地处理负数,因为HashMap可以同时处理正数和负数。
  • 重复值:事实上,即使数组包含重复值,它也能正常工作,因为我们单独存储了不同的索引。
  • 异常处理:如果未提供此类对,我们将创建 `IllegalArgumentException` 以在没有有效对的情况下帮助程序。

效率和性能

时间复杂度:O(n),因为我们只遍历数组一次,而HashMap的 `put` 和 `containsKey` 函数的平均时间复杂度为 O(1)。

空间复杂度:O(n),因为HashMap最多可以存储 n 个元素。

2. 反转链表

问题

问题是反转一个单向链表并返回新链表的头节点。

问题概述

在这个问题中,我们需要反转给定的单向链表。目标是将链表中的节点的指针交换,形成一个反向链表。

解决方案方法

要反转链表,我们需要在维护三个指针:`previous`、`current` 和 `next` 的同时遍历链表。在每一步,我们将当前节点的指针指向前一个节点,然后继续前进。

文件名:ReverseLinkedList.java

输出

 
5 4 3 2 1   

注意事项和边缘情况

  • 空链表:代码的第一个输入也应该是链表为空(head = null)的情况。在这种情况下,函数应返回 null。
  • 单节点链表:如果链表中只有一个节点,那么函数应该返回与接收到的参数完全相同的节点。

效率和性能

时间复杂度:O(n)。我们实际上遍历链表一次以更改每个节点的 next 指针

空间复杂度:O(1)。该解决方案仅使用恒定的额外空间,即三个指针:`prev`、`curr`、`nextTemp`。

3. 回文检查

问题

识别输入字符串是否为回文。

解决方案方法

为了解决这个问题,我们使用两个指针:一个指针从开头开始,另一个指针从结尾开始。我们利用字符的概念,同时排除空格和其他特殊字符。因此,如果所有字符都与其对应的字符匹配,则该字符串就是回文。

文件名:PalindromeCheck.java

输出

 
Is palindrome? true   

注意事项和边缘情况

  • 空字符串:测试字符串是否为回文的最佳方法是删除所有空格并将字符串转换为小写,空字符串是回文。
  • 包含非字母数字字符的字符串:该解决方案必须能够处理包含标点符号或空格的字符串。例如,“A man, a plan, a canal: Panama” 也是回文,并且当考虑这个词的扩展时,“Panama”作为回文,所有空格、空白、标点符号和其他类似元素都应被忽略。
  • 混合大小写字母:确保这些检查是区分大小写的,因此“A”等同于“a”。

效率和性能

时间复杂度:O(n) - 使用双指针技术仅遍历字符串一次,任何字符比较操作都是恒定时间操作。

空间复杂度:O(1) - 我们不使用任何与输入大小成比例的额外空间,只使用两个指针。

4. 合并两个已排序的数组

问题

您提供了两个预先排序的数组。将它们合并成一个已排序的数组。

解决方案方法

通过稍作修改,可以在线性时间内合并两个数组来解决上述问题。第一步是比较数组的第一个元素,将此元素添加到结果数组中,同时增加相应数组的指针。继续此过程,直到所有检索到的元素都被合并。

文件名:MergeSortedArrays.java

输出

 
Merged array: [1, 2, 3, 4, 5, 6, 7, 8]   

注意事项和边缘情况

  • 空数组:如果其中一个数组是空数组,函数必须只返回第二个数组或第一个数组。
  • 长度不同的数组:代码还应处理一个数组比另一个数组长的 H 情况(例如,合并数组 [1, 2, 3] 和 [4])。

效率和性能

时间复杂度:最佳和最坏情况 - 此算法的最佳和最坏情况都取决于两个给定数组 n 和 m 中的元素数量。在计算复杂度方面,此算法为 O(n + m)。我们通过一次迭代每个数组来合并两个数组。

空间复杂度:O(n + m) - 创建了一个新数组来存储合并后的数组,因此占用的空间与两个数组的大小之和成正比。

5. 楼梯问题(动态规划)

问题

您正在爬楼梯。当您向上爬升时,需要 n 步才能到达屋顶。每次您可以向上走 1 步或 2 步。有多少种不同的组合方式可以爬到顶峰?

解决方案方法

这个问题使用动态规划技术来解决。具体来说,意味着比到达 n-1 步多走一步,而到达 n-2 步的路径数是在 n-2 步之后再走两步,因此到达第 n 步的方法数是到达第 n-1 步和第 n-2 步的方法数之和。这就是动态规划解决方案的基础。

文件名:ClimbingStairs.java

输出

 
Ways to climb 5 stairs: 8   

注意事项和边缘情况

  • n = 1:只能向上走一步,只有一种方法可以到达该步骤。
  • n = 0(无台阶):当没有台阶时,通常认为只有一种方法可以到达顶部,这意味着根本不爬。

效率和性能

时间复杂度:O(n) - 我们一次计算一个结果,直到 n,因此这是一个线性过程。

空间复杂度:O(n) - 使用了一个大小为 n+1 的新数组来存储到达每一步的方法数。然而,这可以优化到 O(1),因为我们只需要跟踪最后两个结果。

6. 最长无重复字符的子串

问题

给定一个字符串,编写一个算法来确定包含无重复字符的最长连续子串的长度。

解决方案方法

然而,在单次遍历字符串时,我们不能使用 `StringBuilder` 来跟踪字符,因此我们使用了滑动窗口技术以及 `HashSet`。这就是为什么通过在所有字符唯一时用右指针扩大窗口,并在找到重复字符时用左指针缩小窗口,可以确定最大子串长度。

文件名:LongestSubstring.java

输出

 
Longest substring without repeating characters: 3   

注意事项和边缘情况

  • 空字符串:包含空字符串的字符串应返回 0,因为它不包含任何字符。
  • 所有字符唯一:一个例子是给定字符串“abcdefg”,它只包含唯一的字符,应该返回字符串的长度。

效率和性能

时间复杂度:O(n) - 最佳方法是让两个指针都指向字符串,最多遍历一次字符串。

空间复杂度:Ceil[(O(min(n,m))] - 需要使用的空间取决于字符集的大小和字符串的长度,其中 m = 128 for ASCII

7. 查找缺失的数字

问题

我们需要证明,对于从集合 {0,1,2,…, n} 中选取的 n 个不同数字的任意给定 n 元数组,只存在一个缺失的数字。

解决方案方法

我们可以通过找到从 0 到 n 的 n 个数字的总和(等于 [n * (n + 1) / 2])来解决这个问题。当此总和减去数组元素的总和时,我们可以轻松找到缺失的数字。

文件名:MissingNumber.java

输出

 
Missing number: 2  

注意事项和边缘情况

  • 除一个数字外所有数字都存在:它要求算法能够识别哪个数字在什么位置缺失,可能是第一个数字缺失,可能是列表的中间,也可能是列表的末尾。
  • 大小为 1 的数组:对于给定的数组,如果它只包含一个数字,那么缺失的数字将是零或一。

效率和性能

时间复杂度:O(n) - 处理次数是给定数组中元素的数量。

空间复杂度:O(1) - 我们只使用几个额外的变量进行计算。

效率

8. 数组元素自乘积

问题

假设您有一个整数数组;您可以得到一个输出数组,其中 output[i] 等于给定数组中所有整数的乘积,但不包括索引 i 处的元素。

解决方案方法

这个问题完全通过避免除法来解决。首先,我们创建两个数组:一个数组存储每个元素左侧元素的乘积,另一个数组存储每个元素右侧元素的乘积。最后,对于“M”的每个索引,将第 i 行的左乘积与第 j 列的右乘积相乘,并加到最终结果中。

文件名:ProductExceptSelf.java

输出

 
24 12 8 6   

注意事项和边缘情况

  • 数组中的零:零在数组中的效果很明显,在给定的情况下,任何数乘以零都等于零。
  • 大小为 1 的数组:如果数组只包含一个数字,则结果应为单个 1(表示没有乘积)。

效率和性能

时间复杂度:O(n) - 数组 left 和 right 是通过运行时间获得的。

空间复杂度:O(n) - 我们使用两个辅助数组来编号当前列表,大小为 n。

9. 字符串中的第一个唯一字符

问题

问题是获取一个字符串,然后识别字符串中第一个不重复的字符,然后找到它的位置。如果不存在,则返回 -1。

解决方案方法

为了获取我们正在压缩的字符串中每个字符的频率,我们首先处理 `for` 循环将其存储到 `Hash Map` 中。之后,我们再次遍历字符串,查找出现次数仅为一次的字符。

文件名:FirstUniqueCharacter.java

输出

 
First unique character index: 2   

注意事项和边缘情况

  • 所有字符都重复:如果字符串由所有相同的字符组成,函数应返回 -1。
  • 空字符串:函数应将空字符串识别为这种情况并返回 -1。

效率和性能

时间复杂度:O(n) - 在计算字符串字符频率的情况下,我们扫描字符串两次,这意味着它是 O(n);查找唯一字符的字符串扫描也是 O(n)。

空间复杂度:O(1) - 如果字母表大小是常数,例如 26 个小写字母,则空间复杂度也表示为常数。

10. 有效括号

问题

您应该定义一个函数,该函数接受一个由字符 '(', ')', '{', '}', '[' 和 ']' 组成的字符串,并确定输入字符串是否有效。输入字符串有效,如果

  • 所有开括号必须由相应类型的闭括号配对。
  • 括号的开括号必须正确关闭。

解决方案方法

使用来确保每个闭括号都有最后一个未匹配的开括号。对于字符串,我们遍历字符串,每次将开括号压入栈,并在遇到匹配的闭括号时弹出栈中的开括号。

文件名:ValidParentheses.java

输出

 
Is valid? true   

注意事项和边缘情况

  • 空字符串:空字符串是有效的,因为没有一种类型的括号与另一种类型的括号配对。
  • 不平衡的括号:函数需要找到的问题之一是特定情况,即开括号或闭括号多于另一种。

效率和性能

时间复杂度:O(n) - 因为我们对字符串求解操作运行一个循环,并保持恒定时间的栈操作。

空间复杂度:O(n) - 在最坏的情况下,栈可以增长到字符串的最大长度。

结论

学习算法问题对于编码面试和普遍提高编程能力至关重要。所讨论问题的特点,包括最长无重复字符子串、缺失数字和有效括号,突显了在整个开发过程中决定软件开发的元素和结构。

所有解决方案都强调了批判性地处理问题的必要性,考虑了边界情况以及输入的约束。例如,滑动窗口技术提供了一种非常有效的方法来跟踪唯一字符,而用于识别缺失数字的数学公式可以减少所需时间。栈,尤其是用于验证括号的栈,最好地解释了数据结构如何适应不同的关系。

了解解决方案的效率非常有用;计算复杂度是衡量时间和空间以提高工作时间,尤其是在处理海量数据集时。解决这些挑战是一个形成性过程,有助于为在未来活动中处理问题和发展演讲技巧创造条件。最后,只有当某人掌握了这些概念后,他才能为自己一直渴望的软件工程师职位进行技术面试。