1. 小米笔试题——升级蓄水池
题目描述:
在米兔生活的二维世界中,建造蓄水池非常简单。
一个蓄水池可以用n个坐标轴上的非负整数表示,代表区间为【0-n】范围内宽度为1的墙壁的高度。
如下图1,黑色部分是墙壁,墙壁的高度是[0,1,0,2,1,0,1,3,2,1,2,1] ,蓝色部分是蓄水的面积,可以看出蓄水池最大蓄水容量是6。
现在米兔想通过增加某些墙壁的高度对蓄水池扩容,但是经费有限,最多只能增加最多m的高度,增加高度只能在【0-n】范围内,高度为0的区域也是可以增加的,为了追求最大的性价比,米兔想要找到一种最优方案,使扩容后蓄水池的容量最大,你能帮帮他么?
提示:
对于样例,图2,图3,是样例可能的两种扩容方案,显然图2是比图3更优的方案
关于题目数据范围
20%的数据, n<10,m<10,蓄水池最大高度小于10
50%的数据, n<100,m<100,蓄水池最大高度小于100
100%的数据, n<1000,m<1000,蓄水池最大高度小于1000
输入
第一行为一个数字n
接下来n行,每行一个数字,代表n个墙壁的高度
最后一行为一个数字m
输出
一个数字,表示扩容之后蓄水池能达到的最大容量
样例输入
12
0
1
0
2
1
0
1
3
2
1
2
1
2
样例输出
12
分析
LeetCode上 trap water 题目的改进版, 当时没有解出这题,现在回想起来可以用dfs来遍历所有的可能。
import java.util.*;
public class LeetCode {
static int maxCapicity = 0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] array = new int[n];
for (int i = 0; i < n; i++) {
array[i] = sc.nextInt();
}
int m = sc.nextInt();
maxCapicity=capacity(array);
dfs(0,array,m);
System.out.println(maxCapicity);
}
static void dfs(int index, int[] array, int m) {
if(index>=array.length) return;
if(m==0){
maxCapicity=Math.max(maxCapicity, capacity(array));
}
for (int i = 0; i <= m; i++) {
array[index] += i;
dfs(index + 1, array, m - i);
array[index] -= i;
}
}
static int capacity(int[] array) {
int ans = 0;
int n = array.length;
int[] left_max = new int[n];
int[] right_max = new int[n];
left_max[0] = array[0];
right_max[n - 1] = array[n - 1];
for (int i = 1; i < array.length; i++) {
left_max[i] = Math.max(left_max[i - 1], array[i]);
}
for (int i = array.length - 2; i >= 0; i--) {
right_max[i] = Math.max(right_max[i + 1], array[i]);
}
for (int i = 0; i < n; i++) {
ans += Math.min(right_max[i], left_max[i]) - array[i];
}
return ans;
}
}
能解出示例的输入,但是没有平台提供的测试用例所以不知道是否能通过所有的测试用例,仅供参考。
2. 哔哩哔哩笔试题——字符串数字表达式计算值
输入几行字符串形式的表达式,只包含非负整数,加号,减号和乘号,输入其计算值。输入以字符串END结束
输入样例:
3+4*5*6+4-7*4+4-2
3+4*2-1
END
输出
101
10
分析
将原字符串按照加减号分割成字符串数组,计算每个的实际值后再压入栈中。之后倒序扫描原数组,遇到加号或者减号就从栈中弹出两个数字,计算值后再压入栈,遇到乘号则无视。最后栈中剩下的数字就是计算结果。
实际测试法发现分割后的字符串计算后的值应该倒序进栈,然后顺序扫描原数组才对,否则在计算减号时会有错误。
import java.util.*;
public class LeetCode {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
List<String> expressions = new ArrayList<>();
List<Integer> ans = new ArrayList<>();
while (!str.equals("END")) {
expressions.add(str);
str = sc.nextLine();
}
for (String s : expressions) ans.add(calculate(s));
for (int a : ans) {
System.out.println(a);
}
}
private static int calculate(String str) {
Stack<Integer> digit = new Stack<>();
String[] strs = str.split("\+|\-");
for (int i = strs.length - 1; i >= 0; i--) {
String s = strs[i];
if (s.contains("*")) {
String[] multi = s.split("\*");
int result = 1;
for (String mu : multi) result *= Integer.parseInt(mu);
digit.push(result);
} else {
digit.push(Integer.parseInt(s));
}
}
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == '+' || str.charAt(i) == '-') {
int num1 = digit.pop();
int num2 = digit.pop();
int result = (str.charAt(i) == '+' ? num1 + num2 : num1 - num2);
digit.push(result);
}
}
return digit.peek();
}
}
3. 求旅游完所有景点需要的最少天数
去一个地方出差,顺便取旅游,根据导游提供的方案,每一天可以去当地的一个景点。选择从某一天开始自己的假期,使得在最短的天数内能去所有导游提供的景点。
输入示例1:(开始一个数字表示总共有多少天。然后第一列表示第几天,第二列表示第几天可以去的景点编号)
7
1,7
2,5
3,1
4,7
5,2
6,5
7,1
输出:
4
即从第2天开始假期,持续到第5天,总共4天。或者从第5天开始假期,也是4天。
输入示例2:
11
1,2
2,3
3,1
4,3
5,3
6,7
7,2
8,2
9,1
10,1
11,3
输出:
5,即从第3天到第7天。
分析
可以通过循环遍历的方式来穷举所有的可能,不过会超时,这题关键时要求用有效率的方法来解。想到的法还是根据循环遍历而来,只不过原来的循环会有重复计算的可能,我们要去掉这个重复计算的部分。当从以位置 i 为起始点向后遍历的时候,记住不同景点的出现次数。如果到了某一点 j 使得从 i 到 j 能够取到所有景点时,停止向后。转而从i的下一位开始,更新当前能到达的景点的计数器,如果此时还能到达所以景点则继续往下一位,如果不能则往 j 之后继续遍历直至能到达所有景点,重复这个过程即可。(我感觉有点像Leetcode中Minimum Window Substring这一题的解法,额,看了一下,感觉根本是一个题目 )
import java.util.*;
public class LeetCode {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
String[] trip = new String[n];
sc.nextLine();
for (int i = 0; i < n; i++) {
String[] strs = sc.nextLine().split(",");
trip[i] = strs[1]; // 数组索引直接0~n-1对应第1天到第n天
}
// 先遍历一遍确定有几个不同的景点
Set<String> spots = new HashSet<>();
for (String s : trip) {
spots.add(s);
}
Map<String, Integer> map = new HashMap<>(); // 景点计数器,若要能到达所有景点则至少每个景点要到达一次
for (String s : spots) {
map.put(s, 0);
}
int begin = 0, end = 0, d = Integer.MAX_VALUE;
while (end < n) { // 先从begin=0开始,找左边第一个包含所有景点的时间段
String str = trip[end];
if (map.containsKey(str)) map.put(str, map.get(str) + 1);
//检查当前是否能够到达所有景点
boolean canTravelAll = true;
for (String s : spots) {
if (map.get(s) <= 0) canTravelAll = false;
}
//如果可以则回到左边,往左移缩小范围,如果不能则继续在右边向后移动
while (canTravelAll) {
d = Math.min(d, end - begin); // 更新最小范围
String s = trip[begin];
map.put(s, map.get(s) - 1); // 左移后要判断当前是否包括了所有的景点,是的话继续左移
if (map.get(s) <= 0) canTravelAll = false;
}
end++;
}
System.out.println(d);
}
}
4. 宝箱怪
宝箱怪是游戏中常见的一种怪物,它们伪装成普通的宝箱,并在被玩家打开时攻击玩家。假设你操控的游戏角色身处一个放着N个宝箱的房间,每个宝箱或者是普通的宝箱,或者是宝箱怪。每个宝箱上都贴着一张字条,字条上写着以下两种信息中的一种:
①第x个宝箱是普通宝箱;
②第x个宝箱是宝箱怪。
其中普通宝箱上的信息一定是真的,而宝箱怪上的信息可能是假的,那么根据这些信息,有多少个宝箱一定是普通宝箱,又有多少个宝箱一定是宝箱怪?
输入
第一行包含一个整数N,1≤N≤105。
接下来N行,第i行包含两个整数t和x,1≤t≤2,1≤x≤N。若t=1,则第i个宝箱上的信息为:第x个宝箱是普通宝箱;若t=2,则第i个宝箱上的信息为:第x个宝箱是宝箱怪
输出
输出两个以空格隔开的整数,第一个整数表示可以确定为普通宝箱的宝箱数量,第二个整数表示可以确定为宝箱怪的宝箱数量。
样例输入
3
1 2
2 1
1 3
样例输出
0 1
分析
感觉这题在测智商,就目前遇到的算法题来说,一部分时有套路的,利用dfs或者bfs,dp解之即可。有的其实是数学问题,有的感觉更像是测智商的问题。在牛客网的讨论上看到一个比较靠谱的解答:https://www.nowcoder.com/discuss/118930?type=0&order=0&pos=7&page=1
首先要明确的一点是无法确定普通宝箱的数量的,第一个输出数字一定是0,因为宝箱怪可能说真话也可能说假话,就算知道一个宝箱说的是真话也无法确定它到底时宝箱还是宝箱怪。
所以能确定的只可能是宝箱怪,那么怎么确定呢?如果从它给的结论出发,我们一直往下推,结果得到了矛盾的结论,那么可以证明它的话是假话,那么这个宝箱便是宝箱怪。举个例子:
宝箱1说宝箱2是普通宝箱,宝箱2说宝箱3是普通宝箱,宝箱3说宝箱1是宝箱怪。那么如果宝箱1是普通宝箱,说的是真话,那么可以推出宝箱1是宝箱怪,与假设不符。那么假设宝箱1是宝箱怪,在这种情形下,宝箱2和3说的都是真话,整个逻辑链下来没问题。
即从结果往前推的话,真话推不出宝箱还是宝箱怪,而假话可以推出必定是宝箱怪。
所以基本的算法的思路是,先按照上面的思路确定一遍宝箱怪,然后如果有其它宝箱说这些宝箱怪是宝箱的也必定是宝箱怪。
代码
public class LeetCode {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
sc.nextLine();
int[] types = new int[N + 1];
int[] nodes = new int[N + 1];
Set<Integer> monster = new HashSet<>();
for (int i = 1; i <= N; i++) {
String[] inputs = sc.nextLine().split(" ");
types[i] = Integer.parseInt(inputs[0]);
nodes[i] = Integer.parseInt(inputs[1]);
}
int last = 0;
// 遍历判断每个宝箱是不是宝箱怪
for (int i = 1; i <= N; ++i) {
int count = 0;
int k = i;
while (types[k] == 1) {
k = nodes[k];
if (++count >= N) break; // 处理环
}
if (types[k] == 2 && nodes[k] == i)
monster.add(i);
}
// 指向宝箱怪 是 宝箱的 都是 宝箱怪
while (last != monster.size()) {
last = monster.size();
for (int i = 1; i <= N; i++) {
if (types[i] == 1 && monster.contains(nodes[i]))
monster.add(i);
}
}
System.out.println(0 + " " + monster.size());
}
}