当前位置: 首页 > news >正文

leetCode-hot100-动态规划专题

动态规划

  • 动态规划定义
  • 动态规划的核心思想
  • 动态规划的基本特征
  • 动态规划的基本思路
  • 例题
    • 322.零钱兑换
    • 53.最大子数组和
    • 72.编辑距离
    • 139.单词拆分
    • 62.不同路径
    • 63.不同路径Ⅱ
    • 64.最小路径和
    • 70.爬楼梯
    • 121.买卖股票的最佳时机
    • 152.乘积最大子数组

动态规划定义

动态规划(Dynamic Programming,简称DP)是一种用于求解最优化问题的数学方法。它把一个复杂的问题分解成一系列相互重叠的子问题,并从最基础的子问题开始解决,将每个子问题的解存储下来以供后面使用,从而避免了重复计算。

动态规划的核心思想

动态规划的核心思想是“记住已经解决过的子问题的最优解”,这被称作“记忆化”。通过把原问题分解为相对简单的子问题,并在求解过程中保存子问题的解,动态规划能够显著减少计算量。

动态规划的基本特征

  • 最优子结构:一个问题的最优解包含其子问题的最优解。这意味着问题可以通过组合子问题的最优解来解决。
  • 子问题重叠:在解决一个问题时,相同的子问题会被反复计算多次。动态规划算法通过存储这些子问题的解来避免重复计算。
  • 无后效性:一旦某个给定子问题的解被确定后,就不会再改变,不管在这之后会解决哪些子问题。
  • 有重叠子问题:动态规划适用于子问题空间有大量重叠的情况,即不同的子问题具有相同的子子问题。

通过斐波拉契函数来体现以上的基本特征:
问题描述:
斐波那契数列是一个非常经典的递归问题,也可以用动态规划来解决。斐波那契数列的定义如下:

F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2) for n > 1

当求F(5)
在这里插入图片描述

动态规划的基本思路

1.定义状态:将问题分解为一系列的子问题,并定义状态变量来表示这些子问题的解。
2.状态转移方程:找出状态变量之间的关系,建立数学公式,这被称为状态转移方程或递推关系。
3.边界条件:确定最基础子问题的解,这些解将作为算法的起点。
4.计算顺序:确定子问题的解决顺序,确保在解决一个子问题时,它所依赖的子问题已经被解决。
5.实现:根据状态转移方程和边界条件,使用自底向上的方法计算每一个子问题的解,并存储起来。
6.构造最优解:所有子问题的解都计算出来后,根据这些解构造出原问题的最优解。
动态规划可以采用两种基本的实现方式:自顶向下(带记忆化的递归)和 自底向上(迭代)。在实际应用中,自底向上的方法更常见,因为它避免了递归的开销,并且能够更直接地利用数组等数据结构来存储状态。
做每道题目时需要思考的问题,将会在下面的例题中举例讲解:
dp数组以及下标的含义
确定状态转移方程(递推公式)
dp数组如何初始化
遍历顺序(从后向前还是从前向后遍历呢?比较典型的是背包问题)

例题

322.零钱兑换

思路:
本题是一个背包模型,题意总结出来是装满一个容量为j的背包的需要的最少物品数,按照动态规划的解题步骤来逐步分析:
1.dp数组以及下标的含义:
dp[j]:装满容量为j的背包需要的最少物品为dp[j]
最终需要求的是dp[amount]
2.确定状态转移方程:

dp[j] = min(dp[j - coins[i]] + 1, dp[j])

当我们选择使用硬币 coins[i],那么已经有的硬币数量为 dp[j - coins[i]],再加上一个硬币就得到了dp[j],因为我们有很多个coins[i]可以选择,所以就会对应有很多个dp[j],而我们需要求最少硬币数量,所以取所有dp[j]的最小值,即可以得到上面的状态转移方程。
3.dp数组的初始化
dp[0] = 0:凑满价值为0的物品需要的硬币数为0;
非零下标:由于最后取最小值,所以为了防止干扰,所有非零下标的初始值应该为INT_MAX
4.遍历顺序
在本题目中遍历顺序无影响,因为既不是排列数也不是组合数。(如果是组合数需要先遍历物品再遍历背包,如果是排列数需要先遍历背包再遍历物品)
时间复杂度:
代码包含两层嵌套的循环:
外层循环遍历所有的硬币种类,次数为 coins.length
内层循环遍历从当前硬币的面值到 amount,次数为 amount - coins[i] + 1
因此,总的时间复杂度可以表示为:
O(coins.length * amount)
这是因为每个硬币种类都可能遍历到 amount 次。
代码实现:

class Solution {public int coinChange(int[] coins, int amount) {// 初始化 dp 数组,长度为 amount + 1int[] dp = new int[amount + 1];// 基本状态,组成金额 0 需要 0 个硬币dp[0] = 0;// 将 dp 数组的所有元素初始化为正无穷for(int i = 1;i < dp.length;i++){dp[i] = Integer.MAX_VALUE;}// 遍历每一种硬币for(int i = 0;i < coins.length;i++){// 更新 dp 数组,从 coin 到 amountfor(int j = coins[i] ; j <= amount;j++){//检查 dp[j - coins[i]] 是否为 Integer.MAX_VALUE。//如果是,表示金额 j - coins[i] 不能用当前硬币组合出,因此不更新 dp[j]if(dp[j - coins[i]] !=Integer.MAX_VALUE){dp[j] = Math.min(dp[j - coins[i]] + 1, dp[j]);}}}// 如果 dp[amount] 仍为正无穷,说明无法组成该金额,返回 -1return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];}
}

53.最大子数组和

思路
本题采用动态规划来求解,动态规划最重要的是得到动态规划方程,在本题中我们要得到数组中和最大的连续子数组,用f(i)来表示到下标为i时子数组的和,那么f(i-1)就是指到下标为i-1的子数组的和,如果f(i-1)<0,所以要得到最大的和,f(i)就是nums[i],如果f(i-1)>0,那么f(i)的最大和为f(i-1)+nums[i],需要注意的题目中要求是连续的子数组,如果不是连续子数组,那么就不能用该方法求解,可以使用dfs。如果没有理解上面的讲解可以点击观看视频讲解视频讲解-最大子数组和。
时间复杂度
由于只对数组进行了一次遍历,所以时间复杂度为O(n)
代码实现

class Solution {public int maxSubArray(int[] nums) {int pre = nums[0];int ans = nums[0];for(int i = 1; i < nums.length;i++){pre = pre > 0 ? pre + nums[i] : nums[i];ans = Math.max(ans,pre);}return ans;}
}

72.编辑距离

思路
本题采用动态规划来解决,设f(i,j)为匹配word1中的前i个字符和word2中的前j个字符所需要的最少步数,f(i,j)的值可分为两种情况
(1)word1[i] == word2[j]:此时不需要进行任何操作即可匹配,f(i,j) = f(i-1,j-1)
(2)word1[i] != word2[j]:由于有三种操作可供选择,所以又分为三种情况,最后的结果需要在这三种情况中取最小值:

  • 插入:在word1[i]的后面插入word2[j],回到第一种情况,此时word1[i+1]
    =word2[j],所以f(i,j)=f(i,j-1)+1(这个+1是指插入操作)
  • 删除:删除word1[i],此时需要比较word1[i-1]word2[j],所以f(i,j)=f(i-1,j)+1(+1是指删除操作)
  • 替换:替换word1[i]word2[j],此时与第一种情况完全相同,f(i,j)=f(i-1,j-1)+1(+1是指替换操作)

需要注意的是,我们需要对第一行和第一列特殊处理,在两个字符串前加上空格,在初始化第一列时,f[i][0]表示word1的前i个字符匹配word2[0]的最少步骤,也就是匹配空字符串的步数,因此替换word1i个字符为空字符即可,所以步骤为i,初始化第一行同理,视频讲解点击视频讲解-编辑距离。
按照动态规划的解题步骤来逐步分析:
1.dp数组以及下标的含义:
f[i][j]:匹配word1中的前i个字符和word2中的前j个字符所需要的最少步数
最终需要求的是f[n][m]
2.确定状态转移方程:

//word1[i] == word2[j]
f[i][j] = f[i - 1][j - 1];
//word1[i] != word2[j]
f[i][j] = Math.min(f[i - 1][j - 1],Math.min(f[i - 1][j] ,f[i][j - 1])) + 1;

3.dp数组的初始化
需要先对两个字符串进行预处理,在前面加上' '
f[i][0]和f[0][j]f[i][0]表示word1的前i个字符匹配word2[0]的最少步骤,也就是匹配空字符串的步数,因此替换word1i个字符为空字符即可,所以初始化为if[0][j]同理。
非零下标:由于最后取最小值,所以为了防止干扰,所有非零下标的初始值应该为INT_MAX
4.遍历顺序
先遍历word1再遍历word2和先遍历word2再遍历word1没有影响,因为可以看成两个字符串的相互经过一系列的操作最后变为相同,其中插入和删除以及替换操作是相互的,所以对遍历顺序没有要求。
时间复杂度
这段代码的时间复杂度为 O(n * m),其中 nword1 的长度,mword2 的长度。
代码实现

class Solution {public int minDistance(String word1, String word2) {int n = word1.length();int m = word2.length();//给word1和word2前面添加空格,方便处理,所以在后面f数组时,长度要+1word1 = ' ' + word1;word2 = ' ' + word2;//创建二维数组并初始化int[][] f = new int[n + 1][m + 1];for(int[] item : f){Arrays.fill(item,Integer.MAX_VALUE);}//处理第一行和第一列for(int i = 0; i <= n;i++) f[i][0] = i;for(int j = 0; j <= m;j++) f[0][j] = j;for(int i = 1;i <= n;i++){for(int j = 1;j <= m;j++){if(word1.charAt(i) == word2.charAt(j)){f[i][j] = f[i - 1][j - 1];}else{f[i][j] = Math.min(f[i - 1][j - 1],Math.min(f[i - 1][j],f[i][j - 1])) + 1;}}}return f[n][m];}
}

139.单词拆分

思路:
本题采用动态规划求解,按照动态规划的解题步骤来逐步分析:
1.dp数组及下标的含义
dp[i]:表示匹配字符串si个字符的结果,布尔类型
最终要求的是dp[n]
2.确定状态转移方程:

if(dp[j - 1] == true && st.contains(s.substring(j, i + 1))){dp[i] = true;
}

由于本题中的dp数组是布尔类型,所以只要判断赋值为true还是赋值为false,判断条件需要依靠第i个字符之前字符的匹配情况。首先从第i个字符开始向前遍历,找到第j个字符,能使得从ji的子串包含在字典中(使用哈希表来存储字典元素,方便比较),此时当dp[j - 1]true时,说明可以匹配成功,dp[i]赋值为true,这个时候就可以直接退出内循环,继续匹配下一个i
3.dp数组的初始化
在最开始需要给字符串前面加一个' ',为了方便边界的处理
dp[0]dp[0]应该初始化为true,这样才能进行字符串的拆分,因为刚开始的拆分时需要依赖dp[0],如果dp[0]false,那么后续的拆分就不能继续进行,下面举个例子:
字符串 s = "leetcode" 和字典 wordDict = ["leet", "code"]。当算法检查是否可以将 s 拆分成字典中的单词时,它会首先检查 s 的前缀 leet,这是可以拆分的。但是,为了标记 s 的前 4 个字符(即 leet)可以被拆分,算法需要依赖于 dp[0]true,因为 leet 是基于空字符串的拆分结果。如果 dp[0]false,那么 dp[4] 也将会是 false,即使 leet 可以被拆分。
4.遍历顺序:
按照字符串的长度顺序,逐个计算 dp[i] 的值。对于每个 i,需要遍历所有可能的拆分点 j(从 1i),并检查 s 的从 ji 的子字符串是否在字典中,并且 dp[j-1] 是否为 true,如果这两个条件都满足,那么 dp[i] 可以被设置为 true
视频讲解点击视频讲解-单词拆分
时间复杂度:
在两层循环中外层循环运行 n 次,内层循环在最坏情况下也可能运行 n 次(当没有任何单词匹配时),所以时间复杂度为O(n ^ 2)
代码实现:

class Solution {public boolean wordBreak(String s, List<String> wordDict) {Set<String> st = new HashSet<>(wordDict);int n = s.length();//给字符串前面加' ',方便边界的判断s = ' ' + s;//定义dp数组,长度为n + 1boolean[] dp = new boolean[n + 1];//初始化dp数组dp[0] = true;for(int i = 1; i <= n; i++){for(int j = i; j > 0; j--){if(dp[j - 1] == true && st.contains(s.substring(j, i + 1))){dp[i] = true;break;}}}return dp[n];}
}

62.不同路径

思路
本题采用动态规划来求解,按照动态规划的解题步骤来逐步分析:
1.dp数组及下标的含义:
f[i][j]:到第i行第j列的格子的路径数
最终要求的是f[m-1][n-1]
2.确定状态转移方程:

f[i][j] = f[i - 1][j] + f[i][j - 1];

由于每次只能向下或者向右移动,所以可以用f(i,j)=f(i-1,j)+f(i,j-1)来计算路径数量,其中f(i-1,j)是指到f[i][j]上一格的数量,f(i,j-1)是指到达f[i][j]左边一格的路径数量,两中情况相加即为到f[i][j]的路径总数量。
3.dp数组的初始化:
dp数组全部的值初始化为1,因为第一列只能通过从第一格向下走,第一列的每一格只有一种走法,第一行只能通过从第一格向右走,第一行的每一格只有一种走法,其他的格子需要依靠其上一个格子和左边的格子的路径数量来确定,全部初始化为1后可以在计算出每一个格子的值后替换,不涉及比较问题,所以为了方便统一进行初始化,全部初始化为1
4.遍历顺序:
只要对整个二维数组全部遍历到即可,先遍历行还是先遍历列没有影响,所以遍历顺序可以是先行后列,也可以是先列后行。
详细的视频讲解点击视频讲解-不同路径。
时间复杂度
由于要对整个二维数组进行遍历计算,所以时间复杂度为O(mn),需要开辟一个二维数组来存储对应格子的路径数,所以空间复杂度也为O(mn)
代码实现

class Solution {public int uniquePaths(int m, int n) {int[][] f = new int[m][n];for(int[] item : f){Arrays.fill(item,1);}for(int i = 1; i < m; i++){for(int j = 1;j < n;j++){f[i][j] = f[i - 1][j] + f[i][j - 1];}}return f[m-1][n-1];}
}

知识点扩展
对数组元素进行初始化设置可以使用以下函数,需要注意的是是的,Arrays.fill(nums,x)方法只能用于一维数组。该方法用指定的值填充整个数组,对于多维数组,每个维度需要分别使用fill方法进行填充。

//将nums数组的元素初始化为x
Arrays.fill(nums,x)

63.不同路径Ⅱ

思路
1.dp数组及下标的含义:
f[i][j]:到第i行第j列的格子的路径数
最终要求的是f[m-1][n-1]
2.确定状态转移方程:

//i>0时 路径数等于到达上面格子的路径数+f[i][j]
f[i][j] += f[i - 1][j];
//j>0时 路径数等于到达左边格子的路径数+f[i][j]
f[i][j] += f[i][j - 1];

本题是62题的加强版,思路基本一样,都用到了动态规划的方法,唯一不同的是本题中多了一个障碍物的限制,有障碍物的网格是不能经过的,所以我们只要加一个判断条件,如果某个网格有障碍物,则将这个网格的可到达路径设置为0即可。
dp数组默认值为0,在循环时首先判断是否有障碍物,有的话直接跳过即可,这里将ij分开处理是因为dp数组的初始化和62题不同,下面第3步中详细解释。
3.dp数组的初始化:
f[0][0]:初始化为1,但是初始化的前提条件是该格没有障碍物,所以在初始化之前会进行判断。
非零下标的格子:本题中每个格子都有可能出现障碍物,所以不能像62题那样统一初始化为1,因为第一行和第一列也可能出现障碍物,因此遍历也需要从0开始,对于第一行和第一列不能直接使用62题中的状态转移方程,需要将两个条件分开来分别处理,注意这两个条件是并列的,都会执行,所以对于里面的格子相当于先整体加了从其上面格向下走的路径数量,再加上从其左边格子向右走的路径数量,本质上和第62题是一样的,只不过这样可以对第一行和第一列方便处理,另一点就是开始时先判断该格是否有障碍物,有的话默认值0不改变,在后续更新其他格子的路径时0也不会有影响。
4.遍历顺序:
只要对整个二维数组全部遍历到即可,先遍历行还是先遍历列没有影响,所以遍历顺序可以是先行后列,也可以是先列后行。
视频讲解点击视频讲解-不同路径Ⅱ。
时间复杂度
时间复杂度和空间复杂度同62题,均为O(mn)
代码实现

class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m = obstacleGrid.length;int n = obstacleGrid[0].length;int[][] f = new int[m][n];for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){//判断是否有障碍物,有则直接跳过if(obstacleGrid[i][j] == 1) continue;//将f[0][0] = 1,方便对第一行和第一列的路径数的计算if(i == 0 && j == 0) f[i][j] = 1;else{//i>0时 路径数等于到达上面格子的路径数+f[i][j]if(i > 0) f[i][j] += f[i - 1][j];//j>0时 路径数等于到达左边格子的路径数+f[i][j]if(j > 0) f[i][j] += f[i][j - 1];}}}return f[m - 1][n - 1];}
}

64.最小路径和

思路
1.dp数组及下标的含义:
f[i][j]:表示从(0,0)到(i,j)的路径和
最终要求的是f[m - 1][n - 1]
2.确定状态转移方程
由于每次只能向右或者向下走,所以可以得到以下的两个动态方程:

//从上面格子向下走
f[i][j] = f[i - 1][j] + grid[i][j]
//从左边格子向右走
f[i][j] = f[i][j - 1] + grid[i][j]

要得到最小路径和,所以两种走法中取最小值即可,所以最终的动态方程为:

f[i][j] = Math.min(f[i - 1][j],f[i][j - 1]) + grid[i][j]

注意最后在代码中不要直接使用该动态方程,因为要分别处理ij为0的情况
3.dp数组的初始化
由于最后要求的是最小值,为了防止干扰,所以需要将dp数组全部初始化为Integer.MAX_VALUE,这样在使用Math.min()比较时就不会受到干扰。
4.遍历顺序
只要对整个二维数组全部遍历到即可,先遍历行还是先遍历列没有影响,所以遍历顺序可以是先行后列,也可以是先列后行。
视频讲解点击视频讲解-最小路径和。
时间复杂度
由于要遍历二维数组得到每一格的路径和,所以时间复杂度为O(mn),开辟一个二维数组来记录每一格的路径和,所以空间复杂度为O(mn)
代码实现

class Solution {public int minPathSum(int[][] grid) {int m = grid.length;int n = grid[0].length;int[][] f = new int[m][n];//初始化为最大值for(int[] item : f){Arrays.fill(item,Integer.MAX_VALUE);}for(int i = 0;i < m;i++){for(int j = 0;j < n;j++){//(0,0)处的值为网格的值,特判处理if(i == 0 && j == 0) f[i][j] = grid[i][j];else{//处理j为0,即第一列的情况if(i > 0) f[i][j] = Math.min(f[i][j],f[i - 1][j] + grid[i][j]);//处理i为0,即第一行的情况if(j > 0) f[i][j] = Math.min(f[i][j],f[i][j - 1] + grid[i][j]);}}}return f[m - 1][n - 1];}
}

70.爬楼梯

思路:
1.dp数组及下标含义
f[i]:表示到第i阶台阶的方法数
最终要求的是f[n]
2.确定状态转移方程

f[i] = f[i - 1] + f[i - 2]

由于一次可以爬1阶或2阶,所以动态方程为f(i)=f(i-1)+f(i-2),其中f(i-1)表示爬到i-1阶的方法数,爬到i需要爬1阶,可以到达i阶,f(i-2)表示爬到i-2阶的方法数,爬到i阶需要爬2阶,可以到达i阶,两者加起来即为爬到i阶的方法数
3.dp数组的初始化
f[1]:初始化为f[1] = 1,第1阶爬1阶到达,有一种方法
f[2]:初始化为f[2] = 2,第2阶可以可以从1阶爬到2阶,也可以直接爬2阶到2,所以有两种方法
4.遍历顺序
本题不需要区分遍历顺序,在这里对另一个容易出错的点进行说明,需要注意的是遍历时从3开始,方法数的计算依赖于爬到前一个和前两个阶梯的方法数,这里dp数组创建时的长度要设置为n+2,否则会发生溢出错误,一般情况下,如果dp数组下标从0开始,那么数组长度设置为n+1,否则计算f[n]时会溢出(如果长度为n,那么下标只能到n - 1),在该题中,由于没有第0个台阶,下标从1开始,所以长度设置为n+2,其他情况依次类推增加dp数组的长度。
视频讲解点击视频讲解-爬楼梯。
时间复杂度
时间复杂度为O(n),由于开辟了一个数组来保存爬到每一阶的方法数,空间复杂度为O(n)
代码实现

class Solution {public int climbStairs(int n) {//数组大小设置为n+2,防止溢出int[] f = new int[n + 2];f[1] = 1;f[2] = 2;for(int i = 3;i <= n;i++){f[i] = f[i - 1] + f[i - 2];}return f[n];}
}

121.买卖股票的最佳时机

思路1
股票问题是动态规划的典型题,按照动态规划的解题步骤来逐步分析:
1.dp数组及下标的含义
dp[i][0]:第i天持有股票获得的最大现金,需要注意的是这里并不表示第i天买入,可能在第i天前就买入了,第i天还持有没有卖出
dp[i][1]:第i天不持有股票获得的最大现金,需要注意的是这里并不表示第i天卖出,可能在第i天前就卖出了,此时第i天是不持有的,因为只能进行一次买卖
最终要求的是dp[n - 1][1]
2.确定状态转移方程

dp[i][0] = Math.max(dp[i - 1][0],-prices[i]);
dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][0] + prices[i]);

dp[i][0]的计算包括两种情况:
(1)第i - 1天也是持有的,那么说明在这两天前的某一天买入的,此时dp[i][0]持有的现金和dp[i - 1][0]是一样的,因此dp[i][0] = dp[i - 1][0]
(2)第i - 1天不持有,那么说明是在第i天买入的,所以可以此时dp[i][0] = -prices[i]
dp[i][1]的计算包括两种情况:
(1)第i - 1天也是不持有的,那么说明在这两天前的某一天就卖出了,此时dp[i][0]持有的现金和dp[i - 1][1]是一样的,因此dp[i][0] = dp[i - 1][1]
(2)第i - 1天还是持有状态,那么说明是在第i天卖出的,所以可以此时dp[i][0] = dp[i - 1][0] + prices[i]
3.dp数组的初始化
dp[0][0] = -prices[0]:第0天持有,说明第0天买入,所以初始值为-prices[0]
dp[0][1] = 0:第0天不持有,因此此时拥有现金为0
其他情况默认初始值为0,后续需要求最大值,初始值为0不会在比较时有影响。
4.遍历顺序
因为后一天的状态依赖于前一天,所以从前向后遍历,并且第0天已经初始化,所以从1开始遍历。
视频讲解点击视频讲解-买卖股票的最佳时机
时间复杂度:
时间复杂度为O(n),只对数组进行了一次遍历。
代码实现:

class Solution {public int maxProfit(int[] prices) {int n = prices.length;int[][] dp = new int[n + 1][2];dp[0][0] = -prices[0];dp[0][1] = 0;for(int i = 1; i < n; i++){dp[i][0] = Math.max(dp[i - 1][0],-prices[i]);dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][0] + prices[i]);}return dp[n - 1][1];}
}

思路2:
本题还可以使用贪心算法来解决,通过找到第i天之前的最小价格,并取后续每一天减去之前最小价格的最大值即可。视频解析点击视频解析-买卖股票的最佳时机。
时间复杂度:
时间复杂度为O(n)n为数组长度,只对数组进行了一次遍历。
代码实现:

class Solution {public int maxProfit(int[] prices) {int ans = 0;int minPrice = prices[0];for(int i = 0; i < prices.length; i++){ans = Math.max(ans, prices[i] - minPrice);minPrice = Math.min(minPrice, prices[i]);}return ans;}  
}

152.乘积最大子数组

思路:
本题如果使用动态规划做会比较复杂,但是可以用到动态规划的思想,由于需要求得乘积最大的连续子数组,可能会出现负负得正的情况,所以需要同时维护一个最大值和最小值,每次循环都需要更新最大值和最小值,并更新ans的值,最后返回ans即可。
注:本题使用int类型或者long类型都会有一个测试用例([0,10,10,10,10,10,10,10,10,10,-10,10,10,10,10,10,10,10,10,10,0])不通过,因为会发生整型溢出,但其实按照题目中的“测试用例的答案是一个 32-位 整数”这个测试用例是不合法的,在这里我使用了double类型可以通过全部的测试用例,是因为在这个特定的测试用例中,最大的乘积是在没有发生整数溢出的情况下计算得出的。即使使用 double 类型,也不会影响到最终结果的精度,因为最终的结果仍然是一个整数,使用 double 类型可以避免在乘法运算中可能发生的整数溢出问题,因为它可以表示比 long 类型更大范围的数值。然而,这并不意味着 double 类型总是适合所有情况,因为 double 类型在表示大数时可能会有精度损失。在这个特定的例子中,由于最终结果是一个整数,所以使用 double 类型不会导致精度损失,这种情况应该使用 Java 中的 BigInteger 类来处理,有兴趣的伙伴可以去看看,我后续会补上相关的内容。
视频讲解点击视频讲解-乘积最大子数组。
时间复杂度:
时间复杂度是 O(n),其中 n 是数组 nums 的长度。
代码实现:

class Solution {public int maxProduct(int[] nums) {double ans = nums[0];double preMax = nums[0];double preMin = nums[0];for(int i = 1; i < nums.length; i++){double a = preMax * nums[i];double b = preMin * nums[i];preMax = Math.max((double)nums[i],Math.max(a,b));preMin = Math.min((double)nums[i],Math.min(a,b));ans = Math.max(ans,preMax);}return (int)ans;}
}

相关文章:

leetCode-hot100-动态规划专题

动态规划 动态规划定义动态规划的核心思想动态规划的基本特征动态规划的基本思路例题322.零钱兑换53.最大子数组和72.编辑距离139.单词拆分62.不同路径63.不同路径Ⅱ64.最小路径和70.爬楼梯121.买卖股票的最佳时机152.乘积最大子数组 动态规划定义 动态规划&#xff08;Dynami…...

【算法笔记自学】入门篇(2)——算法初步

4.1排序 自己写的题解 #include <stdio.h> #include <stdlib.h>void selectSort(int A[], int n) {for(int i 0; i < n - 1; i) { // 修正索引范围int k i;for(int j i 1; j < n; j) { // 修正索引范围if(A[j] < A[k]) {k j;}}if (k ! i) { // 仅在…...

Redis基础教程(六):redis 哈希(Hash)

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…...

鸿蒙开发设备管理:【@ohos.account.appAccount (应用帐号管理)】

应用帐号管理 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 导入模…...

java项目自定义打印日志,打印请求方式,参数用时等

1.相关依赖 <!-- 私人工具包 --><dependency><groupId>cn.changeforyou</groupId><artifactId>location</artifactId><version>1.13-SNAPSHOT</version></dependency><!-- hutool工具依赖 --><dependency>…...

03:EDA的进阶使用

使用EDA设计一个38译码器电路和245放大电路 1、38译码器1.1、查看74HC138芯片数据1.2、电路设计 2、245放大电路2.1、查看数据手册2.2、设计电路 3、绘制PCB3.1、导入3.2、放置3.3、飞线3.4、特殊方式连接GND3.5、泪滴3.6、配置丝印和划分区域3.7、添加typc接口供电 1、38译码器…...

Linux/Unix系统指令:(tar压缩和解压)

tar 是一个在Linux和Unix系统中用于创建和处理归档文件的命令。 下面是tar命令的详细用法&#xff0c;包括它的所有常用选项和一些示例。 基本语法 tar [选项] [归档文件] [文件或目录]常用选项 基本操作 -c&#xff1a;创建一个新的归档文件&#xff08;create&#xff09…...

MySQL 日期和时间函数知识点总结

引言 在数据库管理和开发中&#xff0c;日期查询是一项基础且频繁使用的功能。MySQL提供了丰富的日期和时间处理函数&#xff0c;使得我们能够灵活地进行日期查询和数据处理。本文将详细介绍MySQL中关于日期查询的几个重要知识点&#xff0c;并附上具体的案例。 1. MySQL的日…...

鸿蒙登录页面及页面跳转的设计

目录 任务目标任务分析任务实施1.新建工程项目HMLogin2.设计登录页面Index.visual3.设计第二个页面SecondPage4.修改Index.ets代码5.修改SecondPage.ets代码6.运行工程 任务目标 设计一个简单的登录页面&#xff0c;要求可以将第一页的登录信息&#xff0c;传递到第二个页面&a…...

【居家养老实训室】:看中医保健在养老中的应用

本文以居家养老实训室为视角&#xff0c;深入探讨了中医保健在养老中的应用。通过对中医保健理念、常用方法以及在居家养老中的具体实践进行分析&#xff0c;阐述了其在改善老年人健康状况、提高生活质量方面的重要作用。同时&#xff0c;也指出了目前应用中存在的问题&#xf…...

【区块链+基础设施】区块链服务网络 BSN | FISCO BCOS应用案例

BSN&#xff08;Blockchain-based Service Network&#xff0c;区块链服务网络&#xff09;是一个跨云服务、跨门户、跨底层框架&#xff0c;用于部 署和运行各类区块链应用的全球性基础设施网络&#xff0c;旨在为开发者提供低成本和技术互通的区块链一站式服务。 2019 年 12…...

六、快速启动框架:SpringBoot3实战-个人版

六、快速启动框架&#xff1a;SpringBoot3实战 文章目录 六、快速启动框架&#xff1a;SpringBoot3实战一、SpringBoot3介绍1.1 SpringBoot3简介1.2 系统要求1.3 快速入门1.4 入门总结回顾复习 二、SpringBoot3配置文件2.1 统一配置管理概述2.2 属性配置文件使用2.3 YAML配置文…...

SA 注册流程

目录 1. UE开机后按照3GPP TS 38.104定义的Synchronization Raster搜索特定频点 2.UE尝试检测PSS/SSS&#xff0c;取得下行时钟同步&#xff0c;并获取小区的PCI&#xff1b;如果失败则转步骤1搜索下一个频点&#xff1b;否则继续后续步骤&#xff1b; 3.解析Mib&#xff0c;…...

图像的灰度直方图

先来认识一下灰度直方图&#xff0c;灰度直方图是图像灰度级的函数&#xff0c;用来描述每个灰度级在图像矩阵中的像素个数或者占有率。接下来使用程序实现直方图&#xff1a; 首先导入所需的程序包&#xff1a; In [ ]: import cv2 import numpy as np import matplotlib…...

软件测试面试题:Redis的五种数据结构,以及使用的场景是什么?

字符串&#xff08;Strings&#xff09;&#xff1a;简单直接&#xff0c;就像记事本一样&#xff0c;用来存储和快速访问简单的数据&#xff0c;比如缓存网页或者保存用户会话信息。 列表&#xff08;Lists&#xff09;&#xff1a;有序的数据集合&#xff0c;适合用来存储按…...

Java后端每日面试题(day1)

目录 JavaWeb三大组件依赖注入的方式Autowire和Resurce有什么区别&#xff1f;Spring Boot的优点Spring IoC是什么&#xff1f;说说Spring Aop的优点Component和Bean的区别自定义注解时使用的RetentionPolicy枚举类有哪些值&#xff1f;如何理解Spring的SPI机制&#xff1f;Spr…...

AI与测试相辅相成

AI助力软件测试 1.AI赋能软件测试 使用AI工具来帮助测试人员提高测试效率&#xff0c;提供缺陷分析和缺陷预测。 语法格式 设定角色 具体指示 上下文格式 例: 角色&#xff1a;你是一个测试人员 内容&#xff1a;请帮我生成登录案例的测试用例 ​ 1.只有输入正确账号和密码才…...

搜索+动态规划

刷题刷题刷题刷题 ​​​​​​​​​​​​​​Forgery - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路&#xff1a; 需要两个数组&#xff0c;一个数组全部初始化为".",另一个数组输入数据&#xff0c;每碰到一个“.”就进行染色操作&#xff0c;将其周围的…...

strcpy,srtcmp,strlen函数漏洞利用

strcpy,srtcmp,strlen函数漏洞利用 strcpy strcpy函数用于将字符串复制到另一个指针指向的空间中&#xff0c;遇到空字符 **b’x\00’**时停止&#xff0c;&#xff1a; 所以可以利用 strcpy不检查缓冲区 的漏洞&#xff08;构造的字符串要以\0结尾&#xff09;&#xff0c;…...

SketchUp + Enscape+ HTC Focus3 VR

1. 硬件: 设备连接 2. 软件: 安装steam steamVR Vive Business streaming 3. 操作: 双方登录steam 账号,然后带上头盔,用手柄在HTC Focus3 安装 串流软件,选择串流软件,在Enscape中选择 VR 模式即可 4.最终效果: SketchUp Enscape HTC Focus 3 VR 实时预览_哔哩哔哩_bi…...

推荐3款Windows系统的神级软件,免费、轻量、绝对好用!

DiskView DiskView是一款用于管理和查看磁盘空间的工具&#xff0c;它集成了于微软的Windows操作系统资源管理器中&#xff0c;以显示直观的磁盘空间使用情况。该软件通过生成图形化地图&#xff0c;帮助用户组织和管理大量文件和文件夹&#xff0c;从而高效地管理磁盘空间。用…...

-bash: /snap/bin/docker: 没有那个文件或目录

-bash: /snap/bin/docker: 没有那个文件或目录 解决办法 export PATH$PATH:/usr/bin/docker然后&#xff0c;重新加载配置文件 source ~/.bashrc...

[深度学习]卷积理解

单通道卷积 看这个的可视化就很好理解了 https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md 多通道卷积 当输入有多个通道时,卷积核需要拥有相同的通道数. 假设输入有c个通道,那么卷积核的每个通道分别于相应的输入数据通道进行卷积,然后将得到的特征图对…...

基于aardio web.view2库和python playwright包的内嵌浏览器自动化操作

通过cdp协议可以实现playwright操控webview。 新建Python窗口工程 修改pip.aardio 修改pip.aardio&#xff0c;并执行&#xff0c;安装playwright。 //安装模块 import process.python.pip; //process.python.path "python.exe";/* 安装模块。 参数可以用一个字…...

《数据仓库与数据挖掘》 总复习

试卷组成 第一章图 第二章图 第三章图 第四章图 第五章图 第六章图 第九章图 第一章 DW与DM概述 &#xff08;特点、特性&#xff09; DB到DW 主要特征 &#xff08;1&#xff09;数据太多&#xff0c;信息贫乏&#xff08;Data Rich&#xff0c; Information Poor)。 &a…...

EtherCAT主站IGH-- 8 -- IGH之domain.h/c文件解析

EtherCAT主站IGH-- 8 -- IGH之domain.h/c文件解析 0 预览一 该文件功能`domain.c` 文件功能函数预览二 函数功能介绍1. `ec_domain_init`2. `ec_domain_clear`3. `ec_domain_add_fmmu_config`4. `ec_domain_add_datagram_pair`5. `ec_domain_finish`6. `ecrt_domain_reg_pdo_en…...

《昇思25天学习打卡营第10天|使用静态图加速》

文章目录 今日所学&#xff1a;一、背景介绍1. 动态图模式2. 静态图模式 三、静态图模式的使用场景四、静态图模式开启方式1. 基于装饰器的开启方式2. 基于context的开启方式 总结&#xff1a; 今日所学&#xff1a; 在上一集中&#xff0c;我学习了保存与加载的方法&#xff…...

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(二十二)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 32 节&#xff09; P32《31.通知-基础通知》 基础文本类型通知&#xff1a;briefText 没有用&#xff0c;写了也白写。 长文本类型…...

六西格玛绿带培训如何告别“走过场”?落地生根

近年来&#xff0c;六西格玛绿带培训已经成为了众多企业提升管理水平和员工技能的重要途径。然而&#xff0c;不少企业在实施六西格玛绿带培训时&#xff0c;往往陷入形式主义的泥潭&#xff0c;导致培训效果大打折扣。那么&#xff0c;如何避免六西格玛绿带培训变成“走过场”…...

Linux——提取包文件到指定目录,命令解释器-shell,type 命令

- 提取包文件到指定目录 bash tar xf/-xf/-xzf 文件名.tar.gz [-C 目标路径] tar xf/-xf/-xjf 文件名.tar.bz2 [-C 目标路径] tar xf/-xf/-xJf 文件名.tar.xz [-C 目标路径] ### 示例 - 将/etc下所有内容打包压缩到/root目录中 bash [rootserver ~]# tar -cvf taretc…...

【最详细】PhotoScan(MetaShape)全流程教程

愿天下心诚士子&#xff0c;人人会PhotoScan&#xff01; 愿天下惊艳后辈&#xff0c;人人可剑开天门&#xff01; 本教程由CSDN用户CV_X.Wang撰写&#xff0c;所用数据均来自山东科技大学视觉测量研究团队&#xff0c;特此鸣谢&#xff01;盗版必究&#xff01; 一、引子 Ph…...

Excel多表格合并

我这里一共有25张表格: 所有表的表头和格式都一样,但是内容不一样: 现在我要做的是把所有表格的内容合并到一起,研究了一下发现WPS的这项功能要开会员的,本来想用代码撸出来的,但是后来想想还是找其他办法,后来找到"易用宝"这个插件,这个插件可以从如下地址下载:ht…...

AI作画工具深度剖析:Midjourney vs. Stable Diffusion (SD)

在人工智能技术的推动下&#xff0c;艺术创作的边界被不断拓宽&#xff0c;AI作画工具成为数字艺术家与创意人士的新宠。其中&#xff0c;Midjourney与Stable Diffusion&#xff08;SD&#xff09;作为当前领域的佼佼者&#xff0c;以其独特的算法机制、丰富的功能特性及高质量…...

ASP.NET Core Blazor 5:Blazor表单和数据

本章将描述 Blazor 为处理 HTML 表单提供的特性&#xff0c;包括对数据验证的支持。 1 准备工作 继续使用上一章项目。   创建 Blazor/Forms 文件夹并添加一个名为 EmptyLayout.razor 的 Razor 组件。本章使用这个组件作为主要的布局。 inherits LayoutComponentBase<div …...

C++ 仿QT信号槽二

// 实现原理 // 每个signal映射到bitset位&#xff0c;全集 // 每个slot做为signal的bitset子集 // signal全集触发&#xff0c;标志位有效 // flip将触发事件队列前置 // slot检测智能指针全集触发的标志位&#xff0c;主动运行子集绑定的函数 // 下一帧对bitset全集进行触发清…...

联合概率密度函数

目录 1. 什么是概率密度由联合概率密度求概率参考链接 1. 什么是概率密度 概率密度到底在表达什么&#xff1f; 外卖在20-40分钟内送达的概率 随机变量落在[20,40]之间的概率。下图中&#xff0c;对总面积做规范化处理&#xff0c;令总面积1&#xff0c; f ( x ) f(x) f(x)则成…...

【Java10】成员变量与局部变量

Java中的变量只有两种&#xff1a;成员变量和局部变量。 和C不同&#xff0c;没有全局变量了。 成员变量&#xff0c;field&#xff0c;我习惯称之为**”属性“**&#xff08;但这些年&#xff0c;因为attribute更适合被叫做属性&#xff0c;所以渐渐不这么叫了&#xff09;。 …...

Spring Session与分布式会话管理详解

随着微服务架构的普及&#xff0c;分布式系统中的会话管理变得尤为重要。传统的单点会话管理已经不能满足现代应用的需求。本文将深入探讨Spring Session及其在分布式会话管理中的应用。 什么是Spring Session&#xff1f; Spring Session是一个用于管理HttpSession的Spring框…...

从0开始学习pyspark--Spark DataFrame数据的选取与访问[第5节]

在PySpark中&#xff0c;选择和访问数据是处理Spark DataFrame的基本操作。以下是一些常用的方法来选择和访问DataFrame中的数据。 选择列&#xff08;Selecting Columns&#xff09;: select: 用于选择DataFrame中的特定列。selectExpr: 用于通过SQL表达式选择列。 df.select…...

Fastjson首字母大小写问题

1、问题 使用Fastjson转json之后发现首字母小写。实体类如下&#xff1a; Data public class DataIdentity {private String BYDBSM;private String SNWRSSJSJ;private Integer CJFS 20; } 测试代码如下&#xff1a; public static void main(String[] args) {DataIdentit…...

GuLi商城-商品服务-API-品牌管理-效果优化与快速显示开关

<template><div class"mod-config"><el-form :inline"true" :model"dataForm" keyup.enter.native"getDataList()"><el-form-item><el-input v-model"dataForm.key" placeholder"参数名&qu…...

如何成为C#编程高手?

成为C#编程高手需要时间、实践和持续的学习。以下是一些建议&#xff0c;可以帮助你提升C#编程技能&#xff1a; 深入理解基础知识&#xff1a; 确保你对C#的基本语法、数据类型、控制结构、面向对象编程&#xff08;OOP&#xff09;原则有深刻的理解。学习如何使用Visual Stud…...

SpringBoot学习06-[SpringBoot与AOP、SpringBoot自定义starter]

SpringBoot自定义starter SpringBoot与AOPSpringBoot集成Mybatis-整合druid在不使用启动器的情况下&#xff0c;手动写配置类进行整合使用启动器的情况下,进行整合 SpringBoot启动原理源码解析创建SpringApplication初始化SpringApplication总结 启动 SpringBoot自定义Starter定…...

Maven - 在没有网络的情况下强制使用本地jar包

文章目录 问题解决思路解决办法删除 _remote.repositories 文件代码手动操作步骤验证 问题 非互联网环境&#xff0c;无法从中央仓库or镜像里拉取jar包。 服务器上搭建了一套Nexus私服。 Nexus私服故障&#xff0c;无法连接。 工程里新增了一个Jar的依赖&#xff0c; 本地仓…...

JAVA--JSON转换工具类

JSON转换工具类 import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackso…...

每日复盘-20240705

今日关注&#xff1a; 20240705 六日涨幅最大: ------1--------300391--------- 长药控股 五日涨幅最大: ------1--------300391--------- 长药控股 四日涨幅最大: ------1--------300391--------- 长药控股 三日涨幅最大: ------1--------300391--------- 长药控股 二日涨幅最…...

MySQL 一些用来做比较的函数

目录 IF&#xff1a;根据不同条件返回不同的值 CASE&#xff1a;多条件判断&#xff0c;类似于Switch函数 IFNULL&#xff1a;用于检查一个值是否为NULL&#xff0c;如果是&#xff0c;则用指定值代替 NULLIF&#xff1a;比较两个值&#xff0c;如果相等则返回NULL&#xff…...

一个使用率超高的大数据实验室是如何练成的?

厦门大学嘉庚学院“大数据应用实训中心”&#xff08;以下简称“实训中心”&#xff09;自2022年建成以来&#xff0c;已经成为支撑“大数据专业”复合型人才培养的重要支撑&#xff0c;目前实训中心在专业课程实验教学、项目实训、数据分析类双创比赛、毕业设计等方面都有深入…...

Chiasmodon:一款针对域名安全的公开资源情报OSINT工具

关于Chiasmodon Chiasmodon是一款针对域名安全的公开资源情报OSINT工具&#xff0c;该工具可以帮助广大研究人员从各种来源收集目标域名的相关信息&#xff0c;并根据域名、Google Play应用程序、电子邮件地址、IP地址、组织和URL等信息进行有针对性的数据收集。 该工具可以提…...

如何在Java中实现PDF生成

如何在Java中实现PDF生成 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 在软件开发和企业应用中&#xff0c;生成PDF文档是一项常见的需求。Java作为一种强大…...