217.存在重复元素
方法一:事先排序
注意for循环中数组越界的问题。
class solution{
public:
bool containsDuplicate(vector<int>&nums){
sort(nums.begin(),nums.end());
int n=nums.size();
for(int i=0;i<n-1;i++){
if(nums[i]==nums[i+1]{
return true;
}
}
return false;
}
方法二:哈希表
class solution{
public:
bool containsDuplicate(vector<int>&nums){
unordered_set<int>s;
for(int x:nums){
if(s..find(x)!=s.end()){
return true;
}
s.insert(x);
}
return false;
}
53.最大子序和
方法一:动态规划
用 f(i)代表以第 i个数结尾的「连续子数组的最大和」,
只需要求出每个位置的 f(i),然后返回 f数组中的最大值即可
考虑 nums[i]单独成为一段还是加入 f(i−1)对应的那一段,这取决于 nums[i] 和 f(i−1)+nums[i]的大小,我们希望获得一个比较大的,于是可以写出这样的动态规划转移方程:
f(i)=max{f(i−1)+nums[i],nums[i]}
不难给出一个时间复杂度 O(n)、空间复杂度 O(n)的实现,即用一个 f数组来保存 f(i)的值,用一个循环求出所有 f(i)。
考虑到 f(i)只和 f(i−1)相关,于是我们可以只用一个变量 prepre 来维护对于当前 f(i)的 f(i−1)的值是多少,从而让空间复杂度降低到 O(1),这有点类似「滚动数组」的思想。
class solution{
public:
int maxSubArray(vector<int>&nums){
int pre=0,maxAns=num[0];
for(const auto&x:nums){
pre=max(pre+x,x);
maxAns=max(maxAns,pre);
}
return maxAns;
}
复杂度分析:
时间复杂度:O(n),其中 n 为 nums数组的长度。我们只需要遍历一遍数组即可求得答案。
空间复杂度:O(1)。我们只需要常数空间存放若干变量。
什么是动态规划?
动态规划其实就是,给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决。
然后把子问题答案保存起来,以减少重复计算。
再根据子问题答案反推,得出原问题解的一种方法。
一般这些子问题很相似,可以通过函数关系式递推出来。然后呢,动态规划就致力于解决每个子问题一次,减少重复计算,比如斐波那契数列就可以看做入门级的经典动态规划问题。
动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算。
我们按照以上动态规划的解题思路,
- 穷举分析
- 确定边界
- 找规律,确定最优子结构
- 状态转移方程
方法二:分治法
定义一个操作 get(a, l, r) 表示查询 a 序列 [l,r]区间内的最大子段和,
那么最终要求的答案就是 get(nums, 0, nums.size() - 1)。
对于一个区间 [l,r],我们取 m=⌊(l+r)/2⌋,对区间 [l,m]和 [m+1,r]分治求解。
当递归逐层深入直到区间长度缩小为 1的时候,递归「开始回升」。
这个时候我们考虑如何通过 [l,m]区间的信息和 [m+1,r]区间的信息合并成区间 [l,r]的信息。
对于一个区间 [l,r],我们可以维护四个量:
- lSum 表示 [l,r]内以 l为左端点的最大子段和
- rSum 表示 [l,r] 内以 r为右端点的最大子段和
- mSum表示 [l,r]内的最大子段和
- iSum 表示 [l,r]的区间和
[l,m] 为 [l,r] 的「左子区间」,[m+1,r]为 [l,r] 的「右子区间」
对于长度为 1的区间 [i,i],四个量的值都和 nums[i]相等。
对于长度大于 1的区间:
对于[l,r] 的 iSum=「左子区间」的 iSum+「右子区间」的 iSum。
对于[l,r] 的 lSum,要么等于「左子区间」的 lSum,要么等于「左子区间」的 iSum 加上「右子区间」的 lSum,二者取大。
对于 [l,r] 的 rSum,要么等于「右子区间」的 rSum,要么等于「右子区间」的 iSum 加上「左子区间」的 rSum,二者取大。
考虑 [l,r] 的 mSum 对应的区间是否跨越 m
它可能不跨越 m,也就是说 [l,r] 的 mSum 可能是「左子区间」的 mSum和 「右子区间」的 mSum 中的一个;
它也可能跨越 m,可能是「左子区间」的 rSum 和 「右子区间」的 lSum 求和。三者取大。
status是一种函数类型,当函数返回值为函数结果状态代码时,函数定义为Status类型。函数结果状态码:TRUE 1、FALSE 0;OK 1、ERROR 0;INFEASIBLE -1、OVERFLOW -2
class Solution {
public:
struct Status {
int lSum, rSum, mSum, iSum;
};
Status pushUp(Status l, Status r) {
int iSum = l.iSum + r.iSum;
int lSum = max(l.lSum, l.iSum + r.lSum);
int rSum = max(r.rSum, r.iSum + l.rSum);
int mSum = max(max(l.mSum, r.mSum), l.rSum + r.lSum);
return (Status) {lSum, rSum, mSum, iSum};
};
Status get(vector<int> &a, int l, int r) {
if (l == r) {
return (Status) {a[l], a[l], a[l], a[l]};
}
int m = (l + r) >> 1;
Status lSub = get(a, l, m);
Status rSub = get(a, m + 1, r);
return pushUp(lSub, rSub);
}
int maxSubArray(vector<int>& nums) {
return get(nums, 0, nums.size() - 1).mSum;
}
};