一、 数据结构类面试题
1. 数组 & 字符串
这类问题通常考察对基础数据结构的熟练度和边界处理能力。
-
经典题目:
- 两数之和:给定一个数组和一个目标值,找出和为目标值的两个数。考点:哈希表的运用。
- 思路:主要有三种方案,分别是暴力解决,hash表,双指针法(有序数组)
- 暴力解决:使用两层循环,计算是否有符合num[i] + num[j] = target的值
- hash表:使用字典的方式对已经遍历过的数据进行num[i] : i的方式进行存储,如果数据可以重复使用,那么就先放入,再判断是否存在 target - num[i] 的值,如果数据不可以重复使用,那么就先判断,再放入
- 双指针,记录left,right两个指针,计算num[lift] + num[right]与target的关系,如果相等,即可返回,如果值偏小,则将left指针右移,如果值偏大,则将right指针左移,直到两指针相遇
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/TwoNumbersSum.java at main · github2me2/java-interview
- 移动零:将数组中的所有 0 移动到末尾,同时保持非零元素的相对顺序。考点:双指针(快慢指针)。
- 思路:使用快慢指针的方式,分别记录第一个非零的位置和当前遍历的位置; 慢指针记录当前第一个非零的位置的下一个元素,第二个快指针对数组进行遍历,如果当前指针的值是零,向下继续遍历,如果当前指针是非零,则将当前位置与慢指针进行交换;因为如果没有碰上零元素,两个指针会同步向后走,当出现零元素,慢指针不动,快指针继续走,所以慢指针相当于记录到的是第一个零元素的位置,当出现交换的时候,实际是非零与第一个零元素的交换,而如果后面又出现了非零,慢指针则逐渐向后走,直到快指针遍历结束,慢指针始终指向最后一个非零元素的下一个元素
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/MoveZero.java at main · github2me2/java-interview
- 盛最多水的容器:给定一个高度数组,找出两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。考点:双指针(对撞指针)。
- 思路:从两端指针向中间递进,计算最大面积并更新,一直到两个指针相遇;每次更新都是更新较短的指针,这样,虽然宽度变短了,但是长度有可能增加,从而获取到更大的容量
- 拓展问题:接雨水问题,如果所有的高度同时存在,能形成多个可以容纳雨水的容器,可以容纳多少雨水
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/ContainerWithMostWater.java at main · github2me2/java-interview
- 最长无重复字符子串:给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。考点:滑动窗口 + 哈希表。
- 思路:使用一个指针记录最后一个不重复元素的位置,使用哈希表记录字符出现的位置,对字符串进行迭代,每次迭代时判断当前字符是否以及存在过,如果存在,则将指针更新为已存在的位置的下一个;将字符放入哈希表,用当前下标减去指针位置,更新最大长度,继续向下迭代
- 拓展问题:最多包含K个不同字符的最长子串;至少包含K个重复字符的最长子串
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/LongestSubstringWithoutRepeatingCharacters.java at main · github2me2/java-interview
- 有效的括号:给定一个只包括
'(',')','{','}','[',']'的字符串,判断是否有效。考点:栈的应用。- 思路:使用栈的先进后出的特点对数据进行匹配,当遇到左括号时,将其压入栈,当遇到右括号时,从栈中弹出元素,如果匹配,则进行下一个,不匹配则返回false,当遍历结束后,如果栈中还有元素,则返回false,如果栈为空,则返回true
- 拓展问题:只有一种括号,可以简化使用计数器来完成;包含其他字符,需要维护合法括号map,只对合法值进行处理;最长有效括号子串,统计长度,统计数量,获取实际字串等,可以使用栈,动态规划,双指针解决;
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/ValidParentheses.java at main · github2me2/java-interview
- 二分法查找:给定一个有序数组,要求查找出指定的值
- 思路:记录当前的范围的左右指针,每次折半即左右指针之差/2之后加上左指针得到当前索引,如果值相等,则返回当前索引,如果值小于目标值,则说明目标值在右侧,更新左指针为当前索引加一,如果值大于目标值,说明目标值在左侧,更新右指针为当前索引减一,需要注意循环条件应该是左指针小于等于右指针,而不是小于
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/array/BinarySearch.java at main · github2me2/java-interview
- 两数之和:给定一个数组和一个目标值,找出和为目标值的两个数。考点:哈希表的运用。
2. 链表
链表问题常考察指针操作、边界情况处理以及是否引入哑节点。
-
经典题目:
- 反转链表:经典入门题,考察指针的指向变换。有递归和迭代两种写法。
- 思路:迭代法,递归法,头插法
- 迭代法:使用prev,curr,next三个指针,逐个反转节点的指向
- 递归法:递归到链表末尾,然后从后向前反转
- 头插法:遍历原链表,将每个节点插入到新链表的头部
- 拓展问题:反转一部分;k个一组的进行反转;反转相邻节点
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/linked/ReverseLinkedList.java at main · github2me2/java-interview
- 链表中环的检测:判断链表中是否有环。考点:快慢指针(Floyd判圈算法)。
- 思路:hash法,快慢指针法(floyd判圈算法)
- hash法:遍历链表将每个节点存入hash表中(也可以使用hashset),然后判断是否已经在hash表中,如果有,则代表有环
- 快慢指针法:使用两个指针对链表进行遍历,慢指针每步前进一个,快指针每步前进两个,如果快慢指针相遇,则说明有环,如果快指针先走到的null节点,则说明无环(存在数学上的证明成立)
- 拓展问题:找到环的入口;
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/linked/LinkedListCycle.java at main · github2me2/java-interview
- 合并两个有序链表:将两个升序链表合并为一个新的升序链表。考点:递归或迭代。
- 思路:迭代法,递归法,核心思想是每次对两个链表的头节点进行比较,将较小的拼到新的链表之后
- 迭代法:同时遍历两个链表,需要创建新的链表,最后需要合并剩余的节点
- 递归法:每次判断是否有节点已经结束,否则将另一个节点的结果拼接到当前节点之后
- 拓展问题:合并k个有序链表,原地合并(不创建新节点),降序合并
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/linked/MergeTwoLinkedLists.java at main · github2me2/java-interview
- 删除链表的倒数第 N 个结点:考点:双指针(快指针先走N步,然后快慢指针一起走)。
- 思路:双指针,两次遍历法
- 双指针:使用快慢两个指针,间距n,当快指针走到链表末尾的时候,删除慢指针所在的节点即可
- 两次遍历法:第一次遍历获取链表的长度,然后计算要删除的位置,第二次遍历的时候进行删除
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/linked/DeleteLastN.java at main · github2me2/java-interview
- 相交链表:找到两个单链表相交的起始节点。考点:双指针的巧妙运用。
- 思路:双指针,两次遍历法
- 双指针:两个指针分别从两个链表的头节点出发,当走到末尾时,跳到另一个链表的头节点继续执行,如果存在相交点,两个指针最终会相遇,如果不相交,会同时变为null。假设链表a的长度为m,b的长度为n,公共部分为c,指针1走过的路径m + (n-c) ,指针2走过的路径 n + (m - c),两者总路程相等
- 两次遍历法:先让较长的向后遍历两个链表的差值,然后同时向后遍历,如果有相同,则存在相交,如果不相交,两个链表同时变为null
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/linked/IntersectingLinkedLists.java at main · github2me2/java-interview · GitHub
- 反转链表:经典入门题,考察指针的指向变换。有递归和迭代两种写法。
3. 栈 & 队列
考察这两种线性结构的特性和应用场景。
-
经典题目:
- 最小栈:设计一个支持
push,pop,top操作,并能在常数时间内检索到最小元素的栈。考点:辅助栈的使用。- 思路:利用辅助栈进行计数,记录当前值的同时记录当前最小值,每次弹出时,如果弹出值和当前辅助栈中的栈顶大小一致,则弹出最小栈的栈顶元素,可以使用已有的数据结构实现,例如数组,链表,java中栈的实现等
- 拓展问题:最大值,同时获取最大最小值
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/stack/MinStack.java at main · github2me2/java-interview · GitHub
- 用栈实现队列:使用栈实现队列的基本操作。考点:双栈(一个入,一个出)。
- 思路:栈为先进后出,队列为先进先出,因此需要使用另一个辅助栈存储倒序列表,当取出值时从辅助栈中取值,当辅助栈为空后,将主栈中的元素存入辅助栈,存入数据时正常存入主栈
- 拓展问题:用队列实现栈
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/stack/StackQueue.java at main · github2me2/java-interview · GitHub
- 滑动窗口最大值:给定一个数组和滑动窗口的大小,找出所有滑动窗口里的最大值。考点:单调队列。
- 思路:使用双端队列维护一个索引的队列,存储窗口中的元素索引,每次都将索引存入队列,在遍历的时候去除已经离开当前窗口的索引,然后去除所有比当前遍历值小的索引,剩下的就是当前窗口内最大的值,然后存储到结果数组中。遍历结束,结果数组中即所有窗口中的最大值。
- 拓展问题:滑动窗口最小值,滑动窗口中位数,滑动窗口和,滑动窗口平均数
- 代码示例:java-interview/0-data-structures-and-algorithms/src/main/java/org/interview/stack/MaxSlidingWindow.java at main · github2me2/java-interview · GitHub
- 最小栈:设计一个支持
4. 哈希表
考察利用空间换时间的思想。
-
经典题目:
- 两数之和:(哈希表的经典应用)。
- 最长无重复字符子串:(需要哈希表记录字符位置)。
- 字母异位词分组:给定一个字符串数组,将字母异位词组合在一起。考点:哈希表的键设计(可以用排序后的字符串或字符计数数组作为键)。
5. 树
二叉树是面试的重中之重,广泛考察递归和遍历。
-
经典题目:
- 二叉树的最大深度:基础递归题。
- 二叉树的层序遍历:广度优先搜索(BFS)的典型应用。
- 二叉树的最近公共祖先:求有根树中两个节点的最近公共祖先。考点:递归。
- 验证二叉搜索树:判断一个二叉树是否是有效的二叉搜索树(BST)。考点:中序遍历的性质或递归传递上下界。
- 二叉树的序列化与反序列化:将二叉树转换成一个字符串,并且能将这个字符串还原成原始的二叉树。考点:各种遍历方式。
6. 堆
常用来解决 Top K 问题。
-
经典题目:
- 数组中的第K个最大元素:考点:最小堆(维护大小为K的堆)或快速选择算法。
- 前K个高频元素:给定一个非空的整数数组,返回其中出现频率前 k 高的元素。考点:哈希表统计频率 + 最小堆。
二、 算法类面试题
1. 排序 & 搜索
考察对基础算法的理解和应用。
-
经典题目:
- 合并两个有序数组:给定两个有序整数数组
nums1和nums2,将nums2合并到nums1中,使nums1成为一个有序数组。考点:逆向双指针。 - 第一个错误的版本:假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。考点:二分查找的变体。
- 合并两个有序数组:给定两个有序整数数组
2. 动态规划
面试难点,核心是找到“状态”和“状态转移方程”。
-
经典题目:
- 爬楼梯:每次你可以爬 1 或 2 个台阶。有多少种不同的方法可以爬到楼顶?最简单的DP入门题。
- 买卖股票的最佳时机系列:有多种变体(如只能买卖一次、可以买卖多次、含冷冻期等)。考点:状态机DP。
- 最长递增子序列:给你一个整数数组,找到其中最长严格递增子序列的长度。考点:经典DP或贪心+二分。
- 最大子数组和:给你一个整数数组
nums,请你找出一个具有最大和的连续子数组,返回其最大和。考点:Kadane算法。 - 编辑距离:给你两个单词
word1和word2,请返回将word1转换成word2所使用的最少操作数(插入、删除、替换)。考点:二维DP经典问题。
3. 回溯算法
用于解决排列、组合、分割等问题,本质是暴力穷举的优化(剪枝)。
-
经典题目:
- 全排列:给定一个不含重复数字的数组
nums,返回其所有可能的全排列。考点:回溯模板。 - 子集:给定一组不含重复元素的整数数组
nums,返回该数组所有可能的子集。考点:回溯或迭代。 - 括号生成:数字
n代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。考点:回溯 + 剪枝条件(左括号数 < n, 右括号数 < 左括号数)。 - N 皇后:经典难题。
- 全排列:给定一个不含重复数字的数组
4. 贪心算法
在每一步选择中都采取在当前状态下最好或最优的选择。
-
经典题目:
- 买卖股票的最佳时机 II:你可以尽可能地完成更多的交易(多次买卖一支股票)。考点:贪心(收集所有上坡)。
- 跳跃游戏:给定一个非负整数数组
nums,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。考点:贪心维护最远可达距离。
5. 图
考察图的表示和遍历(BFS/DFS)。
-
经典题目:
- 岛屿数量:给你一个由
'1'(陆地)和'0'(水)组成的的二维网格,请你计算网格中岛屿的数量。考点:DFS/BFS 遍历连通分量。 - 课程表:你这个学期必须选修
numCourses门课程,记为0到numCourses - 1。在选修某些课程之前需要一些先修课程。判断是否可能完成所有课程的学习?考点:拓扑排序(判断有向图是否有环)。
- 岛屿数量:给你一个由