今日头条2018校园招聘后端开发工程师(第二批)编程题 (Java版)

本人技术小白一枚,文章只是记录个人的解题思路和过程

牛客网地址:原题链接

第一题:用户喜好

题目

为了不断优化推荐效果,今日头条每天要存储和处理海量数据。假设有这样一种场景:我们对用户按照它们的注册时间先后来标号,对于一类文章,每个用户都有不同的喜好值,我们会想知道某一段时间内注册的用户(标号相连的一批用户)中,有多少用户对这类文章喜好值为k。因为一些特殊的原因,不会出现一个查询的用户区间完全覆盖另一个查询的用户区间(不存在L1<=L2<=R2<=R1)。

输入描述:

输入: 第1行为n代表用户的个数 第2行为n个整数,第i个代表用户标号为i的用户对某类文章的喜好度 第3行为一个正整数q代表查询的组数 第4行到第(3+q)行,每行包含3个整数l,r,k代表一组查询,即标号为l<=i<=r的用户中对这类文章喜好值为k的用户的个数。 数据范围n <= 300000,q<=300000 k是整型

输出描述:

输出:一共q行,每行一个整数代表喜好值为k的用户的个数

输入例子:

5
1 2 3 3 5
3
1 2 1
2 4 5
3 5 3

输出例子:

1
0
2

分析

一看题目,最先想到的就是枚举遍历:
将所有的用户喜好度k值保存在数组arr[]中,然后遍历查询的区间[l , r],判断是否喜好值是否是k。
不过看一下数据范围:n<=300000,q<=300000,直接遍历的方法时间复杂度是 O(nq),很明显是无法通过所有测试用例的。

这个时候我们必须换一种思路了,再分析一下题目,我们可以发现,除了可以使用用户序列号来获取喜好值k,我们同样可以通过喜好值k来反向获取用户的序列号列表,即将k作为key值,value为用户序列号列表List;key-value的存储方式,很明显,选用的集合类是HashMap;

想明白这一点,这道题就不难了,直接通过查询的k值来判断是否存在用户集合List,并遍历集合List,在[l, r]区间内即符合条件。

代码

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class Main {

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		while(scanner.hasNext()) {
			int n = scanner.nextInt();
			int[] a = new int[n];
			for (int i = 0; i < n; i++) {
				a[i] = scanner.nextInt();
			}
			Map<Integer, List<Integer>> map = new HashMap<>();
			for (int i = 0; i < n; i++) {
				int key = a[i];
				if (map.containsKey(key)) {
					List<Integer> list = map.get(key);
					list.add(i+1);
				}else {
					List<Integer> list = new ArrayList<>();
					list.add(i+1);
					map.put(key, list);
				}
			}
			int q = scanner.nextInt();
			while((q--) > 0) {
				int l = scanner.nextInt();
				int r = scanner.nextInt();
				int k = scanner.nextInt();
				int count = 0;
				List<Integer> list = map.get(k);
				if (list != null) {
					for(Integer integer : list) {
						if (integer >= l && integer <= r) {
							count++;
						}
					}
				}
				System.out.println(count);
			}
		}
	}

}

第二题:手串

题目:

作为一个手串艺人,有金主向你订购了一条包含n个杂色串珠的手串——每个串珠要么无色,要么涂了若干种颜色。为了使手串的色彩看起来不那么单调,金主要求,手串上的任意一种颜色(不包含无色),在任意连续的m个串珠里至多出现一次(注意这里手串是一个环形)。手串上的颜色一共有c种。现在按顺时针序告诉你n个串珠的手串上,每个串珠用所包含的颜色分别有哪些。请你判断该手串上有多少种颜色不符合要求。即询问有多少种颜色在任意连续m个串珠中出现了至少两次。

输入描述:

第一行输入n,m,c三个数,用空格隔开。(1 <= n <= 10000, 1 <= m <= 1000, 1 <= c <= 50) 接下来n行每行的第一个数num_i(0 <= num_i <= c)表示第i颗珠子有多少种颜色。接下来依次读入num_i个数字,每个数字x表示第i颗柱子上包含第x种颜色(1 <= x <= c)

输出描述:

一个非负整数,表示该手链上有多少种颜色不符需求。

输入例子:

5 2 3
3 1 2 3
0
2 2 3
1 2
1 3

输出例子:

2

分析

这道题同样可以使用和第一题一样的方法,用颜色c做key值,value是串珠标号列表List;

m个连续串珠内不能一种颜色出现两次,记录不合格的颜色数量,就变成了获取每种颜色的串珠标号列表List,判断List中相邻的两个数之间的差值是否大于m。

需要注意的是,因为珠串是一个环状结构,所以List中最后一个元素需要单独判断。

代码

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class Main2 {

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		while(scanner.hasNext()) {
			int n = scanner.nextInt();
			int m = scanner.nextInt();
			int c = scanner.nextInt();
			Map<Integer, List<Integer>> map = new HashMap<>();
			for(int i=0; i<n; i++) {
				int num = scanner.nextInt();
				for(int j=0; j<num; j++) {
					int key = scanner.nextInt();
					if (map.containsKey(key)) {
						List<Integer> list = map.get(key);
						list.add(i);
					}else {
						List<Integer> list = new ArrayList<>();
						list.add(i);
						map.put(key, list);
					}
				}
			}
			int count = 0;
			Boolean flag;
			for (int i = 1; i <= c; i++) {
				flag = false;
				List<Integer> list = map.get(i);
				if (list != null) {
					Collections.sort(list);
					int j;
					for (j = 0; j < list.size()-1; j++) {
						int left = list.get(j);
						int right = list.get(j+1);
						if (left + m > right) {
							count++;
							flag = true;
							break;
						}
					}
					if (!flag && (j > 0) && ((list.get(j) + m) % n) > list.get(0)) {
						count++;
					}
				}
			}
			System.out.println(count);
		}
	}

}

第三题:字母交换

题目:

字符串S由小写字母构成,长度为n。定义一种操作,每次都可以挑选字符串中任意的两个相邻字母进行交换。询问在至多交换m次之后,字符串中最多有多少个连续的位置上的字母相同?

输入描述:

第一行为一个字符串S与一个非负整数m。(1 <= |S| <= 1000, 1 <= m <= 1000000)

输出描述:

一个非负整数,表示操作之后,连续最长的相同字母数量。

输入例子:

abcbaa 2

输出例子:

2

分析

要求移动后形成最长连续相同字母子串,这个最长连续子串可能是a或b……z中的任意一个字母,很明显,可以把每个字母单独拆分出来,作为一个整体考虑,最后的结果比较每个字母得到的最长子串的长度,选取最大的即可。

首先就是遍历这个字符串的所有字符,使用字母的顺序值作为key值,把同一字母的位置,保存在一个List中,List作为一个序列从小到大排列。

接下来就是求解连续最长子序列的问题了,并且操作的次数还不能超过m,因此我们可以使用动态规划,先从小到大枚举段长,依次求得该段长的所有子序列的操作次数,并判断是否小于等于m,如果满足要求,就更新答案。

注:这道题的思路是我参考别的大神的
原文地址:
https://blog.csdn.net/flushhip/article/details/79416715

代码

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class Main3 {

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		while(scanner.hasNext()) {
			String string = scanner.next();
			int m = scanner.nextInt();
			char[] cs = string.toCharArray();
			Map<Integer, List<Integer>> map = new HashMap<>();
			for (int i = 0; i < cs.length; i++) {
				int key = cs[i] - 'a';
				if (map.containsKey(key)) {
					List<Integer> list = map.get(key);
					list.add(i);
				}else {
					List<Integer> list = new ArrayList<>();
					list.add(i);
					map.put(key, list);
				}
			}
			int count = 0;
			for(int i=0;i<26;i++) {
				List<Integer> list = map.get(i);
				
				if (list != null) {
					int[][] dp = new int[list.size()][list.size()];
					//len指连续子串长度
					for(int len=2;len<=list.size();len++) {
						//j指子串起始位置,(j + len -1)指子串结束位置,
						//dp[j][j+len-1]指在j~(j+len-1)这段子串移动成连续子串所需要的次数
						for (int j = 0; j + len - 1 < list.size(); j ++) {
						/*dp[j+1][j+len-2]指将(j+1)~(j+len-2)之间的目标字母移动到一起,
						这个移动次数就是dp[i + 1][i + len - 2];
						然后将两个端点的字母向中间移,移动的距离就为
						(list.get(j+len-1) - list.get(j)) - (len-1)
						*/
							dp[j][j+len-1] = dp[j+1][j+len-2] + list.get(j+len-1) - list.get(j) + 1 -len; 
							if (dp[j][j+len-1] <= m && count < len) {
								count = len;
							}
						}
					}
				}
			}
			System.out.println(count);
		}
	}
	

}

你可能感兴趣的:(编程算法题)