目录
存在重复元素
给你一个整数数组
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 容器不以键值对的形式存储数据,读者也可以这样认为,即 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() | 返回当前容器使用的哈希函数对象。 |
最大子数组和
难度简单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]来维护 最大连续子数组的值得话,空间复杂度为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(n)。
空间复杂度:递归会使用 O(logn) 的栈空间,故渐进空间复杂度为 O(logn)。
第一天打卡完成!