✨✨欢迎来到T_X_Parallel的博客!!
🛰️博客主页:T_X_Parallel
🛰️专栏 : C++算法题
🛰️欢迎关注:👍点赞🙌收藏✍️留言
第一题 最大子数组和
题目
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
进阶:
如果你已经实现复杂度为 O(n)
的解法,尝试使用更为精妙的 分治法 求解。
题目解析
像这种找一个特定的连续子数组,第一种方法就是暴力求解,遍历出所有的连续字串,找和最大的即可,但是有些题目或者在面试的时候,肯定是用不了暴力求解的,因为没有什么技术含量并且时间复杂度为O(N²)
。
第二种方法使用的是动态规划思想,我们要找最大和连续子数组,我们可以遍历一遍找以每个数字结尾的最大和连续子数组,在更新这个动态规划数组的时候,要看前一个位置的dp数组的大小来更新当前位置,当前位置的数字加上上一个位置的dp数组的值如果比这个数字大,那这个位置的dp数组更新为两者之和,如果比这个数字小,那这个位置的dp数组更新为这个数字,就是找一个最大值
假设输入数组为nums
,动态规划数组为dp
,i
位置为以i
位置数字结尾的最大和连续子数组,那么i+1
位置就有以下两种情况
- 当
dp[i] < 0
时,dp[i + 1] = nums[i + 1]
因为
dp[i] < 0
说明以i
位置的数字结尾的最大和子数组为负数,那么不管i+1
位置的数字是多少,以i+1
位置结尾的最大和子数组一定等于num[i + 1]
- 当
dp[i] >= 0
时,dp[i + 1] = nums[i + 1] + dp[i]
因为
dp[i] >= 0
说明以i
位置的数字结尾的最大和子数组为非负数,那么不管i+1
位置的数字是多少,以i+1
位置结尾的最大和子数组一定等于num[i + 1] + dp[i]
(dp[i] == 0是特殊值,两种情况都可以)任何一个数加上一个负数一定小于这个数原本大小,任何一个数加上一个非负数一定等于数字本身或者比原本数字大
注:dp数组的第一个位置需要特殊处理,可以多增添一个位置,还可以提前初始化第一个位置
事实上,这个题目还有第三种方法——分治思想解题,毕竟题目中的进阶提示中也是建议尝试使用分治思想,但是使用这个方法相较于动态规划来说,时间复杂度相同,但是分治方法既要使用递归又要维护结构体,运行时间和空间复杂度都不如动态规划优秀,博主就不介绍这个方法了,感兴趣想要研究这个方法以及代码可以查看leetcode官方题解——最大子数组和,在官方题解最后也给出了这种方法在一些题目中的优势
代码
int maxSubArray(vector<int>& nums) {
int ret = INT_MIN, n = nums.size();
vector<int> dp(n, 0);
dp[0] = nums[0];
ret = max(ret, dp[0]);
for (int i = 1; i < n; i++)
{
if (dp[i - 1] < 0)
dp[i] = nums[i];
else
dp[i] = nums[i] + dp[i - 1];
ret = max(ret, dp[i]);
}
return ret;
}
时间复杂度:
O(N)
空间复杂度:
O(1)
第二题 除自身以外数组的乘积
题目
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 **不要使用除法,**且在 O(n)
时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
提示:
2 <= nums.length <= 105
-30 <= nums[i] <= 30
- 输入 保证 数组
answer[i]
在 32 位 整数范围内
进阶:
你可以在 O(1)
的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组 不被视为 额外空间。)
题目解析
由于题目中要求不使用除法,并且在O(N)
时间复杂度下解决题目,那么就不能使用暴力求解(直接使用除法和双循环遍历)。
我们观察题目要求,是算出除去自身的其余数字的乘积,那么假设在输入数组最左和最右增添两个元素并元素大小为1
,那么任何一个所要求的值不就是左右所有元素的乘积,只要知道左边所有值的乘积以及右边所有值的乘积,两者相乘即为这个位置的值,那么我们可以从左和从右分别计算每个位置左边所有值的乘积以及每个位置右边所有值的乘积,这样我们就能从两个数组中拿到每个位置左边所有值的乘积以及右边所有值的乘积
原数组只是解题描述中假设在左右添加一个为
1
的元素,但是代码中可以直接提前初始化left
和right
数组最左边和最右边位置的值
进阶中要求使用O(1)
的空间复杂度,并提示输出数组不被视为额外空间,所以我们可以在上面这个方法基础上进行改进,我们可以将left
数组当作输出数组ans
,第一步从左向右的遍历过程不变,第二步从右向左遍历过程中使用的right
数组可以摒弃了,因为上一个方法在最后同样要遍历求乘积,那么不如我们在这个遍历过程直接求乘积,只需要一个int
变量存储遍历到的当前位置右边所有数字的乘积,那么只需让数组中每个遍历到的数字与该变量相乘就得到答案了
和上面一样,
ans
数组和int tmp
需要提前初始化
代码
// 方法一:使用两个数组和一个输出数组
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> forward(n, 1);
vector<int> back(n, 1);
for (int i = 1; i < n; i++)
{
forward[i] = forward[i - 1] * nums[i - 1];
back[n - 1 - i] = back[n - i] * nums[n - i];
}
vector<int> ret(n);
for (int i = 0; i < n; i++)
ret[i] = forward[i] * back[i];
return ret;
}
时间复杂度:
O(N)
空间复杂度:
O(N)
// 方法二:使用一个输出数组和一个int变量
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> ret(n, 1);
for (int i = 1; i < n; i++)
{
ret[i] = ret[i - 1] * nums[i - 1];
}
int sign = 1;
for (int i = n - 1; i >= 0; i--)
{
ret[i] *= sign;
sign *= nums[i];
}
return ret;
}
时间复杂度:
O(N)
空间复杂度:
O(1)
专栏:C++算法题
都看到这里了,留下你们的珍贵的👍点赞+⭐收藏+📋评论吧