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

【代码随想录】-动态规划专题

文章目录

  • 理论基础
  • 斐波拉契数列
  • 爬楼梯
  • 使用最小花费爬楼梯
  • 不同路径
  • 不同路径 II
  • 整数拆分
  • 不同的二叉搜索树
  • 背包问题——理论基础
    • 01背包
    • 二维dp数组01背包
    • 一维数组(滚动数组)
    • 装满背包
  • 分割等和子集
  • 最后一块石头的重量 II
  • 目标和
  • 一和零
  • 完全背包
  • 零钱兑换 II
  • 组合总和 Ⅳ
  • 爬楼梯(dp)
  • 零钱兑换
  • 完全平方数
  • 背包问题总结
    • 背包递推公式
    • 遍历顺序
  • 单词拆分
  • 打家劫舍
  • 打家劫舍2
  • 打家劫舍3
  • 股票问题
    • 买卖股票的最佳时机
    • 买卖股票的最佳时机2
    • 买卖股票的最佳时机3
    • 买卖股票的最佳时机4
    • 最佳买卖股票时机含冷冻期
    • 买卖股票的最佳时机含手续费
    • 总结
  • 最长递增子序列
  • 最长连续递增序列
  • 最长重复子数组
  • 最长公共子序列
  • 不相交的线
  • 最大子数组和
  • 不同的子序列
  • 两个字符串的删除操作
  • 编辑距离
  • 回文子串
  • 最长回文子序列

理论基础

动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。

所以动态规划中每一个状态一定是由上一个状态推导出来的这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的

例如:有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

动态规划中dp[j]是由dp[j-weight[i]]推导出来的,然后取max(dp[j], dp[j - weight[i]] + value[i])。

但如果是贪心呢,每次拿物品选一个最大的或者最小的就完事了,和上一个状态没有关系。

所以贪心解决不了动态规划的问题。

动态规划问题,将拆解为如下五步曲:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

动态规划在debug时,找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!

斐波拉契数列

作为入门动态规划,讲的很好:代码随想录 (programmercarl.com)

/*** 509. 斐波那契数* @param n* @return*/
public int fib(int n) {if(n == 0) return 0;int[] dp = new int[n+1];dp[0] = 0;dp[1] = 1;for (int i = 2; i < dp.length; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];
}

爬楼梯

/*** 70. 爬楼梯* @param n* @return*/
public int climbStairs(int n) {if(n <=2) return n;int[] dp = new int[n + 1];dp[1] = 1;dp[2] = 2;for (int i = 3; i < dp.length; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];
}

使用最小花费爬楼梯

/*** leetcode-746. 使用最小花费爬楼梯* @param cost* @return*/
public int minCostClimbingStairs(int[] cost) {int[] dp = new int[cost.length + 1];//到达第i台阶所花费的最少体力为dp[i]dp[0] = 0;dp[1] = 0;for (int i = 2; i <= cost.length; i++) { //数组最后元素的后一个才是要的答案//两种跳法// 1. dp[i - 1] 跳到 dp[i] :  cost[i-1]+dp[i-1]// 2.dp[i - 2] 跳到 dp[i] :cost[i-2]+dp[i-2]dp[i] = Math.min(cost[i - 1] + dp[i - 1], cost[i - 2] + dp[i - 2]);}return dp[cost.length];
}

不同路径

/*** 62. 不同路径** @param m* @param n* @return*/public int uniquePaths(int m, int n) { // m是行 n是列int[][] dp = new int[m][n];
//        dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。// dp[i][j] 由上 左两个方向推导而来 dp[i][j] = dp[i-1][j] + dp[i][j-1]for (int i = 0; i < m; i++) { //第一行 第一列肯定是只有一种可能dp[i][0] = 1;}for (int i = 0; i < n; i++) {dp[0][i] = 1;}for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {dp[i][j] = dp[i-1][j] + dp[i][j-1];}}return dp[m-1][n-1];}

不同路径 II

 /*** 63. 不同路径 II* @param obstacleGrid* @return*/public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m = obstacleGrid.length;int n = obstacleGrid[0].length;int[][] dp = new int[m][n];
//        dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。// dp[i][j] 由上 左两个方向推导而来 dp[i][j] = dp[i-1][j] + dp[i][j-1]//第一行 第一列肯定是只有一种可能 中间要是有障碍物就过不去了for (int i = 0; i < m&& obstacleGrid[i][0] == 0;  i++) {dp[i][0] = 1;}for (int i = 0; i < n&&obstacleGrid[0][i] == 0; i++) {dp[0][i] = 1;}for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {if (obstacleGrid[i][j] == 1) continue;dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}return dp[m - 1][n - 1];}

整数拆分

/*** leetcode-343. 整数拆分* @param n* @return*/
public int integerBreak(int n) {/*当 n≥2时,可以拆分成至少两个正整数的和。令 k是拆分出的第一个正整数,则剩下的部分是 n−k,n−k 可以不继续拆分,或者继续拆分成至少两个正整数的和*/int[] dp = new int[n+1]; // 拆解数字i,可以得到的最大乘积为dp[i]。dp[1] = 1;dp[2] = 1;for (int i = 3; i <= n; i++) {for (int j = 1; j <= i-1 ; j++) { // j在动态变化 保留最大值dp[i] = Math.max(dp[i],Math.max(j*dp[i-j],j*(i-j)));}}return dp[n];
}

不同的二叉搜索树

/*** leetcode-96. 不同的二叉搜索树* @param n* @return*/
public int numTrees(int n) {int[] dp = new int[n + 1];dp[0] = 0;dp[1] = 1;for (int i = 2; i <= n; i++) {for (int j = 1; j <= i; j++) {dp[i] += dp[j - 1] * dp[i - j];}}return dp[n];
}

背包问题——理论基础

01背包,和完全背包应对面试,最多可以再来一个多重背包。

在这里插入图片描述

背包问题的理论基础重中之重是01背包,一定要理解透!

leetcode上没有纯01背包的问题,都是01背包应用方面的题目。

01背包

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是o(2n)o(2^n)o(2n),这里的n表示物品数量。

暴力解法是指数级别的时间复杂度。

举例:

背包的最大容量是4:

物品:

在这里插入图片描述

背包能背的物品最大价值是多少?

二维dp数组01背包

  1. 确定dp数组以及下标的含义

    二维数组的写法:

    dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

在这里插入图片描述

​ 横i,竖j

  1. 确定递推公式

    j容量下到前i个物品的讨论,有两种情况:

    • 选择不装第i个物品,则退回到i-1行,直接选择dp[i-1][j]作为该情况下最大价值()
    • 装第i个物品,由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值(左上, j - weight【i】,这里可以理解为背包需要留出这个物品i的容量才可以放物品i)

    当i放进去时,那么这时候整个物品集就被分成两部分,1到i-1和第i个,而这是i是确定要放进去的,那么就把j空间里的wi给占据了,只剩下j-wi的空间给前面i-1,那么只要这时候前面i-1在j-wi空间里构造出最大价值,即dp【i-1】【j-wi】,再加上此时放入的i的价值vi,就是dpij了

    所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

    这样做的原因主要就在于,表dp[i][j]以前的所有数据都已经代表了价值最大的最佳情况。

    • 装i

      0-1背包问题下每个物品只能放一件,所以用j-i物品的体积=f,查表dp[i-1][f]直接得到,装i时前f空间的最大价值。所以装i的最大价值就等于dp[i][f]+value[i]

    • 不装i

    不装i即需要到前i-1个里面选,也就是前i-1行j背包容量下的最大价值,同理,由于前面都已经是最优解,直接查表dp[i-1][j]就是不装i条件下的最大价值

    因此比较两者价值大小选大者即可再次得到dp[i][j]情况下的最优解

代码: 先遍历物品 先遍历背包容量 均可。

// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品for(int j = 0; j <= bagweight; j++) { // 遍历背包容量if (j < weight[i]) dp[i][j] = dp[i - 1][j]; else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);}
}

……

一维数组(滚动数组)

让数组从二维降到一维,即当前层值和上一层i-1的值共存,当当前层遍历完毕,当前层的所有值都会把一维数组上一层的值全部覆盖掉。(去掉了维度)

为什么要先遍历物品再遍历背包?

本质上二维数组和一维数组差不多,计算某一个值需要使用到上一层的值,在二维数组的图中,先遍历物品可以得出整个i-1层的数据,而先遍历背包每次只有一个i-1层的数据,并且每一层都会把上一个覆盖掉,实际计算中需要的是一整层的数据。

为什么第二层循环(遍历背包容量)要从后往前遍历?

本质上二维数组和一维数组差不多,不过二维数组没有覆盖数据。采用一维数组时,如果先计算前面的(下标前后),在做后面的计算时,需要使用上一层的数据(只会使用下标比自己小的),而前面因为先做计算已经把上一层中后面计算需要使用的数据覆盖掉,无法获取到上一层原来的值,因此不能从前往后遍历。而从后往前遍历,计算下标大的值的时候,下标小的上一层值未被覆盖,可以使用,这样就不会出现需要使用的数据被覆盖掉的问题。

for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}
}

装满背包

**求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]];**dp[0] 初始化为1 ,dp[j]其他下标对应的数值应该初始化为0。

在求装满背包有几种方案的时候,认清遍历顺序是非常关键的。

如果求组合数就是外层for循环遍历物品,内层for遍历背包

如果求排列数就是外层for遍历背包,内层for循环遍历物品

例如,nums[0] = 1,nums[1] = 5,先遍历物品是先把1加入计算,再把5加入计算,得到的方法数量只会是{1, 5}这种情况。而不会出现{5, 1}的情况。所以先遍历物品求的是组合。

若先遍历背包,背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。所以先遍历背包求的是排列。

分割等和子集

/*** 416. 分割等和子集** @param nums* @return*/
public boolean canPartition(int[] nums) {//类比于01背包 在所给定的数组中挑选出一些数 总和为所有元素总和的一半//nums[i]相当于01背包中的weight[i]//背包要放入的商品(集合里的元素)重量为元素的数值,价值也为元素的数值if (nums == null || nums.length == 0) return false;int sum = 0;for (int num : nums) {sum += num;}if (sum % 2 != 0) return false; // 奇数,肯定不成立int target = sum / 2;int[] dp = new int[target + 1];for (int i = 0; i < nums.length; i++) {/* for (int j = target; j > 0; j--) {if (j >= nums[i]) {dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);}else{dp[j] = dp[j];}*///简略写法 j < w[i]时候什么都不做即可。换句话说,只需要遍历到j >= w[i]for (int j = target; j >= nums[i]; j--) {dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);}}return dp[target] == target;
}

最后一块石头的重量 II

/*** leetcode-1049. 最后一块石头的重量 II* @param stones* @return*/
public int lastStoneWeightII(int[] stones) {// 问题可以转化为 把一堆石头分成两堆,求两堆石头重量差最小值// 若要差值最小 则两堆石头的重量要尽量接近所有石头总和的一半//dp[target]里是容量为target的背包所能背的最大重量。//一堆石头的总重量是dp[target],另一堆就是sum - dp[target]int sum = 0;for (int stone : stones) {sum += stone;}int target = sum / 2;int[] dp = new int[target + 1];for (int i = 0; i < stones.length; i++) {for (int j = target; j >= stones[i]; j--) {dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);}}// target是sum向下取整 sum - dp[target] 一定是大于等于dp[target]return sum - 2*dp[target];
}

目标和

/*** leetcode-494. 目标和** @param nums* @param target* @return*/
public int findTargetSumWays(int[] nums, int target) {// 添加加号的数和为pos 添加减号的数和为neg 元素总和为sum// pos+neg=total pos−neg=target// 进一步:pos=(total+target)/2 neg=(total−target)/2// total和target均已知 此时问题变为在数组中找到和为pos的组合// 题目中的每个1 只能用一次 所以是01背包int sum = 0;for (int num : nums) {sum += num;}if (sum < target) return 0;if ((sum + target) % 2 != 0) return 0; //不整除//dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法int[] dp = new int[target + 1];int pos = (target + sum) / 2;dp[0] = 1; // 填满容量为0的背包有1中方法for (int i = 0; i < nums.length; i++) {for (int j = target; j > nums[i]; j++) {dp[j] += dp[j - nums[j]];}}return dp[pos];
}

一和零

/*** leetcode-474. 一和零** @param strs* @param m* @param n* @return*/
public int findMaxForm(String[] strs, int m, int n) {//字符串数组中的元素看作物品 物品的重量有两个维度// m 和 n相当于是一个背包,两个维度的背包int zeroNum;int oneNum;//dp[i][j]表示i个0和j个1时的最大子集int[][] dp = new int[m + 1][n + 1];for (String str : strs) { // 先遍历物品zeroNum = 0;oneNum = 0;for (char c : str.toCharArray()) {if (c == '0') zeroNum++;else oneNum++;}// 再遍历背包 且倒序for (int i = m; i >= zeroNum; i--) {for (int j = n; j >= oneNum; j--) {dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);}}}return dp[m][n];
}

完全背包

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件(也就是可以放入背包多次)

01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。而完全背包的物品是可以添加多次的,所以要从小到大去遍历,即:

// 先遍历物品,再遍历背包 均可
for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}
}
// 先遍历背包,再遍历物品
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量for(int i = 0; i < weight.size(); i++) { // 遍历物品if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}
}

从小到大遍历:由于一个物品可以被选择多次,更新dp[j]时,可能因为放入物品i而发生变化。

https://blog.csdn.net/mch2869253130/article/details/81906962

在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!

零钱兑换 II

/*** leetcode-518. 零钱兑换 II** @param amount* @param coins* @return*/
public int change(int amount, int[] coins) {//dp[j]:凑成总金额j的货币组合数为dp[j]int[] dp = new int[amount + 1];dp[0] = 1;for (int i = 0; i < coins.length; i++) {for (int j = coins[i]; j <= amount; j++) { //完全背包,正序dp[j] += dp[j - coins[i]];}}return dp[amount];
}

组合总和 Ⅳ

/*** leetcode-377. 组合总和 Ⅳ** @param nums* @param target* @return*/
public int combinationSum4(int[] nums, int target) {//和为j的排列数dp[j]int[] dp = new int[target + 1];dp[0] = 1;//可以取多次,完全背包//dp求排列  先遍历背包容量,for (int i = 1; i <= target; i++) {for (int j = 0; j < nums.length; j++) {if (i >= nums[j]) dp[i] += dp[i - nums[j]];}}return dp[target];
}

爬楼梯(dp)

/*** 70. 爬楼梯** @param n* @return*/
public int climbStairs(int n) {if (n <= 2) return n;int[] dp = new int[n + 1];dp[0] = 1;// 完全背包,求排列数for (int i = 1; i < dp.length; i++) {for (int j = 1; j <= 2; j++) {if (i >= j) dp[i] += dp[i - j];}}return dp[n];
}

零钱兑换

/*** leetcode-322. 零钱兑换** @param coins* @param amount* @return*/
public int coinChange(int[] coins, int amount) {if (amount == 0) return 0;//凑成金额j所需的最少硬币个数dp[j]int[] dp = new int[amount + 1];//求个数 对顺序没有要求 与组合或者是排列没关系int max = Integer.MAX_VALUE;Arrays.fill(dp, max);dp[0] = 0;for (int i = 0; i < coins.length; i++) {for (int j = coins[i]; j <= amount; j++) {// 只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要if(dp[j-coins[i]] != max){dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);}}}return dp[amount] == max ? -1 : dp[amount];
}

完全平方数

/*** leetcode-279. 完全平方数** @param n* @return*/
public int numSquares(int n) {// 完全背包问题//凑成j所需要的完全平方数最小个数dp[j]int[] dp = new int[n + 1];Arrays.fill(dp,Integer.MAX_VALUE);dp[0] = 0; // 若干个平方数中没有0for (int i = 1; i <= n; i++) { // 先背包for (int j = 1; j*j <= i; j++) { // 后物品dp[i] = Math.min(dp[i], dp[i - j * j] + 1);}}/* 也可以for (int i = 1; i * i <= n; i++) { // 遍历物品for (int j = i * i; j <= n; j++) { // 遍历背包dp[j] = Math.min(dp[j - i * i] + 1, dp[j]);}}*/return dp[n];
}

背包问题总结

背包递推公式

  1. 问能否能装满背包(或者最多装多少):dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);对应题目:416 1049
  2. 问装满背包有几种方法:dp[j] += dp[j - nums[i]] ,对应题目如下:494 518 377 70
  3. 问背包装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); ,对应题目如下:474
  4. 问装满背包所有物品的最小个数:dp[j] = min(dp[j - coins[i]] + 1, dp[j]); ,对应题目如下:322 279

遍历顺序

01背包中二维dp,物品和背包遍历顺序谁先均可。一维是先物品后背包,且第二层循环是由大到小。

完全背包中一维dp数组实现,先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历。仅仅是纯完全背包是上述情况,但是场景不同,会有差别。求组合是先物品后背包,求排列是先背包后物品。

求组合:518

求排列:377

若求最小数,遍历顺序无所谓:322

单词拆分

/*** leetcode-139. 单词拆分* @param s* @param wordDict* @return*/
public boolean wordBreak(String s, List<String> wordDict) {// 一个单词可以使用多次 完全背包问题//字符串长度为i,dp[i]为真,表示可以拆分为一个或多个在字典中出现的单词HashSet<String> set = new HashSet<>(wordDict);boolean[] dp = new boolean[s.length() + 1];dp[0] = true;// 本题相当于求排列 先遍历背包for (int i = 1; i <= s.length(); i++) {for (int j = 0; j < i; j++) {if(dp[j] && set.contains(s.substring(j,i-j))){dp[i] = true;break; // 不需要再划分}}}return dp[s.length()];
}

打家劫舍

/*** leetcode-198. 打家劫舍** @param nums* @return*/
public int rob(int[] nums) {if(nums.length == 0) return 0;if(nums.length == 1) return nums[0];// dp[i] 下标为j(包括j)之前可偷的最大金额int[] dp = new int[nums.length];dp[0] = nums[0];dp[1] = Math.max(nums[0],nums[1]);for (int i = 2; i < nums.length; i++) {//不取当前,dp[i-1],取当前dp[i-2]+nums[i]dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);}return dp[nums.length-1];
}

打家劫舍2

/*** leetcode-213. 打家劫舍 II** @param nums* @return*/
public int rob(int[] nums) {if (nums.length == 0) return 0;if (nums.length == 1) return nums[0];//之前是一排排列 现在是环形 第一个和最后一个只能偷一个// 将其变为两个单排列int res1 = getResult(nums, 1, nums.length - 1);//不偷第一个int res2 = getResult(nums, 0, nums.length - 2);//最后一个不偷if (res1 > res2) return res1;return res2;
}public int getResult(int[] nums, int start, int end) {if(start == end) return nums[start];// dp[j] 下标为j(包括j)之前可偷的最大金额int[] dp = new int[end + 1];dp[start] = nums[start];dp[start + 1] = Math.max(nums[start], nums[start + 1]);for (int i = start + 2; i <= end; i++) {dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);}return dp[end];
}

打家劫舍3

/*** leetcode-337. 打家劫舍 III* @param root* @return*/
public int rob(TreeNode root) {int[] ans = getResult(root);return Math.max(ans[0], ans[1]);
}public int[] getResult(TreeNode root) {if (root == null) return new int[2];int[] left = getResult(root.left); //后序遍历int[] right = getResult(root.right);int[] ans = new int[2];//当前节点不偷 两个孩子拿出最多的钱 孩子偷不偷无所谓ans[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);//当前节点偷,孩子节点就不能偷了//左右孩子选择不偷的钱 加上当前节点的值ans[1] = left[0] + right[0] + root.val;return ans;
}

股票问题

买卖股票的最佳时机

/*** 暴力法,超时* leetocde-121. 买卖股票的最佳时机* @param prices* @return*/
public int maxProfit3(int[] prices) {// 找到最大间距int ans = 0;for (int i = 0; i < prices.length; i++) {for (int j = i; j < prices.length; j++) {ans = Math.max(prices[j]-prices[i],ans);}}return ans;
}/*** 贪心* leetocde-121. 买卖股票的最佳时机* @param prices* @return*/
public int maxProfit2(int[] prices) {//找左最小,右最大int ans = 0;int left = prices[0];for (int i = 0; i < prices.length; i++) {left = Math.min(left,prices[i]);ans = Math.max(ans,prices[i]-left);}return ans;
}/*** 动态规划* leetocde-121. 买卖股票的最佳时机* @param prices* @return*/
public int maxProfit(int[] prices) {if(prices.length < 2) return 0;//dp[i][0] 下标为 i 这天结束的时候,不持股,手上拥有的现金数//dp[i][1] 下标为 i 这天结束的时候,持股,手上拥有的现金数int[][] dp = new int[prices.length][2];dp[0][0] = 0; //不持股就是0dp[0][1] = -prices[0];//开始的现金是0 买入后就是-price[0]for (int i = 1; i < prices.length; i++) {//dp[i][0],今天不持股// 1. 昨天就不持股,今天保持原状// 2.昨天持股 今天卖出dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);//dp[i][1],今天持股// 1. 昨天就持股,今天保持原状// 2.昨天不持股 今天买入dp[i][1] = Math.max(dp[i-1][1],-prices[i]);}return dp[prices.length-1][0];
}

买卖股票的最佳时机2

/***leetcode-122. 买卖股票的最佳时机 II* @param prices* @return*/
public int maxProfit(int[] prices) {//dp[i][0] 表示第i天不持有股票所得现金。//dp[i][1] 表示第i天持有股票所得现金。int[][] dp = new int[prices.length][2];dp[0][0] = 0;dp[0][1] = -prices[0];for (int i = 1; i < prices.length; i++) {//dp[i][0] 当天不持有股票// 1. 昨天就不持股,今天保持原状// 2.昨天持股 今天卖出dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);//dp[i]s[1],今天持股// 1. 昨天就持股,今天保持原状// 2.昨天不持股 今天买入dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);}return dp[prices.length-1][0];
}

买卖股票的最佳时机3

/*** leetcode-123. 买卖股票的最佳时机 III** @param prices* @return*/
public int maxProfit(int[] prices) {//本题关键在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。/*一天一共就有五个状态, 0. 没有操作1第一次持有股票2第一次不持有股票3第二次持有股票4第二次不持有股票*///dp[i][j]表示第i天,j为 [0−4]五个状态,第i天状态j所得最大现金。int[][] dp = new int[prices.length][5];dp[0][0] = 0;//没有操作,就是0dp[0][1] = -prices[0];dp[0][2] = 0;dp[0][3] = -prices[0];//第二次持有依赖于第一次持有,相当于第一次买了又卖再买入dp[0][4] = 0;for (int i = 1; i < prices.length; i++) {dp[i][0] = dp[i - 1][0];dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]);dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]);dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);}//最后一次卖出的状态是最大的利润return dp[prices.length - 1][4];
}

买卖股票的最佳时机4

/*** leetcode-188. 买卖股票的最佳时机4** @param k* @param prices* @return*/
public int maxProfit(int k, int[] prices) {int len = 2 * k + 1;int[][] dp = new int[prices.length][len];for (int i = 0; i < len; i++) {if (i % 2 != 0) dp[0][i] = -prices[0];}for (int i = 1; i < prices.length; i++) {dp[i][0] = dp[i - 1][0];for (int j = 1; j < len; j++) {if (j % 2 != 0)dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]);elsedp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]);}}return dp[prices.length - 1][len - 1];
}

最佳买卖股票时机含冷冻期

/*** leetcode-309. 最佳买卖股票时机含冷冻期* @param prices* @return*/
public int maxProfit(int[] prices) {//冷冻期指的是今天卖出股票后下一天不能进行任何操作//关键在于哪一天卖出,在今天买入的时候判断前一天是否卖出过股票//从而不持有股票时 细分为两种状态//0 不持有股票,本来就不持有 并不是因为当天卖出过股票//1 不持有股票 ,因为今天卖出过股票//2 持有股票//第i天状态为j,所剩的最多现金为dp[i][j]。int[][] dp = new int[prices.length][3];dp[0][0] = 0;dp[0][1] = 0;//看作今天买了今天又卖dp[0][2] = -prices[0]; //今天买了股票for (int i = 1; i < prices.length; i++) {// 没有股票,且不是因为我卖出股票而没有// 1 昨天就没股票 2 昨天把股票卖了dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);//没有股票,因为昨天把股票卖了dp[i][1] = dp[i - 1][2] + prices[i];//持股dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][0] - prices[i]);}return Math.max(dp[prices.length - 1][0], dp[prices.length - 1][1]);
}

买卖股票的最佳时机含手续费

/*** leetcode-714. 买卖股票的最佳时机含手续费** @param prices* @param fee* @return*/
public int maxProfit(int[] prices, int fee) {int[][] dp = new int[prices.length - 1][2];dp[0][0] = 0; //不持股dp[0][1] = -prices[0];// 持股for (int i = 1; i < prices.length; i++) {dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);}return dp[prices.length - 1][0];
}

总结

在这里插入图片描述

最长递增子序列

/*** leetcode-300. 最长递增子序列** @param nums* @return*/
public int lengthOfLIS(int[] nums) {//dp[i]:以nums[i] 结尾的最长递增子序列长度int[] dp = new int[nums.length];Arrays.fill(dp, 1); //最长递增子序列长度最少是1int ans =1;for (int i = 1; i < nums.length; i++) {for (int j = 0; j < i; j++) {if (nums[i] > nums[j])dp[i] = Math.max(dp[i], dp[j] + 1); //取dp[j] + 1的最大值,b}ans = Math.max(dp[i],ans);}//找最大值,并不一定结尾的就最大(例如,结尾的数字最小,就是1,前面的可能更大)return ans;
}

最长连续递增序列

/*** 动态规划* leetcode-674. 最长连续递增序列** @param nums* @return*/
public int findLengthOfLCIS2(int[] nums) {//dp[i]:以nums[i] 结尾的最长连续递增子序列长度int[] dp = new int[nums.length];Arrays.fill(dp, 1); //最长递增子序列长度最少是1int ans = 1;for (int i = 1; i < nums.length; i++) {if (nums[i] > nums[i - 1]) {dp[i] = dp[i - 1] + 1;}ans = Math.max(ans, dp[i]);}return ans;
}/*** 贪心* leetcode-674. 最长连续递增序列** @param nums* @return*/
public int findLengthOfLCIS(int[] nums) {int ans = 1;int count = 1;for (int i = 1; i < nums.length; i++) {if (nums[i] > nums[i - 1]) {count++;ans = Math.max(ans, count);} else count = 1;}return ans;
}

最长重复子数组

/*** leetcode-718. 最长重复子数组** @param nums1* @param nums2* @return*/
public int findLength(int[] nums1, int[] nums2) {//以当前下标的前一个下标结尾的最长重复子数组int[][] dp = new int[nums1.length + 1][nums2.length + 1];int ans = 0;for (int i = 0; i < nums1.length; i++) {for (int j = 0; j < nums2.length; j++) {if (nums1[i] == nums2[j]) {dp[i + 1][j + 1] = dp[i][j] + 1;}ans = Math.max(ans, dp[i + 1][j + 1]);}}return ans;
}public int findLength(int[] nums1, int[] nums2) {// dp[i][j] 只依赖上一行上一列的对角线的值,所以我们从右上角开始计算。//以当前下标的前一个下标结尾的最长重复子数组int[] dp = new int[nums2.length + 1];int result = 0;for (int i = 1; i <= nums1.length; i++) {for (int j = nums2.length; j > 0; j--) {if (nums1[i - 1] == nums2[j - 1]) {dp[j] = dp[j - 1] + 1;}else {dp[j] = 0;}result = Math.max(result, dp[j]);}}return result;
}

https://leetcode.cn/problems/maximum-length-of-repeated-subarray/solutions/310917/yi-zhang-biao-ba-ju-hua-kan-dong-dong-tai-gui-hua-/

最长公共子序列

/*** leetcode-1143. 最长公共子序列** @param text1* @param text2* @return*/
public int longestCommonSubsequence(String text1, String text2) {int len1 = text1.length();int len2 = text2.length();//表示 text1[0:i-1] 和 text2[0:j-1] 的最长公共子序列//不是i的原因:当i或者j为0时,希望表示的含义是空字符串和另外一个字符串的匹配(相当于往后移动一格)int[][] dp = new int[len1 + 1][len2 + 1];for (int i = 1; i <= len1; i++) {char c1 = text1.charAt(i - 1);for (int j = 1; j <= len2; j++) {char c2 = text2.charAt(j - 1);//两个字符都在lcs中if (c1 == c2) dp[i][j] = dp[i - 1][j - 1] + 1;//两个字符至少有一个不在lcs中 需要delse dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);}}return dp[len1][len2];
}

不相交的线

/*** leetcode-1035. 不相交的线** @param nums1* @param nums2* @return*/
public int maxUncrossedLines(int[] nums1, int[] nums2) {int len = nums1.length;int len2 = nums2.length;int[][] dp = new int[len + 1][len2 + 1];for (int i = 1; i < len + 1; i++) {for (int j = 1; j < len2 + 1; j++) {if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);}}return dp[len][len2];
}

最大子数组和

/*** leetcode-53. 最大子数组和** @param nums* @return*/
public int maxSubArray(int[] nums) {//以nums[i]结尾的最大连续子序列和为dp[i]int[] dp = new int[nums.length + 1];int ans = 0;for (int i = 1; i < nums.length; i++) {//两个方向可以推出来//dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和//nums[i],即:从头开始计算当前连续子序列和(前面的和<=0,丢弃)ans = Math.max(Math.max(dp[i - 1] + nums[i], nums[i]), ans);}return ans;
}

不同的子序列

/*** leetcode-115. 不同的子序列** @param s* @param t* @return*/
public int numDistinct(String s, String t) {int len1 = s.length();int len2 = t.length();//以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]int[][] dp = new int[len1 + 1][len2 + 1];for (int i = 0; i < len1+1; i++) { //t为空串去做匹配dp[i][0] = 1;}for (int i = 1; i < len1 + 1; i++) {char c = s.charAt(i - 1);for (int j = 1; j < len2 + 1; j++) {if (c == t.charAt(j - 1)) {dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];}else dp[i][j] = dp[i-1][j];}}return dp[len1][len2];
}

两个字符串的删除操作

/*** leetcode-583. 两个字符串的删除操作** @param word1* @param word2* @return*/
public int minDistance(String word1, String word2) {int len1 = word1.length();int len2 = word2.length();int[][] dp = new int[len1 + 1][len2 + 1];for (int i = 1; i < len1 + 1; i++) {char c = word1.charAt(i - 1);for (int j = 1; j < len2 + 1; j++) {if (c == word2.charAt(j - 1)) {dp[i][j] = dp[i - 1][j - 1] + 1;} else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);}}return len1 + len2 - 2 * dp[len1][len2];
}public int minDistance(String word1, String word2) {int len1 = word1.length();int len2 = word2.length();// dp数组中存储需要删除的字符个数int[][] dp = new int[len1 + 1][len2 + 1];for (int i = 0; i < len1 + 1; i++) dp[i][0] = i;for (int j = 0; j < len2 + 1; j++) dp[0][j] = j;for (int i = 1; i < len1 + 1; i++) {for (int j = 1; j < len2 + 1; j++) {if (word1.charAt(i - 1) == word2.charAt(j - 1)) {dp[i][j] = dp[i - 1][j - 1];}else{//dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});//dp[i][j - 1] =dp[i-1][j]= dp[i - 1][j - 1] + 1//->dp[i][j - 1] + 1 = dp[i - 1][j - 1] + 2//可以简化为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);}}}return dp[len1][len2];
}

编辑距离

/*** leetcode-72. 编辑距离** @param word1* @param word2* @return*/
public int minDistance(String word1, String word2) {int len1 = word1.length();int len2 = word2.length();//dp数组中存储的是最少操作数int[][] dp = new int[len1 + 1][len2 + 1];for (int i = 0; i < len1 + 1; i++) dp[i][0] = i;for (int j = 0; j < len2 + 1; j++) dp[0][j] = j;for (int i = 1; i < len1 + 1; i++) {char c = word1.charAt(i - 1);for (int j = 1; j < len2 + 1; j++) {if (c == word2.charAt(j - 1)) {dp[i][j] = dp[i - 1][j - 1];} else {//word1删除一个元素 dp[i - 1][j]+1//word1添加一个元素 等价于word2删除一个元素即dp[i][j - 1] + 1//例如 word1 = "ad" ,word2 = "a",word1删除元素'd' 和 word2添加一个元素'd',// 变成word1="a", word2="ad", 最终的操作数是一样!//word2替换 dp[i - 1][j - 1] + 1dp[i][j] = Math.min(dp[i - 1][j], Math.min(dp[i - 1][j - 1], dp[i][j - 1])) + 1;}}}return dp[len1][len2];
}

回文子串

/*** leetcode-647. 回文子串** @param s* @return*/
public int countSubstrings2(String s) {char[] chars = s.toCharArray();int len = chars.length;int ans = 0;// 下标范围(i,j)的字串是否是回文boolean[][] dp = new boolean[len][len];for (int i = len - 1; i >= 0; i--) { //从下到上,从左到右遍历for (int j = i; j < len; j++) {if (chars[i] == chars[j]) {if (i == j || j - i == 1 || dp[i + 1][j - 1]) {ans++;dp[i][j] = true;}}}}return ans;
}/*** 双指针法(中心扩散法)** @param s* @return*/
public int countSubstrings(String s) {char[] chars = s.toCharArray();int len = chars.length;int ans = 0;for (int i = 0; i < len; i++) {ans += getCount(chars, i, i); //单个作为中心点ans += getCount(chars, i, i + 1);//两个作为中心点}return ans;
}public int getCount(char[] chars, int i, int j) {int ans = 0;while (i >= 0 && j < chars.length && chars[i] == chars[j]) {ans++;i--;j++;}return ans;
}

最长回文子序列

/*** leetcode-516. 最长回文子序列** @param s* @return*/
public int longestPalindromeSubseq(String s) {//注意:子序列和字串不同,字串要求连续,而子序列不需要连续char[] chars = s.toCharArray();int len = chars.length;// 下标范围(i,j)的最长的回文子序列的长度int[][] dp = new int[len][len];for (int i = 0; i < len; i++) dp[i][i] = 1; //初始化for (int i = len - 1; i >= 0; i--) { //从下到上for (int j = i + 1; j < len; j++) {if (chars[i] == chars[j]) {dp[i][j] = dp[i + 1][j - 1] + 2;} elsedp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);}}return dp[0][len - 1];
}

相关文章:

【代码随想录】-动态规划专题

文章目录理论基础斐波拉契数列爬楼梯使用最小花费爬楼梯不同路径不同路径 II整数拆分不同的二叉搜索树背包问题——理论基础01背包二维dp数组01背包一维数组&#xff08;滚动数组&#xff09;装满背包分割等和子集最后一块石头的重量 II目标和一和零完全背包零钱兑换 II组合总和…...

c++数据类型 输入输出

C++语法 //常用包: iostream:cin cout endl cstdio:scanf printf algorithm:max min reverse swap cstring:memset memcpymemset(a,-1,sizeof a) 填充数组memcpy(b,a,sizeof a) 将a数组复制到b数组,长度是a数组字节长度 cmath:sin sqrt pow abs fabs编程是一种控制计…...

【设计模式-11】责任链模式

认识设计模式&#xff08;十一&#xff09;---责任链模式【一】责任链模式【二】介绍&#xff08;1&#xff09;意图&#xff08;2&#xff09;主要解决&#xff08;3&#xff09;何时使用&#xff08;4&#xff09;如何解决&#xff08;5&#xff09;关键代码&#xff08;6&am…...

SpringBoot+Vue实现智能物流管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…...

【MT7628】MT7628如何修改串口波特率、调试串口物理口、使用UART3口

环境说明 sdk版本:Mediatek_ApSoC_SDK_4320_20150414.tar.bz2 芯片方案:MT7628A Uboot修改串口波特率方法 修改rt2880.h文件 修改include/configs/rt2880.h文件CONFIG_BAUDRATE宏的值 - #define CONFIG_BAUDRATE 57600 +#define CONFIG_BAUDRATE 115200 Kernel中修改串口波特…...

css盒模型介绍

在使用CSS进行网页布局时&#xff0c;我们一定离不开的一个东西————盒子模型。盒子模型&#xff0c;顾名思义&#xff0c;盒子就是用来装东西的&#xff0c;它装的东西就是HTML元素的内容。或者说&#xff0c;每一个可见的 HTML 元素都是一个盒子&#xff0c;下面所说的盒子…...

onetab 谷歌插件历史数据清除

文章目录方法1&#xff1a;测试也可以步骤1&#xff1a;批量执行点击步骤2&#xff1a;python 脚本模拟点击确定操作方法2&#xff1a;成功【推荐】步骤1&#xff1a;修改confirm&#xff0c;类似于hook操作步骤2&#xff1a;批量点击删除操作&#xff1a;onetab 谷歌插件历史数…...

GRBL源码简单分析

结构体说明 GRBL里面的速度规划是带运动段前瞻的&#xff0c;所以有规划运动段数据和微小运动段的区分 这里的“规划运动段”对应的数据结构是plan_block_t&#xff0c;前瞻和加减速会使用到&#xff0c;也就是通过解析G代码后出来的直接直线数据或是圆弧插补出来的拟合直线数据…...

第一部分:简单句——第一章:简单句的核心——二、简单句的核心变化(谓语动词的情态)

二、简单句的核心变化 简单句的核心变化其实就是 一主一谓&#xff08;n. v.&#xff09; 表达一件事情&#xff0c;谓语动词是其中最重要的部分&#xff0c;谓语动词的变化主要有四种&#xff1a;三态加一否&#xff08;时态、语态、情态、否定&#xff09;&#xff0c;其中…...

软考高级考试中有五大证书,其中哪个更值得考?

计算机软考属于专业技术人员职业资格水平评价类&#xff0c;是职业资格、专业技术资格&#xff08;职称&#xff09;和专业技术水平"三合一"的考试&#xff0c;是目前IT行业仅有的国家级考试。考试不受学历、专业、资历等条件限制。软考高级考试中有五大证书&#xf…...

FlexRay™ 协议控制器 (E-Ray)-04

网络管理 累积的网络管理 (NM) 向量位于网络管理寄存器 1 到网络管理寄存器 3 (NMVx (x = 1-3)) 中。【The accrued Network Management (NM) vector is located in the Network Management Register 1 to Network Management Register 3 (NMVx (x = 1-3)).】 网络管理向量 x…...

container_of 根据成员变量获得包含其的对象的地址!

写在前面 本系列文章的灵感出处均是各个技术书籍的读后感&#xff0c;详细书籍信息见文章最后的参考文献 CONTAINER_OF 在书中发现一个很有意思的宏&#xff0c;以此可以衍生出来其很多的用法&#xff0c;这个宏可以根据某个成员变量的地址得到包含这个成员变量地址的对象的…...

Linux进程概念

Linux进程概念前言冯诺依曼体系操作系统设计操作系统的目的如何理解OS是一款搞“管理”的软件&#xff1f;系统调用和库函数的概念进程的概念描述进程组织进程查看进程fork&#xff08;&#xff09;前言 本篇博客主要介绍一些&#xff1a;冯诺依曼体系、OS的理解、进程的一些概…...

算法设计与分析

两个例子:调度问题与投资问题 例1&#xff1a;调度问题 问题 有 n 项任务&#xff0c;每项任务加工时间已知.从 0时刻开始陆续安排到一台机器上加工. 每个任务的完成时间是从 0 时刻到任务加工截止的时间. 求: 总完成时间&#xff08;所有任务完成时间之和&#xff09;最短…...

C++ 基础

命名空间 在 C/C 中&#xff0c;变量、函数和类都是大量存在的&#xff0c;这些变量、函数和类的名称将都存在全局作用域中&#xff0c;可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化&#xff0c;以避免命名冲突或名字污染&#xff0c;namespace 关键字的…...

[golang gin框架] 2.Gin HTML模板渲染以及模板语法,自定义模板函数,静态文件服务

一.Gin HTML 模板渲染全部模板放在一个目录里面的配置方法首先在项目根目录新建 templates 文件夹&#xff0c;然后在文件夹中新建 对应的index.html<!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta http…...

数据仓库层Repository(CrudRepository、PagingAndSortingRepository、JpaRepository)

什么是数据仓库层Repository&#xff1f; 数据仓库接口的作用&#xff1a;Repository原意指的是仓库&#xff0c;即数据仓库的意思。Repository居于业务层和数据层之间&#xff0c;将两者隔离开来&#xff0c;在它的内部封装了数据查询和存储的逻辑。 Repository接口&#xff…...

大数据技术架构(组件)33——Spark:Spark SQL--Join Type

2.2.2、Join Type2.2.2.1、Broadcast Hash Join (Not Shuffled)就是常说的MapJoin,join操作在map端进行的。场景&#xff1a;join的其中一张表要很小&#xff0c;可以放到Driver或者Executor端的内存中。原理:1、将小表的数据广播到所有的Executor端&#xff0c;利用collect算子…...

Linux: bash起后台进程引发的僵尸进程

1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. 案例 原来的故事是 这样 的&#xff0c;感兴趣的读者可以直接前往。我从中截取了一段重现故事中问题的代码&#xff08;对原代码做了小小调整&a…...

网络安全攻防中,Rock-ON自动化的多功能网络侦查工具,Burpsuite被动扫描流量转发

网络安全攻防中&#xff0c;Rock-ON自动化的多功能网络侦查工具&#xff0c;Burpsuite被动扫描流量转发。 #################### 免责声明&#xff1a;工具本身并无好坏&#xff0c;希望大家以遵守《网络安全法》相关法律为前提来使用该工具&#xff0c;支持研究学习&#xff…...

电子技术——共模抑制

电子技术——共模抑制 我们在之前学习过&#xff0c;无论是MOS还是BJT的差分输入对&#xff0c;共模信号并不会改变漏极电流的大小&#xff0c;因此我们说差分输入对共模信号无响应。但是实际上由于各种客观非理想因素&#xff0c;例如电流源有限阻抗等&#xff0c;此时共模是影…...

对KMP简单的理解

声明&#xff1a;下边的例子均表示下标从1开始的数组 ne数组的定义&#xff1a; next[i] 就是使子串 s[1…i] 有最长相等前后缀的前缀的最后一位的下标。ne[i]也可以表示相等子串的长度 准备执行jne[j]时&#xff0c; 表示当前s[i]!p[j1] , 如果ne[j]1 &#xff0c;那么下…...

Hibernate不是过时了么?SpringDataJpa又是什么?和Mybatis有什么区别?

一、前言 ps: 大三下学期&#xff0c;拿到了一份实习。进入公司后发现用到的技术栈有Spring Data Jpa\Hibernate,但对于持久层框架我只接触了Mybatis\Mybatis-Plus&#xff0c;所以就来学习一下Spring Data Jpa。 1.回顾MyBatis 来自官方文档的介绍&#xff1a;MyBatis 是一款…...

数学建模拓展内容:卡方检验和Fisher精确性检验(附有SPSS使用步骤)

卡方检验和Fisher精确性检验卡方拟合度检验卡方独立性检验卡方检验的前提假设Fisher精确性检验卡方拟合度检验 卡方拟合度检验概要&#xff1a;卡方拟合度检验也被称为单因素卡方检验&#xff0c;用于检验一个分类变量的预期频率和观察到的频率之间是否存在显著差异。 卡方拟…...

【Python学习笔记之七大数据类型】

Python数据类型&#xff1a;Number数字、Boolean布尔值、String字符串、list列表、tuple元组、set集合、dictionary字典 int整数 a1 print(a,type(a))float浮点数 b1.1 print(b,type(b))complex复数 c100.5j print(c,type(c))bool布尔值:True、False,true和false并非Python…...

Android系统之onFirstRef自动调用原理

前言&#xff1a;抽丝剥茧探究onFirstRef究竟为何在初始化sp<xxx>第一个调用&#xff1f;1.onFirstRef调用位置<1>.system/core/libutils/RefBase.cpp#include <utils/RefBase.h>//1.初始化强指针 void RefBase::incStrong(const void* id) const {weakref_i…...

ipv6上网配置

一般现在的宽带都已经支持ipv6了&#xff0c;但是需要一些配置才能真正用上ipv6。记录一下配置过程。 当前测试环境为移动宽带&#xff0c;光猫下面接了一个路由器&#xff0c;家里所有的设备都挂到这个路由器下面的。 1. 光猫改桥接 光猫在使用路由模式下&#xff0c;ipv6无…...

python实现聚类技术—复杂网络社团检测 附完整代码

实验内容 某跆拳道俱乐部数据由 34 个节点组成,由于管理上的分歧,俱乐部要分解成两个社团。 该实验的任务即:要求我们在给定的复杂网络上检测出两个社团。 分析与设计 实验思路分析如下: 聚类算法通常可以描述为用相似度来衡量两个数据的远近,搜索可能的划分方案,使得目标…...

如何判断两架飞机在汇聚飞行?(如何计算两架飞机的航向夹角?)内含程序源码

ok&#xff0c;在开始一切之前&#xff0c;让我先猜一猜&#xff0c;你是不是想百度“二维平面下如何计算两个移动物体的航向夹角&#xff1f;”如果是&#xff0c;那就请继续往下看。 首先&#xff0c;我们要明确一个概念&#xff1a;航向角≠航向夹角&#xff01;&#xff0…...

Scipy稀疏矩阵bsr_array

文章目录基本原理初始化内置方法基本原理 bsr&#xff0c;即Block Sparse Row&#xff0c;bsr_array即块稀疏行矩阵&#xff0c;顾名思义就是将稀疏矩阵分割成一个个非0的子块&#xff0c;然后对这些子块进行存储。通过输入维度&#xff0c;可以创建一个空的bsr数组&#xff0…...

设计网站国外网站/百度云搜索

快速启动终端: ctraltt终端字体放大: ctrshift终端字体缩小: ctr-ls: 查看当前目录的下文件信息pwd: 查看当前目录的路径touch: 创建文件mkdir: 创建文件夹rmdir&#xff1a; 删除空文件夹rm: 默认删除的是文件&#xff0c; -r表示以递归的方式删除文件夹里面的所有文件信息最后…...

网站首页一般做多大尺寸/百度站长平台工具

目录 1.CPU与GPU分析 1.GPU渲染工具:GPU-RENDERING-PROFILE 2.GPR显示内容说明: 检查 GPU 渲染速度和过度绘制了解设备上的开发者选项如何帮助您直观地查看您的应用可能会在何处遇到问题。https://developer.android.google.cn/topic/performance/rendering/inspect-gpu-rend…...

网站建设需要实现哪些目标/推广咨询服务公司

小弟此处只是记录一下参考的文章&#xff0c;因为我是用的两种方案结合的&#xff0c;没有别的意思 详细出处参考&#xff1a;http://blog.csdn.net/linghao00/article/details/8058730 以及http://blog.csdn.net/lqh4188/article/details/46828409 第一篇文章的内容&#xff1…...

wordpress 登陆链接/如何进入网站

缘起 随着互联网企业的不断发展&#xff0c;产品项目中的模块越来越多&#xff0c;用户体验要求也越来越高&#xff0c;想实现小步快跑、快速迭代的目的越来越难&#xff0c;还有65535&#xff0c;应用之间的互相调用等等问题&#xff0c;插件化技术应用而生。如果没有插件化技…...

公司装修费用账务处理/seo营销推广公司

共有以下三个步骤一、Python安装点击合适的版本&#xff0c;我这里就选择了最新的3.7.3&#xff0c;在页面底部的Files表格中点击下图的一项进行下载(64位)下载完成后双击打开进入安装程序&#xff0c;窗口底部的两个可选项推荐都选中&#xff0c;第一项“为所有用户安装”是默…...

装修类网站模板下载/整合营销传播方案

双向链表的概念 双向链表也叫双链表&#xff0c;是链表的一种&#xff0c;它的每个数据结点中都有两个指针&#xff0c;分别指向直接后继和直接前驱。所以&#xff0c;从双向链表中的任意一个结点开始&#xff0c;都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向…...