单周赛 251 题解

最后一题真考验人的码力,这种大数据结构题,一定要想清楚思路之后再实现,实现完将大大加强你的自信!

知识点:数位和,贪心,全排列,海明距离,深度优先搜索,树序列化

字符串转化后的各位数字之和

给定一个字符串 \(s\),每一位都是一个小写字母

现在将小写字母替换成数字,即 a = 1, b = 2, ...,计算数位和得到新数字,做 \(k\) 轮,返回最终的答案

题解

模拟

class Solution {
public:
    int getSum(int x)
    {
        int ans = 0;
        while (x) {
            ans += x % 10;
            x /= 10;
        }
        return ans;
    }
    int getLucky(string s, int k) {
        int ans = 0;
        for (int i = 0; i < s.length(); ++i) {
            ans += getSum(s[i] - 'a' + 1);
        }
        k--;
        for (int i = 0; i < k; ++i) {
            ans = getSum(ans);
        }
        return ans;
    }
};

子字符串突变后可能得到的最大整数

给定一个字符串 \(s\),每一位都是一个 \(0-9\) 的数字

给定一个数组 \(w\),你可以将 \(s\) 中的数字 \(i\) 映射成 \(w[i]\)

现在可以对 \(s\) 的任意子字符串进行映射,返回可能的最大整数

题解

贪心,遍历数组

  • 如果当前数字映射之后比映射前大,那么完成映射
  • 如果比之前小
    • 如果还没有开始映射,继续遍历
    • 如果已经开始映射了,退出循环
class Solution {
public:
    string maximumNumber(string s, vector<int>& c) {
        int f = 0;
        for (int i = 0; i < s.length(); ++i) {
            if (c[s[i] - '0'] > s[i] - '0') s[i] = c[s[i] - '0'] + '0', f = 1;
            else if (f && c[s[i] - '0'] < s[i] - '0') break;
        }
        return s;
    }
};

最大兼容性评分和

给定 \(n\) 个题的试卷,每道题只有 \(0,\ 1\) 即对错两个答案

给定 \(m\) 个学生和 \(m\) 个老师,每个学生和老师都有自己的 \(n\) 个答案

现在每个学生分配给一个老师,把老师和学生相同答案的数量记为兼容值,要求计算一种方案,使得 \(m\) 对老师学生的兼容值的和最大,返回最大的兼容值的和

数据规定

\(1\leq n,\ m\leq 8\)

题解

数据非常小,考虑全排列枚举分配关系,使用 c++next_permutation

考虑计算兼容值,可以把答案看作二进制数,利用异或计算海明距离,再使用 __builtin_popcount 可以求出不同值的个数,用 \(n\) 减去这个值即可

时间复杂度 \(O(m!\cdot mn)\)

当然也可以预处理老师学生的兼容值,时间复杂度 \(O(m!\cdot m + m^2n)\)

class Solution {
public:
    int maxCompatibilitySum(vector< vector< int > > &S, vector< vector< int > > &M)
    {
        int n = S[0].size();
        int m = S.size();
        int ans = 0;
        vector< int > per(m), a(m), b(m);
        for (int i = 0; i < m; ++i) {
            int sum1 = 0, sum2 = 0;
            for (int j = 0; j < n; ++j) {
                sum1 *= 2, sum1 += S[i][j];
                sum2 *= 2, sum2 += M[i][j];
            }
            a[i] = sum1, b[i] = sum2;
        }
        for (int i = 0; i < m; ++i) per[i] = i;
        do {
            int temp = 0;
            for (int i = 0; i < m; ++i) {
                temp += n - __builtin_popcount(a[i] ^ b[per[i]]);
            }
            ans = max(ans, temp);
        } while (next_permutation(per.begin(), per.end()));
        return ans;
    }
};

删除系统中的重复文件夹

给定一个二维字符串数组,用来表示文件系统,每个字符串数组构成绝对路径

例如 [["a"],["c"],["d"],["a","b"],["c","b"],["d","a"]] 表示 a, c, d, a/b, c/b, d/a\(6\) 个文件

对于 非空且拥有相同结构和相同文件夹的文件目录,我们要进行删除操作

在上例中 a/b, c/b 是同样的结构,因此我们要删除这两个文件夹

现在希望用一个二维字符串数组,仿照上面的路径表示法返回删除后的结果

设共有 \(n\) 个绝对路径,每个绝对路径的长度为 \(m_{i}\),每个文件名长度为 \(l_{ij}\)

数据规定

\(1\leq n\leq 2\cdot 10^4\)

\(1\leq m_{i}\leq 500\)

\(1\leq l\leq 10\)

\(1\leq \sum l_{ij}\leq2\cdot 10^5\)

题解

用一棵多叉树来维护文件系统,每个节点存储文件名和子树信息

使用括号序列化将树结构映射成字符串,遍历树,得到所有子树的括号序列化值,并用哈希表维护序列值出现的次数

再次遍历子树,只保存序列值出现一次的子树即可

具体来讲,括号序列化是一种将树结构映射成字符串的方法

     r
 /   |   \
 a   b   c

上述树结构可以用 (r(a)(b)(c)) 来表示,获取括号序列化值通过一次 dfs 就能完成

建树的过程可以考虑动态开点,绝对路径即为树上一条链,只要判断当前节点是否已经被父亲节点存储即可

// cpp
#define pb push_back
struct node {
    string val, h;
    unordered_map< string, node * > son;  // 子树
    node() {}
    node(string _val): 
        val(_val) {}
};
class Solution {
public:
    unordered_map< string, int > mp;
    void debug(vector<vector<string>> &ans)
    {
        for (auto &i : ans) {
            cout << "[";
            for (auto &j : i) {
                cout << j << " ";
            }
            cout << "]" << endl;
        }
    }
    string dfs1(node *cur) // 第一次 dfs 获取子树的括号序列表达式
    {
        string temp;
        for (auto &i : cur->son) temp += dfs1(i.second);
        if (temp != "") mp[cur->h = temp]++; // 叶子结点不存在子节点,所以不存储
        return "(" + cur->val + temp + ")"; // 将括号序列表达式返回给父节点
    }
    void dfs2(node *cur, vector< vector< string > > &ans, vector< string > &pre) // 第二次 dfs 维护路径
    {
        if (mp[cur->h] > 1) return; // 子树的括号序列值出现了多次
        if (pre.size()) ans.pb(pre);
        for (auto &i : cur->son) {
            pre.pb(i.first); // 添加当前目录
            dfs2(i.second, ans, pre);
            pre.pop_back(); // 删除当前目录
        }
    }
    vector< vector< string > > deleteDuplicateFolder(vector< vector< string > > &p)
    {
        node *root = new node("/");
        for (auto &i : p) {
            node *cur = root;
            for (auto &j : i) {
                if (!cur->son[j]) cur->son[j] = new node(j);
                cur = cur->son[j];
            }
        }
        dfs1(root);
        vector< vector< string > > ans;
        vector< string > pre;
        dfs2(root, ans, pre);
        return ans;
    }
};
// go
package main

import "fmt"

type node struct {
    val string
    h   string
    son map[string]*node
}

func newNode(_val string) *node {
    return &node{
        val: _val,
        son: make(map[string]*node),
    }
}

func deleteDuplicateFolder(p [][]string) (ans [][]string) {

    root := newNode("/")
    for _, i := range p {
        cur := root
        for _, j := range i {
            if _, ok := cur.son[j]; !ok {
                cur.son[j] = newNode(j)
            }
            cur = cur.son[j]
        }
    }

    var mp = make(map[string]int)
    var dfs1 func(cur *node) string
    dfs1 = func(cur *node) string {
        var temp string
        for _, v := range cur.son {
            temp += dfs1(v)
        }
        if temp != "" {
            cur.h = temp
            mp[cur.h] += 1
        }
        return "(" + cur.val + temp + ")"
    }
    dfs1(root)

    pre := []string{}
    var dfs2 func(cur *node)
    dfs2 = func(cur *node) {
        if mp[cur.h] > 1 {
            return
        }
        if len(pre) > 0 {
            ans = append(ans, append([]string(nil), pre...))
        }
        for k, v := range cur.son {
            pre = append(pre, k)
            dfs2(v)
            pre = pre[:len(pre)-1]
        }
    }
    dfs2(root)
    return ans
}
posted @ 2021-08-04 21:27  徐摆渡  阅读(31)  评论(0编辑  收藏  举报