LeetCode 力扣周赛 260

116 阅读2分钟

[周赛传送门]leetcode-cn.com/contest/wee…

2016. 增量元素之间的最大差值

思路:暴力枚举

时间复杂度O(n2)O(n^2)

先说最直白的方法,枚举所有下标对 (i,j)(i,j),判断 numsinums_inumsjnums_j 的大小关系并更新答案。

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;
    }
};

思路:预处理最小值

时间复杂度O(n)O(n)

设有长度为 nn 的一维数组 optoptoptiopt_i 表示在前 ii 个元素中的最小值。那么对于每个 numsii1nums_i,i \ge 1,所能贡献的最大差值为 :

  • opti1numsiopt_{i-1} \le nums_i 时,为 numsiopti1nums_i - opt_{i-1}
  • 反之为 -1。

整体实现上,可以先 O(n)O(n) 的预处理 optopt。然后再 O(n)O(n) 的求出每个 numsinums_i 的最大差值,其中最大的即为答案。

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. 网格游戏

思路:贪心

时间复杂度O(n)O(n)

不难发现,先手必定走过 n+1n+1 个格子。先手有 nn 种走法,选择一个 位置 ppp[0,n)p\in[0,n),先手会走过第一排的 [0p][0,p] ,以及第二排的 [p,n)[p, n)

后手有以下两种方案:

  • (0,0)(0,n1)(1,n1)(0,0)→(0,n-1)→(1,n-1),得分为第一排的 (p,n)(p,n) 格子的和。
  • (0,0)(1,n1)(1,n1)(0,0)→(1,n-1)→(1,n-1),得分为第二排的 [0,p)[0,p) 格子的和。

不难得出先手的策略,选择一个 pp,使得第一排 (p,n)(p,n) 和 第二排 [0,p)[0,p) 中的最大值最小。

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. 判断单词是否能放入填字游戏内

思路 暴力枚举

时间复杂度O((nm)3/2)O((nm)^{3/2})

枚举坐标 (i,j)(i,j),检查四个方向:

  • (i,j)(i+k1,j)(i,j) → (i+k-1,j)
  • (i,j)(ik+1,j)(i,j) → (i-k+1,j)
  • (i,j)(i,j+k1)(i,j) → (i,j+k-1)
  • (i,j)(i,jk+1)(i,j) → (i,j-k+1)

是否存在一个方向能放下 wordswords,其中 kkwordswords 的长度。

另外,还需检查对应的边界是否符合要求。

整体上的时间复杂度为 O(nmk)O(nmk),考虑到 kk的取值不超过 (n,m)\sqrt{(n,m)} ,因此时间复杂度可简化为 O((nm)3/2)O((nm)^{3/2})

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. 解出数学表达式的学生分数

思路:动态规划

时间复杂度O(n2m2+k)O(n^2*m^2 + k)nn 为 字符串的长度。mm 为取值上限,本题为 1000。kk 为提交的答案数。

计算正确值的思路比较简单,可以借助栈来实现先乘后加。

    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;
    }

乱序执行的结果,可用动态规划求解。设有二维数组 dpdpdpl,rdp_{l,r} 是一个集合,表示表达式 sl,rs_{l,r} 乱序执行结果的集合。

通过枚举运算符将表达式一分为二,比如有 sl,rs_{l,r},以及运算符 sps_p, 则将表达式分为 sl,p1s_{l,p-1} 以及 sp+1,rs_{p+1,r}

不难想到状态转移方程:

  • l=rl = r 时: dpl,r=sldp_{l,r} = s_l
  • lrl\ne r时: dpl,r=xy,xdpl,p1,ydpp+1,rdp_{l,r} = x · y, x\in dp_{l,p-1}, y\in dp_{p+1,r}

因此,可在 O(n2m2)O(n^2*m^2) 时间复杂度内求出所有的 dpl,rdp_{l,r}nn 为 字符串的长度。mm 为取值上限,本题为 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;
    }
};