文章目录
- leetcode 53 最大子数组和 E && 剑指offer42 连续子数组的最大和
- leetcode 209 长度最小的子数组 【字节】M
- 剑指offer 2 面试题10 和为k的连续子数组 `【字节】`
- 剑指offer II 009 乘积小于k的子数组
- leetcode 260 只出现一次的数字 III M
- leetcode 274/275 H 指数
- 剑指offer 49 丑数 M
- leetcode 239 【滑动窗口最大值 && 剑指offer 59-I 滑动窗口最大值 H】
- Leetcode 445 两数相加 II
- leetcode 70 爬楼梯 【字节】
- 剑指offer II 008 【爬楼梯的最小成本】
leetcode 53 最大子数组和 E && 剑指offer42 连续子数组的最大和
题目要求:
输入一个整型数组nums
,请你找出一个具有最大和的连续子数组
,返回其最大和;
例如:
输入:nums=[-2,1,-3,4,-1,2,1,-5,4]
输出:6
这个连续数组为 [4,-1,2,1]
代码实现:
//贪心解法
class Solution
{
public:
int maxArray(vector<int>& nums)
{
int res = INT_MIN;
int n = nums.size();
int sum = 0;
for (int i = 0; i < n; i++)
{
sum += nums[i];
res = max(res, sum);
if (sum < 0)sum = 0;//sum小于0时 sum清零 重新开始计算结果 当给定数组中的元素都是负数时 返回的是最大的那个负数 也就是最大的和
}
return res;
}
};
//动态规划解法
class Solution
{
public:
int maxSubArray(vector<int>& nums)
{
int pre = 0, maxAns = nums[0];//pre用来统计当前遍历元素的和;maxAns用来返回最大的和 初始化为数组第一个元素的值;
for (const auto& x : nums)//遍历每个元素
{
pre = max(pre + x, x);//更新当前最大的和
maxAns = max(maxAns, pre);
}
return maxAns;//返回子数组的最大和
}
};
leetcode 209 长度最小的子数组 【字节】M
算法要求:
给定一个含有 n 个正整数
的数组和一个正整数 target
。
找出该数组中满足其和 ≥ target
的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
例如:
输入 target=7 nums=[2.3.1.2.4.3]
输出:2
[4,3]是符合要求的最短数组
用滑动窗口解法:
在这个题目中,和sum充当滑动窗口的角色
,当滑窗内元素之和大于等于给定的目标值时
,开始缩小窗口左边界
,左侧的元素依次移出窗口,直到元素之和小于给定的目标值;当滑窗内元素之和小于给定的目标值时
,扩大窗口右边界
,右侧的元素依次进入窗口;
算法实现: 用一个左右指针标记滑动窗口的起始和终止位置;
//在滑动的过程中,根据和的变化不断移动左右指针,不断的更新一个最小的符合要求的长度,返回这个最小长度即可
代码实现:
class Solution
{
public:
int minSubArrayLen(int s, vector<int>&nums)
{
int n = nums.size();
if (n == 0)return 0;
int ans = INT_MAX;//初始化ans为一个最大值用于存储输出的最小长度;如果想要得到数组中最小值,可以先将最小值赋值为INT_MAX ;同理如果想要得到数组中最大值,可以先将最大值赋值为INT_MIN ;
int left = 0, right = 0;
int sum = 0;
while (right < n)//当right移动到最后一个元素时,不再移动,返回此时的ans就是最大的ans;
{
sum += nums[right];//sum依次累加数组nums的元素,直到其和大于给定的s,开始进入以下循环
while (sum >= s)
{
ans = min(ans, right - left + 1);//更新ans为符合要求的连续数组的最短长度
sum -= nums[left];//滑动窗口后移 从左侧开始逐个将左侧元素移出窗口 直到sum<s
left++;
}
right++;
}
return ans == INT_MAX ? 0 : ans;
}
};
剑指offer 2 面试题10 和为k的连续子数组 【字节】
题目要求:
给定一个整数数组nums
和一个整数 k
,请找到该数组中和为 k 的连续子数组的个数
。
例如:
输入:nums=[1,1,1] k=2
输出:2
思路分析:
这道题和上面的题差不多是一致的 ,但是题目要求略有差别;给定的整数数组并非都是正整数,因此这道题不能用双指针解;
为什么不能用双指针?(滑动窗口解法)
这道题粗粗一看,似乎可以使用双指针来求解。左指针和右指针确定一个子数组,若子数组的和大于等于k,则右移左指针减小子数组的和,反之则右移右指针增大子数组的和。
但是在这里是行不通的,因为在使用双指针基于如下假设:右移左指针相当于删除子数组中一个元素,子数组和减少,右移右指针相当于在子数组中增加一个元素,子数组和增大。
如果删除、增加的元素都是正整数那么假设是成立的,但本题中数组中的元素不能保证都为正数,所以原假设不成立。这就是不能使用双指针的原因所在。
解法一:超时了 但是可以参考思路
//一个容易理解但是会超时的解法---前缀和的思路
//和为k的子数组
class Solution
{
public:
int subArraySum(vector<int>& nums, int k)
{
int n = nums.size();
vector<int>sum(n + 1);//sum[i]表示数组中前i个元素的和
for (int i = 0; i < n; i++)
{
sum[i + 1] = sum[i] + nums[i];//从数组中第二个位置开始依次求得前缀和
}
int ret = 0;//存储目标子数组的个数
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= i; j++)
{
if (sum[i + 1] - sum[j] == k)ret++;//找到一个和为k的子数组
}
}
return ret;
}
};
解法二:哈希解法
哈希解法
哈希表保存的是出现过的累加和sum出现的次数
,sum是指从0到当前元素的累加和;初始化为map[0]=1
,表示和为0的连续子数组出现一次;
首先要查找map中是否出现过sum-k的元素,也就是在查找此前所有从0项开始累加的连续子项和中有没有sum-k;如果有的话,则说明从该项到当前项的连续子数组和必定为k
;sum-k出现的次数就是和为k的连续子数组出现的次数--逆向思维
; 如下图:
//利用哈希表进行优化
//sum-k出现的次数就是k出现的次数;
class Solution
{
public:
int subarraySum(vector<int>& nums, int k)
{
int sum = 0, res = 0;//sum用于进行累加和;res用于存储并返回和为k的子数组的个数
unordered_map<int, int>mp;//key是一个累加和;value是累加和出现的次数
mp[0] = 1;//初始化sum==0 出现的次数为1
for (auto& num : nums)
{
sum += num;//遍历数组nums 计算从第0个元素到当前元素的和 ,用哈希表保存出现过的累加和sum的次数;
res += mp[sum - k];//如果sum-k在哈希表中出现过,则表示从当前下标向前有连续的子数组的和为k,此时需要将res的值加一;
mp[sum]++;//将每一个出现过的子数组的和都加一
}
return res;
}
};
剑指offer II 009 乘积小于k的子数组
题目要求:
给定一个正整数数组 nums
和整数 k
,请找出该数组内乘积小于 k 的连续的子数组
的个数。(给定的数组是正整数,可以考虑用双指针进行求解)
例如:
输入:nums=[10,5,2,6] k=100
输出:8
这八个子数组分别是: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]
滑动窗口解法:
//乘积小于k的连续子数组
class Solution
{
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k)
{
int l = 0, r = 0;//左右指针
int n = nums.size();
int cur = 1;//cur存储当前遍历到的元素的累积,初始化为1
int res = 0;//存储乘积小于k的子数组的个数
for (; r < n; r++) //因为无需再次进行初始化了 所以for语句的第一部分为空
{
cur *= nums[r];//用cur存储乘积的结果
while (l <= r && cur >= k)//当前乘积的结果大于给定的k
{
cur /= nums[l];//除以左边界元素,一直除到乘积小于k
l++;//滑动窗口右移(左边界右移) 缩小滑窗的范围
}
res += (r-l+1);//通过规律会发现符合这个式子
}
return res;//返回最后子数组的个数
}
};
leetcode 260 只出现一次的数字 III M
题目要求:
给定一个整数数组 nums
,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素
。你可以按 任意顺序 返回答案。
例如:
输入:nums=[1,2,1,3,2,5]
输出:[3,5] 或 [5,3]
算法思想:
创建一个无序集合即哈希集合,用于存储遍历到的数组中的元素,
一开始集合中是空的,开始把元素依次放入集合中,当要放入的元素和集合中的元素有一样的时候,删除集合中的元素即可,这个元素是不符合要求的
简单说,就是不断地向集合中加入删除元素,如果集合中不存在和要放入的元素一样的元素就加入,否则删除;最终集合中保存下来的元素就是我们要的答案
代码实现:
class Solution
{
public:
vector<int>singleNumber(vector<int>& nums)
{
unordered_set<int>ansSet;//创建一个无序集合 本质上是哈希表
for (auto& x : nums)
{
if (ansSet.find(x) != ansSet.end())//判断集合中是否存在和数组中一样的元素 也就是判断数组中是不是有重复元素
{
ansSet.erase(x);//数组中有重复元素 则删除集合中的元素
}
else
{
ansSet.insert(x);//不含重复元素 在集合中加入该遍历到的元素 保存
}
}
}
};
用经典的哈希解法----我自己很熟悉的方法
小注:
对范围for循环的一点理解:
for(auto &r:v)
;
使用auto自动类型推导
可以保证类型相容,令编译器为r指定正确的类型;
使用引用类型变量&r
: 当你准备修改v的值时,需要这样做;因为只有这样才可以对r执行写操作;
class Solution {
public:
vector<int> singleNumber(vector<int>& nums)
{
unordered_map<int,int>map;
vector<int>res(2,0);
for(auto &num:nums) //引用的方式进行遍历
{
map[num]++;
}
for(auto p:map)
{
if(p.second==1)
{
res.push_back(p.first);
}
}
return res;
}
};
类似的题目 第一次只出现一次的字符
;
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
例如:
输入:s = “abaccdeff”
输出:‘b’
class Solution
{
public:
char firstUniqChar(string s)
{
unordered_map<char, int>map;
for (char c : s)//第一次遍历用于统计字符出现的次数
{
map[c]++;
}
for (char c : s)//第二次遍历用于查找出现次数为1的字符
{
if (map[c] == 1)return c;
}
return ' ';
}
};
leetcode 274/275 H 指数
题目要求:
给你一个整数数组 citations
,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数
。计算并返回该研究者的 h 指数。
根据维基百科上 h 指数的定义:h 代表“高引用次数”
,一名科研人员的 h指数是指他(她)的 (n 篇论文中)总共有 h 篇论文分别被引用了至少 h 次
。且其余的 n - h 篇论文每篇被引用次数 不超过 h 次。
如果 h 有多种可能的值,h 指数 是其中最大的那个。
例如:
输入:citations=[3,1,6,0,5]
输出:3
算法思想:
如果在当前H指数为h并且在遍历过程中**找到当前值citations[i]>h,则说明我们找到了一篇被引用次数至少为h+1的论文
** ,所有将现有的h加1继续遍历直到h无法继续增大,缩小右边界继续查找,最后返回h作为最终答案;
代码实现:
class Solution
{
public:
int hIndex(vector<int>& citations)
{
sort(citations.begin(), citations.end());//对引用次数进行排序 默认升序
int h= 0, i= citations.size() - 1;//定义h为引用次数 i表示右边界元素的下标
while (i >= 0 && citations[i] > h){ //找到一篇引用次数至少为h+1的论文
h++;
i--;
}
return h;
}
};
剑指offer 49 丑数 M
我们把只包含质因子2 3 5的数字称为丑数
,求按从小到大的顺序的第n个丑数;
例如:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
小注:
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但7、14不是,因为它们包含质因子7。 习惯上我们把1当做是第一个丑数。
前20个丑数为:1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 36
。
代码实现:
//丑数 动规解法/三指针解法 高效的解法
//对于任意一个丑数(除了1) 都是由小于它的某一个丑数*2或*3或*5得到的
//那么就可以将所有的丑数分为三大部分:*2得到的/ *3得到的 / *5得到的
class Solution
{
public:
int nthUglyNumber(int n)
{
int p2 = 0, p3 = 0, p5 = 0; // 初始化三个指针分别用来遍历三大部分
vector<int>dp(n, 0); // dp[i]表示第i个丑数 第n个丑数就是dp[n-1]
dp[0] = 1;
for (int i = 1; i < n; i++)
{
int num2 = 2 * dp[p2];//num2 num3 num5分别是从三大部分得到的丑数
int num3 = 3 * dp[p3];
int num5 = 5 * dp[p5];
//dp[i]表示当前丑数 是从num2 num3 num5中选最小值选出来的
dp[i] = min(num2, min(num3, num5));//因为所求的丑数要求从小到大 因此取三种情况下的最小值
if (dp[i] == num2)p2++;//当num2 num3 num5中当选为当前丑数时 对应部分的指针加一 继续向下遍历这一部分
if (dp[i] == num3)p3++;
if (dp[i] == num5)p5++;
}
return dp[n - 1];//返回第n个丑数
}
};
//=============================================最小堆解法 低效一些===========================================
class Solution {
public:
int nthUglyNumber(int n)
{
vector<int>factors={2,3,5};
unordered_set<long>seen;
priority_queue<long,vector<long>,greater<long>>heap;
seen.insert(1L);
heap.push(1L);
int ugly=0;
for(int i=0;i<n;i++)
{
long curr=heap.top();
heap.pop();
ugly=(int)curr;
for(int factor:factors)
{
long next=curr*factor;
if(!seen.count(next))
{
seen.insert(next);
heap.push(next);
}
}
}
return ugly;
}
};
//给定一个数 判断这个数字是不是丑数
class Solution {
public:
bool isUgly(int n)
{
if(n<=0)return false;
vector<int>factors={2,3,5};//丑数集合
for(int factor:factors)
{
while(n%factor==0)//任意一个丑数除以质因数2 3 5 余数都是0
{
n/=factor;//n=n/factor 若是丑数 最后的n肯定是factor的一倍 即n==1
}
}
return n==1;
}
};
leetcode 239 【滑动窗口最大值 && 剑指offer 59-I 滑动窗口最大值 H】
给你一个整数数组nums
,有一个大小为k的滑动窗口
从数组的最左侧移动到数组的最右侧,你只可以看到在滑动窗口内的k个数字,滑动窗口每次只向右移动一位;返回滑动窗口中的最大值;
例如:
简单高效容易理解的做法
//一个简单做法
//这个双端队列的作用
//队列只维护k个元素 随着队列的移动 滑窗里的元素动态更新 每次都返回窗口内的最大值即可
class Solution
{
public:
vector<int>maxSlidWin(vector<int>& nums, int k)
{
vector<int>res;
deque<int>q;//双端队列
for (int i = 0; i < nums.size(); i++)
{
while (!q.empty() && nums[i] >= nums[q.back()])q.pop_back();//保证滑窗内队首元素是最大的
q.push_back(i);//队列中维护的是数组中元素的下标
if (q.back() - q.front() >= k)q.pop_front();//用一个双端队列模拟滑动窗口 超出窗口容量 依次删除队首元素;
if (i >= k - 1)res.push_back(nums[q.front()]);//达到指定的窗口大小后 开始保存队首元素也就是滑窗内的最大值;
}
return res;
}
};
题目要求:
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
例如
输入:[1,3,-1,-3,5,3,6,7]
输出:[3,3,5,5,6,7]
算法思想:
双端队列的解法:
首先对变量**win
** 做出以下解释:
//win的特点:
//变量的最前端即win.front()是此时遍历的最大值的下标
//当我们遇到新的数时,将新的数和双向队列的末尾即win.back()比较
//如果末尾比新数小,则把末尾扔掉,直到该队列的末尾比新数大或队列为空的时候才停止
//双向队列中的所有值都要在窗口范围内
(对size_t
类型的解释:
//对size_t 类型的解释:保存了一个整数类型
// size_t 和 int的区别:
// size_t这个类型足以用来表示对象的大小,size_t的真实类型与操作系统有关;
在32位架构中是4字节,在64位架构上是8字节
// 而int无论在哪种架构下都是4字节,因此有时候使用int可能会范围不够,用size_t可以保证范围够用)
解题框架:
//第一个for循环是找到第一个滑窗内的最大值
//第二个for循环是从剩余的元素继续构建滑窗,并不断更新滑窗内的元素最大值,存储到res中
代码实现:
class Solution
{
public:
vector<int>maxSlidingWindow(vector<int>& nums, int k)
{
if (k == 0)return {};
vector<int>res;//存储最大值
deque<size_t>win;//创建一个双端队列的滑窗。存储下标
for (size_t i = 0; i < k; i++)//建立第一个滑窗
{
while (!win.empty() && nums[i] > nums[win.back()])//保证队列里的元素都是降序排列的
{
win.pop_back();
}
win.push_back(i);//遍历到的第i个位置的元素比当前窗口内的最后一个元素大,那么将最后一个元素下标删除,将较大的元素下标补充进去
}
//找到第一个滑窗内的最大值
res.push_back(nums[win.front()]);//win.front()是此次遍历的最大值的下标
for (size_t i = k; i < nums.size(); i++) //从第一个窗口后开始遍历,继续寻找下一个滑窗
{
if (!win.empty() && win.front() <= i - k)//保证滑窗的元素个数只有k个,多的时候开始从队首元素依次删除
{
win.pop_front();
}
while (!win.empty() && nums[i] > nums[win.back()])//将遍历到的元素和滑窗内的元素比较,将较大的元素添加到滑窗内,将较小的删除
{
win.pop_back();
}
win.push_back(i);
res.push_back(nums[win.front()]);
}
return res;
}
};
Leetcode 445 两数相加 II
题目要求:
给你两个 非空 链表来代表两个非负整数
。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
例如:
输入: l1=[7,2,4,3] l2=[5,6,4]
输出:[7,8,0,7]
算法思想:
先将两个链表元素的数值放到栈里,再依次将元素出栈进行相加
;
代码实现:
struct ListNode
{
int val;
ListNode* next;
ListNode(int x):val(x),next(NULL){}
};
class Solution
{
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
stack<int>s1, s2;//创建两个栈,分别将两个链表的元素放入到两个栈中
while (l1)
{
s1.push(l1->val);
l1 = l1->next;//头节点l1起到指针的作用来遍历这个链表
}
while (l2)
{
s2.push(l2->val);
l2 = l2->next;
}
int carry = 0;//存储进位
int res=0;//存储余数
ListNode* temp = nullptr;//遍历新链表的辅助节点
while (!s1.empty() or !s2.empty() or carry != 0)
{
//因为栈的先进后出,a b分别代表两个链表的末尾元素
int a = s1.empty() ? 0 : s1.top();// a b分别表示栈顶元素 也就是链表的尾元素
int b = s2.empty() ? 0 : s2.top();
if (!s1.empty())s1.pop();//删除栈顶元素 继续向下遍历栈内元素
if (!s2.empty())s2.pop();
int cur = a + b + carry;//cur表示两链表末尾元素数字之和
carry = cur / 10;//carry表示进位
res =cur% 10;//cur表示余数
auto curnode = new ListNode(res);//以当前计算得到的余数为节点,作为新链表的第一个节点;先构建个位,再构建十位,从右向左的顺序
curnode->next = temp;//新节点的下一个节点指向ans
temp = curnode;//将curnode更新为下一个位置,继续向下遍历
}
return temp;//返回ans是新链表的头节点
}
};
leetcode 70 爬楼梯 【字节】
题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶
。你有多少种不同的方法可以爬到楼顶呢?
思路:
**第n阶台阶只能从第n-1或第n-2个上来。到第n-1个台阶的走法+到第n-2个台阶的走法=到第n个台阶的走法**
;
动态规划解法:
class Solution {
public:
int climbStairs(int n)
{
vector<int>dp;
for(int i=0;i<=n;i++)
{
if(i==0||i==1)dp.push_back(1);//初始情况
else dp.push_back(dp[i-1]+dp[i-2]);
}
return dp[n];
}
};
剑指offer II 008 【爬楼梯的最小成本】
数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i]
(下标从 0 开始)。
每当爬上一个阶梯都要花费对应的体力值,一旦支付了相应的体力值,就可以选择向上爬一个阶梯或者爬两个阶梯。
请找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。
例如:
输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。
dp[i]的定义:到达下标为i的台阶所花费的最少体力为dp[i]
有两个途径可以得到dp[i],一个是dp[i-1]一个是dp[i-2],所以到达顶层的肯定是二者其中最小的
;
因为初始位置可以从下标0或者下标1开始,因此结束位置也相应的有两个,倒数第一个和倒数第二个,所以最后的结果取二者中的最小值;
动态规划实现:
//代码随想录 动态规划解法
class Solution
{
public:
int minCostClimbingStairs(vector<int>& cost)
{
vector<int>dp(cost.size());
dp[0] = cost[0];//初始化最简单的情况
dp[1] = cost[1];
for (int i = 2; i < cost.size(); i++)
{
dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];
}
return min(dp[cost.size() - 1], dp[cost.size() - 2]);//返回两种情况里的最小值;
//当从下标为0的楼梯开始爬的时候 向上爬的总的楼梯数目为cost.size()-1;
//当从下标为1的楼梯开始爬的时候 向上爬的总的楼梯数目为cost.size()-2;
}
};