Java面试黄金宝典23
1. 手写 KMP 算法
- 定义
KMP(Knuth - Morris - Pratt)算法是一种高效的字符串匹配算法。它通过预处理模式串,构建一个部分匹配表(也称为最长前缀后缀表),利用已匹配的信息,避免在匹配过程中进行不必要的回溯,从而提高匹配效率。
- 要点
- 构建部分匹配表(LPS 表),记录模式串中每个位置之前的子串的最长公共前后缀长度。
- 在匹配过程中,当发生不匹配时,根据 LPS 表移动模式串的位置,避免从头开始匹配。
代码示例
java
public class KMP {
public static int kmpSearch(String text, String pattern) {
int[] lps = computeLPSArray(pattern);
int i = 0;
int j = 0;
while (i < text.length()) {
if (pattern.charAt(j) == text.charAt(i)) {
j++;
i++;
}
if (j == pattern.length()) {
return i - j;
} else if (i < text.length() && pattern.charAt(j) != text.charAt(i)) {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
return -1;
}
private static int[] computeLPSArray(String pattern) {
int[] lps = new int[pattern.length()];
int len = 0;
int i = 1;
lps[0] = 0;
while (i < pattern.length()) {
if (pattern.charAt(i) == pattern.charAt(len)) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}
public static void main(String[] args) {
String text = "ABABDABACDABABCABAB";
String pattern = "ABABCABAB";
int index = kmpSearch(text, pattern);
System.out.println("Pattern found at index: " + index);
}
}
- 应用
- 文本编辑器中的查找功能:在大文本中快速查找特定的字符串。
- 生物信息学:在 DNA 序列中查找特定的基因片段。
- 网络爬虫:在网页内容中查找特定的关键词。
2. 对一个基本有序的数组应该采用什么方式进行排序,对一个乱序的数组应该采用什么方式排序能快速找到前 n 个数?为什么?
- 基本有序数组排序
排序方式及定义
采用插入排序。插入排序是一种简单直观的排序算法,它的工作原理是将未排序的数据插入到已排序序列的合适位置。
- 原因
对于基本有序的数组,插入排序只需要进行少量的比较和移动操作。因为大部分元素已经处于正确的位置,每次插入新元素时,只需要移动少数几个元素,所以时间复杂度接近 O(n)。
- 要点
每次将一个未排序的元素插入到已排序序列的合适位置。
乱序数组找前 n 个数
- 排序方式及定义
采用堆排序。堆排序是利用堆这种数据结构所设计的一种排序算法,它可以在 O(nlogk) 的时间复杂度内找到前 k 大(或前 k 小)的数。
- 原因
通过构建一个大小为 k 的最小堆(或最大堆),遍历数组,将元素与堆顶元素比较,若满足条件则替换堆顶元素并调整堆。这样可以在不将整个数组排序的情况下,快速找到前 k 个元素。
- 要点
构建堆和调整堆的操作。
3. 给定一个数组,里面放置任意数量的随机数,如何快速统计出数组中重复的数字以及出现次数
- 定义
使用哈希表(如 HashMap
)来统计数组中每个数字的出现次数。哈希表是一种根据键值对存储数据的数据结构,通过哈希函数将键映射到存储位置,从而实现快速的查找和插入操作。
- 要点
- 使用哈希表存储数字和出现次数。
- 遍历数组更新哈希表。
代码示例
java
import java.util.HashMap;
import java.util.Map;
public class CountDuplicates {
public static void main(String[] args) {
int[] arr = {1, 2, 2, 3, 3, 3, 4, 4, 4, 4};
Map<Integer, Integer> countMap = new HashMap<>();
for (int num : arr) {
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
}
for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
if (entry.getValue() > 1) {
System.out.println("Number: " + entry.getKey() + ", Count: " + entry.getValue());
}
}
}
}
- 应用
- 数据去重:在处理大量数据时,统计重复数据的出现次数,以便进行去重操作。
- 频率分析:在文本处理中,统计单词的出现频率。
4. 给定字母集合 (a - z),求出由集合中这些字母组成的所有非空子集
- 定义
使用位运算来生成所有子集。对于一个包含 n 个元素的集合,它的子集个数为 2n。可以通过遍历从 1 到 2n−1 的所有整数,每个整数的二进制表示对应一个子集。
- 要点
- 使用位运算判断每个元素是否在子集中。
- 遍历所有可能的子集。
代码示例
java
import java.util.ArrayList;
import java.util.List;
public class Subsets {
public static List<List<Character>> getAllSubsets() {
List<List<Character>> subsets = new ArrayList<>();
char[] letters = new char[26];
for (int i = 0; i < 26; i++) {
letters[i] = (char) ('a' + i);
}
int n = letters.length;
for (int i = 1; i < (1 << n); i++) {
List<Character> subset = new ArrayList<>();
for (int j = 0; j < n; j++) {
if ((i & (1 << j)) != 0) {
subset.add(letters[j]);
}
}
subsets.add(subset);
}
return subsets;
}
public static void main(String[] args) {
List<List<Character>> subsets = getAllSubsets();
for (List<Character> subset : subsets) {
System.out.println(subset);
}
}
}
- 应用
- 组合优化问题:在求解组合优化问题时,需要枚举所有可能的组合,子集生成算法可以用于生成所有可能的组合。
- 密码学:在密码学中,需要生成所有可能的密钥组合,子集生成算法可以用于生成密钥空间。
5. 用 5 行代码实现字符个数统计
- 定义
使用 HashMap
统计字符串中每个字符的出现次数。HashMap
是 Java 中的一种哈希表实现,它可以快速地存储和查找键值对。
代码示例
java
import java.util.HashMap;
import java.util.Map;
public class CharacterCount {
public static void main(String[] args) {
String str = "hello world";
Map<Character, Integer> countMap = new HashMap<>();
for (char c : str.toCharArray()) {
countMap.put(c, countMap.getOrDefault(c, 0) + 1);
}
System.out.println(countMap);
}
}
- 应用
- 文本分析:统计文本中每个字符的出现频率,用于分析文本的特征。
- 数据压缩:根据字符的出现频率进行编码,实现数据压缩。
6. 如何反转单链表
- 定义
反转单链表是将链表中节点的指针方向反转,使得原来的头节点变成尾节点,原来的尾节点变成头节点。可以使用迭代或递归的方式实现。
- 要点
- 迭代方法:需要记录前一个节点和当前节点,在遍历链表的过程中,将当前节点的
next
指针指向前一个节点。 - 递归方法:先递归地反转后续节点,再将当前节点的
next
指针指向前一个节点,需要注意递归的终止条件。
迭代代码示例
java
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
public class ReverseLinkedList {
public static ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nextTemp = curr.next;
curr.next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
public static void main(String[] args) {
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
ListNode reversedHead = reverseList(head);
while (reversedHead != null) {
System.out.print(reversedHead.val + " ");
reversedHead = reversedHead.next;
}
}
}
递归代码示例
java
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
public class ReverseLinkedListRecursive {
public static ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode p = reverseList(head.next);
head.next.next = head;
head.next = null;
return p;
}
public static void main(String[] args) {
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
ListNode reversedHead = reverseList(head);
while (reversedHead != null) {
System.out.print(reversedHead.val + " ");
reversedHead = reversedHead.next;
}
}
}
- 应用
- 链表操作:在链表的各种操作中,反转链表是一个基本的操作,常用于解决其他链表相关的问题。
- 数据处理:在某些数据处理场景中,需要对链表进行反转操作。
7. 如何实现快速排序,快速排序的时间复杂度为什么是 O(nlogn)
- 定义
快速排序是一种基于分治法的排序算法。它选择一个基准元素,将数组分为两部分,使得左边部分的元素都小于等于基准元素,右边部分的元素都大于等于基准元素,然后递归地对左右两部分进行排序。
- 要点
- 选择基准元素。
- 分区操作,将数组分为两部分。
- 递归排序左右两部分。
代码示例
java
public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
public static void main(String[] args) {
int[] arr = {10, 7, 8, 9, 1, 5};
quickSort(arr, 0, arr.length - 1);
for (int num : arr) {
System.out.print(num + " ");
}
}
}
时间复杂度分析
快速排序的平均时间复杂度是 O(nlogn)。在每次分区操作中,需要遍历数组一次,时间复杂度为 O(n)。而递归的深度平均为 logn,因为每次分区都将数组大致分为两部分。因此,总的时间复杂度为 O(nlogn)。
- 应用
- 大规模数据排序:快速排序在平均情况下具有较高的效率,适用于大规模数据的排序。
- 编程语言的内置排序函数:很多编程语言的内置排序函数都采用了快速排序或其变种。
8. 如何实现并发场景下的多线程代码
- 定义
在 Java 中,可以通过继承 Thread
类或实现 Runnable
接口来创建线程。使用 synchronized
关键字或 Lock
接口来实现线程同步,避免多个线程同时访问共享资源导致的数据不一致问题。
- 要点
- 创建线程的两种方式:继承
Thread
类和实现Runnable
接口。 - 线程同步的方法:
synchronized
关键字和Lock
接口。
代码示例
java
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
class MyRunnable implements Runnable {
private Counter counter;
public MyRunnable(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
}
}
public class MultiThreadingExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(new MyRunnable(counter));
Thread t2 = new Thread(new MyRunnable(counter));
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + counter.getCount());
}
}
- 应用
- 服务器端编程:在服务器端处理多个客户端请求时,使用多线程可以提高服务器的并发处理能力。
- 并行计算:在需要进行大量计算的场景中,使用多线程可以将计算任务分配到多个线程中并行执行,提高计算效率。
9. 一个数组里的数据只有一个是 3 个相同的,其他都是两个相同 怎么找出这个数
- 定义
使用哈希表统计每个数字的出现次数,然后遍历哈希表,找出出现次数为 3 的数字。
- 要点
- 使用哈希表存储数字和出现次数。
- 遍历哈希表找出出现次数为 3 的数字。
代码示例
java
import java.util.HashMap;
import java.util.Map;
public class FindTripleNumber {
public static int findTriple(int[] arr) {
Map<Integer, Integer> countMap = new HashMap<>();
for (int num : arr) {
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
}
for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
if (entry.getValue() == 3) {
return entry.getKey();
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = {1, 1, 2, 2, 3, 3, 3, 4, 4};
int triple = findTriple(arr);
System.out.println("The number that appears three times is: " + triple);
}
}
- 应用
- 数据错误检测:在数据处理过程中,检测数据中是否存在异常的重复次数。
- 密码学:在密码学中,通过统计字符的出现次数来检测密码的安全性。
10. 如何实现字符转 int 型,需要考虑负数,异常等问题
- 定义
遍历字符串,将每个字符转换为数字,然后根据正负号计算最终的整数。需要处理输入为空、包含非数字字符等异常情况。
- 要点
- 处理正负号。
- 处理非数字字符。
- 处理溢出问题。
代码示例
java
public class StringToInt {
public static int myAtoi(String str) {
str = str.trim();
if (str.length() == 0) {
return 0;
}
int sign = 1;
int index = 0;
if (str.charAt(0) == '-') {
sign = -1;
index++;
} else if (str.charAt(0) == '+') {
index++;
}
long result = 0;
while (index < str.length() && Character.isDigit(str.charAt(index))) {
int digit = str.charAt(index) - '0';
result = result * 10 + digit;
if (sign == 1 && result > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
if (sign == -1 && -result < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
index++;
}
return (int) (sign * result);
}
public static void main(String[] args) {
String str = "-123";
int num = myAtoi(str);
System.out.println("Converted number: " + num);
}
}
- 应用
- 数据输入处理:在处理用户输入时,需要将字符串类型的数字转换为整数类型。
- 文件读取:在读取文件中的数字数据时,需要将字符串形式的数字转换为整数。
友情提示:本文已经整理成文档,可以到如下链接免积分下载阅读
https://download.csdn.net/download/ylfhpy/90548891