题意整理
- 给定N个城市,以及每个城市的开销。
- 一个前置城市数组,去y城市之前必须先去x城市。
- 牛妹手上有V元,她每次都会去开销最小的城市,如果开销相同,就选编号较小的城市,求牛妹最多能去几个城市。
方法一(优先队列+拓扑排序)
1.解题思路
首先明确一个事实,如果要去某个城市,那么他的前置城市一定都去过了,否则去不了这个城市。于是只要前置城市数目为0,就可以尝试访问这个城市,我们假设前置城市数为入度,记录在对应的Indeg入度数组。
由于每次都要选择花费最小的城市,如果花费相同,则选择编号最小的城市,所以可以将待去城市加入到优先队列(优先队列存放城市开销和城市编号组成的长度为2的数组),每次取优先队列的最小值,即可满足上述选择策略。
- 明确思路后,先将入度为0的城市开销、编号添加到队列。
- 如果当前剩余花费V还够当前城市的开销,则减去对应的开销cost,同时可去城市数cnt加一。然后检查所去的这个城市的所有后置城市,将对应后置城市入度减一,如果入度变为0,则将对应城市、编号加入到队列。
动图展示:
2.代码实现
import java.util.*; /* * public class Point { * int x; * int y; * public Point(int x, int y) { * this.x = x; * this.y = y; * } * } */ public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param N int整型 N * @param V int整型 V * @param A int整型一维数组 A * @param list Point类一维数组 List * @return int整型 */ public int Travel (int N, int V, int[] A, Point[] list) { //初始化优先队列 PriorityQueue<int[]> queue=new PriorityQueue<>( (o1,o2)->o1[0]==o2[0]?o1[1]-o2[1]:o1[0]-o2[0]); //初始化入度数组、后置城市数组 int[] Indeg=new int[N]; int[][] limit=new int[N][N]; //给后置城市数组和入度数组赋值 for(Point p:list){ limit[p.x-1][p.y-1]=1; Indeg[p.y-1]++; } //如果入度为0,则表示可以访问,加入到队列 for(int i=0;i<N;i++){ if(Indeg[i]==0){ queue.offer(new int[]{A[i],i}); } } //记录最多可去城市数目 int cnt=0; while(!queue.isEmpty()){ int[] city=queue.poll(); int cost=city[0]; int id=city[1]; //如果剩余花费不够,则直接跳出循环 if(V<cost) break; V-=cost; cnt++; //如果存在后置城市,将对应后置城市入度减一 for(int j=0;j<N;j++){ if(limit[id][j]==1){ Indeg[j]--; //如果入度为0,则加入到队列 if(Indeg[j]==0){ queue.offer(new int[]{A[j],j}); } } } } return cnt; } }
3.复杂度分析
- 时间复杂度:由于每个城市最多访问一次,而从队列中添加或移除城市的时间复杂度都是,所以最终的时间复杂度是。
- 空间复杂度:需要额外大小为的limit数组,所以空间复杂度是。
方法二(优先队列+标记数组)
1.解题思路
牛妹每次都要选择花费最小的城市,如果花费相同,则选择编号最小的城市,所以可以将待去城市加入到优先队列(优先队列存放城市开销和城市编号组成的长度为2的数组),每次取优先队列的最小值,即可满足上述选择策略。
- 首先初始化优先队列、入度数组、标记数组(用来判断某个城市是否来过)、后置城市数组,先将入度为0的城市开销、编号添加到队列,并将对应标记置为true。
- 每次取优先队列的最小值,如果当前花费还够,则减去对应开销,并将可去城市数cnt加一。然后将对应的后置数组里的所有城市入度减一。遍历所有的城市,如果没有访问过,并且入度为0,则加入到队列。
2.代码实现
import java.util.*; /* * public class Point { * int x; * int y; * public Point(int x, int y) { * this.x = x; * this.y = y; * } * } */ public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param N int整型 N * @param V int整型 V * @param A int整型一维数组 A * @param list Point类一维数组 List * @return int整型 */ public int Travel (int N, int V, int[] A, Point[] list) { //初始化优先队列 PriorityQueue<int[]> queue=new PriorityQueue<>( (o1,o2)->o1[0]==o2[0]?o1[1]-o2[1]:o1[0]-o2[0]); //初始化入度数组、标记数组、后置城市数组 int[] Indeg=new int[N]; boolean[] v=new boolean[N]; int[][] limit=new int[N][N]; //给后置城市数组和入度数组赋值 for(Point p:list){ limit[p.x-1][p.y-1]=1; Indeg[p.y-1]++; } //如果入度为0,则表示可以访问,加入到队列,并且访问标记置为true for(int i=0;i<N;i++){ if(Indeg[i]==0){ v[i]=true; queue.offer(new int[]{A[i],i}); } } //记录最多可去城市数目 int cnt=0; while(!queue.isEmpty()){ int[] city=queue.poll(); int cost=city[0]; int id=city[1]; //如果剩余花费不够,则直接跳出循环 if(V<cost) break; V-=cost; cnt++; //如果存在后置城市,将对应后置城市入度减一 for(int j=0;j<N;j++){ if(limit[id][j]==1){ Indeg[j]--; } } //如果入度为0,并且没有访问过,则加入到队列,并且访问标记置为true for(int i=0;i<N;i++){ if(!v[i]&&Indeg[i]==0){ v[i]=true; queue.offer(new int[]{A[i],i}); } } } return cnt; } }
3.复杂度分析
- 时间复杂度:当队列里的某个城市被选之后,遍历所有的城市,通过标记数组+入度数组判断哪些城市可以入队,会有很多重复的判断,这个重复判断的操作次数为级别,如果没有重复判断,正常入队的城市最多有n个,时间复杂度是,所以最终的时间复杂度是。
- 空间复杂度:需要额外大小为的limit数组,与方法一相比,多了标记数组的空间开销,空间复杂度是。