leetcode 周赛第418场

Leetcode 周赛第418场

为什么记录周赛内容,主要本人比较懒惰,想通过写博客的方式激励自己,这是参加的第一场周赛,希望下面每场周赛都加油都参加。

题目一

原题链接,题目的大概意思是要求出拼接的三个数,使其二进制的表示最大。
本题的核心思想是贪心, 将数字按照如下的排序顺序从小到大排序(下面的代码解释使用c++, 完整的过程有c++,python两个不同的版本)

public static bool cmp(int a, int b) {
	int pa = (a << (__lg(b) + 1) | b;
	int pb = (b << (__lg(a) + 1) | a;
	return pa > pb;
	}

对于贪心的证明:
我们可以采用交换法,如果我们将上述贪心思路得到的顺序序列中的相邻下标为 i , j ∈ 0 , 1 , 2 i, j\in{0, 1, 2} ij0,1,2进行交换,那么此时在相同长度下得到的是 p j pj pj 但是我们贪心思路下得到的是 p i pi pi,因此贪心成立,即贪心得到的是最优解。

// c++的解法
class Solution {
public:
    static bool cmp(int a, int b) {
        int pa = (a << (__lg(b) + 1)) | b;
        int pb = (b << (__lg(a) + 1)) | a;
        return pa > pb;
    }
    int maxGoodNumber(vector<int>& nums) {
        sort(nums.begin(), nums.end(), cmp);
        cout << (1 << (__lg(2) + 1) | 2) << " " << (2 << (__lg(1) + 1) | 1) << endl;  
        int ans = 0;
        
        for (auto& num: nums) {
            cout << num << endl;
            ans = ans << (__lg(num) + 1) | num;
        }
        return ans;
    }
};
// python 的解法
class Solution:
    def maxGoodNumber(self, nums: List[int]) -> int:
        def cmp(a, b):
            pa = (a << b.bit_length()) | b
            pb = (b << a.bit_length()) | a
            return pb - pa
        nums.sort(key=cmp_to_key(cmp))
        print(nums)
        ans = 0
        for num in nums:
            ans = ans << num.bit_length() | num
        return an

注意Python中的比较函数返回值是int, 如果返回的正值,那么a排在b的后面,如果返回的负值,a排在b的前面。

总结:在python中我们使用 s . b i t _ l e n g t h ( ) s.bit\_length() s.bit_length()快速得到一个数字的二进制表示长度,在c++中我们使用 _ _ l g ( s ) + 1 \_\_lg(s) + 1 __lg(s)+1来快速得到一个数字二进制表示长度

题目二

原题链接
题目描述

这道题目核心性质就是使用dfs/bfs/并查集,将所有的可疑方法全部标记出来,然后进行是否移除的判断,也就是是否存在非可疑点调用了可疑点(核心)

// c++
// 存边
class Solution {
public:
    vector<int> remainingMethods(int n, int k, vector<vector<int>>& invocations) {
        vector<vector<int>> edges(n); // 存边
        // 将可疑函数全部记载下来
        vector<bool> isDoubt(n, false);
        for (auto &v: invocations) {
            // caller 调用callee
            edges[v[0]].emplace_back(v[1]);
        }


        isDoubt[k] = true;
        // 使用dfs遍历可疑点调用的所有可疑方法
        auto dfs = [&] (auto&& dfs, int root) -> void {
            for (auto &t: edges[root]) {
                if (!isDoubt[t]) {
                    isDoubt[t] = true;
                    dfs(dfs, t);
                }
            }
        };
        
        dfs(dfs, k); // 标记所有的可疑点
        
        // 如果存在非可疑方法调用了可疑方法
        for (auto& e: invocations) {
            if (!isDoubt[e[0]] && isDoubt[e[1]]) {
                vector<int> ans(n);
                iota(ans.begin(), ans.end(), 0);
                return ans;
            }
        }

        // 移除所有可疑方法
        vector<int> ans;
        for (int i = 0; i < n; i++) {
            if (!isDoubt[i]) {
                ans.emplace_back(i);
            }
        }
    return ans;
    }
};

上述写法可以学到两种东西,第一个是lambda表达式如何写递归,这有点像cs61A中的一个困难的练习,还有一个就是如何快速以递增的方式填充一个容器std::iota(vec.begin(), vec.end(), 0);

# python
class Solution:
    def remainingMethods(self, n: int, k: int, invocations: List[List[int]]) -> List[int]:
        edges = [[] for _ in range(n)]   # 存边
        for x, y in invocations:
            edges[x].append(y)
        # 收集所有的可以方法
        DoubtSet = set()

        def dfs(root):
            DoubtSet.add(root)
            for t in edges[root]:
                if t not in DoubtSet:
                    dfs(t)

        dfs(k)   # 找出所有的可疑点
        # 如果非可疑点调用了可以可疑点
        for x, y in invocations:
            if x not in DoubtSet and y in DoubtSet:
                return list(range(n))
        return list(set(range(n)) - DoubtSet)

题目三

原题链接
题目描述
这道题目的核心性质就是我们要能够快速找出第一行应该放置什么样的元素,以及我们应该针对第一行可以放置怎么样的元素做考虑,下面说的点数是指相邻的点的个数。

如果有元素的点数为1,那么证明最多只能放置一行,如果没有元素的点数为4,那么证明最多放置2行,其余情况可以放置多行,至于为什么 要考虑放置2行的情况,是因为我们考虑放置第一行时(如果不是最多可以放置一行的时候),我们采取2-3-3-…-3-2的形式放置数字,那么2行会导致我们枚举的第一行变成蛇形,无法构成一行,因此在两行的时候我们需要第一行放置成2-2型,因此我们可以知道第一行的唯一表示形式就是
对于有点数为1的情况 ( 1 , 2 , 2 ⋯   , 2 ) (1, 2, 2\cdots, 2) (1,2,2,2)
对于没有点数为4的情况 ( 2 , 2 ) (2,2) (2,2)
其他情况 ( 2 , 3 , ⋯   , 3 , 2 ) (2, 3, \cdots, 3, 2) (2,3,,3,2)

// c++实现
class Solution {
public:
    vector<vector<int>> constructGridLayout(int n, vector<vector<int>>& edges) {
        // 建立图形
        vector<vector<int>> g(n);
        for (auto& v: edges) {
            g[v[0]].push_back(v[1]);
            g[v[1]].push_back(v[0]);
        }

        // 每个度数对应一个点, 最多有四个点
        vector<int> degree_to_node(5, -1);
        for (int i = 0; i < n; i++) {
            degree_to_node[g[i].size()] = i; // 如果0有三个邻居的话,度数为三就对应0
        }

        // 分情况讨论
        vector<int> row;
        if (degree_to_node[1] != - 1) {
            // 存在度数为1的点
            row.emplace_back(degree_to_node[1]);
        } else if (degree_to_node[4] == -1) {
            // 只有两列
            row.emplace_back(degree_to_node[2]);
            // 寻找下一个度数为2的点
            for (int t: g[degree_to_node[2]]) {
                if (g[t].size() == 2) {
                    row.emplace_back(t);
                    break;
                }
            }
        } else {
            // 证明存在多列
            // 先将其中一个2的端点加入
            int x = degree_to_node[2];
            row.emplace_back(x);
            int pre = x;
            x = g[x][0];

            while (g[x].size() > 2) {
                row.emplace_back(x);
                for (int t: g[x]) {
                    if (t != pre && g[t].size() < 4) {
                        pre = x;
                        x = t;
                        break;
                    }
                }
            }
            // 将最后一个2的端点加入
            row.emplace_back(x);
        }

        // 利用第一列构造整个答案
        vector<vector<int>> ans(n / row.size());
        ans[0] = move(row);
        vector<bool> isVisited(n, false);

        for (int i = 0; i < ans[0].size(); i++) isVisited[ans[0][i]] = true;

        for (int i = 1; i < ans.size(); i++) {
            for (int x: ans[i - 1]) {
                cout << x << endl;
                for (int t: g[x]) {
                    if (!isVisited[t]) {
                        isVisited[t] = true;
                        ans[i].push_back(t);
                        break;
                    }
                }
            }
        }
        return ans;
    }
};
# python 实现
class Solution:
    def constructGridLayout(self, n: int, edges: List[List[int]]) -> List[List[int]]:
        # 建图
        g = [[] for _ in range(n)]
        for x, y in edges:
            g[x].append(y)
            g[y].append(x)
        # 将每个度数与一个点对应
        degree_to_node = [-1] * 5
        for i, e in enumerate(g):
            degree_to_node[len(e)] = i  # 例如如果0的度数为3,那么degree_to_node[3] = 0
        # 分情况讨论 找出第一列应该放什么
        row = []
        if degree_to_node[1] != -1:
            row.append(degree_to_node[1])
        elif degree_to_node[4] == -1:
            row.append(degree_to_node[2])   # 先放第一个度数为2的点
            for i in g[degree_to_node[2]]:
                if len(g[i]) == 2:
                    row.append(i)
                    break   # 将第二个度数为2的点放入
        else:
            # 2-3-3-3-2
            x = degree_to_node[2]
            row.append(x)   # 放入第一个度数为2的点
            pre = x
            x = g[x][0]     # 此时x的度数为3
            while len(g[x]) > 2:
                row.append(x)
                for t in g[x]:
                    if pre != t and len(g[t]) < 4:
                        # row.append(t)
                        pre = x
                        x = t
                        break
            row.append(x)
        print(row)
        isVisited = [False for i in range(n)]
        ans = [[] for i in range(n // len(row))]
        ans[0] = row
        for num in ans[0]:
            isVisited[num] = True
        # 循环枚举下一行应该防什么
        for i in range(1, len(ans)):
            for x in ans[i - 1]:
                for y in g[x]:
                    if not isVisited[y]:
                        ans[i].append(y)
                        isVisited[y] = True
                        break
        return ans

注意这里的细节就是枚举完一个点后就要break,不然会出现很多重复的点,导致每一行都加了很多点,最后爆空间(T_T)

题目四

原题链接
题目描述

本题的核心是要快速统计出每一个gcd = i的数对个数,注意到如下的数学性质

数学性质:记 c = 所有 i 的倍数 − g c d [ 2 ∗ i ] − g c d [ 3 ∗ i ] − ⋯ − g c d [ n ∗ i ] c=所有i的倍数-gcd[2*i] - gcd[3*i] -\cdots - gcd[n *i] c=所有i的倍数gcd[2i]gcd[3i]gcd[ni],其中n*i < 数组中最大的数
那么gcd[i] = c ∗ ( c − 1 ) 2 \frac{c*(c -1)}{2} 2c(c1)

这样我们就可以在 O ( N l g ( N ) ) O(Nlg(N)) O(Nlg(N))的时间复杂度内求出gcd[i]的值


//c++
class Solution {
public:
    vector<int> gcdValues(vector<int>& nums, vector<long long>& queries) {
        int n = *max_element(nums.begin(), nums.end());
        vector<int> cnt(n + 1, 0);
        for (auto& num: nums) cnt[num] += 1;
        vector<long long> cnt_gcd(n + 1, 0);
        for (int i = n; i > 0; i--) {
            // 枚举每一个cnt_gcd[i] 的个数
            // cnt_gcd[i] = 所有i的倍数 - cnt_gcd[2i] - cnt_gcd[3i] - ...
            int c = 0;
            for (int j = i; j < n + 1; j += i) {
                c += cnt[j];
                cnt_gcd[i] -= cnt_gcd[j];
            }
            cnt_gcd[i] += (long long)c * (c - 1) / 2;
        }
        for (auto num:cnt_gcd) cout << num << endl;
        // for (auto num:cnt_gcd) cout << num << endl;
        // return vector<int>(n, 0);
        vector<long long> s(n + 1, 0);
        s[0] = cnt_gcd[0];
        for (int i = 1; i < n + 1; i++) s[i] = s[i - 1] + cnt_gcd[i];
        // for (auto& num: s) cout << num << endl;
        vector<int> ans;
        for(int i = 0; i < queries.size(); i ++) {
            ans.push_back(upper_bound(s.begin(), s.end(), queries[i]) - s.begin());
        }
        return ans;
    }
};
#python
class Solution:
    def gcdValues(self, nums: List[int], queries: List[int]) -> List[int]:
        mx = max(nums)  # 找出最大的数是多少
        cnt = [0] * (mx + 1)
        for x in nums:
            cnt[x] += 1
        cnt_gcd = [0] * (mx + 1)
        for i in range(mx, 0, -1):
            c = 0
            for j in range(i, mx + 1, i):
                c += cnt[j]
                cnt_gcd[i] -= cnt_gcd[j]
            cnt_gcd[i] += (c - 1) * c // 2
            print(i, cnt_gcd[i])
        s = list(accumulate(cnt_gcd))
        print(s)
        return [bisect_right(s, q) for q in queries]

注意python中bisect_left返回的是有序序列中第一个大于等于元素的位置,bisect_right返回的是有序序列中第一个大于该元素的位置,c++中upper_bound返回的是有序序列中第一个大于元素的位置,lower_bound返回的是有序序列中第一个大于等于元素的位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值