发布于 

华为OD后端开发岗笔面试记录

三道题, 分值分别为100, 100, 200. 做到150分以上就有面试机会, 半年内只能考一次.

笔试

第一题: 寻找子串

最后过了97%

字符串1和字符串2都是由数字和小写字母组成.
其中字符串1采用了数字和小写字母a-f作为分隔符, 可以被分割为多个子串.

要求输出字符串1的子串, 满足: 该子串中不同字符的数量不大于字符串2中不同字符的数量.
如果有多个子串, 按照子串的长度降序排列; 如果子串长度相同, 按照字母序降序排列.

输入为两行, 第一行为字符串1, 第二行为字符串2.

思路: 处理字符串, 自己写一个Comparator.

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

public class Exm1 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String str1 = in.nextLine();
String str2 = in.nextLine();

//先算str2
int str2Count = countChars(str2);

//分割str1
String[] strInStr1 = str1.split("[0-9a-f]"); // 有空字符串
Arrays.sort(strInStr1, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return countChars(o1) != countChars(o2) ? countChars(o2) - countChars(o1) : o2.compareTo(o1);
}
});

for (String s : strInStr1) {
int curCount = countChars(s);
if (curCount <= str2Count && curCount != 0) {
System.out.println(s);
return;
}
}
}

private static int countChars(String str) {
int[] strCount = new int[26];
for(char c : str.toCharArray()) {
int idx = c - 'a';
strCount[idx] = 1;
}
int charCount = 0;
for(int i = 0; i < 26; i++) charCount += strCount[i];
return charCount;
}
}

第二题: 求排列组合数

最后过了95%

系统中有两个任务, 第一个任务的执行时间为task1, 第二个任务的执行时间为task2, 总共能够执行n个任务.
求执行的n个任务可能花费的时间.
输入: task1,task2,n
输出: [time1, time2, …]

思路: 组合.

  1. 易错点在于task1和task2执行时间相同时, 数组中不能有重复的时间.
  2. 直接打印数组: Arrays.toString(arr);.
import java.util.Arrays;
import java.util.Scanner;

public class Exm2 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String[] input = in.nextLine().split(",");
int time1 = Integer.parseInt(input[0]);
int time2 = Integer.parseInt(input[1]);
int totalNum = Integer.parseInt(input[2]);

int[] res = new int[totalNum+1];
int minTime = Math.min(time1, time2);
int maxTime = Math.max(time1, time2);
int curTime = minTime * totalNum;
res[0] = curTime;
System.out.printf("[%d, ", res[0]);
for(int i = 1; i < totalNum+1; i++) {
curTime = curTime - minTime + maxTime;
res[i] = curTime;
if(i != res.length-1) System.out.printf("%d, ", res[i]);
else System.out.print(res[i]);
}
//
// //打印[3, 4, 5, 6]
// System.out.print("[");
// for(int i = 0; i < res.length; i++) {
// if(i != res.length-1) System.out.printf("%d, ", res[i]);
// else System.out.print(res[i]);
// }
System.out.println("]");
// System.out.println(Arrays.toString(res));
}
}

第三题: 求树的第x行第y个节点

最后过了83%.

输入:
第一行为节点数, 接下来的每一行第一个数字是节点的value, 后面的数是它的孩子.
最后一行是”坐标”.

输出:
{节点值}, 如果不存在节点则输出{}

样例输入:

6
15 1 2 3
6 4
7 5
23
9
11
2 2

样例输出:
{7}

代码:

import java.util.*;

public class Exm3 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 构建树
//创建节点
int num = Integer.parseInt(in.nextLine());
TreeNode[] nodes = new TreeNode[num];
List<Integer>[] childs = new List[num];
for(int i = 0; i < num; i++) {
String[] line = in.nextLine().split("\\s+");
nodes[i] = new TreeNode(Integer.parseInt(line[0]));
childs[i] = new ArrayList<Integer>();
for(int j = 1; j < line.length; j++) childs[i].add(Integer.valueOf(line[j]));
}

//连接节点
for(int i = 0; i < num; i++) {
List<Integer> cs = childs[i];
TreeNode curNode = nodes[i];
for(int idx : cs) {
curNode.childs.add(nodes[idx]);
}
}
TreeNode root = nodes[0];

// 构建完成
// 用队列进行层次遍历
String[] loc = in.nextLine().split("\\s+");
int x = Integer.parseInt(loc[0]);
int y = Integer.parseInt(loc[1]);
Queue<TreeNode> queue = new ArrayDeque<TreeNode>();
int curRow = 0;
queue.offer(root);
int curLineNum = 1;
int nextLineNum = 0;
while(curRow < x && !queue.isEmpty()) {
TreeNode cur = queue.poll();
List<TreeNode> curChilds = cur.childs;
for(TreeNode child : curChilds) {
queue.offer(child);
nextLineNum++;
}
curLineNum--;
if(curLineNum == 0) {
curLineNum = nextLineNum;
nextLineNum = 0;
curRow++;
}
}

if(queue.isEmpty()) {
System.out.println("{}");
return;
}
//寻找y
for(int j = 0; j < y && !queue.isEmpty(); j++) {
queue.poll();
}

if(queue.isEmpty()) {
System.out.println("{}");
return;
} else System.out.printf("{%d}\n", queue.poll().value);
}

private static class TreeNode {
int value;
List<TreeNode> childs = new ArrayList<>();
private TreeNode(int value, List<TreeNode> childs) {
this.value = value;
this.childs = childs;
}

private TreeNode(int value) {
this.value = value;
}
}
}

技术一面

基础知识部分

  1. 问我的代码量. 我整个懵逼了, 怎么直接问这个的? 最后我说几千应该是有的.
  2. 进程和线程了解吗?进程间怎么进行通信?
  3. 线程间是如何进行同步的?(注意区分进程通信和线程同步)
  4. 怎么优化数据库的查询速度? (没复习到这个, 详见Javaguide)
    答:
    • 使用InnoDB引擎;
    • 统一使用UTF-8编码;
    • 反范式;
    • 控制数据表的大小, 过大的表会导致修改表的存储结构(or跨区存储);
    • 限制索引数量(否则会导致插入和删除时效率变低)
    • 选择索引的顺序: 区分度最高的索引在最左侧, 最频繁使用的放在最左侧, 字段长度小的放在最左侧;
  5. HTTP和TCP了解多少? 向我介绍一下?

代码部分

  1. 删除链表后面的一个节点;
  2. 查询表A的所有项目, 并且按照B进行排列.
  3. 还有一个Linux的题目, 我给忘记了呜呜..

手撕代码

[输入] 第一行一个整数T(1 <= T <= 100), 表示有T组测试数据。对于每组测试数据:一行无序的括号,最长有3000个半括号。
[输出] 一个整数,即最少要添加的括号数使之成为满足题意的括号序列。
[样例1]

输入样例:

3
[[[
[)(]
[)

输出样例:

3
2
2

技术二面

手撕代码: 力扣554 砖墙 怎么新建一个

  1. java的进程和线程了解吗?
  2. ArrayList怎么进行扩容?
  3. ArrayList和LinkedList区别?
  4. 线程池了解吗?
  5. ThreadLocal了解吗?
  6. JVM的垃圾回收算法?
  7. Java的锁向我介绍一下?

知识点

ThreadLocal

ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

  • Thread.start() 和 run()的区别?
    直接运行run方法, 会把它当成main线程下的一个普通方法运行, 并不是多线程.
    start方法会创建新的线程, 等待分配到时间片就开始运行.
  1. ThreadLocal有什么用?
    让每个线程都绑定自己的值.
  2. ThreadLocal原理了解吗?
    每一个线程都有一个ThreadLocalMap, key是ThreadLocal, value是存储的对象.
  3. ThreadLocal内存泄漏问题是怎么导致的?
    key是弱引用而value是强引用. ThreadLocal在调用set, get, remove方法后会清理key为null的记录. 使用完ThreadLocal的方法后最好手动调用remove方法.
    补充: Java中强引用, 弱引用, 软引用, 虚引用测试
    理解Java中各种引用

tips: 软连接那里, 在命令行执行

javac softref.java
java -Xmx20m softref

线程池

  1. 什么是线程池?
    线程池就是管理一系列线程的资源池. 当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务.
  2. 为什么要使用线程池?
    节约资源; 提高响应速度; 提高线程可管理性.
  3. 如何创建线程?
    使用ThreadPoolExecutor构造函数的方式创建, 而不使用Executer.
    原因: FixedThreadPool, SingleThreadPoolScheduledThreadPool 使用无界的阻塞队列
  4. 线程池的重要参数
    一图流
    corePoolSize 任务队列未达到队列容量时, 最大可以同时运行的线程数量;
    maxiumumPoolSize 任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数;
    workQueue 新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中.
  5. 线程池的饱和策略?
    ThreadPoolExcuter.AbortPolicy: 抛出RejectedExcutionException来拒绝新任务的处理.
    ThreadPoolExcuter.CallerRunsPolicy: 把任务退回给调用者, 使用调用者的线程来执行任务.
    ThreadPoolExcuter.DiscardPolicy: 不处理新任务, 直接丢掉.
    ThreadPoolExcuter.DiscardOldestPolicy: 丢弃最早的未处理请求;
  6. 线程池常用的阻塞队列有哪些?
  • 无界队列LinkedBlockingQueue: FixedThreadPool,SingleThreadExcuter
  • 同步队列 CacheThreadPool
  • 延迟阻塞队列 ScheduledThreadPoolSingleThreadScheduledExcutor

ArrayList扩容机制

JVM垃圾回收详解

技术三面

因为前两面定级不一致, 开始第三面.
第三面主要就是挖简历.
手撕代码: 力扣105: 从前序和中序恢复二叉树.


最后定岗定到测开了, HR面完就拒了.