4.8
53. 最大子数组和 - 力扣(LeetCode)
思路1:前缀和
我们可以一边遍历数组计算前缀和pre_sum,一边维护前缀和的最小值min_pre_sum(相当于股票最低价格),用当前的前缀和(卖出价格)减去前缀和的最小值(买入价格),就得到了以当前元素结尾的子数组和的最大值(利润),用它来更新答案的最大值ans(最大利润)。
由于题目要求子数组不能为空,应当先计算前缀和-最小前缀和,再更新最小前缀和。相当于不能在同一天买入股票又卖出股票。
如果先更新最小前缀和,再计算前缀和-最小前缀和,就会把空数组的元素和 0 算入答案。
class Solution { public: int maxSubArray(vector<int>& nums) { int ans = INT_MIN; int min_pre_sum = 0; int pre_sum = 0; for(int i : nums){ pre_sum += x; // 当前的前缀和 ans = max(ans, pre_sum - min_pre_sum); // 减去前缀和的最小值 min_pre_sum = min(min_pre_sum, pre_sum); // 维护前缀和的最小值 } return ans; } };复杂度分析
- 时间复杂度:O(n),其中 n 为 nums 的长度。
- 空间复杂度:O(1)。仅用到若干额外变量。
思路二:DP
定义 f[i] 表示以 nums[i] 结尾的最大子数组和。
递推公式
分类讨论:
- nums[i] 单独组成一个子数组,那么 f[i]=nums[i]。
- nums[i] 和前面的子数组拼起来,也就是在以 nums[i−1] 结尾的最大子数组和之后添加 nums[i],那么 f[i]=f[i−1]+nums[i]。
两种情况取最大值,得
f[i]= max(f[i−1],0)+nums[i], i≥1
简单地说,如果 nums[i] 左边的子数组元素和是负的,就不用和左边的子数组拼在一起了。
答案为 max(f)。
初始化
vector<int> dp(nums.size() , 0) dp[0] = nums[0];循环
从左到右
class Solution { public: int maxSubArray(vector<int>& nums) { vector<int> f(nums.size()); f[0] = nums[0]; for (int i = 1; i < nums.size(); i++) { f[i] = max(f[i - 1], 0) + nums[i]; } return ranges::max(f); } }; //或者将dp数组向右平移一个单位 class Solution { public: int maxSubArray(vector<int>& nums) { int n = nums.size(); int res = INT_MIN; vector<int> dp(n + 1 , INT_MIN); for (int i = 1; i <= n; i++) { dp[i] = max(dp[i - 1] , 0) + nums[i - 1]; res = max(res , dp[i]); } return res; } };空间优化
由于计算 f[i] 只会用到 f[i−1],不会用到更早的状态,所以可以用一个变量滚动计算。
状态转移方程简化为:f=max(f,0)+nums[i]
f 可以初始化成 0 或者任意负数。
class Solution { public: int maxSubArray(vector<int>& nums) { int ans = INT_MIN; // 注意答案可以是负数,不能初始化成 0 int f = 0; for (int x : nums) { f = max(f, 0) + x; ans = max(ans, f); } return ans; } };复杂度分析
- 时间复杂度:O(n),其中 n 为 nums 的长度。
- 空间复杂度:O(1)。仅用到若干额外变量。
21. 合并两个有序链表 - 力扣(LeetCode)
1.新建一个链表
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* dummy = new ListNode(0);
ListNode* cur = dummy;
while(list1 && list2){
if(list1->val <= list2->val) {
cur->next = list1;
list1 = list1->next;
}
else {
cur->next = list2;
list2 = list2->next;
}
cur = cur->next;
}
cur->next = list1 ? list1 : list2;
return dummy->next;
}
};
2.递归

//递归
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if (list1 == nullptr) return list2; // 注:如果都为空则返回空
if (list2 == nullptr) return list1;
if (list1->val < list2->val) {
list1->next = mergeTwoLists(list1->next, list2);
return list1;
}
list2->next = mergeTwoLists(list1, list2->next);
return list2;
}
};
415. 字符串相加 - 力扣(LeetCode)
关键改进点
- 逐位计算:从字符串末尾开始逐位相加,避免数值溢出
- 进位处理:显式维护进位值
carry- 动态构建结果:直接生成结果字符串,保留所有有效位
- 前导零兼容性:正确处理含前导零的输入(如
"000" + "00" = "0")
复杂度分析
- 时间复杂度:O(max(m,n)),其中 m,n 为输入字符串长度
- 空间复杂度:O(max(m,n)),存储结果字符串
此解法可处理任意长度的数字字符串相加,完全避免数值类型限制。
class Solution {
public:
string addStrings(string num1, string num2) {
int carry = 0;
int i = num1.size() - 1;
int j = num2.size() - 1;
string res;
while(i >= 0 || j >= 0 || carry){
int n1 = (i >= 0) ? (num1[i --] - '0') : 0;
int n2 = (j >= 0) ? (num2[j --] - '0') : 0;
int sum = n1 + n2 + carry;
carry = sum / 10;
res.push_back(sum % 10 + '0');
}
reverse(res.begin() , res.end());
return res;
}
};
代码细节:
res.push_back(sum % 10 + '0');为什么不能写成res.push_back(sum % 10 - '0');?
根本原因:ASCII编码规则
- 字符转数字时:
'5' - '0' = 5(正确)
通过减去字符'0'的ASCII值(十进制48),可以将字符数字转为实际数值。- 数字转字符时:
5 + '0' = '5'(正确)
通过加上字符'0'的ASCII值(十进制48),可以将数值转为对应的字符。
错误修改后的行为
若将代码改为:
cpp
复制
res.push_back(sum % 10 - '0'); // 错误写法
假设
sum % 10 = 5,则实际计算为:cpp
复制
5 - '0' = 5 - 48 = -43
-43被当作ASCII码解释时,对应不可打印字符(如乱码或空字符)
示例分析
以
sum = 15为例:
正确代码:
15 % 10 + '0' = 5 + 48 = 53 → ASCII字符 '5'结果字符串追加字符
'5'。错误代码:
15 % 10 - '0' = 5 - 48 = -43 → 非法字符结果字符串追加无效字符,最终输出完全错误。
关键结论
操作类型 正确表达式 错误表达式 结果 字符转数字 '5' - '0'- 数值 5 数字转字符 5 + '0'5 - '0'(错误)字符 '5'
- 错误修改的本质:混淆了字符与数字转换的方向性规则。
- 最终影响:生成的字符串包含非法字符,导致结果完全错误。
正确代码片段
int sum = 15; char correct_char = sum % 10 + '0'; // '5' char wrong_char = sum % 10 - '0'; // 非法字符(ASCII值为-43)始终记住:
- 数字 → 字符:加
'0'- 字符 → 数字:减
'0'
300. 最长递增子序列 - 力扣(LeetCode)
dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度。位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。所以:
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);注意这里不是要
dp[i]与dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值。每一个i,对应的
dp[i](即最长递增子序列)起始大小至少都是1.
dp[i]是有0到i-1各个位置的最长递增子序列推导而来,那么遍历i一定是从前向后遍历。j其实就是遍历0到i-1,那么是从前到后,还是从后到前遍历都无所谓,只要吧 0 到 i-1 的元素都遍历了就行了。 所以默认习惯从前向后遍历。
遍历i的循环在外层,遍历j则在内层,代码如下:
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); } if (dp[i] > result) result = dp[i]; // 取长的子序列 }
- 举例推导dp数组
输入:[0,1,0,3,2],dp数组的变化如下:
如果代码写出来,但一直AC不了,那么就把dp数组打印出来,看看对不对!
本题还需注意边界样例
nums.size() == 0 || 1,最后输出的是dp数组的最大值,而不是dp[nums.size() - 1]!!class Solution { public: int lengthOfLIS(vector<int>& nums) { int n = nums.size(); if(n == 0 || n == 1) return n; int res = 0; vector<int> dp(n , 1); for (int i = 1; i < n; 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; } };代码注意点:
- dp数组初始化: vector
dp(n , 1);任何数自身序列长度都为1 - 边界条件:数组长度n <= 1时,return n
- 输出的是过程中统计的最大dp值。而不是输出dp[nums.size() - 1],得到的不一定是最大的,最大子序列不一定包括nums[n - 1]。


浙公网安备 33010602011771号