继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

德意志银行Java后端面试经验分享

收到一只叮咚
关注TA
已关注
手记 306
粉丝 22
获赞 113

面试在他们的办公室里进行,双方面对面。轮次安排如下所示:

  1. 笔试评估
  2. 第一次技术面试
  3. 第二次技术面试
  4. HR 面试环节
评估题:
  1. 求出数字N的阶乘,打印其前10位并验证结果。
  2. 在给定的链表中找到中间元素。
  3. 将给定的字符串转换为整数。s = “$123,840/y”
  4. 分析以下代码,找出可能存在的问题。
void 转账(Amount from, Amount to, Amount amount) {  
    synchronized (从) {  
        synchronized (到) {  
            从减少(金额);  
            到增加(金额);  
        }  
    }  
}

第一轮问题

  1. 请介绍你自己以及你的个人简介。
  2. 如果你需要在代码中为不同地区运行调度器,并且每个地区执行不同的业务规则,而不使用 if-else 语句,你将如何实现这一点?答案 — 使用策略模式
  3. 你的任务是从一组类中找出它们所属的父类,你将如何做到这一点?答案 — 使用反射
  4. Kafka 相比于 Rabbit MQ、IBMMQ 等具有哪些优势?
  5. 如果输入是 Region,并且它可能包含多个子字段,如国家、州、县、镇、村等,每个这些字段可能属于一些分组,你将如何在数据库中设计这些内容?答案 — 将表进行规范化
  6. 阐述评估中的解决方案。答案 — 如下
  7. 解释一下你在设计中使用的设计模式和 SOLID 原则。
  8. 解释一下多线程中的锁、通知和等待。
  9. 解释一下死锁的情况。
  10. 解释 Oauth2 授权与认证。
第二轮的问题如下:
  1. 详细说明如何优化阶乘计算,以便无需任何迭代或转换为字符串即可得到答案。
  2. 详细说明如何利用内置函数优化字符串转换问题。
  3. 详细说明如何实现同步,以避免提到的潜在问题。
评估解答:
  1. 如果我们要找出一个数的阶乘并打印出它的前10位,我们可以通过递归计算出那个数,然后将其转换为字符串形式,并从中提取所需的子字符串部分。
    import java.math.BigInteger;  

    public class FactorialFirst10Digits {  
        public static void main(String[] args) {  
            int num = 100; // 此数字可根据需要更改  
            BigInteger factorial = calculateFactorial(num);  

            // 将结果转换为字符串并获取前10位数字  
            String factStr = factorial.toString();  
            String first10Digits = factStr.substring(0, Math.min(10, factStr.length()));  

            System.out.println(num + " 的阶乘为: " + factorial);  
            System.out.println("前10位: " + first10Digits);  
        }  

        private static BigInteger calculateFactorial(int n) {  
            BigInteger fact = BigInteger.ONE;  
            for (int i = 2; i <= n; i++) {  
                fact = fact.multiply(BigInteger.valueOf(i));  
            }  
            return fact;  
        }  
    }

更好的解决办法:

    import java.math.BigInteger;  

    public class FactorialBigInteger {  
        public static BigInteger factorial(BigInteger n) {  
            if (n.equals(BigInteger.ZERO) || n.equals(BigInteger.ONE)) {  
                return BigInteger.ONE; // 基本情况:  
            }  
            return n.multiply(factorial(n.subtract(BigInteger.ONE))); // 递归步骤:  
        }  

        public static void main(String[] args) {  
            BigInteger num = new BigInteger("50");  
            BigInteger fact = factorial(num);  
            String factStr = fact.toString();  
            // 将结果转换为字符串并获取前10位数字  
            String first10Digits = factStr.substring(0, Math.min(10, factStr.length()));  

            // 输出结果为:  
            System.out.println("50的阶乘为: " + factStr);  
            System.out.println("前10位为: " + first10Digits);  
        }  
    }

预期优化方案:

对数求和法:
我们不直接计算 n!,而是对从 i=1 到 n 的每个 i 求 log10(i),然后将这些值相加,得到 log10(n!)。

小数部分:
分离对数的小数部分fff有助于我们捕捉有效数字。

首位数字的缩放:
通过计算 10^(f+L-1),这样就把数字调整为使得 n! 的前 L 位成为整数部分。

例如:

  • 当 n=5 时,该方法的结果是 120(正好是 5!)。
  • 当 n=100, 该方法的结果是 9332620170,这就是 100! 的前 10 位。

当 n=100 时,100! 非常大(有158位数字),但我们仍然可以不计算它的全部就能提取出它的前10位数字。

  1. 计算对数之和:

S = lg(100!) 大约是 157.97。

  1. (具体的数值其实不重要,也就是我们需要得到小数点后的部分。)

把小数点后面的部分分开:

比如说,

S≈157.97.

那么:

  1. 取157.97的整数部分就是取整后的结果157。

小数部分:f≈157.97-157=0.97。

算一下最前面的数字:

  1. 当L=10时,我们计算:
  2. 值=10^(f+L−1)=10^(0.97+10−1)=10^(0.97+9)=10^9.97。
  3. 10^9.97大约是9.33×10^9(在实际情况中,这个精确的值是依赖于计算出的对数)。当我们把它转换成long类型的时候,我们取其整数部分:
  4. ⌊10^9.97⌋≈9332620170。
  5. 这个数代表了100!的前10位数字。(实际上,我们知道100!的前10位数字是9332620170。)
    public class FactorialLeadingDigits {  
        public static void main(String[] args) {  
            int n = 100; // 例如,计算100的阶乘  
            int leadingDigitsCount = 10; // 我们想要前10位数字(即小数点前),  

            double logSum = 0.0;  

            // 计算从1到n的对数之和  
            for (int i = 1; i <= n; i++) {  
                logSum += Math.log10(i);  
            }  

            // 提取logSum的小数部分  
            double fractionalPart = logSum - Math.floor(logSum);  

            // 计算前导数字:  
            // 这样在转换为long时,  
            // 我们就能得到所需的前导数字,即  
            double leadingDigits = Math.pow(10, fractionalPart + leadingDigitsCount - 1);  

            // 转换为long会截断小数部分,从而得到前10位数字,如  
            System.out.println((long) leadingDigits);  
        }  
    }

2. 方法如下:双指针技术

  1. 慢指针(slow 每次移动 一格
  2. 快指针(fast 每次移动 两格
  3. fast 到达末尾时,slow 就会在列表的中间。
    class LinkedList {  
        Node head; // 链表的头  

        // 静态内部类 Node  
        static class Node {  
            int data;  
            Node next;  

            Node(int data) {  
                这个.data = data;  
                这个.next = null;  
            }  
        }  

        // 找到链表中间节点的方法  
        public Node findMiddle() {  
            if (head == null) return null; // 特殊情况

3. 我犯了个错误,手动排除了不同的字符,而不是应该用“\D”来排除非数字。

正确的应该是:

    public class 数字提取器 {  
        public static void main(String[] args) {  
            String str = "abc123xyz"; // 一个包含数字的示例字符串  
            int num = 提取和转换(str);  
            System.out.println("提取到的数字: " + num);  
        }  

        public static int 提取和转换(String str) {  
            // 移除所有非数字字符  
            String numericStr = str.replaceAll("\\D", "");   

            // 如果没有找到数字,则  
            if (numericStr.isEmpty()) {  
                return 0; // 如果没有找到数字,则返回默认值0(表示没有找到数字)  
            }  

            // 转换为整数  
            return Integer.parseInt(numericStr);  
        }  
    }

4. 死锁的可能性:

  • 问题: 如果两个线程同时尝试在相反方向上在同一账户之间转账,可能会导致死锁。
  • 参考: 避免使用嵌套锁是防止死锁的一种常见做法。
解决这个的办法,
    1. 总是按照一致的顺序获取锁,以防止死锁。例如,定义一个全局顺序(比如基于账户ID),并始终先锁定ID较小的账户。
    1. 只对修改共享数据的临界区代码进行同步。这样可以缩短持有锁的时间,并尽量减少这种竞争。

转账(Amount from, Amount to, Amount amount) {
金额 first = from.getId() < to.getId() ? from : to;
金额 second = from.getId() < to.getId() ? to : from;

同步 (first) {  
    同步 (second) {  
        from.借记(amount);  
        to.贷记(amount);  
    }  
}  

}

// 此函数的作用是将金额从一个账户转移到另一个账户。首先比较两个账户的ID,将较小的ID对应账户赋值给first,较大的ID对应账户赋值给second。然后使用同步机制确保在转账过程中两个账户的操作是同步的,从而避免并发问题。首先对from账户执行借记操作,然后对to账户执行贷记操作,完成转账过程。

定义一个函数 转账操作(金额 from, 金额 to, 金额 amount) {  
    synchronized (from) {  
        from.扣款(amount);  
    }  
    synchronized (to) {  
        to.存入(amount);  
    }  
}
打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP