数据结构-入门篇--1--数组

 

目录

存在重复元素 

方法一:排序

方法二:哈希

C++ unordered_set容器的成员方法

最大子数组和

方法一:贪心

法二:动态规划

方法三:分治(线段树维护)

借鉴本篇博客,描述的很清晰

第一天打卡完成!


存在重复元素 

217. 存在重复元素

给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。

示例 1:

输入:nums = [1,2,3,1]
输出:true

示例 2:

输入:nums = [1,2,3,4]
输出:false

示例 3:

输入:nums = [1,1,1,3,3,4,3,2,4,2]
输出:true

提示:

  • 1 <= nums.length <= 105
  • -109 <= nums[i] <= 109

方法一:排序

给数组先排个序,重复的元素必然会排到相邻的位置,我们遍历一遍排序好的数组,如果存在相邻位置元素相等,即表示存在重复元素。

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

复杂度分析

时间复杂度:O(Nlog N),其中 N 为数组的长度。需要对数组进行排序。

空间复杂度:O(log N),其中 N 为数组的长度。注意我们在这里应当考虑递归调用栈的深度。

方法二:哈希

对于数组中每一个元素,都插入到哈希表中,如果在插入的过程中发现该元素已经在哈希表里,则表示存在重复元素。

 bool containsDuplicate(vector<int>& nums){
	unordered_set<int>s; //无序set 容器
	for(int i:nums)  //for循环范围内枚举
		{
			if(s.find(i)!=s.end())  //如果找到i,则返回一个指向该元素的正向迭代器;
//反之,则返回一个指向容器中最后一个元素之后位置的迭代器
				{
					return true;
				}
			s.insert(i);
		}
	return false;
 }

复杂度分析

  • 时间复杂度:O(N)O(N),其中 NN 为数组的长度。

  • 空间复杂度:O(N)O(N),其中 NN 为数组的长度。

 STL容器之 unordered_set 无序set容器

unordered_set使用详细请看这里

unordered_set 容器具有以下几个特性:

  1. 不再以键值对的形式存储数据,而是直接存储数据的值;
  2. 容器内部存储的各个元素的值都互不相等,且不能被修改。
  3. 不会对内部存储的数据进行排序

对于 unordered_set 容器不以键值对的形式存储数据,读者也可以这样认为,即 unordered_set 存储的都是键和值相等的键值对,为了节省存储空间,该类容器在实际存储时选择只存储每个键值对的值。

实现 unordered_set 容器的模板类定义在<unordered_set>头文件,并位于 std 命名空间中。这意味着,如果程序中需要使用该类型容器,则首先应该包含如下代码:

#include <unordered_set>
using namespace std;

C++ unordered_set容器的成员方法

成员方法功能
begin()返回指向容器中第一个元素的正向迭代器。
end();返回指向容器中最后一个元素之后位置的正向迭代器。
cbegin()和 begin() 功能相同,只不过其返回的是 const 类型的正向迭代器。
cend()和 end() 功能相同,只不过其返回的是 const 类型的正向迭代器。
empty()若容器为空,则返回 true;否则 false。
size()返回当前容器中存有元素的个数。
max_size()返回容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
find(key)查找以值为 key 的元素,如果找到,则返回一个指向该元素的正向迭代器;反之,则返回一个指向容器中最后一个元素之后位置的迭代器(如果 end() 方法返回的迭代器)。
count(key)在容器中查找值为 key 的元素的个数。
equal_range(key)返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中值为 key 的元素所在的范围。
emplace()向容器中添加新元素,效率比 insert() 方法高。
emplace_hint()向容器中添加新元素,效率比 insert() 方法高。
insert()向容器中添加新元素。
erase()删除指定元素。
clear()清空容器,即删除容器中存储的所有元素。
swap()交换 2 个 unordered_map 容器存储的元素,前提是必须保证这 2 个容器的类型完全相等。
bucket_count()返回当前容器底层存储元素时,使用桶(一个线性链表代表一个桶)的数量。
max_bucket_count()返回当前系统中,unordered_map 容器底层最多可以使用多少桶。
bucket_size(n)返回第 n 个桶中存储元素的数量。
bucket(key)返回值为 key 的元素所在桶的编号。
load_factor()返回 unordered_map 容器中当前的负载因子。负载因子,指的是的当前容器中存储元素的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。
max_load_factor()返回或者设置当前 unordered_map 容器的负载因子。
rehash(n)将当前容器底层使用桶的数量设置为 n。
reserve()将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。
hash_function()返回当前容器使用的哈希函数对象。

最大子数组和

53. 最大子数组和

难度简单4597

给你一个整数数组 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) 的解法,尝试使用更为精妙的 分治法 求解。

方法一:贪心

遍历一遍找最大子数组的和,当count加和为负数时,改赋值为0。

通俗点说就是:加到目前为止子数组和为一个负数,当下一个位置 开始计算时,肯定不能带上之前这个结果为负数的子数组“累赘”~   所以执行count=0。

maxmum是一直在记录和更新,子数组的长度 以及 子数组和的值

 int maxSubArray(vector<int>& nums) {
        int n=nums.size();
        int maxmum=-0x3f3f3f;   ///注意这里
        int count=0;
        for(int i=0;i<n;i++)
        {
            count+=nums[i];
            maxmum=max(maxmum,count);
            if(count<=0)
            count=0;
        }
        return maxmum;
 }

需要提醒的一点是:

int maxmum=-0x3f3f3f;

如果不是赋值为一个负无穷,赋值为一个0的话,思想是由漏洞的,(如果整个数组只有负数,结果总不能输出0吧???哈哈)。。。当然也没有必要赋值负无穷,只是一个习惯!   -104 <= nums[i] <= 104,题中给的才1e4..

看了官方题解,  其实赋值为第一个数就行了..太久没做题,思想都打不开了...果然是一个又菜又爱划水的菜狗..T^T

比如下面这组数据:

复杂度分析:

时间复杂度: O(N),只遍历一次数组。
空间复杂度: O(1),只使用了常数空间。

法二:动态规划

f[i]表示,以i数组下标结尾的 最大连续子数组之和,显然 max (f[i]) 即为结果。

和上面想法一样,考虑重新计算,还是要加上count值(上一个提到的变量),于是就有了下面的动态转移方程。

转移方程:

f[i]=max\left \{ nums[i],f[i-1]+nums[i]\right \}

压缩数组:

但是用数组f[i]来维护 最大连续子数组的值得话,空间复杂度为O(n),

因为f[i]只与f[i-1]有关,所以用一个变量pre来记录,空间复杂度为O(1),

int maxSubArray(vector<int>& nums) {
        int n=nums.size();
        int pre=0;
        int maxmum=nums[0];
        for(int i=0;i<n;i++)
        {
            pre=max(pre+nums[i],nums[i]);
            maxmum=max(maxmum,pre);
        }
        return maxmum;
}

复杂度分析:

时间复杂度: O(N),只遍历一次数组。
空间复杂度: O(1), 只使用了常数空间。

方法三:分治(线段树维护)

借鉴本篇博客,描述的很清晰

1.线段树求解最长公共上升子序列问题
2.用get(a,l,r) 代表a序列[l,r]区间内的最大子段和,可以用区间[l,m],[m+1,r]分治求解
3.维护的信息有
        lSum 表示 [l,r]内以 l 为左端点的最大子段和
        rSum 表示 [l,r] 内以 r 为右端点的最大子段和
        mSum 表示 [l,r] 内的最大子段和
        iSum 表示 [l,r] 的区间和
4.如何维护呢
        区间的[l,r] iSum = [l,m]iSum + [m+1,r]iSum 左区间+右区间
        [l,r] lSum = max([l,m] lSum,[l,m] lSum + [m+1,r]iSum)                                                              左边的 或者 左边的加上所有右边的
        [l,r] rSum = max([m+1,r] rSum,[m+1,r] rSum + [l,m] iSum)                                                        右边的 或者 右边加上所有左边
        [l,r] mSum = max([l,m] rSum,[m+1,r] lSum, max(lSum,rSum)                                                   可以根据mSum是否跨越m 没有跨越,就是max(左mSum,右mSum)                                                                             跨越了的话 需要比较[l,m]rSum+[m+1]lSum
5.分治排序 维护

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

复杂度分析:

时间复杂度:递归的过程看作是一颗二叉树的先序遍历,那么这颗二叉树的深度的渐进上界为 O(logn),这里的总时间相当于遍历这颗二叉树的所有节点,故总时间的渐进上界是  O\sum_{i=1}^{logn}2^{i-1}=O\left ( n \right )  故渐进时间复杂度为O(n)。
空间复杂度:递归会使用 O(logn) 的栈空间,故渐进空间复杂度为 O(logn)。

第一天打卡完成!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵩韵儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值