0
点赞
收藏
分享

微信扫一扫

LeetCode 力扣周赛 281

狗啃月亮_Rachel 2022-02-20 阅读 50

6012. 统计各位数字之和为偶数的整数个数

思路 预处理

时间复杂度 查询 O ( 1 ) \mathrel{O}(1) O(1),预处理 O ( n ) \mathrel{O}(n) O(n)

空间复杂度 O ( n ) \mathrel{O}(n) O(n)

在查询之前预处理所有数字的答案,这样查询时即可 O ( 1 ) \mathrel{O}(1) O(1) 的得到答案。

借助 std::call_once 可使得预处理逻辑仅执行一次。

// cnt 存储答案
int cnt[1001] = {0};
// 预处理函数
void init() {
    for (int i = 1; i <= 1000; i++) {
        int sum = 0;
        for (int j = i; j; j /= 10) {
            sum += j % 10;
        }
        cnt[i] = cnt[i-1] + ((sum%2) ? 0 : 1);
    }
}
// std::call_once 函数所需的标记变量
std::once_flag flag;

class Solution {
public:
    int countEven(int num) {
        // 尝试调用预处理函数
        std::call_once(flag, init);
        // 返回答案
        return cnt[num];
    }
};

6013. 合并零之间的节点

思路 链表的基操啦

时间复杂度 O ( n ) \mathrel{O}(n) O(n)

空间复杂度 O ( 1 ) \mathrel{O}(1) O(1)

我觉得这道题主要在考察链表的遍历和删除。

设有 ListNode *cur 初始时指向头节点。使用 cur 从头至尾遍历链表。

设有 ListNode *zero 指向已遍历的节点中最靠后的零节点。初始时指向头节点。

按照上述变量的定义,在遍历过程中:

  • 合并逻辑变为: zero->val += cur->val
  • 删除逻辑变为:每当 cur 指向一个零节点,则将 zero->next = cur; zero = cur。即丢掉上一个零节点当前零节点之间的节点,并更新zero

不难发现,遍历结束后,最后一个节点的值仍为零,需删除。

设有 ListNode *truncate 指向已遍历的节点中的倒数第二个零节点。初始时指向头结点。当遍历结束后,执行 tuncate->next = nullptr 即可解决上述问题。

/**
 * 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:
    ListNode* mergeNodes(ListNode* head) {
        ListNode *truncate = head;
        for (ListNode *zero = head, *cur = head->next; cur != nullptr;cur = cur->next) {
            if (cur->val == 0) {
                // zero 要跳了。
                // 在跳之前,更新 truncate。
                // 这样 truncate 就指向了倒数第二个零节点。
                truncate = zero;
                
                // 删除节点
                zero->next = cur;
                // zero 跳到 cur
                zero = cur;
            } else {
                // 合并
                zero->val += cur->val;
            }
        }
        // 丢弃最后一个零节点
        truncate->next = nullptr;
        return head;
    }
};

6014. 构造限制重复的字符串

思路 贪心

时间复杂度 O ( n ∗ c ) \mathrel{O}(n*c) O(nc) n n n 为字符串长度, c c c 为字符种类数。

空间复杂度 O ( n + c ) \mathrel{O}(n+c) O(n+c)

设有字符串 a n w anw anw 存储答案,初始为空。

因为要是字典序最大,所以总是选择尽可能大的字符追加至 a n w anw anw 尾部。

一次选择字符的过程如下:

  • 如果, s s s 中的最大字符与 a n w anw anw 尾部字符不同,或添加后不会超出重复限制,则将该字符从 s s s 删除,并追加至 a n w anw anw
  • 否则,将一个次大字符从 s s s 中删除,并追加至 a n w anw anw

最多重复 n n n 次挑选过程,即可构造出最大的 a n w anw anw

class Solution {
public:
    string repeatLimitedString(string s, int repeatLimit) {
        // 统计每种字符出现的次数
        int cnt[26] = {0};
        for (auto c : s) {
            cnt[c-'a']++;
        }
        
        // anw 存储答案
        string anw;
        // rep anw尾部连续重复字符的个数
        int rep = 0; 
        // pre anw 尾部字符的编号,方便处理anw为空的情形。
        char pre = -1;
        // n 次挑选过程
        for (int op = 0; op < s.size(); op++) {
            // 从大的开始挑选
            for (int i = 25; i >= 0; i--) {
                // 检查第 i 种字符是否符合条件
                if (cnt[i] <= 0) continue;
                if (i == pre && rep >= repeatLimit) continue;
                
                // 更新数据
                anw += char(i+'a');
                if (i == pre) rep++;
                else rep = 1;
                pre = i;
                cnt[i]--;
                
                // 一次挑选过程只能挑一个字符
                break;
            }
        }
        return anw;
    }
};

6015. 统计可以被 K 整除的下标对数目

思路 最大公约数

时间复杂度 O ( n V ) \mathrel{O}(n\sqrt V) O(nV )

空间复杂度 O ( V ) \mathrel{O}(V) O(V)

如果三个正整数 x x x y y y k k k 满足 x ∗ y x*y xy k k k 整除。

k k k 的所有质因子必然包含在 x ∗ y x*y xy 的质因子集合中。比如,

  • k = 60 = 2 ∗ 2 ∗ 3 ∗ 5 k = 60 = 2*2*3*5 k=60=2235
  • x = 6 = 2 ∗ 3 x = 6 = 2*3 x=6=23
  • y = 10 = 2 ∗ 5 y = 10 = 2*5 y=10=25

显然 6 ∗ 10 6*10 610 能被 60 60 60 整除。

根据上述结论,我们可枚举 n u m s i nums_i numsi,找出那些不在 n u m s i nums_i numsi 中的 k k k 的质因子,设这些质因子的乘积为 p i p_i pi,则有
p i = k g c d ( n u m s i , k ) p_i = \frac{k}{gcd(nums_i, k)} pi=gcd(numsi,k)k

然后,统计出 n u m s [ 0.. i − 1 ] nums[0..i-1] nums[0..i1] 中有多少个数字被 p i p_i pi 整除,该数量记为 c i c_i ci。则答案为:
a n w = ∑ i = 0 n − 1 c i anw = \sum_{i=0}^{n-1}{c_i} anw=i=0n1ci

可以配合注释一起理解~

class Solution {
public:
    // cnt[i] 表示 nums 中能被 i 整除的数字的个数
    int cnt[100001] = {0};
    
    long long coutPairs(vector<int>& nums, int k) {
        // anw 记录答案
        int64_t anw = 0;
        // 开始枚举数字 nums[i]
        for(int i = 0; i < nums.size(); i++) {
            // 计算 p
            int p = k / __gcd(nums[i], k);
            // 累加上 nums[0..i-1] 区间内,能被 p 整除的数字的数量。
            anw += cnt[p];
            
            // 更新 cnt。将 nums[i] 统计进去,注意 nums[i] 能被多个数整除。
            for (int j = 1, s = sqrt(nums[i]); j <= s; j++) {
                if (nums[i]%j == 0) {
                    // j 能整除 nums[i],则 nums[i]/j 也能整除 nums[i]。
                    // 但须注意 j == nums[i]/j 的情形,避免重复统计。
                    cnt[j]++;
                    if (nums[i]/j != j) {
                        cnt[nums[i]/j]++;
                    }
                } 
            }
        }
        return anw;
    }
};
举报

相关推荐

0 条评论