leetcode数据结构入门 day1| 217.存在重复元素 53.最大子序和

本文介绍了两种算法方法,动态规划和分治法,用于解决数组中的重复元素检测(方法一、二)及最大子序和计算(方法三、四)。动态规划通过维护一个状态数组或滚动变量,减少了重复计算,而分治法通过递归分解问题并合并信息,解决了区间内的最大子段和问题。这两种方法都是解决数组问题的有效策略。
摘要由CSDN通过智能技术生成

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;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值