算法思想图

目录
算法题
队列和栈
public class p64 {
/**
* 题目描述
* 幼儿园里有一个放倒的圆桶
* 它是一个线性结构
* 允许在桶的右边将篮球放入
* 可以在桶的左边和右边将篮球取出
* 每个篮球有单独的编号,
* 老师可以连续放入一个或多个篮球,
* 小朋友可以在桶左边或右边将篮球取出,
* 当桶只有一个篮球的情况下,
* 必须从左边取出。
* 如老师按顺序放入1、2、3、4、5 共有 5 个编号的篮球,
* 那么小朋友可以依次取出编号为1、2、3、4、5 或者 3、1、2、4、5 编号的篮球,
* 无法取出 5、1、3、2、4 编号的篮球。
* 其中 3、1、2、4、5 的取出场景为:
* 连续放入1、2、3号
* 从右边取出3号
* 从左边取出1号
* 从左边取出2号
* 放入4号
* 从左边取出4号
* 放入5号
* 从左边取出5号
* 简答起见,我们以 L 表示左,R表示右,此时取出篮球的依次取出序列为“RLLLL”。
* 输入描述
* 每次输入包含一个测试用例:
* 第一行的数字作为老师依次放入的篮球编号
* 第二行的数字作为要检查是否能够按照放入的顺序取出给定的篮球的编号,其中篮球的编号用逗号进行分隔。
* 其中篮球编号用逗号进行分隔。
* 输出描述
* 对于每个篮球的取出序列,
* 如果确实可以获取,
* 请打印出其按照左右方向的操作取出顺序
* 如果无法获取则打印“NO”。
* 备注
* 1 ≤ 篮球编号,篮球个数 ≤ 200
* 篮球上的数字不重复
* 输出的结果中 LR 必须为大写
* 用例
* 输入
* 4,5,6,7,0,1,2
* 6,4,0,1,2,5,7
* 输出
* RLRRRLL
* 说明
* 篮球的取出顺序依次为“右、左、右、右、右、左、左”
* 输入
* 4,5,6,7,0,1,2
* 6,0,5,1,2,4,7
* 输出
* NO
* 说明
* 无法取出对应序列的篮球
* 输入
* 1,2,3,4
* 1,2,3,5
* 输出
* NO
* 说明
* 不存在编号为5的篮球,所以无法取出对应编号的数据
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 假设输入通过标准输入给出,这里使用Scanner模拟
String inputLine1 = scanner.nextLine(); // 读取放入的篮球编号
String inputLine2 = scanner.nextLine(); // 读取要检查的取出序列
List<Integer> putIn = Arrays.stream(inputLine1.split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());
List<Integer> takeOutOrder = Arrays.stream(inputLine2.split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());
String result = solve(putIn, takeOutOrder);
System.out.println(result);
scanner.close();
}
private static String solve(List<Integer> putIn, List<Integer> takeOutOrder) {
Deque<Integer> stack = new ArrayDeque<>();
List<Character> moves = new ArrayList<>(); // 存储LR序列
int takeOutIndex = 0; // 用于跟踪takeOutOrder的索引
for (int ball : putIn) {
stack.push(ball);
// 尝试从取出序列中取出篮球
while (!stack.isEmpty() && takeOutIndex < takeOutOrder.size() && stack.peek() == takeOutOrder.get(takeOutIndex)
&& stack.size()>1 ) { //stack.size()>1 当桶只有一个篮球的情况下 必须从左边取出
stack.pop(); // 从右边取出
moves.add('R');
takeOutIndex++;
}
// 检查是否可以从右边取出(即栈中是否有且不是栈顶)
while (takeOutIndex < takeOutOrder.size() && stack.contains(takeOutOrder.get(takeOutIndex))) {
int rightTake = takeOutOrder.get(takeOutIndex);
Iterator<Integer> iterator = stack.descendingIterator(); // 从栈底到栈顶遍历
// 找到要取出的篮球的位置
boolean found = false;
while (iterator.hasNext()) {
int current = iterator.next();
if (current == rightTake) {
// 由于栈是后进先出的,我们需要将所有在它之上的元素重新放入一个临时栈中
Deque<Integer> tempStack = new ArrayDeque<>();
while (iterator.hasNext()) {
tempStack.push(iterator.next());
}
// 弹出元素
while (!stack.isEmpty() && !stack.peek().equals(rightTake)) {
tempStack.push(stack.pop());
}
if (!stack.isEmpty() && stack.peek() == rightTake) {
stack.pop(); // 从左边取出
moves.add('L');
}
// 将临时栈中的元素重新放回原栈
while (!tempStack.isEmpty()) {
stack.push(tempStack.pop());
}
found = true;
break;
}
}
if (found) {
takeOutIndex++;
} else {
// 如果没有找到,则无法按照给定的顺序取出篮球
return "NO";
}
}
}
// 检查是否所有篮球都按照顺序被取出
if (takeOutIndex != takeOutOrder.size()) {
return "NO";
}
// 将LR序列转换为字符串
StringBuilder sb = new StringBuilder();
for (char move : moves) {
sb.append(move);
}
return sb.toString();
}
}
|
图的遍历
广度优先搜索
BFS 遍历图 使用队列 LinkedList<Integer> queue = new LinkedList<>();
public static class Graph {
private int V; // 图的顶点数
private int[][] adjMatrix; // 邻接矩阵
// 构造函数
public Graph(int v) {
V = v;
adjMatrix = new int[v][v];
}
// 添加边到图中
void addEdge(int v, int w) {
adjMatrix[v][w] = 1; // 假设是有向图
}
// 使用 DFS 遍历图
void print( ) {
int[][] adjMatrix = this.adjMatrix;
for (int i = 0; i <V ; i++) {
for (int j = 0; j < V; j++) {
System.out.print(adjMatrix[i][j] + " ");
}
System.out.println( );
}
}
public void printGraph( ) {
int[][] adjMatrix = this.adjMatrix;
for (int i = 0; i < adjMatrix.length; i++) {
System.out.println("节点 " + i + " 的邻居: ");
for (int j = 0; j < adjMatrix[i].length; j++) {
if (adjMatrix[i][j] != 0) {
System.out.println(" --" + j);
}
}
}
}
// 使用 DFS 遍历图
void DFS(int v) {
boolean visited[] = new boolean[V];
DFSUtil(v, visited);
}
// DFS 的辅助函数,使用递归
void DFSUtil(int v, boolean visited[]) {
visited[v] = true;
System.out.print(v + " ");
for (int i = 0; i < V; ++i) {
System.out.println("DFS Visiting vertex: " +v+"," +i);
if (adjMatrix[v][i] == 1 && !visited[i]) {
DFSUtil(i, visited);
}
}
}
// 使用 BFS 遍历图
void BFS(int s) {
boolean visited[] = new boolean[V];
LinkedList<Integer> queue = new LinkedList<>();
visited[s] = true;
queue.add(s);
while (!queue.isEmpty()) {
s = queue.poll();
System.out.print(s + " ");
for (int i = 0; i < V; ++i) {
System.out.println("BFS Visiting vertex: " +s+"," +i);
if (adjMatrix[s][i] == 1 && !visited[i]) {
visited[i] = true;
queue.add(i);
}
}
}
}
}
// 测试代码
public static void main(String[] args) {
Graph g = new Graph(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
g.print();
g.printGraph( );
System.out.println("DFS (starting from vertex 2):");
g.DFS(2);
System.out.println("\nBFS (starting from vertex 2):");
g.BFS(2);
}
} |
深度优先搜索dfs
运用递归方法遍历,一般要定义方向和是否访问。
public class p15 {
/**
*题目描述:
* 在一个机房中,服务器的位置标识在 n*m 的整数矩阵网格中,1 表示单元格上有服务器,0 表示没有。如果两台服务器位于同一行或者同一列中紧邻的位置,则认为它们之间可以组成一个局域网。
* 请你统计机房中最大的局域网包含的服务器个数。
* 输入描述:
* 第一行输入两个正整数,n和m,0<n,m<=100
* 之后为n*m的二维数组,代表服务器信息
* 输出描述:
* 最大局域网包含的服务器个数。
* 示例1
* 输入:
* 2 2
* 1 0
* 1 1
* 输出:
* 3
* 说明:
* [0][0]、[1][0]、[1][1]三台服务器相互连接,可以组成局域网
*/
//广度搜索
private static int[] dx = {-1, 1, 0, 0}; // 上下左右移动
private static int[] dy = {0, 0, -1, 1};
private static int maxServers = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[][] grid = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
grid[i][j] = scanner.nextInt();
}
}
scanner.close();
boolean[][] visited = new boolean[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 1 && !visited[i][j]) {
int servers = dfs(grid, visited, i, j);
maxServers = Math.max(maxServers, servers);
}
}
}
System.out.println(maxServers);
}
private static int dfs(int[][] grid, boolean[][] visited, int x, int y) {
int servers = 1;
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 0 && nx < grid.length && ny >= 0 && ny < grid[0].length && grid[nx][ny] == 1 && !visited[nx][ny]) {
servers += dfs(grid, visited, nx, ny);
}
}
return servers;
}
}
|
双指针和滑动窗口
//双指针技术通常指的是在数组或字符串等数据结构中,使用两个指针(或索引)进行遍历或搜索的一种技术。这两个指针可以同向移动(比如都向右移动),也可以反向移动(一个向右,一个向左),或者其中一个指针在特定条件下保持不动。
滑动窗口数组/字符串处理中常用的一种技术手段,它也是一种双指针技术。在滑动窗口中,通常维护一个窗口,该窗口由开始指针和结束指针界定,窗口的大小可以变化(通过移动指针).
就是一个窗口可以理解为一个解空间,里面的满足一定条件。条件满足right++,不满足缩小窗口,通过left--,从头开的的是left++.
// 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度,
// 字符串本身是其最长的子串,子串要求:
// 1、 只包含1个字母 (a~z, A~Z),其余必须是数字;
// 2、 字母可以在子串中的任意位置;
//如果找不到满足要求的子串,如全是字母或全是数字,则返回-1。
// 输入 abC124ACb
// 输出 4 满足条件的最长子串是C124或者124A,长度都是4
//输入 a5
// 输出 2
// 字符串自身就是满足条件的子串,长度为2
/**
* 窗口的滑动:滑动窗口算法的核心在于窗口的滑动。通过不断移动右指针来扩大窗口,包含更多的元素;同时,根据需要移动左指针来缩小窗口,排除不再需要的元素。这种滑动机制使得算法能够高效地遍历数组或字符串,而无需重复处理已经检查过的元素。
* 问题的转换:滑动窗口算法通常用于解决一类特定的问题,即将复杂的嵌套循环问题转换为单层循环问题。通过维护窗口内的状态信息,算法可以避免对整个数组或字符串的重复遍历,从而降低时间复杂度。例如,在查找最长无重复字符子串的问题中,滑动窗口算法可以将原本需要两层循环的暴力解法优化为单层循环。
* 窗口的维护:在滑动窗口的过程中,需要维护窗口内的某些状态信息,以便在窗口滑动时能够高效地更新结果。这通常涉及到一些辅助数据结构,如哈希表、队列等。例如,在查找最长无重复字符子串的问题中,可以使用哈希表来记录窗口内每个字符最后出现的位置,以便在出现重复字符时能够快速地移动左指针。
* 优化与剪枝:滑动窗口算法通过维护窗口内的状态信息,并利用这些信息进行优化和剪枝。在窗口滑动的过程中,算法会不断地根据当前窗口内的状态来判断是否需要继续扩大窗口或缩小窗口,以及是否需要更新结果。这种优化机制使得算法能够在保证正确性的同时,尽可能地减少不必要的计算量。
* 应用场景:滑动窗口算法广泛应用于各种需要处理连续子数组、子序列或子字符串的问题中。例如,它可以用于查找最长无重复字符子串、最大连续子数组和、和为特定值的连续子数组等。在这些问题中,滑动窗口算法都能够通过高效地遍历和剪枝来降低时间复杂度,提高算法的效率。
*
*/
//*****"滑动窗口和双指针技术不是完全相同的东西,但它们有重叠之处和相似的应用场景。
//
//双指针技术
//双指针技术通常指的是在数组或字符串等数据结构中,使用两个指针(或索引)进行遍历或搜索的一种技术。这两个指针可以同向移动(比如都向右移动),也可以反向移动(一个向右,一个向左),或者其中一个指针在特定条件下保持不动。双指针技术的主要目的是通过减少不必要的遍历或搜索来优化算法的时间复杂度。
//
//滑动窗口
//滑动窗口是数组/字符串处理中常用的一种技术手段,它也是一种双指针技术。在滑动窗口中,通常维护一个窗口,该窗口由开始指针和结束指针界定,窗口的大小可以变化(通过移动指针),但始终保证窗口内的元素满足某种特定的性质或条件。滑动窗口的主要应用场景包括求解字符串中的子串问题、数组中的子数组问题等,尤其是在需要找到满足某种条件的最小子串/子数组长度时非常有用。
//
//两者之间的关系
//相似点:滑动窗口可以看作是双指针技术的一种特殊应用,它们都使用两个指针(或索引)来遍历数据结构。
//不同点:滑动窗口更侧重于维护一个满足特定条件的窗口,并通过移动窗口的边界来寻找解,而双指针技术则更广泛地应用于各种场景,不一定需要维护一个窗口。
//示例
//双指针技术示例:在数组中寻找两个数,使得它们的和等于一个给定的数。这里可以使用两个指针分别从数组的开头和结尾向中间遍历,直到找到满足条件的两个数或指针相遇。
//滑动窗口示例:给定一个数组和滑动窗口的大小,求滑动窗口内元素的最大值或最小值。这里维护一个固定大小的窗口,并随着遍历移动窗口的边界,同时更新窗口内的最大值或最小值。
//总的来说,滑动窗口是双指针技术的一个子集,它们在处理数组或字符串等数据结构时都非常有用,但应用场景和目的有所不同。"
//abC124ACb
public static int longestSpecialSubstringLength(String s) {
int maxLength = -1;
for (int i = 0; i < s.length(); i++) {//窗口的滑动
char c = s.charAt(i);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
// 尝试以当前字符为唯一字母,向两边扩展
int left = i - 1;
int right = i + 1;
// 检查左边是否满足条件
while (left >= 0 && (s.charAt(left) >= '0' && s.charAt(left) <= '9')) {
left--;
}
// 检查右边是否满足条件
while (right < s.length() && (s.charAt(right) >= '0' && s.charAt(right) <= '9')) {
right++;
}
// 如果左右两边都不再满足条件,则计算当前子串的长度,并更新最长长度
if ((left < 0 || (s.charAt(left) >= 'a' && s.charAt(left) <= 'z') || (s.charAt(left) >= 'A' && s.charAt(left) <= 'Z')) &&
(right >= s.length() || (s.charAt(right) >= 'a' && s.charAt(right) <= 'z') || (s.charAt(right) >= 'A' && s.charAt(right) <= 'Z'))) {
int currentLength = right - left - 1;
maxLength = Math.max(maxLength, currentLength);
}
}
}
return maxLength;
}
private void f(){
String s="abc";
int length = s.length();
//数组
int[][] data=new int[length][length];
int maxlength=Integer.MAX_VALUE;
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
// 字符串本身是其最长的子串,子串要求:
// 1、 只包含1个字母 (a~z, A~Z),其余必须是数字;
// 2、 字母可以在子串中的任意位置;
//窗口是什么 //以字符c为中心,向两边扩散的动态窗口
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
// 尝试以当前字符为唯一字母,向两边扩展
int left = i - 1;
int right = i + 1;
}
}
}
public static void main(String[] args) {
String input1 = "abC124ACb";
System.out.println("The length of the longest special substring is: " +
longestSpecialSubstringLength(input1)
); // 输出 4
String input2 = "a5";
System.out.println("The length of the longest special substring is: " + longestSpecialSubstringLength(input2)); // 输出 2
}
}
|
动态规划
计数
计算剩余数量类型,先确定数量为第一维度逐步的增加或者减少。接下来剩余数量再分配就是员工数量,第二维度就是接收者。
/* 中秋节公司分月饼,m个员工,买了n个月饼,m<=n, 单人份到最多月饼的个数为Max1,单人分到第二多月饼的个数是Max2,Max1-Max2<=3。
单人分到第n-1多月饼的个数是Max(n-1),单人分到第n多月饼的个数是Max(n),Max(n-1)-Max(n)<=3。
问有多少种分月饼的方法?
输入描述:
每一行输入m n,表示m个员工,n个月饼,m<=n
输出描述:
输出有多少种月饼分法
示例:
输入 2 4
输出 2
这个问题属于动态规划中的“计数类”问题,特别是涉及到将一定数量的物品(在这里是月饼)分配给一定数量的个体(在这里是员工),同时满足一些特定的条件(如最大月饼数量差值的限制)的计数问题。
这类问题通常要求设计一种动态规划的状态和转移方程来记录满足条件的分配方式的数量。在这个具体的问题中,状态设计可能需要考虑到:
当前已经分配的月饼数量。
当前分配后,员工手中月饼的最大值和次大值(或者更一般地,前几个最大值的分布情况),以满足Max1 - Max2 <= 3等条件。
然而,由于直接跟踪每个员工的月饼数量并验证所有条件可能会导致状态空间过大,通常我们需要找到一种更简洁的方式来表示状态,比如使用“差值”或者“分组”的概念来减少状态数。
例如,我们可以设计一个三维DP数组(或更高维,取决于具体实现和优化的复杂度),其中:
但是,值得注意的是,这个问题的直接解法可能会非常复杂,因为需要精确地跟踪和验证每一次分配后所有员工的月饼数量,并确保满足给定的差值条件。因此,在实际编程中,可能需要采用一些近似算法、启发式搜索或优化技术来降低问题的复杂度。
另外,这个问题也可能通过其他方式解决,如使用生成函数、递归加记忆化搜索等,但核心思路都是利用动态规划或类似的技术来计数满足条件的分配方式。
*/
public static void main(String[] args) {
//Max(n-1)-Max(n)<=3。
//问有多少种 分月饼的方法
int m = 0;
int n = 1;
//dp[i][j]表示有i个月饼,分给j个员工的方法数。
int[][] dp = new int[n + 1][m + 1];
//第每个人的分配到的月饼数量
// int[] d=new int[m+1];
for (int i = 1; i <= n; i++) {//月饼数量
dp[i][0] = 0;
for (int j = 1; j <= m; j++) {//员工数量 第i个员工
// 单人分到第n多月饼的个数是Max(n)
if (i < j) { // 如果月饼数少于员工数,不可能分配
dp[i][j] = 0;
} else {
//给每个员工至少分配一个
dp[i][j] = dp[i - j][j];//每个员工至少分1个月饼,但可以分多个,
// 单人分到第n-1多月饼的个数是Max(n-1),单人分到第n多月饼的个数是Max(n),Max(n-1)-Max(n)<=3。
//变量k代表在分配月饼时,额外给某个员工多分配的月饼数量。
/* 您提到的“Max(n-1)-Max(n)<=3”确实描述了在某个具体的分配方案中,第n-1个员工和第n个员工之间分配到的月饼数量的最大差值。这里的n和n-1指的是员工的编号或者顺序,而不是任意的两个员工。
然而,在动态规划的过程中,我们实际上是在考虑所有可能的分配方案。对于每一对相邻的员工(比如第1个和第2个,第2个和第3个,...,第n-1个和第n个),我们都要确保他们之间分配到的月饼数量的差值不超过3。
换句话说,“Max(n-1)-Max(n)<=3”这个条件是对每一对相邻员工都适用的。它不是一个全局的条件,而是一个局部的条件,用于确保在任何相邻的员工之间,分配到的月饼数量的差值都不会超过3。
当我们说“转变成任何了”时,我们实际上是指,在构建动态规划解决方案的过程中,我们会考虑所有可能的员工对,并确保他们之间的月饼数量差值满足条件。这样,最终的解决方案就会满足题目中对所有员工分配月饼的要求。
因此,尽管“Max(n-1)-Max(n)<=3”描述的是相邻员工之间的关系,但在动态规划的过程中,我们会确保这个条件对所有相邻的员工对都成立,从而得到一个满足题目要求的整体解决方案。
为什么k只能取1、2、3呢?这是因为你要满足那个额外的条件,即任意两个员工之间分配的月饼数量之差不能超过3。如果你给某个员工额外分配了4个月饼或更多,那么他与分到最多月饼的员工之间的差值就会超过3,这就违反了你的条件。
*/
//变量k代表在分配月饼时,额外给某个员工多分配的月饼数量
for (int k = 1; k <= 3 && i - j - k >= 0; k++) {
dp[i][j] += dp[i - j - k][j];
}
}
}
}
}
}
|
状态压缩
1 << n用来计算状态位数,就是总的状态数,用0101的二进制数表示状态,0表示否定,1表示占有或者肯定,可以看作决票的灯。用简单数据进行状态的压缩,节省算法的空间
状态压缩 DP 这里的“最短路径长度”通常指的是从起始点(通常是投递站或某个固定的起点)出发,经过一系列客户节点后,最终停留在某个客户节点上的总距离(或成本) int[][] dp = new int[(1 << n)][n + 1];
/**
* 题目描述
* 快递公司每日早晨,给每位快递员推送需要淡到客户手中的快递以及路线信息,快递员自己又查找了一些客户与客户之间的路线距离信息,
* 请你依据这些信息,给快递员设计一条最短路径,告诉他最短路径的距离。
* 不限制快递包裹送到客户手中的顺序,但必须保证都送到客户手中;
* 用例保证一定存在投递站到每位客户之间的路线,但不保证客户与客户之间有路线,客户位置及投递站均允许多次经过;
* 所有快递送完后,快递员需回到投递站;
* 输入描述
* 首行输入两个正整数n、m.
* 接下来n行,输入快递公司发布的客户快递信息,格式为:客户id 投递站到客户之间的距离distance
* 再接下来的m行,是快递员自行查找的客户与客户之间的距离信息,格式为:客户1id 客户2id distance
* 在每行数据中,数据与数据之间均以单个空格分割规格:
* 0 <=n <= 10 0 <= m <= 10 0 < 客户id <= 1000 0 < distance <= 10000
* 输出描述
* 最短路径距离,如无法找到,请输出-1
* 示例1
* 输入:
* 2 1
* 1 1000
* 2 1200
* 1 2 300
* 输出:
* 2500
* 说明:
* 快递员先把快递送到客户1手中,接下来直接走客户1到客户2之间的直通线路,最后走投递站和客户2之间的路,回到投递站,距离为1000+300+1200= 2500
* 示例2
* 输入:
* 5 1
* 5 1000
* 9 1200
* 17 300
* 132 700
* 500 2300
* 5 9 400
* 输出:
* 9200
*/
private static final int INF = Integer.MAX_VALUE / 2;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String[] s1 = scanner.nextLine().split(" ");
int n = Integer.parseInt(s1[0].toString()); // 客户数量
int m = Integer.parseInt(s1[1].toString()); // 路线数量
int[][] graph = new int[n + 1][n + 1]; // 0号节点为投递站
Map<Integer,Integer> map = new HashMap<>();
// 初始化投递站到客户的距离
for (int i = 1; i <= n; i++) {
String[] sm = scanner.nextLine().split(" ");
// Integer index = Integer.parseInt(sm[0]);
graph[0][i] =Integer.parseInt(sm[1]);
graph[i][0]=Integer.parseInt(sm[1]);
map.put(Integer.parseInt(sm[0]),i);
}
// 初始化客户之间的距离
for (int i = 0; i < m; i++) {
String[] l = scanner.nextLine().split(" ");
int[] i1=new int[l.length];
for (int j = 0; j <l.length ; j++) {
i1[j]= Integer.parseInt(l[j]);
}
int u = map.get(i1[0]);
int v = map.get(i1[1]);
int distance = i1[2];
graph[u][v] = distance;
graph[v][u] = distance;
}
// Floyd-Warshall 算法预处理所有点对之间的最短距离
for (int k = 0; k <= n; k++) {
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
if (graph[i][k]>0&& graph[k][j] >0&& graph[i][k] + graph[k][j] < graph[i][j]) {
graph[i][j] = graph[i][k] + graph[k][j];
}
}
}
}
// 状态压缩 DP 这里的“最短路径长度”通常指的是从起始点(通常是投递站或某个固定的起点)出发,经过一系列客户节点后,最终停留在某个客户节点上的总距离(或成本)
int[][] dp = new int[(1 << n)][n + 1];
for (int[] row : dp) {
Arrays.fill(row, INF);
}
dp[0][0] = 0; // 初始状态:在投递站,没有访问任何客户
// 遍历所有状态
for (int mask = 0; mask < (1 << n); mask++) {
for (int last = 0; last <= n; last++) {
if (dp[mask][last] == INF) continue; // 如果这个状态不可达,则跳过
// 尝试从last节点出发,访问下一个未访问的客户节点
for (int next = 1; next <= n; next++) {
if ((mask & (1 << (next - 1))) == 0) { // 如果next客户未被访问
int newMask = mask | (1 << (next - 1)); // 更新状态,标记next客户为已访问
dp[newMask][next] = Math.min(dp[newMask][next], dp[mask][last] + graph[last][next]);
}
}
}
}
// 查找最终结果:访问了所有客户后返回投递站的最短路径
int result = INF;
for (int i = 1; i <= n; i++) {
result = Math.min(result, dp[(1 << n) - 1][i] + graph[i][0]);
}
if (result == INF) {
System.out.println("-1"); // 如果没有解(理论上不应该发生,因为题目保证有解)
} else {
System.out.println(result);
}
scanner.close();
}
} |
回溯法
回溯一般结合递归来遍历所有结构,图中的运用需要结合dfs来使用。
public class p22 {
/**
* 题目描述
* 有一个考古学家发现一个石碑,但是很可惜,发现时其已经断成多段,原地发现n个断口整齐的石碑碎片。为了破解石碑内容,考古学家希望有程序能帮忙计算复原后的石碑文字组合数,你能帮忙吗?
* 输入描述
* 第一行输入n,n表示石碑碎片的个数。
* 第二行依次输入石碑碎片上的文字内容s,共有n组。
* 输出描述
* 输出石碑文字的组合(按照升序排列),行末无多余空格。
* 用例
* 输入 3
* a b c
* 输出 abc
* acb
* bac
* bca
* cab
* cba
* 说明 无
* 输入 3
* a b a
* 输出 aab
* aba
* baa
* 说明 无
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
String[] fragments = new String[n];
for (int i = 0; i < n; i++) {
fragments[i] = scanner.next();
}
Set<String> permutations = new HashSet<>();
permute(fragments, 0, n - 1, permutations);
List<String> sortedPermutations = new ArrayList<>(permutations);
Collections.sort(sortedPermutations);
for (String permutation : sortedPermutations) {
System.out.println(permutation);
}
}
public static void permute(String[] arr, int k, int m, Set<String> permutations) {
if (k == m) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= m; i++) {
sb.append(arr[i]);
}
permutations.add(sb.toString());
} else {
for (int i = k; i <= m; i++) {
swap(arr, k, i);
permute(arr, k + 1, m, permutations);
swap(arr, k, i); // backtrack
}
}
}
public static void swap(String[] arr, int i, int j) {
String temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
|
贪心算法
贪心算法,局部自己选择优越的方式得到全局最优解,但不一定是最优解,求最优解需要动态规划,有可能得到的,先排序,后逐步操作。
/**
Wonderland是小王居住地一家很受欢迎的游乐园。Wonderland目前有4种售票方式,分别为一日票(1天)、三日票(3天)、周票(7天)和月票(30天)
每种售票方式的价格将由一个数组给出,每种票据在票面时限内可以无限制的进行游玩。
例如,小王在第10日买了一张三日票,小王可以在第10日第11日和第12日进行无限制的游玩。
小王计划在接下来一年内多次游玩该游乐园。
小王计划的游玩日期将由一个数组给出。现在,请您根据给出的售票价格数组和小王计划游玩日期数组,返回完成游玩计划所需要的最低消费。
输入描述:
输入为2个数组 售票价格数组为costs,costs.length=4,默认顺序为一日票、三日票、周票和月票。 小王计划游玩日期数组为days,1<=days.length<=365,1<=days[i]<=365,默认顺序为升序。
输出描述:
完成游玩计划的最低消费 备注: 样例说明: 根据售票价格数组和游玩日期数组给出的信息,发现每次去玩的时候买一张一日票是最省钱的,所以小王会买8张一日票,每张5元,最低花费是40元。
示例1
输入
5 14 30 100
1,3 ,5 20 21 200 202 230
输出
40
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String costsString = scanner.nextLine();
String daysString = scanner.nextLine();
String[] costsString2 = costsString.split(" ");
String[] daysString2 = daysString.split(" ");
int[][] costs=new int[4][1];
int[] days=new int[daysString2.length];
costs[0][0]=Integer.parseInt(costsString2[0]);
costs[1][0]=Integer.parseInt(costsString2[1]);
costs[2][0]=Integer.parseInt(costsString2[2]);
costs[3][0]=Integer.parseInt(costsString2[3]);
for (int i = 0; i <daysString2.length ; i++) {
days[i]=Integer.parseInt(daysString2[i]);
}
System.out.println(calculateMinCost(costs,days) );
}
public static int calculateMinCost(int[][] costs, int[] days) {
int minCost = 0;
int lastDay = 0; // 记录上一次游玩日期,初始化为0,表示还没有游玩
for (int day : days) {
if (day > lastDay) { // 如果当前日期大于上一次游玩日期,则需要考虑是否需要购买新票
// 计算距离上一次游玩日期过了几天
int passedDays = day - lastDay;
// 尝试从长到短买票,以满足当前游玩需求
if (passedDays > 30) { // 超过30天,只能买月票
minCost += costs[3][0]; // 月票
lastDay = day; // 更新上一次游玩日期为当前日期
} else if (passedDays > 7) { // 超过7天,只能买周票
minCost += costs[2][0]; // 周票
lastDay = day; // 更新上一次游玩日期为当前日期
} else if (passedDays > 3) { // 超过3天,只能买三日票
minCost += costs[1][0]; // 三日票
lastDay = day; // 更新上一次游玩日期为当前日期
} else { // 否则买一日票
minCost += costs[0][0]; // 一日票
}
}
// 如果当前日期等于上一次游玩日期,则不需要额外买票
}
return minCost;
}
} |

被折叠的 条评论
为什么被折叠?



