字节刷题记录
一、字节跳动2019春招研发部分编程题
1、(字符串)万万没想到之聪明的编辑
我叫王大锤,是一家出版社的编辑。我负责校对投稿来的英文稿件,这份工作非常烦人,因为每天都要去修正无数的拼写错误。但是,优秀的人总能在平凡的工作中发现真理。我发现一个发现拼写错误的捷径:
- 三个同样的字母连在一起,一定是拼写错误,去掉一个的就好啦:比如 helllo -> hello
- 两对一样的字母(AABB型)连在一起,一定是拼写错误,去掉第二对的一个字母就好啦:比如 helloo -> hello
- 上面的规则优先“从左到右”匹配,即如果是AABBCC,虽然AABB和BBCC都是错误拼写,应该优先考虑修复AABB,结果为AABCC
我特喵是个天才!我在蓝翔学过挖掘机和程序设计,按照这个原理写了一个自动校对器,工作效率从此起飞。用不了多久,我就会出任CEO,当上董事长,迎娶白富美,走上人生巅峰,想想都有点小激动呢!
……
万万没想到,我被开除了,临走时老板对我说: “做人做事要兢兢业业、勤勤恳恳、本本分分,人要是行,干一行行一行。一行行行行行;要是不行,干一行不行一行,一行不行行行不行。” 我现在整个人红红火火恍恍惚惚的……
请听题:请实现大锤的自动校对程序
数据范围: 1<=n<=50 ,每个用例的字符串长度满足1<=l<=1000
-
思路1:利用
StringBuild
判断/双指针法-
public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int count = Integer.parseInt(scanner.nextLine()); List<String> result = new ArrayList<>(); for (int i = 0; i < count; i++) { StringBuilder des = new StringBuilder(); String src = scanner.nextLine(); for (int j = 0; j < src.length(); j++) { int len = des.length(); // 如果目标字符串长度大于1,并且最后一个字符和当前字符相同时进行比较 if (len > 1 && src.charAt(j) == des.charAt(len - 1)) { // 1.倒数第一个和倒数第二个相同(符合AAA) // 2.倒数第二个和倒数第三个相同(符合AABB) if ((des.charAt(len - 1) == des.charAt(len - 2)) || (len > 2 && des.charAt(len - 2) == des.charAt(len - 3))) { continue; } } des.append(src.charAt(j)); } result.add(des.toString()); } result.forEach(System.out::println); scanner.close(); }
-
-
思路2:自动机
-
思路3:正则表达式
-
public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int line = scanner.nextInt(); scanner.nextLine(); for (int i = 0; i < line; i++) { System.out.println( scanner.nextLine() .replaceAll("(.)\\1+","$1$1") .replaceAll("(.)\\1(.)\\2","$1$1$2") ); } } }
-
正则含义
- (.)\\1+ 表示 表示任意一个字符重复两次或两次以上
- .表示任意字符,后面的\1表示取第一个括号匹配的内容,后面的加号表示匹配1次或1次以上。二者加在一起就是某个字符重复两次或两次以上
- (.)\\2表示取第二个括号中匹配的字符,(.)\\1(.)\\2即表示形如AABB
- $1是第一个小括号里的内容,$2是第二个小括号里面的内容
-
2、(数组、组合)万万没想到之抓捕孔连顺
我叫王大锤,是一名特工。我刚刚接到任务:在字节跳动大街进行埋伏,抓捕恐怖分子孔连顺。和我一起行动的还有另外两名特工,我提议
- 我们在字节跳动大街的 N 个建筑中选定 3 个埋伏地点。
- 为了相互照应,我们决定相距最远的两名特工间的距离不超过 D 。
我特喵是个天才! 经过精密的计算,我们从X种可行的埋伏方案中选择了一种。这个方案万无一失,颤抖吧,孔连顺!
……
万万没想到,计划还是失败了,孔连顺化妆成小龙女,混在cosplay的队伍中逃出了字节跳动大街。只怪他的伪装太成功了,就是杨过本人来了也发现不了的!
请听题:给定 N(可选作为埋伏点的建筑物数)、 D(相距最远的两名特工间的距离的最大值)以及可选建筑的坐标,计算在这次行动中,大锤的小队有多少种埋伏选择。
注意:
- 两个特工不能埋伏在同一地点
- 三个特工是等价的:即同样的位置组合( A , B , C ) 只算一种埋伏方法,不能因“特工之间互换位置”而重复使用
输入要求:
第一行包含空格分隔的两个数字 N和D(1 ≤ N ≤ 1000000; 1 ≤ D ≤ 1000000)第二行包含N个建筑物的的位置,每个位置用一个整数(取值区间为[0, 1000000])表示,从小到大排列(将字节跳动大街看做一条数轴)
输出要求:
一个数字,表示不同埋伏方案的数量。结果可能溢出,请对 99997867 取模
-
双指针法 组合
-
快指针先找到第一个大于D的位置 j ,然后从慢指针 i 到快指针前一个位置进行选择(组合问题)
-
由于从 i 到 j - 1中选3个,到下一次再选会出现重复,所以固定第一个值,从后面选两个
- 例,1 2 3 4,i = 0, j = 3,从1 - 4中选3个会有4种情况,再i = 1, j = 3时从 2 - 4中选3中也会有1种情况,但这种情况再 1 - 4中选过了(2 3 4)
- 组合公式: C r n = n ! r ! ( n − r ) ! C_{r}^{n}= \frac{n!}{r!\left (n - r \right )!} Crn=r!(n−r)!n!,组合是没有顺序的,有顺序的是排列
-
代码(ps. 这么简单的代码为啥我就写不出来呢)
public static void main (String[] args){ Scanner sc=new Scanner(System.in); int N=sc.nextInt(); int D=sc.nextInt(); int []s=new int[N]; long num=0; for(int i=0;i<N;i++){ s[i]=sc.nextInt(); } for(int i=0,j=i+2;i<N-2;i++){ long p; while(j<N&&(s[j]-s[i]<=D)) j++; p=j-i-1; num=num+(p*(p-1)/2); } System.out.println(num%99997867); }
-
3、(模拟、回溯)雀魂启动!
小包最近迷上了一款叫做雀魂的麻将游戏,但是这个游戏规则太复杂,小包玩了几个月了还是输多赢少。
于是生气的小包根据游戏简化了一下规则发明了一种新的麻将,只留下一种花色,并且去除了一些特殊和牌方式(例如七对子等),具体的规则如下:
- 总共有36张牌,每张牌是1~9。每个数字4张牌。
- 你手里有其中的14张牌,如果这14张牌满足如下条件,即算作和牌
- 14张牌中有2张相同数字的牌,称为雀头。
- 除去上述2张牌,剩下12张牌可以组成4个顺子或刻子。顺子的意思是递增的连续3个数字牌(例如234,567等),刻子的意思是相同数字的3个数字牌(例如111,777)
例如:
1 1 1 2 2 2 6 6 6 7 7 7 9 9 可以组成1,2,6,7的4个刻子和9的雀头,可以和牌
1 1 1 1 2 2 3 3 5 6 7 7 8 9 用1做雀头,组123,123,567,789的四个顺子,可以和牌
1 1 1 2 2 2 3 3 3 5 6 7 7 9 无论用1 2 3 7哪个做雀头,都无法组成和牌的条件。
现在,小包从36张牌中抽取了13张牌,他想知道在剩下的23张牌中,再取一张牌,取到哪几种数字牌可以和牌。
输入只有一行,包含13个数字,用空格分隔,每个数字在1~9之间,数据保证同种数字最多出现4次。
输出同样是一行,包含1个或以上的数字。代表他再取到哪些牌可以和牌。若满足条件的有多种牌,请按从小到大的顺序输出。若没有满足条件的牌,请输出一个数字0
-
回溯
-
只有9张牌,挨个放进去
-
回溯:雀头挨个检查,不行就回溯
-
如果不是刻子就一定是顺子,不是就回溯
-
Map不能直接作为形参传递,会改变;Java中只有值传递,对象传递的是引用
-
public static boolean judge(TreeMap<Integer, Integer> map,int count) { boolean flag = false; if (count == 0) return true; for (Integer i : map.keySet()) { if(map.get(i) >= 3){ map.put(i,map.get(i)-3); flag = judge(map,count-3); if(flag) return true; map.put(i,map.get(i)+3); } if(map.get(i) > 0){ if(map.containsKey(i+1) && map.containsKey(i+2) && map.get(i+1)>0 && map.get(i+2)>0){ map.put(i,map.get(i)-1); map.put(i+1,map.get(i+1)-1); map.put(i+2,map.get(i+2)-1); flag = judge(map,count-3); if(flag) return true; map.put(i,map.get(i)+1); map.put(i+1,map.get(i+1)+1); map.put(i+2,map.get(i+2)+1); }else return false; } } return false; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int[] arr = new int[13]; TreeMap<Integer, Integer> map = new TreeMap<>(); ArrayList<Integer> ans = new ArrayList<>(); for (int i = 0; i < 13; i++) { arr[i] = sc.nextInt(); if (map.containsKey(arr[i])) { map.put(arr[i], map.get(arr[i]) + 1); } else { map.put(arr[i], 1); } } sc.close(); for(int i = 1;i<= 9;i++){ if(map.containsKey(i)){ if(map.get(i)<4){ map.put(i,map.get(i)+1); // 挨个放牌 boolean flag = false; for(int j : map.keySet()){ // 挨个取雀头 if(map.get(j)>=2){ map.put(j,map.get(j)-2); // 去掉雀头 TreeMap<Integer, Integer> map_ = new TreeMap<>(); map_.putAll(map); flag = judge(map_, 12); if(flag) { ans.add(i); // 加对的牌 map.put(j,map.get(j)+2); // 把雀头放回去 break; // 结束这次雀头 } map.put(j,map.get(j)+2); } } map.put(i,map.get(i)-1); } }else { map.put(i,1); // 挨个放牌 boolean flag = false; for(int j : map.keySet()){ if(map.get(j)>=2){ map.put(j,map.get(j)-2); TreeMap<Integer, Integer> map_ = new TreeMap<>(); map_.putAll(map); flag = judge(map_, 12); if(flag) { ans.add(i); map.put(j,map.get(j)+2); break; } map.put(j,map.get(j)+2); } } // map.put(i,0); map.remove(i); } } for(int i : ans) System.out.println(i); }
-
4、(模拟)特征提取
小明是一名算法工程师,同时也是一名铲屎官。某天,他突发奇想,想从猫咪的视频里挖掘一些猫咪的运动信息。为了提取运动信息,他需要从视频的每一帧提取“猫咪特征”。一个猫咪特征是一个两维的vector<x, y>。如果x_1=x_2 and y_1=y_2,那么这俩是同一个特征。
因此,如果喵咪特征连续一致,可以认为喵咪在运动。也就是说,如果特征<a, b>在持续帧里出现,那么它将构成特征运动。比如,特征<a, b>在第2/3/4/7/8帧出现,那么该特征将形成两个特征运动2-3-4 和7-8。
现在,给定每一帧的特征,特征的数量可能不一样。小明期望能找到最长的特征运动。
输入描述:
第一行包含一个正整数N,代表测试用例的个数。每个测试用例的第一行包含一个正整数M,代表视频的帧数。接下来的M行,每行代表一帧。其中,第一个数字是该帧的特征个数,接下来的数字是在特征的取值;比如样例输入第三行里,2代表该帧有两个猫咪特征,<1,1>和<2,2>所有用例的输入特征总数和<100000N满足1≤N≤100000,M满足1≤M≤10000,一帧的特征个数满足 ≤ 10000。特征取值均为非负整数。
输出描述:
对每一个测试用例,输出特征运动的长度作为一行
输入例子1:
1 8 2 1 1 2 2 2 1 1 1 4 2 1 1 2 2 2 2 2 1 4 0 0 1 1 1 1 1 1
输出例子1:
3
例子说明1:
特征<1,1>在连续的帧中连续出现3次,相比其他特征连续出现的次数大,所以输出3
-
如何存储数组,如何保存连续值,如何判断最大值
-
public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); HashMap<String,Integer> map = new HashMap<>(); for(int t = 0;t<n;t++){ int m = sc.nextInt(); HashMap<String,ArrayList<Integer>> list = null; int max = 1; for(int i = 0;i<m;i++){ HashMap<String,ArrayList<Integer>> list_0 = new HashMap<>(); HashMap<String,Integer> map_ = new HashMap<>(); int s = sc.nextInt(); for(int ss = 0;ss<s;ss++){ int p = sc.nextInt(); int q = sc.nextInt(); if(!list_0.containsKey(p+"")){ list_0.put(p+"",new ArrayList<>()); } list_0.get(p+"").add(q); if(list!=null){ if(list.containsKey(p+"") && list.get(p+"").contains(q)){ if(map.containsKey(p+" "+q)){ int tmp = map.get(p+" "+q); map_.put(p+" "+q,tmp+1); max = max >= tmp+1 ? max:tmp+1; }else { map_.put(p+" "+q,2); max = max >= 2 ? max:2; } } } } map = map_; list = list_0; } System.out.println(max); } }
-
二、字节面试题猜测
1、(动态规划)接雨水
给定
n
个非负整数表示每个宽度为1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/trapping-rain-water
-
遍历两边,从左往右找左边最大边 d p [ i ] = m a x ( h e i g h t [ i ] , d p [ i − 1 ] ) dp[i]=max(height [i],dp[i-1]) dp[i]=max(height[i],dp[i−1]);从右往左找右边最大的边 d p 2 [ i ] = m a x ( h e i g h t [ i ] , d p 2 [ i + 1 ] ) dp2[i]=max(height [i],dp2[i+1]) dp2[i]=max(height[i],dp2[i+1])
-
class Solution { public int trap(int[] height) { int n = height.length; if (n == 0) { return 0; } int[] leftMax = new int[n]; leftMax[0] = height[0]; for (int i = 1; i < n; ++i) { leftMax[i] = Math.max(leftMax[i - 1], height[i]); } int[] rightMax = new int[n]; rightMax[n - 1] = height[n - 1]; for (int i = n - 2; i >= 0; --i) { rightMax[i] = Math.max(rightMax[i + 1], height[i]); } int ans = 0; for (int i = 0; i < n; ++i) { ans += Math.min(leftMax[i], rightMax[i]) - height[i]; } return ans; } }
2、(动态规划、bfs)接雨水Ⅱ
给你一个
m x n
的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。输入: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
输出: 4
解释: 下雨后,雨水将会被上图蓝色的方块中。总的接雨水量为1+2+1=4。来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/trapping-rain-water-ii
- 一开始尝试使用每一层进行bfs判断,想法应该也没错,但是不知道那里写错了过了一半
- 假设方块的索引为 ( i , j ) (i,j) (i,j),方块的高度为 heightMap [ i ] [ j ] h e i g h t M a p [ i ] [ j ] \textit{heightMap}[i][j]heightMap[i][j] heightMap[i][j]heightMap[i][j],方块接水后的高度为 water [ i ] [ j ] w a t e r [ i ] [ j ] \textit{water}[i][j]water[i][j] water[i][j]water[i][j]。则我们知道方块 (i,j) 的接水后的高度为:
w a t e r [ i ] [ j ] = m a x ( h e i g h t M a p [ i ] [ j ] , m i n ( w a t e r [ i − 1 ] [ j ] , w a t e r [ i + 1 ] [ j ] , w a t e r [ i ] [ j − 1 ] , w a t e r [ i ] [ j + 1 ] ) ) water[i][j]=max(heightMap[i][j],min(water[i−1][j],water[i+1][j],water[i][j−1],water[i][j+1])) water[i][j]=max(heightMap[i][j],min(water[i−1][j],water[i+1][j],water[i][j−1],water[i][j+1]))
-
思想:先把水接满,然后判断边界应该是多高,然后bfs旁边的除了实际高度,不能比我高
-
方案:bfs(最小堆看着有点麻烦就用能理解了的吧),时间复杂度: O ( M 2 N 2 ) O(M^2N^2) O(M2N2)
-
class Solution { public int trapRainWater(int[][] heightMap) { int m = heightMap.length; int n = heightMap[0].length; int[] dirs = {-1, 0, 1, 0, -1}; int maxHeight = 0; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { // 找到最大的点 maxHeight = Math.max(maxHeight, heightMap[i][j]); } } int[][] water = new int[m][n]; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j){ water[i][j] = maxHeight; // 全部初始化为最大 } } Queue<int[]> qu = new LinkedList<>(); for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (i == 0 || i == m - 1 || j == 0 || j == n - 1) { // 将边界并且不是最大的点加入队列,设置他们接水后的高为自己 if (water[i][j] > heightMap[i][j]) { water[i][j] = heightMap[i][j]; qu.offer(new int[]{i, j}); } } } } while (!qu.isEmpty()) { int[] curr = qu.poll(); int x = curr[0]; int y = curr[1]; for (int i = 0; i < 4; ++i) { int nx = x + dirs[i], ny = y + dirs[i + 1]; if (nx < 0 || nx >= m || ny < 0 || ny >= n) { // 判断是否越界 continue; } if (water[x][y] < water[nx][ny] && water[nx][ny] > heightMap[nx][ny]) { // 判断1:当旁边的比我高时 // 判断2:当旁边的接满水还比他本身高时 // 旁边的等于自己的高或者我的水平面 water[nx][ny] = Math.max(water[x][y], heightMap[nx][ny]); qu.offer(new int[]{nx, ny}); } } } // 最后记录一共接了多少水 int res = 0; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { res += water[i][j] - heightMap[i][j]; } } return res; } }
3、(树、dfs、bfs) 二叉树中所有距离为 K 的结点
给定一个二叉树(具有根结点 root), 一个目标结点 target ,和一个整数值 k 。
返回到目标结点 target 距离为 k 的所有结点的值的列表。 答案可以以 任何顺序 返回。
节点数在 [1, 500] 范围内
0 <= Node.val <= 500
Node.val 中所有值 不同
目标结点 target 是树上的结点。
0 <= k <= 1000来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/all-nodes-distance-k-in-binary-tree
-
思路1:DFS,记录所有节点的父节点(用hashmap),然后从target开始dfs(注意不能搜索过去又回来)
-
思路2:树转图+BFS,一共就501个值,转成一张501*501的图,权重是1,bfs
-
class Solution { ArrayList<Integer> list = new ArrayList<>(); HashMap<TreeNode,TreeNode> map = new HashMap<>(); // 记录父节点 public void dfs(TreeNode root){ if(root == null) return; if(root.left!=null){ map.put(root.left,root); dfs(root.left); } if(root.right!=null){ map.put(root.right,root); dfs(root.right); } } // dfs找距离为k的节点 public void dfs2(TreeNode target, TreeNode from, int n, int k){ if(target==null) return; if(n==k) list.add(target.val); if(target.left!=null && target.left != from) dfs2(target.left,target,n+1,k); if(target.right!=null && target.right != from) dfs2(target.right,target,n+1,k); if(map.get(target) != from) dfs2(map.get(target),target,n+1,k); } public List<Integer> distanceK(TreeNode root, TreeNode target, int k) { map.put(root,null); dfs(root); dfs2(target,null,0,k); return list; } }
4、树的操作集合
-
二叉树的层序遍历
- bfs,记录队列的大小然后遍历整个队列
-
【剑指offer】重建二叉树
-
给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
-
递归法建树,前序的找根,然后中序根左为左子树,中序根右为右子树
-
-
【剑指offer】二叉树中两个节点的最近公共祖先
- 前序遍历,然后存入栈中(用arraylist模拟),两个栈分别对应两个节点路径,最后一个相同的就是
5、链表
- 两个链表的第一个公共节点
- 各设一个指针,然后相同速度,第二次相遇那个值
- 判断是不是有环
- 快慢指针,一个每次走俩,一个每次走一个
6、其他
-
数值的整数次方
-
实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题
-
快速幂, x n x^n xn,当n是偶数时 y = x n / 2 , x n = y ∗ y y=x^{n/2},x^n=y*y y=xn/2,xn=y∗y,奇数是就是 y ∗ y ∗ x y*y*x y∗y∗x
-
-
快排
三、实际题目
- (曼哈顿距离)给你一堆坐标,求这一堆数据中所有两两之间的曼哈顿距离之和,曼哈顿距离定义为
∣
x
i
−
x
j
∣
+
∣
y
i
−
y
j
∣
|x_i - x_j| + |y_i - y_j|
∣xi−xj∣+∣yi−yj∣, 要求时间复杂度小于
n
2
n^2
n2
- x和y相对独立所以分开算一个就行了
- 去掉绝对值就相当于大的减小的,排序之后发现规律,例如 ( 1 , 1 ) ( 2 , 2 ) ( 3 , 3 ) (1,1) (2,2) (3,3) (1,1)(2,2)(3,3)三个点,光看x的话去掉绝对值 3 − 2 , 3 − 1 , 2 − 1 3-2,3-1,2-1 3−2,3−1,2−1,其中 + 3 +3 +3出现了 n − 1 n-1 n−1次, − 3 -3 −3出现了 0 0 0次,同理 + 2 +2 +2出现了 n − 2 n-2 n−2次, − 2 -2 −2出现了 1 1 1次。出现规律,从后往前加了第 i i i个数 i i i次,减了第 i i i个数 n − 1 − i n-1-i n−1−i次
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int []x = new int [n];
int []y = new int [n];
for(int i = 0;i<n;i++){
x[i] = sc.nextInt();
y[i] = sc.nextInt();
}
sc.close();
Arrays.sort(x);
Arrays.sort(y);
int ans = 0;
for(int i = n-1;i>=0;i--){
ans += x[i] * i - x[i] * (n - 1 - i);
ans += y[i] * i - y[i] * (n - 1 - i);
}
System.out.println(ans);
}
}