初级算法题(上)

这篇博客详细介绍了LeetCode官方推出的初级算法题目,包括数组和字符串两大主题,涵盖删除有序数组重复项、买卖股票最佳时机、旋转数组、存在重复元素等经典问题,旨在帮助初学者提升算法和数据结构能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

这是由 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->first;
}

// 方法二哈希表,测试通过
// 向表中填入数字,遇到重复的数字将其删除,最后一定剩下一个单独的数字将其返回
int singleNumber(vector<int>& nums) {
	unordered_set<int> myset;
	for (auto x : nums)
	{
		if (myset.find(x) != myset.end())
		{
			myset.erase(x);
		}
		else
		{
			myset.insert(x);
		}
	}
	unordered_set<int>::iterator it = myset.begin()++;
	return *it;
}

// 方法三位运算,测试通过
// 通过异或性质0^a = a, a^a = 0, a^b^a = a^a^b = b得到结论,时间复杂度低
int singleNumber(vector<int>& nums) {
	int reduce = 0;
	for (auto x : nums)
	{
		reduce ^= x;
	}
	return reduce;
}

1.06 两个数组的交集

给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,
应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

示例 1:
输入:nums1 = [1, 2, 2, 1], nums2 = [2, 2]
输出:[2, 2]

示例 2:
输入:nums1 = [4, 9, 5], nums2 = [9, 4, 9, 8, 4]
输出:[4, 9]

// 方法一哈希表,测试通过
// 采用两个哈希表,记录两个数组中元素出现个数,空间复杂度高
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
	map<int, int> map1;
	map<int, int> map2;
	for (auto x : nums1)
	{
		auto it = map1.find(x);
		if (it != map1.end())
		{
			it->second += 1;
		}
		else
		{
			map1.insert(std::pair<int, int>(x, 1));
		}
	}
	for (auto x : nums2)
	{
		auto it = map2.find(x);
		if (it != map2.end())
		{
			it->second += 1;
		}
		else
		{
			map2.insert(std::pair<int, int>(x, 1));
		}
	}
	if (nums1.size() <= nums2.size())
	{
		vector<int> res;
		map<int, int>::iterator it = map1.begin();
		for (; it != map1.end(); ++it)
		{
			auto x = map2.find(it->first);
			if (x != map2.end())
			{
				int count = std::min(it->second, x->second);
				for (int i = 0; i < count; ++i)
				{
					res.push_back(x->first);
				}
			}
		}
		return res;
	}
	else
	{
		vector<int> res;
		map<int, int>::iterator it = map2.begin();
		for (; it != map2.end(); ++it)
		{
			auto x = map1.find(it->first);
			if (x != map1.end())
			{
				int count = std::min(it->second, x->second);
				for (int i = 0; i < count; ++i)
				{
					res.push_back(x->first);
				}
			}
		}
		return res;
	}
}

// 方法二哈希表,测试通过
// 代码简洁,空间消耗小
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
	unordered_map<int, int> mymap;
	for (auto x : nums1)
	{
		auto it = mymap.find(x);
		if (it != mymap.end())
		{
			it->second += 1;
		}
		else
		{
			mymap.insert(std::pair<int, int>(x, 1));
		}
	}
	vector<int> res;
	for (auto x : nums2)
	{
		auto it = mymap.find(x);
		if (it != mymap.end())
		{
			if (it->second != 0)
			{
				res.push_back(x);
				it->second -= 1;
			}
		}
	}
	return res;
}

// 方法三排序+双指针,测试通过
// 排序后定义两个指针遍历两个数组
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
	std::sort(nums1.begin(), nums1.end());
	std::sort(nums2.begin(), nums2.end());
	vector<int>::iterator it1 = nums1.begin();
	vector<int>::iterator it2 = nums2.begin();
	vector<int> result;
	while (it1 != nums1.end() && it2 != nums2.end())
	{
		if (*it1 == *it2)
		{
			result.push_back(*it1);
			++it1;
			++it2;
		}
		else if (*it1 < *it2)
		{
			++it1;
		}
		else
		{
			++it2;
		}
	}
	return result;
}

1.07 加一

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

示例 1:
输入:digits = [1, 2, 3]
输出:[1, 2, 4]
解释:输入数组表示数字 123。

示例 2:
输入:digits = [9,9,9,9]
输出:[1, 0, 0, 0, 0]
解释:输入数组表示数字 10000。

示例 3:
输入:digits = [0]
输出:[1]

// 直接法,测试通过
vector<int> plusOne(vector<int>& digits) {
	int n = digits.size();
	if (digits[n - 1] != 9)
	{
		digits[n - 1] += 1;
		return digits;
	}
	else
	{
		int c = 1;
		for (int i = n - 1; i >= 0; --i)
		{
			if (c == 0) break;
			digits[i] += 1;
			digits[i] %= 10;
			if (digits[i] != 0) c = 0;
		}
		if (c == 1)
		{
			vector<int> newarray;
			newarray.push_back(1);
			for (auto x : digits)
			{
				newarray.push_back(x);
			}
			return newarray;
		}
		return digits;
	}
}

1.08 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:
输入: nums = [0, 1, 0, 3, 12]
输出 : [1, 3, 12, 0, 0]

示例 2 :
输入 : nums = [0]
输出 : [0]

// 方法一直接法,测试通过
void moveZeroes(vector<int>& nums) {
	int n = nums.size();
	int index = 0;
	for (int i = 0; i < n; ++i)
	{
		if (nums[i] != 0)
		{
			nums[index++] = nums[i];
		}
	}
	while (index < n)
	{
		nums[index++] = 0;
	}
}

// 方法二双指针,测试通过
// 左指针左边保证非零值,右指针到左指针之间保证零值
void moveZeroes(vector<int>& nums) {
	int n = nums.size(), left = 0, right = 0;
	while (right < n) {
		if (nums[right]) {
			swap(nums[left], nums[right]);
			left++;
		}
		right++;
	}
}

1.09 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:
输入:nums = [2, 7, 11, 15], target = 9
输出:[0, 1]
解释:因为 nums[0] + nums[1] == 9 ,返回[0, 1] 。

示例 2:
输入:nums = [3, 2, 4], target = 6
输出:[1, 2]

示例 3:
输入:nums = [3, 3], target = 6
输出:[0, 1]

// 方法一暴力枚举,测试通过
// 时间复杂度很高,不建议使用
vector<int> twoSum(vector<int>& nums, int target) {
	vector<int> resarray;
	bool flag = false;
	for (int i = 0; i < nums.size(); ++i)
	{
		for (int j = i + 1; j < nums.size(); ++j)
		{
			if (nums[i] + nums[j] == target)
			{
				resarray.push_back(i);
				resarray.push_back(j);
				flag = true;
				break;
			}
		}
		if (flag) break;
	}
	return resarray;
}

// 方法二哈希表,测试通过
// 遍历数组,查找target-nums[i],查找成功返回,不成功插入,时间复杂度很低
vector<int> twoSum(vector<int>& nums, int target) {
	unordered_map<int, int> hashtable;
	for (int i = 0; i < nums.size(); ++i)
	{
		auto ret = hashtable.find(target - nums[i]);
		if (ret != hashtable.end())
		{
			return { ret->second,i };
		}
		hashtable.insert(std::pair<int, int>(nums[i], i));
	}
	return {};
}

1.10 有效数独

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

数字 1 - 9 在每一行只能出现一次。
数字 1 - 9 在每一列只能出现一次。
数字 1 - 9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

示例 1:
输入:board =
[ [“5”, “3”, “.”, “.”, “7”, “.”, “.”, “.”, “.”]
, [“6”, “.”, “.”, “1”, “9”, “5”, “.”, “.”, “.”]
, [“.”, “9”, “8”, “.”, “.”, “.”, “.”, “6”, “.”]
, [“8”, “.”, “.”, “.”, “6”, “.”, “.”, “.”, “3”]
, [“4”, “.”, “.”, “8”, “.”, “3”, “.”, “.”, “1”]
, [“7”, “.”, “.”, “.”, “2”, “.”, “.”, “.”, “6”]
, [“.”, “6”, “.”, “.”, “.”, “.”, “2”, “8”, “.”]
, [“.”, “.”, “.”, “4”, “1”, “9”, “.”, “.”, “5”]
, [“.”, “.”, “.”, “.”, “8”, “.”, “.”, “7”, “9”]]
输出:true

示例 2:
输入:board =
[ [“8”, “3”, “.”, “.”, “7”, “.”, “.”, “.”, “.”]
, [“6”, “.”, “.”, “1”, “9”, “5”, “.”, “.”, “.”]
, [“.”, “9”, “8”, “.”, “.”, “.”, “.”, “6”, “.”]
, [“8”, “.”, “.”, “.”, “6”, “.”, “.”, “.”, “3”]
, [“4”, “.”, “.”, “8”, “.”, “3”, “.”, “.”, “1”]
, [“7”, “.”, “.”, “.”, “2”, “.”, “.”, “.”, “6”]
, [“.”, “6”, “.”, “.”, “.”, “.”, “2”, “8”, “.”]
, [“.”, “.”, “.”, “4”, “1”, “9”, “.”, “.”, “5”]
, [“.”, “.”, “.”, “.”, “8”, “.”, “.”, “7”, “9”]]

// 方法一自己的方法,测试通过
// 代码复杂,时间、空间复杂度高,不建议采用
class index
{
	public:
		int _x;
		int _y;
		index(int x, int y) :_x(x), _y(y) {}
		bool operator==(index& a)
		{
			if (_x == a._x || _y == a._y) return true;
			return false;
		}
};
bool isValidSudoku(vector<vector<char>>& board) {
	unordered_multimap<char, index> hashmap;
	map<char, int> counttable;
	for (int i = 1; i <= 9; ++i)
	{
		counttable.insert(std::pair<char, int>('0' + i, 0));
	}
	for (int i = 0; i < board.size(); ++i)
	{
		for (int j = 0; j < board[i].size(); ++j)
		{
			if (board[i][j] == '.') continue;
			auto ret = hashmap.find(board[i][j]);
			if (ret != hashmap.end())
			{
				auto cnt = hashmap.count(board[i][j]);
				index position(i, j);
				for (; cnt > 0; cnt--, ret++)
				{
					if (ret->second == position) return false;
				}
			}
			hashmap.insert(std::pair<char, index>(board[i][j], index(i, j)));
		}
	}
	for (int i = 0; i < 9; ++i)
	{
		for (int j = 0; j < 9; ++j)
		{
			int p = i / 3 * 3 + j / 3;
			int q = i % 3 * 3 + j % 3;
			auto ret = counttable.find(board[p][q]);
			if (ret != counttable.end())
			{
				ret->second += 1;
				if (ret->second >= 2) return false;
			}
		}
		map<char, int>::iterator it = counttable.begin();
		while (it != counttable.end())
		{
			it->second = 0;
			++it;
		}
	}
	return true;
}

// 方法二直接求解法,测试通过
// 设置行列以及子矩阵的二维矩阵,一次性判断所有条件。空间、时间复杂度很低,建议采用
bool isValidSudoku(vector<vector<char>>& board) {
	int row[9][10] = { 0 };
	int col[9][10] = { 0 };
	int box[9][10] = { 0 };
	for (int i = 0; i < 9; i++) {
		for (int j = 0; j < 9; j++) {
			if (board[i][j] == '.') continue;
			int curNumber = board[i][j] - '0';
			if (row[i][curNumber]) return false;
			if (col[j][curNumber]) return false;
			if (box[j / 3 + (i / 3) * 3][curNumber]) return false;

			row[i][curNumber] = 1;
			col[j][curNumber] = 1;
			box[j / 3 + (i / 3) * 3][curNumber] = 1;
		}
	}
	return true;
}

1.11 旋转图像

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

示例 1:
输入:matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]
输出: [ [7, 4, 1], [8, 5, 2], [9, 6, 3] ]

示例 2:
输入:matrix = [ [5, 1, 9, 11], [2, 4, 8, 10], [13, 3, 6, 7], [15, 14, 12, 16] ]
输出: [ [15, 13, 2, 5], [14, 3, 4, 1], [12, 6, 8, 9], [16, 7, 10, 11] ]

// 找规律法,测试通过
// 首先上下交换,然后对角线交换
void rotate(vector<vector<int>>& matrix) {
	int row = matrix.size();
	int first = 0, last = row - 1;
	while (first < last)
	{
		std::swap(matrix[first], matrix[last]);
		first++;
		last--;
	}
	for (int i = 0; i < row; ++i)
	{
		for (int j = i + 1; j < row; ++j)
		{
			std::swap(matrix[i][j], matrix[j][i]);
		}
	}
}

字符串

字符串问题在面试中出现频率很高,你极有可能在面试中遇到。

我们推荐以下题目:反转字符串,字符串中第一个唯一字符,字符串转整数(atoi)和 实现 strStr() 。

2.01 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:
输入:s = [“h”, “e”, “l”, “l”, “o”]
输出:[“o”, “l”, “l”, “e”, “h”]

示例 2:
输入:s = [“H”, “a”, “n”, “n”, “a”, “h”]
输出:[“h”, “a”, “n”, “n”, “a”, “H”]

// 方法一调用算法库函数,测试通过
void reverseString(vector<char>& s) {
	reverse(s.begin(), s.end());
}

// 方法二双指针,测试通过
void reverseString(vector<char>& s) {
	int left = 0;
	int right = s.size() - 1;
	while (left < right)
	{
		std::swap(s.at(left), s.at(right));
		left++;
		right--;
	}
}

2.02 整数反转

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [−2^31, 2^31 − 1] ,就返回 0。

假设环境不允许存储 64 位整数(有符号或无符号)。

示例 1:
输入:x = 123
输出:321

示例 2:
输入:x = -123
输出: - 321

示例 3:
输入:x = 120
输出:21

示例 4:
输入:x = 0
输出:0

// 防止算数溢出,设置INT_MAX INT_MIN
int reverse(int x) {
	int res = 0;
	while (x != 0)
	{
		int t = x % 10;
		if (res > INT_MAX / 10 || res < INT_MIN / 10) return 0;
		res = res * 10 + t;
		x = x / 10;
	}
	return res;
}

2.03 字符串中的第一个唯一字符

给定一个字符串 s ,找到它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 - 1 。

示例 1:
输入 : s = “leetcode”
输出 : 0

示例 2 :
输入 : s = “loveleetcode”
输出 : 2

示例 3 :
输入 : s = “aabb”
输出 : -1

// 方法一数组,测试通过
// 用数组存储字符出现的次数,然后再遍历一遍数组,直到找到第一个计数为1的元素返回其下标
int firstUniqChar(string s) {
	int* arr = (int*)malloc(sizeof(int) * 26);
	memset(arr, 0, sizeof(int) * 26);
	for (int i = 0; i < s.size(); ++i)
	{
		arr[s[i] - 'a'] += 1;
	}
	for (int i = 0; i < s.size(); ++i)
	{
		if (arr[s[i] - 'a'] == 1) return i;
	}
	return -1;
}

// 方法二哈希表,测试通过
// 将方法一的数组改成哈希表即可
int firstUniqChar(string s) {
	unordered_map<char, int> hashtable;
	for (int i = 0; i < s.size(); ++i)
	{
		auto x = hashtable.find(s[i]);
		if (x != hashtable.end())
		{
			x->second += 1;
		}
		else
		{
			hashtable.insert(make_pair(s[i], 1));
		}
	}
	for (int i = 0; i < s.size(); ++i)
	{
		if (hashtable.find(s[i])->second == 1) return i;
	}
	return -1;
}

// 方法三哈希表,测试通过
// 使用哈希表,不记录出现次数,记录首次出现的下标
// 如果再次出现,就讲原先的下标记成-1,然后遍历数组,找到第一个不为-1的元素返回其下标
int firstUniqChar(string s) {
	unordered_map<char, int> hashmap;
	for (int i = 0; i < s.length(); ++i)
	{
		auto x = hashmap.find(s[i]);
		if (x != hashmap.end())
		{
			x->second = -1;
		}
		else
		{
			hashmap.insert(make_pair(s[i], i));
		}
	}
	for (int i = 0; i < s.length(); ++i)
	{
		if (hashmap.find(s[i])->second != -1) return i;
	}
	return -1;
}

附加题 前k个高频单词

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。

示例 1:
输入 : words = [“i”, “love”, “leetcode”, “i”, “love”, “coding”], k = 2
输出 : [“i”, “love”]
解析 : “i” 和 “love” 为出现次数最多的两个单词,均为2次。
注意,按字母顺序 “i” 在 “love” 之前。

示例 2:
输入 : [“the”, “day”, “is”, “sunny”, “the”, “the”, “the”, “sunny”, “is”, “is”] , k = 4
输出 : [“the”, “is”, “sunny”, “day”]
解析 : “the”, “is”, “sunny” 和 “day” 是出现次数最多的四个单词,
出现次数依次为 4, 3, 2 和 1 次。

// 哈希表,测试通过
// 记录所用单词的出现频率,然后根据题目要求排序,并取出前k个字母返回
vector<string> topKFrequent(vector<string>& words, int k) {
	unordered_map<string, int> cnt;
	for (auto& word : words) {
		++cnt[word];
	}
	vector<string> rec;
	for (auto& x : cnt) {
		rec.emplace_back(x.first);
	}
	sort(rec.begin(), rec.end(), [&](const string& a, const string& b) -> bool {
		return cnt[a] == cnt[b] ? a < b : cnt[a] > cnt[b];
		});
	rec.erase(rec.begin() + k, rec.end());
	return rec;
}

2.04 有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

示例 1:
输入: s = “anagram”, t = “nagaram”
输出 : true

示例 2 :
输入 : s = “rat”, t = “car”
输出 : false

// 方法一哈希表,测试通过
// 统计第一个单词中字母个数,与第二个单词中所有字母作差,如果小于零说明不是异位词
bool isAnagram(string s, string t) {
	unordered_map<char, int> hashtable;
	for (int i = 0; i < s.size(); ++i)
	{
		++hashtable[s[i]];
	}
	for (int i = 0; i < t.size(); ++i)
	{
		--hashtable[t[i]];
	}
	auto it = hashtable.begin();
	while (it != hashtable.end())
	{
		if (it->second != 0) return false;
		++it;
	}
	return true;
}

// 方法二哈希表,测试通过
// 思路同方法一,但是哈希表使用数组方式,速度较方法一更快
bool isAnagram(string s, string t) {
	if (s.size() != t.size()) return false;
	vector<int> table(26, 0);
	for (auto& x : s)
	{
		table[x - 'a']++;
	}
	for (auto& x : t)
	{
		table[x - 'a']--;
		if (table[x - 'a'] < 0) return false;
	}
	return true;
}

// 方法三排序,测试通过
// 如果两个字母是异位词,那么经过排序后一定相等
bool isAnagram(string s, string t) {
	if (s.size() != t.size()) return false;
	sort(s.begin(), s.end());
	sort(t.begin(), t.end());
	return s == t;
}

2.05 验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

示例 1:
输入: “A man, a plan, a canal: Panama”
输出 : true
解释:“amanaplanacanalpanama” 是回文串

示例 2 :
输入 : “race a car”
输出 : false
解释:“raceacar” 不是回文串

// 双指针,测试通过
// 主要是要跳过非字母数字字符以及转换大小写字母然后比较即可
bool isPalindrome(string s) {
	if (s.empty()) return true;
	int left = 0;
	int right = s.size() - 1;
	while (left <= right)
	{
		if (!isalnum(s[left]))
		{
			left++;
			continue;
		}
		if (!isalnum(s[right]))
		{
			right--;
			continue;
		}
		if (isupper(s[left])) s[left] = tolower(s[left]);
		if (isupper(s[right])) s[right] = tolower(s[right]);
		if (s[left] == s[right])
		{
			left++;
			right--;
		}
		else return false;
	}
	return true;
}

2.06 字符串转换整数

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C / C++ 中的 atoi 函数)。

函数 myAtoi(string s) 的算法如下:

  • 读入字符串并丢弃无用的前导空格。
  • 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
  • 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
  • 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
  • 如果整数数超过 32 位有符号整数范围[−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
  • 返回整数作为最终结果。

注意:
本题中的空白字符只包括空格字符 ’ ’ 。
除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。

示例 1:
输入:s = “42”
输出:42
解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。
第 1 步:“42”(当前没有读入字符,因为没有前导空格)
^
第 2 步:“42”(当前没有读入字符,因为这里不存在 ‘-’ 或者 ‘+’)
^
第 3 步:“42”(读入 “42”)
^
解析得到整数 42 。
由于 “42” 在范围[-231, 231 - 1] 内,最终结果为 42 。

示例 2:
输入:s = " -42"
输出: - 42
解释:
第 1 步:" -42"(读入前导空格,但忽视掉)
^
第 2 步:" -42"(读入 ‘-’ 字符,所以结果应该是负数)
^
第 3 步:" -42"(读入 “42”)
^
解析得到整数 - 42 。
由于 “-42” 在范围[-231, 231 - 1] 内,最终结果为 - 42 。

示例 3:
输入:s = “4193 with words”
输出:4193
解释:
第 1 步:“4193 with words”(当前没有读入字符,因为没有前导空格)
^
第 2 步:“4193 with words”(当前没有读入字符,因为这里不存在 ‘-’ 或者 ‘+’)
^
第 3 步:“4193 with words”(读入 “4193”;由于下一个字符不是一个数字,所以读入停止)
^
解析得到整数 4193 。
由于 “4193” 在范围[-231, 231 - 1] 内,最终结果为 4193 。

// 方法一逻辑方法,测试通过
// 根据题意考虑各种情况,给出合适条件判断
// 缺点是题目条件要求多,极易造成代码的架构混乱,不建议采用
int myAtoi(string s) {
	int res = 0;
	int c = 1;
	int i = 0;
	while (i < s.size())
	{
		if (s[i] == ' ')
		{
			i++;
		}
		else if (s[i] == '-')
		{
			c = -1;
			i++;
			break;
		}
		else if (s[i] == '+')
		{
			c = 1;
			i++;
			break;
		}
		else break;
	}
	for (int j = i; j < s.size(); ++j)
	{
		if (isdigit(s[j]))
		{
			int num = s[j] - '0';
			// 越界判断
			if (res > INT_MAX / 10 || res == INT_MAX / 10 && num > INT_MAX % 10)
			{
				return c == -1 ? INT_MIN : INT_MAX;
			}
			res = res * 10 + num;
		}
		else break;
	}
	return res * c;
}

// 方法二自动机,测试通过
// 程序在每个时刻有一个状态 s,每次从序列中输入一个字符 c,并根据字符 c 转移到下一个状态 s'。
// 这样,我们只需要建立一个覆盖所有情况的从 s 与 c 映射到 s' 的表格即可解决题目中的问题。
// 这是一种类似状态方案的思想,代码清晰明了,建议采用
class Automaton {
	string state = "start";
	unordered_map<string, vector<string>> table = {
		{"start",		{"start", "signed", "in_number", "end"}},
		{"signed",		{"end", "end", "in_number", "end"}},
		{"in_number",	{"end", "end", "in_number", "end"}},
		{"end",			{"end", "end", "end", "end"}}
	};

	int get_col(char c) {
		if (isspace(c)) return 0;
		if (c == '+' or c == '-') return 1;
		if (isdigit(c)) return 2;
		return 3;
	}
public:
	int sign = 1;
	long long ans = 0;

	void get(char c) {
		state = table[state][get_col(c)];
		if (state == "in_number") {
			ans = ans * 10 + c - '0';
			ans = sign == 1 ? min(ans, (long long)INT_MAX) : min(ans, -(long long)INT_MIN);
		}
		else if (state == "signed")
			sign = c == '+' ? 1 : -1;
	}
};
int myAtoi(string str) {
	Automaton automaton;
	for (char c : str)
		automaton.get(c);
	return automaton.sign * automaton.ans;
}

// 方法三剑走偏锋法,测试通过
// 利用C++ stringstream类,直接进行转换
int myAtoi(string str)
{
	stringstream ss(str);
	int n;
	ss >> n;
	return n;
}

2.07 strStr()

实现 strStr() 函数。

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回  - 1 。

说明:

当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

// 方法一朴素算法,测试通过
// 按照规则逐个匹配即可
int strStr(string haystack, string needle) {
	if (needle.empty()) return 0;
	int ih = 0;
	int in = 0;
	for (; ih < haystack.size(); )
	{
		if (in == needle.size()) break;
		if (haystack[ih] == needle[in])
		{
			in++;
			ih++;
		}
		else
		{
			ih = ih - in + 1;
			in = 0;
		}
	}
	if (in == needle.size()) return ih - needle.size();
	else return -1;
}

// 方法二调用系统库函数,测试通过
int strStr(string haystack, string needle) {
	return haystack.find(needle);
}
// 方法三KMP算法

2.08 外观数组

给定一个正整数 n ,输出外观数列的第 n 项。

「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。

你可以将其视作是由递归公式定义的数字字符串序列:

countAndSay(1) = “1”
countAndSay(n) 是对 countAndSay(n - 1) 的描述,然后转换成另一个数字字符串。
前五项如下:

  1. 1
    
  2. 11
    
  3. 21
    
  4. 1211
    
  5. 111221
    

第一项是数字 1
描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 “11”
描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 “21”
描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 “1211”
描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 “111221”
要 描述 一个数字字符串,首先要将字符串分割为 最小 数量的组,每个组都由连续的最多 相同字符 组成。然后对于每个组,
先描述字符的数量,然后描述字符,形成一个描述组。要将描述转换为数字字符串,先将每组中的字符数量用数字替换,再将所有描述组连接起来。

// 遍历求解法,测试通过
// 统计相同字符个数按照题目要求填入即可
string countAndSay(int n) {
	string res = { "1" };
	for (int i = 2; i <= n; ++i)
	{
		string temp;
		int pos = 0;
		int count = 0;
		char ch = res[0];
		while (pos < res.size())
		{
			while (ch == res[pos])
			{
				count += 1;
				pos += 1;
			}
			temp += to_string(count) + ch;
			count = 0;
			ch = res[pos];
		}
		res = temp;
	}
	return res;
}

2.09 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

示例 1:
输入:strs = [“flower”, “flow”, “flight”]
输出:“fl”

示例 2:
输入:strs = [“dog”, “racecar”, “car”]
输出:“”
解释:输入不存在公共前缀。

// 方法一纵向比较法,测试通过
string longestCommonPrefix(vector<string>& strs) {
	string res;
	for (int i = 0; i < strs[0].size(); ++i)
	{
		int count = 1;
		for (int j = 1; j < strs.size(); ++j)
		{
			if (strs[j][i] == strs[0][i])
			{
				count += 1;
			}
			else return res;
		}
		if (count == strs.size())
		{
			res.push_back(strs[0][i]);
		}
	}
	return res;
}

// 方法二横向比较法,测试通过
string longestCommonPrefix(vector<string>& strs) {
	string prefix = strs[0];
	for (int i = 1; i < strs.size(); ++i)
	{
		int length = std::min(prefix.size(), strs[i].size());
		int j = 0;
		for (; j < length; ++j)
		{
			if (prefix[j] != strs[i].at(j))
			{
				break;
			}
		}
		prefix = prefix.substr(0, j);
		if (!prefix.size())
		{
			break;
		}
	}
	return prefix;
}


// 方法三分治法,测试通过
// 以单词为单位划分,划分至两两比较最长前缀,以此求得原问题的解
string longestCommonPrefix(string& str1, string& str2)
{
	int length = std::min(str1.size(), str2.size());
	for (int index = 0; index < length; ++index)
	{
		if (str1[index] != str2[index])
		{
			return str1.substr(0, index);
		}
	}
	return str1.substr(0, length);
}
string longestCommonPrefix(vector<string>& strs, int left, int right)
{
	if (left == right)
	{
		return strs[left];
	}
	else
	{
		int mid = (left + right) / 2;
		string lcpleft = longestCommonPrefix(strs, left, mid);
		string lcpright = longestCommonPrefix(strs, mid + 1, right);
		return longestCommonPrefix(lcpleft, lcpright);
	}
}
string longestCommonPrefix(vector<string>& strs) {
	return longestCommonPrefix(strs, 0, strs.size() - 1);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值