动态规划高频问题(算法村第十九关白银挑战)

最少硬币数

322. 零钱兑换 - 力扣(LeetCode)

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:0

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104

动态规划

public int coinChange(int[] coins, int amount)
{//dp数组的索引表示零钱面额,数组元素表示兑换零钱所需的最少硬币数int[] dp = new int[amount +1];	int unreachable = amount + 1;//unreachable表示无法用硬币兑换该面额,因为极端情况是全用一元的硬币兑换,此时最少硬币数应为 amount。另外,一些在1~amount之间的面额,也是无法用硬币兑换的Arrays.fill(dp, unreachable);	dp[0] = 0;	//特例,同时也是递推的起点for (int curAmount = 1; curAmount <= amount; curAmount++)for (int coin : coins)	//逐一用不同的硬币兑换,取所用的最小硬币数;或者换不了,dp值依然为unreachableif (coin <= curAmount)dp[curAmount] = Math.min(dp[curAmount], dp[curAmount - coin] + 1);	//后面的兑换结果,由前面的兑换结果确定return dp[amount] == unreachable ? -1 : dp[amount];
}

当然,分析问题时,首先是from top to down

在这里插入图片描述

设计动态规划算法时,再from botton to up

最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

动态规划

public int lengthOfLIS(int[] nums)
{int ans = 0;//确定状态,即`dp[i]`的值表示以`nums[i]`为结尾的最长递增子序列的长度。初始化为0int[] dp = new int[nums.length];for (int right = 0; right < nums.length; right++){for (int left = 0; left < right; left++)//**跳过非递增的元素**这导致了结尾靠后的递增子序列的长度不一定大于结尾靠前的//以nums[left]为结尾的最长递增子序列,是以nums[right]为结尾的最长递增子序列的一部分if(nums[left] < nums[right])//保持或更新以nums[right]为结尾的最长递增子序列的长度dp[right] = Math.max(dp[right], dp[left]);//遍历所有在当前nums[right]之前的nums[left],确定以nums[right]为结尾的最长递增子序列的长度//将结尾元素计入最大递增子序列长度,然后维护答案ans = Math.max(ans, ++dp[right]); //分别计算不同结尾的递增子序列的结果}return ans;
}

最少完全平方个数

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,14916 都是完全平方数,而 311 不是。

示例 1:

输入:n = 12
输出:3 
解释:12 = 4 + 4 + 4

示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9

提示:

  • 1 <= n <= 104

动态规划

  1. 定义状态:dp[i]表示和为 i的完全平方数的最少数量
  2. 状态转移方程:dp[i] = min{dp[i], dp[i - j*j] + 1} ,其中j<= i的完全平方数,是一个需要遍历的变量;dp[i - j*j]表示数i - j*j能分解为最少几个完全平方数之和,用减法来衔接是可行的。
  3. 起始状态:dp[0] = 0。边界条件:dp[n],即和为 n 的完全平方数的最少数量。
public int numSquares(int n)
{int[] dp = new int[n  + 1];dp[0] = 0;for(int i = 1; i <= n; i++){dp[i] = Integer.MAX_VALUE;for (int j = 1; j * j <= i; j++)dp[i] = Math.min(dp[i], dp[i - j*j] + 1);}return dp[n];
}

再论青蛙跳

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false

示例 1:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 13 步到达最后一个下标。

示例 2:

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

提示:

  • 1 <= nums.length <= 104
  • 0 <= nums[i] <= 105

动态规划

状态:布尔值dp[i]表示能否跳到第i个下标。

public boolean canJump(int[] nums)
{boolean[] dp = new boolean[nums.length];dp[0] = true;   //起点是下标0,可以到达for (int i = 1; i < nums.length; i++){for (int j = 0; j < i; j++)//能从前面的某个能到达的下标j跳到当前下标iif (dp[j] && j + nums[j] >= i){dp[i] = true;break;}}return dp[nums.length - 1];
}

只要修路的速度比人走的速度快

public boolean canJump(int[] nums)
{int road = 0;for (int man = 0; man < nums.length; man++){//人走到前面了,路还没修好if (man > road)return false;//从man的位置继续修路road = Math.max(road, man + nums[man]);}//修路的速度一直比人走的速度快,则人一定能走路到达终点return true;
}

解码方法

一条包含字母 A-Z 的消息通过以下映射进行了 编码

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"

解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"11106" 可以映射为:

  • "AAJF" ,将消息分组为 (1 1 10 6)
  • "KJF" ,将消息分组为 (11 10 6)

注意,消息不能分组为 (1 11 06) ,因为 "06" 不能映射为 "F" ,这是由于 "6""06" 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数

题目数据保证答案肯定是一个 32 位 的整数。

示例 1:

输入:s = "12"
输出:2
解释:它可以解码为 "AB"1 2)或者 "L"12)。

示例 2:

输入:s = "226"
输出:3
解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6)

示例 3:

输入:s = "06"
输出:0
解释:"06" 无法映射到 "F" ,因为存在前导零("6""06" 并不等价)。

提示:

  • 1 <= s.length <= 100
  • s 只包含数字,并且可能包含前导零。

动态规划

定义状态:1 <= i <= ndp[i]表示前i个数字所能构成的解码方案数。数组初始化为0。

初始状态:dp[0] = 1,“前0个数字”即空串。根据下面的状态转移方程,dp[1] = dp[0] = 1,合理,因为用一个数字只能构成一种解码方案。

public int numDecodings(String s)
{int n = s.length();int[] dp = new int[n + 1];dp[0] = 1;for (int i = 1; i <= n; i++){//拿一位数来解码if(s.charAt(i - 1) != '0')dp[i] = dp[i - 1];//拿两位数来解码if (i >= 2 && s.charAt(i - 2) != '0'){int num = (s.charAt(i - 2) - '0') * 10 + s.charAt(i - 1) - '0';if (num <= 26)//衔接总解码数dp[i] += dp[i - 2];}}return dp[n];
}

路径中存在障碍物

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 10 来表示。

示例 1:

img

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

示例 2:

img

输入:obstacleGrid = [[0,1],[0,0]]
输出:1

提示:

  • m == obstacleGrid.length
  • n == obstacleGrid[i].length
  • 1 <= m, n <= 100
  • obstacleGrid[i][j]01

参考笔记

动态规划青铜:不同路径

错误方法

本方法假定第一个格子无障碍物的情况下,第一列均可达,而这有根本性错误:第一列的其他行可能存在障碍物,此时不可达。就比如测试用例obstacleGrid = {{0},{1}}

所以,对每个格子,都要先判断是否有障碍物,再对其dp赋值(dp[i]表示从起点开始到达当前位置的路径数)。具体实现见“正确方法”

public static void main(String[] args){
//        int[][] obstacleGrid = {{0,0,0},{0,1,0},{0,0,0}};
//        int[][] obstacleGrid = {{0,1},{0,0}};
//        int[][] obstacleGrid = {{0,1}};int[][] obstacleGrid = {{0},{1}};int ans = uniquePathsWithObstacles(obstacleGrid);System.out.println(ans); //预期:0;结果:1}public static int uniquePathsWithObstacles(int[][] obstacleGrid){int row = obstacleGrid.length;int col = obstacleGrid[0].length;int[] dp = new int[col];//确定第一行的障碍物位置int flag = -1;for (int j = 0; j < col; j++)if (obstacleGrid[0][j] == 1){flag = j;break;}//因为第一行只能往右走,所以往后均不可达到if (flag == -1)     //第一行无障碍物Arrays.fill(dp, 1);else                //将索引0到flag - 1 的dp值设置为1Arrays.fill(dp, 0, flag, 1);//从第二行滚动数组开始for (int i = 1; i < row; i++)for (int j = 1; j < col; j++){if (obstacleGrid[i][j] == 1){dp[j] = 0;  //没法到达有障碍物的格子continue;}dp[j] = dp[j] + dp[j - 1];}return dp[col - 1];}

正确方法

public static int uniquePathsWithObstacles_2(int[][] obstacleGrid)
{int row = obstacleGrid.length;int col = obstacleGrid[0].length;int[] dp = new int[col];//设置初始状态if (obstacleGrid[0][0] == 0)dp[0] = 1;else	return 0;	//开局即被堵死for (int i = 0; i < row; i++)for (int j = 0; j < col; j++){if (obstacleGrid[i][j] == 1){dp[j] = 0;continue;}//第一列的格子的dp值由初始状态和obstacleGrid[i][0]共同确定//这里值需考虑其他列的dp值:当前 <- 左 + 上if (j >= 1 && obstacleGrid[i][j] == 0)dp[j] = dp[j] + dp[j - 1];}return dp[col - 1];
}

杨辉三角Ⅱ

给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

img

示例 1:

输入: rowIndex = 3
输出: [1,3,3,1]

示例 2:

输入: rowIndex = 0
输出: [1]

示例 3:

输入: rowIndex = 1
输出: [1,1]

提示:

  • 0 <= rowIndex <= 33

进阶:

你可以优化你的算法到 *O*(*rowIndex*) 空间复杂度吗?

动态规划

计算dp[i]的时,还需要上一轮的dp[i-1],但是dp[i-1]已经被覆盖,故至少需要两个数组进行滚动,第一个数组用于完整保留上一轮的dp

public static List<Integer> getRow(int rowIndex)
{//保留前一行的所有元素ArrayList<Integer> pre = new ArrayList<Integer>();pre.add(1);for (int i = 1; i <= rowIndex; i++){//每次生成一个新的一维数组(列表)ArrayList<Integer> cur = new ArrayList<>();//每行的长度 = 行索引(从0开始) + 1for (int j = 0; j <= i; j++){//第一个和最后一个都为1if(j == 0 || j == i)cur.add(1);//新一行的中间元素 = 上 + 上左elsecur.add(pre.get(j) + pre.get(j - 1));}pre = cur;  //上一行变更,继续滚动}return pre;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

各种姿势打穿企业内网

以前不是说要讲隧道吗&#xff1f;&#xff1f;&#xff1f; 鸽了这么久终于想起来了&#xff01;&#xff01;&#xff01; 1.本次实验环境拓扑 先来讲一下本次的实验环境吧&#xff0c;这样会更加清晰明了一点 首先我们是拿到了win7&#xff0c;然后最终目标上线内网的Wi…

ubuntu 计算器 gnome-calculator

sudo apt install gnome-calculator gnome-calculator

霍庭格TruPlasma MF 7100 7050电源现货50KW

霍庭格TruPlasma MF 7100 7050电源现货50KW

文档分类FastText模型 (pytorch实现)

文档分类FastText FastText简介层次softmaxN-gram特征FastText代码&#xff08;文档分类&#xff09; FastText简介 FastText与之前介绍过的CBOW架构相似&#xff0c;我们先来会议一下CBOW架构&#xff0c;如下图&#xff1a; CBOW的任务是通过上下文去预测中间的词&#xff0…

详解动态规划之01背包问题及其空间压缩(图文并茂+例题讲解)

1. 动态规划问题的本质 记忆化地暴力搜索所有可能性来得到问题的解 我们常常会遇到一些问题&#xff0c;需要我们在n次操作&#xff0c;且每次操作有k种选择时&#xff0c;求出最终需要的最小或最大代价。处理类似的问题&#xff0c;我们一般需要遍历所有的可能性(相当于走一遍…

STM32-串口通信波特率计算以及寄存器的配置详解

您好&#xff0c;我们一些喜欢嵌入式的朋友一起建立的一个技术交流平台&#xff0c;本着大家一起互相学习的心态而建立&#xff0c;不太成熟&#xff0c;希望志同道合的朋友一起来&#xff0c;抱歉打扰您了QQ群372991598 串口通信基本原理 处理器与外部设备通信的两种方式 并行…

邮箱地址验证软件有哪些-邮件地址验证软件

邮箱地址验证软件是帮助用户验证电子邮箱地址是否有效和真实存在的工具。以下是一些常用的邮箱地址验证软件&#xff1a; 易邮件地址验证大师&#xff1a;这是电子邮件营销平台MailerLite提供的一个简单的电子邮件验证工具&#xff0c;通过多层验证过程保证高准确率。寅甲邮件…

ChatGPT-4o 实战 如何快速分析混淆加密和webpack打包的源码

ChatGPT-4o 几个特点 一个对话拥有长时间的记忆&#xff0c;可以连续上传文件&#xff0c;让其分析&#xff0c;最大一个代码文件只能3M&#xff0c;超出3M的文件&#xff0c;可以通过split-file可以进行拆分 其次ChatGPT-4o可以生成文件的下载链接&#xff0c;这有利于大文件的…

TypeScript的数据类型系统

TypeScript的数据类型系统 在上一篇文章中&#xff0c;我们介绍了TypeScript的基本概念和它与JavaScript的关系。TypeScript的核心优势之一是其强大的类型系统&#xff0c;它提供了丰富的数据类型&#xff0c;使得代码更加可靠和易于维护。本文将深入探讨TypeScript中的各种数…

gpt4o在哪用?

GPT-4o功能&#xff1f; 1.感知用户情绪&#xff1a;前沿研究部门主管陈信翰&#xff08;Mark Chen&#xff09;让ChatGPT-4o聆听他的呼吸&#xff0c;聊天机器人侦测到他急促的呼吸&#xff0c;并幽默地建议他不要像吸尘器那样呼吸&#xff0c;要放慢速度。随后Mark深呼吸一次…

浏览器插件Video Speed Controller(视频倍速播放),与网页自身快捷键冲突/重复/叠加的解决办法

浏览器插件Video Speed Controller&#xff08;视频倍速播放&#xff09;&#xff0c;与网站自身快捷键冲突/重复/叠加的解决办法 插件介绍问题曾今尝试的办法今日发现插件列表中打开Video Speed Controller的设置设置页面翻到下面&#xff0c;打开实验性功能。将需要屏蔽的原网…

邮件API接口的优势有哪些?如何有效整合?

邮件API怎么选&#xff1f;SendCloud与AokSend的性能对比分析&#xff1f; 邮件API接口作为企业与用户沟通的重要桥梁&#xff0c;其重要性不言而喻。Aok将深入探讨邮件API接口的优势、有效整合的方法、选择标准以及SendCloud与AokSend两款邮件发送服务的性能对比分析。 邮件…