什么是时间空间复杂度?
时间空间增长的趋势:
随着问题量级的增大,时间空间增长的趋势,就能用bigO来表示
198.打家劫舍
主要思路:
// dp[i]的含义:i的范围为0~size-1
// 因此对于nums[i]偷还是不偷这个问题所能产生的最大收益
代码如下:
// 198.打家劫舍问题
int rob(vector<int>& nums)
{
if (nums.empty())
{
return 0;
}
int size = nums.size();
if (size == 1)
{
return nums[0];
}
// dp[i]的含义:i的范围为0~size-1
// 因此对于nums[i]偷还是不偷
vector<int> dp = vector<int>(size, 0);
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < size; i++)
{
// max(对于nums[i]偷,对于nums[i]不偷)
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[size - 1];
}
53.最大子序和
主要思路:
可以用动态规划来做,nums[i]选或者不选
int maxSubArray(vector<int>& nums) {
// 最值问题,最优子序列,dp[i]由dp[i-1]推导?
// 定义dp[i] 以nums[i]结尾的最优子序列
int n =nums.size();
vector<int> dp(n, 0);
// base case
dp[0]=nums[0];
// 递归/状态转移方程
for(int i = 1; i<n; i++){
dp[i] = max(dp[i-1]+nums[i], nums[i]);
}
// 接下来求解我们的问题
int res=INT_MIN;
for(int i =0; i<n; i++){
res=max(res, dp[i]);
}
return res;
}
也可以构建前缀和数组来解决这道题,当需要你给出具体的最大子序和的下标时
// 禾赛科技:最小子串和,最小子串和序列的第一个元素和最后一个元素的序列
pair<int, int> findMinSum(vector<int>& nums)
{
int size = nums.size(); // size是原数组的尺寸
vector<int> arr_sum(nums.size() + 1); // 前缀和
// arr_sum[0] = 0;
for (int i = 0; i < size; i++)
{
arr_sum[i + 1] = arr_sum[i] + nums[i];
}
pair<int, int> q;
int min = 0;
// 遍历前缀和的数组
for (int i = 0; i < size; i++)
{
for (int j = i; j < size; j++)
{
// 区间和[i,j]
int sum = arr_sum[j + 1] - arr_sum[i];
# 求最大子序和在这里改成if (sum > max)
if (sum < min)
{
min = sum;
q.first = i;
q.second = j;
}
}
}
return q;
}
152.乘积最大子数组
主要思路:
参考weiwei大神
主要思路:
这一题没法构建子序乘积,因为会有0的情况,没法构建子序的乘法
类比53题用的动态规划方法:以nums[i]结尾的子序的最大乘积为dp[i]
但是,有个情况需要考虑就是当出现负数的情况,因此增加dp的维度减少讨论,同时问题转变为无后效性,因为如果不增加维度,dp[i]可能由前面nums[i-2]为负数决定(dp[i-2])而不是只由dp[i-1]决定
状态的定义
// 明确状态:dp:以 nums[i]结尾的连续子数组的最大值
// 求乘积的最大值,示例中负数的出现,告诉我们这题和 53 题(最大子序和:对于nums[i]偷还是不偷)不一样了
// 一个正数乘以负数就变成负数,即:最大值乘以负数就变成了最小值
// 最大值和最小值是相互转换的,这一点提示我们可以把这种转换关系设计到「状态转移方程」里去
// 如何解决这个问题呢?这里常见的技巧是在「状态设计」的时候,在原始的状态设计后面多加一个维度,减少分类讨论,降低解决问题的难度。
// dp[i][j]:以 nums[i] 结尾的连续子数组的最值,计算最大值还是最小值由 j 来表示,j 就两个值
// 当 j = 0 的时候,表示计算的是最小值
// 当 j = 1 的时候,表示计算的是最大值
代码如下
int maxProduct(vector<int> &nums)
{
int len = nums.size();
if(0==len) return 0;
// dp[i][0]:以 nums[i] 结尾的连续子数组的最小值
// dp[i][1]:以 nums[i] 结尾的连续子数组的最大值
vector<vector<int>> dp(len, vector<int>(2));
// base case:不管咋样,nums[0]还是要偷的
dp[0][0]=nums[0];
dp[0][1]=nums[0];
for(int i=1;i<len;i++){
if(nums[i]>=0){
// 当nums[i]>0, 选还是不选
dp[i][0]=min(nums[i],nums[i]*dp[i-1][0]);
dp[i][1]=max(nums[i],nums[i]*dp[i-1][1]);
}
else{
// 若nums[i]<0,那么最大值dp[i-1][1]*nums[i]变最小值
// 最小值dp[i-1][0]*nums[i]变最大值
dp[i][0]=min(nums[i],nums[i] * dp[i - 1][1]);
dp[i][1]=max(nums[i],nums[i] * dp[i - 1][0]);
}
}
// 只关心最大值,需要遍历
int res=dp[0][1];
for(int i=0;i<len;i++){
res=max(res,dp[i][1]);
}
return res;
}
主要思路:
即环状数组分成两个子数组来解决:第一个房子和最后一个房子只能选择一个来偷窃
题解
class Solution {
public:
int dfs(vector<int> &nums)
{
if (nums.empty())
{
return 0;
}
int size = nums.size();
if (size == 1)
{
return nums[0];
}
// dp[i]的含义:i的范围为0~size-1
// 因此对于nums[i]偷还是不偷
vector<int> dp = vector<int>(size, 0);
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < size; i++)
{
// max(对于nums[i]偷,对于nums[i]不偷)
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[size - 1];
}
// 213.打家劫舍2
int rob(vector<int> &nums)
{
if (nums.empty())
{
return 0;
}
int size = nums.size();
if (size == 1)
{
return nums[0];
}
vector<int>::iterator start = nums.begin();
vector<int>::iterator end = nums.end();
vector<int> nums1(start, end - 1);
vector<int> nums2(start + 1, end);
return max(dfs(nums1), dfs(nums2));
}
};