000. 前言
这篇文章主要是用于记录笔者遇到过的各种笔试题,笔试题是面试前一个比较重要的考核指标,如果笔试题做的很糟糕,很可能连面试的机会都没有,笔者自己就因为春招的时候笔试太糟糕只获得了很少的面试机会,所以对于笔试我们还是要比较重视。还有最重要的一点是笔试一定要诚实,被发现了作弊行为后果是很严重的~
最后说一下笔试过程的相关注意事项:
- 遇到字符串处理回文的问题,例如【006. 构造回文】,优先考虑将字符串进行反转,通过原先字符串和反转后字符串的比对可以简化问题。
- 动态规划问题是后面一个问题可以通过前面一个问题推到而出,所以思考时应当是从后往前思考,考虑所有的情况然后再写代码,一般来说递归代码比循环代码好写。
- 遇到键值对的存储时,如果是依赖于输入顺序的(就像数组存储一样),使用 LinkedHashMap ,需要排序的使用 TreeMap,对于不需要存在重复值的考虑使用 Set。
- 在遇到较为复杂的问题时,如旅行商问题,如果数据较小时可以用暴力求解,使用全排列的思路可以很容易做出来。
- 实在是想不出来时,有时候从后往前遍历会发现新大陆。
- 在遇到处理整型数字的问题时,特别是翻转等操作特别要注意数值溢出的问题。
001. Valid Parentheses
题目描述
Given a string containing just the characters
'('
,')'
,'{'
,'}'
,'['
and']'
, determine if the input string is valid.
An input string is valid if:
- Open brackets must be closed by the same type of brackets.
- Open brackets must be closed int the correct order.
Note that an empty string is also considered valid.
Example 1:
Input: “()”
Output: true
Example 2:
Input: “()[]{}”
Output: true
Example 3:
Input: “(]”
Output: false
问题分析
这道题的思路是使用一个栈来进行判断,先将第一个字符压入栈中,然后逐个遍历后面的字符,如果遇到相匹配的字符就出栈。遍历完成之后判断栈是否为空,为空说明是合法的,否则不合法。
实现代码
public class Main {
public static boolean isValid(String str){
if (str == null){
return false;
}
// 空字符串也是合法的
if ("".equals(str)){
return true;
}
char[] ch = str.toCharArray();
Stack<Character> stack = new Stack<>();
stack.push(ch[0]);
for (int i = 1; i < ch.length; i++){
if (!stack.isEmpty() && isMatch(stack.peek(), ch[i])){
stack.pop();
} else {
stack.push(ch[i]);
}
}
return stack.isEmpty();
}
private static boolean isMatch(char a, char b){
return (a == '(' && b == ')') ||
(a == '[' && b == ']') ||
(a == '{' && b == '}');
}
public static void main(String[] args) {
System.out.println(isValid("(([]))[]"));
}
}
002. 平安果
题目描述
给定一个M行N列的矩阵(M*N个格子),每个格子中放着一定数量的平安果。你从左上角的各自开始,只能向下或者向右走,目的地是右下角的格子。每走过一个格子,就把格子上的平安果都收集起来。求你最多能收集到多少平安果。
注意:当经过一个格子时,需要一次性把格子里的平安果都拿走。
限制条件:1<N, M<=50;每个格子里的平安果数量是0到1000(包含0和1000)。
输入描述
输入包含两部分:
第一行M, N
接下来M行,包含N个平安果数量
输出描述
一个整数,表示最多拿走的平安果的数量。
输入
2 4
1 2 3 40
6 7 8 90
输出
136
问题分析
这道题是一道很典型的动态规划问题,用 f ( i , j ) \ f(i, j) f(i,j) 表示到达坐标为 ( i , j ) \ (i, j) (i,j) 的格子时所能拿到的平安果的最大数量。而 f ( i , j ) \ f(i, j) f(i,j) 的来源有两个,一个是从左边 f ( i − 1 , j ) \ f(i-1, j) f(i−1,j) 而来,另一个是从上边 f ( i , j − 1 ) \ f(i, j-1) f(i,j−1) 而来,我们只需要在二者之中取最大值即可,代码如下所示:
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
int n = in.nextInt();
int m = in.nextInt();
int[][] apples = new int[n][m];
for (int i = 0; i < n; i++){
for (int j = 0; j < m; j++){
apples[i][j] = in.nextInt();
}
}
solution(apples);
}
in.close();
}
private static void solution(int[][] apples) {
if (apples == null || (apples.length == 0)){
System.out.println(0);
return;
}
int n = apples.length;
int m = apples[0].length;
int[][] dp = new int[n][m];
for (int i = 0; i < n; i++){
for (int j = 0; j < m; j++){
int left = 0;
int up = 0;
if (i > 0){
left = dp[i-1][j];
}
if (j > 0){
up = dp[i][j-1];
}
dp[i][j] = Math.max(left, up) + apples[i][j];
}
}
System.out.println(dp[n-1][m-1]);
}
}
003. 向右看齐
题目描述
小Q是一名体育老师,需要对学生们的队伍整队。现在有n名学生站成一排,第i个学生的身高是hi。现在小Q一声令下:“向右看齐!”,所有的学生都向右看齐了。如果对于下标为i的学生来说,如果学生j满足i<j并且hi<hj,我们就可以说学生i看齐的对象是学生j。现在求出每位学生离他最近的看齐对象。
输入描述
第一行输入一个数字n。1 <= n <= 100,000
之后每行一个数字,表示每一个孩子的身高hi(1 <= hi <= 1,000,000)
输出描述
共n行,按顺序输出每位同学的最近看齐的对象,如果没有,则输出0。
输入
6
3
2
6
1
1
2
输出
3
3
0
6
6
0
问题分析
这道题的难度不大,通过暴力解法的方式其实也可以很轻易地求出来,但是复杂度为 O ( n 2 ) \ O(n^{2}) O(n2),在测试的时候很容易造成超时,因此暴力解法并不可行。
而优化的方法笔者这里想到的是从后往前遍历,利用后面的信息减少冗余的判断,例如队列 3 2 6 1 1 2,数字 6 对齐右边的时候,其右边的 1 小于 6,因此我们直接找到 1 的对齐目标,如果对齐目标仍然小于 6,那么我们就继续往对齐目标的对齐目标找…依次类推,直到找不到对齐目标或者找到一个目标大于 6 为止。代码如下所示:
public class Main {
public static void solution(int[] people){
if (people == null || people.length == 0){
throw new IllegalArgumentException();
}
if (people.length == 1){
System.out.println(0);
}
int n = people.length;
int[] record = new int[n];
// 最右边的人肯定没有可对齐的人
record[n-1] = -1;
for (int i = n-2; i >= 0; i--){
int target = i+1;
while (target != -1 && people[i] >= people[target]){
target = record[target];
}
record[i] = target;
}
for (int i = 0; i < n; i++){
System.out.print((record[i] + 1) + " ");
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
int n = in.nextInt();
int[] people = new int[n];
for (int i = 0; i < n; i++){
people[i] = in.nextInt();
}
solution(people);
}
}
}
004. 圆桌会议
问题描述
有 n 个人围坐在一个圆桌周围进行一场圆桌会议,会议开始前从第s (注意不是5) 开始报数,数到第 m 的人就出列退出会议,然后从出列的下一个人重新开始报数,数到第 m 的人又出列,…,,如此重复直到所有的人全部出列为止,现在希望你能求出按退出会议次序得到的人员的序号序列。
输入描述
三个正整数n,s,m(n,s,m < 10000)
输出描述
退出会议次序序号,一行一个。
输入
3 1 2
输出
2
1
3
问题分析
这是一道很经典的约瑟夫环问题,只不过做了一点变式,代码如下所示:
public class JosephRing {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
int n = in.nextInt();
int s = in.nextInt();
int m = in.nextInt();
solution(n, s, m);
}
in.close();
}
private static void solution(int n, int s, int m) {
if (n <= 0 || s <= 0 || m <= 0 || s > m){
throw new IllegalArgumentException();
}
// 由于不是从1开始数的,因此要计算出它的偏移值
int offset = -(n+s-1) % n;
for (int i = 1; i <= n; i ++){
int result = (f(n, m, i) + offset + n - 1) % n + 1;
System.out.println(result);
}
}
private static int f(int n, int m, int turn){
if (turn == 1){
return (n + m - 1) % n + 1;
} else {
return (f(n-1, m, turn-1) + m - 1) % n + 1;
}
}
}
如果对约瑟夫环不熟悉的同学,可以参考这篇文章:【约瑟夫环问题递归解法的一点理解】,说的非常详细。
005. 打怪兽
问题描述
小Q打算穿越怪兽谷,他不会打怪,但是他有钱,他知道,只要给怪兽一定的金币,怪兽就会一直护送着他出谷。
在谷中,他会依次遇见N只怪兽,每只怪兽都有自己的武力值和要“贿赂”它所需的金币数,如果小Q没有“贿赂”某只怪兽,而这只怪兽“武力值”又大于护送他的怪兽武力之和,这只怪兽就会攻击他。
小Q想知道,要想成功穿越怪兽而不被攻击,他最少要准备多少金币。
输入描述
第一行输入一个整数N,代表怪兽的只数。
第二行输入N的整数d1,d2,。。。,dn,代表武力值
第三行输入N个整数p1,p2,。。。,pn,代表收买第N只怪兽所需的金币数
(1 <= N <= 50,1 <= d1,d2,…,dn <= 1012,1 <= p1,p2,…,pn <= 2)
输出描述
输出一个整数,代表所需最小金币数
输入
3
8 5 10
1 1 2
输出
2
输入
4
1 2 4 8
1 2 1 2
输出
6
问题分析
这道题是一道比较典型的动态规划问题,在遇到怪兽的时候,如果我们的武力值小于怪兽,那么我们为了保命只能买下它,而如果我们的武力值大于怪兽,我们可以选择买下它,也可以选择直接将它打到,代码如下所示:
public class Main {
static int[] power;
static int[] price;
public static int solution() {
if (power == null || power.length == 0){
return 0;
}
return solution(0, 0, 0);
}
private static int solution(int selfPower, int cost, int i) {
if (i == power.length){
return cost;
}
if (selfPower < power[i]){
return solution(selfPower+power[i], cost+price[i], i+1);
} else {
int a = solution(selfPower, cost, i+1);
int b = solution(selfPower+power[i], cost+price[i], i+1);
return Math.min(a, b);
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
int n = in.nextInt();
power = new int[n];
price = new int[n];
for (int i = 0; i < n; i++){
power[i] = in.nextInt();
}
for (int i = 0; i < n; i++){
price[i] = in.nextInt();
}
System.out.println(solution());
}
in.close();
}
}
如果对动态规划问题比较陌生的可以查看这篇文章:【动态规划问题】,可能会对你理解该问题的代码有所帮助~
006. 构造回文
问题描述
给定一个字符串s,你可以从中删除一些字符,使得剩下的串是一个回文串。如何删除才能使得回文串最长呢?输出需要删除的字符个数。
输入描述
输入数据有多组,每组包含一个字符串s,且保证:1<=s.length<=1000.
输出描述
对于每组数据,输出一个整数,代表最少需要删除的字符个数。
输入
abcda
输出
2
2
问题分析
这道题我们可以做如下转换:求原字符串和反转字符串的公共最长子序列,将原字符串长度减去该子序列的长度即为要删除的字符个数。所以这道题转化为了一个动态规划问题,代码如下所示:
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
String str = in.nextLine();
System.out.println(solution(str));
}
}
public static int solution(String str) {
StringBuilder src = new StringBuilder(str);
String reverse = src.reverse().toString();
int n = str.length();
int[][] dp = new int[n+1][n+1];
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
if (str.charAt(i-1) == reverse.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return n-dp[n][n];
}
}
007. 字符移位
问题描述
小Q最近遇到了一个难题:把一个字符串的大写字母放到字符串的后面,各个字符的相对位置不变,且不能申请额外的空间。你能帮帮小Q吗?
输入描述
输入数据有多组,每组包含一个字符串s,且保证:1<=s.length<=1000.
输出描述
对于每组数据,输出移位后的字符串。
输入
AkleBiCeilD
输出
kleieilABCD
问题分析
这道题的简单做法就是从前往后遍历,检测到大写字母时直接将大写字母移动到字符串的最后一个位置上去,时间复杂度为 O ( n 2 ) \ O(n^{2}) O(n2),实现代码如下所示:
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
String str = in.nextLine();
System.out.println(solution(str));
}
}
private static String solution(String str) {
if (str == null || str.length() == 0){
return "";
}
int len = str.length();
int n = len-1;
char[] ch = str.toCharArray();
for (int i = 0; i <= n; ){
if (ch[i] <= 'Z' && ch[i] >= 'A'){
char temp = ch[i];
int index = i;
// 将大写字符后面的字符全部往前挪一个位置,空出最后面的位置给大写字符进行放置
for (int j = i+1; j < len; j++){
ch[index++] = ch[j];
}
ch[len-1] = temp;
n--;
} else {
i++;
}
}
return new String(ch);
}
}
008. 有趣的数字【待解决】
问题描述
小Q今天在上厕所时想到了这个问题:有n个数,两两组成二元组,相差最小的有多少对呢?相差最大呢?
输入描述
输入包含多组测试数据。
对于每组测试数据:
N - 本组测试数据有n个数
a1,a2…an - 需要计算的数据
保证:
1<=N<=100000,0<=ai<=INT_MAX.
输出描述
对于每组数据,输出两个数,第一个数表示差最小的对数,第二个数表示差最大的对数。
输入
6
45 12 45 32 5 6
输出
1 2
问题分析
先将数据从小到大进行排序,相差最大的肯定是头尾两个数组成的了,而相差最小的则是出现在相邻两个数之差,代码如下所示:
009. 零钱兑换
问题描述
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例一
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例二
输入: coins = [2], amount = 3
输出: -1
说明
你可以认为每种硬币的数量是无限的。
问题分析
典型的动态规划问题,用 d p [ i ] \ dp[i] dp[i] 表示凑成 i \ i i 元时,所需硬币的个数,代码如下所示:
public class Main {
public static int solution(int[] coins, int amount) {
if (coins == null || coins.length == 0 || amount <= 0){
return -1;
}
int n = coins.length;
int[] dp = new int[amount+1];
for (int i = 0; i <= dp.length; i++) {
dp[i] = Integer.MAX_VALUE;
}
dp[0] = 0;
for (int i = 1; i <= amount; i++){
for (int j = 0; j < n; j++){
if (i-coins[j] >= 0 && dp[i-coins[j]] != Integer.MAX_VALUE && dp[i] > dp[i-coins[j]] + 1){
dp[i] = dp[i-coins[j]] + 1;
}
}
}
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
int n = in.nextInt();
int amount = in.nextInt();
int[] coins = new int[n];
for (int i = 0; i < n; i++){
coins[i] = in.nextInt();
}
System.out.println(solution(coins, amount));
}
}
}
010. 走栅格
问题描述
输入一个 N × M \ N×M N×M 矩阵栅格,每个栅格都有一个高程值代表海拔高度,小明从出发点到终点有几条不同路径?每次只能往更高海拔的地方去,走过的地方不能再走,只能前后左右走。
输入描述
第一行代表 M × N \ M×N M×N 网格大小。后面是 M × N \ M×N M×N 的矩阵。最后一行前两个数字代表起始坐标,后面两个数字代表目的坐标(坐标从左上角(0,0)开始)。
输出描述
路径数
输入
5 4
0 1 0 0
0 2 3 3
0 3 0 0
0 4 5 6
0 7 6 0
0 1 4 1
输出
2
问题分析
走迷宫类的问题一般采用深度优先搜索的方式进行解题,虽然题目有规定走过的地方不能再走,但是由于我们只能往高的地方走,因此走过的地方明显是不会再走的了,可以省去这个判断,代码如下所示:
public class Main {
static int[][] dp;
static int startX;
static int startY;
static int endX;
static int endY;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
int n = in.nextInt();
int m = in.nextInt();
dp = new int[n][m];
for (int i = 0; i < n; i++){
for (int j = 0; j < m; j++){
dp[i][j] = in.nextInt();
}
}
startX = in.nextInt();
startY = in.nextInt();
endX = in.nextInt();
endY = in.nextInt();
System.out.println(solution());
}
}
private static int solution() {
if (dp == null){
return 0;
}
return dfs(startX, startY);
}
private static int dfs(int x, int y) {
int result = 0;
int n = dp.length;
int m = dp[0].length;
if (x == endX && y == endY){
return 1;
} else {
if (x - 1 >= 0 && dp[x-1][y] > dp[x][y]){
result += dfs(x-1, y);
}
if (x + 1 < n && dp[x+1][y] > dp[x][y]){
result += dfs(x+1, y);
}
if (y - 1 >= 0 && dp[x][y-1] > dp[x][y]){
result += dfs(x, y-1);
}
if (y + 1 < m && dp[x][y+1] > dp[x][y]){
result += dfs(x, y+1);
}
}
return result;
}
}
011. 蛇形字符串
问题描述
输入一个字符串(不含空格),请寻找输入中包含的所有蛇形字符串。
蛇形字符串定义:
- 蛇形字符串由连续字符对组成,其特点如下:
1.1 字符对定义:字符对由同一字母的大写和小写组成(前大后小)。如:Aa,Dd
1.2 蛇形字符串中包含的字符对,必须是连续字母,并按照字母顺序排序。如:AaBbCc 或 OoPpQqRrSs- 从输入中寻找字符组成蛇形字符串(字符顺序不限),符合规则:
2.1 每次寻找必须是最长的蛇形字符串
2.2 使用过的字符串不能重复使用
例:输入 SxxsrR^AaSs 正确过程(粗体为寻找到的字符):Step1: S xx srR^AaSs -> RrSs(找到两对连续字符对:Ss, Rr。可以组成蛇形字符串。另,Ss后应该是Tt,但当前字符串SxxsrR^AaSs中不包含,所以当前蛇形字符串到Ss结束。本轮查找解结果为RrSs。)
Step2: xx^Aa Ss -> Aa
Step3: xx^Ss -> Ss
错误过程1:
SxxsrR^AaSs(违反规则2.1:Ss, Rr可以组成更长的连续蛇形字符串RrSs)
问题分析
这道题被说的挺复杂的,实际上难度一般,笔者的做法是用两个长度均为 26 的数组分别保存字符串中大小写字母的个数,然后再对这两个数组进行整理,例如 A 有 6 个,a 有 1 个,那么 A 我们也记录为1个,因为多出的5个我们是无法组成蛇形字符串的。最后从 A/a 开始逐个输出即可,代码如下所示:
public class Main {
static int[] up = new int[26];
static int[] down = new int[26];
public static void solution(String str){
if (str == null || str.length() <= 1){
return;
}
// 1. 遍历存储
for (int i = 0; i < str.length(); i++){
char c = str.charAt(i);
if (c <= 'z' && c >= 'a'){
down[c-'a']++;
} else if (c <= 'Z' && c >= 'A'){
up[c-'A']++;
}
}
// 2. 调整数组
for (int i = 0; i < 26; i++){
int min = Math.min(down[i], up[i]);
down[i] = min;
up[i] = min;
}
// 3. 输出
for (int i = 0; i < 26;){
if (up[i] > 0){
StringBuilder sb = new StringBuilder();
for (int j = i; j < 26; j++){
if (up[j] == 0){
break;
}
char upChar = (char) (j + 'A');
char downChar = (char) (j + 'a');
up[j]--;
sb.append(upChar).append(downChar);
}
System.out.println(sb);
i = 0;
} else {
i++;
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
String str = in.next();
solution(str);
}
}
}
012. 蜜蜂采蜜
问题描述
蜂巢在坐标(0,0)的位置,有五处花丛,蜜蜂从蜂巢出发,要把五处花丛的花蜜采完再回到蜂巢,蜜蜂飞行的最短距离是多少?
输入描述
一行输入,10个数分别是五处花丛的坐标( x1, y1, x2, y2, x3, y3, x4, y4, x5, y5),用空格隔开。
输出描述
输出最短距离,距离向下取整。
问题分析
这道题是一道旅行商问题,一开始想到是用贪心算法来做,但是最后贪心算法没做出来,后面看了别人的实现,用的全排列的方式,虽然属于暴力求解,但是对于5个点的情况还是可以接受的,代码如下所示:
public class Main {
private static int dist = Integer.MAX_VALUE;
private static Point[] points = new Point[6];
static class Point{
int x;
int y;
Point(int x, int y){
this.x = x;
this.y = y;
}
}
/**
* 交换
*/
private static void swap(Point[] points, int i, int j){
Point temp = points[i];
points[i] = points[j];
points[j] = temp;
}
/**
* 全排类,穷举出所有可能的路径依次计算然后取最小值即为我们要求的路径长度
*/
public static void solution(int k){
if (k == 5){
dist = Math.min(dist, calculateDist());
} else {
for (int i = k; i <= 5; i++){
swap(points, i, k);
solution(k+1);
swap(points, i, k);
}
}
}
private static int calculateDist() {
double sum = 0;
for (int i = 1; i < 6; i++){
int x1 = points[i].x;
int y1 = points[i].y;
int x2 = points[i-1].x;
int y2 = points[i-1].y;
sum += Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2));
}
// 返程路段
sum += Math.sqrt(Math.pow(points[0].x - points[5].x, 2)
+ Math.pow(points[0].y - points[5].y, 2));
return (int) Math.ceil(sum);
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Point point = new Point(0,0);
points[0] = point;
while (in.hasNext()){
for (int i = 1; i <= 5; i++){
point = new Point(in.nextInt(), in.nextInt());
points[i] = point;
}
// 这里我们从1开始全排列而不是0,因为蜂巢永远是蜜蜂的起点,所以它不需要
// 参与全排列,永远位于首位
solution(1);
System.out.println(dist);
}
}
}
如果对这道题的实现代码有疑问的话可以先学习下全排列算法的相关知识再来看这段代码就会很简单了!
013. 农夫养牛
问题描述
一个农夫饲养了一批怪物牛,他发现这批牛的繁殖能力惊人,一对牛每月繁殖一对小牛。每一对小牛在出生后,需要花三个月时间来生长,第四个月开始繁殖。
输入描述
第一行输入N,表示N组数据
第二行开始,输入N数据,每组数据两行:分别为 M ( 1 ≤ M ≤ 100 ) \ M(1≤ M ≤ 100) M(1≤M≤100) 对牛,第 N ( 1 ≤ M ≤ 50 ) \ N(1≤ M ≤ 50) N(1≤M≤50) 个月。
输出描述
输出N组数据的结果,每组结果占一行
假设怪物牛不存在死亡的情况,计算开始初始数量为M对牛的情况下,第N个月牛的总数(对)。
问题分析
这道题有点类似于找规律的问题,笔者的做法是直接模拟牛的生长规律,通过递归的方式获得最终结果,代码如下所示:
public class Main {
public static int solution(int parent, int n){
if (parent == 0){
return 0;
}
return f(parent, 0, 0, 0, 0, 0, n);
}
/**
* 状态方程
*
* @param parent 成年牛的对数
* @param child0 初生的小牛的对数
* @param child1 一个月大的牛的对数
* @param child2 两个月大的牛的对数
* @param child3 三个月大的牛的对数
* @param i 第i个月
* @param n 总共有n个月
* @return 所有牛的对数
*/
private static int f(int parent, int child0, int child1, int child2, int child3, int i, int n) {
if (i == n){
return parent + child0 + child1 + child2 + child3;
} else {
return f(parent+child3, parent+child3, child0, child1, child2, i+1, n);
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
int cnt = in.nextInt();
for (int i = 0; i < cnt; i++){
int n = in.nextInt();
int m = in.nextInt();
System.out.println(solution(n, m));
}
}
in.close();
}
}
014. 重要城市
问题描述
小Q所在的王国有n个城市,城市之间有m条单向道路连接起来。对于一个城市v,从城市v出发可以到达的城市数量为x,从某个城市出发可以到达城市v的城市数量为y,如果y>x,则城市v是一个重要城市(间接可达也算可以到达)。
小Q希望你能帮他计算一下王国中一共有多少个重要城市。
输入描述
输入包括m+1行,
第一行包括两个数n和m(1 <= n,m <= 1000),分别表示城市数和道路数。
接下来m行,每行两个树u,v(1 <= u,v <= n),表示一条从u到v的有向道路,输入中可能包含重边和自环。
输出描述
输出一个数,重要节点的个数。
输入
4 3
2 1
3 2
4 3
输出
2
问题分析
这道题凭直觉就是一道有向图的问题,采用一个二维数组 reachable[ ][ ] 来表示两个点之前是否可达,例如 reachable[1][2] = true表示城市1到城市2是可达的,注意这里的可达是包括间接可达的。因此问题的关键就变成了除去题目中给出的直接可达的点之外,我们还要关于某个城市间接可达的点,这里采用了深度优先搜索进行处理,代码如下所示:
public class Main {
static boolean[][] dp;
static boolean[][] reachable;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNext()){
int n = in.nextInt();
int m = in.nextInt();
dp = new boolean[n][n];
reachable = new boolean[n][n];
for (int i = 0; i < m; i++){
int from = in.nextInt();
int to = in.nextInt();
dp[from-1][to-1] = true;
}
System.out.println(solution());
}
}
private static int solution() {
if (dp == null || dp.length == 0){
return 0;
}
int n = dp.length;
// 深度优先搜索
for (int i = 0; i < n; i++){
dfs(i, i);
}
int result = 0;
for (int i = 0; i < n; i++){
int from = 0;
int to = 0;
for (int j = 0; j < n; j++){
if (reachable[i][j]){
to++;
}
if (reachable[j][i]){
from++;
}
}
if (from > to){
result++;
}
}
return result;
}
/**
* 深度优先搜索
*
* @param src 原始点
* @param from 原始点可达的点
*/
private static void dfs(int src, int from) {
for (int i = 0; i < dp[from].length; i++