【左神算法刷题班】第18节:汉诺塔问题、岛屿问题、最大路径和问题

news2024/7/6 18:29:20

第18节

题目1:汉诺塔问题(变体)

体系学习班18节有讲暴力递归的汉诺塔原题。

给定一个数组arr,长度为N,arr中的值只有1,2,3三种

arr[i] == 1,代表汉诺塔问题中,从上往下第i个圆盘目前在左

arr[i] == 2,代表汉诺塔问题中,从上往下第i个圆盘目前在中

arr[i] == 3,代表汉诺塔问题中,从上往下第i个圆盘目前在右

那么arr整体就代表汉诺塔游戏过程中的一个状况

如果这个状况不是汉诺塔最优解运动过程中的状况,返回-1

如果这个状况是汉诺塔最优解运动过程中的状况,返回它是第几个状况(而不是还剩几个状况)

思路

对于传统的汉诺塔问题,如果我要将 123456 从最左边的柱子上移动到最右边的柱子上,需要分成三大步:

  1. 【第一大步】将 12345 从左边的柱子移动到中间的位置
  2. 【第二大步】将 6 从左边的柱子移动到右边的位置
  3. 【第三大步】将 12345 从中间的位置移动到右边的位置

上述传统问题的解法是,定义递归函数 f(i, from, to, other),表示将 [0~i] 的圆盘从 from 柱子移动到 to 柱子上,另外那个柱子叫 other。

对于本题,需要明确一下题意,有几个已知条件:

  1. 汉诺塔问题,最优解是唯一的路径。
  2. 题目中给的过程状态,如果不在唯一路径上,就返回-1。
  3. 举个极端的例子,1层汉诺塔问题,把1从最左边的柱子上移动到最右边的柱子上,只要一步就可以了。而”1在中间这个柱子上“这个状态,就是一个不在最优解路径上的例子。

本题的解法是,定义递归函数int step(int[] arr, int i, int from, int to, int other),表示当前来到 arr 状态下,将 [0~i] 的圆盘从 from 柱子移动到 to 柱子上,另外那个柱子叫 other,返回至少需要几步。

public static int kth(int[] arr) {
    int N = arr.length;
    return step(arr, N - 1, 1, 3, 2);
}

// 我的疑问:arr为什么全程不更新?
// 自问自答:因为返回它当前走到了第几个状况,而不是还剩几个状况。
public static int step(int[] arr, int index, int from, int to, int other) {
    if (index == -1) {
        return 0;
    }
    if (arr[index] == other) { // 最大的数字只可能在from或者to的底部,不可能在other上
        return -1;
    }
    // arr[index]的值,剩下两种情况:
    // 情况1:arr[index] == from
    // 情况2:arr[index] == to
    if (arr[index] == from) { // 情况1:如果index号圆盘还在from上,说明上述连【第一大步】都没走完
      	// 因为我只想知道当前已经走过多少步了,所以只要统计在【第一大步】中走了多少步就可以了,后面的【第二大步】【第三大步】肯定根本没走
        // 怎么统计呢?我们知道【第一大步】的目标是将[0~i-1]从from挪到other上,而且当前已经走到arr状态了,所以就这样继续递归
        return step(arr, index - 1, from, other, to);
    } else { // 情况2:如果index号圆盘已经在to上了,说明已经完成[0~index-1]的汉诺塔问题了
        // 【第一大步】已经完成的从[0~index-1]范围上的index层汉诺塔问题需要的步骤数(n层汉诺塔,最优解2^n-1步)
        int p1 = (1 << index) - 1; 
        // 【第二大步】已经将index号圆盘从from挪到to了,因为我们从arr中看到index号圆盘已经在to上了
        int p2 = 1; 
        // 【第三大步】当前正在经历的,将[0~i-1]号圆盘从other挪到to上,在arr状态下,统计已经走过多少步了?
        int p3 = step(arr, index - 1, other, to, from); 
        // 如果发现它的子问题根本就不是最优解的某一步,直接返回-1
        if (p3 == -1) { 
            return -1;
        }
        return p1 + p2 + p3;
    }
}
image-20230814002726237

题目2:两个岛屿的距离,“感染”问题

Leetcode 原题:

https://leetcode.com/problems/shortest-bridge/

我在力扣上的自己写的答案:

class Solution {
    int m, n;
    public static final int offset = 100;

    public int shortestBridge(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        
        // 将其中一个岛A加offset,用来区分两个岛
        label:
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    incr(grid, i, j);
                    break label; // 中断所有循环,回到label处,但并不重新进入循环
                }
            }
        }
        
        // 左上角主动感染,右下角原地不动
        int term = offset;
        while (true) {
            boolean[][] seen = new boolean[m][n];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (grid[i][j] == term && !seen[i][j]) {
                        int result = process(grid, i, j, term, seen);
                        if (result != Integer.MAX_VALUE) return result - offset;
                    }
                }
            }
            term++;
        }
    }

    // 当前岛屿向外感染
    public int process(int[][] grid, int i, int j, int term, boolean[][] seen) {
        int result = Integer.MAX_VALUE;
        if (i < 0 || i == m || j < 0 || j == n || seen[i][j]) return result; // 越界,或重复路线
        seen[i][j] = true;

        if (grid[i][j] == 0) { // 当前位置未感染,则感染
            grid[i][j] = term + 1;
        } else if (grid[i][j] == term) { // 当前位置是感染源,则去感染周围
            result = Math.min(result, process(grid, i + 1, j, term, seen));
            result = Math.min(result, process(grid, i - 1, j, term, seen));
            result = Math.min(result, process(grid, i, j + 1, term, seen));
            result = Math.min(result, process(grid, i, j - 1, term, seen));
        } else if (grid[i][j] == 1) { // 两岛接壤,则快速返回
            return term;
        }
        return result;
    }

    // 给其中一个岛加offset
    public void incr(int[][] grid, int i, int j) {
        if (i < 0 || i == m || j < 0 || j == n) return;
        if (grid[i][j] == 1) {
            grid[i][j] = offset;
            incr(grid, i + 1, j);
            incr(grid, i - 1, j);
            incr(grid, i, j + 1);
            incr(grid, i, j - 1);
        }
    }
}

题目3:最大路径和

牛客网原题:

https://www.nowcoder.com/questionTerminal/8ecfe02124674e908b2aae65aad4efdf

给定一个矩阵matrix,先从左上角开始,每一步只能往右或者往下走,走到右下角。然后从右下角出发,每一步只能往上或者往左走,再回到左上角。任何一个位置的数字,只能获得一遍。返回最大路径和。

输入描述
第一行输入两个整数M和N,M,N<=200
接下来M行,每行N个整数,表示矩阵中元素

5 10
1 1 1 1 1 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 0 1 0 0 0 0 1
1 0 0 0 1 0 0 0 0 0
0 0 0 0 1 1 1 1 1 1
输出描述
输出一个整数,表示最大路径和

16
思路

第一次见到这题,是在体系学习班第14节。当时只讲了不能贪心,应该用dp,但没有细说。

黄色部分表示我想要拿到的位置:

在这里插入图片描述

错误的贪心路径

少拿一个灰色的格子。
在这里插入图片描述

正确的路径

最好情况下,能够拿到所有的格子。

在这里插入图片描述

虽然题目要求是一来一回,但我们可以想象成有两个人 a、b,都从左上角走到右下角,求整个过程中,最多能拿到多少值。

内存超限版本如下。其实可以省掉一个维度就不会超了,因为(i1, j1), (i2, j2) 两个坐标中,存在关系:i1+j1=i2+j2。可变参数数量能省则省!

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    static int[][] arr;

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int m = in.nextInt();
        int n = in.nextInt();

        arr = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                arr[i][j] = in.nextInt();
            }
        }

        int[][][][] dp = new int[m][n][m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                for (int k = 0; k < m; k++) {
                    for (int l = 0; l < n; l++) {
                        dp[i][j][k][l] = -1;
                    }
                }
            }
        }
        int res = process(0, 0, 0, 0, dp);
        System.out.println(res);
    }

    // 当前a在i1,j1位置,b在i2,j2位置
    // 两个人都只能向右走或者向下走,求能拿到的最多点数
    public static int process(int i1, int j1, int i2, int j2, int[][][][] dp) {
        if (i1 == arr.length || j1 == arr[0].length) return 0;
        if (i2 == arr.length || j2 == arr[0].length) return 0;

        if (dp[i1][j1][i2][j2] >= 0) return dp[i1][j1][i2][j2];

        // a,b如果走到了同一个位置,点数只能累加一次
        int res = arr[i1][j1];
        if (i1 != i2 && j1 != j2) res += arr[i2][j2];

        // a向右,b向右
        int p1 = process(i1 + 1, j1, i2 + 1, j2, dp);
        // a向下,b向下
        int p2 = process(i1, j1 + 1, i2, j2 + 1, dp);
        // a向右,b向下
        int p3 = process(i1, j1 + 1, i2 + 1, j2, dp);
        // a向下,b向右
        int p4 = process(i1 + 1, j1, i2, j2 + 1, dp);

        res += Math.max(Math.max(p1, p2), Math.max(p3, p4));
        dp[i1][j1][i2][j2] = res;
        return res;
    }
}
/*

5 10
1 1 1 1 1 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 0 1 0 0 0 0 1
1 0 0 0 1 0 0 0 0 0
0 0 0 0 1 1 1 1 1 1

2 2
1 1
1 1

 */

题目4

牛客网原题:

https://www.nowcoder.com/practice/7201cacf73e7495aa5f88b223bbbf6d1

给定两个有序数组arr1和arr2,再给定一个整数k,你可以从来自arr1和arr2的两个数各选一个数,返回相加和最大的前k个。

思路

不能用双指针从最右边开始往左滑动,因为这样会丢失本来可以重复使用的数字。

正确的方法是用大根堆。

当从大根堆拿走一个元素之后,将表格中在它左边和上边的元素,加入大根堆。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/879229.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Azure添加网络接口

添加网络接口的意义 在 Azure 上&#xff0c;为虚拟机添加网络接口的意义包括以下几个方面&#xff1a; 扩展网络带宽&#xff1a;通过添加多个网络接口&#xff0c;可以增加虚拟机的网络带宽&#xff0c;提高网络传输速度和数据吞吐量。实现网络隔离&#xff1a;每个网络接口…

网络安全体系架构介绍

网络安全体系是一项复杂的系统工程&#xff0c;需要把安全组织体系、安全技术体系和安全管理体系等手段进行有机融合&#xff0c;构建一体化的整体安全屏障。针对网络安全防护&#xff0c;美国曾提出多个网络安全体系模型和架构&#xff0c;其中比较经典的包括PDRR模型、P2DR模…

2021年06月 C/C++(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;数字放大 给定一个整数序列以及放大倍数x&#xff0c;将序列中每个整数放大x倍后输出。 时间限制&#xff1a;1000 内存限制&#xff1a;65536 输入 包含三行&#xff1a; 第一行为N&#xff0c;表示整数序列的长度(N ≤ 100); 第二行为N个整数(不超过整型范围…

一个脚本 专治杂乱

背景 之前不是自己手动搞了一个COS嘛&#xff0c;直接复制粘贴图片&#xff0c;上传到后端的服务器&#xff0c;返回一个可访问的地址。我在哔哩哔哩上也分享过这样的一期视频。 今天偶尔上服务器一看&#xff0c;我靠&#xff0c;我的文件真的乱&#xff01; 这还得了了&…

【C++精华铺】7.C++内存管理

目录 1. C语言动态内存管理 2. C内存管理方式 2.1 new/delete和new T[]/delete[] 2.1.1 操作内置类型 2.1.2 操作自定义类型 2.2 new/delete和new T[]/delete[]的原理 2.2.1 原理 2.2.2 operator new和operator delete 2.2.3 new T[]的特殊处理&#xff08;可以…

Altium DXP原理图转换成Orcad Capture

买了个开发板&#xff0c;原图是Altium DXP的&#xff0c;但是个人熟悉的Orcad&#xff0c;PCB无所谓了&#xff0c;反正都要重画&#xff0c;但是原理图是件大工程&#xff0c;重画还可能出问题&#xff0c;所以想着把DXP转成Capture格式&#xff0c;查阅了相关文档&#xff0…

【Linux命令详解 | ps命令】 ps命令用于显示当前系统中运行的进程列表,帮助监控系统状态。

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. 基本用法2. 显示所有进程3. 显示进程详细信息4. 根据CPU使用率排序5. 查找特定进程6. 显示特定用户的进程7. 显示进程内存占用8. 查看进程树9. 实时监控进程10. 查看特定进程的详细信息11. 查看特定用户的进程统计…

RTT(RT-Thread)串口设备(RTT保姆级教程)

目录 UART串口设备 串口概述 访问串口设备接口 数据发送方法 数据接收方法 串口设备使用流程 串口中断接受实例 串口配置及串口发送 串口中断接收 DMA接收 UART串口设备 串口概述 本章主要介绍串口设备在RT-Thread操作系统中应用层如何使用。关于串口设备的使用&am…

解析TCP/IP协议的分层模型

了解ISO模型&#xff1a;构建通信的蓝图 为了促进网络应用的普及&#xff0c;国际标准化组织&#xff08;ISO&#xff09;引入了开放式系统互联&#xff08;Open System Interconnect&#xff0c;OSI&#xff09;模型。这个模型包括了七个层次&#xff0c;从底层的物理连接到顶…

Spring AOP实践:如何通过aop记录日志?

目录 一、依赖 二、自定义注解 三、切面 一、依赖 以SpringBoot工程为例&#xff0c;导入aop的依赖。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency> 二…

GCviewer分析java垃圾回收的情况

一&#xff0c;下载并打包 1.在github上下载gcviewer,并解压。 2. 运行maven命令打包。mvn clean package -DskipTests 二&#xff0c;启动GCViewer 进入target目录&#xff0c;运行 java -jar gcviewer-1.37-SNAPSHOT.jar 运行后&#xff0c;会出来界面 三&#xff0c;加参…

轻松学会WiFi模块(ESP8266)—基于STM32,学到就是赚到!

目录 前言 一、ESP8266介绍 二、如何实现WiFi传输&#xff1f;代码详解附上 三、结果实现流程与展示 四、总结 题外话&#xff1a; 前言 哎哎哎&#xff0c;发觉好久没有更新博客了&#xff0c;最近一直事情比较多&#xff0c;也没什么时间注意博客&#xff0c;不过接下…

每天一道leetcode:1129. 颜色交替的最短路径(图论中等广度优先遍历)

今日份题目&#xff1a; 给定一个整数 n&#xff0c;即有向图中的节点数&#xff0c;其中节点标记为 0 到 n - 1。图中的每条边为红色或者蓝色&#xff0c;并且可能存在自环或平行边。 给定两个数组 redEdges 和 blueEdges&#xff0c;其中&#xff1a; redEdges[i] [ai, bi…

opencv实战项目 手势识别-实现尺寸缩放效果

手势识别系列文章目录 手势识别是一种人机交互技术&#xff0c;通过识别人的手势动作&#xff0c;从而实现对计算机、智能手机、智能电视等设备的操作和控制。 1. opencv实现手部追踪&#xff08;定位手部关键点&#xff09; 2.opencv实战项目 实现手势跟踪并返回位置信息&…

【Git】安装以及基本操作

目录 一、初识Git二、 在Linux底下安装Git一&#xff09;centOS二&#xff09;Ubuntu 三、 Git基本操作一&#xff09; 创建本地仓库二&#xff09;配置本地仓库三&#xff09;认识工作区、暂存区、版本库四&#xff09;添加文件五&#xff09;查看.git文件六&#xff09;修改文…

问道管理:旅游酒店板块逆市拉升,桂林旅游、华天酒店涨停

游览酒店板块14日盘中逆市拉升&#xff0c;到发稿&#xff0c;桂林游览、华天酒店涨停&#xff0c;张家界涨超8%&#xff0c;君亭酒店涨超5%&#xff0c;众信游览、云南游览涨逾4%。 音讯面上&#xff0c;8月10日&#xff0c;文旅部办公厅发布康复出境团队游览第三批名单&#…

Flink源码之StreamTask启动流程

每个ExecutionVertex分配Slot后&#xff0c;JobMaster就会向Slot所在的TaskExecutor提交RPC请求执行Task&#xff0c;接口为TaskExecutorGateway::submitTask CompletableFuture<Acknowledge> submitTask(TaskDeploymentDescriptor tdd, JobMasterId jobMasterId, RpcTi…

无涯教程-Perl - select函数

描述 此函数将输出的默认文件句柄设置为FILEHANDLE,如果未指定文件句柄,则设置由print和write等功能使用的文件句柄。如果未指定FILEHANDLE,则它将返回当前默认文件句柄的名称。 select(RBITS,WBITS,EBITS,TIMEOUT)使用指定的位调用系统功能select()。 select函数设置用于处理…

Postgresql 基础使用语法

1.数据类型 1.数字类型 类型 长度 说明 范围 与其他db比较 Smallint 2字节 小范围整数类型 32768到32767 integer 4字节 整数类型 2147483648到2147483647 bigint 8字节 大范围整数类型 -9233203685477808到9223203685477807 decimal 可变 用户指定 精度小…

用友Java后端笔试2023-8-5

计算被直线划分区域 在笛卡尔坐标系&#xff0c;存在区域[A,B],被不同线划分成多块小的区域&#xff0c;简单起见&#xff0c;假设这些不同线都直线并且不存在三条直线相交于一点的情况。 img 那么&#xff0c;如何快速计算某个时刻&#xff0c;在 X 坐标轴上[ A&#xff0c;…