经典java 面试题详解
- 请谈谈Java中的多态性是什么?
答:多态性是指一个对象可以被不同的角度来看待,也可以有多种形态。Java中有两种形态:静态多态和动态多态。静态多态是指方法的重载,而动态多态则是指方法的覆盖。
- Java中什么是反射?
答:反射是指在运行时动态地获取类的信息并对类进行操作的一种机制。通过反射可以在运行时获取类的名称、属性、方法等信息,并对其进行访问和修改。
- 请谈谈Java中的异常是什么?
答:Java中的异常是指程序在运行中出现的错误,包括运行时异常和检查时异常。运行时异常通常是由于程序逻辑错误导致的,而检查时异常则是由于外部因素导致的,比如输入输出问题。
- 请谈谈Java中的HashMap是如何工作的?
答:HashMap是一种基于哈希表实现的映射表,用于存储键-值对。在HashMap中,每个键都必须是唯一的,而值可以不唯一。HashMap通过将键的哈希值映射到内部数组的索引来查找对应的值。
- 请谈谈Java中的线程是什么?
答:Java中的线程是指程序执行的最小单位,可以同时运行多个线程以实现并发操作。线程包含了一个执行上下文(堆栈、计数器等)以及消耗的资源(内存、CPU时间等)。
- 请谈谈Java中的集合是什么?
答:Java中的集合是指一组元素的容器,可以用来存储、操作和访问数据。Java中的集合分为List、Set、Queue和Map四类,分别用来存储有序的元素列表、无序的元素集合、先进先出的元素队列和键-值对映射表。
- 请谈谈Java中的静态变量和静态方法是什么?
答:Java中的静态变量和静态方法是指属于类而不是属于对象的变量和方法。静态变量和静态方法可以通过类名直接访问,无需创建对象。静态变量的值对所有对象都是一样的,而静态方法只能访问静态变量和调用静态方法。
- 请谈谈Java中的注解是什么?
答:Java中的注解是一种元数据,用于向编译器或运行时环境提供说明性信息。Java中的注解可以修饰类、方法、字段等元素,注解本身是通过Java代码编写的,可以在编译时保留或者运行时保留。
- 请谈谈Java中的字符串是如何工作的?
答:Java中的字符串是一种不可变的对象,每次对字符串的修改都会创建一个新的字符串对象。Java中的字符串使用Unicode编码表示,每个字符占用两个字节。
- 请谈谈Java中的设计模式是什么?
答:Java中的设计模式是一种通用的解决方案,用于解决各种问题。设计模式把一些常用的解决方案抽象成类、接口和对象,并通过组合和继承等方式实现不同的功能。Java中的常用设计模式有单例模式、工厂模式、装饰器模式、观察者模式等。
经典机试真题:
题目描述:
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。
要求算法的时间复杂度为 O(log(m+n))。
示例1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例二:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
解题思路:
对于两个长度分别为m和n的有序数组,可以将其视为列,通过列的方式求中位数。
假设将两个有序数组A,B合并到一起,将这两个数组分别在i,j处分成两部分:A[0:i-1],A[i,m-1]和B[0:j-1],B[j,n-1],其中i+j=m-i+n-j(或者j=m-i+n-j),如果这个划分恰好满足如下条件:
-
A[i-1]≤B[j] && B[j-1]≤A[i]
-
将两个集合合并在一起后的长度为偶数时:(max(A[i-1],B[j-1])+min(A[i],B[j]))/2
-
将两个集合合并在一起后的长度为奇数时:max(A[i-1],B[j-1])
那么这个数就是中位数。
为了满足这些条件,可以对于这两个有序数组进行二分查找,找到恰当的i和j,使得上述条件满足。
程序解释如下:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
// 确保 nums1 的长度小于等于 nums2 的长度
if (m > n) {
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
int tmp = m;
m = n;
n = tmp;
}
int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
while (iMin <= iMax) {
int i = (iMin + iMax) / 2;
int j = halfLen - i;
if (i < iMax && nums2[j - 1] > nums1[i]) {
iMin = i + 1; // i 太小,增大它
} else if (i > iMin && nums1[i - 1] > nums2[j]) {
iMax = i - 1; // i 太大,减小它
} else {
int maxLeft = 0;
if (i == 0) { maxLeft = nums2[j - 1]; }
else if (j == 0) { maxLeft = nums1[i - 1]; }
else { maxLeft = Math.max(nums1[i - 1], nums2[j - 1]); }
if ((m + n) % 2 == 1) { return maxLeft; }
int minRight = 0;
if (i == m) { minRight = nums2[j]; }
else if (j == n) { minRight = nums1[i]; }
else { minRight = Math.min(nums2[j], nums1[i]); }
return (maxLeft + minRight) / 2.0;
}
}
return 0.0;
}
}
这是一个求两个有序数组的中位数的解法,时间复杂度为O(log (m+n))。
- 最短回文串
题目描述:
给定一个字符串 s, 你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。
示例1:
输入: s = “aacecaaa”
输出: “aaacecaaa”
示例2:
输入: s = “abcd”
输出: “dcbabcd”
题解:
首先需要找到s的最长回文前缀,然后将s的所有未匹配的字符反转,并拼接在s前即可。
比如对于字符串s = “aacecaaa"来说,最长回文前缀就是"aa”,反转后未匹配的字符就是"cec",将其反转并拼接在字符串前面,得到的最短回文串就是"aaacecaaa"。
Java代码实现:
class Solution {
public String shortestPalindrome(String s) {
int j = 0;
for (int i = s.length() - 1; i >= 0; i–) {
if (s.charAt(i) == s.charAt(j)) {
j += 1;
}
}
if (j == s.length()) {
return s;
}
String suffix = s.substring(j);
String prefix = new StringBuilder(suffix).reverse().toString();
String mid = shortestPalindrome(s.substring(0, j));
return prefix + mid + suffix;
}
}
时间复杂度:O(n^2)
- 最大子序和
题目描述:
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例2:
输入:nums = [1]
输出:1
示例3:
输入:nums = [0]
输出:0
题解:
我们可以定义状态f[i]表示以第i个元素结尾的最大子序和,那么有:
- f[0] = nums[0]
- f[i] = max(f[i-1] + nums[i], nums[i])
用一个变量maxSum来记录f数组中的最大值即可。
Java代码实现:
class Solution {
public int maxSubArray(int[] nums) {
int maxSum = nums[0];
int[] f = new int[nums.length];
f[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
f[i] = Math.max(f[i-1] + nums[i], nums[i]);
maxSum = Math.max(maxSum, f[i]);
}
return maxSum;
}
}
时间复杂度:O(n)
- 重建二叉树
题目描述:
根据一棵树的前序遍历和中序遍历构造二叉树。
示例:
输入:
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
输出:
3
/
9 20
/
15 7
题解:
前序遍历的第一个元素是根节点,根据根节点在中序遍历中的位置可以将中序遍历分成左右两部分。左边是根节点的左子树,右边是根节点的右子树。这样就可以递归的构建左子树和右子树。
Java代码实现:
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
Map<Integer, Integer> indexMap = new HashMap<>();
for (int i = 0; i < n; ++i) {
indexMap.put(inorder[i], i);
}
return build(preorder, inorder, 0, n - 1, 0, n - 1, indexMap);
}
private TreeNode build(int[] preorder, int[] inorder, int preStart, int preEnd, int inStart, int inEnd, Map<Integer, Integer> indexMap) {
if (preStart > preEnd) {
return null;
}
int rootVal = preorder[preStart];
TreeNode root = new TreeNode(rootVal);
int rootIndex = indexMap.get(rootVal);
int leftSize = rootIndex - inStart;
root.left = build(preorder, inorder, preStart + 1, preStart + leftSize, inStart, rootIndex - 1, indexMap);
root.right = build(preorder, inorder, preStart + leftSize + 1, preEnd, rootIndex + 1, inEnd, indexMap);
return root;
}
}
时间复杂度:O(n)
- 正则表达式匹配
题目描述:
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
输入: s = “aa” p = “a*”
输出: true
解释: ‘*’ 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 ‘a’。因此,字符串 “aa” 可被视为 ‘a’ 重复了一次。
输入: s = “mississippi” p = “misisp*.”
输出: false
题解:
本题需要用到动态规划来进行求解。
假设dp[i][j]表示字符串s的前i个字符和p的前j个字符是否匹配。
当p[j-1]!='*'时,dp[i][j] = dp[i-1][j-1] && (s[i-1] == p[j-1] || p[j-1] == ‘.’)
当p[j-1]=='*'时,分为以下两种情况:
- '*'匹配0个前元素,即忽略p的前j-2个字符,dp[i][j] = dp[i][j-2]
- '*'匹配1个或多个前元素,即s的第i个字符匹配p的前j-2个字符,且s的前i-1个字符和p的前j个字符也匹配,dp[i][j] = dp[i-1][j] && (s[i-1] == p[j-2] || p[j-2] == ‘.’)
Java代码实现:
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
for (int i = 0; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (p.charAt(j - 1) != ‘*’) {
dp[i][j] = i > 0 && dp[i - 1][j - 1] && (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == ‘.’);
} else {
dp[i][j] = dp[i][j - 2] || (i > 0 && dp[i - 1][j] && (s.charAt(i - 1) == p.charAt(j - 2) || p.charAt(j - 2) == ‘.’));
}
}
}
return dp[m][n];
}
}
时间复杂度:O(mn)