递归到动态规划- X-空间压缩技巧

news2024/9/21 22:21:03

空间压缩技巧的示例代码代码, LeetCode第64题

验证链接:力扣

package dataStructure.recurrence.practice;

/**
 * https://leetcode.cn/problems/minimum-path-sum/
 * Leecode第64题
 * 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
 *
 * 说明:每次只能向下或者向右移动一步。
 * 输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
 * 输出:7
 * 解释:因为路径 1→3→1→1→1 的总和最小。
 * 示例 2:
 *
 * 输入:grid = [[1,2,3],[4,5,6]]
 * 输出:12
 *
 *
 * 提示:
 *
 * m == grid.length
 * n == grid[i].length
 * 1 <= m, n <= 200
 * 0 <= grid[i][j] <= 100
 */
public class MinPathSum {
    public static int min1(int[][] grid) {
        int rows = grid.length;
        int cols = grid[0].length;
        return process1(grid, rows - 1, cols - 1, 0, 0);
    }

    public static int minDp(int[][] grid) {
        int N = grid.length;
        int M = grid[0].length;
        //row和col的变化范围分别是0~N-1、0~M-1
        int[][] dp = new int[N][M];
        //从右下角开始走只需要走过当前的格子
        dp[N - 1][M - 1] = grid[N - 1][M - 1];
        //最后一行只能往右走
        for(int j = M - 2; j >= 0; j --) {
            dp[N - 1][j] = grid[N - 1][j] + dp[N - 1][j + 1];
        }
        //最后一列只能往下走
        for(int i = N - 2; i >= 0; i --) {
            dp[i][M - 1] = grid[i][M - 1] + dp[i + 1][M - 1];
        }
        //非最后一行和最后一列的普通情况
        for(int curRow = N - 2; curRow >=0; curRow --) {
            for(int curCol = M - 2; curCol >= 0; curCol --) {
                int p1 = grid[curRow][curCol] + dp[curRow][curCol + 1];
                int p2 = grid[curRow][curCol] + dp[curRow + 1][curCol];
                dp[curRow][curCol] =  Math.min(p1, p2);
            }
        }
        return dp[0][0];
    }

    public static int minDp2(int[][] grid) {
        int N = grid.length;
        int M = grid[0].length;
        //原来的二维数组:row和col的变化范围分别是0~N-1、0~M-1
        //每个位置依赖于自己下行同列(最后一列)或者同行下列(最后一行)或者二者的最小值(除了前两种情况)
        int[] dp = new int[M];
        //从右下角开始走只需要走过当前的格子
        dp[M - 1] = grid[N - 1][M - 1];
        //初始化最后一行的动态规划数组
        for(int j = M - 2; j >=0; j--) {
            //最后一行每个位置到右下角最小累加和都是当前位置的数字加上当前位置右边的动态规划格子的值
            dp[j] = grid[N-1][j] + dp[j+1];
        }

        //对于从倒数第二行到第一行进行dp数组的值替换(没一行执行完成的时候,dp[j]就是当前行的j列到右下角的累加和最小值)
        for(int i = N - 2; i >= 0; i--) {
            //没一行的最后一列没有选择,他只能是当前值+下一行同位置的值
            dp[M-1] = dp[M - 1] + grid[i][M-1];
            //其他位置是他右边和他下边的值取最小加上当前位置的值
            for(int j = M - 2; j >=0; j --) {
                //dp[j+1]是当前位置右边的值,因为从右到左更新,所以dp[j+1]已经根据dp和j+1更新过了
                int p1 = grid[i][j] + dp[j + 1];
                //dp[j]这个时候还是下一行同列的值
                int p2 = grid[i][j] + dp[j];
                //根据二者最小值赋值给当前行的dp[j]
                dp[j] = Math.min(p1, p2);
            }
        }

        //dp数组是常规动态规划解法的第一行的数据,取dp[0]就是原来的dp[0][0]
        return dp[0];
    }

    /**
     * 暴力递归版本,过于简单,没啥可解释的,从(curRow, curRow)到(targetRow, targetCol)的最小累加和(包括当前节点)
     * @param grid 原始数组
     * @param targetRow 目前的行
     * @param targetCol 目标的列
     * @param curRow 当前所在行
     * @param curCol 当前所在列
     * @return
     */
    public static int process1(int[][] grid, int targetRow, int targetCol, int curRow, int curCol) {
        //当前已经是目标点,直接返回当前节点值即可
        if(curRow == targetRow && curCol == targetCol) {
            return grid[curRow][curCol];
        }
        //最后一行,所有点只能往右走
        if(curRow == targetRow) {
            return grid[curRow][curCol] + process1(grid, targetRow, targetCol, curRow, curCol + 1);
        }
        //最后一列,所有点只能往下走
        if(curCol == targetCol) {
            return grid[curRow][curCol] + process1(grid, targetRow, targetCol, curRow + 1, curCol);
        }
        //非最后一行和最后一列的点,取下一步向右和下一步向下走到目标节点的最小值
        int p1 = grid[curRow][curCol] + process1(grid, targetRow, targetCol, curRow, curCol + 1);
        int p2 = grid[curRow][curCol] + process1(grid, targetRow, targetCol, curRow + 1, curCol);
        return Math.min(p1, p2);
    }

    public static void main(String[] args) {
        int[][] grid = {{1,2,3},{4,5,6}};
        int minPath = min1(grid);
        System.out.println(minPath);
        int minPathDp = minDp(grid);
        System.out.println(minPathDp);
        int minPathDp2 = minDp2(grid);
        System.out.println(minPathDp2);
    }
}

验证结果如下:

 

代码中的DP2方法是我们使用了空间压缩技巧的

public static int minDp2(int[][] grid) {
        int N = grid.length;
        int M = grid[0].length;
        //原来的二维数组:row和col的变化范围分别是0~N-1、0~M-1
        //每个位置依赖于自己下行同列(最后一列)或者同行下列(最后一行)或者二者的最小值(除了前两种情况)
        int[] dp = new int[M];
        //从右下角开始走只需要走过当前的格子
        dp[M - 1] = grid[N - 1][M - 1];
        //初始化最后一行的动态规划数组
        for(int j = M - 2; j >=0; j--) {
            //最后一行每个位置到右下角最小累加和都是当前位置的数字加上当前位置右边的动态规划格子的值
            dp[j] = grid[N-1][j] + dp[j+1];
        }

        //对于从倒数第二行到第一行进行dp数组的值替换(没一行执行完成的时候,dp[j]就是当前行的j列到右下角的累加和最小值)
        for(int i = N - 2; i >= 0; i--) {
            //没一行的最后一列没有选择,他只能是当前值+下一行同位置的值
            dp[M-1] = dp[M - 1] + grid[i][M-1];
            //其他位置是他右边和他下边的值取最小加上当前位置的值
            for(int j = M - 2; j >=0; j --) {
                //dp[j+1]是当前位置右边的值,因为从右到左更新,所以dp[j+1]已经根据dp和j+1更新过了
                int p1 = grid[i][j] + dp[j + 1];
                //dp[j]这个时候还是下一行同列的值
                int p2 = grid[i][j] + dp[j];
                //根据二者最小值赋值给当前行的dp[j]
                dp[j] = Math.min(p1, p2);
            }
        }

        //dp数组是常规动态规划解法的第一行的数据,取dp[0]就是原来的dp[0][0]
        return dp[0];
    }

标准的动态规划应该是和原数组的大小相同的,这个方法值使用了一个和列数相同的一维数组来做动态规划。

步骤如下:

(1)先把原来的dp[N-1][M-1](右下角的点到它自己的最小累加和,也就是grid右下角的节点值)填到dp[M-1]位置

(2) 根据右下角的值从右向左填满dp[M-2]~dp[0]

(3)依次从下往上填满每一行的值(最后一列等于它本身加上下一行同列,其他位置取同行列+1和同列行+1的最小值+它本身)

(4)最后dp数组代表的就是第一行的每个位置到右下角的最小累加和。0位置就是我们的解。

比如10000*4的矩阵,我们的dp数组只需要4个位置,对比原来和矩阵一样的40000个位置节省了很多空间。

反过来,如果矩阵是4*10000,我们就可以把行列反过来用,用长度为4的dp数组,而不是10000的长度。

这个技巧是可选技巧,加分项,不会也不影响面试。

可以使用空间压缩技巧的大概场景:某一行某一列的值只依赖本行或者上一行或者下一行的某个值

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

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

相关文章

C++好难(6):模板初阶

【本节目标】 1. 泛型编程2. 函数模板3. 类模板 目录 【本节目标】 1.泛型编程 2.函数模板 概念&#xff1a; 格式&#xff1a; 原理&#xff1a; 实例化&#xff1a; 1.隐式实例化&#xff1a; 2.显式实例化 原则一&#xff1a; 原则二&#xff1a; 原则三&#…

数组存储与指针学习笔记(三)指针与数组

嵌入式C语言学习进阶系列文章 GUN C编译器拓展语法学习笔记(一&#xff09;GNU C特殊语法部分详解 GUN C编译器拓展语法学习笔记(二&#xff09;属性声明 GUN C编译器拓展语法学习笔记(三&#xff09;内联函数、内建函数与可变参数宏 数组存储与指针学习笔记(一&#xff09;数…

OpenCv更改颜色空间以及图像阈值

本文主要讲解以下几个方面: 如何将图片从一个颜色空间转换到另一个&#xff0c;例如 BGR 到 Gray&#xff0c;BGR 到 HSV 等。简单阈值法另外&#xff0c;我们会创建一个从图片中提取彩色对象的应用。 1.改变颜色空间 cv.cvtColor(img, flag) 参数flag表示颜色空间转换的方…

Hive语言2(大数据的核心:窗口函数)

1、Common Table Expressions&#xff08;CTE&#xff09;> 重点 公用表达式(CTE)是一个临时结果集&#xff0c;该结果集是从WITH子句中指定的简单查询派生而来的&#xff0c;该查询紧接在SELECT或INSERT关键字之前。 2.inner join&#xff08;内连接&#xff09;、left joi…

网页源码加密JavaScript程序,有效压缩和加密JS、Html、Css页面数据

我们知道&#xff0c;基于Des或Aes对称加密时&#xff0c;当明文和密码相同&#xff0c;则密文相同。而我们此次发布是WJLSymmetricEncryption4.js&#xff08;点击链接跳转到下载页面&#xff09;加密程序&#xff0c;当明文和密码相同&#xff0c;每次加密后的密文不相同&…

20230510vmlinux编译过程

1.进入linux内核源码目录下&#xff0c;打开Makefile文件&#xff0c;搜索vmlinux cmd_link-vmlinux \ $(CONFIG_SHELL) $< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)"; …

第10 CURD操作与RedisCache缓存的强制清理的实现

using System.Net; using Microsoft.Extensions.Caching.Distributed; using Core.Caching; using Core.Configuration; using StackExchange.Redis; namespace Services.Caching { /// <summary> /// 【Redis分布式缓存数据库软件管理器--类】 /// <remarks>…

索引 ---MySQL的总结(五)

索引 在mysql数据库之中&#xff0c;如果数据量过大&#xff0c;直接进行遍历会需要使用许多时间。这里使用空间换时间解决这一个问题。 目前就是从解决问题的这一个角度出发&#xff0c;需要增加搜索的速度&#xff0c;一定是要选择好用的数据结构进行搜索&#xff08;遍历的…

第十五届吉林省赛个人题解【中档题(不过可能对你来说是简单题)】(H、G、C)

文章目录 H. Visit the Park(STL)G. Matrix Repair(思维题)C.Random Number Generator(BSGS算法) H. Visit the Park(STL) 题意&#xff1a;给你一个无向图&#xff0c;每条边上都有一个数码&#xff0c;然后给你一个路径&#xff0c;每次你必须从Ai走到Ai1&#xff08;直接走…

【EHub_tx1_tx2_A200】Ubuntu18.04 + ROS_ Melodic + 锐驰LakiBeam 1L单线激光 雷达评测

大家好&#xff0c;我是虎哥&#xff0c;最近这段时间&#xff0c;又手欠入手了锐驰LakiBeam 1L激光雷达&#xff0c;实在是性价比太优秀&#xff0c;话说&#xff0c;最近激光雷达圈确实有点卷。锐驰官网的资料已经很丰富&#xff0c;我这里总结一下自己的简单测试经验&#x…

挑战14天学完Python---

抛弃了数学思维,引入了计算思维,计算思维是抽象和自动化相结合的结果 抽象:抽象问题的形式化逻辑 自动化:将抽象的结果通过程序自动实现 0.1在计算机内部转二进制 0.1转二进制 二进制的0.1与二进制0.2计算 结果再转十进制 在众多编程语言中 ,只有Python语言提供了复数类型.空间…

OpenCL编程指南-1.2OpenCL基本概念

OpenCL概念基础 面向异构平台的应用都必须完成以下步骤&#xff1a; 1&#xff09;发现构成异构系统的组件。 2&#xff09;探查这些组件的特征&#xff0c;使软件能够适应不同硬件单元的特定特性。 3&#xff09;创建将在平台上运行的指令块&#xff08;内核)。 4&#xff09…

紧跟 AI 步伐, Gitee 已支持 AI 模型托管

AI 时代已经来了&#xff01; 现在&#xff0c;越来越多的企业和个人开始使用 AI 技术来解决各种问题。想要了解 AI&#xff0c;那么就一定要了解 AI 模型&#xff0c;作为 AI 的核心技术之一&#xff0c;AI 模型为各种进阶的人工智能应用奠定了基础&#xff0c;从 ChatGPT 、…

Mysql 存储过程+触发器+存储函数+游标

视图&#xff08;view&#xff09; 虚拟存在的表&#xff0c;不保存查询结构&#xff0c;只保存查询的sql逻辑 语法 存储过程 实现定义并存储在数据库的一段sql语句的集合&#xff0c;可减少网络交互&#xff0c;提高性能&#xff0c;代码复用,内容包括&#xff1a;变量&am…

并发编程进阶

并发编程进阶 文章目录 并发编程进阶一、JMM1. JMM的定义&#xff1a;2. 内存屏障&#xff1a; 三. volatile四. as-if-serial五. happens-before六. 缓存一致性&#xff08;Cache coherence&#xff09;7. Synchronized1. synchronized 的使用2. synchronized底层原理 8. Conc…

Web3.0介绍与产业赛道(去中心化,金融与数字资产,应用与存储,区块链技术)

文章目录 1、web3.0时代——区块链技术2、产业赛道&#xff1a;去中心化金融与数字资产3、产业赛道&#xff1a;去中心化应用与存储4、区块链&#xff1a;基础设施与区块链安全和隐私 1、web3.0时代——区块链技术 Web3.0是什么 Web3.0是指下一代互联网技术&#xff0c;它将在…

最优化理论-线性规划的标准形

目录 一、引言 二、线性规划的标准形 1. 线性规划的定义 2. 线性规划的标准形 3. 线性规划的约束条件 三、线性规划的求解方法 1. 单纯形法 2. 内点法 3. 割平面法 四、线性规划的应用 1. 生产计划 2. 运输问题 3. 投资组合问题 五、总结 一、引言 最优化理论是…

数据链路层及其重要协议——以太网

文章目录 数据链路层前言1. 以太网协议2. MTU&#xff08;传输的限制&#xff09;3. ARP协议 数据链路层 前言 以太网&#xff1a; 不是一种具体的网络&#xff0c;而是一种技术标准。既包含了数据链路层的内容&#xff0c;也包含了一些物理层的内容&#xff0c;例如&#xf…

网络层——IP协议详细解读

文章目录 IP协议1. IP协议的报文格式2. IP协议的地址管理3. IP地址的组成4. IP协议的路由选择 IP协议 之前介绍了传输层的重点协议&#xff0c;TCP和UDP协议&#xff0c;以下将介绍网络层的重点协议IP协议。 1. IP协议的报文格式 IP地址 本质上是一个32位整数&#xff0c;在…

华为OD机试真题 Java 实现【不爱施肥的小布】【2023Q2】

一、题目描述 某农村主管理了一大片果园&#xff0c;fields[i]表示不同国林的面积&#xff0c;单位m2&#xff0c;现在要为所有的果林施肥且必须在n天之内完成&#xff0c;否则影响收成。小布是国林的工作人员&#xff0c;他每次选择一片果林进行施肥&#xff0c;且一片国林施…