LeetCode【代码随想录】刷题(动态规划篇)
509. 斐波那契数
力扣题目链接
题目:斐波那契数(通常用F(n)
表示)形成的序列称为斐波那契数列 。该数列由0
和1
开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n
,请计算 F(n)
。
思路:数据量不大,递推一下
通过代码:
class Solution {
public:int fib(int n) {if(n < 2)return n;int a = 0, b = 1, c;for(int i = 0; i < n - 1; i++){c = a + b;a = b;b = c;}return c;}
};
70. 爬楼梯
力扣题目链接
假设你正在爬楼梯。需要n
阶你才能到达楼顶。
每次你可以爬1
或2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
思路:每一级台阶都可以由前两个台阶走到,前一个台阶跨一步,前两个台阶跨两步
通过代码:
class Solution {
public:int climbStairs(int n) {vector<int> steps(46, 0);steps[1] = 1;steps[2] = 2;for(int i = 3; i <= n; i++)steps[i] = steps[i - 1] + steps[i - 2];return steps[n];}
};
746. 使用最小花费爬楼梯
力扣题目链接
题目:给你一个整数数组cost
,其中cost[i]
是从楼梯第i
个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为0
或下标为1
的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
思路:可以有两个途径得到dp[i],一个是dp[i-1]
一个是dp[i-2]
。dp[i-1]
跳到dp[i]
需要花费dp[i - 1] + cost[i - 1]
。dp[i-2]
跳到dp[i]
需要花费dp[i-2] + cost[i-2]
。
那么究竟是选从dp[i - 1]
跳还是从dp[i - 2]
跳呢?一定是选最小的,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
通过代码:
class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {int n = cost.size();vector<int> dp(n + 1, 0);dp[0] = 0;dp[1] = 0;for(int i = 2; i <= n; i++)dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);return dp[n];}
};
62.不同路径
力扣题目链接
题目:一个机器人位于一个m x n
网格的左上角(起始点在下图中标记为“Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?
思路:每个网格只能从其上方或左边过来,因此其路径数为其上方和左侧之和。
通过代码:
class Solution {
public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m, vector<int> (n));for(int i = 0; i < n; i++)dp[0][i] = 1;for(int i = 0; i < m; i++)dp[i][0] = 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];}
};
63. 不同路径 II
力扣题目链接
题目:一个机器人位于一个m x n
网格的左上角(起始点在下图中标记为“Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 1
和 0
来表示。
思路:和前一题类似,只不过需要处理一下有障碍的情况。状态转移的时候有障碍的格子赋0即可,初始化的时候一旦遇到障碍就要结束。
通过代码:
class Solution {
public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m = obstacleGrid.size(), n = obstacleGrid[0].size();vector<vector<int>> dp(m, vector<int> (n, 0));if(obstacleGrid[0][0])dp[0][0] = 0;elsedp[0][0] = 1;for(int i = 1; i < n && obstacleGrid[0][i] == 0; i++)dp[0][i] = dp[0][i - 1];for(int i = 1; i < m && obstacleGrid[i][0] == 0; i++)dp[i][0] = dp[i - 1][0];for(int i = 1; i < m; i++)for(int j = 1; j < n; j++){if(obstacleGrid[i][j])dp[i][j] = 0;elsedp[i][j] = dp[i -1][j] + dp[i][j - 1];}return dp[m - 1][n - 1];}
};
343. 整数拆分
力扣题目链接
题目:给定一个正整数n
,将其拆分为k
个正整数的和(k >= 2
),并使这些整数的乘积最大化。返回你可以获得的最大乘积 。
思路:dp[i]
表示i的最大乘积。有两种渠道可以得到dp[i]
,一种是j*(i - j)
,另一种是dp[j] * (i - j)
。前者代表不对j拆分,后者代表对j进行拆分,由于j差分的最大乘积在之前的遍历已经算出来了,所以直接用dp[j]
即可。在这种渠道选一个大的即可,最后在整个遍历过程中取一个大的。
通过代码:
class Solution {
public:int integerBreak(int n) {vector<int> dp(n + 1);dp[1] = 1;dp[2] = 1;for(int i = 3; i <= n; i++){dp[i] = dp[i - 1];for(int j = i - 2; j >= 1; j--)dp[i] = max(dp[i], max(dp[j], j) * (i - j));}return dp[n];}
};
96.不同的二叉搜索树
力扣题目链接
题目:给你一个整数n
,求恰由n
个节点组成且节点值从1
到n
互不相同的二叉搜索树有多少种?返回满足题意的二叉搜索树的种数。
思路:dp[i]
表示i个节点组成的树的种数。以j为根节点,则前j-1
个节点必在其左子树,其种数为dp[j - 1]
;后i-j
个节点必在其右子树,其种数为dp[i - j]
。所以,以j为根节点的种数合计为dp[j - 1] * dp[i - j]
。dp[i]
的值对以1到i为根节点的种数求和即可。
通过代码:
class Solution {
public:int numTrees(int n) {vector<int> dp(n + 1, 0);dp[0] = 1;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];}
};
416. 分割等和子集
力扣题目链接
题目:给你一个只包含正整数的非空数组nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
思路:首先排除一些明显无法分割的情况:元素数量小于2、总和为奇数、最大元素超过总和的一半。将问题转化为01背包,取一些数使得其和为target。定义dp数组,dp[i][j]
表示能否在下标为0到i的范围选一些数使得和为j。对于新扩展进来的数nums[i]
,我们有两种选择,一是不选,那么结果就为dp[i-1][j]
,二是选,结果就为dp[i-1][j-nums[i]]
。只要这两个结果中的一个为true,那么dp[i][j]
就为true。
通过代码:
class Solution {
public:bool canPartition(vector<int>& nums) {int n = nums.size(), sum = 0, maxn = 0;if(n < 2)return false;for(int num : nums){sum += num;maxn = max(maxn, num);}if(sum % 2 == 1)return false;int target = sum / 2;if(maxn > target)return false;vector<bool> dp(target + 1, false);dp[0] = true;dp[nums[0]] = true;for(int i = 1; i < n; i++)for(int j = target; j >= nums[i]; j--)dp[j] = dp[j] || dp[j - nums[i]];return dp[target];}
};
1049.最后一块石头的重量II
力扣题目链接
题目:有一堆石头,用整数数组stones
表示。其中stones[i]
表示第i
块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x
和y
,且x <= y
。那么粉碎的可能结果如下:
- 如果
x == y
,那么两块石头都会被完全粉碎; - 如果
x != y
,那么重量为x
的石头将会完全粉碎,而重量为y
的石头新重量为y-x
。
最后,最多只会剩下一块石头。返回此石头最小的可能重量 。如果没有石头剩下,就返回0
。
思路:考虑将石头分成两堆,每次从两堆中各取一个石头粉碎。小的那个石头会完全粉碎,大的石头会减去小石头的重量。因此每次粉碎对于整个堆的总和来说都是减去相同的重量。由此,问题转化为了将石头分成重量尽可能接近的两堆。这就类似于上一题了。分成重量尽可能接近的两堆,又可以转化为选取一些石头,使得这些石头的重量接近总重的一半。所以背包容量就是总重的一半。石头的价值就是石头的重量,价值越大越好就是重量越接近背包上限越好。而背包上限就是总重的一半,因此就能找到最接近总重一半的石头堆。
通过代码:
class Solution {
public:int lastStoneWeightII(vector<int>& stones) {int sum = 0;for(int num : stones)sum += num;int target = sum / 2 + 1;vector<int> dp(target, 0);for(int i = 0; i < stones.size(); i++)for(int j = target - 1; j >= stones[i]; j--)dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);return sum - dp[target - 1] - dp[target - 1];}
};
494.目标和
力扣题目链接
题目:给你一个非负整数数组nums
和一个整数target
。向数组中的每个整数前添加'+'
或'-'
,然后串联起所有整数,可以构造一个表达式 :
- 例如,
nums = [2, 1]
,可以在2
之前添加'+'
,在1
之前添加'-'
,然后串联起来得到表达式"+2-1"
。
返回可以通过上述方法构造的、运算结果等于target
的不同表达式的数目。
思路:难在如何转化为背包问题。一旦找到转化的思路,就容易了。假设加负号的整数的和为neg,那么加正号的整数的和为sum-neg(sum为nums的总和)。根据题意有(sum-neg)-neg=target,即sum-2*neg=target,由于sum和target都是定值,因此也能求出neg为(sum-target)/2。由于是非负整数数组,所以neg肯定也是非负整数,不是的话就是无解。于是问题就可以转化为在nums中选一些数使得和为neg,求几种选法,从而转化为了01背包问题。后面不再赘述。
通过代码:
class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int sum = 0;for(int num : nums)sum += num;if(sum - target < 0 || (sum - target) % 2)return 0;int neg = (sum - target) / 2;vector<int> dp(neg + 1, 0);dp[0] = 1;for(int i = 0; i < nums.size(); i++)for(int j = neg; j >= nums[i]; j--)dp[j] = dp[j] + dp[j - nums[i]];return dp[neg];}
};
474.一和零
力扣题目链接
题目:给你一个二进制字符串数组strs
和两个整数m
和n
。请你找出并返回 strs
的最大子集的长度,该子集中最多有m
个0
和n
个1
。
如果x
的所有元素也是y
的元素,集合x
是集合y
的子集 。
思路:01背包的影子很容易看出来,就是背包容量有了两个维度,一个是0的数量,一个是1的数量。换成背包问题的表述就是背包容量为m和n,求能装的字符串的最大数量。一个字符串中的1的数量和0的数量就是代价,价值就是个数可以+1。如果不压缩空间的话就要开三维数组,因此这里仍然采用滚动数组。最外层依然是遍历字符串的个数。里面两层依次遍历背包的两个维度,注意都要从后往前遍历。
通过代码:
class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0));for(string s : strs){int ones = count(s.begin(), s.end(), '1');int zeros = s.size() - ones;for(int i = m; i >= zeros; i--)for(int j = n; j >= ones; j--)dp[i][j] = max(dp[i][j], dp[i -zeros][j - ones] + 1);}return dp[m][n];}
};
518.零钱兑换II
力扣题目链接
题目:给你一个整数数组coins
表示不同面额的硬币,另给一个整数amount
表示总金额。请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回0
。
假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号整数。
思路:近乎完全背包模板题(参考链接)。背包容量为amount
,每个硬币无限数量,硬币面值就是代价。注意dp[0]
一定要为1。
通过代码:
class Solution {
public:int change(int amount, vector<int>& coins) {vector<int> dp(amount + 1, 0);dp[0] = 1;for(int i = 0; i < coins.size(); i++)for(int j = coins[i]; j <= amount; j++)dp[j] += dp[j - coins[i]];return dp[amount];}
};
377. 组合总和 Ⅳ
力扣题目链接
题目:给你一个由 不同 整数组成的数组nums
,和一个目标整数target
。请你从nums
中找出并返回总和为target
的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
思路:完全背包。把两个for循环反过来,就是排列。target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。
通过代码:
class Solution {
public:int combinationSum4(vector<int>& nums, int target) {vector<int> dp(target + 1, 0);dp[0] = 1;for(int i = 1; i <= target; i++)for(int j = 0; j < nums.size(); j++){if(i >= nums[j] && dp[i] < INT_MAX - dp[i - nums[j]])dp[i] += dp[i - nums[j]];}return dp[target];}
};
322. 零钱兑换
力扣题目链接
题目:给你一个整数数组coins
,表示不同面额的硬币;以及一个整数amount
,表示总金额。计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回-1
。你可以认为每种硬币的数量是无限的。
思路:典型的完全背包。凑足总额为j - coins[i]
的最少个数为dp[j - coins[i]]
,那么只需要加上一个钱币coins[i]
,即dp[j - coins[i]] + 1
就是dp[j]
。所以dp[j]
要取所有dp[j - coins[i]] + 1
中最小的。
递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
注意我初始化都为-1,除了dp[0]=0
,递推的时候还要注意无解的情况。
通过代码:
class Solution {
public:int coinChange(vector<int>& coins, int amount) {vector<int> dp(amount + 1, -1);dp[0] = 0;for(int i = 0; i < coins.size(); i++)for(int j = coins[i]; j <= amount; j++){if(dp[j] != -1 && dp[j - coins[i]] != -1)dp[j] = min(dp[j], dp[j - coins[i]] + 1);else if(dp[j] == -1 && dp[j - coins[i]] != -1)dp[j] = dp[j - coins[i]] + 1;} return dp[amount];}
};
279.完全平方数
力扣题目链接
题目:给你一个整数n
,返回和为n
的完全平方数的最少数量 。
完全平方数是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而3
和11
不是。
思路:n的最大值为10000,所以完全平方数的范围就是1-100的平方。而且都是可以无限使用的,所以就是典型的完全背包。类似于上面一道题,1-100就相当于硬币面值,n就相当于amount,也就是背包容量。
通过代码:
class Solution {
public:int numSquares(int n) {vector<int> dp(n + 1, INT_MAX);dp[0] = 0;for(int i = 1; i <= 100; i++)for(int j = i * i; j <= n; j++)dp[j] = min(dp[j], dp[j - i * i] + 1);return dp[n];}
};
139.单词拆分
力扣题目链接
题目:给你一个字符串s
和一个字符串列表wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出s
则返回 true
。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
思路:单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。如果确定dp[j]是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true(j < i)。
通过代码:
class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(), wordDict.end());vector<bool> dp(s.size() + 1, false);dp[0] = true;for(int i = 1; i <= s.size(); i++)for(int j = 0; j < i; j++) //枚举字符串就是枚举起始位置{string word = s.substr(j, i - j);if(wordSet.find(word) != wordSet.end() && dp[j])dp[i] = true;}return dp[s.size()];}
};
198.打家劫舍
力扣题目链接
题目:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
思路:dp[i]
:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]
。如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i]
,即:第i-1房一定是不考虑的,找出下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为dp[i-2]
加上第i房间偷到的钱。如果不偷第i房间,那么dp[i] = dp[i - 1]
,即考虑i-1房。
通过代码:
class Solution {
public:int rob(vector<int>& nums) {if(nums.size() == 0)return 0;if(nums.size() == 1)return nums[0];vector<int> dp(nums.size(), 0);dp[0] = nums[0];dp[1] = max(nums[0], nums[1]);for(int i = 2; i < nums.size(); i++)dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);return dp[nums.size() - 1];}
};
213.打家劫舍II
力扣题目链接
题目:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
思路:如果偷窃了第一间房屋,则不能偷窃最后一间房屋,因此偷窃房屋的范围是第一间房屋到最后第二间房屋;如果偷窃了最后一间房屋,则不能偷窃第一间房屋,因此偷窃房屋的范围是第二间房屋到最后一间房屋。所以问题就转化为了两个上一题,最后取最大值即可。
通过代码:
class Solution {
public:int myrob(vector<int> &nums, int start, int end){if(end - start == 1)return nums[start];vector<int> dp(nums.size(), 0);dp[start] = nums[start];dp[start + 1] = max(nums[start], nums[start + 1]);for(int i = start + 2; i < end; i++)dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);return dp[end - 1];}int rob(vector<int>& nums) {if(nums.size() == 0)return 0;if(nums.size() == 1)return nums[0];int res1 = myrob(nums, 0, nums.size() - 1);int res2 = myrob(nums, 1, nums.size());return max(res1, res2);}
};
337.打家劫舍 III
力扣题目链接
题目:小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为root
。除了root
之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。如果 两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
给定二叉树的root
。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额。
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
思路一:记忆化搜索。
通过代码:
class Solution {
public:unordered_map<TreeNode*, int> map;int rob(TreeNode* root) {if(map.find(root) != map.end())return map[root];if(!root)return 0;if(!root -> left && !root -> right){map[root] = root -> val;return root -> val;}// 偷父节点int val1 = root -> val;if(root -> left)val1 += rob(root -> left -> left) + rob(root -> left -> right);if(root -> right)val1 += rob(root -> right -> left) + rob(root -> right -> right);// 不偷父节点int val2 = rob(root -> left) + rob(root -> right);map[root] = max(val1, val2);return map[root];}
};
思路二:树形dp。递归函数的返回值是一个长度为2的数组:dp[0]
表示不偷当前节点所得到的最大值,dp[1]
表示偷当前节点所得到的最大值。在单层递归中,如果偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0];
。如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);
。最后当前节点的状态就是{val2, val1};
通过代码:
class Solution {
public:vector<int> robTree(TreeNode *cur){if(!cur)return {0, 0};vector<int> left = robTree(cur -> left);vector<int> right = robTree(cur -> right);// 偷curint val1 = cur -> val + left[0] + right[0];// 不偷curint val2 = max(left[0], left[1]) + max(right[0], right[1]);return {val2, val1};}int rob(TreeNode* root) {vector<int> res = robTree(root);return max(res[0], res[1]);}
};
121. 买卖股票的最佳时机
力扣题目链接
题目:给定一个数组prices
,它的第i
个元素prices[i]
表示一支给定股票第i
天的价格。
你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回0
。
思路一:贪心。
通过代码:
class Solution {
public:int maxProfit(vector<int>& prices) {int res = INT_MIN;int low = INT_MAX;for(int i = 0; i < prices.size(); i++){low = min(low, prices[i]);res = max(res, prices[i] - low);}return res;}
};
思路二:动态规划。相邻两天的股票价格做差,得到当天持有所能产生的收益beni。问题就转化为:在beni中找到一段连续的时间,使得收益最大。dp[i]表示持有到第i天所能产生的最大收益。对于新扩展进来的一天,如果选择持有,那么累计收益就为dp[i - 1] + beni[i];如果选择不持有,那么收益就要从当天重新计算,beni[i]。二者取最大值即可。
通过代码:
class Solution {
public:int maxProfit(vector<int>& prices) {if(prices.size() < 2)return 0;vector<int> beni;for(int i = 1; i < prices.size(); i++)beni.push_back(prices[i] - prices[ i- 1]);int n = beni.size(), res;vector<int> dp(n, 0);dp[0] = beni[0];res = dp[0];for(int i = 1; i < n; i++){dp[i] = max(dp[i - 1] + beni[i], beni[i]);res = max(res, dp[i]);}return res <= 0 ? 0 : res;}
};
122.买卖股票的最佳时机II
力扣题目链接
题目:给你一个整数数组prices
,其中prices[i]
表示某支股票第i
天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多只能持有一股股票。你也可以先购买,然后在同一天出售。返回你能获得的最大利润 。
思路:在贪心一章我们用收集每天的正利润来做,这里用动态规划做。dp[i][0]
表示第i天不持有股票的最大收益,dp[i][1]
表示第i天持有股票的最大收益。对于dp[i][0]
,可以由前一天的两个状态推出:如果前一天也没有持有股票并且今天也选择不持有股票,那么收益就为dp[i-1][0]
,如果前一天持有了股票并且今天选择不持有,即卖出,收益为dp[i-1][1]+prices[i]
,取二者较大值更新状态即可。dp[i][1]
同理,如果前一天没有持有股票,今天选择持有股票,收益为dp[i-1][0]-prices[i]
(购买股票需要花钱,所以要减去prices[i]
),如果前一天持有了股票,并且今天不卖出,收益为dp[i-1][1]
,取二者较大值更新状态即可。
通过代码:
class Solution {
public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(), vector<int>(2,0));dp[0][1] = -prices[0];for(int i = 1; i < prices.size(); i++){dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);}return dp[prices.size() - 1][0];}
};
123.买卖股票的最佳时机III
力扣题目链接
题目:给定一个数组,它的第i
个元素是一支给定的股票在第i
天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成两笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:一天一共就有五个状态,
- 没有操作 (其实我们也可以不设置这个状态)
- 第一次持有股票
- 第一次不持有股票
- 第二次持有股票
- 第二次不持有股票
dp[i][j]
中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。注意,状态j表示第i天仍然处于这个状态。
达到dp[i][1]
状态,有两个具体操作:
- 操作一:第i天买入股票了,那么
dp[i][1] = dp[i-1][0] - prices[i]
- 操作二:第i天没有操作,而是沿用前一天买入的状态,即:
dp[i][1] = dp[i - 1][1]
二者取较大值就是dp[i][1
]更新后的状态。剩下的同理。
通过代码:
class Solution {
public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(), vector<int> (5, 0));dp[0][1] = -prices[0];dp[0][3] = -prices[0];for(int i = 1; i < prices.size(); i++){dp[i][0] = dp[i - 1][0];dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1]);dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2]);dp[i][3] = max(dp[i - 1][2] - prices[i], dp[i - 1][3]);dp[i][4] = max(dp[i - 1][3] + prices[i], dp[i - 1][4]);}return dp[prices.size() - 1][4];}
};
188.买卖股票的最佳时机IV
力扣题目链接
题目:给你一个整数数组prices
和一个整数k
,其中prices[i]
是某支给定的股票在第i
天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成k
笔交易。也就是说,你最多可以买k
次,卖k
次。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:类似于上一题,只不过进行了推广,可以买卖k次。接着上一题的状态往下排:第三次持有股票,第三次不持有股票……可以发现,奇数下标都是持有股票,偶数下标都是不持有股票,而且状态更新也只用到上一层相同位置和其左边一个位置。用一个循环完成这个重复操作即可。
通过代码:
class Solution {
public:int maxProfit(int k, vector<int>& prices) {vector<int> dp(2 * k + 1, 0);for(int i = 1; i < 2 * k + 1; i += 2)dp[i] = -prices[0];for(int i = 1; i < prices.size(); i++)for(int j = 1; j < 2 * k + 1; j++){if(j % 2 == 1)dp[j] = max(dp[j], dp[j - 1] - prices[i]);elsedp[j] = max(dp[j], dp[j - 1] + prices[i]);}return dp[2 * k];}
};
309.最佳买卖股票时机含冷冻期
力扣题目链接
题目:给定一个整数数组prices
,其中第 prices[i]
表示第i
天的股票价格 。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:首先划分状态。0:持有股票(可能是今天买的,也可能是之前买的);1:不持有股票,并且是两天前就卖出的,冷冻期已过;2:今天刚卖出股票;3:昨天卖的股票,今天是冷冻期。
要想得到状态0,可能昨天就持有了股票,即dp[i-1][0]
,也可能昨天冷冻期已过,今天选择买入,即dp[i-1][1] - prices[i]
,也可能昨天是冷冻期,今天买入,即dp[i-1][3] - prices[i]
,三者取最大值更新即可。要想得到状态1,可能昨天就是状态1,即dp[i-1][1]
,也可能昨天是冷冻期,即dp[i-1][3]
,二者取最大值即可。要想得到状态2,只可能昨天持有股票,即dp[i-1][0]+prices[i]
;要想得到状态3,只可能昨天刚卖出股票,即dp[i-1][2]
。
通过代码:
class Solution {
public:int maxProfit(vector<int>& prices) {int n = prices.size();vector<vector<int>> dp(n, vector<int> (4, 0));dp[0][0] = -prices[0];for(int i = 1; i < n; i++){dp[i][0] = max({dp[i - 1][0], dp[i - 1][1] - prices[i], dp[i - 1][3] - prices[i]});dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);dp[i][2] = dp[i - 1][0] + prices[i];dp[i][3] = dp[i - 1][2];}return max({dp[n - 1][1], dp[n - 1][2], dp[n - 1][3]});}
};
714.买卖股票的最佳时机含手续费
力扣题目链接
题目:给定一个整数数组prices
,其中prices[i]
表示第i
天的股票价格 ;整数fee
代表了交易股票的手续费用。你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
思路:类似于买卖股票的最佳时机II,只不过多了一个手续费,在卖出的时候减去手续费即可。
通过代码:
class Solution {
public:int maxProfit(vector<int>& prices, int fee) {vector<int> dp(2, 0);dp[1] = -prices[0];for(int i = 1; i < prices.size(); i++){dp[0] = max(dp[0], dp[1] + prices[i] - fee);dp[1] = max(dp[1], dp[0] - prices[i]);}return dp[0];}
};
300.最长递增子序列
力扣题目链接
题目:给你一个整数数组nums
,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组[0,3,1,6,2,2,7]
的子序列。
思路:dp[i]
表示以nums[i]
结尾的最长子序列长度。位置i的最长递增子序列等于j从0到i-1各个位置的最长升序子序列+1的最大值。
通过代码:
class Solution {
public:int lengthOfLIS(vector<int>& nums) {vector<int> dp(nums.size(), 1);int res = 1;for(int i = 1; i < nums.size(); i++){for(int j = 0; j < i; j++)if(nums[i] > nums[j])dp[i] = max(dp[i], dp[j] + 1);res = max(res, dp[i]);}return res;}
};
674. 最长连续递增序列
力扣题目链接
题目:给定一个未经排序的整数数组,找到最长且连续递增的子序列,并返回该序列的长度。
连续递增的子序列可以由两个下标l
和r
(l < r
)确定,如果对于每个l <= i < r
,都有nums[i] < nums[i + 1]
,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]]
就是连续递增子序列。
思路:dp[i]:以下标i为结尾的连续递增的子序列长度为dp[i]。如果nums[i] > nums[i - 1]
,那么以i为结尾的连续递增的子序列长度一定等于以i - 1为结尾的连续递增的子序列长度 + 1 。
通过代码:
class Solution {
public:int findLengthOfLCIS(vector<int>& nums) {vector<int> dp(nums.size(), 1);int res = 1;for(int i = 1; i < nums.size(); i++){if(nums[i] > nums[i - 1])dp[i] = dp[i - 1] + 1;res = max(res, dp[i]);}return res;}
};
718. 最长重复子数组
力扣题目链接
题目:给两个整数数组nums1
和nums2
,返回两个数组中公共的 、长度最长的子数组的长度 。
思路:dp[i][j]
表示以数组1中第i个数结尾(即nums1[i-1]
)、数组2中第j个数结尾(即nums2[j-1]
)的最长公共子数组的长度。如果nums1[i-1]
和nums2[j-1]
相同,当前的最长公共子数组的长度就要更新为dp[i-1][j-1]+1
。之所以如此定义dp数组,是为了减少初始化的麻烦。如果从下标0开始算,第0行和第0列就要单独初始化。
通过代码:
class Solution {
public:int findLength(vector<int>& nums1, vector<int>& nums2) {int len1 = nums1.size(), len2 = nums2.size();if(len1 == 0 || len2 == 0)return 0;vector<vector<int>> dp(len1 + 1, vector<int> (len2 + 1, 0));int res = 0;for(int i = 1; i <= len1; i++)for(int j = 1; j <= len2; j++){if(nums1[i - 1] == nums2[j - 1])dp[i][j] = dp[i - 1][j - 1] + 1;res = max(res, dp[i][j]);}return res;}
};
1143.最长公共子序列
力扣题目链接
题目:给定两个字符串text1
和text2
,返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列 ,返回0
。
一个字符串的子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的公共子序列是这两个字符串所共同拥有的子序列。
思路:类似上一题,只不过上一题要求连续,这一题可以不连续。dp[i][j]
表示长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列长度。之所以如此设置还是为了避免初始化的麻烦。如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1;
如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的。
通过代码:
class Solution {
public:int longestCommonSubsequence(string text1, string text2) {int len1 = text1.size(), len2 = text2.size();vector<vector<int>> dp(len1 + 1, vector<int> (len2 + 1, 0));for(int i = 1; i <= len1; i++)for(int j = 1; j <= len2; j++){if(text1[i - 1] == text2[j - 1])dp[i][j] = dp[i - 1][j - 1] + 1;elsedp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);}return dp[len1][len2];}
};
1035.不相交的线
力扣题目链接
题目:在两条独立的水平线上按给定的顺序写下nums1
和nums2
中的整数。现在,可以绘制一些连接两个数字nums1[i]
和nums2[j]
的直线,这些直线需要同时满足:
nums1[i] == nums2[j]
- 且绘制的直线不与任何其他连线(非水平线)相交。
请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。
以这种方法绘制线条,并返回可以绘制的最大连线数。
思路:只有相同的数字才能连线,不就是公共子序列吗。不允许线相交就是子序列得按顺序来。所以本题和上一题最长公共子序列是一样的,代码都只要改个数组名。
通过代码:
class Solution {
public:int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {int len1 = nums1.size(), len2 = nums2.size();vector<vector<int>> dp(len1 + 1, vector<int> (len2 + 1, 0));for(int i = 1; i <= len1; i++)for(int j = 1; j <= len2; j++){if(nums1[i - 1] == nums2[j - 1])dp[i][j] = dp[i - 1][j - 1] + 1;elsedp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);}return dp[len1][len2];}
};
53. 最大子序和
力扣题目链接
题目:给你一个整数数组nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。
思路:上次使用贪心做的,这回用动规。dp[i]
表示包括下标i(以nums[i]
为结尾)的最大连续子序列和为dp[i]
。对于nums[i]
,可以选择接在前一个序列后面,则和为dp[i-1]+nums[i]
,也可以选择自己单开一个序列,则和为nums[i]
,选一个大的更新即可。
通过代码:
class Solution {
public:int maxSubArray(vector<int>& nums) {vector<int> dp(nums.size(), 0);dp[0] = nums[0];int res = dp[0];for(int i = 1; i < nums.size(); i++){dp[i] = max(nums[i], dp[i - 1] + nums[i]);res = max(res, dp[i]);}return res;}
};
392.判断子序列
力扣题目链接
题目:给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"
是"abcde"
的一个子序列,而"aec"
不是)。
思路一:很容易想到通过双指针遍历两个串即可。
通过代码:
class Solution {
public:bool isSubsequence(string s, string t) {int i = 0, j = 0;while(i < s.size() && j < t.size()){if(s[i] == t[j])i++;j++;}return i == s.size();}
};
思路二:动态规划。dp[i][j]
表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]
。如果s[i-1]和t[j-1]相等,相同子序列长度自然要在dp[i-1][j-1]
的基础上加1。如果不相等,就相当于t[j-1]
没出现过,结果还是和dp[i][j-1]
一样。
通过代码:
class Solution {
public:bool isSubsequence(string s, string t) {int len1 = s.size(), len2 = t.size();vector<vector<int>> dp(len1 + 1, vector<int> (len2 + 1, 0));for(int i = 1; i <= len1; i++)for(int j = 1; j <= len2; j++){if(s[i - 1] == t[j - 1])dp[i][j] = dp[i - 1][j - 1] + 1;elsedp[i][j] = dp[i][j - 1];}return dp[len1][len2] == len1;}
};
115.不同的子序列
力扣题目链接
题目:给你两个字符串s
和t
,统计并返回在s
的子序列中t
出现的个数,结果需要对$ 10^9+7 $取模。
思路:dp[i][j]
表示以j为结尾的s子序列中出现以i为结尾的t的个数为dp[i][j]
。当s[i]与t[j]相等时,dp[i][j]
可以有两部分组成。一部分是用s[j]来匹配,那么个数为dp[i - 1][j - 1]
。即不需要考虑当前s子串和t子串的最后一位字母,所以只需要dp[i-1][j-1]
。另一部分是不用s[j]来匹配,个数为dp[i][j - 1]
,两部分相加即为总个数。当s[j]与t[i]不相等时,dp[i][j]
肯定无法用s[j]来匹配,个数即为dp[i][j-1]
。初始化比较特殊,需要考虑t[0]在s中的子序列个数。
通过代码:
class Solution {
public:int numDistinct(string s, string t) {const int mod = 1e9 + 7;int len1 = s.size(), len2 = t.size();vector<vector<int>> dp(len2, vector<int> (len1, 0));if(s[0] == t[0])dp[0][0] = 1;for(int i = 1; i < len1; i++)if(t[0] == s[i])dp[0][i] = dp[0][i - 1] + 1;elsedp[0][i] = dp[0][i - 1];for(int i = 1; i < len2; i++)for(int j = 1; j < len1; j++){if(t[i] == s[j])dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - 1]) % mod;elsedp[i][j] = dp[i][j - 1];}return dp[len2 - 1][len1 - 1];}
};
583. 两个字符串的删除操作
力扣题目链接
题目:给定两个单词word1
和word2
,返回使得word1
和word2
相同所需的最小步数。每步可以删除任意一个字符串中的一个字符。
思路:删完之后剩的不就是最长公共子序列吗。所以这道题和最长公共子序列一样的,求出最长公共子序列的长度之后,用两个单词的长度和减去最长公共子序列的长度就好了。
通过代码:
class Solution {
public:int minDistance(string word1, string word2) {int len1 = word1.size(), len2 = word2.size();vector<vector<int>> dp(len1 + 1, vector<int> (len2 + 1, 0));for(int i = 1; i <= len1; i++)for(int j = 1; j <= len2; j++){if(word1[i - 1] == word2[j - 1])dp[i][j] = dp[i - 1][j - 1] + 1;elsedp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);}return len1 + len2 - 2 * dp[len1][len2];}
};
72. 编辑距离
力扣题目链接
题目:给你两个单词word1
和word2
, 请返回将word1
转换成word2
所使用的最少操作数 。你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
思路:dp[i][j]
表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最少编辑次数为dp[i][j]
。如果正在比较的两个字母相等,说明不用任何操作,最少编辑次数还是前一次的次数dp[i-1][j-1]
。如果不相等,此时就有三种操作了:插入、删除和替换。
首先插入和删除操作需要的次数是一样的。例如单词ad和单词a,可以删除第一个单词的d,也可以在第二个单词末尾添加一个d,所需次数都是1。因此只需要考虑删除操作即可。删除可以删word1[i-1]
也可以删word2[j-1]
,对应的次数分别为dp[i-1][j]+1
和dp[i][j-1]+1
。
对于替换操作,替换完成之后当前比较的两个字母都是一样的了。就类似于正在比较的两个字母相等的情况,次数为dp[i-1][j-1]+1
。
上述三者取最小的更新状态即可。初始化时,由于一个单词长度为0,所以另一个单词只能删除全部字母,因此初始化为另一个单词的字母数即可。
通过代码:
class Solution {
public:int minDistance(string word1, string word2) {int len1 = word1.size(), len2 = word2.size();vector<vector<int>> dp(len1 + 1, vector<int> (len2 + 1, 0));for(int i = 0; i <= len1; i++)dp[i][0] = i;for(int i = 0; i <= len2; i++)dp[0][i] = i;for(int i = 1; i <= len1; i++)for(int j = 1; j <= len2; j++){if(word1[i - 1] == word2[j - 1])dp[i][j] = dp[i - 1][j - 1];elsedp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;}return dp[len1][len2];}
};
647. 回文子串
力扣题目链接
题目:给你一个字符串s
,请你统计并返回这个字符串中回文子串的数目。回文字符串是正着读和倒过来读一样的字符串。子字符串是字符串中的由连续字符组成的一个序列。
思路一:动态规划。dp[i][j]
表示区间[i,j]的子串是否是回文子串。
- 如果字符s[i]和s[j]不同,区间[i,j]肯定不是回文串,
dp[i][j]
为false; - 如果字符s[i]和s[j]相同,
- 如果i和j相同,即整个区间只有一个字符,那区间[i,j]还是回文串,
dp[i][j]
为true; - 如果i和j相差1(相邻),即整个区间只有两个字符,那区间[i,j]还是回文串,
dp[i][j]
为true; - 如果i和j不相邻,区间[i+1, j-1]是回文串那整个就是回文串,即
dp[i][j]
取决于dp[i+1][j-1]
。
- 如果i和j相同,即整个区间只有一个字符,那区间[i,j]还是回文串,
通过代码:
class Solution {
public:int countSubstrings(string s) {int res = 0;int n = s.size();vector<vector<bool>> dp(n, vector<bool> (n, false));for(int i = n - 1; i >= 0; i--)for(int j = i; j < n; j++){if(s[i] == s[j]){if(j - i <= 1) // 情况一和情况二{dp[i][j] = true;res++;}else if(dp[i + 1][j - 1]){dp[i][j] = true;res++;}}}return res;}
};
思路二:双指针。判断回文子串可以从中心向两边扩散判断,依次枚举中心即可,注意中心可能有一个字符也可能是两个字符。
通过代码:
class Solution {
public:int extend(string s, int i, int j){int res = 0;while(i >= 0 && j < s.size() && s[i] == s[j]){i--;j++;res++;}return res;}int countSubstrings(string s) {int res = 0;for(int i = 0; i < s.size(); i++){res += extend(s, i, i); // 以i为中心向两边扩散res += extend(s, i, i + 1); // 以i和i+1为中心向两边扩散}return res;}
};
516.最长回文子序列
力扣题目链接
题目:给你一个字符串s
,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
思路:dp[i][j]
表示区间[i,j]范围内最长回文子序列的长度。如果s[i]==s[j]
,在s[i+1, j-1]两边加上相同的字符s[i]和s[j]就能得到新的回文子序列,因此dp[i][j]=dp[i+1][j-1]+2
;如果s[i]!=s[j]
,则要考虑单独加入哪个字母能够使得长度更大,即dp[i][j]=max(dp[i][j-1], dp[i+1][j])
。
注意,i和j相同的时候需要手动初始化长度为1。
通过代码:
class Solution {
public:int longestPalindromeSubseq(string s) {int n = s.size();vector<vector<int>> dp(n, vector<int> (n, 0));for(int i = 0; i < n; i++)dp[i][i] = 1;for(int i = n - 1; i >= 0; i--)for(int j = i + 1; j < n; j++){if(s[i] == s[j])dp[i][j] = dp[i + 1][j - 1] + 2;elsedp[i][j] = max(dp[i][j - 1], dp[i + 1][j]);}return dp[0][n - 1];}
};
相关文章:
LeetCode【代码随想录】刷题(动态规划篇)
509. 斐波那契数 力扣题目链接 题目:斐波那契数(通常用F(n)表示)形成的序列称为斐波那契数列 。该数列由0和1开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) 0,F(1) 1 F(n) F(n - 1) F(n…...
【看海的算法日记✨优选篇✨】第三回:二分之妙,寻径中道
🎬 个人主页:谁在夜里看海. 📖 个人专栏:《C系列》《Linux系列》《算法系列》 ⛰️ 一念既出,万山无阻 目录 📖一、算法思想 细节问题 📚左右临界 📚中点选择 📚…...
基于yolov8、yolov5的铝材缺陷检测识别系统(含UI界面、训练好的模型、Python代码、数据集)
摘要:铝材缺陷检测在现代工业生产和质量管理中具有重要意义,不仅能帮助企业实时监控铝材质量,还为智能化生产系统提供了可靠的数据支撑。本文介绍了一款基于YOLOv8、YOLOv5等深度学习框架的铝材缺陷检测模型,该模型使用了大量包含…...
计算机光电成像理论基础
一、透过散射介质成像 1.1 光在散射介质中传输 光子携带物体信息并进行成像的过程是一个涉及光与物质相互作用的物理现象。这个过程可以分为几个步骤来理解: 1. **光的发射或反射**: - 自然界中的物体可以发射光(如太阳)&am…...
【QNX+Android虚拟化方案】125 - 如何创建android-spare镜像
【QNX+Android虚拟化方案】125 - 如何创建android-spare镜像 1. Android侧创建 (ext4 / sparse) test_img.img 镜像 方法一2. Android侧创建 (ext4 / sparse) test_img.img 镜像 方法二3. qnx 侧 分区透传Android 配置3.1 配置分区透传3.2 Android 侧分区 rename3.3 创建挂载目…...
深度学习基础小结_项目实战:手机价格预测
目录 库函数导入 一、构建数据集 二、构建分类网络模型 三、编写训练函数 四、编写评估函数 五、网络性能调优 鲍勃开了自己的手机公司。他想与苹果、三星等大公司展开硬仗。 他不知道如何估算自己公司生产的手机的价格。在这个竞争激烈的手机市场,你不能简单地…...
EMall实践DDD模拟电商系统总结
目录 一、事件风暴 二、系统用例 三、领域上下文 四、架构设计 (一)六边形架构 (二)系统分层 五、系统实现 (一)项目结构 (二)提交订单功能实现 (三࿰…...
【随笔】AI技术在电商中的应用
这几年,伴随着ChatGPT开始的AI浪潮席卷全球,从聊天场景逐步向多场景扩散,形成了广泛开花的现象。至今,虽然在部分场景的进展已经略显疲态,但当前的这种趋势仍然还在不断的扩展。不少公司,甚至有不少大型电商…...
序列式容器详细攻略(vector、list)C++
vector std::vector 是 STL 提供的 内存连续的、可变长度 的数组(亦称列表)数据结构。能够提供线性复杂度的插入和删除,以及常数复杂度的随机访问。 为什么要使用 vector 作为 OIer,对程序效率的追求远比对工程级别的稳定性要高得多,而 vector 由于其对内存的动态处理,…...
快速启动项目
1 后端项目 https://gitee.com/liuyunkai666/gungun-boot.git 分支: mini 是 springboot3 jdk17 的基础版本,后续其他功能模块陆续在其基础上追加即可。 1.1 必备环境 1.1.1 mysql 创建一个 自定义名称 数据库,【只要】 执行对应数据库…...
springboot347基于web的铁路订票管理系统(论文+源码)_kaic
摘 要 当今社会进入了科技进步、经济社会快速发展的新时代。计算机技术对经济社会发展和人民生活改善的影响也日益突出,人类的生存和思考方式也产生了变化。传统铁路订票管理采取了人工的管理方法,但这种管理方法存在着许多弊端,比如效率低…...
使用API管理Dynadot域名,在账户中添加域名服务器(Name Server)
前言 Dynadot是通过ICANN认证的域名注册商,自2002年成立以来,服务于全球108个国家和地区的客户,为数以万计的客户提供简洁,优惠,安全的域名注册以及管理服务。 Dynadot平台操作教程索引(包括域名邮箱&…...
【Linux | 计网】TCP协议深度解析:从连接管理到流量控制与滑动窗口
目录 前言: 1、三次握手和四次挥手的联系: 为什么挥手必须要将ACK和FIN分开呢? 2.理解 CLOSE_WAIT 状态 CLOSE_WAIT状态的特点 3.FIN_WAIT状态讲解 3.1、FIN_WAIT_1状态 3.2、FIN_WAIT_2状态 3.3、FIN_WAIT状态的作用与意义 4.理解…...
go语言的成神之路-筑基篇-对文件的操作
目录 一、对文件的读写 Reader 接口 Writer接口 copy接口 bufio的使用 ioutil库 二、cat命令 三、包 1. 包的声明 2. 导入包 3. 包的可见性 4. 包的初始化 5. 标准库包 6. 第三方包 7. 包的组织 8. 包的别名 9. 包的路径 10. 包的版本管理 四、go mod 1. 初始…...
两道数据结构编程题
1.写出在顺序存储结构下将线性表逆转的算法,要求使用最少的附加空间。 解:输入:长度为n的线性表数组A(1:n) 输出:逆转后的长度为n的线性表数组A(1:n)。 C语言描述如下(其中ET为数据元素的类型):…...
【Qt】QDateTimeEdit控件实现清空(不保留默认时间/最小时间)
一、QDateTimeEdit控件 QDateTimeEdit 提供了一个用于编辑日期和时间的控件。用户可以通过键盘或使用上下箭头键来增加或减少日期和时间值。日期和时间的显示格式根据设置的格式显示,可以通过 setDisplayFormat() 方法来设置。 二、如何清空 我在使用的时候&#…...
12、字符串
1、字符串概念 字符串用来存储一组字符,因此需要字符数组来存。 C语言中字符串的表示 C语言里面字符串只能用字符数组来存 字符串要求这个数组的末尾必须至少有一个\0 char ch1[] {a,b,c}; // 不是字符串 char ch2[5] {h,e,l,l,o}; // 不是字符串 char…...
DPDK用户态协议栈-Tcp Posix API 1
和udp一样,我们需要实现和系统调用一样的接口来实现我们的tcp server。先来看看我们之前写的unix_tcp使用了哪些接口,这边我加上两个系统调用,分别是接收数据和发送数据。 #include <stdio.h> #include <arpa/inet.h> #include …...
【人工智能-科普】图神经网络(GNN):与传统神经网络的区别与优势
文章目录 图神经网络(GNN):与传统神经网络的区别与优势什么是图神经网络?图的基本概念GNN的工作原理GNN与传统神经网络的不同1. 数据结构的不同2. 信息传递方式的不同3. 模型的可扩展性4. 局部与全局信息的结合GNN的应用领域总结图神经网络(GNN):与传统神经网络的区别与…...
LabVIEW实现UDP通信
目录 1、UDP通信原理 2、硬件环境部署 3、云端环境部署 4、UDP通信函数 5、程序架构 6、前面板设计 7、程序框图设计 8、测试验证 本专栏以LabVIEW为开发平台,讲解物联网通信组网原理与开发方法,覆盖RS232、TCP、MQTT、蓝牙、Wi-Fi、NB-IoT等协议。 结合实际案例,展示如何利…...
[pdf,epub]228页《分析模式》漫谈合集01-45提供下载
《分析模式》漫谈合集01-45的pdf、epub文件提供下载。已上传至本号的CSDN资源。 如果CSDN资源下载有问题,可到umlchina.com/url/ap.html。 已排版成适合手机阅读,pdf的排版更好一些。 ★UMLChina为什么叒要翻译《分析模式》? ★[缝合故事]…...
Kafka的消费消息是如何传递的?
大家好,我是锋哥。今天分享关于【Kafka的消费消息是如何传递的?】面试题。希望对大家有帮助; Kafka的消费消息是如何传递的? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Kafka中,消息的消费是通过消费…...
二分查找(Java实现)(1)
二分查找(Java实现)(1) leetcode 34.排序数组中查找元素第一个和最后一个位置 题目描述: 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如…...
力扣103.二叉树的锯齿形层序遍历
题目描述 题目链接103. 二叉树的锯齿形层序遍历 给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。 示例 1ÿ…...
Search with Orama
1.前言 在不久之前,我把 DevNow 的搜索组件通过 Lunr 进行了重构,从前端角度实现了对文章内容的搜索,但是在使用体验上,感觉不是特别好,大概有如下几个原因: 社区的文章数量比较少,项目的 Com…...
一万台服务器用saltstack还是ansible?
一万台服务器用saltstack还是ansible? 选择使用 SaltStack 还是 Ansible 来管理一万台服务器,取决于几个关键因素,如性能、扩展性、易用性、配置管理需求和团队的熟悉度。以下是两者的对比分析,帮助你做出决策: SaltStack&…...
计算机类大厂实习春招秋招开发算法面试问答练习题
计算机类大厂实习春招秋招开发算法面试问答练习题 下面有十个非常重要且常问,面试者却注意不到的问题,我们一个个来看,一个个来学。 线程创建到删除过程中,底层是怎么实现的 1.线程创建 线程创建是线程生命周期的起点。在操作系统中,线程可以通过多种方式创建,但无论哪…...
【热门主题】000068 筑牢网络安全防线:守护数字世界的坚实堡垒
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 【热…...
RPC与HTTP调用模式的架构差异
RPC(Remote Procedure Call,远程过程调用)和 HTTP 调用是两种常见的通信模式,它们在架构上有以下一些主要差异: 协议层面 RPC:通常使用自定义的二进制协议,对数据进行高效的序列化和反序列化&am…...
计算机网络之传输层协议UDP
个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 计算机网络之传输层协议UDP 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记,欢迎大家在评论区交流讨论💌 目…...
网页标准化对网站开发维护的好处/海南百度推广代理商
网上说的基本都是使用express或http-server作为服务器或其它什么东西自己把玩php也有些年头,就用php好了 服务环境 apache,php先配置好隐藏php后缀扩展名: 在httpd.conf中 FilesMatch 标签内增加:ForceType application/x-httpd-php 这样只针…...
做公司标志用哪个网站/百度软文推广公司
近年来,大规模的个人信息泄漏事件不断发生,由此引发的精准诈骗也经常被媒体报道。有着庞大用户群体和海量交易的阿里巴巴却能独善其身,这背后有什么独门秘籍呢?当我们表明来意时,阿里安全技术平台资深专家玄泰反复提到…...
如何用图片做网站背景/百度投流运营
java中int和Integer的区别 一看就懂int 是基本类型,直接存数值integer是对象,用一个引用指向这个对象1.Java 中的数据类型分为基本数据类型和复杂数据类型int 是前者>>integer 是后者(也就是一个类)Integer 是对象类型 int是原始类型 适用场合有很…...
景观设计公司理念/南宁百度seo排名优化
假设都不太安全,但是我很确信如果我大声宣布小孩爱玩具,你一定就会同意了。是的,他们也喜欢卡通。现在又出现了一个趋势就是:他们还喜欢iPad。有人说这曾是2010年出现的新玩具Y Combinator下63家企业中的一个新创业公司今天出来鸟…...
网站建设及优化 赣icp/免费网站的平台
修改接收端的WCF config文件,注意,如果server端接收request超长,则修改server端config,如果是client 接收response超长,则修改client端,建议两边都修改,保持一致 <bindings> <wsHttpBinding> <binding name"…...
如何在自己建设的网站上发表文章/长尾关键词有哪些
vueconf(2018hangzhou)大会刚刚过去,vue作者尤大大向我们展示了vue3.0的进展,并介绍vue3.0的一些改动,其中最令我期待的就是重写数据监听机制。 回顾vue2.x的双向数据绑定 谈起vue的双向数据绑定,我们首先能想到的就是ES5中Obje…...