[周赛传送门]leetcode-cn.com/contest/wee…
2016. 增量元素之间的最大差值
思路:暴力枚举
时间复杂度:
先说最直白的方法,枚举所有下标对 ,判断 与 的大小关系并更新答案。
class Solution {
public:
int maximumDifference(vector<int>& nums) {
int anw = -1; // 答案初始化为 -1
for (int i = 0; i < nums.size(); i++) {
for (int j = i+1; j < nums.size(); j++) {
if (nums[i] < nums[j]) {
// 找到一对符合要求的下标,尝试更新答案
anw = max(anw, nums[j] - nums[i]);
}
}
}
return anw;
}
};
思路:预处理最小值
时间复杂度:
设有长度为 的一维数组 , 表示在前 个元素中的最小值。那么对于每个 ,所能贡献的最大差值为 :
- 当 时,为
- 反之为 -1。
整体实现上,可以先 的预处理 。然后再 的求出每个 的最大差值,其中最大的即为答案。
class Solution {
public:
int maximumDifference(vector<int>& nums) {
int anw = -1;
// 因为只关心 opt[i-1],所以 opt 可化简为一个变量。
for (int i = 1, opt = nums[0]; i < nums.size(); i++) {
if(opt < nums[i]) {
anw = max(anw, nums[i] - opt);
}
opt = min(opt, nums[i]);
}
return anw;
}
};
2017. 网格游戏
思路:贪心
时间复杂度:
不难发现,先手必定走过 个格子。先手有 种走法,选择一个 位置 ,,先手会走过第一排的 ,以及第二排的 。
后手有以下两种方案:
- ,得分为第一排的 格子的和。
- ,得分为第二排的 格子的和。
不难得出先手的策略,选择一个 ,使得第一排 和 第二排 中的最大值最小。
class Solution {
public:
long long gridGame(vector<vector<int>>& grid) {
int64_t sum[2] = {0};
int n = grid[0].size();
for (int i = 0; i < n; i++) {
sum[0] += grid[0][i]; // 计算第一排的累加和
sum[1] += grid[1][i]; // 计算第二排的累加和
}
int64_t pre[100000] = {grid[0][0]};
for (int i = 1; i < n; i++) {
pre[i] = pre[i-1] + grid[0][i]; // 计算第一排的前缀和
}
// anw 为最终答案,初始化为一个极大值。
int64_t anw = sum[0] + sum[1];
// suf 为第二排的后缀和,简化为了一个变量
int suf = 0;
for (int i = n-1; i >= 0; i--) {
suf += grid[1][i];// 计算后缀和
// opt: 先手选择 p = i 时,后续的最优解.
int opt = max(sum[0] - pre[i], sum[1] - suf);
// anw 保存最小的「后手最优解」。
anw = min(anw, opt);
}
return anw;
}
};
2018. 判断单词是否能放入填字游戏内
思路 暴力枚举
时间复杂度:
枚举坐标 ,检查四个方向:
是否存在一个方向能放下 ,其中 为 的长度。
另外,还需检查对应的边界是否符合要求。
整体上的时间复杂度为 ,考虑到 的取值不超过 ,因此时间复杂度可简化为 。
class Solution {
public:
bool check(int x, int y, const vector<vector<char>> &board, int n, int m) {
// (x,y) 在棋盘内部且为 #
if (0 <= x && x < n && 0 <= y && y < m && board[x][y] == '#') {
return true;
}
// 位于边界外层
if (-1 == x || x == n || -1 == y || y == m) {
return true;
}
return false;
}
bool placeWordInCrossword(vector<vector<char>>& board, string word) {
int n = board.size(); // 行数
int m = board[0].size(); // 列数
int dx[4] = {-1, 1, 0, 0}; // 定义方向数组
int dy[4] = {0 , 0, 1,-1};
// 两层for循环枚举起始点 (i,j)
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 枚举方向
for (int d = 0; d < 4; d++) {
int sx = i - dx[d];
int sy = j - dy[d];
// 检查端点 (sx,sy);
if (!check(sx, sy, board, n, m)) {
continue;
}
int ex = i + (word.size())*dx[d];
int ey = j + (word.size())*dy[d];
// 检查端点 (ex,ey);
if (!check(ex, ey, board, n, m)) {
continue;
}
// 两端点满足要求,继续检查其他位置。
bool flag = true;
for (int k = 0, x = i, y = j; k < word.size() && flag; k++, x += dx[d], y += dy[d]) {
if (!(board[x][y] == ' ' || board[x][y] == word[k])) {
flag = false;
}
}
if (flag) { return flag; }
}
}
}
return false;
}
};
2019. 解出数学表达式的学生分数
思路:动态规划
时间复杂度:。 为 字符串的长度。 为取值上限,本题为 1000。 为提交的答案数。
计算正确值的思路比较简单,可以借助栈来实现先乘后加。
int getCorrect(const std::string &s) {
stack<int> st;
// 把第一个数字先放进去。
st.push(s[0]-'0');
// 枚举运算符号
for (int i = 1; i < s.size(); i += 2) {
if (s[i] == '*') {
// 是乘号,下一数字与栈顶元素相乘
st.top() *= s[i+1]-'0';
} else {
// 是加号,下一数字放入栈顶
st.push(s[i+1]-'0');
}
}
int sum = 0;
// 栈中的数字需要求累加和。
while (!st.empty()) {
sum += st.top();
st.pop();
}
return sum;
}
乱序执行的结果,可用动态规划求解。设有二维数组 。 是一个集合,表示表达式 乱序执行结果的集合。
通过枚举运算符将表达式一分为二,比如有 ,以及运算符 , 则将表达式分为 以及 。
不难想到状态转移方程:
- 当 时:
- 当 时:
因此,可在 时间复杂度内求出所有的 。 为 字符串的长度。 为取值上限,本题为 1000。
最后,枚举提交的答案,求解总分数即可。
class Solution {
public:
int getCorrect(const std::string &s) {
stack<int> st;
// 把第一个数字先放进去。
st.push(s[0]-'0');
// 枚举运算符号
for (int i = 1; i < s.size(); i += 2) {
if (s[i] == '*') {
// 是乘号,下一数字与栈顶元素相乘
st.top() *= s[i+1]-'0';
} else {
// 是加号,下一数字放入栈顶
st.push(s[i+1]-'0');
}
}
int sum = 0;
// 栈中的数字需要求累加和。
while (!st.empty()) {
sum += st.top();
st.pop();
}
return sum;
}
// dp[l][r] 表示子表达式 s[l:r] 可能出现的值的集合
unordered_set<int> dp[32][32];
void getPossible(const std::string &s, int l, int r) {
if (dp[l][r].size()) {
// 已经计算过了,没必要重复计算了。直接返回
return;
}
if (l == r) {
// 长度为 1 的子表达式,必然包含一个数字。
dp[l][r].insert(s[l]-'0');
return;
}
// 枚举运算符号,分割为两个子表达式。
for (int i = l+1; i < r; i += 2) {
getPossible(s, l, i-1); // 计算子表达式 s[l,i-1]
getPossible(s, i+1, r); // 计算子表达式 s[i+1,r]
// 由两个子表达式的集合计算得出 s[l,r] 的值。
for (auto vl : dp[l][i-1]) {
for (auto vr : dp[i+1][r]) {
auto v = ((s[i] == '+') ? (vl + vr) : (vl * vr));
if (v <= 1000) {
dp[l][r].insert(v);
}
}
}
}
}
int scoreOfStudents(string s, vector<int>& answers) {
// 先计算正确值
int correct = getCorrect(s);
// 计算优先级出错可能得出的值
int n = s.size()-1;
getPossible(s, 0, n);
// 累加总分
int anw = 0;
for (auto a : answers) {
if (a == correct) {
anw +=5;
} else if(dp[0][n].count(a) == 1) {
anw += 2;
}
}
return anw;
}
};