【Py/Java/C++三种语言OD2023C卷真题】20天拿下华为OD笔试之【启发式搜索】2023C-寻找最优的路测线路【欧弟算法】全网注释最详细分类最全的华为OD真题题解

题目描述与示例

题目描述

评估一个网络的信号质量,其中一个做法是将网络划分为栅格,然后对每个栅格的信号质量计算,路测的时候,希望选择一条信号最好的路线(彼此相连的栅格集合)进行演示,现给出RC列的整数数组COV,每个单元格的数值S即为该栅格的信号质量(已归一化,无单位,值越大信号越好)

要求从[0,0][R-1,C-1]设计一条最优路测路线。返回该路线得分。规则:

  1. 路测路线可以上下左右四个方向,不能对角
  2. 路线的评分是以路线上信号最差的栅格为准的,例如路径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. 不同路径 IILC64. 最小路径和 等等)。

但本题和该类路径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),它们的值分别为512

暂时无法在飞书文档外展示此内容

类似地,我们会优先选择(0, 2)作为路线的下一个点,因为(0, 2)的值为5,是这三个近邻点的值最大的。

暂时无法在飞书文档外展示此内容

依照上述规律,每次我们都会选择所有近邻点中值最大的那个作为下一个点,直到到达终点。

接下来我们会依次选择(1,2)(2,2),到达右下角终点,完成5->4->5->6->6的路线。

所以,在搜索过程中,每次出队的节点不再是按照入队先后顺序弹出的,而是按照最大值作为优先级来弹出的

显然以某种优先级作为出队依据,应该使用优先队列来代替队列

这种包含了贪心思想的搜索方式并非传统的BFS,称之为启发式搜索(Heuristic Search)

PS:不熟悉优先队列的话,排序后取出最大值也是可以的,在当前数据规模下是可以通过所有用例的

代码

Python

# 题目:【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)

Java

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);
    }
}

C++

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
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算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

  • 51
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值