Leet code 面试经典 150 题
数组 / 字符串
合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1
和 nums2
,另有两个整数 m
和 n
,分别表示 nums1
和 nums2
中的元素数目。
请你 合并 nums2
到 nums1
中,使合并后的数组同样按 非递减顺序 排列。
**注意:**最终,合并后数组不应由函数返回,而是存储在数组 nums1
中。为了应对这种情况,nums1
的初始长度为 m + n
,其中前 m
个元素表示应合并的元素,后 n
个元素为 0
,应忽略。nums2
的长度为 n
。
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
for(int i = m, j = 0;j < n;i++, j++){
nums1[i] = nums2[j];
}
sort(nums1.begin(),nums1.end());
}
};
vector
sort
函数用 begin()
和 end()
移除元素
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素。元素的顺序可能发生改变。然后返回 nums
中与 val
不同的元素的数量。
假设 nums
中不等于 val
的元素数量为 k
,要通过此题,您需要执行以下操作:
- 更改
nums
数组,使nums
的前k
个元素包含不等于val
的元素。nums
的其余元素和nums
的大小并不重要。 - 返回
k
。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int l = 0;
int k = 0;
for(int r = 0;r<nums.size();r++){
if(nums[r]!=val){
nums[l++] = nums[r];
k++;
}
}
return k;
}
};
删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums
,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
- 更改数组
nums
,使nums
的前k
个元素包含唯一元素,并按照它们最初在nums
中出现的顺序排列。nums
的其余元素与nums
的大小不重要。 - 返回
k
。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int left = 0;
for(int r = 0;r<nums.size();r++){
if(nums[r]>nums[left]){
nums[++left] = nums[r];
}
}
return left+1;
}
};
跟上一题还是很像的,left 还是存最左端的数
删除有序数组中的重复项 II
给你一个有序数组 nums
,请你** 原地** 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n = nums.size();
if(n<=2){
return n;
}
int slow = 2,fast = 2;
while(fast < n){
if(nums[slow-2] != nums[fast]){
nums[slow] = nums[fast];
++slow;
}
++fast;
}
return slow;
}
};
感觉不太好理解,意思是 slow
表示已经确定保存的数,fast
表示第一个和 slow - 2 不一样的数,如果他们的差大于等于 2 就说明超过了题干的要求,这时候就把 fast
所在的位置的数的值赋给 slow
,然后再将slow++,fast++。
多数元素
给定一个大小为 n
的数组 nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(),nums.end());
return nums[nums.size()/2];
}
};
次数超过一半的数肯定是中位数
轮转数组(三次翻转)
给定一个整数数组 nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
class Solution {
public:
void reverse(vector<int>& nums, int l, int r){
while(l<r){
swap(nums[l], nums[r]);
l++;
r--;
}
}
void rotate(vector<int>& nums, int k) {
k%=nums.size();
reverse(nums,0,nums.size()-1);
reverse(nums,0,k-1);
reverse(nums,k,nums.size()-1);
}
};
轮转相当于先总体翻转一次,然后在分界点前后分别翻转一次
买卖股票的最佳时机
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ans = 0, minn = prices[0];
for(int i = 0;i<prices.size();i++){
minn = min(minn, prices[i]);
ans = max(ans, prices[i] - minn);
}
return ans;
}
};
存一个当前数之前的最小数,再求差值
买卖股票的最佳时机 II( d p dp dp)
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int dp[30005][2];
dp[0][0] = 0,dp[0][1] = -prices[0];
for(int i = 1;i<prices.size();i++){
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[prices.size() - 1][0];
}
};
dp[i][0]
表示当前不持有第 i 个股票
dp[i][1]
表示当前持有第 i 个股票
状态转移方程:
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
跳跃游戏
给你一个非负整数数组 nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
。
class Solution {
public:
bool canJump(vector<int>& nums) {
int num = nums.size();
for(int i = num - 2;i>=0;i--){
if(nums[i]>=num - i - 1){
num = i + 1;
}
}
if(num == 1)return true;
else return false;
}
};
看了一下题解用的贪心,一个一个遍历
bool canJump(vector<int>& nums) {
int n = nums.size();
int rightmost = 0;
for (int i = 0; i < n; ++i) {
if (i <= rightmost) {
rightmost = max(rightmost, i + nums[i]);
if (rightmost >= n - 1) {
return true;
}
}
}
return false;
}
想了一下差不多就是遍历最远能到的距离,正向好理解
跳跃游戏II
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
-向后跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
class Solution {
public:
int jump(vector<int>& nums) {
int place = nums.size()-1;
int num = 0;
while(place>0){
for(int i = 0;i<place;i++){
if(nums[i]+i>=place){
place = i;
num++;
break;
}
}
}
return num;
}
};
n^2
解法,一次次遍历查找最先能跳到目标位置的位置
H 指数
给你一个整数数组 citations
,其中 citations[i]
表示研究者的第 i
篇论文被引用的次数。计算并返回该研究者的 h
指数。
根据维基百科上 h 指数的定义:h
代表“高引用次数” ,一名科研人员的 h
指数 是指他(她)至少发表了 h
篇论文,并且 至少 有 h
篇论文被引用次数大于等于 h
。如果 h
有多种可能的值,h
指数 是其中最大的那个。
class Solution {
public:
int hIndex(vector<int>& citations) {
int n = citations.size();
sort(citations.begin(), citations.end());
for(int i = 0;i<n;i++){
if(citations[i]>=n-i)return n-i;
}
return 0;
}
};
O(1) 时间插入、删除和获取随机元素(哈希)
class RandomizedSet {
public:
RandomizedSet() {
srand((unsigned)time(NULL));
}
bool insert(int val) {
if (m.count(val)) return false;
a.push_back(val);
m[val] = a.size() - 1;
return true;
}
bool remove(int val) {
if (!m.count(val)) return false;
int idx = m[val];
int last = a.back();
a[idx] = last;
m[last] = idx;
a.pop_back();
m.erase(val);
return true;
}
int getRandom() {
int randomIndex = rand()%a.size();
return a[randomIndex];
}
private:
vector<int>a;
map<int, int>m;
};
/**
* Your RandomizedSet object will be instantiated and called as such:
* RandomizedSet* obj = new RandomizedSet();
* bool param_1 = obj->insert(val);
* bool param_2 = obj->remove(val);
* int param_3 = obj->getRandom();
*/
map
和 vector
除自身以外数组的乘积(双指针)
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 **不要使用除法,**且在 O(n)
时间复杂度内完成此题。
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int num = nums.size();
vector<int>answer(num);
int r = 1;
answer[0] = 1;
for(int i = 1;i<num;i++){
answer[i] = answer[i - 1] * nums[i - 1];
}
for(int i = num - 1;i>=0;i--){
answer[i] *= r;
r *= nums[i];
}
return answer;
}
};
加油站(做了很长时间)
在一条环路上有 n
个加油站,其中第 i
个加油站有汽油 gas[i]
升。
你有一辆油箱容量无限的的汽车,从第 i
个加油站开往第 i+1
个加油站需要消耗汽油 cost[i]
升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gas
和 cost
,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1
。如果存在解,则 保证 它是 唯一 的。
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int n = gas.size(), num = 0;
int p = 0;
int gassum = 0;
int costsum = 0;
for(int i = 0;i<n;i++){
gassum+=gas[i];
costsum+=cost[i];
num += gas[i];
num -= cost[i];
if(num > 0 && num+cost[i]-gas[i] == 0)p=i;
if(num < 0)num = 0;
}
if(costsum>gassum)return -1;
return p;
}
};
如果 x
不能到达 y
,那么 x
到 y
之间的所有点也都到达不了 y
所以是 O(n)
的遍历就好了
分发糖果
n
个孩子站成一排。给你一个整数数组 ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1
个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
class Solution {
public:
int candy(vector<int>& ratings) {
int n = ratings.size();
int num = 1;
vector<int> nums(40005);
nums[0] = 1;
for(int i = 1;i<n;i++){
if(ratings[i] > ratings[i - 1]){
nums[i] = nums[i-1]+1;
num += nums[i];
}
else nums[i] = 1,num++;
}
for(int i = n - 2;i >= 0; i--){
if(ratings[i] > ratings[i + 1] && nums[i] <= nums[i + 1]){
num += nums[i + 1] + 1 - nums[i];
nums[i] = nums[i+1] + 1;
}
}
for(int i = 0;i<n;i++)cout<<nums[i]<<' ';
return num;
}
};
从左到右在从右到左遍历
接雨水
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
class Solution {
public:
int trap(vector<int>& height) {
int sum = 0;
int num = height.size();
int l[20005], r[20005], lmax = height[0], rmax = height[num - 1];
for(int i = num - 2;i > 0; i--){
if(height[i + 1] > rmax){
rmax = height[i + 1];
}
r[i] = rmax;
}
for(int i = 1;i < num; i++){
if(height[i - 1] > lmax){
lmax = height[i - 1];
}
l[i] = lmax;
}
for(int i = 1;i<=min(lmax, rmax);i++){
for(int j = 1;j<num - 1;j++){
if(min(l[j], r[j])>=i && height[j] < i){
sum++;
}
}
}
return sum;
}
};
先从左往右和从右往左分别遍历一次,用 l[] 和 r[] 记录当前点左端的最高点和右端的最高点,然后从一格高度开始,如果当前的高度小于 i
,并且两边最高高度大于等于 i
,则这一格必定有水
然后就愉快的超时了,时间复杂度是 O(m*n)
,m
为最大的数, n
为数组个数
但优化也很简单,我们已经求出了当前列左端的最大值和右端的最大值,我们只需要按列分析,如果当前列高度大于等于左右两端最大值中的小的那一个,那么就接不到雨水,反之可以接到数量等于差值的雨水。
class Solution {
public:
int trap(vector<int>& height) {
int sum = 0;
int num = height.size();
int l[20005], r[20005], lmax = height[0], rmax = height[num - 1];
for(int i = num - 2;i > 0; i--){
if(height[i + 1] > rmax){
rmax = height[i + 1];
}
r[i] = rmax;
}
for(int i = 1;i < num; i++){
if(height[i - 1] > lmax){
lmax = height[i - 1];
}
l[i] = lmax;
}
for(int i = 1;i<num - 1;i++){
if(height[i] < min(l[i], r[i]))sum+=min(l[i], r[i]) - height[i];
}
return sum;
}
};
时间复杂度 O(n)
,潇洒拿下字节offer
找出字符串中第一个匹配项的下标
给你两个字符串 haystack
和 needle
,请你在 haystack
字符串中找出 needle
字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle
不是 haystack
的一部分,则返回 -1
。
class Solution {
public:
int strStr(string haystack, string needle) {
string s = needle + '#' + haystack;
if(needle.size() == 0)return 0;
int len = s.size();
vector<int>pi(len);
for(int i = 1;i<len;i++){
int j = pi[i - 1];
while(j>0&& s[i] != s[j])j = pi[j - 1];
if(s[i] == s[j])j++;
pi[i] = j;
}
for(int i = needle.size() + 1;i<len;i++){
if(pi[i] == needle.size())return i - 2 * needle.size();
}
return -1;
}
};
前缀函数与 kmp
算法
前缀函数 pi[i]
如果 s
的前 j
个字符组成的字符串和后 j
个字符组成的字符串相同且没有交集,并且 j
为能取到的最大值,那么 pi[i] = j
, pi[i]
表示 s
的前 i
个字符组成的字符串的最长的相等的真前缀与真后缀的长度。
接下来就是如何求前缀函数
首先是朴素算法直接按照定义求前缀函数
一个直接按照定义计算前缀函数的算法流程:
vector<int> prefix_function(string s) {
int n = (int)s.length();
vector<int> pi(n);
for (int i = 1; i < n; i++)
for (int j = i; j >= 0; j--)
if (s.substr(0, j) == s.substr(i - j + 1, j)) {
pi[i] = j;
break;
}
return pi;
}
j
从 i
开始,因为 j
不可能超过 i
,实际上也不可能超过 (i+1)/2
,然后依次查询前 j
个和后 j
个
时间复杂度为 O(n^3)
所以很慢
第一个优化 相邻的前缀函数值最多增加 1
vector<int> prefix_function(string s) {
int n = (int)s.length();
vector<int> pi(n);
for (int i = 1; i < n; i++)
for (int j = pi[i - 1] + 1; j >= 0; j--) // improved: j=i => j=pi[i-1]+1
if (s.substr(0, j) == s.substr(i - j + 1, j)) {
pi[i] = j;
break;
}
return pi;
}
总复杂度降为了 O(n^2)
第二个优化 在第一个优化中,当 s[i+1] == s[pi[i]]
时 pi[i] = p[i-1]+1
,现在我们考虑当s[i+1] != s[pi[i]]
时如何跳转而不是 j--
我们需要想第二长度可能是多少,在不相等的情况下我们需要在第一长度内找到真前后缀相等且长度小于 pi[i]
的前后缀,我们会发现这个值是 pi[i-1]
, j(n) = pi[j(n-1) - 1]
vector<int> prefix_function(string s) {
int n = (int)s.length();
vector<int> pi(n);
for (int i = 1; i < n; i++) {
int j = pi[i - 1];
while (j > 0 && s[i] != s[j]) j = pi[j - 1];
if (s[i] == s[j]) j++;
pi[i] = j;
}
return pi;
}
文本左右对齐(没做)
给定一个单词数组 words
和一个长度 maxWidth
,重新排版单词,使其成为每行恰好有 maxWidth
个字符,且左右两端对齐的文本。
你应该使用 “贪心算法” 来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ' '
填充,使得每行恰好有 maxWidth 个字符。
要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。
文本的最后一行应为左对齐,且单词之间不插入额外的空格。
注意:
- 单词是指由非空格字符组成的字符序列。
- 每个单词的长度大于 0,小于等于 maxWidth。
- 输入单词数组
words
至少包含一个单词。
双指针
验证回文串(字符串操作)
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
字母和数字都属于字母数字字符。
给你一个字符串 s
,如果它是 回文串 ,返回 true
;否则,返回 false
。
class Solution {
public:
bool isPalindrome(string s) {
string ss;
for(int i = 0;i<s.size();i++){
if(isalnum(s[i]))ss+=tolower(s[i]);
}
string sss(ss.rbegin(), ss.rend());
return sss == ss;
}
};
tolower()
rbegin(), rend()
isalnum()
判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"
是"abcde"
的一个子序列,而"aec"
不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
class Solution {
public:
bool isSubsequence(string s, string t) {
int n = s.length(), m = t.length();
int i = 0, j = 0;
for(;i<n,j<m;){
if(s[i] == t[j])i++;
j++;
}
return i == n;
}
};
双指针
两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers
,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target
的两个数。如果设这两个数分别是 numbers[index1]
和 numbers[index2]
,则 1 <= index1 < index2 <= numbers.length
。
以长度为 2 的整数数组 [index1, index2]
的形式返回这两个整数的下标 index1
和 index2
。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int left = 0, right = numbers.size() - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return {left + 1, right + 1};
}
else if (sum < target) {
left++;
}
else {
right--;
}
}
return {left + 1, right + 1};
}
};
双指针,本来一开始看到这不哈希吗,结果只能使用常量级空间…
盛最多水的容器
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
**说明:**你不能倾斜容器。
class Solution {
public:
int maxArea(vector<int>& height) {
int l = 0, r = height.size() - 1;
int ans = 0;
while(l < r){
int n = min(height[l], height[r]) * (r-l);
ans = max(ans, n);
if(height[l] <= height[r])l++;
else r--;
}
return ans;
}
};
双指针和贪心
三数之和
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
int n = nums.size();
vector<vector<int>>ans;
for(int i = 0;i<n;i++){
if(i>0 && nums[i] == nums[i-1]){
continue;
}
int k = n - 1;
int target = -nums[i];
for(int j = i + 1;j<n;j++){
if(j > i+1 && nums[j] == nums[j -1])continue;
while(j<k && nums[j] + nums[k] > target)k--;
if(j == k)break;
if(nums[j] + nums[k] == target)
ans.push_back({nums[i], nums[j], nums[k]});
}
}
return ans;
}
};
滑动窗口
长度最小的子数组
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其总和大于等于 target
的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回 0
。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int sum = nums[0], ans = 9999999;
int start = 0,end = 0;
while(start<=end && end < nums.size()){
if(sum >= target){
ans = min(ans, end - start + 1);
sum -= nums[start++];
}
else {
end++;
if(end < nums.size())sum += nums[end];
}
}
if(ans != 9999999)return ans;
return 0;
}
};
太晚了困得不行又成功看错,题目要求数组的和大于等于 target
,也没注意到数组下标是连续的
知道这两点这道题就很简单了,因为是正整数数组所以每多一个数和肯定是增加的, O(n)
级别遍历即可
无重复字符的最长子串
给定一个字符串 s
,请你找出其中不含有重复字符的 最长 子串 的长度。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int start = 0, end = 0;
int b[100000] = {};
int num = 0;
while(end < s.size()){
if(b[int(s[end])] > start){
start = b[int(s[end])];
}
b[int(s[end])] = end + 1;
end++;
num = max(num, end - start);
}
return num;
}
};
剩下的还没做