评估一个网络的信号质量,其中一个做法是将网络划分为栅格,然后对每个栅格的信号质量计算,路测的时候,希望选择一条信号最好的路线(彼此相连的栅格集合)进行演示,现给出R
行C
列的整数数组COV
,每个单元格的数值S
即为该栅格的信号质量(已归一化,无单位,值越大信号越好)
要求从[0,0]
到[R-1,C-1]
设计一条最优路测路线。返回该路线得分。规则:
8->4->5->9
的值为 4
,该线路评分为4
。线路最优表示该条线路的评分最高。第1
行表示栅格的行数R
第2
行表示栅格的列数C
第3
行开始,每一行表示栅格地图一行的信号值,如5 4 5
最优路线的得分
1 <= R,C <= 20
0 <= S <= 65535
3
3
5 4 5
1 2 6
7 4 6
4
路线为5->4->5->6->6
题目要求从左上角找到一条路线到右下角,该条路线中的最小值要尽可能地大。
本题很容易和路径类的dp问题混淆(例如LC62. 不同路径 、LC63. 不同路径 II 、LC64. 最小路径和 等等)。
但本题和该类路径dp问题有一个非常明显的不同,前者是移动方向是上下左右,而后者的移动方向只有向下和向右。本题如果使用dp的话,动态转移方程并不明确,因为转移的方向是未知的,不满足dp的无后效性。
因此本题不能用dp来解决。
寻路类型的问题,除了dp,很容易想到使用DFS或者BFS来解决。
但本题显然不应该使用DFS,因为DFS在寻路问题中本质上就是回溯穷举,在20*20 = 400
的数据规模下必然超时。
因此思考如何用BFS解决该问题。
传统的BFS过程,用队列维护,先入队的节点必然先出队被考虑。但这种传统做法并不能满足该题目的要求。
首先考虑人脑是如何思考这个问题的。以题目所给的示例为例
从值为5
的起点(0, 0)
出发,有两个近邻点可以选择,分别是(0, 1)
和(1, 0)
。
但我们会优先选择(0, 1)
作为路线的下一个点,因为(0, 1)
的值为4
大于(1, 0)
的值1
,能使得当前路线中的最小值更大。
选择了(0, 1)
之后,下一个可能的近邻点为三个,包括(0, 2)
,(1, 0)
和(1, 1)
,它们的值分别为5
,1
,2
。
类似地,我们会优先选择(0, 2)
作为路线的下一个点,因为(0, 2)
的值为5
,是这三个近邻点的值最大的。
依照上述规律,每次我们都会选择所有近邻点中值最大的那个作为下一个点,直到到达终点。
接下来我们会依次选择(1,2)
和(2,2)
,到达右下角终点,完成5->4->5->6->6
的路线。
所以,在搜索过程中,每次出队的节点不再是按照入队先后顺序弹出的,而是按照最大值作为优先级来弹出的。
显然以某种优先级作为出队依据,应该使用优先队列来代替队列。
这种包含了贪心思想的搜索方式并非传统的BFS,称之为启发式搜索(Heuristic Search)。
PS:不熟悉优先队列的话,排序后取出最大值也是可以的,在当前数据规模下是可以通过所有用例的
# 题目:【BFS】2023C-寻找最优的路测线路
# 分值:200
# 作者:闭着眼睛学数理化
# 算法:启发式搜索
# 代码看不懂的地方,请直接在群上提问
# 表示四个方向的数组
DIRECTIONS = [(0,1), (1,0), (-1,0), (0,-1)]
from heapq import heappop, heappush
# 输入行数R,列数C
R = int(input())
C = int(input())
grid = list()
# 输入R行
for _ in range(R):
grid.append(list(map(int, input().split())))
# 路线必然经过起点和终点,初始化答案为两者之间的较小值
ans = min(grid[0][0], grid[R-1][C-1])
# 初始化一个最大堆堆,用于维护启发式搜索过程
# 由于Python的heapq默认是最小堆,因此储存节点值的时候
# 储存其相反数,构建一个伪大根堆
# 同时还需要储存其点的坐标
# 即堆中储存的是一个三元组,分别为值的相反数-grid[x][y],横坐标x,纵坐标y
heap = [(-grid[0][0], 0, 0)]
# 检查数组
check_list = [[0] * C for _ in range(R)]
check_list[0][0] = 1
# 初始化一个是否继续进行启发式搜索的标志
isContinueFind = True
# 进行启发式搜索
while isContinueFind:
# 弹出堆顶元素
cur_val, cur_x, cur_y = heappop(heap)
# 更新答案,注意由于构建了伪大根堆,
# grid[cur_x][cur_y]的值为-cur_val
ans = min(ans, -cur_val)
# 考虑上下左右四个方向
for dx, dy in DIRECTIONS:
# 近邻点(nxt_x, nxt_y)
nxt_x, nxt_y = cur_x+dx, cur_y+dy
# 近邻点为越界且尚未检查过
if 0 <= nxt_x < R and 0 <= nxt_y < C and check_list[nxt_x][nxt_y] == 0:
# 如果近邻点是终点,说明已经找到了最佳路线
if nxt_x == R-1 and nxt_y == C-1:
# 将isEnd标记为True,退出当前循环
isContinueFind = False
break
# 否则,将该近邻点加入优先队列中,同时标记为已检查过
heappush(heap, (-grid[nxt_x][nxt_y], nxt_x, nxt_y))
check_list[nxt_x][nxt_y] = 1
print(ans)
import java.util.PriorityQueue;
import java.util.Scanner;
class Main {
static class Node implements Comparable<Node> {
int val, x, y;
Node(int val, int x, int y) {
this.val = val;
this.x = x;
this.y = y;
}
@Override
public int compareTo(Node other) {
return Integer.compare(other.val, this.val);
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int R = scanner.nextInt();
int C = scanner.nextInt();
int[][] grid = new int[R][C];
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
grid[i][j] = scanner.nextInt();
}
}
int ans = Math.min(grid[0][0], grid[R - 1][C - 1]);
PriorityQueue<Node> heap = new PriorityQueue<>();
heap.offer(new Node(grid[0][0], 0, 0));
boolean[][] checkList = new boolean[R][C];
checkList[0][0] = true;
boolean isContinueFind = true;
while (isContinueFind && !heap.isEmpty()) {
Node cur = heap.poll();
int curVal = cur.val;
int curX = cur.x;
int curY = cur.y;
ans = Math.min(ans, curVal);
int[] dx = {0, 1, -1, 0};
int[] dy = {1, 0, 0, -1};
for (int k = 0; k < 4; k++) {
int nxtX = curX + dx[k];
int nxtY = curY + dy[k];
if (nxtX >= 0 && nxtX < R && nxtY >= 0 && nxtY < C && !checkList[nxtX][nxtY]) {
if (nxtX == R - 1 && nxtY == C - 1) {
isContinueFind = false;
break;
}
heap.offer(new Node(grid[nxtX][nxtY], nxtX, nxtY));
checkList[nxtX][nxtY] = true;
}
}
}
System.out.println(ans);
}
}
#include
#include
#include
#include
using namespace std;
struct Node {
int val, x, y;
Node(int val, int x, int y) : val(val), x(x), y(y) {}
bool operator<(const Node& other) const {
return val < other.val;
}
};
int main() {
int R, C;
cin >> R >> C;
vector<vector<int>> grid(R, vector<int>(C));
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
cin >> grid[i][j];
}
}
int ans = min(grid[0][0], grid[R - 1][C - 1]);
priority_queue<Node> heap;
heap.push(Node(grid[0][0], 0, 0));
vector<vector<bool>> checkList(R, vector<bool>(C, false));
checkList[0][0] = true;
bool isContinueFind = true;
while (isContinueFind) {
Node cur = heap.top();
heap.pop();
int curVal = cur.val;
int curX = cur.x;
int curY = cur.y;
ans = min(ans, curVal);
int dx[] = {0, 1, -1, 0};
int dy[] = {1, 0, 0, -1};
for (int k = 0; k < 4; k++) {
int nxtX = curX + dx[k];
int nxtY = curY + dy[k];
if (nxtX >= 0 && nxtX < R && nxtY >= 0 && nxtY < C && !checkList[nxtX][nxtY]) {
if (nxtX == R - 1 && nxtY == C - 1) {
isContinueFind = false;
break;
}
heap.push(Node(grid[nxtX][nxtY], nxtX, nxtY));
checkList[nxtX][nxtY] = true;
}
}
}
cout << ans << endl;
return 0;
}
时间复杂度:O(R*Clog(R*C))
。单次入堆、出堆的时间复杂度为O(log(R*C))
,最多一共需要遍历R*C
个位置。
空间复杂度:O(R*C)
。堆和检查数组所占空间。
华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
可查看链接 大厂真题汇总 & OD真题汇总(持续更新)
绿色聊天软件戳 od1336
了解更多