Java面试黄金宝典20

发布于:2025-03-31 ⋅ 阅读:(19) ⋅ 点赞:(0)

1. 求二叉树深度

java

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

public class BinaryTreeDepth {
    public static int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);
        return Math.max(leftDepth, rightDepth) + 1;
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(3);
        root.left = new TreeNode(9);
        root.right = new TreeNode(20);
        root.right.left = new TreeNode(15);
        root.right.right = new TreeNode(7);
        System.out.println(maxDepth(root));
    }
}

  • 定义

二叉树的深度是指从根节点到最远叶子节点的最长路径上的节点数。此方法采用递归的方式计算,对于二叉树的每个节点,其深度等于左右子树深度的最大值加 1,当节点为空时,深度为 0。

  • 要点
  1. 递归终止条件为节点为空。
  2. 时间复杂度为 O(n),因为需要遍历每个节点。

  • 应用
  1. 文件系统分析:在分析文件系统的目录树结构时,可计算目录树的深度,以此了解文件系统的层次结构复杂程度。
  2. 数据库树形数据:在处理数据库中树形结构的数据时,计算树的深度有助于评估数据的层次分布,优化查询和存储策略。

2. 二叉树的几种遍历方式

java

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

public class BinaryTreeTraversal {
    // 前序遍历
    public static List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        preorder(root, result);
        return result;
    }

    private static void preorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        result.add(root.val);
        preorder(root.left, result);
        preorder(root.right, result);
    }

    // 中序遍历
    public static List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        inorder(root, result);
        return result;
    }

    private static void inorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        inorder(root.left, result);
        result.add(root.val);
        inorder(root.right, result);
    }

    // 后序遍历
    public static List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        postorder(root, result);
        return result;
    }

    private static void postorder(TreeNode root, List<Integer> result) {
        if (root == null) {
            return;
        }
        postorder(root.left, result);
        postorder(root.right, result);
        result.add(root.val);
    }

    // 层序遍历
    public static List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> level = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            result.add(level);
        }
        return result;
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);

        System.out.println("前序遍历: " + preorderTraversal(root));
        System.out.println("中序遍历: " + inorderTraversal(root));
        System.out.println("后序遍历: " + postorderTraversal(root));
        System.out.println("层序遍历: " + levelOrder(root));
    }
}

  • 定义
  1. 前序遍历:先访问根节点,然后递归遍历左子树,最后递归遍历右子树。
  2. 中序遍历:先递归遍历左子树,接着访问根节点,最后递归遍历右子树。
  3. 后序遍历:先递归遍历左子树,再递归遍历右子树,最后访问根节点。
  4. 层序遍历:使用队列辅助,从根节点开始,将节点依次加入队列,然后依次取出节点并将其左右子节点加入队列,直到队列为空,实现按层次遍历二叉树。

  • 要点
  1. 递归遍历的终止条件是节点为空。
  2. 层序遍历需要使用队列来辅助实现。

  • 应用
  1. 前序遍历:常用于复制二叉树、表达式树求值等场景。例如在复制二叉树时,先复制根节点,再依次复制左右子树。
  2. 中序遍历:对于二叉搜索树,中序遍历可以得到有序序列,可用于对二叉搜索树进行排序输出。
  3. 后序遍历:可用于释放二叉树的内存,因为只有在左右子树都处理完后才处理根节点,符合资源释放的逻辑。
  4. 层序遍历:适用于按层次展示树形结构数据,如在网页上展示组织结构图等。

3. 对整数分解质因数,90 = 2 * 3 * 3 * 5

java

import java.util.ArrayList;
import java.util.List;

public class PrimeFactorization {
    public static List<Integer> primeFactorize(int n) {
        List<Integer> factors = new ArrayList<>();
        for (int i = 2; i * i <= n; i++) {
            while (n % i == 0) {
                factors.add(i);
                n /= i;
            }
        }
        if (n > 1) {
            factors.add(n);
        }
        return factors;
    }

    public static void main(String[] args) {
        int num = 90;
        List<Integer> factors = primeFactorize(num);
        StringBuilder result = new StringBuilder(num + " = ");
        for (int i = 0; i < factors.size(); i++) {
            result.append(factors.get(i));
            if (i < factors.size() - 1) {
                result.append(" * ");
            }
        }
        System.out.println(result);
    }
}

  • 定义

整数分解质因数是将一个大于 1 的整数表示为若干个质数的乘积的形式。该方法从最小的质数 2 开始,不断尝试将该数分解,如果能整除则将该质数加入结果列表,并将原数除以该质数,直到不能整除为止,然后尝试下一个质数,直到原数变为 1 或者无法再分解。

  • 要点
  1. 从 2 开始尝试分解,到 n结束,因为如果一个数有大于 n​ 的因数,那么必然有一个小于 n 的因数。
  2. 最后如果原数大于 1,说明原数本身是一个质数,将其加入结果列表。

  • 应用
  1. 密码学领域:在 RSA 等加密算法中,大整数的分解质因数是破解加密的关键,但目前大整数分解质因数的计算复杂度很高,保证了加密的安全性。
  2. 数学计算:在简化分数、求最大公约数和最小公倍数等数学计算中,分解质因数是重要的基础步骤。

4. 二叉树非递归后续遍历

java

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

public class NonRecursivePostorderTraversal {
    public static List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }
        Stack<TreeNode> stack1 = new Stack<>();
        Stack<TreeNode> stack2 = new Stack<>();
        stack1.push(root);
        while (!stack1.isEmpty()) {
            TreeNode node = stack1.pop();
            stack2.push(node);
            if (node.left != null) {
                stack1.push(node.left);
            }
            if (node.right != null) {
                stack1.push(node.right);
            }
        }
        while (!stack2.isEmpty()) {
            result.add(stack2.pop().val);
        }
        return result;
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);

        System.out.println(postorderTraversal(root));
    }
}

  • 定义

二叉树的非递归后续遍历是指不使用递归的方式,按照左子树、右子树、根节点的顺序遍历二叉树。该方法使用两个栈来模拟递归过程,首先将根节点压入栈 1,然后从栈 1 弹出节点并压入栈 2,同时将该节点的左右子节点压入栈 1,最后从栈 2 弹出节点并输出。

  • 要点
  1. 使用两个栈来模拟递归过程。
  2. 注意节点入栈的顺序,先左子节点后右子节点。

  • 应用
  1. 资源管理:在处理树形结构的资源时,后序遍历可用于释放资源,确保子节点的资源先释放,再释放父节点的资源。
  2. 编译器语法树处理:编译器在处理语法树时,后序遍历可用于对语法树进行语义分析和代码生成。

5. 实现三个线程轮流打印 ABC 十次

java

class PrintABC {
    private int state = 0;
    private final Object lock = new Object();

    public void printA() {
        for (int i = 0; i < 10; i++) {
            synchronized (lock) {
                while (state % 3 != 0) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                System.out.print("A");
                state++;
                lock.notifyAll();
            }
        }
    }

    public void printB() {
        for (int i = 0; i < 10; i++) {
            synchronized (lock) {
                while (state % 3 != 1) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                System.out.print("B");
                state++;
                lock.notifyAll();
            }
        }
    }

    public void printC() {
        for (int i = 0; i < 10; i++) {
            synchronized (lock) {
                while (state % 3 != 2) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                System.out.print("C");
                state++;
                lock.notifyAll();
            }
        }
    }
}

public class PrintABCThreads {
    public static void main(String[] args) {
        PrintABC printABC = new PrintABC();

        Thread threadA = new Thread(printABC::printA);
        Thread threadB = new Thread(printABC::printB);
        Thread threadC = new Thread(printABC::printC);

        threadA.start();
        threadB.start();
        threadC.start();
    }
}

  • 定义

通过多线程编程实现三个线程轮流打印 ABC 十次,使用一个共享的状态变量 state 来控制线程的执行顺序,利用 synchronized 关键字和 wait()notifyAll() 方法来实现线程的同步和通信。每个线程在进入临界区后,检查 state 的值是否满足自己的打印条件,如果不满足则调用 wait() 方法进入等待状态,当满足条件时打印字符并更新 state 的值,然后调用 notifyAll() 方法唤醒其他线程。

  • 要点
  1. 使用 synchronized 保证线程安全。
  2. 使用 wait()notifyAll() 实现线程间的通信和同步。

  • 应用
  1. 多线程任务调度:在多个线程按顺序处理不同阶段的任务时,可采用类似的机制进行线程协调,如流水线作业中的不同工序。
  2. 并发编程资源分配:在并发编程中,多个线程对共享资源的访问需要按顺序进行时,可使用该方法进行资源分配和协调。

6. 列举集合的所有子集

java

import java.util.ArrayList;
import java.util.List;

public class Subsets {
    public static List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        int n = nums.length;
        for (int i = 0; i < (1 << n); i++) {
            List<Integer> subset = new ArrayList<>();
            for (int j = 0; j < n; j++) {
                if ((i & (1 << j)) != 0) {
                    subset.add(nums[j]);
                }
            }
            result.add(subset);
        }
        return result;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3};
        List<List<Integer>> subsets = subsets(nums);
        for (List<Integer> subset : subsets) {
            System.out.println(subset);
        }
    }
}

  • 定义

列举集合的所有子集是指找出给定集合的所有可能的子集。该方法使用位运算来生成集合的所有子集,对于一个包含 n 个元素的集合,它的子集个数为 2n,可以用一个长度为 n 的二进制数来表示每个子集,二进制数的每一位对应集合中的一个元素,如果该位为 1,则表示该元素在子集中,否则不在子集中。

  • 要点
  1. 位运算 1 << n 用于计算 2n。
  2. 位运算 i & (1 << j) 用于判断第 j 位是否为 1。

  • 应用
  1. 组合优化问题:在背包问题中,可通过列举物品集合的所有子集,找出满足背包容量限制且价值最大的物品组合。
  2. 数据库查询:在数据库查询中,可通过列举查询条件集合的所有子集,生成所有可能的查询条件组合,进行数据筛选和分析。

7. 给单链表排序,时间复杂度 O(nlogn),空间复杂度 O(1)

java

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

public class SortList {
    public static ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode slow = head;
        ListNode fast = head;
        ListNode prev = null;
        while (fast != null && fast.next != null) {
            prev = slow;
            slow = slow.next;
            fast = fast.next.next;
        }
        prev.next = null;

        ListNode left = sortList(head);
        ListNode right = sortList(slow);

        return merge(left, right);
    }

    private static ListNode merge(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                current.next = l1;
                l1 = l1.next;
            } else {
                current.next = l2;
                l2 = l2.next;
            }
            current = current.next;
        }
        if (l1 != null) {
            current.next = l1;
        }
        if (l2 != null) {
            current.next = l2;
        }
        return dummy.next;
    }

    public static void main(String[] args) {
        ListNode head = new ListNode(4);
        head.next = new ListNode(2);
        head.next.next = new ListNode(1);
        head.next.next.next = new ListNode(3);

        ListNode sortedHead = sortList(head);
        while (sortedHead != null) {
            System.out.print(sortedHead.val + " ");
            sortedHead = sortedHead.next;
        }
    }
}

  • 定义

使用归并排序的思想对单链表进行排序。首先通过快慢指针将链表分成两部分,然后分别对这两部分进行递归排序,最后将排序好的两部分合并,使得链表中的元素按升序排列,且时间复杂度为 O(nlogn),空间复杂度为 O(1)。

  • 要点
  1. 快慢指针用于找到链表的中间节点。
  2. 递归排序和合并操作是关键。

  • 应用
  1. 数据库链表数据排序:在数据库中,当数据以链表形式存储时,可使用该方法对链表数据进行排序,提高数据查询和处理效率。
  2. 游戏开发链表排序:在游戏开发中,对于链表形式的游戏对象数据,可使用该方法进行排序,如对游戏角色的属性列表进行排序。

8. 判断一个字符串能否被字典完全分词 (dp)

java

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class WordBreak {
    public static boolean wordBreak(String s, String[] wordDict) {
        Set<String> dict = new HashSet<>(Arrays.asList(wordDict));
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 0; j < i; j++) {
                if (dp[j] && dict.contains(s.substring(j, i))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }

    public static void main(String[] args) {
        String s = "leetcode";
        String[] wordDict = {"leet", "code"};
        System.out.println(wordBreak(s, wordDict));
    }
}

  • 定义

使用动态规划的方法判断一个字符串能否被字典中的单词完全分词。定义一个布尔型数组 dpdp[i] 表示字符串的前 i 个字符能否被字典中的单词完全分词。对于每个 i,遍历 0i - 1 的所有 j,如果 dp[j]trues.substring(j, i) 在字典中,则 dp[i]true

  • 要点
  1. 动态规划数组 dp 的初始化和状态转移方程。
  2. 子字符串的截取和字典的查找。

  • 应用
  1. 自然语言处理分词:在自然语言处理的分词任务中,可使用该方法判断一个句子能否由给定的词典进行分词,是中文分词等任务的基础算法之一。
  2. 搜索引擎关键词匹配:在搜索引擎中,可使用该方法判断用户输入的关键词能否由预定义的关键词字典进行分词,以提高搜索的准确性。

9. 找出链表的中间节点,链表的第 n/m 个节点

java

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

public class FindNodeInList {
    // 找出链表的中间节点
    public static ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    // 找出链表的第 n/m 个节点
    public static ListNode findNthMNode(ListNode head, int n, int m) {
        ListNode p1 = head;
        ListNode p2 = head;
        int count = 0;
        while (p1 != null) {
            count++;
            p1 = p1.next;
        }
        int target = count * n / m;
        for (int i = 0; i < target; i++) {
            p2 = p2.next;
        }
        return p2;
    }

    public static void main(String[] args) {
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = new ListNode(5);

        ListNode middle = middleNode(head);
        System.out.println("中间节点的值: " + middle.val);

        ListNode nthM = findNthMNode(head, 1, 3);
        System.out.println("第 1/3 个节点的值: " + nthM.val);
    }
}

  • 定义
  1. 找出链表的中间节点:使用快慢指针的方法,快指针每次移动两步,慢指针每次移动一步,当快指针到达链表末尾时,慢指针正好到达链表的中间。
  2. 找出链表的第 n/m 个节点:先遍历链表计算链表的长度,然后根据 n/m 计算目标节点的位置,最后再次遍历链表找到该节点。

  • 要点
  1. 快慢指针的使用。
  2. 链表长度的计算和目标节点位置的计算。

  • 应用
  1. 链表数据统计分析:在对链表数据进行统计和分析时,找出中间节点或特定比例位置的节点,有助于了解数据的分布情况。
  2. 分布式系统节点定位:在分布式系统中,链表可用于表示节点之间的关系,找出特定位置的节点可用于数据的定位和调度。

10. 打印杨辉三角

java

import java.util.ArrayList;
import java.util.List;

public class PascalTriangle {
    public static List<List<Integer>> generate(int numRows) {
        List<List<Integer>> triangle = new ArrayList<>();
        if (numRows == 0) {
            return triangle;
        }
        for (int i = 0; i < numRows; i++) {
            List<Integer> row = new ArrayList<>();
            for (int j = 0; j <= i; j++) {
                if (j == 0 || j == i) {
                    row.add(1);
                } else {
                    List<Integer> prevRow = triangle.get(i - 1);
                    row.add(prevRow.get(j - 1) + prevRow.get(j));
                }
            }
            triangle.add(row);
        }
        return triangle;
    }

    public static void main(String[] args) {
        int numRows = 5;
        List<List<Integer>> triangle = generate(numRows);
        for (List<Integer> row : triangle) {
            for (int num : row) {
                System.out.print(num + " ");
            }
            System.out.println();
        }
    }
}

  • 定义

杨辉三角是一个由数字排列成的三角形数阵,其特点是每个数等于它上方两数之和,每行的首尾数字都是 1。该方法使用两层循环,外层循环控制行数,内层循环控制每行的元素个数,根据上述规则生成杨辉三角。

  • 要点
  1. 每行首尾元素的特殊处理。
  2. 元素值的计算依赖于上一行的元素。

  • 应用
  1. 数学组合数计算:杨辉三角中的每个数对应着组合数 Cnk​,可用于计算组合数,在概率论、统计学等领域有广泛应用。
  2. 信号处理滤波器设计:在信号处理中,杨辉三角的系数可用于设计滤波器,如 FIR 滤波器的设计。

 友情提示:本文已经整理成文档,可以到如下链接免积分下载阅读

https://download.csdn.net/download/ylfhpy/90541638