1.基础题目
1.回文数(leetcode 9)
- 题目描述:
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121
输出: true
示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
进阶:
你能不将整数转为字符串来解决这个问题吗?
- 分析:
判断是不是回文数,如果是个字符串,可以用中心扩散法判断是不是回文,对于这道题,给的输入是一个整数,我们不能直接知道其含有多少个数,所以中心扩散法就不太好用了,根据回文的性质,一个数正着读和反着读相同那么就是个回文。这里我们将该整数的每个位分离出来重新组合在一起,判断组合的数和原数是否相等,如果相等,那么就是回文数。
class Solution {
public:
bool isPalindrome(int x) {
if(x<0)
return false;
unsigned int sum=0;
int n=x;
while(n)
{
sum=sum*10+n%10;
n=n/10;
}
return sum==x;
}
};
2. 拥有最多糖果的孩子(leetcode 1431)
- 题目描述:
给你一个数组 candies 和一个整数 extraCandies ,其中 candies[i] 代表第 i 个孩子拥有的糖果数目。
对每一个孩子,检查是否存在一种方案,将额外的 extraCandies 个糖果分配给孩子们之后,此孩子有 最多 的糖果。注意,允许有多个孩子同时拥有 最多 的糖果数目。
示例 1:
输入:candies = [2,3,5,1,3], extraCandies = 3
输出:[true,true,true,false,true]
解释:
孩子 1 有 2 个糖果,如果他得到所有额外的糖果(3个),那么他总共有 5 个糖果,他将成为拥有最多糖果的孩子。
孩子 2 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。
孩子 3 有 5 个糖果,他已经是拥有最多糖果的孩子。
孩子 4 有 1 个糖果,即使他得到所有额外的糖果,他也只有 4 个糖果,无法成为拥有糖果最多的孩子。
孩子 5 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。
示例 2:
输入:candies = [4,2,1,1,2], extraCandies = 1
输出:[true,false,false,false,false]
解释:只有 1 个额外糖果,所以不管额外糖果给谁,只有孩子 1 可以成为拥有糖果最多的孩子。
示例 3:
输入:candies = [12,1,12], extraCandies = 10
输出:[true,false,true]
提示:
2 <= candies.length <= 100
1 <= candies[i] <= 100
1 <= extraCandies <= 50
- 分析:
首先,需要知道未加额外糖果时的最大值。
接着,遍历每个孩子,将额外的糖果加到每个孩子的原有糖果上,判断是否比最大值大。
class Solution {
public:
vector<bool> kidsWithCandies(vector<int>& candies, int extraCandies) {
vector<bool> ans;
int maxVal=INT_MIN;
for(int i=0;i<candies.size();i++)
{
maxVal=max(maxVal,candies[i]);
}
for(int i=0;i<candies.size();i++)
{
if(candies[i]+extraCandies>=maxVal)
ans.push_back(true);
else
ans.push_back(false);
}
return ans;
}
};
3.最佳观光组合(leetcode 1014)
- 题目描述:
给定正整数数组 A,A[i] 表示第 i 个观光景点的评分,并且两个景点 i 和 j 之间的距离为 j - i。
一对景点(i < j)组成的观光组合的得分为(A[i] + A[j] + i - j):景点的评分之和减去它们两者之间的距离。
返回一对观光景点能取得的最高分。
示例:
输入:[8,1,5,2,6]
输出:11
解释:i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11
提示:
2 <= A.length <= 50000
1 <= A[i] <= 1000
- 分析:
方法一:从前往后遍历整个A数组,内层循环在(i<j)范围内寻找一个旅游景点,判断景点组合按照评分规则是否最高,此时算法复杂度为O(n^2);
方法二:从评分规则(A[i] + A[j] + i - j)可以看出,当固定A[i],内层循环遍历(j>i)的范围,当前观光景点评分比前面的范围内的景点评分小,那么它一定不能成为最大,所以可以直接跳过最高分的判断。
方法三:参考Leetcode官方题解。
方法一:
class Solution {
public:
int maxScoreSightseeingPair(vector<int>& A) {
int maxScore=INT_MIN;
for(int i=1;i<A.size();i++)
{
for(int j=i-1;j>=0;j--)
{
int score=A[i]+A[j]+j-i;
maxScore=max(maxScore,score);
}
}
return maxScore;
}
};
方法二:
class Solution {
public:
int maxScoreSightseeingPair(vector<int>& A) {
int maxScore=INT_MIN;
for(int i=0;i<A.size()-1;i++)
{
int cur=A[i+1];
for(int j=i+1;j<A.size();j++)
{
if(A[j]<cur)
continue;
cur=max(cur,A[j]);
int score=A[i]+A[j]+i-j;
maxScore=max(maxScore,score);
}
}
return maxScore;
}
};
方法三:
class Solution {
public:
int maxScoreSightseeingPair(vector<int>& A) {
int maxScore=0,Val=A[0]+0;
for(int j=1;j<A.size();j++)
{
maxScore=max(maxScore,A[j]-j+Val);
Val=max(Val,A[j]+j);
}
return maxScore;
}
};
4.跳水板(leetcode 面试题16.11)
- 题目描述:
你正在使用一堆木板建造跳水板。有两种类型的木板,其中长度较短的木板长度为shorter,长度较长的木板长度为longer。你必须正好使用k块木板。编写一个方法,生成跳水板所有可能的长度。
返回的长度需要从小到大排列。
示例:
输入:
shorter = 1
longer = 2
k = 3
输出: {3,4,5,6}
提示:
0 < shorter <= longer
0 <= k <= 100000
- 分析:
题目要求我们得到所有的可能,而且所有可能没有重复,首先想到了回溯的方法使用递归,然后用一个set保存结果,因为set内元素不能重复,且进行了排序,但是时间复杂度不满足。
使用公式,首先对于短的短板,尽可能的选多点,然后依次减少短板的数量,而长板的数量在不断的增加,这样就完成了从小到达排序。
方法一:
class Solution {
public:
set<int> ans;
void helper(int k,int *a,int sum)
{
if (k==0)
{
ans.insert(sum);
return;
}
for(int i=0;i<2;i++)
{
helper(k-1,a,sum+a[i]);
}
return;
}
vector<int> divingBoard(int shorter, int longer, int k) {
vector<int>res;
if(k==0)
return res;
int a[2]={shorter,longer};
helper(k,a,0);
res.insert(res.end(),ans.begin(),ans.end());
return res;
}
};
方法二:
class Solution {
public:
vector<int> divingBoard(int shorter, int longer, int k)
{
vector<int> ans;
if(k==0)
return ans;
if(shorter==longer)
return vector<int>(1,shorter*k);
for(int i=0;i<=k;i++)
ans.push_back((k-i)*shorter+i*longer);
return ans;
}
};
2.区间合并类型
区间合并是一个用来处理区间重叠问题的很高效的技术。通常需要判断是否有重叠,如何合并区间。
1.合并区间(leetcode 56)
- 题目描述:
给出一个区间的集合,请合并所有重叠的区间。
示例 1:
输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
- 分析:
首先,一个区间存在一个起点start,一个终点end,我们要区间合并就必须先判断区间是否存在重叠 ,区间重叠存在着6种情况。
如果我们在无序的情况下去比较两个区间的起点和终点,判断他们是否符合这些情况是比较复杂的。因此我们可以通过先对区间进行排序固定起点,然后比较两个区间的终点的情况,这样就简便许多。
- 第一种情况:两个区间没有重叠
- 第二种情况 :两个区间重叠了部分
- 第三种情况:其中一个区间为另外一个区间的子集
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<vector<int>> res;
if(intervals.empty())
return res;
sort(intervals.begin(),intervals.end());
auto temp=intervals[0];
// cout<<"a: "<<temp[0]<<" b: "<<temp[1]<<endl;
for(int i=1;i<intervals.size();i++)
{
//第一种情况
if(intervals[i][0]>temp[1])
{
res.push_back(temp);
temp=intervals[i];
}
else
{
//第二种情况
if(intervals[i][1]>temp[1])
temp[1]=intervals[i][1];
}
}
res.push_back(temp);
return res;
}
};
3.双指针类型
1.删除排序数组中的重复项(leetcode 26)
- 日期:2020/4/18
- 题目描述:
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(1)额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
- 分析:
要去除重复项,实质上就是两个元素相互比较,如果相同,则为重复,否则就不重复,我们定义两个指针,快指针用来遍历数组,慢指针指着当前元素。
- 如果慢指针和快指针相同,则快指针+1;
- 如果慢指针和快指针不同,则慢指针+1,将快指针给慢指针,快指针+1;
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.empty())
return 0;
int slow=0;
for(int i=1;i<nums.size();i++)
{
if(nums[i]!=nums[slow])
{
nums[++slow]=nums[i];
}
}
return slow+1;
}
};
2.盛最多水的容器(leetcode 11)
- 日期:2020/4/18
- 题目描述
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
示例:
输入:[1,8,6,2,5,4,8,3,7] 输出:49
- 分析
盛水有短板效应,最短的那个板决定了水的高度,其次容量和底板的长度有关,可以通过设置双指针分别指向容器的两个板,容量=短板长度*底板长度。(注意:这个容器只和两边的长短板和底有关,在计算对应的容量时,中间那些柱子不考虑)
class Solution {
public:
int maxArea(vector<int>& height) {
int i=0;
int j=height.size()-1;
int max_res=INT_MIN;
while(i<j)
{
if (height[i] < height[j])
{
max_res = max(max_res, height[i] * (j - i));
i += 1;
}
else
{
max_res = max(max_res, height[j] * (j - i));
j -= 1;
}
}
return max_res;
}
};
3.移动零(leetcode 283)
- 日期:2020/4/19
- 题目描述:
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
- 分析:
首先,我们的目的是移动零,我们定义两个指针,一个指针指向原数组,起遍历的作用,另一个指针指向新数组的索引位置。
- 如果当前元素不是零元素,将其拷贝到新数组索引的位置。
- 如果当前元素是零元素,则跳过。
- 最后对后面空缺的位置进行补零。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int j=0;
for(int i=0;i<nums.size();i++)
{
if(nums[i]!=0)
nums[j++]=nums[i];
}
for(int k=j;k<nums.size();k++)
nums[k]=0;
}
};
4.移除元素(leetcode 27)
- 日期:2020/4/19
- 题目描述:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
- 分析:
这道题和移动零元素其实是一样的做法,只是移除后不需要在末尾补充元素了。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int j=0;
for(int i=0;i<nums.size();i++)
{
if(nums[i]!=val)
nums[j++]=nums[i];
}
return j;
}
};
5.统计【优美子数组】(leetcode 1248)
- 日期:2020/4/21
- 题目描述:
给你一个整数数组 nums 和一个整数 k。
如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中「优美子数组」的数目。
示例 1:
输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。
示例 2:
输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。
示例 3:
输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16
提示:
1 <= nums.length <= 50000
1 <= nums[i] <= 10^5
1 <= k <= nums.length
- 分析:
对于寻找子数组,可以通过双指针实现,一个指针指向子数组的起点,一个指针指向子数组的终点。
- 首先,我们从数组的起点开始进行遍历,左右两个指针分别指向0,-1位置,右指针指向-1位置是因为需要判断第一个元素是否为奇数,是否需要被包含。
- 移动右指针,直到左右指针指向的子数组中包含k个奇数,或者已经把整个数组都已经遍历完了。
- 这时候我们需要判断右指针是否越界,因为如果整个数组中的奇数个数小于k,那么把整个数组遍历完后右指针就越界了,这时候我们直接结束循环。
- 如果没有越界,那么保存右指针的值(flag),接着右指针继续往右移动,直到遇到新的奇数或者数组的末尾。
- 接着,我们需要移动左指针了,需要一直维持左指针和右指针的区域右K个奇数,而且左指针移动一步,相当于有:r-flag+1种结果,因为从保存的位置到右指针的位置,和左指针构成的区域是连续的递增1,所以我们只需要在左指针移动的时候每次都增加r-flag+1。
- 当左指针移动结束后,这时候区间的奇数数量少于k,如果右指针的下一个元素为奇数,且没有到达末尾,重复上述步骤。
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k){
int n=nums.size();
int l=0,r=-1,count=0;
int ans=0;
while(r+1<n)
{
//首先,移动右指针,找到刚好满足k个奇数的位置
while(r+1<n&&count<k)
{
count+=nums[++r]&1;
}
if(r>n)
break;
//记录当前右区间
int flag=r;
// cout<<"flag: "<<flag<<endl;
//向右移动,直到末尾或者下一个数为奇数
while(r+1<n&&!(nums[r+1]&1))
r++;
// cout<<"r: "<<r<<endl;
//移动左指针,每次加r-flag+1,如果为奇数,奇数的数量-1;
while(count==k&&l<=r)
{
ans+=r-flag+1;
count-=nums[l++]&1;
}
// cout<<"l: "<<l<<endl;
}
return ans;
}
};
6.快乐数(leetcode 202)
- 题目描述:
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。
示例:
- 分析:
这道题确实挺快乐的(题外话),从题目和示例中,我们首先需要将这个整数进行拆分,然后对每位进行平方求和,最后循环操作,判断是否能到1。前面的步骤比较容易想到,主要的问题是无限循环,我们可以通过数据结构集合set来记录每次的循环数,set也有不重复性,这样就可以用来解决无限循环操作。
当然,这里也可以用快慢指针,防止开辟额外的内存空间,我们快指针跑两步,慢指针跑一步,如果无限循环,那么快指针最后会反超慢指针一圈,这样就结束了无限循环。
- set集合
class Solution {
public:
int helper(int n)
{
int sum=0;
while(n)
{
sum+=(n%10)*(n%10);
n=n/10;
}
return sum;
}
bool isHappy(int n) {
set<int> s;
s.insert(n);
while(n!=1)
{
// cout<<"n: "<<n<<endl;
n=helper(n);
if(s.count(n)!=0)
return false;
else
s.insert(n);
}
return true;
}
};
- 快慢指针
class Solution {
public:
int helper(int n)
{
int sum=0;
while(n)
{
sum+=(n%10)*(n%10);
n=n/10;
}
return sum;
}
bool isHappy(int n) {
int slow=n;
int fast=n;
do
{
slow=helper(slow);
fast=helper(fast);
fast=helper(fast);
}while(slow!=fast);
if(fast==1)
return true;
else
return false;
}
};
7. 接雨水(leetcode 42)
- 题目描述:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
- 分析:
4.位运算操作
1.数组中数字出现的次数(leetcode 面试题56)
- 题目描述:
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:
2 <= nums <= 10000
- 分析:
这道题如果没有限制条件:时间复杂度是O(n),空间复杂度是O(1),其实方法还是很多的,比如:查表,双指针等等,我就采用的双指针,但是时间复杂度不达标呀。这道主要采用位运算操作:异或。首先看下异或的性质。
那么如果我们将所有数字都异或起来:
根据异或的交换律、 结合律 将相同的数字优先两两进行异或运算。此时根据归零率 ,每两个相同的数字都变成了 000,再根据 恒等率 ,把式子里所有的 000 去了,此时就只剩下只出现一次的那个数了!
但是,两个数字异或在一起,我们没办法分辨出是哪两个数字组合在一起的。这时,可以采用分组异或,保证:
- 两个只出现一次的数字在不同的组中;
- 相同的数字会被分到相同的组中。
那么,如何分组呢?其实实质就是异或的定义:相同为0,不同为1,将所有数字一起异或后,相同的都抵消掉了,就剩下这两个数,那么可以根据两个数的二进制中不同的位(也就是异或中的1)来区分,进而达到分组的目的。【下面是leetcode官方的分析】
- 双指针+快排(不符合题意)
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
vector<int> ans;
if(nums.empty())
return ans;
sort(nums.begin(),nums.end());
int j=0;
int count=0;
for(int i=0;i<nums.size();i++)
{
if(nums[i]==nums[j])
{
count++;
}
else
{
if(count==1)
{
ans.push_back(nums[j]);
}
j=i;
count=1;
}
}
if(count==1)
{
ans.push_back(nums[j]);
}
if(ans.size()==1)
{
ans.push_back(0);
}
return ans;
}
};
- 位操作
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
vector<int> ans;
int d=0;
int a=0;
int b=0;
//所有数进行异或操作
for(int num:nums)
d^=num;
int p=1;
//找到最近的不同位(1)
while((d&p)==0)
p=p<<1;
//分组
for(int num:nums)
{
if(num&p)
a^=num;
else
b^=num;
}
ans.push_back(a);
ans.push_back(b);
return ans;
}
};
2.只出现一次的数字(leetcode 136)
- 题目描述:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
- 分析:
首先,查找只出现一次的数,可以使用hash表对出现的数进行统计,然后找到数量为1对应的数,但是需要开辟额外的空间。
为了满足线性时间复杂度和不使用额外的空间来实现,可以通过异或运算进行实现,异或的性质:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res=0;
for(int i=0;i<nums.size();i++)
{
res^=nums[i];
}
return res;
}
};
5. hash表
1.和为K的子数组(leetcode 560)
- 题目描述【中等难度】:
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明 :
数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。
- 暴力法分析:
首先,因为是找子数组,所以我们需要两个变量,指向子数组的起始位置和结束位置。
接着,固定起始位置,然后移动结束位置的指针,使得区间元素的和为k。
最后,依次移动起始位置的指针,重复步骤2.
这样时间复杂度为O(N^2)
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
if(nums.empty())
return 0;
int ans=0;
for(int i=0;i<nums.size();i++)
{
int sum=0;
for(int j=i;j<nums.size();j++)
{
sum+=nums[j];
if(sum==k)
ans++;
}
}
return ans;
}
};
- 前缀+hash表
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int,int> map;
map[0]=1;
int pre=0;
int count=0;
for(int i=0;i<nums.size();i++)
{
//前i项和
pre+=nums[i];
//查找是否存在pre[j]=pre[i]-k的前j项和
if(map.count(pre-k)!=0)
count+=map[pre-k];
//保存前j项和
map[pre]++;
}
return count;
}
};
2. 两数之和(leetcode 1)
- 题目描述:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
- 分析:
暴力方法:
通过两次遍历nums,判断nums[i]+nums[j]是否等于target,如果等,直接返回。
hash表:
因为在nums数组中,有两个数等于target,可以通过一次遍历查找结果,在遍历的过程中,保存target和当前的值的差值(target-nums[i]),以及其对应的索引(i),在后续的遍历过程中,如果有一个元素能够在hash表中查找到,那么就是最终的结果。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> map;
vector<int> ans;
if(nums.empty())
return ans;
for(int i=0;i<nums.size();i++)
{
if(map.count(nums[i])!=0)
{
ans.push_back(map[nums[i]]);
ans.push_back(i);
}
else
map[target-nums[i]]=i;
}
return ans;
}
};
3.缺失的第一个正数(leetcode 41)
- 题目描述:
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
示例 1:
输入: [1,2,0]
输出: 3
示例 2:
输入: [3,4,-1,1]
输出: 2
示例 3:
输入: [7,8,9,11,12]
输出: 1
提示:
你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。
- 分析:
- 方法1:为了寻找未出现的最小正整数,可以先使用排序,然后对数组进行二分查找实现。
- 方法2:寻找未出现的最小正整数,那么可以将原数组保存到一个hash表中,然后从1到len依次查找是否在hash表中存在,如果不在hash表中,那么直接返回。
- 方法3:将原数组视为一个hash表,将内部元素交换到属于他的位置,然后从1到len依次查找。
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
unordered_set<int> set;
for(auto num:nums)
{
if(num>0&&set.find(num)==set.end())
set.insert(num);
}
int start=1;
while(1)
{
if(set.find(start)==set.end())
return start;
start++;
}
return start;
}
};
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int len=nums.size();
for(int i=0;i<len;i++)
{
while(nums[i]>=1&&nums[i]<=len&&nums[nums[i]-1]!=nums[i])
swap(nums,i,nums[i]-1);
}
for(int i=0;i<len;i++)
{
if(nums[i]!=i+1)
return i+1;
}
return len+1;
}
void swap(vector<int> &nums,int index1,int index2)
{
int temp=nums[index1];
nums[index1]=nums[index2];
nums[index2]=temp;
}
};
4. 两数之和II-(输入有序数据)(leetcode 167)
- 题目描述:
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
- 分析:
因为给定数组为有序数组,因此可以通过依次遍历整个数组,通过二分查找在剩余的部分中寻找目标值和当前值的差值
class Solution {
public:
int helper(vector<int>&nums,int left,int right,int target)
{
int mid;
while(left<=right)
{
mid=left+(right-left)/2;
if(nums[mid]==target)
return mid;
else if(nums[mid]>target)
right=mid-1;
else
left=mid+1;
}
return -1;
}
vector<int> twoSum(vector<int>& numbers, int target) {
vector<int> ans;
if(numbers.empty())
return ans;
for(int i=0;i<numbers.size();i++)
{
int index=helper(numbers,i+1,numbers.size()-1,target-numbers[i]);
if(index!=-1)
{
ans.push_back(i+1);
ans.push_back(index+1);
return ans;
}
}
return ans;
}
};
6.单调栈
单调栈:栈内元素都是单调递减或者单调递增。
时间复杂度:O(n)
空间复杂度:O(n)
当新元素进栈时,为了维护栈的单调性,需要弹出大于等于新元素的所有元素。
主要应用:
对于数组中的元素,可以找到左侧/右侧第一个比它大/小的元素。
寻找某一子数组,使得子数组中的最小值乘以子数组的长度最大。
寻找某一子数组,使得子数组中的最小值乘以子数组所有元素和最大。
1.柱状图中最大矩形(leetcode 84)
- 题目描述:
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
示例:
输入: [2,1,5,6,2,3]
输出: 10
- 分析:
- 暴力解法(O(n^2))
- 首先固定起点,然后移动终点(遍历整个数组)。
- 在遍历的过程中,判断当前终点的高度是否为最低。
- 根据移动的步数乘以最低高度得到面积,再判断面积是否为最大。
- 当终点遍历完后,移动起点,重复上述步骤。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int maxArea=0;
for(int i=0;i<heights.size();i++)
{
int height=heights[i];
for(int j=i;j<heights.size();j++)
{
height=min(height,heights[j]);
int area=(j-i+1)*height;
// cout<<"i: "<<i<<" j: "<<j<<" height: "<<height<<" area: "<<area<<std::endl;
maxArea=max(maxArea,area);
}
}
return maxArea;
}
};
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int ans=0;
heights.push_back(0);
stack<int> s;
for(int i=0;i<heights.size();i++)
{
while(!s.empty()&&heights[i]<heights[s.top()])
{
auto temp=s.top();
s.pop();
ans=max(ans,heights[temp]*(s.empty()?i:i-s.top()-1));
}
s.push(i);
}
return ans;
}
};
2.每日温度(leetcode 739)
- 题目描述:
根据每日 气温 列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。
- 分析:
暴力解法:遍历每日的温度,然后在当日以后的温度中寻找第一个最大值,如果找到就为j-i,否则为0。
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
vector<int> ans(T.size());
for(int i=0;i<T.size()-1;i++)
{
for(int j=i+1;j<T.size();j++)
{
if(T[j]>T[i])
{
ans[i]=j-i;
break;
}
}
}
return ans;
}
};
单调栈: 参考
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
vector<int> ans(T.size());
stack<int> s;
for(int i=0;i<T.size();i++)
{
while(!s.empty()&&T[i]>T[s.top()])
{
ans[s.top()]=i-s.top();
s.pop();
}
s.push(i);
}
return ans;
}
};
7.二分查找
1.转变数组后最接近目标值的数组和(leetcode 1300)
- 题目描述:
给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr 中的数字。
示例 1:
输入:arr = [4,9,3], target = 10
输出:3
解释:当选择 value 为 3 时,数组会变成 [3, 3, 3],和为 9 ,这是最接近 target 的方案。
示例 2:
输入:arr = [2,3,5], target = 10
输出:5
示例 3:
输入:arr = [60864,25176,27249,21296,20204], target = 56803
输出:11361
提示:
1 <= arr.length <= 10^4
1 <= arr[i], target <= 10^5
- 分析:
二分查找:
class Solution {
public:
int findBestValue(vector<int>& arr, int target) {
int n = arr.size();
int minCha = INT_MAX;
sort(arr.begin(),arr.end());
double lowTemp = (1.0)*target/n;
if(lowTemp <= arr[0]){
if(lowTemp * 2 - 1 >=0 && (int)(lowTemp * 2 - 1)/2 == (int)(lowTemp)){
return (int)ceil(lowTemp);
}else return (int)lowTemp;
}
int l = arr[0], r = arr[n-1];
int ans = 0;
while(l <= r){
int mid = (l+r) >> 1;
int sum = 0;
for(int i = 0; i < n; i++){
sum += arr[i] < mid ? arr[i] : mid;
}
int cha = target - sum;
if(abs(cha) <= minCha){
minCha = abs(cha);
ans = mid;
}
if(cha == 0) return mid;
else if(cha > 0) l = mid+1;
else if(cha < 0) r = mid-1;
}
return ans;
}
};
参考:https://labuladong.gitbook.io/algo/suan-fa-si-wei-xi-lie/qu-jian-tiao-du-wen-ti-zhi-qu-jian-he-bing