前言
在本篇博客中,我们将探讨两道常见的算法题,分别是“特别数的和”和“错误的票据”。这类问题考察了对数据结构的基本理解以及如何通过高效算法解决实际问题。通过模拟实现这两道题,我们将详细分析解决过程,并展示如何应用基本的排序、查找和数学运算技巧来优化解法。
特别数的和
小明对数位中含有 2、0、1、9 的数字很感兴趣(不包括前导 0),在 1 到 40
中这样的数包括 1、2、9、10 至 32、39 和 40,共 28 个,他们的和是 574。
请问,在 1 到 n 中,所有这样的数的和是多少?
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数,表示满足条件的数的和。
数据范围
1≤n≤10000
输入样例:
40
输出样例:
574
算法思路
这道题n的最大值到10000,可以直接暴力枚举。
枚举从1~n中的所有数字,然后再枚举每一个数中的每一位,判断其中是否包含2、0、1、9这4位数字,包含就符合条件,然后求符合条件的数的和即可。
代码如下
import java.io.*;
public class Main {
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
public static void main(String[] args) throws Exception{
int n = nextInt();
int res = 0;
for(int i = 1;i <= n;i++){
int x = i;
while (x != 0){
int t = x % 10;
x /= 10;
if(t == 2 || t == 0 || t == 1 || t == 9){
res += i;
break;
}
}
}
pw.println(res);
pw.flush();
}
public static int nextInt() throws Exception {
st.nextToken();
return (int) st.nval;
}
}
错误票据
某涉密单位下发了某种票据,并要在年终全部收回。
每张票据有唯一的ID号。
全年所有票据的ID号是连续的,但ID的开始数码是随机选定的。
因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。
你的任务是通过编程,找出断号的ID和重号的ID。
假设断号不可能发生在最大和最小号。
输入格式
第一行包含整数 N,表示后面共有 N 行数据。
接下来 N 行,每行包含空格分开的若干个(不大于100个)正整数(不大于100000),每个整数代表一个ID号。
输出格式
要求程序输出1行,含两个整数 m,n,用空格分隔。其中,m表示断号ID,n表示重号ID。
数据范围
1≤N≤100
输入样例:
2
5 6 8 11 9
10 12 9
输出样例:
7 9
算法思路
用整型数组arr来存储票据ID,然后设置一个布尔类型的数组st,st[i]为true表示ID为i的号码已存在,为false表示ID为i的号码未存在,遍历每一个号码,先进行判断如果ID已存在,说明此ID就是重号ID,如果未存在就设置为已存在。
还需寻找断号ID,在st数组中下标肯定是有序的,故需找到最小的ID,即设置start = 0;然后循环当st[start]为false时,进入循环,说明此时不是存入的ID,随后start++,当遇到的第一个为true,就说明此时start就是最小的ID。最后再从start开始遍历,找到的第一个st[i]为false就说明此时的i就是断号ID。
另一种做法通过排序的方法做。首先将当前的票据ID进行排序。排序可以帮助我们轻松发现重复的ID和缺失的ID。重复ID:通过遍历排序后的ID数组,如果某个ID与前一个ID相同,则该ID是重复的。缺失ID:如果相邻两个ID之间的差等于2,那么缺失的ID就是大的ID减一。
代码如下
import java.io.*;
public class Main {
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int N = 100010; // 票据ID的最大范围
static int[] arr = new int[N];
static boolean[] flag = new boolean[N];
static int index = 0;
static int n = 0;
public static void main(String[] args) throws Exception {
n = nextInt();
while (n-- > 0) {
String[] s = nextLine().split(" ");
for (int i = 0; i < s.length; i++) {
arr[index++] = Integer.parseInt(s[i]);
}
}
int Did = 0; // 重号ID
for (int i = 0; i < index; i++) {
if (flag[arr[i]]) {
Did = arr[i]; // 找到重号ID
}
flag[arr[i]] = true;
}
int start = 0;
while ( !flag[start]) {
start++; // 找到票据的起点,即最小值
}
int Bid = 0; // 缺失的票据ID
for (int i = start; i < N; i++) {
if (!flag[i]) {
Bid = i; // 找到缺失的ID
break; // 找到第一个缺失的ID就停止
}
}
pw.println(Bid + " " + Did);
pw.flush();
}
public static int nextInt() throws Exception {
st.nextToken();
return (int) st.nval;
}
public static String nextLine() throws Exception {
return br.readLine();
}
}
用排序方法
import java.util.*;
import java.io.*;
public class Main {
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int N = 10010;
static int[] arr = new int[N];
static int n;
public static void main(String[] args)throws Exception {
int cnt = nextInt();
while (cnt-- > 0) {
String[] s = nextLine().split(" ");
for(int i = 0;i < s.length;i++) {
arr[n++] = Integer.parseInt(s[i]);
}
}
Arrays.sort(arr,0,n);
//重号
int res1 = 0;
//断号
int res2 = 0;
for(int i = 1;i < n;i++) {
if(arr[i] == arr[i-1]) {
res1 = arr[i];
}else if(arr[i] == arr[i-1] + 2) {
res2 = arr[i] - 1;
}
}
pw.println(res2 +" "+res1);
pw.flush();
}
public static int nextInt() throws Exception {
st.nextToken();
return (int) st.nval;
}
public static String nextLine() throws Exception {
return br.readLine();
}
}
总结
通过这两道算法题的解决,我们不仅加深了对基本算法思想的理解,也提升了在面对实际问题时,如何运用合适的数据结构和算法来设计高效解决方案的能力。特别数的和问题向我们展示了如何通过数学公式简化计算,而错误的票据问题则考察了我们对排序和重复检测的熟练运用。掌握这些基础算法技巧将为我们处理更复杂的问题打下坚实的基础。