分享

腾讯一面,最后一个问题给我整没了!

 田维常 2025-01-22 发布于广东

大家好,我是田哥

之前一位朋友去腾讯的面试,他的八股文准备的还是不错的,并且最后三个问题中前两个是搞定的,最后一个没有回答好,导致整个面试就GG了。

即将进入春招,希望本文对你有所帮助。

先来看问题:

  • 自我介绍
  • 介绍项目
  • 说说协程和线程区别?
  • Java 虚拟机的作用?垃圾回收的过程?
  • 了解的垃圾回收器?
  • 手写快排
  • 算法题:按 K 位反转链表
  • 一百亿个数,n 个机器,怎么排序?(桶排序)

自我介绍这个就不用再赘述了,可以参考我之前写过的文章:

面试中,如何介绍自己的项目经验?

另外,接下来的几个八股文

  • 说说协程和线程区别?
  • Java 虚拟机的作用?垃圾回收的过程?
  • 了解的垃圾回收器?

这三个问题算是烂大街了,随便找个面试八股文都能看到。

后面的三个问题,就得看你是否有准备了,面试现场手写,多多少少还是有些难度的。

你懂了,你平时会写,并不代表你在面试中就能写出来。

手写快排

在面试中,手写快速排序算法是一个常见的问题。以下是快速排序算法的详细实现,包括代码和解释。快速排序是一种高效的排序算法,采用分而治之的思想,通过递归实现。

快速排序算法的基本实现

快速排序的基本思想是:

  1. 选择一个基准元素(pivot)。
  2. 将数组分为两部分,左边部分的所有元素都小于基准元素,右边部分的所有元素都大于或等于基准元素。
  3. 递归地对左右两部分进行快速排序。

以下是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 = {1078915};
  quickSort(array, 0, array.length - 1);

  System.out.println("Sorted array: ");
  for (int value : array) {
   System.out.print(value + " ");
  }
 }
}

代码解释

  1. quickSort 方法
    • 这是快速排序的主方法,它接受一个数组和两个索引(lowhigh),分别表示当前要排序的子数组的起始和结束位置。
    • 如果 low 小于 high,说明当前子数组至少有两个元素,需要进行排序。
    • 调用 partition 方法将数组分为两部分,并返回基准元素的最终位置。
    • 递归地对基准元素左边和右边的子数组进行排序。
  2. partition 方法
    • 选择最后一个元素作为基准元素(pivot)。
    • 使用两个指针 iji 用于记录小于基准元素的最后一个位置,j 用于遍历数组。
    • 遍历数组,如果当前元素小于基准元素,则将 i 增加 1,并交换 ij 位置的元素。
    • 最后,将基准元素放到正确的位置,并返回基准元素的最终位置。
  3. main 方法
    • 定义一个测试数组,并调用 quickSort 方法进行排序。
    • 打印排序后的数组。

优化建议

  1. 随机选择基准元素
    • 为了减少快速排序的最坏情况(即数组已经有序),可以随机选择基准元素。
    • 例如,在 partition 方法中,可以随机选择一个索引,然后将该索引的元素与最后一个元素交换,再进行分区操作。
  2. 三数取中法
    • 选择数组的首、中、尾三个元素的中值作为基准元素,可以进一步提高算法的稳定性。
  3. 尾递归优化
    • 在递归调用中,可以优化尾递归,减少递归调用的深度。
  4. 小数组使用插入排序
    • 当数组较小时,可以使用插入排序,因为插入排序在小数组上表现更好。

总结

快速排序是一种高效的排序算法,通过分而治之的思想,可以快速对数组进行排序。在面试中,手写快速排序算法不仅可以展示你的算法能力,还可以展示你对递归和数组操作的理解。通过上述代码和优化建议,你可以更好地应对面试中的快速排序问题。

算法题:按 K 位反转链表

根据搜索结果,按 K 位反转链表的问题可以通过递归或迭代的方式解决。以下是一个详细的 Java 实现,包括代码和解释:

问题描述

给定一个链表,每 k 个节点一组进行翻转。如果节点总数不是 k 的整数倍,那么将最后剩余的节点保持原有顺序。你不能只是单纯地改变节点内部的值,而是需要实际进行节点交换。

示例

  • 输入:head = [1,2,3,4,5], k = 2
    • 输出:[2,1,4,3,5]
  • 输入:head = [1,2,3,4,5], k = 3
    • 输出:[3,2,1,4,5]

解题思路

  1. 检查链表长度:首先检查链表的长度是否足够进行翻转。
  2. 翻转 k 个节点:如果长度足够,翻转前 k 个节点。
  3. 递归处理剩余部分:递归处理剩余的链表部分。
  4. 连接翻转后的部分:将翻转后的部分与剩余部分连接起来。

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;
 }
}

代码解释

  1. 检查链表长度
    • 使用一个指针 tail 遍历链表,检查是否有足够的节点进行翻转。
    • 如果不足 k 个节点,直接返回原链表。
  2. 翻转 k 个节点
    • 使用 reverse 方法翻转前 k 个节点。
    • reverse 方法使用三个指针 prevcurrnext,逐个反转节点的指针方向。
  3. 递归处理剩余部分
    • 递归调用 reverseKGroup 方法处理剩余的链表部分。
    • 将翻转后的部分与剩余部分连接起来。
  4. 连接翻转后的部分
    • 将翻转后的链表的尾节点(原链表的头节点)连接到递归处理后的剩余部分。

复杂度分析

  • 时间复杂度:O(n),其中 n 是链表的长度。每个节点都被遍历一次。
  • 空间复杂度:O(n/k),递归调用栈的深度。

总结

通过递归的方式,我们可以高效地实现按 K 位反转链表的功能。这种方法不仅代码简洁,而且逻辑清晰,易于理解和实现。

一百亿个数,n 个机器,怎么排序?(桶排序)

针对“一百亿个数,n个机器,怎么排序”的问题,可以使用桶排序(Bucket Sort)结合分布式计算的思想来解决。以下是详细的解决方案和Java实现:

解题思路

  1. 数据分片:将一百亿个数均匀分配到n个机器上,每个机器负责处理一部分数据。
  2. 局部排序:在每个机器上,使用桶排序对分配到的数据进行排序。
  3. 合并排序:将所有机器上的排序结果合并,得到最终的排序结果。

详细步骤

1. 数据分片

将一百亿个数均匀分配到n个机器上。假设每个机器的内存足够大,可以处理分配到的数据。

2. 局部排序

在每个机器上,使用桶排序对分配到的数据进行排序。具体步骤如下:

  1. 确定桶的数量和范围:根据数据的范围和分布情况,确定桶的数量和每个桶的范围。
  2. 分配数据到桶:将数据分配到对应的桶中。
  3. 对每个桶内的数据排序:对每个桶内的数据使用快速排序或其他高效的排序算法进行排序。
  4. 合并桶内的数据:将所有桶内的数据按顺序合并,得到局部排序结果。
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 = {17045759080224266};
  int machineCount = 3// 假设有3个机器
  List<Integer> sortedArray = distributedBucketSort(arr, machineCount);
  System.out.println(sortedArray);
 }
}

代码解释

  1. bucketSort 方法:实现单机版的桶排序。
    • 确定数据的最小值和最大值。
    • 根据数据范围和桶的数量,将数据分配到不同的桶中。
    • 对每个桶内的数据进行排序。
    • 将所有桶内的数据按顺序合并。
  2. distributedBucketSort 方法:实现分布式桶排序。
    • 将数据均匀分配到多个机器上。
    • 每个机器对分配到的数据进行桶排序。
    • 将所有机器上的排序结果合并。
  3. mergeSort 方法:实现多路归并排序。
    • 使用一个索引数组记录每个机器上当前处理到的位置。
    • 每次选择所有机器中当前最小的元素,加入结果列表。

性能分析

  • 时间复杂度:假设数据均匀分布,每个机器上的桶排序时间复杂度为 O(n/m),其中 n 是数据总数,m 是机器数量。合并排序的时间复杂度为 O(nlogm)。因此,总的时间复杂度为 O(n/m+nlogm)。
  • 空间复杂度:每个机器需要额外的空间来存储桶,空间复杂度为 O(n/m+k),其中 k 是桶的数量。

适用场景

  • 大规模数据排序:适用于数据量非常大,单机无法处理的情况。
  • 分布式计算环境:适用于有多个机器可以并行处理数据的场景。

通过上述方法,可以高效地对一百亿个数进行排序,充分利用分布式计算的优势。

总结

现在面试中,八股文已经是必备的啦,如果八股文都回答不好,可能后面的这些算法问题都不回问。

算法这个东西还得自己去学习去摸索,尤其是一二线大厂的面试,这些常规算法搞不定,那通常都是一面游。

吃得苦中苦方为人上人!加油!

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多