
本篇博客旨在记录自已的算法刷题练习成长,里面注有详细的代码注释以及和个人的思路想法,希望可以给同道之人些许帮助。本人也是算法小白,水平有限,如果文章中有什么错误或遗漏之处,望各位可以在评论区指正出来,各位共勉💪。
文章目录
1、数字接龙
小蓝最近迷上了一款名为《数字接龙》的迷宫游戏,游戏在一个大小为 N×N 的格子棋盘上展开,其中每一个格子处都有着一个 0...K−1 之间的整数。游戏规则如下:
- 从左上角
(0,0) 处出发,目标是到达右下角 (N−1,N−1) 处的格子,每一步可以选择沿着水平/垂直/对角线方向移动到下一个格子。 - 对于路径经过的棋盘格子,按照经过的格子顺序,上面的数字组成的序列要满足:0,1,2,...,K−1,0,1,2,...,K−1,0,1,2... 。
- 途中需要对棋盘上的每个格子恰好都经过一次(仅一次)。
- 路径中不可以出现交叉的线路。例如之前有从 (0,0) 移动到 (1,1) ,那么再从 (1,0) 移动到 (0,1) 线路就会交叉。
为了方便表示,我们对可以行进的所有八个方向进行了数字编号,如下图 2 所示;因此行进路径可以用一个包含 0...7 之间的数字字符串表示,如下图 1 是一个迷宫示例,它所对应的答案就是:41255214。

现在请你帮小蓝规划出一条行进路径并将其输出。如果有多条路径,输出字典序最小的那一个;如果不存在任何一条路径,则输出 −1。
用例规模:
对于 80% 的评测用例:1≤N≤5 。
对于 100% 的评测用例:1≤N≤10,1≤K≤10 。
解题代码:
java
import java.util.Scanner;
// dfs
public class Main {
static int[][] direction = new int[][]{
{0,-1,0}, // 上
{1,-1,1}, // 右上
{1,0,2}, // 右
{1,1,3}, // 右下
{0,1,4}, // 下
{-1,1,5}, // 左下
{-1,0,6}, // 左
{-1,-1,7}, // 坐上
};
static String[][] hash;
static int N;
static int K;
static int[][] graph;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
K = sc.nextInt();
graph = new int[N+1][N+1];
hash = new String[N+1][N+1];
// 表示棋盘内的数字
for (int i = 1;i <= N; i++){
for (int j = 1; j <= N; j++) {
graph[i][j] = sc.nextInt();
}
}
// 至此准备工作已完成
if (!dfs(1,1,graph[1][1],"")) System.out.println("-1");
}
public static boolean dfs(int x,int y, int k, String str) {
if (x == N && y == N && str.length() == N * N - 1) {
System.out.println(str);
return true;
}
for (int[] ints : direction) {
int X = x + ints[1];
int Y = y + ints[0];
//边界判断
if (X == 0 || Y == 0 || X == N + 1 || Y == N + 1) continue;
//判断是否连续 且 判断下一个位置是否已被访问过
if (graph[X][Y] != (k + 1) % K || hash[X][Y] != null) continue;
// 复合边界条件且下一个位置没被访问过且连续
if ((X == x || Y == y)||!(check(x, Y, "" + X + y) || check(X, y, "" + x + Y))) {
// 水平或垂直
hash[x][y] = "" + X + Y;
if (dfs(X, Y, (k + 1) % K, str + ints[2])) return true;
hash[x][y] = null;
}
}
return false;
}
public static boolean check(int x, int y, String re) {
if (hash[x][y] == null) return false;
else if (hash[x][y].equals(re)) return true;
return false;
}
}
2、纪念品
小伟突然获得一种超能力,他知道未来 T 天 N 种纪念品每天的价格。某个纪念品的价格是指购买一个该纪念品所需的金币数量,以及卖出一个该纪念品换回的金币数量。
每天,小伟可以进行以下两种交易无限次:
- 任选一个纪念品,若手上有足够金币,以当日价格购买该纪念品;
- 卖出持有的任意一个纪念品,以当日价格换回金币。
每天卖出纪念品换回的金币可以立即用于购买纪念品,当日购买的纪念品也可以当日卖出换回金币。当然,一直持有纪念品也是可以的。
T 天之后,小伟的超能力消失。因此他一定会在第 T 天卖出所有纪念品换回金币。
小伟现在有 M 枚金币,他想要在超能力消失后拥有尽可能多的金币。
输入描述:
第一行包含三个正整数 T,N,M,相邻两数之间以一个空格分开,分别代表未来天数 T,纪念品数量 N,小伟现在拥有的金币数量 M。
接下来 T 行,每行包含 N 个正整数,相邻两数之间以一个空格分隔。第 i 行的 N 个正整数分别为 Pi,1,Pi,2,......,Pi,N ,其中 Pi,j 表示第 i 天第 j 种纪念品的价格。
其中,T≤100,N≤100,M≤103,所有价格 1≤Pi,j≤104,数据保证任意时刻,小明手上的金币数不可能超过 104。
输出描述:
输出一行,包含一个正整数,表示小伟在超能力消失后最多能拥有的金币数量。
解题代码:
java
import java.util.Arrays;
import java.util.Scanner;
// 动规
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取未来天数t,纪念品数量n,初始金币数量m
int t = sc.nextInt();
int n = sc.nextInt();
int m = sc.nextInt();
// 存储每天每种纪念品的价格
int[][] arr = new int[t+1][n+1];
for (int i = 1; i <= t; i++) {
for (int j = 1; j <= n; j++) {
arr[i][j]=sc.nextInt();
}
}
sc.close();
// dp[i]表示第 i 天结束时最多拥有的金币数量
int[] dp = new int[10001];
int money = m;
// 遍历每一天
for (int day = 1; day < t; day++) {
// 重置dp数组,处理当天的背包问题
Arrays.fill(dp, 0);
// 遍历每种纪念品
for (int item = 1; item <= n; item++) {
int cost = arr[day][item]; // 当日成本
int profit = arr[day+1][item] - cost; // 单件利润
// 完全背包:正序更新; 计算该纪念品当日最多能获得多少利润
for (int p = cost; p <= money; p++) {
dp[p] = Math.max(dp[p], dp[p - cost] + profit);
}
}
// 更新当天结束时拥有的金币数量,以作为第二天的初始金币
money += dp[money];
}
System.out.println(money);
}
}
3、数的划分
将整数 n 分成 k 份,且每份不能为空,任意两份不能相同(不考虑顺序)。
例如:n=7,k=3,下面三种分法被认为是相同的。
1,1,5;1,5,1;5,1,1;
问有多少种不同的分法。
输入描述:
输入一行,2 个整数 n,k (6≤n≤200,2≤k≤6)。
输出描述:
输出一个整数,即不同的分法。
解题代码:
java
import java.util.Scanner;
/**
* 递归
*/
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n =sc.nextInt();
int k =sc.nextInt();
sc.close();
System.out.println(fun(n,k));
}
/**
* 分两种情况:
* 一种是至少有一个部分为1。这时候剩下的n-1分成k-1部分。
* 另一种是所有部分都>=2,这时候每个部分减1,总和减少k,即n-k分成k部分。
*/
private static int fun(int n, int m) {
if (n < m) return 0;
if (n == 1)return 1;
if (m == 1)return 1;
return fun(n-m, m)+fun(n-1, m-1);
}
}
4、选树
已知 n 个整数 x1,x2,⋯,xn,以及一个整数 k(k<n)。从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:
3+7+12=22 3+7+19=29 7+12+19=38 3+12+19=34。
现在,要求你计算出和为素数共有多少种。 例如上例,只有一种的和为素数:3+7+19=29。
输入描述:
n,k(1≤n≤20,k<n)
x1,x2,⋯,xn(1≤xi≤5×106)
输出描述:
一个整数(满足条件的种数)。
解题代码:
java
import java.util.Scanner;
public class Main {
static int[] flat;
static int ans = 0; // 记录数值相加的和
static int count = 0; // 素数个数
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
int[] arr = new int[n];
flat = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
sc.close();
// 递归排列
DFS(k,arr,0,0);
System.out.println(count);
}
private static void DFS(int k,int[] nums, int recode, int index){
// 判断当前递归路径是否已选择了k个数字
if (recode == k){
if (prime(ans)){
count++;
return;
}
return;
}
// 遍历整个数组
for (int i = index; i < nums.length; i++) {
// 判断当前数字是否被访问过
if (flat[i] != 1){
flat[i] = 1; // 标记已访问
ans += nums[i]; // 将该数字加入
DFS(k, nums, recode+1, i+1); // 递归调用
ans -= nums[i]; // 回溯,撤销数字的加入
flat[i] = 0; // 回溯,撤销标记
}
}
}
// 判断是否为素数
private static boolean prime(int num){
// 如果num不是素数,那它的一个因数肯定小于等于他的平方根
for (int i = 2; i <= Math.sqrt(num); i++) {
if (num % i == 0) return false;
}
return true;
}
}
5、过河卒
如图,A 点有一个过河卒,需要走到目标 B 点。卒行走规则:可以向下、或者向右。同时在棋盘上的 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。
例如上图 C 点上的马可以控制 9 个点(图中的 P1,P2,⋯P8 和 C)。卒不能通过对方马的控制点。
棋盘用坐标表示,A 点(0,0)、B 点 ( n , m ) ( n , m ≤ 20 ) (n,m)(n,m \leq 20) (n,m)(n,m≤20),同样马的位置坐标是需要给出的。
现在要求你计算出卒从 A 点能够到达 B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
解题代码:
java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int bx = sc.nextInt(), by = sc.nextInt(), cx = sc.nextInt(), cy = sc.nextInt();
int[] targetX = { 0, -1, -2, -2, -1, 1, 2, 2, 1 };
int[] targetY = { 0, -2, -1, 1, 2, 2, 1, -1, -2 };
long[][] dp = new long[bx + 1][by + 1];
for (int i = 0; i < 9; ++i) { //标记马进攻坐标
int x = cx + targetX[i];
int y = cy + targetY[i];
if (x >= 0 && x <= bx && y >= 0 && y <= by) {
dp[x][y] = -1;
}
}
for (int i = 0; i <= bx; ++i) { // 定义dp坐标x初始值
if (dp[i][0] != -1) {
dp[i][0] = 1;
} else break; //截至前i项
}
for (int j = 0; j <= by; ++j) { // 定义dp坐标y初始值
if (dp[0][j] != -1) {
dp[0][j] = 1;
} else break; //截至前j项
}
for (int i = 1; i <= bx; ++i) {
for (int j = 1; j <= by; ++j) {
if (dp[i][j] == 0) { //不是马进攻坐标,如果是,则当前的上和左不可转移,作废
// dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; //转移
dp[i][j] = Math.max(dp[i - 1][j], 0) + Math.max(dp[i][j - 1], 0);
}
}
}
System.out.print(dp[bx][by]);
}
}
6、好数
一个整数如果按从低位到高位的顺序,奇数位 (个位、百位、万位 ⋯⋯ ) 上的数字是奇数,偶数位 (十位、千位、十万位 ⋯⋯ ) 上的数字是偶数,我们就称之为 "好数"。
给定一个正整数 NN ,请计算从 1 到 NN 一共有多少个好数。
解题代码:
java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long n = sc.nextLong();
int sum = 0;
for (int i = 1; i <= n; i++) {
if (pr(i) == 1){
sum++;
}
}
System.out.println(sum);
}
public static int pr(int a) {
int x = 1;
while (a != 0){
int t = a%10;
// 奇数位上为奇数,偶数位上为偶数
if (x % 2 == 1){
if (t % 2 == 0) return 0;
}else {
if (t % 2 == 1) return 0;
}
x++;
a /= 10;
}
return 1;
}
}
有帮助的话,希望可以点赞❤️+收藏⭐,谢谢各位大佬~~✨️✨️✨️