LeetCode 力扣周赛 265

206 阅读3分钟

周赛传送门

5914. 值相等的最小索引

思路:遍历

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

按题目要求,遍历所有元素,逐个进行检查即可。

class Solution {
public:
    int smallestEqual(vector<int>& nums) {
        for (int i = 0; i < nums.size(); i++) {
            if (i%10 == nums[i]) {
                return i;
            }
        }
        return -1;
    }
};

5915. 找出临界点之间的最小和最大距离

思路:遍历

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

最大距离一定是第一个临界点和最后一个临界点之间的距离。最小距离一定是某两个相邻临界点之前的距离。

因此,我们可以遍历一次链表,按上述思路找出最小和最大距离,详见注释~

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    vector<int> nodesBetweenCriticalPoints(ListNode* head) {
        // 辅助变量
        //  first: 第一个分界点在链表中的位置
        //  pre: 已遍历的节点中,位置最大的分界点在链表中的位置,-1 代表无分界点
        //  now: 当前节点的在链表中的位置
        int first = -1, pre = -1, now = 0;
        // 答案所需的两个距离
        int minDis = -1, maxDis = -1; 

        // p: 指向当前节点
        // last: 指向 p 的前一个节点
        for (ListNode *p = head, *last = nullptr; p != nullptr; last = p, p = p->next, now++) {
            // 标记变量: true → p是临界点,false → p不是临界点
            bool flag = false;
            if (last != nullptr && p->next != nullptr) {
                if (last->val < p->val && p->val > p->next->val) {
                    flag = true;
                } else if (last->val > p->val && p->val < p->next->val) {
                    flag = true;
                }
            }
            // 如果 p 是临界点,则进行更新
            if (flag) {
                // first 为 -1 说明是第一个临界点,无法更新 maxDis,近更新 first 即可。 
                // 反之,则更新一下 maxDis
                (first == -1) ? first = now : maxDis = now - first;
            
                if (pre != -1) {
                    // 同理,pre 不为 -1 时,尝试用相邻临界点的距离更新 minDis
                    (minDis == -1) ? minDis = now - pre : minDis = min(minDis, now-pre);
                }
                pre = now;
            }
        }
        return vector<int>{minDis, maxDis};
    }
};

5916. 转化数字的最小运算数

思路:广度优先遍历(BFS)

时间复杂度O(nv)O(n*v)vv 为可继续操作的取值范围。

标准的无权最短路的题目啦,借助 BFS 求解即可。

不过这题数据有点厉害,竟然卡常数啦。当 x[0,1000]x \notin [0, 1000] 时,不能入队,否则会因为多了一次出队和入队的时间导致超时。

class Solution {
public:
    int minimumOperations(vector<int>& nums, int start, int goal) {
        // BFS 用的队列以及标记数组
        queue<int64_t> q;
        unordered_map<int64_t, int> mark;
        
        // 初始化队列及标记数组
        q.push(start);
        mark[start] = 0;

        // 封装一下三种操作,便于简化后续代码。但是不开编译优化的话,性能掉的很厉害,比赛时慎用~
        std::vector<std::function<int64_t(int64_t, int64_t)>> ops{
            [] (int64_t a, int64_t b) {
                return int64_t(a + b);
            },
            [] (int64_t a, int64_t b) {
                return int64_t(a - b);
            },
            [] (int64_t a, int64_t b) {
                return int64_t(a ^ b);
            }
        };

        int cnt =  0;
        
        // 开始BFS
        while(!q.empty()) {
            auto f = q.front();
            q.pop();
            // 找到了目标值,返回操作次数
            if (f == goal) {
                return mark[f];
            }

            // 遍历 nums 进行操作
            for (auto num : nums) {
                for (const auto &op : ops) {
                    int next = op(f, num);
                    int cost = mark[f] + 1;
                    // 找到了目标值直接返回即可
                    if (next == goal) {
                        return cost;
                    }
                    // 判断是否有必要进队列。
                    // 注意这里应该卡常数了,
                    // next 不属于 [0, 1000] 时,如果入队会导致多了一次入队和出队的时间,导致超时了。
                    if (0 <= next && next <= 1000 && mark.find(next) == mark.end()) {
                        mark[next] = cost;
                        q.push(next);
                    }
                }
            }
        }
        // 未找到返回 -1 
        return -1;
    }
};

5917. 同源字符串检测

思路:记忆化搜索

时间复杂度O(n2d)O(n^2d)nn 为输入字符串长度。dd 为原始字符串长度,也可理解为匹配过程中,两字符串长度之差。

既然要用记忆化搜索,那么先定义状态。(i,j,k)(i,j,k) 表示将 S1S_1 的前 ii 个字符,以及 S2S_2 的前 jj 个字符成功匹配且两者的长度差为 dd。成功匹配可以这样理解:

S1S_1 的前 ii 个字符扩展为字符串 P1P_1',其长度为 L1L_1,将 S2S_2 的前 jj 个字符按扩展为字符串 P2P_2',其长度为 L2L_2。如果 P1P_1P2P_2 的前 min(L1,L2)min(L_1, L_2) 个字符相同,则称其为成功匹配。dd 也可表示为 L1L2L_1 - L_2

那么初始状态即为 (0,0,0)(0,0,0),即 S1S_1 的前 00 个字符,和 S2S_2 的前 00 个字符。因为两者都是空字符串,所以显然可成功匹配,且长度差为 d=0d = 0

有了初始状态,接着定义状态转移过程。

  • 转移一:(i,j,d)(i+l,j,d+x)(i,j,d)→(i\mathrel{+}l,j,d+x)

    当且仅当 S1[i:i+l1]S_1[i:i+l-1] 都是数字时可发生该转移,即将这 ll 个数字改为了 xx 个小写字母。

  • 转移二:(i,j,d)(i,j+l,dx)(i,j,d)→(i,j\mathrel{+}l,d-x)

    与「转移一」对称。当且仅当 S2[j:j+l1]S_2[j:j+l-1] 都是数字时可发生该转移,即将这 ll 个数字改为了 xx 个小写字母。

  • 转移三:(i,j,d)(i+1,j,d+1)(i,j,d)→(i+1,j,d+1)

    当且仅当 d<0d \lt 0S1[i]S_1[i] 是字母时可发生该转移,即 S1[i]S_1[i]S2S_2 中由数字得来的字母配对了。因为只移动了 ii 而未移动 jj,所以必然需要 d<0d\lt 0

  • 转移四:(i,j,d)(i,j+1,d1)(i,j,d)→(i,j+1,d-1)

    与「转移三」对称。与当且仅当 d>0d \gt 0S2[j]S_2[j] 是字母时可发生该转移,即 S2[j]S_2[j]S1S_1 中由数字得来的字母配对了。因为只移动了 jj 而未移动 ii,所以必然需要 d>0d\gt 0

  • 转移五:(i,j,d)(i+1,j+1,d)(i,j,d)→(i+1,j+1,d)

    当且仅当 d=0d = 0S1[i]S_1[i]S2[j]S_2[j] 均为字母且相等时,才能发生该转移。试想当 d0d\ne0 时配对 S1[i]S_1[i]S2[j]S_2[j],意味着 dd 个字符中有一个被确定了,这会导致后续过程中无法进行转移三和转移四了

如果能从(0,0,0)(0,0,0) 转移至 (S1,S2,0)(|S_1|,|S_2|,0),则说明两者同源,反之不同源。

class Solution {
public:
    unordered_set<uint64_t> mark;
    // 压缩一下,用一个uint64_t 表示状态 (i,j,k)
    inline uint64_t makeStatus(int i, int j, int k) {
        uint64_t status = i;
        status <<= 16;
        status |= j;
        status <<= 32;
        // 注意 k 可能为负数,unsigned 和 signed 的位运算会有符号位的问题,因此做一下偏移
        status |= (k + 10000);
        return status;
    }
    bool isDigit(char c) {
        return '0' <= c && c <= '9';
    }
    // 在转移一和转移二中,会用到此函数,即将 l 个数字转换为字符的数量。
    // 因为会有多种转换方案,因此返回一个vector<pair<int, int>>
    // pair<int, int> 的 first 即为转换后的字符的数量。second 即为 l,便于调用处进行状态转移
    std::vector<pair<int, int>> convertDigit(const std::string &s, int p) {
        int len = 0;
        for (int i = p; i < s.size() && isDigit(s[i]); i++, len++) {
        }
        if (len == 1) {
            return std::vector<pair<int, int>>{make_pair(s[p]-'0', len)};
        }
        if (len == 2) {
            return std::vector<pair<int, int>>{
                make_pair((s[p]-'0') + (s[p+1]-'0'), len),
                make_pair((s[p]-'0')*10 + (s[p+1]-'0'), len)
            };
        }
        return std::vector<pair<int, int>>{
            make_pair((s[p]-'0') + (s[p+1]-'0') + (s[p+2]-'0'), len),
            make_pair((s[p]-'0')*100 + (s[p+1]-'0')*10 + (s[p+2]-'0'), len),
            make_pair((s[p]-'0')*10 + (s[p+1]-'0') + (s[p+2]-'0'), len),
            make_pair((s[p]-'0')+ (s[p+1]-'0')*10 + (s[p+2]-'0'), len)
        };
    }

    bool dfs(const std::string &s1, const std::string &s2, int i, int j, int d) {
        // s1 的前 i 个字符,s2 的前 j 个字符匹配了,且长度差为 d。
        // 本次调用会处理 s1[i] 和 s2[j]

        // 已经检查过了,后续过程无法成功匹配,直接返回 false
        uint64_t status = makeStatus(i, j, d);
        if (mark.count(status)) {
            return false;
        }
        // 匹配到末尾了,检查 d 即可
        if (i == s1.size() && j == s2.size()) {
            return d == 0;
        }
        bool res = false;
        // 转移一
        if (i < s1.size() && isDigit(s1[i])) {
            for (const auto &info : convertDigit(s1, i)) {
                res = res || dfs(s1, s2, i + info.second, j, d + info.first);
            }
        }
        // 转移二
        if (j < s2.size() && isDigit(s2[j])) {
            for (const auto &info : convertDigit(s2, j)) {
                res = res || dfs(s1, s2, i, j + info.second, d - info.first);
            }
        }
        // 转移三
        if (d < 0) {
            if (i < s1.size() && !isDigit(s1[i])) {
                res = res || dfs(s1, s2, i+1, j, d+1);
            }
        }
        // 转移四
        if (d > 0) {
            if (j < s2.size() && !isDigit(s2[j])) {
                res = res || dfs(s1, s2, i, j+1, d-1);
            }
        }
        // 转移五
        if (d == 0) {
            if (i < s1.size() && j < s2.size() && s1[i] == s2[j] && !isDigit(s1[i])) {
                res = res || dfs(s1, s2, i+1, j+1, d);
            }
        }
        // 成功啦,直接返回
        if (res == true) {
            return true;
        }
        // 记录状态
        mark.insert(status);
        return false;
    }

    bool possiblyEquals(string s1, string s2) {
        // 从 (0,0,0) 开始转移
        return dfs(s1, s2, 0, 0, 0);
    }
};