面试真实经历某节跳动大厂Java和算法问答以及答案总结(一)

114 阅读4分钟

Java面试问题与解答

  1. 常见的GC回收器
    - Serial GC: 适合单线程环境,暂停时间较长。
    - Parallel GC: 多线程垃圾回收,适合多核处理器,停顿时间较短。
    - CMS (Concurrent Mark-Sweep): 适合响应时间要求高的应用,通过多线程并发清除垃圾。
    - G1 GC: 适用于大内存系统,目标是尽量减少GC停顿时间,分区回收。​编辑

  2. SpringMVC的请求过程
    - 流程: 用户发起请求 → 前端控制器(DispatcherServlet)接收请求 → HandlerMapping根据请求路径找到对应的控制器 → Controller执行业务逻辑 → 返回ModelAndView → 视图解析器返回视图 → 渲染页面返回给用户。

​编辑

算法面试问题与解答​编辑

  1. 每K个节点翻转链表(链表k个一旋转)
    - 思路: 每次遍历K个节点进行翻转,翻转时注意更新连接的指针。
    - 解法: 使用一个指针来跟踪当前链表的位置,每次翻转K个节点,注意边界条件(如剩余节点数小于K):
    java    public ListNode reverseKGroup(ListNode head, int k) {        if (head == null || k == 1) return head;        ListNode dummy = new ListNode(0);        dummy.next = head;        ListNode prev = dummy, curr = head, next = null;        int count = 0;        while (curr != null) {            count++;            curr = curr.next;        }        curr = dummy.next;        while (count >= k) {            curr = prev.next;            next = curr.next;            for (int i = 1; i < k; i++) {                curr.next = next.next;                next.next = prev.next;                prev.next = next;                next = curr.next;            }            prev = curr;            count -= k;        }        return dummy.next;    }    

  2. 二叉搜索树转链表
    - 思路: 中序遍历二叉搜索树,将遍历结果按顺序链接为链表。
    - 解法: 使用递归或迭代的方式进行中序遍历并修改节点指针:
    java    public TreeNode flatten(TreeNode root) {        if (root == null) return null;        flatten(root.left);        flatten(root.right);        TreeNode tmp = root.right;        root.right = root.left;        root.left = null;        while (root.right != null) root = root.right;        root.right = tmp;        return root;    }    

  3. 负载均衡算法
    - 思路: 负载均衡算法有很多种,常见的有轮询(Round Robin)、加权轮询(Weighted Round Robin)、一致性哈希等。
    - 解法: 一致性哈希(Consistent Hashing)是解决分布式系统中负载均衡和节点增减的问题。​编辑
    ```java
    public class ConsistentHashing {
    private final TreeMap<Integer, String> circle = new TreeMap<>();
    private final int virtualNodeCount = 100;

       public void addNode(String node) {
for (int i = 0; i < virtualNodeCount; i++) {
circle.put((node + i).hashCode(), node);
}
}

       public String getNode(String key) {
int hash = key.hashCode();
if (!circle.containsKey(hash)) {
hash = circle.ceilingKey(hash);
}
return circle.get(hash);
}
}
```

  1. 排序算法哪些是稳定的
    - 稳定排序: 排序后,相等元素的顺序不变。常见的稳定排序包括:
    - 冒泡排序(Bubble Sort)
    - 插入排序(Insertion Sort)
    - 归并排序(Merge Sort)
    - 稳定的选择排序
    - 快速排序(Quick Sort)一般不稳定。

  2. 二叉树求和
    - 思路: 递归遍历二叉树并累加每个节点的值。
    java    public int sumOfTree(TreeNode root) {        if (root == null) return 0;        return root.val + sumOfTree(root.left) + sumOfTree(root.right);    }    

  3. 序列化和反序列化二叉树
    - 思路: 序列化将二叉树转换为字符串,反序列化则将字符串转换回二叉树。
    - 解法: 使用前序遍历进行序列化:
    ```java
    public class Codec {
    public String serialize(TreeNode root) {
    StringBuilder sb = new StringBuilder();
    serializeHelper(root, sb);
    return sb.toString();
    }

    private void serializeHelper(TreeNode node, StringBuilder sb) {
    if (node == null) {
    sb.append("null,");
    return;
    }
    sb.append(node.val).append(",");
    serializeHelper(node.left, sb);
    serializeHelper(node.right, sb);
    }

       public TreeNode deserialize(String data) {
String[] nodes = data.split(",");
Queue queue = new LinkedList<>(Arrays.asList(nodes));
return deserializeHelper(queue);
}

       private TreeNode deserializeHelper(Queue queue) {
String val = queue.poll();
if (val.equals("null")) return null;
TreeNode node = new TreeNode(Integer.parseInt(val));
node.left = deserializeHelper(queue);
node.right = deserializeHelper(queue);
return node;
}
}
```

  1. 如何判断一颗树是否是完全二叉树
    - 思路: 完全二叉树的特点是:除最底层外,其他每一层都必须完全填充,且最底层节点从左到右排列。
    - 解法: 使用层序遍历,判断是否遇到空节点后还有非空节点。
    java    public boolean isCompleteTree(TreeNode root) {        if (root == null) return true;        Queue<TreeNode> queue = new LinkedList<>();        queue.offer(root);        boolean flag = false;        while (!queue.isEmpty()) {            TreeNode node = queue.poll();            if (node == null) flag = true;            else {                if (flag) return false;                queue.offer(node.left);                queue.offer(node.right);            }        }        return true;    }    

  2. 求数组的极值点
    - 思路: 极值点是数组中比相邻两个元素大的点(局部最大)或比相邻两个元素小的点(局部最小)。
    - 解法: 使用遍历的方法找出极值点:
    java    public List<Integer> findLocalExtrema(int[] nums) {        List<Integer> extrema = new ArrayList<>();        for (int i = 1; i < nums.length - 1; i++) {            if ((nums[i] > nums[i - 1] && nums[i] > nums[i + 1]) ||                 (nums[i] < nums[i - 1] && nums[i] < nums[i + 1])) {                extrema.add(nums[i]);            }        }        return extrema;    }    

  3. 最大连续子序列
    - 思路: 采用动态规划的方法来求解最大连续子序列的和。
    - 解法:
    java    public int maxSubArray(int[] nums) {        int maxSum = nums[0], currentSum = nums[0];        for (int i = 1; i < nums.length; i++) {            currentSum = Math.max(nums[i], currentSum + nums[i]);            maxSum = Math.max(maxSum, currentSum);        }        return maxSum;    }    

  4. 回文链表
    - 思路: 使用快慢指针找中点,然后反转后半部分进行比较。
    - 解法:
    ```java
    public boolean isPalindrome(ListNode head) {
    if (head == null || head.next == null) return true;
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
    }
    ListNode secondHalf = reverse(slow);
    ListNode firstHalf = head;
    while (secondHalf != null) {
    if (firstHalf.val != secondHalf.val) return false;
    firstHalf = firstHalf.next;
    secondHalf = secondHalf.next;
    }
    return true;
    }

    private ListNode reverse(ListNode head) {
ListNode prev = null, curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
```