leetcode之最短路径+记忆化dfs+bfs+动态规划刷题总结
leetcode之最短路径+记忆化dfs+bfs+动态规划刷题总结
最短路:给定两个顶点,在以这两个顶点为起点和终点的路径中,边的权值和最小的路径。
最短路径中有几种经典的算法,我们主要练习的是Dijkstra算法和Floyd算法,分别用于解决单元最短路径问题和多源最短路径问题。
Dijkstra算法适用于单源最短路径,具体地,找到最短路已经确定的节点,从它出发更新相邻节点的最短距离。
Floyd算法适用于多源最短路径,具体地,通过一系列n阶矩阵来计算一个n节点加权图的最短距离。每轮让第k个节点做中转节点,更新第i个节点到第j个节点的最短路径。
动态规划常常适用于有重叠子问题和最优子结构性质的问题,并且记录所有子问题的结果,因此动态规划方法所耗时间往往远少于朴素解法。
动态规划有自底向上和自顶向下两种解决问题的方式。自顶向下即记忆化递归,自底向上就是递推。
记忆化即记忆化搜索,也叫记忆化递归,其实就是拆分子问题的时候,发现有很多重复子问题,然后再求解它们以后记录下来。以后再遇到要求解同样规模的子问题的时候,直接读取答案。
使用动态规划解决的问题有个明显的特点,一旦一个子问题的求解得到结果,以后的计算过程就不会修改它,这样的特点叫做无后效性,求解问题的过程形成了一张有向无环图。动态规划只解决每个子问题一次,具有天然剪枝的功能,从而减少计算量。
深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次.
广度优先搜索算法(Breadth-First Search,缩写为 BFS),又称为宽度优先搜索,是一种图形搜索算法。简单的说,BFS 是从根结点开始,沿着树的宽度遍历树的结点。如果所有结点均被访问,则算法中止。
1-电动车游城市
题目链接:题目链接戳这里!!!
思路:Dijkstra算法
对paths数组建立邻接表,分别存储两个相邻节点以及节点之间的权值,ans数组记录到达当前节点的最少用时,优先队列中存储所用时间,当前位置,剩余电量,并且按照所用时间升序排序,保证每次选择的都是所用时间最少的,如果电量未满可以考虑充电,如如果剩余电量够,可以往相邻的节点走。
class Solution {
public int electricCarPlan(int[][] paths, int cnt, int start, int end, int[] charge) {
int n = charge.length ;
List<int[]> [] g = new List[n] ;
for(int i=0; i<n; i++){
g[i] = new ArrayList<int[]>() ;
}
for(int [] path : paths){ //建立邻接表,后面好操作
int u = path[0], v = path[1], w = path[2] ;
g[u].add(new int [] {v, w}) ;
g[v].add(new int [] {u, w}) ;
}
int [][] ans = new int [n][cnt+1] ; //当前位置n,剩余电量cnt,时候的最少用时
for(int i=0; i<n; i++){
Arrays.fill(ans[i], Integer.MAX_VALUE/2) ;
}
ans[start][0] = 0 ;
PriorityQueue<int []> queue = new PriorityQueue<>((a,b)->(a[0]-b[0])) ;
queue.add(new int [] {0, start, 0}) ; //数组中三个元素分别为已用时间,当前位置,剩余电量
while(!queue.isEmpty()){
int [] p = queue.poll() ;
int time = p[0], cur = p[1], power = p[2] ;
if(time > ans[cur][power]){
continue ;
}
if(cur == end){
return time ;
}
if(power<cnt){//电量未满,可以考虑充电
int t = time + charge[cur] ;
if(t<ans[cur][power+1]){
ans[cur][power+1] = t ;
queue.add(new int [] {t, cur, power+1}) ;
}
}
for(int [] path : g[cur]){
int next = path[0] ;
int cost = path[1] ;
int t = time + cost ;
int pow = power - cost ;
if(pow>=0 && t<ans[next][pow]){
ans[next][pow] = t ;
queue.add(new int [] {t, next, pow}) ;
}
}
}
return -1 ;
}
}
2-K站中转内最便宜的航班
题目链接:题目链接戳这里!!!
思路1:记忆化搜索(dfs)
从src出发,搜索所有能到dst的,并且中转个数少于k的路径,找出价格最便宜的路径价格。
用memo[i][k]做记忆化,防止重复计算,该数组表示从i点到达终点经历不超过k次中转的最低价格。
class Solution {
int INF = 1000000007 ;
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
//记忆化搜索
//memo[i][k]数组记录从当前顶点i出发经过最多k个中转点到最后的顶点的最便宜的价格
int [][] memo = new int [n][k+2] ;
int ans = dfs(flights, src, dst, k+1, memo) ;
return ans>=INF ? -1 : ans ;
}
public int dfs(int [][] flights, int i, int end, int k, int [][] memo){
if(k<0){
return INF ;
}
if(i==end){
return 0 ;
}
if(memo[i][k] != 0){
return memo[i][k] ;
}
int min = INF ;
for(int [] flight : flights){
if(flight[0]==i){
min = Math.min(min, dfs(flights, flight[1], end, k-1, memo) + flight[2]) ;
}
}
memo[i][k] = min ;
return min ;
}
}
思路2:bfs+剪支
对于当前节点,遍历相邻节点,每一轮bfs,k减少1,如果找到更小的价格,则更新ans数组。
class Solution {
int INF = 1000000007 ;
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
//bfs+剪支
List<int []> [] g = new List[n] ;
for(int i=0; i<n; i++){
g[i] = new ArrayList<>() ;
}
for(int [] flight : flights){
int u = flight[0], v = flight[1], w=flight[2] ;
g[u].add(new int [] {v, w}) ;
}
int [] ans = new int [n] ;
Arrays.fill(ans, Integer.MAX_VALUE) ;
Queue<int []> queue = new LinkedList<>() ;
queue.add(new int [] {src, 0}) ;
while(!queue.isEmpty() && k+1>0){
int size = queue.size() ;
for(int i=0; i<size; i++){
int [] p = queue.poll() ;
for(int [] f : g[p[0]]){
int price = p[1] + f[1];
if(price < ans[f[0]]){
ans[f[0]] = price ;
if(f[0] != dst){
queue.add(new int [] {f[0], price}) ;
}
}
}
}
k -- ;
}
return ans[dst] >= INF ? -1 : ans[dst] ;
}
}
思路3:动态规划
其实就是记忆化递归的转换,自顶向下变成自底向上。
class Solution {
int INF = 1000000007 ;
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
//动态规划
//dp[i][k]表示从i点出发到dst走最多k+1步的最少价格
int [][] dp = new int [n][k+2] ;
for(int i=0; i<n; i++)
Arrays.fill(dp[i],INF) ;
return f(n, flights, src, dst, k+1, dp) ;
}
public int f(int n, int [][] flights, int src, int dst, int K, int [][] dp){
dp[dst][0] = 0 ;
for(int k=1; k<=K; k++){
for(int [] flight : flights){
dp[flight[0]][k] = Math.min(dp[flight[0]][k], dp[flight[1]][k-1] + flight[2]) ;
}
}
int ans = IntStream.of(dp[src]).min().getAsInt() ;
return ans >= INF ? -1 : ans ;
}
}
3-阈值距离内邻居最少的城市
题目链接:题目据链接戳这里!!!
思路:首先要求出来每个节点到其余节点的最短路径,所以可以使用Floyd算法,使用matrix矩阵记录从i节点到j节点的最短路长度,然后遍历所有i节点,如果到达j节点的距离小于等于distanceThreshold,则使用nums[i]记录满足要求的路径数量,选择最小值。
class Solution {
public int findTheCity(int n, int[][] edges, int distanceThreshold) {
//多源最短路径,使用Floyd算法
int [][] matrix = new int [n][n] ;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
matrix[i][j] = 1000000000 ;
}
}
for(int i=0; i<n; i++){
matrix[i][i] = 0 ;
}
for(int [] edge : edges){
int u = edge[0], v = edge[1], w = edge[2] ;
matrix[u][v] = matrix[v][u] = w ;
}
for(int k=0; k<n; k++){
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
matrix[i][j] = Math.min(matrix[i][j], matrix[i][k]+matrix[k][j]) ;
}
}
}
int [] num = new int [n] ;
int pos = -1, min = 1000000000 ;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
if(matrix[i][j]<=distanceThreshold){
num[i] ++ ;
}
}
if(num[i] <= min){
min = num[i] ;
pos = i ;
}
}
return pos ;
}
}
**
不辜负每一份热情,不讨好每一份冷漠,做好自己,健健康康为祖国工作50年!!!
**