leetcode
78.给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择
class Solution {
vector<int>path;
vector<vector<int>> res;
void backtrack(vector<int>nums,vector<int>&path,int start)
{
res.push_back(path);//结束返回值
for(int i=start;i<nums.size();i++) //进入选择列表,每次进入不同起始位置
{
path.push_back(nums[i]);//做出选择
backtrack(nums,path,i+1);//递归进入下一层,注意i+1,标识下一个选择列表的开始位置,最重要的一步
path.pop_back();//撤销选择
}
}
public:
vector<vector<int>> subsets(vector<int>& nums) {
backtrack(nums,path,0 );//调用回溯
return res;
}
};
90给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列(bt)
class Solution {
vector<vector<int>> res;
vector<int> path;
void backtrack(vector<int>& nums,vector<int>&path,int start)
{
res.push_back(path);//结束
for(int i=start;i<nums.size();i++)//选择分支列表
{
if(i>start&&nums[i]==nums[i-1])//剪枝去重
continue;
path.push_back(nums[i]);//选择
backtrack(nums,path,i+1);//下层
path.pop_back();//撤销返回上层
}
}
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());
backtrack(nums, path, 0);
return res;
}
};
//结,选,剪,选,下,上。
53.给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。(dp)
注:考虑重点是前面元素的正负
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int len = nums.size();
vector<int> dp(len);
dp[0] = nums[0];
for(int i=1; i<len; i++){
if(dp[i-1]>=0){
dp[i] = dp[i-1] + nums[i];
}
else{
dp[i] = nums[i];
}
}
return *max_element(dp.begin(), dp.end());
}
};
152.给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
子数组 是数组的连续子序列
注:乘法时考虑重点当前元素的正负性,当前元素为正时举例(-1,3)和(1,3)
为负举例(-1,-3)(1,-3)
class Solution {
public:
int maxProduct(vector<int>& nums) {
int len = nums.size();
vector<int> maxdp(len);
vector<int> mindp(len);
mindp[0] = nums[0];
maxdp[0] = nums[0];
for(int i=1;i<len; i++){
if(nums[i]>=0){
mindp[i] = min(mindp[i-1]*nums[i],nums[i]);
maxdp[i] = max(maxdp[i-1]*nums[i],nums[i]);
}
else{
mindp[i] = min(maxdp[i-1]*nums[i],nums[i]);
maxdp[i] = max(mindp[i-1]*nums[i],nums[i]);
}
}
return *max_element(maxdp.begin(), maxdp.end());
}
};
239给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
学习重点
一.优先队列:
1.构造
priority_queue
priority_queue
2.函数
top 访问队头元素
empty 队列是否为空
size 返回队列内元素个数
push 插入元素到队尾 (并排序)
emplace 原地构造一个元素并插入队列,和vector中的emplace(pos,value)位置加数值
pop 弹出队头元素
swap 交换内容
3. while (q.top().second <= i - k) {
q.pop();
}//滑动窗口剔除元素,=也行
4.二元组:更加方便判断元素个数
4.总结:优先队列,初始化窗口,移动窗口
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
priority_queue<pair<int, int>> q;//构造优先队列,即大根堆作为滑动窗口,每次最大值存放在队头
//第一步:初始化滑动窗口,把二元组插入q中
for (int i = 0; i < k; ++i) {
q.emplace(nums[i], i);
}
vector<int> ans = {q.top().first};//获取最大值top获取根堆顶元素,即最大值
//第二步:开始滑动
for (int i = k; i < n; ++i) {
q.emplace(nums[i], i);//新元素加入
//去掉第一个元素,这里相当于用二元组表示的len
while (q.top().second <= i - k) {
q.pop();
}
ans.push_back(q.top().first);//获取每个窗口最大值
}
return ans;
}
};
295.中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。(比较,加入,调整)
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
注:1.大根堆存放较小的元素,小根堆存放较大的一半元素这样保证max.top()或者min.top()是中位数
2.选一个堆作为存储中位数的堆
class MedianFinder {
public:
priority_queue<int, vector<int>, less<int>> maxheap;//大根堆降序
priority_queue<int, vector<int>, greater<int>> minheap;//小根堆升序
MedianFinder() {}//构造函数
void addNum(int num) {
//默认先加入大根堆并且大根堆的最大值作为中位数(或者小根堆的最小值),
//小于中位数加入存放小元素的大根堆,调整大根堆和小根堆的数据量,保证相差最大为1
if(maxheap.empty()||num<=maxheap.top()){
maxheap.push(num);
if(minheap.size()+1<maxheap.size()){
minheap.push(maxheap.top());
maxheap.pop();
}
}
else{
minheap.push(num);
if(maxheap.size()<minheap.size()){
maxheap.push(minheap.top());
minheap.pop();
}
}
}
double findMedian() {
if(minheap.size()<maxheap.size()){
return maxheap.top();
}
else return (minheap.top()+maxheap.top())/2.0;
}
};
228.给定一个 无重复元素 的 有序 整数数组 nums 。
返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。
1.字符穿存储形式使用to_string转换格式
2.设置起点终点双指针,更新str,重要的是每次判断是否起点终点不在一起即没有发生更新
3.这种返回子串的,大多要把子串用一个容器接受更新,再放入大的容器中。
class Solution {
public:
vector<string> summaryRanges(vector<int>& nums) {
vector<string> res;
for(int i=0;i<nums.size();i++){
int start = i;//设置起点
string str=to_string(nums[start]);//特点可知每次第一个点i都会加入str中
while(i<nums.size()-1 && nums[i]+1==nums[i+1]) i++;//更新i,即重点位置,注意i的范围
//判断i是否更新过,更新过则更新str,没有则直接加入结果
if(start!=i){
str+="->" + to_string(nums[i]);
}
res.push_back(str);
}
return res;
}
};
163给定一个排序的整数数组 nums ,其中元素的范围在 闭区间 [lower, upper] 当中,返回不包含在数组中的缺失区间。
从题意可知, nums 肯定在 [lower, upper] 闭区间内。
遍历 nums 数组,定义一个变量 pre 用来表示遍历到的数字的前一个数字。
pre 初始值为 lower - 1.
每遍历一个数字:
1、如果 nums[i] - pre == 2,说明 pre 和 nums[i] 中间缺失了一个数字,这个缺失的数字就是 pre + 1,也可以用 nums[i] - 1 表示。
2、如果 nums[i] - pre >= 3,说明 pre 和 nums[i] 之间缺失了两个以上的数字,缺失的数字的范围是 pre + 1 到 nums[i] - 1。
3、每处理完一个 nums[i],更新 pre 为 nums[i]。即,对于 nums[i+1] 来说,它的前一个数字就是 nums[i]。
4、遍历完毕 nums[] 数组,此时 pre == nums[nums.length - 1],
4.1 如果 upper - pre == 1,说明 nums[] 数组最后一个数字尚未达到边界,还差一个,这个数字为 pre + 1,也可以用 upper - 1 表示;
4.2 如果 upper - pre >= 2,说明 nums[] 数组最后一个数字尚未到达边界,还差两个以上的数字,区间为 pre + 1 到 upper。
class Solution {
public:
vector<string> findMissingRanges(vector<int>& nums, int lower, int upper) {
long pre = long lower -1;
vector<string> res;
for(int i =0 ; i<nums.size();i++){
if(nums[i]-pre==2){
res.push_back(to_string(pre+1))
}
else if(nums[i] - pre>=3){
res.push_back(to_string(pre+1)+"->"+to_string(nums[i]-1))
}
pre = nums[i];
}
if(upper-pre==1){
res.push_back(to_string(upper))
}
else if(upper -pre>=2){
res.push_back(to_string(pre+1)+"->"+to_string(upper))
}
return res;
}
};
209给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。(变长滑动窗口)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int start=0, end=0;
int sum=0;
int len=nums.size();
int res=INT_MAX;//需要不断比较获取最小长度,所以为了获得第一段长度可以初始化长度为无穷大
if(len==0) return 0;
while(end<len){
sum+=nums[end];//更新和
while(sum>=target){//符合条件更新
res = min(res,end-start+1);//取最小长度
sum=sum-nums[start];//更新窗口内和,以及start位置,注意的是该循环内第一次写我写成if,导致下以内窗口内和不会再次判断,出错。
start++;
}
end++;//没有符合时,end后移
}
return res==INT_MAX? 0:res;//判断最后是否存在大于目标的和
}
};
238给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请不要使用除法,且在 O(n) 时间复杂度内完成此题
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int length = nums.size();
// L 和 R 分别表示左右两侧的乘积列表
vector<int> L(length, 0), R(length, 0);
vector<int> answer(length);
// L[i] 为索引 i 左侧所有元素的乘积
// 对于索引为 '0' 的元素,因为左侧没有元素,所以 L[0] = 1
L[0] = 1;
for (int i = 1; i < length; i++) {
L[i] = nums[i - 1] * L[i - 1];
}
// R[i] 为索引 i 右侧所有元素的乘积
// 对于索引为 'length-1' 的元素,因为右侧没有元素,所以 R[length-1] = 1
R[length - 1] = 1;
for (int i = length - 2; i >= 0; i--) {
R[i] = nums[i + 1] * R[i + 1];
}
// 对于索引 i,除 nums[i] 之外其余各元素的乘积就是左侧所有元素的乘积乘以右侧所有元素的乘积
for (int i = 0; i < length; i++) {
answer[i] = L[i] * R[i];
}
return answer;
}
};
128.给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
哈希表:将元素按照哈希函数得到的数组下标,分配到数组不同下标位置上,根据冲突函数(拉链法常用)解决地址冲突问题。
思路:
1.构建哈希表,去重,也更方便访问插入查找(链式法则避免地址冲突)
2.先判断每个值是否可以作为起始值,不存在比他小1的值,保证数据从最小的开始
设置当前元素curnum不断往后寻找连续元素,更新当前长度和最大长度。
重点
1.unordered_set nums_set;构建哈希表
具有常用函数如下:
empty() : 空则返回true
size(): unordered_set中的元素个数
max_size(): unordered_set的最大容量(因为unordered_set是线性存储的,会有大小上限)
find():查找,如果能找到,则返回迭代器,如果找不到,则返回unordered_set::end()
count():返回与k的哈希值相同的元素个数,k存在find(k)!=unoredered_set::end(),count(k)>0,本体就使用该特点判断是否存在左值和右值。
insert():返回值为pair
emplace():更加节约空间,将插入和构造合并了,减少了中间变量的产生
erase() 删除:1.按照位置删除,返回下一个元素的迭代器,2.按照值删除,返回下一个元素的迭代器3.按照值或者位置删除,返回删除元素
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
//构建哈希表,哈希表是集合没有重复元素
unordered_set<int> nums_set;
for(const int& num : nums){
nums_set.emplace(num);
}
int maxlen = 0;
int curnum;
int curlen;
for(const int& num : nums_set){
if(!nums_set.count(num-1)){
curlen = 1;
curnum = num;
while(nums_set.count(curnum+1)){
curnum++;
curlen++;
}
maxlen = max(maxlen,curlen);
}
}
return maxlen;
}
};