剑指Offer题目笔记27(动态规划单序列问题)

面试题89:

面试题89

问题:

​ 输入一个数组表示某条街道上的一排房屋内财产的数量。相邻两栋房屋不能同时被盗,问小偷能偷取到的最多财物。

解决方案一(带缓存的递归):

解决方案:
  1. 由于有报警系统,小偷不能同时偷相邻两栋房屋,故小偷在到达标号为i的房屋时他能偷取的财物的最大值,即f(i)=max(f(i-2)+nums[i],f(i-1))。
  2. 创建一个数组dp,它的第i个元素dp[i]用来保存f(i)的结果。如果f(i)之前已经计算出结果,那么只需要从数组dp中读取dp[i]的值,不用再重复计算。如果之前从来没有计算过,则根据状态转移方程递归计算。
源代码:
class Solution {
    public int rob(int[] nums) {
        int[] dp = new int[nums.length];
        //将dp数组的所有值赋值为-1
        Arrays.fill(dp,-1);

        dfs(nums,nums.length-1,dp);
        return dp[nums.length-1];
    }

    private void dfs(int[] nums,int len,int[] dp){
        if(len == 0){
            dp[len] = nums[0];
        }else if(len == 1){
            dp[len] = Math.max(nums[0],nums[1]);
        }else if(dp[len] < 0){
            dfs(nums,len-1,dp);
            dfs(nums,len-2,dp);

            dp[len] = Math.max(dp[len-1],dp[len-2] + nums[len]);
        }
    }
}

解决方案二:(空间复杂度为O(n)的迭代):

解决方案:

​ 由下到上,先求出f(0)和f(1)的值,再通过状态转移方程f(i) = Math,max(f(i-1),f(i-2) + nums[i])求出f(2)以此类推求得f(i)。

源代码:
class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        int[] dp = new int[len];

        dp[0] = nums[0];

        if(len >= 2){
            dp[1] = Math.max(nums[1],nums[0]);
        }

        for(int i = 2;i < len;i++){
            dp[i] = Math.max(dp[i-1],dp[i-2] + nums[i]);
        }

        return dp[len-1];
    }
}

解决方案三:(空间复杂度为O(1)的迭代):

解决方案:

​ 观察上述代码,就能发现计算“dp[i]”时只需要用到“dp[i-1]”和“dp[i-2]”这两个值,也就是说,只需要缓存两个值就足够了,并不需要一个长度为n的数组。

源代码:
class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        int[] dp = new int[2];

        dp[0] = nums[0];

        if(len >= 2){
            dp[1] = Math.max(nums[0],nums[1]);
        }

        for(int i = 2;i < len;i++){
            dp[i % 2] = Math.max(dp[(i-1) % 2],dp[(i-2) % 2 ] + nums[i]);
        }

        return dp[(len - 1) % 2];
    }
}

解决方案四:(使用两个状态转移方程):

解决方案:

​ 由于小偷到达标记为i的房屋是有两个选择,他进去偷东西和不进去偷东西,定义f(i)为不进去偷东西,g(i)为进去偷东西,因此f(i)= max(f(i-1),g(i-1)),g(i) = f(i-1) + nums[1]。这两个状态转移方程有隐含条件i必须大于0,不然i-1没有意义,因此f(0)= 0,g(0) = nums[0]。

源代码:
class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        int[][] dp = new int[2][2];
		//dp【0】为f(i)
        dp[0][0] = 0;
        //dp【1】为g(i)
        dp[1][0] = nums[0];

        for(int i = 1;i < len;i++){
            dp[0][i % 2] = Math.max(dp[0][(i - 1) % 2],dp[1][(i - 1) % 2]);
            dp[1][i % 2] = Math.max(dp[0][i % 2],dp[0][(i-1) % 2] + nums[i]);
        }

        return Math.max(dp[0][(len - 1) % 2],dp[1][(len - 1) % 2]);
    }
}

面试题90:

面试题90

问题:

​ 一条环形街道上有若干房屋,输入一个数组表示该条街道上的房屋内财产的数量,计算小偷在这条街道上最多能偷取到的财产数量。

解决方案:
  1. 如果小偷去偷标号为0的房屋,那么他就不能去偷标号为n-1的房屋;如果小偷去偷标号为n-1的房屋,那么他就不能去标号为0的房屋。
  2. 可以将这个问题分解成两个子问题:一个问题是求小偷从标号为0开始到标号为n-2结束的房屋内能偷得的最多财物数量,另一个问题是求小偷从标号为1开始到标号为n-1结束的房屋内能偷得的最多财物数量。
源代码:
class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        if(len == 0){
            return 0;
        }

        if(len == 1){
            return nums[0];
        }
		//0~n-2
        int result1 = dfs(nums,0,len-2);
        //1~n-1
        int result2 = dfs(nums,1,len-1);

        return Math.max(result1,result2);
    }

    private int dfs(int[] nums,int start,int end){
        int[] dp = new int[2];
        dp[0] = nums[start];

        if(start < end){
            dp[1] = Math.max(nums[start],nums[start+1]);
        }

        for(int i = start+2;i <= end;i++){
            int j = i - start;
            dp[j % 2] = Math.max(dp[(j-1) % 2],dp[(j-2) % 2] + nums[i]); 
        }

        return Math.max(dp[0],dp[1]);
    }
}

面试题91:

面试题91

问题:

​ 一排n幢房子要粉刷成红色、绿色和蓝色,不同房子被粉刷成不同颜色的成本不同。要求任意相邻的两幢房子的颜色都不一样。

解决方案:
  1. 当计算粉刷标记为i的房子时的成本,得先考虑标记i-1的房子的颜色。因此,需要3个表达式,r(i)表示粉刷红色、g(i)表示粉刷绿色、b(i)表示粉刷蓝色。
  2. 如果标记i的房子粉刷红色时,那么标记i-1的房子可以被粉刷为绿色或蓝色,r(i) = min(g(i-1),b(i-1))+ costs[i]。如果标记i的房子粉刷绿色时,那么标记i-1的房子可以被粉刷为红色或蓝色,g(i) = min(r(i-1),b(i-1))+ costs[i]。如果标记i的房子粉刷蓝色时,那么标记i-1的房子可以被粉刷为绿色或红色,b(i) = min(g(i-1),r(i-1))+ costs[i]。
源代码:
class Solution {
    public int minCost(int[][] costs) {
        int len = costs.length;
        //将三个一维数组合成为一个二维数组
        int[][] dp = new int[3][2];

        for(int i = 0;i < 3;i++){
            dp[i][0] = costs[0][i];
        }

        for(int i = 1;i < len;i++){
            for(int j = 0;j < 3;j++){
            	//计算另外两种颜色的成本
                int result1 = dp[(j + 1) % 3][(i - 1) % 2];
                int result2 = dp[(j + 2) % 3][(i - 1) % 2];
                
                dp[j][i % 2] = Math.min(result1,result2) + costs[i][j];
            }
        }

        int last = (len-1) % 2;
        return Math.min(Math.min(dp[0][last],dp[1][last]),dp[2][last]);
    }
}

面试题92:

面试题92

问题:

​ 输入一个只包含‘0’和‘1’的字符串,至少需要翻转几个字符,才可以使翻转之后的字符串中所有的‘0’位于‘1’的前面。

解决方案:
  1. 由于翻转下标为i的字符依赖于前i个字符翻转之后的最后一个字符是‘0’还是‘1’,因此要分两种情况讨论,假设f(i)是翻转之后最后一个字符为0的函数,g(i)为翻转之后最后一个字符为1的函数。
  2. 如果下标为i的字符是‘0’,那么f(i)= f(i-1)不需要进行翻转;如果下标为i的字符是‘1’,那么f(i) = f(i-1)+ 1。如果下标为i的字符是‘0’,那么g(i) = min(f(i-1),g(i-1))+ 1,因为当我们将下标为i的字符翻转为‘1’时,那么不管下标i-1的字符是0或1都可以;如果下标为i的字符是‘1’,那么g(i)= min(f(i-1),g(i-1)),因为下标为i的字符已经为‘1’了,不需要翻转。
源代码:
class Solution {
    public int minFlipsMonoIncr(String s) {
        int len = s.length();
        int[][] dp = new int[2][2];
        char ch = s.charAt(0);
        dp[0][0] = ch == '0'?0:1;
        dp[1][0] = ch == '0'?1:0;

        for(int i = 1;i < len;i++){
            char sh = s.charAt(i);
            int result1 = dp[0][(i - 1) % 2];
            int result2 = dp[1][(i - 1) % 2];
            dp[0][i % 2] = result1 + (sh == '0'?0:1);
            dp[1][i % 2] = Math.min(result1,result2) + (sh == '1'?0:1); 
        }

        return Math.min(dp[0][(len - 1) % 2],dp[1][(len - 1) % 2]);
    }
}

面试题93:

面试题93

问题:

​ 输入一个没有重复数字的单调递增的数组,数组中至少有3个数字,请问数组中最长的斐波那契数列的长度是多少。

解决方案:
  1. 将数组记为A,A[i]表示数组中下标为i的数字。对于每个j(0≤j<i),A[j]都有可能是在某个斐波那契数列中A[i]前面的一个数字。如果存在一个k(0≤k< j)满足A[k]+A[j]=A[i],那么这3个数字就组成了一个斐波那契数列。这个以A[i]为结尾、前一个数字是A[j]的斐波那契数列是在以A[j]为结尾、前一个数字是A[k]的序列的基础上增加一个数字A[i],因此前者的长度是在后者的长度的基础上加1。
  2. 由于状态转移方程有两个参数i和j,因此需要一个二维数组来缓存f(i,j)的计算结果。i对应二维数组的行号,j对应二维数组的列号。由于i大于j,因此实际上只用到了二维数组的左下角部分。如果数组的长度是n,那么i的取值范围为1~n-1,而j的取值范围为0~n-2。
源代码:
class Solution {
    public int lenLongestFibSubseq(int[] arr) {
        int len = arr.length;
        //使用map数组记录每个数字在数组中的下标
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i < len;i++){
            map.put(arr[i],i);
        }
		
        int[][] dp = new int[len][len];
        int result = 2;
        for(int i = 1;i < len;i++){
            for(int j = 0;j < i;j++){
                //判断数组中是否存在一个数字 arr[k]满足arr[k]=arr[i]-arr[j]。
                int k = map.getOrDefault(arr[i]-arr[j],-1);
                //如果存在,就在f(j,k)的基础上加一
                dp[i][j] = k >= 0 && k < j?dp[j][k] + 1:2;

                result = Math.max(result,dp[i][j]);
            }
        }

        return result > 2?result:0;
    }
}

面试题94:

面试题94

问题:

​ 输入一个字符串,请问至少需要分割几次才可以使分割出的每个字符串都是回文。

解决方案:
  1. 假设字符串为S,下标为i的字符为S[i],下标从j到i的子字符串为S[j…i]。用 f(i)表示从下标为0到i的子字符串S[0…i]的符合条件的最少分割次数。如果字符串的长度是n,那么f(n-1)就是问题的解。
  2. 如果子字符串S[0…i]本身就是一个回文,那么不需要分割就符合要求,此时f(i)等于0。如果子字符串S[0…i]不是一个回文,那么对每个下标j(1≤j≤i)逐一判断子字符串S[j…i]是不是回文。如果是回文,那么这就是一个有效的分割方法,此时的分割次数相当于子字符串S[0…j-1]的分割次数再加1,因为这是将子字符串S[0…j-1]按照要求分割之后再在S[j-1]和S[j]这两个字符中间再分割一次。因此,f(i)就是所有符合条件的j对应的f(j-1)的最小值加1。
源代码:
class Solution {
    public int minCut(String s) {
        int len = s.length();
        //使用二维数组记录j到i是否回文
        boolean[][] flag = new boolean[len][len];
        for(int i = 0;i < len;i++){
            for(int j = 0;j <= i;j++){
                char ch1 = s.charAt(i);
                char ch2 = s.charAt(j);
                //i <= j+1用于判断i和j相邻的情况、flag[j+1][i-1]用于判断j到i不相邻的情况
                if(ch1 == ch2 && (i <= j+1 || flag[j+1][i-1])){
                    flag[j][i] = true;
                }
            }
        }

        int[] dp = new int[len];
        for(int i = 0;i < len;i++){
            //如果0到i是回文数,那么就不需要分割
            if(flag[0][i]){
                dp[i] = 0;
            }else{
                //先做最坏的打算,字符需要分割成一个一个的字符。例如abcd,就需要分割3次
                dp[i] = i;
                for(int j = 1;j <= i;j++){
                    //如果j到i是回文数,那么就在dp【j-1】切割次数的基础上加一
                    if(flag[j][i]){
                        dp[i] = Math.min(dp[i],dp[j-1] + 1);
                    }
                }
            }
        }

        return dp[len-1];
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

训练大模型的显卡参数辨析

以NVIDIA A100&#xff08;80GB&#xff09;为例&#xff1a; A100中的A是Ampere&#xff08;安培体系&#xff09;首字母&#xff0c;100是系列号&#xff0c;除了A100&#xff0c;还有A800 80GB指的是这张显卡的显存为80GB PCIe&#xff1a;PCIe本身是一种总线协议&#xf…

nodejs应用程序不同部署环境下的差异配置方案

一、背景 nodejs应用程序&#xff0c;不同于java语言使用分布式配置&#xff0c;当部署于不同的环境里&#xff0c;因为环境的差异&#xff0c;配置项的值也不尽相同。 最常见的差异就是数据库的连接信息&#xff0c;而代码是一份&#xff0c;不能把生产环境的信息暴露在非生产…

书生·浦语大模型实战营 | 第2次学习笔记

前言 书生浦语大模型应用实战营 第二期正在开营&#xff0c;欢迎大家来学习。&#xff08;参与链接&#xff1a;课程升级&#xff0c;算力免费&#xff0c;书生浦语实战营第二期学员招募&#xff5c;活动预告https://mp.weixin.qq.com/s/YYSr3re6IduLJCAh-jgZqg&#xff09; …

多因子量化的框架

基础概念 多因子模型&#xff08;Multiple-Factor Model, MFM&#xff09;正是基于 APT 模型的思想发展出来的完整的风险模型。 多因子模型定量刻画了股票预期收益率与股票在每个因子上的因子载荷&#xff08;风险敞口&#xff09;&#xff0c;以及每个因子每单位因子载荷&am…

什么是数据库?如何安装SQL Server(超详细版)

文章目录 什么是数据库数据库与数据库管理系统数据库系统之间的区别和联系数据库在生活中的应用 安装SQL Server数据库系统要求 安装步骤(超详细)安装前的准备 安装SSMS 什么是数据库 数据库&#xff0c;顾名思义&#xff0c;是存储数据的“仓库”。它不仅仅是简单的数据存储&…

2024年租用阿里云服务器多少钱一年?连夜整理分享

阿里云服务器租用价格表2024年最新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元&#xff0c;ECS u1服务器2核4G5M固定带宽199元一年&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;2核…

jdk api之AbstractMethodError基础、应用、实战

博主18年的互联网软件开发经验&#xff0c;从一名程序员小白逐步成为了一名架构师&#xff0c;我想通过平台将经验分享给大家&#xff0c;因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验&#xff0c;晚上进行用心精简、整理、总结、定稿&…

博客部署002-centos安装nginx

1、centos 如何安装nginx? 在CentOS系统上安装Nginx的过程相对直接&#xff0c;通常可以通过系统自带的Yum包管理器来安装。以下是安装Nginx的最新稳定版的步骤&#xff1a; 1.1 更新系统软件包 在安装Nginx之前&#xff0c;首先确保系统软件包是最新的&#xff0c;运行…

Java——数据类型、运算符、逻辑控制、方法、数组

1.前置知识 Java是一门面向对象的编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表&#xff0c;极好地实现了面向对象理论…

精心整理-数据分类分级赋能企业数据安全建设资料合集

以下是资料目录&#xff0c;如需下载请前往知识星球下载&#xff1a;https://t.zsxq.com/18KTZnJMX 企业数据安全建设数据分类分级架构.pdf 企业数据分类分级模板.xls 数据分类分级的实践与挑战.pdf 数据分类分级制度评述.pdf 电信和互联网大数据安全管控分类分级实施指南.pdf …

瑞吉外卖实战学习-17、用户地址簿相关功能

用户地址簿相关功能 效果图1、根据规则创建相关文件2、新增收货地址接口3、列表查询页面以及设置默认地址 效果图 1、根据规则创建相关文件 2、新增收货地址接口 获取到传入的数据然后将id添加进去&#xff0c;然后存储到数据库 3、列表查询页面以及设置默认地址 list接口&am…

GPU部署ChatGLM3

首先&#xff0c;检查一下自己的电脑有没有CUDA环境&#xff0c;没有的话&#xff0c;去安装一个。我的电脑是4060显卡&#xff0c;买回来就自带这些环境了。没有显卡的话&#xff0c;也不要紧&#xff0c;这个懒人安装包支持CPU运行&#xff0c;会自动识别没有GPU&#xff0c;…

智能视频分析边缘AI盒子及应用场景:社区、校园、酒店、商场、餐饮门店、医院、港口等诸多领域

应用场景: 社区、校园、酒店、商场、餐饮门店、医院、港口等诸多领域 一、边缘AI盒子产品介绍 1、基于算法仓丰富算法&#xff0c;可针对不同场景进行算法灵活配置使用和远程实时更新迭代。 2、支持自定义视频通道算法执行计划。 3、支持根据事件名称、时间等进行预警事件视频…

【Easy云盘 | 第十三篇】分享模块(获取目录信息、获取文件信息、创建下载链接)

文章目录 4.4.7获取目录信息4.4.8获取文件信息4.4.9创建下载链接 4.4.7获取目录信息 明天做 4.4.8获取文件信息 明天做 4.4.9创建下载链接 明天做

FreeRTOSFreeRTOS列表和列表项

FreeRTOS列表和列表项 今天继续跟着正点原子学习FreeRTOS列表和列表项的内容。列表和列表项这个知识点用到了C语言链表的知识点。所以必须对C语言中的链表这个数据结构才能更好的理解这部分内容。TIPS&#xff1a;正点原子这节课内容讲的特别好&#xff0c;强烈推荐&#xff1…

08 | Swoole 源码分析之 Timer 定时器模块

原文首发链接&#xff1a;Swoole 源码分析之 Timer 定时器模块 大家好&#xff0c;我是码农先森。 引言 Swoole 中的毫秒精度的定时器。底层基于 epoll_wait 和 setitimer 实现&#xff0c;数据结构使用最小堆&#xff0c;可支持添加大量定时器。 在同步 IO 进程中使用 seti…

数据库系统概论(超详解!!!)第三节 关系数据库标准语言SQL(Ⅵ)

1.空值的处理 空值就是“不知道”或“不存在”或“无意义”的值。 一般有以下几种情况&#xff1a; 该属性应该有一个值&#xff0c;但目前不知道它的具体值 &#xff1b;该属性不应该有值 &#xff1b;由于某种原因不便于填写。 1.空值的产生 空值是一个很特殊的值&#x…

什么牌子开放式耳机好用?优选五大高分好物真诚分享

对于习惯长时间佩戴耳机的朋友来说&#xff0c;入耳式耳机固然能够提供较优质的音质体验。但是&#xff0c;由于其较为封闭的设计以及对耳洞的压迫&#xff0c;舒适感较差&#xff0c;长时间佩戴可能会对听力造成一定的影响。因此&#xff0c;开放式耳机的出现为音乐发烧友们提…

青风环境带您了解2024第13届生物发酵展

参展企业介绍 浙江青风环境股份有限公司创立于1998年&#xff0c;是一家集科研、生产及贸易为一体的高新技术企业。公司座落于浙江省丽水市水阁工业区&#xff0c;占地面积120亩&#xff0c;建筑面积近11万平方米&#xff0c;年产值可达20亿元&#xff0c;建有标准的冷&#x…

回归预测 | Matlab实现WOA-GPR鲸鱼算法优化高斯过程回归多变量回归预测

回归预测 | Matlab实现WOA-GPR鲸鱼算法优化高斯过程回归多变量回归预测 目录 回归预测 | Matlab实现WOA-GPR鲸鱼算法优化高斯过程回归多变量回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现WOA-GPR鲸鱼算法优化高斯过程回归多变量回归预测 1.Matlab实现…