一、跳石头(二分)
又是没看出来的二分,二分题目藏的好深啊,主要是难在二分的check函数上,不好写。
import java.util.*;
import java.io.*;
public class Main {
static int[] dist;
static int n, m;
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
int l = scanner.nextInt();
n = scanner.nextInt();
m = scanner.nextInt();
// L起点到终点的距离
// n起点和终点间岩石数
// m最多可以移去的岩石数
dist = new int[n];
// 记录每块石头到起点的距离
for (int i = 0; i < n; i++) dist[i] = scanner.nextInt();
int left = 1;
int right = l;
int mid = 0;
int ans = 0;
// 注意最小为left,最大为right,因为left = mid + 1,right = mid - 1
// !!所以必须left <= right
while (left <= right) {
mid = left + (right - left) / 2;
if (check(mid)) {
ans = mid;
// 最大化最短跳跃距离,所以需要把左指针往大的方向移动
left = mid + 1;
} else {
right = mid - 1;
}
}
System.out.println(ans);
}
static boolean check(int d) {
// 搬走的石头数
int cnt = 0;
// 起点位置=0
int pos = 0;
for (int i = 0; i < n; i++) {
if (dist[i] - pos < d) {
// 距离小于d说明必须要搬走
cnt++;
} else {
// 比d大就不用搬走,可以跳到这里,同时pos也要更新
pos = dist[i];
}
}
// 看搬走的石头数是否超过至多的石头数
return cnt <= m;
}
}
二、蓝肽子序列(DP)
题目有点类似于最长公共子序列,但是不是以一个个字符为单位,而是以一个个蓝肽为单位,一个蓝肽也就是肽链的一个子串,以大写字母打头,后续跟着若干个小写字母,也就是说,我们要先把肽链中的蓝肽分割出来。
好了,分割出来了,我们把这一个个蓝肽当作一个单位,就当作是一个字符吧,现在要求最长公共子序列的最大长度,那不就是经典的DP模型?
回忆一下最长公共子序列的转移方程:
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (str[i] == str[j]) {
// 相等的话那就不用删字符,从前面的状态转移过来即可
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// 不相等可以选择删s1的字符,也可以选择删s2的字符
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
三、谈判(优先队列)
先把人数少的村庄进行谈判,把人数多的村庄留到后面谈,也就是优先队列。(本来是和合并石子一样的题目,发现并不是哦,因为两个村庄的合并是任意的,而没有顺序要求)
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int i = 0; i < n; i++) {
int cur = in.nextInt();
pq.offer(cur);
}
int sum = 0;
while (pq.size() != 1) {
int x = pq.poll();
int y = pq.poll();
sum += x + y;
pq.offer(x + y);
}
System.out.println(sum);
}
}
四、幸运数(模拟)
模拟整个删除的过程即可,外层循环遍历应该删除的下标,内层循环遍历现在有的数,在内层循环遍历过程中记录每个数的新的下标。
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
Scanner in = new Scanner(System.in);
int m = in.nextInt();
int n = in.nextInt();
boolean[] vis = new boolean[1000001];
for (int i = 2; i <= n; i++) {
if (vis[i]) continue; // 已经被删除了
int cnt = 0; // 记录下标
for (int j = 1; j <= n; j++) {
// 遍历每个数,确定它们当前的下标值
if (vis[j] == false) {
cnt++;
if (cnt % i == 0) {
vis[j] = true; // 当前数又被删掉了
}
}
}
}
int ans = 0;
for (int i = m + 1; i < n; i++) {
if (vis[i] == false) ans++;
}
System.out.println(ans);
}
}
五、二倍数对数组(中等)
先排序,例如:4,-2,2,-4变成-4,-2,2,4,如果是负数应该是第二个数的两倍等于第一个数,如果是正数,应该是第一个数的两倍等于第二个数,所以用队列进行模拟即可。
class Solution {
public boolean canReorderDoubled(int[] arr) {
Arrays.sort(arr);
Queue<Integer> queue = new LinkedList<>();
for (int cur : arr) {
if (queue.isEmpty()) {
queue.offer(cur);
} else if (queue.peek() * 2 == cur || cur * 2 == queue.peek()) {
queue.poll();
} else {
queue.offer(cur);
}
}
return queue.isEmpty();
}
}
※六、阶乘约数(约数问题)
(不同)约数个数问题是常考的问题,我们知道一个数可以唯一的表示为,某些质因数的乘积:
1的约束个数=1,2的约束个数=2(1,2),3的约束个数=2(1,3),4 = 1 * 2 * 2 = 2^2,所以4的约数个数=3(1,2,4),5 = 1 * 5,其约数个数=2(1,5),注意这里求的约数个数都是不同的约数个数,并且每个约数都是正约数(>=1)。
注意,1不是质数,所以质因子就不可能有1
有了上面的数学定理后,我们可以把1 - 100每个数做质因数分解,最后分解完100个数后,再去看每个质因数(从2开始)的指数,然后用公式(1 + a1) * (1 + a2) * (1 + a3) … * (1 + ak)算出来即可。
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) {
int[] cnt = new int[110];
for (int i = 2; i <= 100; i++) {
int tmp = i;
// 做质因数分解
for (int j = 2; j <= Math.sqrt(tmp); j++) {
while (tmp % j == 0) {
cnt[j]++;
tmp /= j;
}
}
if (tmp != 1) {
// 说明是质数
cnt[tmp]++;
}
}
long ans = 1;
// 按照质因数分解的质数求约数个数
for (int i = 2; i <= 100; i++) {
if (cnt[i] > 0) {
ans *= (1 + cnt[i]);
}
}
System.out.println(ans);
}
}
答案:39001250856960000
七、含2天数(日期问题)
日期问题最关键的是对闰年的判断,能够被4整除但不能被100整除,或者能够被400整除,就是闰年,否则不是。闰年的特点就在于,该年的天数要多一天,变成366天,该年的2月份要多1天是29天,就只有这些区别。
import java.util.*;
import java.io.*;
public class Main {
// 闰年判断
static boolean isLeap(int year) {
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
return true;
}
return false;
}
// 判断年份中是否有2
static boolean check(int year) {
while (year != 0) {
if (year % 10 == 2) return true;
year /= 10;
}
return false;
}
public static void main(String[] args) {
int ans = 0;
int[] month = new int[] {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
for (int i = 1900; i <= 9999; i++) {
if (check(i)) {
// 月份含有2那就一整年都算
if (isLeap(i)) ans += 366;
else ans += 365;
// 闰年一年366,普通年一年365
} else {
// 如果当前年没有包含2,那就看月份有没有包含2
for (int j = 1; j <= 12; j++) {
if (check(j)) {
// 如果包含2
ans += month[j];
if (j == 2 && isLeap(i)) ans++; // 如果是闰年2月还要再加1天
} else {
// 月份也没有2,那就看日期
int day = month[j];
if (j == 2 && isLeap(i)) day++;
for (int k = 1; k <= day; k++) {
if (check(k)) {
// 这一天是有2的
ans++;
}
}
}
}
}
}
System.out.println(ans);
}
}
答案:1994240
八、缩位求和(找规律)
把数字的每位加起来,如果能够被9整除,那结果就是9,否则结果就是结果对9取余。
九、积木大赛(贪心)
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] num = new int[n];
for (int i = 0; i < n; i++) {
num[i] = scanner.nextInt();
}
int ans = num[0];
for (int i = 1; i < n; i++) {
if (num[i] > num[i - 1]) ans += num[i] - num[i - 1];
}
System.out.println(ans);
}
}
※十、发现环(并查集 + 环存储)
先用并查集找到环的起点和终点,怎么找呢?用并查集进行union的时候,如果connected了,那此时的两个节点就是一个起点一个终点(题目保证只有一个环),然后用环的起点去dfs搜索,记录子节点的父节点(因为我们需要从环终点推到环起点,(环起点推到环终点并不容易,但是环终点到环起点却可以很容易找到))
import java.util.*;
import java.io.*;
public class Main {
static int start, end;
static boolean[] vis;
static int[] path;
static LinkedList<Integer>[] graph;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
vis = new boolean[n + 1];
graph = new LinkedList[n + 1];
path = new int[n + 1];
for (int i = 0; i < n + 1; i++) {
graph[i] = new LinkedList<>();
}
UF uf = new UF(n + 1);
// 1-n
start = -1;
end = -1;
while (n-- > 0) {
int a = in.nextInt();
int b = in.nextInt();
if (uf.connected(a, b)) {
// 如果a b相连了,说明a b之间有环,则这个环的起点终点就是这两个节点
// 题目保证了只有一个环
start = a;
end = b;
} else {
uf.union(a, b);
}
graph[a].add(b);
graph[b].add(a);
}
vis[start] = true;
dfs(start);
// 输出答案
LinkedList<Integer> ans = new LinkedList<>();
int tmp = end;
ans.add(end);
// 从环终点推到环起点(不要从环起点开始推!)
while (path[tmp] != 0) {
ans.add(path[tmp]);
tmp = path[tmp];
}
Collections.sort(ans);
for (int cur : ans) {
System.out.print(cur + " ");
}
}
static void dfs(int u) {
for (int v : graph[u]) {
if (vis[v]) continue;
vis[v] = true;
// 记录路径
path[v] = u;
// 找到了环终点就不要再找了
if (v == end) {
return;
}
dfs(v);
}
}
}
class UF {
int count;
int[] size;
int[] parent;
UF(int n) {
this.count = n;
this.parent = new int[n];
this.size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
int find(int x) {
while (x != parent[x]) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
boolean connected(int p, int q) {
return find(q) == find(p);
}
void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
if (size[rootP] > size[rootQ]) {
// 小树接在大树后面
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
count--;
}
int count() {
return count;
}
}
十一、数列求值(数学推导)
重在一个数学公式的推导,用到了数列中的很多知识。
十二、公约数(数论)
问题描述
给定正整数 a, b, c,请问有多少个正整数,是其中至少两个数的约数。
输入格式
输入一行包含三个正整数 a, b, c。
输出格式
输出一行包含一个整数,表示答案。
样例输入
30 70 35
样例输出
6
样例说明
1、2、5、7、10、35满足条件。
评测用例规模与约定
对于 50% 的评测用例,1 <= a, b, c <= 1000000。
对于所有评测用例,a, b, c 不超过 10**12(10的12次方)。
找数,使得该数是a b 或 a c 或 b c的公约数,这里需要注意的是,两个数的公约数一定是这两个数的最大公约数(GCD)的约数,eg:24 与 8,gcd=8,24和8的公约数有1、2、4、8,都是gcd的约数。
import java.io.*;
import java.util.*;
public class Main {
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
static Set<Long> set = new HashSet<>(); // 方便去重
public static void main(String[] args) throws IOException {
String[] input = reader.readLine().trim().split(" ");
long a = Long.parseLong(input[0]);
long b = Long.parseLong(input[1]);
long c = Long.parseLong(input[2]);
solve(a, b);
solve(b, c);
solve(a, c);
System.out.println(set.size());
}
static long gcd(long a, long b) {
return b == 0 ? a : gcd(b, a % b);
}
static void solve(long a, long b) {
long d = gcd(a, b);
// 两个数的公约数一定是两个数的gcd的约数
for (long i = 1; i * i <= d; i++) { // 只遍历到sqrt(d)
if (d % i == 0) {
set.add(i);
// 8 % 2 == 0, 8 / 2 = 4, 4 != 2, 所以4也算入约数
if (i != d / i) set.add(d / i); // 快速找到另一半约数
}
}
}
}