牛客最近考过的题

欢迎访问我的博客首页


1. 三角形


  2021年11月6号美图。
  题目:给出一个三角形,计算从三角形顶部到底部的最小路径和,每一步都可以移动到下面一行相邻的数字。例如,给出的三角形如下:[[20],[30,40],[60,50,70],[40,10,80,30]],最小的从顶部到底部的路径和是20 + 30 + 50 + 10 = 110。链接
  分析:假如用二维数组 triangle 存储三角形,三角形的每行 i 都从 triangle[i][0] 开始存储。则 triangle[i][j] 下面一行相邻的数字是 triangle[i+1][j] 和 triangle[i+1][j+1]。

1. 分治算法

  使用分治算法提交,没有超时。

class Solution {
public:
	int minimumTotal(vector<vector<int>> &triangle) {
		int h = triangle.size();
		int res = INT_MAX;
		fac(triangle, res);
		return res;
	}
private:
	void fac(vector<vector<int>> &triangle, int& res, int x = 0, int y = 0, int sum = 0) {
		if (y == triangle.size()) {
			res = min(res, sum);
			return;
		}
		sum += triangle[y][x];
		fac(triangle, res, x, y + 1, sum);
		if (x + 1 <= y + 1)
			fac(triangle, res, x + 1, y + 1, sum);
	}
};

2. 由上而下的动态规划算法

  题目中说,如果你能只用O(N)的额外的空间来完成这项工作的话,就可以得到附加分,其中N是三角形中的行总数。和 01 背包一样,本题中第 i 层仅与第 i-1 层有关,所以我们可以使用一维的 dp 数组。

class Solution {
public:
	int minimumTotal(vector<vector<int>> &triangle) {
		if (triangle.size() == 0)
			return 0;
		if (triangle.size() == 1)
			return triangle[0][0];

		vector<int> dp(triangle.size());
		// 1.最小问题的解。
		dp[0] = triangle[0][0];
		// 2.为求最小值做准备。
		for (int i = 1; i < triangle.size(); i++)
			dp[i] = INT_MAX;
		// 3.递推。
		for (int i = 1; i < triangle.size(); i++) {
			for (int j = triangle[i].size() - 1; j >= 0; j--) {
				if (j == 0)
					dp[j] = dp[j] + triangle[i][j];
				else if (j == triangle[i].size() - 1)
					dp[j] = dp[j - 1] + triangle[i][j];
				else
					dp[j] = min(dp[j - 1], dp[j]) + triangle[i][j];
			}
		}
		// 4.在dp中找最小值。
		int res = dp[0];
		for (int i = 1; i < dp.size(); i++)
			res = min(res, dp[i]);
		return res;
	}
};

  我们从第 2 行开始往下推导,所以第 6 行提前处理三角形只有一行的情况,这样我们就可以把 dp[0] 初始化为 triangle[0][0]。当然也可以从第 1 行开始往下推导,这时的 dp[0] 应该初始化为何值,就要根据递推公式的需要而决定:

class Solution {
public:
	int minimumTotal(vector<vector<int>> &triangle) {
		if (triangle.size() == 0)
			return 0;

		vector<int> dp(triangle.size());
		// 1.递推的基础(根据递推公式的需要而决定)。
		dp[0] = 0;
		// 2.为求最小值做准备。
		for (int i = 1; i < triangle.size(); i++)
			dp[i] = INT_MAX;
		// 3.递推。
		for (int i = 0; i < triangle.size(); i++) {
			for (int j = triangle[i].size() - 1; j >= 0; j--) {
				if (j == 0)
					dp[j] = dp[j] + triangle[i][j];
				else if (j == triangle[i].size() - 1)
					dp[j] = dp[j - 1] + triangle[i][j];
				else
					dp[j] = min(dp[j - 1], dp[j]) + triangle[i][j];
			}
		}
		// 4.在dp中找最小值。
		int res = dp[0];
		for (int i = 1; i < dp.size(); i++)
			res = min(res, dp[i]);
		return res;
	}
};

3. 由下而上的动态规划算法

  因为三角形最上一层只有一个元素,最下一层有多个元素。所以从上向下推导,需要在 dp 数组中找最小值。而从下向上推导,dp[0] 就是结果。

class Solution {
public:
	int minimumTotal(vector<vector<int>> &triangle) {
		if (triangle.size() == 0)
			return 0;

		int H = triangle.size();
		vector<int> dp(H);
		// 1.最小问题的解。
		for (int i = 0; i < triangle[H - 1].size(); i++)
			dp[i] = triangle[H - 1][i];
		// 2.递推。
		for (int i = H - 2; i >= 0; i--) {
			for (int j = 0; j < triangle[i].size(); j++) {
				dp[j] = min(dp[j], dp[j + 1]) + triangle[i][j];
			}
		}
		return dp[0];
	}
};

  由上而下的动态规划算法中,dp[j] = min(dp[j - 1], dp[j]) + triangle[i][j],即 dp[j] 与更小的 dp[j-1] 有关。所以为了避免覆盖,第二层循环中 j 是从大到小的。由下而上的动态规划算法中,dp[j] = min(dp[j], dp[j + 1]) + triangle[i][j],即 dp[j] 与更大的 dp[j+1] 有关。所以为了避免覆盖,第二层循环中 j 是从小到大的。

2. 设计LRU缓存结构


  题目
  分析:我们可以使用一个数据结构,里面的缓存按新旧程度顺序存放。插入和更新操作在一端执行,删除操作在另一端执行。队列、双端队列、双向链表在一端插入、在另一端删除的时间复杂度都是 O(1)。
  但更新时,我们需要找到待更新元素在数据结构中的位置,先删除它,然后重新插入。怎么在 O(1) 的时间复杂度内找到这个待更新的元素且删除它,这是本题的关键。在 O(1) 的时间复杂度内删除元素,只能使用链表;在 O(1) 的时间复杂度内查找元素,只能使用哈希表。所以方法是:使用哈希表,哈希表的键与缓存结点的键相同。哈希表的值是迭代器,它指向链表中与之键相同的结点。这样我们就可以根据键从哈希表中直接定位链表的结点。
  因为链表的 erase() 函数的参数只能是迭代器,所以哈希表的值也只能是迭代器。如果我们自己实现链表,哈希表的值当然可以是指针或引用类型。
  如果哈希表的值是正向迭代器,最新的缓存结点应该放在双向链表头部;如果哈希表的值是反向迭代器,最新的缓存结点应该放在双向链表尾部。因为链表的迭代器不支持自加自减运算,使用正向迭代器无法直接得到指向链表最后一个元素的迭代器,使用反向迭代器无法直接得到指向链表第一个元素的迭代器。

class Solution {
public:
	vector<int> LRU(vector<vector<int>>& operators, int k) {
		vector<int> res;
		for (auto op : operators) {
			if (op.size() == 3)
				set(op[1], op[2], k);
			else
				res.push_back(get(op[1]));
		}
		return res;
	}
private:
	void set(int key, int value, int k) {
		// 1.如果存在,先删除。
		if (ump.find(key) != ump.end()) {
			cache.erase(ump[key]);
		}
		// 2.插入链表头部并记入哈希表。
		cache.push_front(Node(key, value));
		ump[key] = cache.begin();
		// 3.如果缓存过大,就删除链表尾部较久未使用的元素。
		if (cache.size() > k) {
			ump.erase(cache.back().key);
			cache.pop_back();
		}
	}
	int get(int key) {
		// 1.找不到。
		if (ump.find(key) == ump.end())
			return -1;
		// 2.为了更新,先记录下来。
		Node update(key, ump[key]->value);
		// 3.更新:删除。
		cache.erase(ump[key]);
		// 3.更新:插入链表头部且更新哈希表中的记录。
		cache.push_front(update);
		ump[key] = cache.begin();
		return ump[key]->value;
	}
private:
	struct Node {
		Node(int k = 0, int v = 0) :key(k), value(v) {}
		int key, value;
	};
	list<Node> cache;
	unordered_map<int, list<Node>::iterator> ump;
};

3. 最长回文子串


  2021年11月8号嘀嘀。详见《LeetCode 5.最长回文子串》

4. 翻译字符串


  2021年11月9号商汤。不知道这道题叫什么名字,面试官给个例子,让写代码。例子如下:

input  = "2[ab3[c]]2[de]fg"
output = "abcccabcccdedefg"

嵌套,n>10

  首先给几个测试样例供参考:

string str = "2[ab2[c4[de]]]";	//完全的嵌套结构。
string str = "2[2[bc]]";		//完全的嵌套结构,第二个2前面没有字母。
string str = "2[ab3[c]]2[de]";	//一个嵌套结构和一个基本结构。
string str = "2[a2[c]2[d]]";	// 一个嵌套结构包含两个基本结构。
string str = "12[a]";			//数字可能不只1位。

  这道题和表达式求值的思路是一样的,甚至比表达式求值更简单,因为表达式求值要分一级运算和二级运算。本题使用两个栈很容易解决。

class Solution {
public:
	string fun(string& str) {
		if (str.size() == 0)
			return "";

		stack<char> st1, st2;
		// 1.把字符串全部放入st1。
		for (auto ch : str)
			st1.push(ch);
		// 2.把st1的元素向st2转移,每遇到左括号就处理第3步。
		while (st1.empty() != true) {
			char ch = st1.top();
			st2.push(ch);
			st1.pop();
			if (ch == '[') {
				// 3.继续从st1读取数字部分。
				string n_string;
				while (st1.empty() != true) {
					char ch = st1.top();
					if (ch < '0' || ch > '9')
						break;
					n_string = ch + n_string;
					st1.pop();
				}
				int n_digit = atoi(n_string.c_str());
				// 4.处理st2中顶部一个括号内的字符串,放入group,group是正序的。
				string group;
				st2.pop();
				while (true) {
					char ch = st2.top();
					group.push_back(ch);
					st2.pop();
					if (ch == ']')
						break;
				}
				group.pop_back();
				// 5.把n_digit个group放入st2。
				while (n_digit--) {
					for (int i = group.size() - 1; i >= 0; i--)
						st2.push(group[i]);
				}
			}
		}
		// 6.把st2中的字符放入字符串。
		string res;
		while (st2.empty() != true) {
			res.push_back(st2.top());
			st2.pop();
		}
		return res;
	}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值