概述
这是由 LeetCode 官方推出的经典面试题目清单,我们将题目重新整理规划,从而为大家提供更好的练习体验和帮助大家找到理想的工作。 我们将题目分为以下三个部分:
- 初级算法 - 帮助入门
- 中级算法 - 巩固训练
- 高级算法 - 提升进阶
这一系列 LeetBook 将帮助您掌握算法及数据结构,并提高您的编程能力。
编程能力就像任何其他技能一样,也是一个可以通过刻意练习大大提高的。
大多数经典面试题目都有多种解决方案。 为了达到最佳的练习效果,我们强烈建议您至少将此清单里的题目练习两遍,如果可以的话,三遍会更好。
在第二遍练习时,你可能会发现一些新的技巧或新的方法。 到第三遍的时候,你会发现你的代码要比第一次提交时更加简洁。 如果你达到了这样的效果,那么恭喜你,你已经掌握了正确的练习方法!
数组
数组问题在面试中出现频率很高,你极有可能在面试中遇到。
我们推荐以下题目:只出现一次的数字,旋转数组,两个数组的交集 II 和 两数之和。
1.01 删除有序数组的重复项
给你一个升序排列的数组 nums ,请你原地删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度。元素的相对顺序应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。
更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在原地修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
输入:nums = [1, 1, 2]
输出:2, nums = [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]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
//方法一调用系统库函数,测试通过
int removeDuplicates(vector<int>& nums) {
int length = nums.size();
if (length == 0 || length == 1) return length;
auto x = unique(nums.begin(), nums.end());
vector<int>::iterator it = nums.begin();
int count = 0;
while (it != x)
{
++count;
it++;
}
return count;
}
// 方法二快慢指针,测试通过
int removeDuplicates(vector<int>& nums) {
int length = nums.size();
if (length == 0 || length == 1) return length;
int slow = 1;
int fast = 1;
while (fast < length)
{
if (nums[fast] != nums[fast - 1])
{
nums[slow++] = nums[fast++];
}
else
{
++fast;
}
}
return slow;
}
1.02 买卖股票的最佳时机II
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和 / 或出售股票。你在任何时候最多只能持有一股股票。你也可以先购买,然后在同一天出售。
返回你能获得的最大利润 。
示例 1:
输入:prices = [7, 1, 5, 3, 6, 4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
总利润为 4 + 3 = 7 。
示例 2:
输入:prices = [1, 2, 3, 4, 5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
总利润为 4 。
示例 3:
输入:prices = [7, 6, 4, 3, 1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。
// 方法一动态规划,测试通过
// 考虑两种情况,nohold当天交易结束未持有股票,可能是前一天没有持股今天也没有购买或者前一天持股今天抛售;
// hold当天交易结束持有股票,可能是前一天持股今天没有抛售或者前一天没有持股今天购买股票。
int maxProfit_II(vector<int>& prices) {
int length = prices.size();
if (length == 1) return 0;
vector<vector<int> > dp(length, vector<int>(2));
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < length; ++i)
{
dp[i][0] = std::max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = std::max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[length - 1][0];
}
// 方法二动态规划优化,测试通过
// 使用变量代替数组,思路同方法一完全相同
int maxProfit_II(vector<int>& prices) {
if (prices.size() < 2) return 0;
int length = prices.size();
int hold = -prices[0];
int nohold = 0;
for (int i = 1; i < length; ++i)
{
nohold = std::max(nohold, hold + prices[i]);
hold = std::max(hold, nohold - prices[i]);
}
return nohold;
}
// 方法二贪心算法,测试通过
// 在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。
int maxProfit_II(vector<int>& prices) {
if (prices.size() < 2) return 0;
int length = prices.size();
int total = 0;
for (int i = 0; i < length - 1; ++i)
{
total += std::max(0, prices[i + 1] - prices[i]);
}
return total;
}
1.03 旋转数组
给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例 1:
输入: nums = [1, 2, 3, 4, 5, 6, 7], k = 3
输出 : [5, 6, 7, 1, 2, 3, 4]
解释 :
向右轮转 1 步 : [7, 1, 2, 3, 4, 5, 6]
向右轮转 2 步 : [6, 7, 1, 2, 3, 4, 5]
向右轮转 3 步 : [5, 6, 7, 1, 2, 3, 4]
示例 2 :
输入:nums = [-1, -100, 3, 99], k = 2
输出:[3, 99, -1, -100]
解释 :
向右轮转 1 步 : [99, -1, -100, 3]
向右轮转 2 步 : [3, 99, -1, -100]
// 方法一普通旋转,超时
// 计算余数可以大幅提高效率,但时间复杂度仍然高
void move_right(vector<int>& nums)
{
int length = nums.size();
int tmp = nums[length - 1];
for (int i = length - 1; i > 0; --i)
{
nums[i] = nums[i - 1];
}
nums[0] = tmp;
}
void rotate(vector<int>& nums, int k) {
int length = nums.size();
if (length < 2) return;
k = k % length;
if (k >= 0)
{
while (k--)
{
move_right(nums);
}
}
}
// 方法二反转数列,测试通过
// On级别的时间复杂度
void ReverseArray(vector<int>& nums, int left, int right) {
while (left < right) {
std::swap(nums[left++], nums[right--]);
}
}
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k = n - (k % n);
ReverseArray(nums, 0, k - 1);
ReverseArray(nums, k, n - 1);
ReverseArray(nums, 0, n - 1);
}
1.04 存在重复元素
给你一个整数数组 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
// 方法一排序,测试通过
// 排完序两两比较
bool containsDuplicate(vector<int>& nums) {
std::sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 1; ++i)
{
if (nums[i] == nums[i + 1]) return true;
}
return false;
}
// 方法二哈希表,测试通过
// 时间复杂度较低
bool containsDuplicate(vector<int>& nums) {
unordered_set<int> myset;
bool res = false;
for (auto x : nums)
{
auto flag = myset.find(x);
if (flag == myset.end())
{
myset.insert(x);
}
else
{
res = true;
break;
}
}
return res;
}
1.05 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2, 2, 1]
输出 : 1
示例 2 :
输入 : [4, 1, 2, 1, 2]
输出 : 4
// 方法一哈希表,测试通过
// 记录每个数字出现的次数,然后查找只出现一次的数字
int singleNumber(vector<int>& nums) {
unordered_map<int, int> mymap;
for (auto x : nums)
{
auto res = mymap.find(x);
if (res != mymap.end())
{
res->second += 1;
}
else
{
mymap.insert(std::pair<int, int>(x, 0));
}
}
unordered_map<int, int>::iterator it = mymap.begin();
for (; it != mymap.end(); ++it)
{
if (it->second == 0) break;
}
return it-&g