ddd严格来说这还是我第一次参加大厂的算法考试,总的来说其实题目不是非常难,但是中间理解题目意思发生了偏差,浪费了大量的时间。
这导致最后只AC了第一题,第二题重做后以较短的时间完成了主体思路,但是没有处理好字典顺序输出,导致最后通过33%,非常可惜。
第三题基本没来得及看思考= =
第一题:简单模拟/数学
总结题设:签到积分活动,第一天签到积分+2,第二天积分+4,之后每天连续签到+8;签到每满一周额外+20分,满30天额外积分100.
输入:
第一行 输入ture/false表示是否是闰年(366天)
第二行 输入这一年断签的日期,以空格隔开
输出:
输出最后积分总额.
Java主要代码:
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int day = sc.nextLine().equals("true")? 366:365;
// 日签,周签,月签比较容易理解,但是其实下面两个可以用日签sign/7和sign/30是否等于0代替
int sign =0;
int monthSign = 0;
int weekSign = 0;
int score=0;
String[] str = sc.nextLine().split(" ");
// 懒得做排列的小伙伴们可以直接优先队列,或者自己做一个Arrays.sort()后装进Queue也行
PriorityQueue<String> queue = new PriorityQueue<>();
queue.addAll(Arrays.asList(strs));
// 注意取等号
for(int i=1; i<=day; i++){
// 如果当天断签,那么所有签到归零
if(i==queue.peek()){
sign =0;
int monthSign = 0;
int weekSign = 0;
}
// 没有断签,分数开始上涨
else{
sign++;
weekSign++;
monthSign++;
if(sign==1) score+=2;
else if(sign==2) score+=4;
else score+=8;
if(weekSign==7){
score+=20;
weekSign =0;
}
if(monthSign==30){
score+=100;
monthSign =0;
}
}
}
System.out.println(score);
}
但其实更简单的方式是直接用数学的结果。
同样的得到断签日期后,你可以直接通过两次断签日期直接的天数计算结果。
设输入为true,
断签日期分别是 1 180 364
那么从第2天到第179天一共经历了178天,除去第2天和第三天得分为2和4,之后176天日得分均为8。且178/7可以计算出有多少个周,178/30可以计算多少个月,再分别加上他们的分数便可。
181-363,365-366计算同理。
通过数学可以把计算次数从366减少到n+1次.
(不过时间复杂度都是常数级别,计算不会超出366,所以问题不大,模拟/数学都可以).
第二题:最长重复子字符串:
很不幸的是在这一题我理解错了题目意思,导致整个第二题最后推到重做,时间不够。
第二题的真正的题目应当为:最频繁的重复连续子字符串。
真正的题设应该为:输出的是拥有最多连续重复频率的子字符串,如果频率相等,则按照字典大小输出首位。
比如字符串aabcbcbcddd,这个最长是指它重复的频率,bc连续重复3次,d也连续重复了3次。因为bc是重复次数最多并且首字符更小,最后输出bc。
而我在做题的时候直接按照题目标题理解,a重复了,bc重复了,d重复了,但是bc是最长的子字符串(长度为2, 其他两个为1),所以输出bc。
先提一下我所理解的题目,解法:
Hash + BinarySearch.
我们可以直接枚举每个位置作为起点,同时从大到小选举每一个可能的长度(n-1到1),看他是否会造成连续重复字符串,并且用一个Set集合来存储已经枚举过的字符串,如果遍历时判断是否有遇到重复的子字符串说明已经找到最长的连续重复子字符串。
主要代码如下:
int left = 0;
int right = n;
int ans ="";
while (l < r) {
int mid = left + (right - left) / 2;
String str = getString(s, mid,n,arr1,arr2);
if (index != -1) {
// 有重复子串,往右查找
left = mid + 1;
if(ans.length()<str.length()){
ans = str;
}
// 相同长度的话按照字典顺序得到ans
else if(ans.length()==str.length()){
PriorityQueue<String> queue = new PriorityQueue<>();
queue.add(ans);
queue.add(str);
ans = queue.poll();
}
}
else {
// 往左
right = mid - 1;
}
}
public String getString()(String s, int mid,int n, int[] arr1, int[] arr2) {
Set<Integer> set = new HashSet<>();
for (int i = 0; i + mid - 1 < n; i++) {
int cur = i + mid - 1;
int cur = (i > 0) ? arr2[j] - arr2[i-1] * arr1[cur] : arr2[j];
if(hashs.contains(hash)){
return s.substring(i, i + cur);
}
hashs.add(hash);
}
return "";
}
但是题目并不是我理解的意思。
回归到第二题:解法只需要简单枚举便可。
在每一个点枚举可能的长度,判断能否得到后续的重复字符串,如果能,则重复频率++,如果重复频率最后大于最大值则进行更新。
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String s = sc.nextLine();
// 如果是空字符串直接返回
if (s.equals("")) {
System.out.println("");
continue;
}
String ans ="";
int maxTime =1;
// i表示当前节点
for(int i=0; i<s.length(); i++){
// j表示我要取的区间右端点 substring()是左闭右开
for(int j = i+1; j<=s.length();j++){
// 当前取的字符串,开始往后对比
String sub = s.substring(i,j);
int length = sub.length();
int time = 1;
int next = j;
// 这里进行判断,如果当前子字符串和后面等长的子字符串相同,则频率增加,同时对比的字符串往后遍历
while(next+length<=s.length() && sub.equal(s.substring(next,next+length)))
time++;
next += length;
// 如果频率不变,直接跳过,否则和最大重复频率对比,看是否能更新
if(time==1) continue;
if(time>maxTime){
ans = sub;
maxTime = time;
}
// 频率相等按照字典输出,但是考试时这里没时间写了,导致最后没有AC,非常可惜
else if(time == maxTime){
//两种方式,一种调用PriorityQueue,另一种自己手动实现
//手动实现的例子,如果字符一直相等就一直往后遍历,直到获取到更小字符串
int max = Math.max(ans.length(), sub.length());
System.out.println("enter " + ans + " " + sub);
boolean flag = false;
for (int m = 0; m < max; m++) {
if (m >= sub.length()) {
flag = true;
break;
}
if (m >= ans.length()) {
flag = false;
break;
}
if (sub.charAt(m) < ans.charAt(m)) {
flag = true;
break;
}
if (sub.charAt(m) > ans.charAt(m)) {
flag = false;
break;
}
}
if (flag) ans = sub;
}
}
}
System.out.println(ans);
}
第三题
没来的仔细看题目,似乎是一个数字分组。
输入n个数字,输入分组数量为k,使得最大的小组数字和最小。
例子:
输入:
n = 9;
1 2 3 1 2 3 1 2 3
k=3
输出 6, 最好的分组就是123 123 123
现在回想的话,第一想法还是使用回溯。
就好比n个数字放k个篮子题,当所有数字都放好时,使得篮子的最大数字和能够最小。
事后第一感觉解法的主要代码段:
static int ans = Integer.MAX_VALUE;
static boolean[] used;
static int[] arr;
public static int caculate(int[] nums, int k) {
used = new boolean[nums.length];
arr = new int[k];
dfs(nums,0,k,k,used,arr);
return ans;
}
public static void dfs(int[] nums,int selectedNum,int curBucket,int k,boolean[] used, int[] arr)
{
// 所有篮子都装好了,那么开始比较最大值
if(curBucket==1){
int max = 0;
for(int count:arr){
max = Math.max(max,arr);
}
// 如果本次递归能够找到更小的最大值,进行更新
ans = Math.min(ans,max);
return;
}
// 如果当前篮子已经找完了,找下一个(总共n个数字,k组,一组则最多n/k个)
if(selectedNum == nums.length/k){
dfs(nums,begin,curBucket-1,k,used,arr);
}
for(int i=0;i< nums.length;i++)
{
//使用过的元素就不能再使用了
if(used[i])
continue;
//添加元素nums[i]
arr[curBucket] += nums[i];
used[i]=true;
dfs(nums,selectedNum+1,curBucket,k,used,arr);
//回溯
arr[curBucket] -= nums[i];
used[i]=false;
}
}
}
第一想法还没有实验过,但是感觉这个思路能够通过大部分测试(取决于测试的数据量),毕竟是回溯法,其实时间复杂度还是很高的。
小总结
总而言之,昨晚这次机考后还是挺绝的自己实力的不足,算法题还是刷的太少,另一个临场需要调整一下心态,太紧张了反而思路没有平时灵活。
没有看清题目尤为可惜,思路到了没有AC,导致第二题只通过了33%,最后一题没时间写只通过了20%。