据难度和主题生成高质量编程面试题及解析
### 问题描述 给定一个字符串 `s`,请你找到其中最长的没有重复字符的子串,并返回其长度。 **要求:** - 最优时间复杂度需要达到 O(n)。 - 你需要保证对字符串处理的逻辑足够清晰,并能正确处理边界情况(如空字符串、单字符字符串等)。 --- **输入约束:** 1. 字符串长度范围:`1 <= len(s) <= 10^5` 2. 字符串由字母、数字、符号和空格组成。 --- ### 输入与输出 **示例 1:** ``` 输入: "abcabcbb" 输出: 3 解释: 最长子串是 "abc",它的长度为 3。 ``` **示例 2:** ``` 输入: "bbbbb" 输出: 1 解释: 最长子串是 "b",它的长度为 1。 ``` **示例 3:** ``` 输入: "pwwkew" 输出: 3 解释: 最长子串是 "wke",它的长度为 3。 注意,子串 "pwke" 并不是有效的,因为子串中的字符必须是连续的。 ``` **示例 4:** ``` 输入: "" 输出: 0 ``` --- ### Python代码 ```python def length_of_longest_substring(s: str) -> int: # 使用滑动窗口 char_set = set() # 用于存储当前窗口的字符 left = 0 # 左指针,表示窗口的起始位置 max_len = 0 # 记录最长无重复子串的长度 for right in range(len(s)): # 右指针遍历字符串 # 如果字符已经在窗口中,收缩左边界,直到窗口中没有重复字符 while s[right] in char_set: char_set.remove(s[left]) left += 1 # 将当前字符加入窗口 char_set.add(s[right]) # 更新最大子串的长度 max_len = max(max_len, right - left + 1) return max_len ``` --- ### 解题逻辑逐步解析 1. **问题分析** 本题要求找到没有重复字符的最长连续子串。使用滑动窗口技巧,可以高效地解决该问题,将问题从 O(n²) 优化为 O(n)。 2. **滑动窗口解释** - 滑动窗口使用两个指针 `left` 和 `right`,分别表示当前考察的子串的起始和结束位置。 - 子串的内容通过 `set` 数据结构动态维护。 - 当右指针右移时,我们向窗口中添加字符: - 如果字符重复(即已经存在于 `set` 中),通过移动左指针缩小窗口大小,直到窗口中没有重复字符。 - 每次移动窗口后,计算窗口大小并更新全局最大值。 3. **步骤拆解** - 初始化一个空的哈希集合 `char_set` 用来跟踪窗口中的字符。 - 使用一个变量 `left` 代表滑动窗口的左边界,初始值为 `0`。 - 遍历字符串时,使用 `right` 作为滑动窗口的右边界。 - 如果当前字符需要加入 `char_set`,而 `char_set` 中已经有重复字符,则移动左边界,逐步从窗口中移除字符,直到重复被消除。 - 每次右边界扩展后,更新窗口最大值 `max_len`。 4. **边界条件** - 输入为空字符串时,返回 0。 - 输入中无重复字符时,返回字符串的长度。 - 输入全为相同字符时,返回 1。 --- ### 时间复杂度与空间复杂度分析 1. **时间复杂度** - 外层循环:右指针 `right` 总共只会从索引 0 移动到 n-1,各字符最多加入窗口一次,因此为 O(n)。 - 内层循环:每次有重复字符时,左指针才会移动,且整个过程中左指针 `left` 也最多移动 n 次。 - 综合来看,滑动窗口的整体操作次数为 O(n),所以时间复杂度为 **O(n)**。 2. **空间复杂度** - 额外使用了一个哈希集合 `char_set`,用于存储窗口范围内的字符。集合的最大大小不会超过输入字符串的字符集规模,最差情况下为 O(k),其中 `k` 是字符集的大小(如所有 ASCII 字符为 O(128))。 - 故空间复杂度为 **O(min(n, k))**,其中 `n` 是字符串长度。 --- ### 总结 这道题考察了滑动窗口、哈希表等经典技巧,意在测试开发者对字符串处理效率的把控能力。解法清晰、高效,是较为常见的中等难度编码面试问题。
### 问题描述 给定一个二维网格 `grid`,其中每个单元格包含一个整数,表示可以获得的金币数量。你可以从网格中的任意一个单元格出发,按照以下规则收集金币: 1. **移动规则**: 你可以从当前位置移动到其上下左右相邻的单元格,但不能移出网格边界,也不能移动到已经访问过的单元格。 2. **停止条件**: 你需要从某个开始单元格开始,在路径上收集金币,直到无法继续收集金币后停止。 3. **目标**: 找到一条路径,使得收集到的金币总数最大。 **注意**:你可以从任意一个网格中的单元格开始,但在每次路径的采集过程中,不能重复访问同一个单元格。 输入的 `grid` 是一个大小为 `m x n` 的二维数组,其中: - `grid[i][j]` 是一个非负整数,表示该位置的金币数。 - `0` 表示该位置无法进入(即不可用,障碍物)。 返回一个整数,表示能够收集到的最大金币数量。 --- ### 输入示例及输出 #### 示例 1: **输入:** ```text grid = [ [0, 6, 0], [5, 8, 7], [0, 9, 0] ] ``` **输出:** ```text 24 ``` **解释:** 最优路径为从 `(1,1)` → `(1,2)` → `(2,1)` 收集的金币,路径总金币数为 `8 + 7 + 9 = 24`。 --- #### 示例 2: **输入:** ```text grid = [ [1, 0, 7], [2, 0, 6], [3, 4, 5], [0, 3, 0] ] ``` **输出:** ```text 28 ``` **解释:** 最优路径为从 `(2,2)` → `(2,1)` → `(1,0)` → `(0,0)` 收集的金币,路径金币总数为 `5 + 4 + 2 + 1 = 28`。 --- ### 解法 #### 使用“Java”编写的代码 ```java public class MaxGoldCollector { public int getMaximumGold(int[][] grid) { int rows = grid.length; int cols = grid[0].length; int maxGold = 0; // Iterate over all cells as potential starting points for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { // Start DFS only if the cell contains gold if (grid[i][j] > 0) { maxGold = Math.max(maxGold, dfs(grid, i, j)); } } } return maxGold; } private int dfs(int[][] grid, int x, int y) { // Boundary check and obstacle check if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length || grid[x][y] == 0) { return 0; } // Capture the current cell's gold value and mark it as visited int currentGold = grid[x][y]; grid[x][y] = 0; // Temporarily mark as visited // Explore all 4 possible directions (up, down, left, right) int up = dfs(grid, x - 1, y); int down = dfs(grid, x + 1, y); int left = dfs(grid, x, y - 1); int right = dfs(grid, x, y + 1); // Restore the current cell's gold (backtracking) grid[x][y] = currentGold; // Return the maximum gold collectable from this cell return currentGold + Math.max(Math.max(up, down), Math.max(left, right)); } public static void main(String[] args) { MaxGoldCollector solver = new MaxGoldCollector(); int[][] grid1 = { {0, 6, 0}, {5, 8, 7}, {0, 9, 0} }; System.out.println(solver.getMaximumGold(grid1)); // Output: 24 int[][] grid2 = { {1, 0, 7}, {2, 0, 6}, {3, 4, 5}, {0, 3, 0} }; System.out.println(solver.getMaximumGold(grid2)); // Output: 28 } } ``` --- ### 解题逻辑的逐步讲解 1. **从每个单元格作为起点进行搜索**:对于网格中的每个单元格,如果其金币数大于零,假设它是起点,并计算从该位置可以获得的最大金币数。 2. **深度优先搜索 (DFS) 实现路径查找**:在 DFS 中,从当前单元格向其上下左右四个方向进行递归查找,直到无法继续前进为止(超出边界或碰到空单元格)。 3. **回溯处理**:为了确保路径中的单元格不会被重复访问,在访问单元格时,将其金币数标记为零(暂时清空)。递归结束后,恢复其金币数。 4. **累计最大值**:通过从所有可能的起点调用 DFS,找到最大的金币数并返回。 --- ### 时间复杂度与空间复杂度 #### 时间复杂度 设网格的尺寸为 `m x n`,同时假设平均每个单元格的 DFS 最大深度为 `d`: - **最坏情况下**,每个单元格都可能作为起点,因此需要调用一次 DFS。每次 DFS 最多访问 `m * n` 个单元格。 - 因此时间复杂度为 `O(m * n * d)`。但在实际问题中,`d` 通常不会太大。 #### 空间复杂度 - 递归调用时的栈空间:递归深度与网格的大小相关,最坏情况下需要 `O(m * n)` 的额外栈空间。 - 除此之外,算法本身未使用任何额外的数据结构。 因此,空间复杂度为 **O(m * n)**。 --- ### 总结 通过这道动态规划与回溯结合的题目,可以有效地考察 Java 开发者在编写递归、处理特殊边界情况以及实现问题优化时的能力。此实现既遵循了清晰的逻辑,又是性能合理的解法。
### 编程面试题:使用递归计算数组元素之和 #### 1. 问题描述 编写一个递归函数,用于计算一个整数数组中所有元素的和。 要求如下: - 函数接收两个参数:一个整数数组 `arr`,以及数组的长度 `n`。 - 函数需使用递归来解决问题,不能使用任何循环语句(如 `for` 或 `while`)。 - 如果数组为空(即 `n == 0`),函数应返回 0。 #### 2. 输入示例及预期输出 输入 1: ```text arr = [1, 2, 3, 4, 5] n = 5 ``` 输出 1: ```text 15 ``` 输入 2: ```text arr = [10, -2, 3] n = 3 ``` 输出 2: ```text 11 ``` 输入 3: ```text arr = [] n = 0 ``` 输出 3: ```text 0 ``` #### 3. 使用 C++ 编写的解法 ```cpp #include <iostream> #include <vector> using namespace std; // 递归函数定义 int recursiveSum(const int arr[], int n) { // 基本情况:如果数组为空 if (n == 0) { return 0; } // 递归关系:当前元素 + 剩余部分的和 return arr[n - 1] + recursiveSum(arr, n - 1); } // 主函数用于测试 int main() { // 示例输入 1 int arr1[] = {1, 2, 3, 4, 5}; int n1 = sizeof(arr1) / sizeof(arr1[0]); cout << "Sum of arr1: " << recursiveSum(arr1, n1) << endl; // 示例输入 2 int arr2[] = {10, -2, 3}; int n2 = sizeof(arr2) / sizeof(arr2[0]); cout << "Sum of arr2: " << recursiveSum(arr2, n2) << endl; // 示例输入 3 int arr3[] = {}; int n3 = sizeof(arr3) / sizeof(arr3[0]); cout << "Sum of arr3: " << recursiveSum(arr3, n3) << endl; return 0; } ``` #### 4. 解题逻辑逐步讲解 1. **递归基础:** 递归的本质是将一个问题分解为规模更小的子问题,并通过函数的自身调用来解决子问题,逐步返回结果。对于求数组的和问题,可以将数组划分为两部分:最后一个元素和其余数组,问题规模随着数组长度逐步减少,最终变为一个基本情况。 2. **基本情况:** 如果数组的长度 `n == 0`,说明已经没有任何元素,直接返回 0,这是递归的终止条件。 3. **递归关系:** 对于任意非空数组,可以将其表示为前 `n-1` 个元素以及最后一个元素的组合。因此,数组的和为: ```text recursiveSum(arr, n) = arr[n-1] + recursiveSum(arr, n-1) ``` 4. **递归调用:** 每次函数调用会将当前数组的元素 `arr[n-1]` 加到递归处理结果中。再次调用函数会进一步减少数组的规模,直到触发基本情况,递归返回开始。 5. **举例分析:** 对数组 `[1, 2, 3, 4, 5]`,递归调用的过程如下: ```text recursiveSum(arr, 5) => arr[4] + recursiveSum(arr, 4) // 5 + (递归) => arr[4] + (arr[3] + recursiveSum(arr, 3)) // 5 + 4 + (递归) => arr[4] + (arr[3] + (arr[2] + recursiveSum(arr, 2))) // 5 + 4 + 3 + (递归) => arr[4] + (arr[3] + (arr[2] + (arr[1] + recursiveSum(arr, 1)))) // 5 + 4 + 3 + 2 + (递归) => arr[4] + (arr[3] + (arr[2] + (arr[1] + (arr[0] + recursiveSum(arr, 0))))) // 5 + 4 + 3 + 2 + 1 + 0 => 15 ``` #### 5. 时间复杂度与空间复杂度分析 1. **时间复杂度:** 假设数组的大小为 `n`,每次递归调用都会减少数组的规模 1,因此递归的深度为 `n`。每次递归进行一个简单的加法操作,耗时为 `O(1)`。因此,总时间复杂度为: ```text O(n) ``` 2. **空间复杂度:** 每次递归调用都会在调用栈中创建一个栈帧以存储函数参数和局部变量,因此递归的空间复杂度等同于递归深度。在最坏情况下(数组大小为 `n`),递归深度为 `n`。因此,空间复杂度为: ```text O(n) ``` 3 tighten
快速创建符合岗位需求的编程面试题,帮助公司精准筛选技术候选人,优化招聘效率。
借助自定义难度和主题功能,定制高质量面试题,评估候选人解决实际技术问题的能力。
根据学生水平和课程内容快速生成练习题及解析,助力课堂教学和作业设计。
在资源有限的情况下快速设计技术面试评估环节,帮助公司打造高效面试流程。
为学员提供贴近企业真实场景的编程题库和优质讲解,提升学员通过面试的成功率。
帮助技术招聘人员或开发者设计高质量、针对性强的编程面试题以评估应聘者能力,同时为候选人提供学习与练习的资源。通过灵活调整难度、主题和语言,满足从初学者到高级开发者的多样化需求。
将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。
把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。
在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。
免费获取高级提示词-优惠即将到期