先说下自己的笔试情况:0.81 + 0.91 + 0.18
1. 拼接数字,有两种方法可以做,第一种是类似全排列的解法,只不过要把每次收集的上限改为3。我笔试中使用这个方法只过了81%,原因是没有加base case,题目中给了N张卡片, N的范围是1 <= N <= 100000。加上特殊判断条件应该就可以ac了。
第二种方法是,先将卡片上的数字按照长度排列,然后长度一样的话,按照字典序排序。需要注意的是,取出前三个数字以后,还需要特殊处理。比如取出56,561,3。
不能单纯按照字典序排列,否则就是561563,但是565613才是最优解。所以需要两两相加比较,比如o1:56, o2:561 (o2+o1).compareTo(o1+o2) ,56就到了前面。
public class Main {
static long ans = Long.MIN_VALUE;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
long[] arr = new long[N];
for (int i = 0; i < N; i++) {
arr[i] = scanner.nextLong();
}
// base case
if (N == 0) {
System.out.println(0);
return;
} else if (N == 1) {
System.out.println(arr[0]);
return;
} else if (N == 2) {
String s1 = arr[0] + "";
String s2 = arr[1] + "";
System.out.println(Math.max(Long.parseLong(s1 + s2), Long.parseLong(s2 + s1)));
return;
}
// 代表访问过的数组位置
int[] visited = new int[N];
process(arr, new ArrayList<>(), visited);
System.out.println(ans);
}
// 收集最大的数字
private static void process(long[] arr, ArrayList<String> tmp, int[] visited) {
if (tmp.size() == 3) {
// 已经收集完三个数字
strToLong(new ArrayList<>(tmp));
return;
}
for (int i = 0; i < arr.length; i++) {
if (visited[i] == 1) {
continue;
}
visited[i] = 1;
tmp.add(arr[i] + "");
process(arr, tmp, visited);
visited[i] = 0;
tmp.remove(tmp.size() - 1);
}
}
// 将String类型转换为Long类型
private static void strToLong(ArrayList<String> tmp) {
StringBuilder res = new StringBuilder();
for (String s : tmp) {
res.append(s);
}
long aLong = Long.parseLong(res.toString());
ans = Math.max(ans, aLong);
}
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
// 使用字符串数组接收整数
String[] nums = new String[N];
for (int i = 0; i < N; i++) {
nums[i] = sc.next();
}
// 对数字进行排序,从大到小排序。先按照长度从大到小排序,毕竟长度越长,拼接后的位数越多,也就越大
// 如果长度相同按照,按照字典序进行排序,也就是o1 = "91", o2 = "81" 那么91应该排在前面
Arrays.sort(nums, (o1, o2) -> o1.length() == o2.length() ? o2.compareTo(o1) : o2.length() - o1.length());
// 取出来3个数字,这时候取出来的三个,可能是3个长度从大到小排列的三个数字,也可能是长度都相同,按照字典序排序的三个数字
// 但是可以保证的是,这三个一定是长度排名前三的数字(可以第四个也是和第三个长度一样,但是字典序不如第三个)
String[] tmp = new String[3];
for (int i = 0; i < 3; i++) {
tmp[i] = nums[i];
}
// 这时候长度已经定了,现在就是看怎么排列最大的问题了,肯定是按照字典序从大到小排列
// 比如三个数字是 1234 91 99,这时候99 91 1234 这样拼接最大
// 但是如果测试用例为1 2 56 561,通过第一轮排序后剩下的是2 56 561,如果只按照字典序从大到小排序的话
// 561 56 2,最后输出的就是561562.但是很显然565612是最大的
// 所以需要两两相加排序,比如此时o1 = "56", o2 = "561"
// (o1 + o2) = "56561" (o2 + o1) = "56156"
// 很显然(o1 + o2).compareTo(o2 + o1),会返回一个4(遇到的第一位不相同的字符,ASCII码相减)
// 然后返回的是正数,就会调整顺序,所以改成(o2 + o1).compareTo(o1 + o2))
Arrays.sort(tmp, (o1, o2) -> (o2 + o1).compareTo(o1 + o2));
StringBuilder sb = new StringBuilder();
for (String s : tmp) {
sb.append(s);
}
System.out.println(Long.parseLong(sb.toString()));
}
2. 移位游戏,这道题过了91%,因为当时找bug的时候,全把注意力放在了两个数字能不能为0的case,但是题目中给的参数范围1≤ a,b ≤10^18,根本不会有0的存在。
具体解法,放代码里面了。
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
long[][] arr = new long[N][2];
for (int i = 0; i < N; i++) {
arr[i][0] = scanner.nextLong();
arr[i][1] = scanner.nextLong();
}
for (int i = 0; i < arr.length; i++) {
long a = arr[i][0];
long b = arr[i][1];
System.out.println(get(a, b));
}
}
public static int get(long num, long target) {
if (num == target) {
// num和target本来就相等
return 0;
}
if (num > target && num % 2 != 0) {
// 如果num大于target,代表需要除法运算
// 但是num不可以被2整除,也就代表着不能被2、 4、 8整除
// 所以不可能变成target
return -1;
}
int option = 0;
// 不能使用除法,例如num = 90, target = 5
// 90 / 2 = 45, 45 / 2 = 22, 22 / 2 = 11, 11 / 2 = 5
// 这样就会num == target,实际上是不可能相等的,因为运算过程中自动向下取整,才会出现相等的情况。
long max = Math.max(num, target);
long min = Math.min(num, target);
while (min != max) {
min *= 2;
option++;
if (min > max) {
// 迭代不断增大的过程中,居然min 大于了 max,说明min根本就不可能通过乘2和max相等
return -1;
}
}
// 这里啰嗦一句,说一下我是怎么通过option生成返回值的
// 如果option,也就是num 变成 target(或者target变成num)所需的2的次数
// 不管是乘2也好,除2也好,反正到这说明他们是可以通过option次变成对方的
// 所以一个option就代表一个2 ^ 1。首次尽最大努力,用2 ^ 8来完成转换操作。
// 比如通过9次乘2操作,num变成了target。那么使用3次2 ^ 8无疑是最省的方案。
// 有点贪心的思想吧,能只用2 ^ 3次方,就只用 2 ^ 3。
// 例如option = 13, 14 % 3 = 2。说明不能只使用2 ^ 3,那么就看看能最多用几次2 ^ 3, 14 / 3 = 4
// 然后剩下一个tmp = 14 % 3 = 2,那么 2,就使用 2 ^ 2次方尽最大努力来凑齐。
// 这里因为第一步是模运算,option mod 3,所以只会有三个结果 0, 1, 2
// 那肯定等于0的提前返回了,等于2的,使用一次2 ^ 2,然后返回,等于1的,就用一次2 ^ 1。最后返回
if (option % 3 == 0) {
// 只用2 ^ 3就可以完成简化操作,那么直接返回
return option / 3;
}
// 到这说明,不可以只使用2 ^ 3完成简化操作
// 能使用 2 ^ 3简化的最大次数
int three = option / 3;
// 剩下的值
int tmp = option % 3;
// 能使用 2 ^ 2简化的最大次数
int two = 0;
// 能使用 2 ^ 1简化的最大次数
int one = 0;
if (tmp % 2 == 0) {
// 可以用2 ^ 2简化所有
two = tmp / 2;
} else {
// 只能用2 ^ 1简化
one = tmp;
}
// 这里为了容易理解,三数相加。其实在判断的时候,就可以返回结果了。
return three + two + one;
}
}
3. 上升子序列,这时候没找到很好的解法,笔试完补了一个暴力递归的方法,当时卡在了怎么生成所有的情况,看来递归还得多练。
public class Main {
static long ans = 0;
static long mod = 998244353;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 每个数组的长度
int N = scanner.nextInt();
// 数组的可取值范围[1,M]
int M = scanner.nextInt();
int[] nums = new int[N];
process(nums, 0, M);
System.out.println(ans % mod);
}
private static void process(int[] nums, int index, int M) {
if (index == nums.length) {
// 已经填满了nums,可以进行一次判断
if (judge(nums) == 3) {
ans++;
}
return;
}
for (int i = 1; i <= M; i++) {
nums[index] = i;
process(nums, index + 1, M);
}
}
private static int judge(int[] nums) {
int ans = 0;
int N = nums.length;
int[] dp = new int[N];
Arrays.fill(dp, 1);
for (int i = 1; i < N; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
ans = Math.max(ans, dp[i]);
}
return ans;
}
}
通过这几天的笔试带来的经验教训:
- 注意看给定参数的范围,非常重要!
- 最近考察排列组合问题很多,比如好多思想都是凑齐硬币、求子集、全排列等等。
另外就是,笔试完很难复现场景,LeetCode可以看不通过的用例,然后自己整理思路。笔试的话,一是ACM格式,二是没有原题。所以很多次笔试完,即使有时间去复盘,也能还原当时的情况,尤其是通过大部分测试用例,找不到对应的case。这里推荐大家学一下对数器的使用。可以比较轻松的复原当时出现错误的情况,有小伙伴需要的话,下次我也可以写一个关于对数器的文章,嘿嘿。
#笔试##美团笔试##后端开发实习生##字节笔试#