大家好,我是田哥
之前一位朋友去腾讯的面试,他的八股文准备的还是不错的,并且最后三个问题中前两个是搞定的,最后一个没有回答好,导致整个面试就GG了。
即将进入春招,希望本文对你有所帮助。
先来看问题:
自我介绍这个就不用再赘述了,可以参考我之前写过的文章:
面试中,如何介绍自己的项目经验?
另外,接下来的几个八股文
这三个问题算是烂大街了,随便找个面试八股文都能看到。
后面的三个问题,就得看你是否有准备了,面试现场手写,多多少少还是有些难度的。
你懂了,你平时会写,并不代表你在面试中就能写出来。
手写快排
在面试中,手写快速排序算法是一个常见的问题。以下是快速排序算法的详细实现,包括代码和解释。快速排序是一种高效的排序算法,采用分而治之的思想,通过递归实现。
快速排序算法的基本实现
快速排序的基本思想是:
- 将数组分为两部分,左边部分的所有元素都小于基准元素,右边部分的所有元素都大于或等于基准元素。
以下是Java实现:
public class QuickSort {
public static void quickSort(int[] array, int low, int high) {
if (low < high) {
// 找到基准元素的最终位置
int pivotIndex = partition(array, low, high);
// 递归排序基准元素左边的部分
quickSort(array, low, pivotIndex - 1);
// 递归排序基准元素右边的部分
quickSort(array, pivotIndex + 1, high);
}
}
private static int partition(int[] array, int low, int high) {
// 选择最后一个元素作为基准元素
int pivot = array[high];
// i 是小于基准元素的最后一个位置
int i = low - 1;
for (int j = low; j < high; j++) {
// 如果当前元素小于基准元素
if (array[j] < pivot) {
i++;
// 交换元素
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// 将基准元素放到正确的位置
int temp = array[i + 1];
array[i + 1] = array[high];
array[high] = temp;
// 返回基准元素的最终位置
return i + 1;
}
public static void main(String[] args) {
int[] array = {10, 7, 8, 9, 1, 5};
quickSort(array, 0, array.length - 1);
System.out.println("Sorted array: ");
for (int value : array) {
System.out.print(value + " ");
}
}
}
代码解释
- 这是快速排序的主方法,它接受一个数组和两个索引(
low
和 high
),分别表示当前要排序的子数组的起始和结束位置。 - 如果
low
小于 high
,说明当前子数组至少有两个元素,需要进行排序。 - 调用
partition
方法将数组分为两部分,并返回基准元素的最终位置。
- 使用两个指针
i
和 j
,i
用于记录小于基准元素的最后一个位置,j
用于遍历数组。 - 遍历数组,如果当前元素小于基准元素,则将
i
增加 1,并交换 i
和 j
位置的元素。 - 最后,将基准元素放到正确的位置,并返回基准元素的最终位置。
- 定义一个测试数组,并调用
quickSort
方法进行排序。
优化建议
- 为了减少快速排序的最坏情况(即数组已经有序),可以随机选择基准元素。
- 例如,在
partition
方法中,可以随机选择一个索引,然后将该索引的元素与最后一个元素交换,再进行分区操作。
- 选择数组的首、中、尾三个元素的中值作为基准元素,可以进一步提高算法的稳定性。
- 在递归调用中,可以优化尾递归,减少递归调用的深度。
- 当数组较小时,可以使用插入排序,因为插入排序在小数组上表现更好。
总结
快速排序是一种高效的排序算法,通过分而治之的思想,可以快速对数组进行排序。在面试中,手写快速排序算法不仅可以展示你的算法能力,还可以展示你对递归和数组操作的理解。通过上述代码和优化建议,你可以更好地应对面试中的快速排序问题。
算法题:按 K 位反转链表
根据搜索结果,按 K 位反转链表的问题可以通过递归或迭代的方式解决。以下是一个详细的 Java 实现,包括代码和解释:
问题描述
给定一个链表,每 k 个节点一组进行翻转。如果节点总数不是 k 的整数倍,那么将最后剩余的节点保持原有顺序。你不能只是单纯地改变节点内部的值,而是需要实际进行节点交换。
示例
- 输入:
head = [1,2,3,4,5]
, k = 2
- 输入:
head = [1,2,3,4,5]
, k = 3
解题思路
- 检查链表长度:首先检查链表的长度是否足够进行翻转。
- 翻转 k 个节点:如果长度足够,翻转前 k 个节点。
- 连接翻转后的部分:将翻转后的部分与剩余部分连接起来。
Java 实现
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null || k == 1) {
return head;
}
// 检查是否有足够的节点进行翻转
ListNode tail = head;
for (int i = 0; i < k; i++) {
if (tail == null) {
return head; // 不足 k 个节点,不需要翻转
}
tail = tail.next;
}
// 翻转前 k 个节点
ListNode newHead = reverse(head, tail);
// 递归处理剩余部分
head.next = reverseKGroup(tail, k);
return newHead;
}
// 翻转链表的前 k 个节点
private ListNode reverse(ListNode head, ListNode tail) {
ListNode prev = null;
ListNode curr = head;
while (curr != tail) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
代码解释
- 使用一个指针
tail
遍历链表,检查是否有足够的节点进行翻转。
reverse
方法使用三个指针 prev
、curr
和 next
,逐个反转节点的指针方向。
- 递归调用
reverseKGroup
方法处理剩余的链表部分。
- 将翻转后的链表的尾节点(原链表的头节点)连接到递归处理后的剩余部分。
复杂度分析
- 时间复杂度:O(n),其中 n 是链表的长度。每个节点都被遍历一次。
总结
通过递归的方式,我们可以高效地实现按 K 位反转链表的功能。这种方法不仅代码简洁,而且逻辑清晰,易于理解和实现。
一百亿个数,n 个机器,怎么排序?(桶排序)
针对“一百亿个数,n个机器,怎么排序”的问题,可以使用桶排序(Bucket Sort)结合分布式计算的思想来解决。以下是详细的解决方案和Java实现:
解题思路
- 数据分片:将一百亿个数均匀分配到n个机器上,每个机器负责处理一部分数据。
- 局部排序:在每个机器上,使用桶排序对分配到的数据进行排序。
- 合并排序:将所有机器上的排序结果合并,得到最终的排序结果。
详细步骤
1. 数据分片
将一百亿个数均匀分配到n个机器上。假设每个机器的内存足够大,可以处理分配到的数据。
2. 局部排序
在每个机器上,使用桶排序对分配到的数据进行排序。具体步骤如下:
- 确定桶的数量和范围:根据数据的范围和分布情况,确定桶的数量和每个桶的范围。
- 对每个桶内的数据排序:对每个桶内的数据使用快速排序或其他高效的排序算法进行排序。
- 合并桶内的数据:将所有桶内的数据按顺序合并,得到局部排序结果。
3. 合并排序
将所有机器上的局部排序结果合并,得到最终的排序结果。可以使用归并排序的思想进行合并。
Java实现
以下是一个简化的Java实现,假设我们有3个机器,每个机器处理一部分数据:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DistributedBucketSort {
// 桶排序
public static List<Integer> bucketSort(int[] arr, int bucketCount) {
if (arr == null || arr.length == 0) {
return Collections.emptyList();
}
int minValue = arr[0];
int maxValue = arr[0];
for (int value : arr) {
if (value < minValue) {
minValue = value;
}
if (value > maxValue) {
maxValue = value;
}
}
int range = maxValue - minValue;
List<List<Integer>> buckets = new ArrayList<>();
for (int i = 0; i < bucketCount; i++) {
buckets.add(new ArrayList<>());
}
for (int value : arr) {
int bucketIndex = (value - minValue) * (bucketCount - 1) / range;
buckets.get(bucketIndex).add(value);
}
List<Integer> sortedArray = new ArrayList<>();
for (List<Integer> bucket : buckets) {
Collections.sort(bucket); // 使用快速排序或其他排序算法
sortedArray.addAll(bucket);
}
return sortedArray;
}
// 分布式桶排序
public static List<Integer> distributedBucketSort(int[] arr, int machineCount) {
int chunkSize = arr.length / machineCount;
List<List<Integer>> chunks = new ArrayList<>();
for (int i = 0; i < machineCount; i++) {
int start = i * chunkSize;
int end = (i == machineCount - 1) ? arr.length : start + chunkSize;
List<Integer> chunk = new ArrayList<>();
for (int j = start; j < end; j++) {
chunk.add(arr[j]);
}
chunks.add(chunk);
}
List<List<Integer>> sortedChunks = new ArrayList<>();
for (List<Integer> chunk : chunks) {
int[] chunkArray = chunk.stream().mapToInt(i -> i).toArray();
sortedChunks.add(bucketSort(chunkArray, (int) Math.sqrt(chunk.size())));
}
return mergeSort(sortedChunks);
}
// 合并排序
public static List<Integer> mergeSort(List<List<Integer>> sortedChunks) {
List<Integer> result = new ArrayList<>();
int totalSize = sortedChunks.stream().mapToInt(List::size).sum();
int[] indices = new int[sortedChunks.size()];
for (int i = 0; i < totalSize; i++) {
int minIndex = -1;
int minValue = Integer.MAX_VALUE;
for (int j = 0; j < sortedChunks.size(); j++) {
if (indices[j] < sortedChunks.get(j).size() && sortedChunks.get(j).get(indices[j]) < minValue) {
minValue = sortedChunks.get(j).get(indices[j]);
minIndex = j;
}
}
result.add(minValue);
indices[minIndex]++;
}
return result;
}
public static void main(String[] args) {
int[] arr = {170, 45, 75, 90, 802, 24, 2, 66};
int machineCount = 3; // 假设有3个机器
List<Integer> sortedArray = distributedBucketSort(arr, machineCount);
System.out.println(sortedArray);
}
}
代码解释
- distributedBucketSort 方法:实现分布式桶排序。
性能分析
- 时间复杂度:假设数据均匀分布,每个机器上的桶排序时间复杂度为 O(n/m),其中 n 是数据总数,m 是机器数量。合并排序的时间复杂度为 O(nlogm)。因此,总的时间复杂度为 O(n/m+nlogm)。
- 空间复杂度:每个机器需要额外的空间来存储桶,空间复杂度为 O(n/m+k),其中 k 是桶的数量。
适用场景
- 大规模数据排序:适用于数据量非常大,单机无法处理的情况。
- 分布式计算环境:适用于有多个机器可以并行处理数据的场景。
通过上述方法,可以高效地对一百亿个数进行排序,充分利用分布式计算的优势。
总结
现在面试中,八股文已经是必备的啦,如果八股文都回答不好,可能后面的这些算法问题都不回问。
算法这个东西还得自己去学习去摸索,尤其是一二线大厂的面试,这些常规算法搞不定,那通常都是一面游。
吃得苦中苦方为人上人!加油!


