以下是本人在以往的后端面试中遇到的一些题目的小整理,答案和分析仅供参考,未必正确,如果发现有错误的地方,欢迎指出。
1、有n个台阶,小明一次能跨i个台阶(如:3个台阶即是1或2或3个台阶),问小明有多少种上台阶的组合?
答:
当n为1时,1,共1;
当n为2时,1-1,2,共2;
当n为3时,1-1-1,1-2,2-1,3,共4;
当n为4时,1-1-1-1,1-1-2,1-2-1,1-3,2-1-1,2-2,3-1,4,共8;
当n为5时,1-1-1-1-1,1-1-1-2,1-1-2-1,1-1-3,1-2-1-1,1-2-2,1-3-1,1-4,2-1-1-1,2-1-2,2-2-1,2-3,3-1-1,3-2,4-1,5,共16;
从上可以看出,当n为4时,若第1个为1,后面的组合刚好就是n为3时(4-1=3)的组合,往前往后看都是这样,所以可以用递归法,StepCombination.java文件的代码如下:
import java.util.Scanner;
/**
* 有n个台阶,小明一次能跨i个台阶(如:3个台阶即是1或2或3个台阶),问小明有多少种上台阶的组合
*/
public class StepCombination {
static int m = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Please input n:");
int n = Integer.parseInt(scanner.next());
scanner.close();
combinate(n);
System.out.println("m:" + m);
}
public static void combinate(int n) {
for (int i = 1; i <= n; ++i) {
if (n - i == 0) {
++m;
} else {
combinate(i);
}
}
}
}
运行结果如下:
跨台阶组合问题运行结果
2、怎么从a表查出b表?又怎么从b表查出a表?数据如下:
a表:
name subject score
张三 语文 70
张三 数学 92
张三 英语 95
李四 语文 78
李四 数学 85
李四 英语 93
b表:
name 语文 数学 英语
张三 70 92 95
李四 78 85 93
答:
1)从a表查出b表:(mysql行转列)
select name, max(case subject when '语文' then score else 0 end) 语文, max(case subject when '数学' then score else 0 end) 数学, max(case subject when '英语' then score else 0 end) 英语 from a group by name;
运行结果:
从a表查出b表运行结果
分析:关键是利用case when then else end进行记录的筛选,case后面的subject表示条件变量,when表示把条件变量设为'语文',then表示取这时符合条件的记录中的score字段的值,else 0表示当when后设置的值不符合条件时取值为0,end作为结束的标识符。
注意:max函数比较的是每个组里的数据,这里的max函数比较的是在每个组的基础上再加上函数括号内的条件的数据,范围更小一点,所以比较的数据也包括0,如果改为min函数的话,查出的结果都会是0。
2)从b表查出a表:(mysql列转行)
select name, '语文' subject, chinese_score as score from b union select name, '数学' subject, math_score as score from b union select name, '英语' subject, english_score as score from b order by name;
运行结果:
从b表查出a表运行结果
分析:关键是利用union进行记录的纵向合并,最后再用order by以name排下序。
3、4个女人晚上过桥,她们都需要手电筒才能过桥,手电筒只能传递,女人1过桥需要1分钟,女人2过桥需要2分钟,女人3过桥需要5分钟,女人4过桥需要10分钟,过1次过桥最多2个女人,若2人一起过桥速度快的要迎合速度慢的,问怎么在17分钟内4个女人都过桥?
答:
女人1和女人2一起过桥,花费2分钟;
女人1回来,花费1分钟;
女人3和女人4一起过桥,花费10分钟;
女人2回来,花费2分钟;
女人1和女人2一起过桥,花费2分钟;
一共刚好就是17分钟。
4、用1条sql语句查出每门课程都大于79分的学生姓名,再用1条sql语句查出平均分大于80分的学生姓名,数据如下:
name course score
张三 语文 85
张三 数学 75
李四 语文 76
李四 数学 90
王五 语文 81
王五 数学 100
王五 英语 90
李四 英语 95
答:
1)查每门课程都大于79分的学生姓名:
select distinct name from grade where name not in (select distinct name from grade where score <= 79);
分析:先找出小于等于79分的学生姓名,然后再用not in找出不在这里面的学生姓名,要注意用distinct对名字去重。
或者:
select name from grade group by name having min(score) > 79;
分析:对表以学生姓名进行分组,再通过having对每组进行筛选,使用min函数找出每组的分数最小值,如果该组的分数最小值都大于79,那么就是每门课程都大于79。
运行结果:
每门课程都大于79分正确运行结果
以下写法是错误的:
select name from grade where score > 79 group by name order by score desc;
分析:这里的where只能对整体结果进行筛选,后面的order by排序排的是分组后的整体结果,不是对每个组进行排序,而且用了group by,也就是每个组的数据都有了,但是每门课程都大于79分的学生不一定每个组都有,执行结果会是这样:
每门课程都大于79分错误运行结果
2)查平均分大于80分的学生姓名:
select name, avg(score) from grade group by name having avg(score) > 80;
运行结果:
平均分大于80分方法1运行结果
分析:通过姓名分组,用having找出平均分大于80分的组。
或者:
select name from (select count(*) count, sum(score) sum, name from grade group by name) as a where a.sum > 80 * count;
运行结果:
平均分大于80分方法2运行结果
分析:通过姓名分组找出每组的分数总和以及记录数,把此命名为表a,放到from后,再查这个表a,通过分数总和是否大于80乘以记录数,来判断平均分是否大于80分。
5、写1条sql语句找出各个部门中工资最高的员工,以下是表的结构:
表名:employee
id name salary departmen_id
1 Joe 70000 1
2 Henry 80000 2
3 Sam 60000 2
4 Max 90000 1
5 Tommy 80000 2
表名:department
id name
1 IT
2 Sales
要求结果如下:
department employee salary
Sales Henry 80000
IT Max 90000
Sales Tommy 80000
答:
select dep.name as department, emp.name as employee, emp.salary from department dep, employee emp where emp.department_id = dep.id and emp.salary = (select max(salary) from employee e where e.department_id = dep.id);
或者用join on的方式合表:
select department.name as department, employee.name as employee, employee.salary as salary from employee join department on employee.department_id = department.id and employee.salary = (select max(salary) from employee e where e.department_id = employee.department_id);
或者把and改为where:
select department.name as department, employee.name as employee, employee.salary as salary from employee join department on employee.department_id = department.id where employee.salary = (select max(salary) from employee e where e.department_id = employee.department_id);
运行结果如下:
找部门工资最高员工正确运行结果
分析:先department表和employee表合表,再根据工资条件挑选记录。在合表中根据工资挑选记录时,是逐条比较是否符合条件的,例如:当id=1时,合表中salary为70000,department_id为1,这时子查询的employee表就要寻找department_id为1的记录,找到是id为1和id为4的,然后因为max(salary)所以找出工资最大值为90000,而70000不等于90000,所以合表中id=1的记录不符合子查询条件。也可以这么理解,判断合表的salary是否等于一个数,但是这个数是个可变的数,对合表的每条记录来说可能是不同的。
以下写法是错误的:
select department.name as department, employee.name as employee, max(employee.salary) as salary from employee join department on employee.department_id = department.id group by employee.department_id;
运行结果会是:
找部门工资最高员工错误运行结果
分析:因为max(employee.salary)的取最大值只针对这一字段,不会影响到其他字段,前面的department和employee字段还是取group by分组后的第一条记录,但salary就取了分组后组内的最大值。那么首先得到的结果就是名字部门和薪水不对应的;其次只能显示部门只有一个薪资最高的情况,如果部门内有多个人薪资同时达到最高,就只显示第一个了。
6、订单表user_order结构和数据如下,请编写sql语句查出首次下单日期是2018年05月22号的用户数量,注意是首次下单。
id user_id product price create_date
1 234 坚果Pro2 1400 '2018-05-21'
2 234 锤子TNT 8888 '2018-05-22'
3 356 小米mix 2400 '2018-05-22'
4 357 硅胶娃娃 10400 '2018-05-23'
5 234 华为Mate 5999 '2018-05-20'
6 356 魅族Note 2000 '2018-05-24'
7 234 荣耀畅玩 1999 '2018-05-23'
8 789 三星Note8 7999 '2018-05-19'
9 456 三星Note7 6999 '2018-05-22'
10 123 Vivo柔光 3999 '2018-04-28'
答:
执行以下sql语句,
select * from user_order group by user_id having min(create_date) = '2018-05-22';
运行结果是:
查出首次下单是某天的分析过程
但是题目要求的是查出用户数量,所以正确的是以下sql语句,
select count(*) from (select * from user_order group by user_id having min(create_date) = '2018-05-22') as a;
运行结果是:
查出首次下单是某天的正确运行结果
分析:先通过user_id分组,然后通过having以及min函数找出每组的最小值,判断最小值是否为'2018-05-22'(因为若一个组的create_date最小值为'2018-05-22',那就是首次下单日期是2018年05月22号的用户),然后再把这个整体结果作为from后的临时查询表,最后用count(*)统计出数量。
以下写法是错误的:
select count(*) from user_order group by user_id having min(create_date) = '2018-05-22';
运行结果是:
查出首次下单是某天的错误运行结果
分析:因为这里查到的是分组数据,分组后只显示每组的第1条记录,但实际的记录数是可能多于显示的记录数的,看上面的运行结果,2表示的是第1个分组的记录数是2(356有两条记录),1表示的是第2个分组的记录数是1(456只有一条记录)。
7、写一个函数,输入参数有两个,一个是int数组nums,另一个是int类型target,其中nums中的数据不重复并且一定有两个项相加等于target。要求该函数返回nums中相加等于target两个项的下标。比如nums=[3,8,11,15,16],target=19,因为8+11=19,所以返回[1,2],分别代表8和11的下标。要求算法复杂度n,也就是不要两个for嵌套进行计算,建议先考虑存储结构再考虑实现。
答:
第一遍遍历:遍历数组nums,将(target-nums[i])和i作为key和value,存入hash表,遍历时间复杂度为O(n)。
第二遍遍历:遍历数组nums,每次都查询在hash表中是否有和当前数相同的key,每次查询时间复杂度为O(1),遍历时间复杂度为O(n)。
所以,总的时间复杂度是O(2n),也就是O(n)。
为何hash表的containsKey方法的时间复杂度为O(1),可以参考下这篇文章,https://www.jianshu.com/p/06e2be54d4d6。
本题代码TwoSum.java如下:
import java.util.HashMap;
import java.util.Map;
/**
* 在不重复数组中寻找两个数,使得两数等于一个目标值,返回两数的数组下标
*/
class TwoSum {
public static void main(String[] args) {
int[] nums = { 3, 8, 11, 15, 16 };
int[] target_index_nums = new TwoSum().getIndexs(nums, 26);
for (int i = 0; i < target_index_nums.length; ++i) {
System.out.println(target_index_nums[i]);
}
}
public int[] getIndexs(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
// 第一遍遍历:将(target-nums[i])和i作为key和value,存入hash表,遍历时间复杂度为O(n)
for (int i = 0; i < nums.length; ++i) {
map.put(target - nums[i], i);
}
// 保存结果下标的数组
int[] target_index_nums = new int[2];
// 第二遍遍历:查询在Hash表中是否有和当前数相同的key,每次查询时间复杂度为O(1),遍历时间复杂度为O(n)
for (int i = 0; i< nums.length; ++i) {
// 有可能出现两数相同,比如数组中有8,目标数为16,那么target-nums[i]=8,那返回的下标就会是两个都是8的下标
// 用i != map.get(nums[i])可以避免这种情况
if (map.containsKey(nums[i]) && i != map.get(nums[i])) {
int j = map.get(nums[i]);
// 确保返回的结果下标数组中小的下标在前面,大的下标在后面
if (i < j) {
target_index_nums[0] = i;
target_index_nums[1] = j;
} else {
target_index_nums[0] = j;
target_index_nums[1] = i;
}
break;
}
}
return target_index_nums;
}
}
运行结果是:
返回两数和的数组下标运行结果
8、删除员工表中姓名重复的数据,只保留重复数据中的一条数据(员工表:emp,员工姓名:name),这里录入的数据是:
id name
1 Joe
2 Henry
3 Henry
4 Max
5 Tommy
6 Tommy
7 Tommy
答:
delete from emp where id not in (select a.id from (select * from emp group by name) as a);
运行结果是:
删除重复数据正确运行结果
分析:通过姓名来分组找出一份不重复的姓名记录,把这份记录放到一个临时表a里,再查询这个临时表a找出这些记录的id,删除姓名重复数据就是删除不是这些id的记录。
以下写法是错误的:
delete from emp where id not in (select id from emp group by name);
运行结果会是:
删除重复数据错误运行结果
分析:会抛出错误ERROR 1093 (HY000): You can't specify target table 'emp' for update in FROM clause,这事因为在mysql中不能通过嵌套子查询来直接删除或者修改记录,就是说update或delete的where语句后不能有本表的子查询。解决的办法是,给嵌套子查询的结果取一个别名作为临时表,再从这个临时表中再次查询出记录,要删除或者修改就操作从这个临时表中查询出的记录即可。
这里引出一个值得注意的问题:分组后显示的数据和实际的数据不一定一样,分组后显示的数据是每组的第1条,而实际的数据还是全部,如果要获取分组后显示的数据,可用临时表的方法实现,这道题就是一个很好的验证,执行下面的sql语句进行姓名分组,
select * from emp group by name;
删除重复数据注意地方1
我们可以看到分组后显示的数据是,id为1,2,4,5的数据。再分别执行下面2条sql语句,
select * from emp where id in (select id from emp group by name);
select * from emp where id not in (select id from emp group by name);
删除重复数据注意地方2
执行第1条sql语句,显示的数据竟然是,id为1,2,3,4,5,6,7的数据,即全部数据;执行第2条sql语句,提示数据为空。所以证明了上面的select * from emp group by name;这条sql分组语句得到的实际的数据其实是全部的数据,而不是显示的数据。如果要获取分组后显示的数据,那就要生成临时表了,也就是把分组后显示的数据作为一个临时表来查询,等于把当前显示的数据保存为一个临时表,分别执行下面2条sql语句,
select * from emp where id in (select a.id from (select * from emp group by name) as a);
select * from emp where id not in (select a.id from (select * from emp group by name) as a);
删除重复数据注意地方3
执行第1条sql语句,显示的数据是,id为1,2,4,5的数据,正好就是分组后显示的数据;执行第2条sql语句,显示的数据是,id为3,6,7的数据,正好就是全部的数据除去分组后显示的数据而剩下的数据。
9、运行B.java程序,输出结果是什么?B.java程序如下:
class A {
public int i = method();
public static int j = method2();
public int k = 0;
public A() {
System.out.println(1);
}
public int method() {
System.out.println(2);
return 2;
}
public static int method2() {
System.out.println(3);
return 3;
}
}
public class B extends A {
public int m = method3();
public static int n = method4();
public int t = 0;
public B() {
System.out.println(4);
}
public int method3() {
System.out.println(5);
return 5;
}
public static int method4() {
System.out.println(6);
return 6;
}
public static void main(String[] args) {
System.out.println(7);
A a = new B();
}
}
答:输出结果是:
A、B程序输出结果
分析:
1)首先加载两个类,加载类时先加载静态变量,先加载到的是A类的j静态变量,所以执行method2,输出3。
2)然后加载到B类的静态变量n,所以执行method4,输出6。
3)再到执行B类的main方法,执行System.out.println(7),输出7。
4)再到执行A a,首先加载A类的成员变量i,所以执行method,输出2。
5)然后加载A类的构造方法A(),所以执行System.out.println(1),输出1。
6)再到执行new B(),首先加载B类的成员变量m,所以执行method3,输出5。
7)然后加载B类的构造方法B(),所以执行System.out.println(4),输出4。
10、使用堵塞队列来实现一个简单的生产者-消费者模型。
答:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
public class PCBlockingQueue {
// 堵塞队列的最大值,也可以不指定
private static final int MAX_CAPACITY = 10;
// 顺序堵塞队列,基于数组,必须指定最大值
// private static BlockingQueue<Object> goods = new ArrayBlockingQueue<>(MAX_CAPACITY);
// 链式堵塞队列,基于链表,最大值可以指定也可以不指定
private static BlockingQueue<Object> goods = new LinkedBlockingQueue<Object>(MAX_CAPACITY);
// 若不指定堵塞队列的最大值,队列会一直增长下去
// private static BlockingQueue<Object> goods = new LinkedBlockingQueue<Object>();
public static void main(String[] args) {
new ProducerThread().start();
new ConsumerThread().start();
}
static class ProducerThread extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(500);
goods.put(new Object());
System.out.println("Produce goods, total: " + goods.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class ConsumerThread extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
goods.take();
System.out.println("Consume goods, total: " + goods.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果是:
堵塞队列的生产者-消费者模型
分析:因为指定了堵塞队列的最大值MAX_CAPACITY为10,所以最后增长到了10的时候就不再增长了。若不指定最大值,会一直增长下去,因为这里的生产者每次只睡眠500毫秒,而消费者每次睡眠1000毫秒,生产比消费快。
网友评论