Jekyll2026-03-17T00:43:43+00:00https://haruko386.github.io/feed.xmlHaruko386Haruko386[email protected]Cpp Template2022-10-26T00:00:00+00:002022-10-26T00:00:00+00:00https://haruko386.github.io/posts/2022/10/Moudle

前言: 所有模板仅仅为笔者自己在算竞中用到的模板 由于笔者竞赛分都很低,高级模板还请前往查看old_yan的算竞模板

数学

最大公约数

int gcd(int a, int b) {
    while (b ^= a ^= b ^= a %= b);
    return a;
}

最小公倍数

long long lcm(long long a, long long b) {
    return a * b / gcd(a, b);
}

快速幂

//快速幂
template<typename T>
T power(T a, ll b) {
    T res = 1;
    for (; b; b /= 2, a *= a)
        if (b % 2)
            res *= a;
    return res;
}

快速判断质数

template<typename typC>
bool isPrime(typC num) {
    if (num == 1 || num == 4)return 0;
    if (num == 2 || num == 3)return 1;
    if (num % 6 != 1 && num % 6 != 5)return 0;
    typC tmp = sqrt(num);
    for (int i = 5; i <= tmp; i += 6)
        if (num % i == 0 || num % (i + 2) == 0)return 0;
    return 1;
}

根据三点获取角度

double get_angle(double x1, double y1, double x2, double y2, double x3, double y3) {
    double theta = atan2(x1 - x3, y1 - y3) - atan2(x2 - x3, y2 - y3);
    if (theta > M_PI)
        theta -= 2 * M_PI;
    if (theta < -M_PI)
        theta += 2 * M_PI;

    theta = abs(theta * 180.0 / M_PI);
    return theta;
}

图论

并查集

class UnionFind {
    vector<int> root;
    vector<int> rank;
public:
    UnionFind(int size) {
        root.resize(size);
        rank.resize(size);
        for (int i = 0; i < size; ++i) {
            root[i] = rank[i] = i;
        }
    }

    int find(int x) {
        if (x == root[x]) return x;
        return root[x] = find(root[x]);
    }

    void connect(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            if (rank[rootX] > rank[rootY]) {
                root[rootY] = rootX;
            } else if (rank[rootX] < rank[rootY]) {
                root[rootX] = rootY;
            } else {
                root[rootY] = rootX;
                rank[rootX] += 1;
            }
        }
    }

    bool isConnected(int x, int y) {
        return find(x) == find(y);
    }
};

BellmanFord

namespace BellmanFord {
    template <typename Tp>
    struct BellmanFord {
        struct Edge {
            uint32_t from, to;
            Tp distance;
        };
        std::vector<Edge> m_edges;
        std::vector<Tp> m_distances;
        std::vector<uint32_t> m_from;
        uint32_t m_vertexNum;
        Tp m_infiniteDistance;
        BellmanFord(uint32_t _vertexNum, uint32_t _edgeNum, Tp _infiniteDistance = std::numeric_limits<Tp>::max() / 2) : m_distances(_vertexNum, _infiniteDistance), m_vertexNum(_vertexNum), m_infiniteDistance(_infiniteDistance) { m_edges.reserve(_edgeNum); }
        void addEdge(uint32_t _a, uint32_t _b, Tp _distance) { m_edges.push_back({_a, _b, _distance}); }
        void setDistance(uint32_t _i, Tp _distance = 0) { m_distances[_i] = _distance; }
        template <bool GetPath = false>
        bool calc() {
            if constexpr (GetPath) m_from.resize(m_vertexNum, -1);
            uint32_t lastUpdate = -1;
            for (uint32_t i = 0; i < m_vertexNum && lastUpdate == i - 1; i++)
                for (uint32_t index = 0; index < m_edges.size(); index++)
                    if (auto &[from, to, distance] = m_edges[index]; m_distances[from] != m_infiniteDistance && chmin(m_distances[to], m_distances[from] + distance)) {
                        lastUpdate = i;
                        if constexpr (GetPath) m_from[to] = index;
                    }
            return lastUpdate != m_vertexNum - 1;
        }
        std::vector<uint32_t> getPath_edge(uint32_t _target) const {
            std::vector<uint32_t> path;
            for (uint32_t cur = _target; ~m_from[cur]; cur = m_edges[m_from[cur]].from) path.push_back(m_from[cur]);
            std::reverse(path.begin(), path.end());
            return path;
        }
        std::vector<uint32_t> getPath_vertex(uint32_t _target) const {
            std::vector<uint32_t> path;
            path.push_back(_target);
            for (uint32_t cur = _target; ~m_from[cur];) path.push_back(cur = m_edges[m_from[cur]].from);
            std::reverse(path.begin(), path.end());
            return path;
        }
    };
}

最大流

namespace Dinic {
    template<typename Tp>
    struct Dinic {
        struct RawEdge {
            uint32_t from, to;
            Tp cap;
        };

        struct Edge {
            uint32_t to, rev;
            Tp cap;

            bool operator>(const Edge &other) const { return cap > other.cap; }
        };

        std::vector<RawEdge> m_rawEdges;
        std::vector<Edge> m_edges;
        std::vector<uint32_t> m_starts;
        uint32_t m_vertexNum;

        Dinic(uint32_t _vertexNum, uint32_t __edgeNum);

        void addEdge(uint32_t __a, uint32_t __b, Tp __cap) { m_rawEdges.push_back({__a, __b, __cap}); }

        void prepare() {
            for (auto &[from, to, cap]: m_rawEdges)
                if (from != to) {
                    m_starts[from + 1]++;
                    m_starts[to + 1]++;
                }
            std::partial_sum(m_starts.begin(), m_starts.end(), m_starts.begin());
            m_edges.resize(m_starts.back());
            uint32_t cursor[m_vertexNum];
            std::copy(m_starts.begin(), m_starts.begin() + m_vertexNum, cursor);
            for (auto &[from, to, cap]: m_rawEdges)
                if (from != to) {
                    m_edges[cursor[from]] = Edge{to, cursor[to], cap};
                    m_edges[cursor[to]++] = Edge{from, cursor[from]++, 0};
                }
        }

        template<typename _Compare = std::greater<Edge>>
        void prepareSorted(_Compare __comp = _Compare()) {
            prepare();
            for (uint32_t i = 0; i < m_vertexNum; i++) {
                uint32_t start = m_starts[i], end = m_starts[i + 1];
                std::sort(m_edges.begin() + start, m_edges.begin() + end, __comp);
                for (uint32_t j = start; j < end; j++) m_edges[m_edges[j].rev].rev = j;
            }
        }

        Tp calc(uint32_t _source, uint32_t _target, Tp _infiniteCap = std::numeric_limits<Tp>::max() / 2) {
            uint32_t queue[m_vertexNum], depth[m_vertexNum], it[m_vertexNum], end[m_vertexNum];
            Tp res = 0;
            for (uint32_t i = 0; i < m_vertexNum; i++) end[i] = m_starts[i + 1];
            auto dfs = [&](auto self, uint32_t i, Tp _cap) {
                if (i == _target || !_cap) return _cap;
                Tp flow = 0, f;
                for (uint32_t &cur = it[i]; cur != end[i]; cur++)
                    if (auto &[to, rev, cap] = m_edges[cur]; depth[i] + 1 == depth[to] &&
                                                             (f = self(self, to, std::min(_cap, cap))))
                        if (flow += f, _cap -= f, cap -= f, m_edges[rev].cap += f; !_cap) break;
                return flow;
            };
            while (true) {
                std::fill(depth, depth + m_vertexNum, -1);
                uint32_t head = 0, tail = 0;
                depth[_source] = 0;
                queue[tail++] = _source;
                while (head < tail)
                    for (uint32_t from = queue[head++], cur = m_starts[from], end = m_starts[from + 1];
                         cur < end; cur++)
                        if (auto &[to, rev, cap] = m_edges[cur]; cap && chmin(depth[to], depth[from] + 1))
                            queue[tail++] = to;
                if (!~depth[_target]) break;
                for (uint32_t i = 0; i < m_vertexNum; i++) it[i] = m_starts[i];
                while (Tp flow = dfs(dfs, _source, _infiniteCap)) res += flow;
            }
            return res;
        }
    };

    template<typename Tp>
    Dinic<Tp>::Dinic(uint32_t _vertexNum, uint32_t _edgeNum) : m_starts(_vertexNum + 1, 0),
                                                               m_vertexNum(_vertexNum) {
        m_rawEdges.reserve(_edgeNum);
    }
}

线段树

class segmentTree {
    int mod = 0x3f3f3f3f;
    int a[100010];

    struct Segment_Tree {
        ll sum, add, mul;
        int l, r;
    } s[100010 * 4];

    void update(int pos) {
        s[pos].sum = (s[pos << 1].sum + s[pos << 1 | 1].sum) % mod;
        return;
    }

    void pushdown(int pos) { //pushdown的维护
        s[pos << 1].sum = (s[pos << 1].sum * s[pos].mul + s[pos].add * (s[pos << 1].r - s[pos << 1].l + 1)) % mod;
        s[pos << 1 | 1].sum =
                (s[pos << 1 | 1].sum * s[pos].mul + s[pos].add * (s[pos << 1 | 1].r - s[pos << 1 | 1].l + 1)) % mod;

        s[pos << 1].mul = (s[pos << 1].mul * s[pos].mul) % mod;
        s[pos << 1 | 1].mul = (s[pos << 1 | 1].mul * s[pos].mul) % mod;

        s[pos << 1].add = (s[pos << 1].add * s[pos].mul + s[pos].add) % mod;
        s[pos << 1 | 1].add = (s[pos << 1 | 1].add * s[pos].mul + s[pos].add) % mod;

        s[pos].add = 0;
        s[pos].mul = 1;
        return;
    }

    void build_tree(int pos, int l, int r) { //建树
        s[pos].l = l;
        s[pos].r = r;
        s[pos].mul = 1;

        if (l == r) {
            s[pos].sum = a[l] % mod;
            return;
        }

        int mid = (l + r) >> 1;
        build_tree(pos << 1, l, mid);
        build_tree(pos << 1 | 1, mid + 1, r);
        update(pos);
        return;
    }

    void mul(int pos, int x, int y, int k) { //区间乘法
        if (x <= s[pos].l && s[pos].r <= y) {
            s[pos].add = (s[pos].add * k) % mod;
            s[pos].mul = (s[pos].mul * k) % mod;
            s[pos].sum = (s[pos].sum * k) % mod;
            return;
        }

        pushdown(pos);
        int mid = (s[pos].l + s[pos].r) >> 1;
        if (x <= mid) mul(pos << 1, x, y, k);
        if (y > mid) mul(pos << 1 | 1, x, y, k);
        update(pos);
        return;
    }

    void add(int pos, int x, int y, int k) { //区间加法
        if (x <= s[pos].l && s[pos].r <= y) {
            s[pos].add = (s[pos].add + k) % mod;
            s[pos].sum = (s[pos].sum + k * (s[pos].r - s[pos].l + 1)) % mod;
            return;
        }

        pushdown(pos);
        int mid = (s[pos].l + s[pos].r) >> 1;
        if (x <= mid) add(pos << 1, x, y, k);
        if (y > mid) add(pos << 1 | 1, x, y, k);
        update(pos);
        return;
    }

    ll AskRange(int pos, int x, int y) { //区间询问
        if (x <= s[pos].l && s[pos].r <= y) {
            return s[pos].sum;
        }
        pushdown(pos);
        ll val = 0;
        int mid = (s[pos].l + s[pos].r) >> 1;
        if (x <= mid) val = (val + AskRange(pos << 1, x, y)) % mod;
        if (y > mid) val = (val + AskRange(pos << 1 | 1, x, y)) % mod;
        return val;
    }
};

字典树

class Trie {
public:
    int nCnt;
    vector<vector<int>> ch;
    vector<int> f;

    int newNode() {
        ch.emplace_back(26, -1);
        f.push_back(0);
        return nCnt++;
    }

    void add(string &s) {
        int now = 0;
        for (char i: s) {
            f[now]++;
            int c = i - 'a';
            if (ch[now][c] == -1) ch[now][c] = newNode();
            now = ch[now][c];
        }
        f[now]++;
    }

    int query(string &s) {
        int now = 0, ret = 0;
        for (char i: s) {
            if (now > 0) ret += f[now];
            int c = i - 'a';
            now = ch[now][c];
        }
        ret += f[now];
        return ret;
    }
};

树状数组

template<typename T>
struct FenWick {
    int N;
    vector<T> arr;
    FenWick(int sz): N(sz), arr(sz + 1, 0) {}
    void update(int pos, T val) {
        for (; pos <= N;pos |= (pos + 1)) {
            arr[pos] += val;
        }
    }
    // 获取 [1, pos] 的和
    T get(int pos) {
        T ret = 0;
        for (; pos > 0; --pos) {
            ret += arr[pos];
            pos &= (pos + 1);
        }
        return ret;
    }
    // 获取 [l, r] 的和
    T query(int l, int r) {
        return get(r) - get(l - 1);
    }
};

珂朵莉树

namespace Chtholly {
    struct Node {
        int l, r;
        mutable int v;

        Node(int il, int ir, int iv) : l(il), r(ir), v(iv) {}

        bool operator<(const Node &arg) const {
            return l < arg.l;
        }
    };

    class Tree {
    protected:
        auto split(int pos) {
            if (pos > _sz) return odt.end();
            auto it = --odt.upper_bound(Node{pos, 0, 0});
            if (it->l == pos) return it;
            auto tmp = *it;
            odt.erase(it);
            odt.insert({tmp.l, pos - 1, tmp.v});
            return odt.insert({pos, tmp.r, tmp.v}).first;
        }

    public:
        Tree(int sz, int ini = 1) : _sz(sz), odt({Node{1, sz, ini}}) {}

        virtual void assign(int l, int r, int v) {
            auto itr = split(r + 1), itl = split(l);
            // operations here
            odt.erase(itl, itr);
            odt.insert({l, r, v});
        }

    protected:
        int _sz;
        set<Node> odt;
    };
}

ST表

template<typename iter, typename BinOp>
class SparseTable {
    using T = typename remove_reference<decltype(*declval<iter>())>::type;
    vector<vector<T>> arr;
    BinOp binOp;
public:
    SparseTable(iter begin, iter end, BinOp binOp) : arr(1), binOp(binOp) {
        int n = distance(begin, end);
        arr.assign(32 - __builtin_clz(n), vector<T>(n));
        arr[0].assign(begin, end);
        for (int i = 1; i < arr.size(); ++i) {
            for (int j = 0; j < n - (1 << i) + 1; ++j) {
                arr[i][j] = binOp(arr[i - 1][j], arr[i - 1][j + (1 << (i - 1))]);
            }
        }
    }

    T query(int lPos, int rPos) {
        int h = floor(log2(rPos - lPos + 1));
        return binOp(arr[h][lPos], arr[h][rPos - (1 << h) + 1]);
    }
};

后缀数组

class SuffixArray {
private:
    void radixSort(int n, int m, int w, vector<int> &sa, vector<int> &rk, vector<int> &bucket, vector<int> &idx) {
        fill(all(bucket), 0);
        for (int i = 0; i < n; ++i) idx[i] = sa[i];
        for (int i = 0; i < n; ++i) ++bucket[rk[idx[i] + w]];
        for (int i = 1; i < m; ++i) bucket[i] += bucket[i - 1];

        for (int i = n - 1; i >= 0; --i) sa[--bucket[rk[idx[i] + w]]] = idx[i];
        fill(all(bucket), 0);
        for (int i = 0; i < n; ++i) idx[i] = sa[i];
        for (int i = 0; i < n; ++i) ++bucket[rk[idx[i]]];
        for (int i = 1; i < m; ++i) bucket[i] += bucket[i - 1];
        for (int i = n - 1; i >= 0; --i) sa[--bucket[rk[idx[i]]]] = idx[i];
    }

public:
    SuffixArray(const string &s) :
            n(s.length() + 1),
            m(max((int) s.length() + 1, 300)),
            rk(2, vector<int>((s.length() + 1) << 1)),
            bucket(max((int) s.length() + 1, 300)),
            idx(s.length() + 1),
            sa(s.length() + 1),
            ht(s.length()) {

        for (int i = 0; i < n; ++i) ++bucket[rk[0][i] = s[i]];
        for (int i = 1; i < m; ++i) bucket[i] += bucket[i - 1];
        for (int i = n - 1; i >= 0; --i) sa[--bucket[rk[0][i]]] = i;
        int pre = 1;
        int cur = 0;
        for (int w = 1; w < n; w <<= 1) {
            swap(cur, pre);
            radixSort(n, m, w, sa, rk[pre], bucket, idx);
            for (int i = 1; i < n; ++i) {
                if (rk[pre][sa[i]] == rk[pre][sa[i - 1]] and rk[pre][sa[i] + w] == rk[pre][sa[i - 1] + w]) {
                    rk[cur][sa[i]] = rk[cur][sa[i - 1]];
                } else {
                    rk[cur][sa[i]] = rk[cur][sa[i - 1]] + 1;
                }
            }
        }
        for (int i = 0, k = 0; i < n - 1; ++i) {
            if (k) --k;
            while (s[i + k] == s[sa[rk[cur][i] - 1] + k]) ++k;
            ht[rk[cur][i] - 1] = k;
        }
    }

    vector<int> sa;
    vector<int> ht;
private:
    int n, m;
    vector<vector<int>> rk;
    vector<int> bucket, idx;
};

字符串

KMP

class KMP {
public:
    /**
     * @brief 统计目标串中有多少个模式串
     * @param target 目标字符串
     * @param pattern 模式字符串
     * */
    static int solve(string &target, string &pattern) {
        int ans = 0;
        int idxTarget = 0, idxPattern = 0;
        vector<int> next(std::move(_prefix(pattern)));
        while (idxTarget < target.length()) {
            while (idxPattern != -1 and pattern[idxPattern] != target[idxTarget]) {
                idxPattern = next[idxPattern];
            }
            ++idxTarget;
            ++idxPattern;
            if (idxPattern >= pattern.length()) {
                ++ans;
                idxPattern = next[idxPattern];
            }
        }
        return ans;
    }

private:
    static vector<int> _prefix(const string &pattern) {
        int i = 0, j = -1;
        vector<int> ret(pattern.length() + 1, -1);
        while (i < pattern.length()) {
            while (j != -1 and pattern[i] != pattern[j]) j = ret[j];
            if (pattern[++i] == pattern[++j]) {
                ret[i] = ret[j];
            } else {
                ret[i] = j;
            }
        }
        return ret;
    }
};

字符串哈希

class StringHash {
public:
    static unsigned BKDR(const std::string &str) {
        unsigned seed = 131; // 31 131 1313 13131 131313 etc..
        unsigned hash = 0;
        for (auto c: str) {
            hash = hash * seed + c;
        }
        return (hash & 0x7FFFFFFF);
    }

    static unsigned AP(const std::string &str) {
        unsigned hash = 0;
        for (int i = 0; i < str.length(); ++i) {
            if (i & 1) {
                hash ^= (~((hash << 11) ^ str[i] ^ (hash >> 5)));
            } else {
                hash ^= ((hash << 7) ^ str[i] ^ (hash >> 3));
            }
        }
        return (hash & 0x7FFFFFFF);
    }

    static unsigned DJB(const std::string &str) {
        unsigned hash = 5381;
        for (auto c: str) {
            hash += (hash << 5) + c;
        }
        return (hash & 0x7FFFFFFF);
    }

    static unsigned JS(const std::string &str) {
        unsigned hash = 1315423911;
        for (auto c: str) hash ^= ((hash << 5) + c + (hash >> 2));
        return (hash & 0x7FFFFFFF);
    }

    static unsigned SDBM(const std::string &str) {
        unsigned hash = 0;
        for (auto c: str) hash = c + (hash << 6) + (hash << 16) - hash;
        return (hash & 0x7FFFFFFF);
    }

    static unsigned PJW(const std::string &str) {
        auto bits_in_unsigned_int = (unsigned) (sizeof(unsigned) * 8);
        auto three_quarters = (unsigned) (bits_in_unsigned_int * 3 / 4);
        auto one_eighth = (unsigned) (bits_in_unsigned_int / 8);
        unsigned high_bits = (unsigned) (0xFFFFFFFF) << (bits_in_unsigned_int - one_eighth);
        unsigned hash = 0;
        unsigned test = 0;
        for (auto c: str) {
            hash = (hash << one_eighth) + c;
            if ((test = hash & high_bits) != 0) {
                hash = (hash ^ (test >> three_quarters)) & (~high_bits);
            }
        }
        return (hash & 0x7FFFFFFF);
    }

    static unsigned ELF(const std::string &str) {
        unsigned hash = 0, x = 0;
        for (auto c: str) {
            hash = (hash << 4) + c;
            if ((x = hash & 0xF0000000ll) != 0) {
                hash ^= (x >> 24);
                hash &= (~x);
            }
        }
        return (hash & 0x7FFFFFFF);
    }
};

AC自动机

namespace Automaton {
    struct ACNode {
        vector<int> nex;
        int fail;
        int cnt;

        ACNode() : nex(26, 0), cnt(0), fail(0) {}
    };

    class AC {
    public:
        AC() : nodes(1) {}

        void insert(const string &arg) {
            int cur = 0;
            for (auto &c: arg) {
                int to = c - 'a';
                if (!nodes[cur].nex[to]) {
                    nodes[cur].nex[to] = (int) nodes.size();
                    nodes.emplace_back();
                }
                cur = nodes[cur].nex[to];
            }
            nodes[cur].cnt++;
        }

        void build() {
            queue<int> Q;
            for (int i = 0; i < 26; ++i) {
                if (nodes[0].nex[i]) {
                    Q.push(nodes[0].nex[i]);
                }
            }
            while (!Q.empty()) {
                int cur = Q.front();
                Q.pop();
                for (int i = 0; i < 26; ++i) {
                    if (nodes[cur].nex[i]) {
                        nodes[nodes[cur].nex[i]].fail = nodes[nodes[cur].fail].nex[i];
                        Q.push(nodes[cur].nex[i]);
                    } else {
                        nodes[cur].nex[i] = nodes[nodes[cur].fail].nex[i];
                    }
                }
            }
        }

        int query(const string &arg) {
            int cur = 0, ans = 0;
            for (auto &c: arg) {
                cur = nodes[cur].nex[c - 'a'];
                for (int j = cur; j and nodes[j].cnt != -1; j = nodes[j].fail) {
                    ans += nodes[j].cnt;
                    nodes[j].cnt = -1;
                }
            }
            return ans;
        }

    private:
        vector<ACNode> nodes;
    };
}

Tricks

fastIO

#define fastIO() ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr)

least power of 2 and greater power of 2

//摘自dianhsu大佬
int leastPowerOfTwo(int val){
    return 32 - __builtin_clz(val - 1);
}
int greaterPowerOfTwo(int val){
    return 32 - __builtin_clz(val);
}

一些方便IO的重构

template<typename typC, typename typD>
istream &operator>>(istream &cin, pair<typC, typD> &a) { return cin >> a.first >> a.second; }

template<typename typC>
istream &operator>>(istream &cin, vector<typC> &a) {
    for (auto &x: a) cin >> x;
    return cin;
}

template<typename typC, typename typD>
ostream &operator<<(ostream &cout, const pair<typC, typD> &a) { return cout << a.first << ' ' << a.second; }

template<typename typC, typename typD>
ostream &operator<<(ostream &cout, const vector<pair<typC, typD>> &a) {
    for (auto &x: a) cout << x << '\n';
    return cout;
}

template<typename typC>
ostream &operator<<(ostream &cout, const vector<typC> &a) {
    int n = a.size();
    if (!n) return cout;
    cout << a[0];
    for (int i = 1; i < n; i++) cout << ' ' << a[i];
    return cout;
}
]]>
Haruko386[email protected]
CNUPC 20222022-09-16T00:00:00+00:002022-09-16T00:00:00+00:00https://haruko386.github.io/posts/2022/09/CNUPC2022专场信息

比赛链接:中国银联专场竞赛(2023届校园招聘专场) - 力扣(LeetCode)

排名:

cdsCE.png

题目解析(Q1, Q2, Q3)

银联-1. 重构链表

题意分析


给定一个链表,删除其中的所有偶数的节点

链表题大多数都可以用线性表水过去,笔者在此提供水版和正解版两种

正解:

  • 使用一个哨兵节点,选择性地将链表中的元素加入其中

参考代码


using vi = vector<int>;
class Solution {		//水题版
public:
    ListNode* reContruct(ListNode* head) {
        vi a;
        while(head)a.push_back(head->val),head=head->next;
        auto* ans=new ListNode(-1),*p=ans;
        
        for(int v:a){
            if(v%2)p->next=new ListNode(v),p=p->next;
        }
        return ans->next;
    }
};

class Solution {		//正解
public:
    ListNode* reContruct(ListNode* head) {
        auto *dummy=new ListNode(-1),*p=dummy;
        while(head){
            if(head->val%2){
                p->next=new ListNode(head->val);
                p=p->next;
            }
            head=head->next;
        }
        return dummy->next;
    }
};

银联-2. 勘探补给

题意分析


给定工程队的位置和所有补给点的位置,返回工程队选择补给的位置下标

二分查找每一个工程队的补给位置即可(这里采用maplower_bound方法);

本题中使用了统计最大最小值的方法,来巧妙处理边界问题(实际上就是边界WA了好几次懒得改了)

参考代码


#define pb push_back

class Solution {
public:
    vector<int> explorationSupply(vector<int> &station, vector<int> &pos) {
        vector<int> ans;
        map<int, int> map;
        int maxx=*max_element(all(station)), minn=*min_element(all(station));
        for (int i = 0; i < station.size(); ++i) {
            map[station[i]] = i;
        }
        for (int p: pos) {
            if(p>=maxx){
                ans.push_back(station.size()-1);
                continue;
            }else if (p<minn){
                ans.push_back(0);
                continue;
            }
            
            auto it = map.lower_bound(p);
            if (it->first == p) {
                ans.push_back(it->second);
                continue;
            }
            if (it != map.begin())--it;
            
            if (it == map.end()) {
                --it;
                ans.pb(it->second);
            } else if (it == map.begin()) {
                int i1 = it->second, i2 = i32;
                ++it;
                if (it != map.end())i2 = it->second;
                if (abs(station[i1] - p) <= abs(station[i2] - p))ans.pb(i1);
                else ans.pb(i2);
            } else {
                int i1 = it->second, i2, i3 = i32;
                --it;
                i2 = it->second;
                ++it;
                ++it;
                if (it != map.end())i3 = it->second;

                int d1 = abs(p - station[i1]), d2 = abs(p - station[i2]), d3 = abs(p - station[i3]);
                if (d1 <= d2 && d1 <= d3)ans.pb(i1);
                else if (d2 <= d1 && d2 <= d3)ans.pb(i2);
                else ans.pb(i3);
            }
        }
        return ans;
    }
};

银联-3. 风能发电

题意分析


给定容量为storeLimit的容器,在每一个使用区间内:

  • 若此时的供电量小于minSupply则从容器中去除电量(如果有富余)
  • 若此时的供电量大于maxSupply则向容器中加入电量(如果还有空间)

随时间变化,供电的最大最小需求也会不同,返回最终容器中剩余的电荷量

双指针模拟,一个指向供电,一个指向时间戳

参考代码


class Solution {
public:
    int StoredEnergy(int storeLimit, const vector<int>& power, const vector<vector<int>>& supply){
        int i=0,j=0,m=power.size(),n=supply.size(),time=0;
        int limit=0;
        while (i<m){
            if(time>=supply[min(j+1,n-1)][0])j=min(j+1,n-1);
            int p=power[i];

            if(p<=supply[j][1])limit=max(limit-(supply[j][1]-p),0);
            else if(p>= supply[j][2])limit=min(storeLimit, limit+(p-supply[j][2]));
            ++i;
            ++time;
        }
        return limit;
    }
};
]]>
Haruko386[email protected]
LeetCode-Weekly-Contest-album12022-08-20T00:00:00+00:002022-08-20T00:00:00+00:00https://haruko386.github.io/posts/2022/08/leetcode

后期退役ACM后再未更新

B85 - T3 字母移位 II

题意分析


每次将给的区间内的字母+1-1

区间和问题,可以使用差分数组来完成

参考代码


class Solution {
public:
    string shiftingLetters(string s, vector<vector<int>>& shifts) {
        int n = s.size();
        int cnt[n];
        memset(cnt, 0, sizeof(cnt));

        for(vector<int> a:shifts){
            int start=a[0],end=a[1],op=a[2];
            if (op==0){
                cnt[start]--;
                if (end+1<n)cnt[end+1]++;
            } else{
                cnt[start]++;
                if (end+1<n)cnt[end+1]--;
            }
        }
        for(int i = 1;i<n;++i)
            cnt[i]+=cnt[i-1];
        

        for (int i = 0; i < n; ++i) {
            cnt[i]+=(s[i]-'a');
            while(cnt[i]<0)cnt[i]+=26;
            if(cnt[i]>=26)cnt[i]%=26;
        }
   
        string a;
        for (int i = 0; i < n; ++i) 
            a+=(char)(cnt[i] + 'a');
        
        return a;
    }
};

B85 - T4 删除操作后的最大子段和

题意分析


每次删除数组中的一个数字,将数组分成多个子段

返回每次分割后的,子串的最大和

逆向遍历 + 并查集:

  • 正向为删除,逆向则为链接子段
  • 每次要链接相应的子段,可以考虑使用并查集
  • 添加 \(x=removeQueries[i]\) 时,用并查集合并xx+1,并把nums[x]加入到子段中
  • ans[i] 要么取上一个ans[i+1]的子段和,要么取合并后的子段和(二者取最大值)

参考代码


class Solution {
    typedef long long ll;
public:
    vector<long long> maximumSegmentSum(vector<int>& nums, vector<int>& removeQueries) {
        int n = nums.size();
        int f[n+1];
        iota(f, f + n + 1, 0);
        ll sum[n+1];
        memset(sum, 0, sizeof(sum));

        function<int(int)> find = [&](int x) -> int { return f[x] == x ? x : f[x] = find(f[x]); };
        std::vector<ll> ans(n);

        for(int i=n-1; i>0; --i){
            int x = removeQueries[i];
            int to = find(x+1);
            f[x]=to;
            sum[to]+= sum[x] + nums[x];
            ans[i-1] = max(ans[i], sum[to]);
        }
        return ans;
    }
};

W307-T2 最大回文数字

题意分析


本题主要使用贪心算法解决,读者很快就可以想到,有趣的是在如何减少字符串相加带来的时间开销和处理前缀0的问题

  • 对于字符串相加问题,由于题目要求得到回文串,我们可以在增加字符时只增加一半,剩下一半通过反转后再相加,从而减少了时间开销
  • 对于前缀0问题,我们可以使用一个flag来统计,由于0只能在结果字符串的中间部分,则若向字符串内增加了0,暂时将flag标记为1。若后续向0前方又增加了数字,此时便可以将flag0(没有前缀0问题了)

参考代码


class Solution {
public:
    string largestPalindromic(string num) {
        map<int, int> map;
        for(char c:num)map[c-'0']++;
        if(map.size() == 1) {
            return map.begin()->first == 0 ? "0" : num;
        }
        string a;
        bool flag = false;
        for(auto &it: map){
            int to = it.second;
            if(to > 1){
                if(!flag && it.first == 0) flag = 1;
                else flag = 0;
                
                for(int i = 0; i < to / 2; ++i) {
                    a.push_back((char)(it.first + '0'));
                }
                if(to % 2 == 1) it.second = 1;
                else it.second = 0;
            }
        }
        string b;
        if (flag) b = "";
        else {
            b = a; 
            reverse(b.begin(), b.end());
            b += a;
        }
        int mid = b.size() / 2;
        for (auto it = map.rbegin(); it != map.rend(); ++it){
            if (it->second != 0) {
                b.insert(b.begin() + mid, (char)(it->first + '0'));
                break;
            }
        }
        return b;
    }
};

W307 - T3 感染二叉树需要的总时间

题意分析


本题没有什么思维量,仅存的难点在于由TreeNode建图

作者此处使用了较为方便的unordered_map建图

参考代码


class Solution {
public:
    void dfs(unordered_map<int, vector<int>> &map, TreeNode* root){
        if (root== nullptr) return;
        if (root->left!= nullptr)map[root->val].push_back(root->left->val),map[root->left->val].push_back(root->val), dfs(map, root->left);
        if (root->right!= nullptr)map[root->val].push_back(root->right->val),map[root->right->val].push_back(root->val), dfs(map,root->right);
    }

    int amountOfTime(TreeNode* root, int start) {
        unordered_map<int, vector<int>> map;
        dfs(map, root);

        queue<int> queue;
        queue.push(start);

        int ans = 0, n = map.size();
        unordered_set<int> set;


        while (!queue.empty()){
            int size = queue.size();
            while (size-->0){
                int pos = queue.front();
                queue.pop();

                if(set.count(pos)) continue;
                set.insert(pos);

                for(int v: map[pos]) if (!set.count(v)) queue.push(v);
            }
            ++ans;
        }
        return ans - 1;
    }
};

W308-T3 收集垃圾的最少总时间

题意分析


每次只能使用一辆垃圾车,每次必须收集完全部的垃圾

则可以对每一栋房子中的三种垃圾以及所有的三种垃圾计数

直到采集完成后结束

参考代码


class Solution {
public:
    int garbageCollection(vector<string>& garbage, vector<int>& travel) {
        unordered_map<int, unordered_map<char, int>> map;
        unordered_map<char, int> cnt;
        int n = garbage.size();

        for (int i=0; i<n; ++i) {
            string g = garbage[i];
            for(char c:g) map[i][c]++,++cnt[c];
        }

        char a[3] = {'P','M','G'};

        int ans=0;

        for (const char& c: a){
            int p=0;
            while (cnt[c]>0){
                ans += map[p][c],cnt[c]-=map[p][c];
                if (cnt[c]>0)ans+=travel[p],++p;
                else break;
            }
        }
        return ans;
    }
};

W308-T4 给定条件下构造矩阵

题意分析


给的一个行元素限制和列元素限制,返回按照限制之下所能构成的矩阵

由于元素之间只有先后出现之分,可以使用拓扑排序来确定每一个元素由下到上的出现次序和由右到左的出现次序

参考代码


class Solution {
    vector<int> topo_sort(int k, vector<vector<int>>& edges) {
        vector<vector<int>> grid(k);
        vector<int> in(k);

        for(auto &e: edges){
            int x = e[0]-1,y=e[1]-1;
            grid[x].push_back(y);
            in[y]++;
        }

        vector<int> order;
        queue<int> queue;
        for(int i=0; i<k; ++i)
            if(in[i]==0)queue.push(i);
        
        while(!queue.empty()){
            int pos = queue.front();
            queue.pop();
            order.push_back(pos);
            for(int v: grid[pos])if(--in[v]==0)queue.push(v);
        }
        return order;
    }

public:
    vector<vector<int>> buildMatrix(int k, vector<vector<int>>& rowConditions, vector<vector<int>>& colConditions) {
        auto row=topo_sort(k, rowConditions), col=topo_sort(k,colConditions);
        if(row.size()<k || col.size()<k)return {};
        vector<int> pos(k);
        for(int i=0; i<k; ++i)pos[col[i]]=i;

        vector<vector<int>> ans(k,vector<int>(k,0));

        for(int i=0; i<k; ++i)
            ans[i][pos[row[i]]] = row[i] + 1;
    
        return ans;
    }
};

B86-T3 被列覆盖的最多行数

题意分析


选择其中col列,检测是否每一行中所有的1都被选中了

数据量小,直接回溯即可

参考代码


class Solution {
    int ans = 0;

    void check(vector<vector<>int> &mat, unordered_set<int> &set) {
        int cnt = 0;
        for (int i = 0; i < mat.size(); ++i) {
            int j;
            for (j = 0; j < mat[0].size(); ++j) {
                if (mat[i][j] == 1) {
                    if (!set.count(j))break;
                }
            }
            if (j == mat[0].size())++cnt;
        }
        ans = max(ans, cnt);
    }

public:
    void dfs(vector<vector<int>> &mat, int cols, unordered_set<int> set, int idx) {
        if (idx == mat[0].size()&&cols!=0)return;
        if (cols == 0) {
            check(mat, set);
            return;
        }
        for (int i = idx; i < mat[0].size(); ++i) {
            set.insert(i);
            dfs(mat, cols - 1, set, i + 1);
            set.erase(i);
        }
    }

    int maximumRows(vector<vector<int>> &mat, int cols) {
        unordered_set<int> set;
        dfs(mat, cols, set, 0);
        return ans;
    }
};

B86-T4 预算内的最多机器人数目

题意分析


运行 k 个机器人 总开销max(chargeTimes) + k * sum(runningCosts)

注意到需要取所有机器人中的最大充电时间,我们可以使用单调队列来动态维护区间最大值,双指针进行区间的选择

参考代码


class Solution {
public:
    int maximumRobots(vector<int>& chargeTimes, vector<int>& runningCosts, long long budget) {
        int ans=0,left=0,right=0;
        deque<int> deque;
        long long sum=0ll;
        while(right<chargeTimes.size()){
            while(!deque.empty()&&chargeTimes[right]>=chargeTimes[deque.back()])
                deque.pop_back();
            deque.push_back(right);
            sum += runningCosts[right];
            while(!deque.empty()&&chargeTimes[deque.front()]+(right-left+1)*sum>budget){
                if(deque.front()==left)deque.pop_front();
                sum-=runningCosts[left],left++;
            }
            ans=max(ans,right-left+1),++right;
        }
        return ans;
    }
};

W309-T3 最长优雅子数组

题意分析


一个数组中各个元素相与之和均为0,则这个数组为一个优雅子数组;返回长度最长的优雅子数组

模拟位运算+滑动窗口

根据位运算知识易知:

  • 两个数字a与b的二进制串中,只有每一个位置上只有其中一个数字可以出现1时,两数相与的结果才为0
  • 推广到多个数字,即为任意数字在32位长的二进制串上,只有一个数字可以占据其中一个二进制位上的1

Java提供了使得数字转换为二进制数的方法,利用其进行模拟

参考代码


import java.util.ArrayList;
import java.util.List;
import java.util.Map;

class Solution {
    boolean check(List<String> list, String s, int[] cnt) {
        int n = s.length();
        boolean f = true;
        for (int i = n - 1, j = 0; i >= 0; --i, ++j) {
            if (s.charAt(i) == '1' && cnt[j] == 1) {
                f = false;
                break;
            }
        }
        return f;
    }

    public int longestNiceSubarray(int[] nums) {
        int[] cnt = new int[32];
        List<String> list = new ArrayList<>();
        int maxLen = 0;
        for (int n : nums) {
            list.add(Integer.toBinaryString(n));
        }

        int ans = 1;
        int l = 0, r = 0;
        while (r < nums.length) {
            String s = list.get(r);
            if (check(list,s,cnt)){
                ans= Math.max(ans, r-l+1);
                ++r;
                for (int i = s.length() - 1, j = 0; i >= 0; --i, ++j) {
                    if (s.charAt(i)=='1')cnt[j]=1;
                }
            } else {
                while (!check(list, s,cnt)){
                    String q=list.get(l);
                    for (int i = q.length() - 1, j = 0; i >= 0; --i, ++j) {
                        if (q.charAt(i)=='1')cnt[j]=0;
                    }
                    ++l;
                }
            }
        }
        return ans;
    }
}

W310-T3 将区间分为最少组数

题意分析


给定一些区间段,按照一定的顺序将其分为n组,使得在n组中,每一个区间都不相交,返回n的最小数目

由于两区间段是否相交只取决于前一个区间的end和后一个区间的start

  • start <= end则两个区间会相交

  • 例如[1,4][3,5]

则可以维护一个区间end值的优先队列,每次一添加新的区间时,只需要贪心地选择最小的那个end值进行判断

  • start > end则可以加入该分组中,并更新该分组的末尾

  • start <= end则不可加入任何分组(这一点接下来会有详细的分析),直接重新开一个分组,并将start对应的end加入其中

由于要求区间不可相交,则需要从start小的区间开始选取;因此需要对数组进行一个排序

参考代码


#include "bits/stdc++.h"
#include "iomanip"

using namespace std;

#define all(a) a.begin(),a.end()
#define rall(a) rbegin(a), rend(a)

using vi = vector<int>;

class Solution {
public:
    int minGroups(vector<vector<int>>& intervals) {
        sort(all(intervals));//排序,满足从小到大选择区间
        
        priority_queue<int, vi, greater<>> pq;
        
        for (vi a:intervals){
            int start=a[0],end=a[1];//当前请求加入分组的区间的起始值
            if (pq.empty())pq.push(end);//空分组时
            else{
                if (start <= pq.top()){//不可加入已有的分组,直接以end新建分组
                    pq.push(end);
                }else{//可加入已有分组,加入其中并更新区间尾数end
                    pq.pop();
                    pq.push(end);
                }
            }
        }
        //答案即为优先队列大小
        return pq.size();
    }
};

B87 - T3 按位或最大的最小子数组长度

题意分析


给定一个数组nums,对于其中的每一位i,找到包含这一位的一个子数组[i,len(nums)],使得该子数组中所有数或运算结果最大

枚举 + 二分

对于每一个数,结果最大则需要每一个二进制位上均为1,因此使用哈希表统计各个位上,存在1的下标,使用二分查找确定最远位置即可

参考代码


string Binary(int x) {
    string s = "";
    while (x) {
        if (x % 2 == 0) s = '0' + s;
        else s = '1' + s;
        x /= 2;
    }
    return s.size()==0 ? "0" : s;
}

using vi=vector<int>;
using vs=vector<string>;

class Solution {
public:
    vector<int> smallestSubarrays(vector<int>& nums) {
        int n=nums.size(),maxx=0;

        vs s(n);
        vi ans(n);
        map<int, set<int>> map;

        for (int i = 0; i < n; ++i) {
            s[i]= Binary(nums[i]);
            maxx=max(maxx,(int)s[i].size());
        }

        for (int i = 0; i < n; ++i) {
            string b=s[i];
            int j=b.size()-1;
            while (j>=0){
                if (b[j]=='1'){
                    map[b.size()-j].insert(i);
                }
                --j;
            }
        }

        for (int i = 0; i < n; ++i) {
            string target=s[i];
            int last=i;
            int j=maxx-1;
            while (target.size() < maxx)target.insert(target.begin(), '0');
            while (j>=0){
                if (target[j]=='0'){
                    auto it=map[maxx-j].lower_bound(i);
                    if(it!=map[maxx-j].end())last=max(last, *it);
                }
                --j;
            }
            ans[i]=last-i+1;
        }
        return ans;
    }
};

W311 - T4 字符串的前缀分数和

题意分析


找到每一个字符的前缀在全部数组中的数量

字典树的每个节点记录以该节点为前缀的字符串有几个,计算答案的时候只需要将字符串所有前缀节点的值加起来即可。复杂度 $$\mathcal{O}(n s )$$,其中 s 是字符串长度

参考代码


class Solution {
    int nCnt;
    vector<vector<int>> ch;
    vector<int> f;

    int newNode() {
        ch.push_back(vector<int>(26, -1));
        f.push_back(0);
        return nCnt++;
    }

    void add(string &s) {
        int now = 0;
        for (int i = 0; i < s.size(); i++) {
            f[now]++;
            int c = s[i] - 'a';
            if (ch[now][c] == -1) ch[now][c] = newNode();
            now = ch[now][c];
        }
        f[now]++;
    }

    int query(string &s) {
        int now = 0, ret = 0;
        for (int i = 0; i < s.size(); i++) {
            if (now > 0) ret += f[now];
            int c = s[i] - 'a';
            now = ch[now][c];
        }
        ret += f[now];
        return ret;
    }

public:
    vector<int> sumPrefixScores(vector<string>& words) {
        newNode(); 
        for (string &s : words) add(s);
        vector<int> ans;
        for (string &s : words) ans.push_back(query(s));
        return ans;
    }
};

W314 - T4 矩阵中和能被 K 整除的路径

题意分析


(0,0)出发前往(m - 1, n - 1),返回路径上和可以整除k的路径数目

动态规划:

  • 定义状态为\(f[i][j][l]\)表示以(i,j)为路径结尾时,整除k余数l的路径数目

  • 状态转移:

    • 由格子上方转移而来:

      f[i][j][l] = (f[i][j][l] + f[i - 1][j][(l - grid[i][j] % k + k) % k]) % mod;
      
    • 由格子左边转移而来

      f[i][j][l] = (f[i][j][l] + f[i][j - 1][(l - grid[i][j] % k + k) % k]) % mod;
      
  • 状态初始化:

    • f[0][0][grid[0][0] % k] = 1

参考代码


class Solution {
    typedef long long ll;
    const int mod = 1e9 + 7;
public:
    int numberOfPaths(vector<vector<int>> &grid, int k) {
        int m = grid.size(), n = grid[0].size();

        ll f[m][n][k];
        memset(f, 0, sizeof(f));

        f[0][0][grid[0][0] % k] = 1;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                for (int l = 0; l < k; l++) {
                    if (i > 0)
                        f[i][j][l] = (f[i][j][l] + f[i - 1][j][(l - grid[i][j] % k + k) % k]) % mod;
                    if (j > 0) 
                        f[i][j][l] = (f[i][j][l] + f[i][j - 1][(l - grid[i][j] % k + k) % k]) % mod;
                }
            }
        }
        return (int) (f[m - 1][n - 1][0] % mod);
    }
};

B90 - T3 摧毁一系列目标

题意分析


在水平的位置上有一些目标需要击毁,你可以击毁的目标为 \(nums[i] + c * space\) 返回可以击毁的最大数目

“表达式中有space,那我去掉space会怎么样?”

两边同时整除space得到(MarkDown|为整除) \(nums[i] \mid space + 0\) 那么问题一下子就简洁了,直接计数即可出结果

参考代码


class Solution {
public:
    int destroyTargets(vector<int>& nums, int space) {
        std::unordered_map<long long, int> map;
        for(int v:nums) map[v % space]++;
        int tot = 0, ans = INT_MAX;
        
        for (int i: nums) {
            int have = map[i % space];
            if (tot == have) {
                ans = min(ans, i);
            } else if (tot < have) {
                tot = have;
                ans = i;
            }
        }
        return ans;
    }
};

W317 - T3 美丽整数的最小增量

题意分析


给你两个正整数 ntarget

如果某个整数每一位上的数字相加小于或等于 target ,则认为这个整数是一个 美丽整数

目标是找到最小的X则可以使用贪心法

由于是要让整个数字之和变小,则我们直接让每一个数字都变0,从最小开始即可

参考代码


typedef long long ll;

class Solution {
    bool check(ll n, int target) {
        int to = 0;
        while (n > 0){
            to += n % 10;
            n /= 10;
        }
        return to <= target;
    }
public:
    long long makeIntegerBeautiful(long long n, int target) {
        if (check(n,target)) return 0;
        ll t = n;
        ll ans = 0, p = 1;
        while (!check(n, target)) {
            ll num = t % 10;
            t /= 10;

            if (num != 0){
                n += ((10 - num) * p);
                ans += p * (10 - num);
            }
            p *= 10;
            t = n / p;
        }
        return ans;
    }
};
]]>
Haruko386[email protected]
LFU Cache2022-06-29T00:00:00+00:002022-06-29T00:00:00+00:00https://haruko386.github.io/posts/2022/06/LFU-Cache

缓存算法是指令的一个明细表,用于提示计算设备的缓存信息中哪些条目应该被删去。常见类型包括LFU、LRU、ARC、FIFO、MRU。

本文中将介绍LRU缓存LFU缓存

最近最少使用算法(LRU)

介绍


LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。

例如音乐播放软件的最近播放页面采取的就是LRU缓存来存储一定数量的音乐缓存。

思路分析


  1. 对于该缓存,当我们调用某一某一值时,需要同时将调用的该值移动至序列头部(因为我们更新了自上次被访问以来所经历的时间 t);由于我们需要频繁地调整某一节点地位置,若使用线性表,每一次调整位置的开销为O(n);使用链表可以将每一次移动的开销减小到O(1)
  2. 我们已经确定了使用链表,但对于链表来说,每一次的移动开销很小,但每一次的查询开销却依然为O(n),这样和线性表基本没有区别了(线性表查询效率为O(1),移动效率为O(n));对于这个问题,哈希表无疑是最佳选择(查询效率O(1));因此,在建表的过程中,同时把节点存储到一个哈希表中,以便于我们快速地访问需要进行修改地节点
  3. 对于本题我们使用一个双向链表,这样在使用哈希查询时可以直接查询到该节点,且对节点进行移动时也可以快速地访问到该节点的前驱节点后继节点

参考代码

class LRUCache {
private:
    struct Node {
        int key, value;
        Node *pre = nullptr;
        Node *next = nullptr;

        Node(int key, int value) : key(key), value(value) {}
    };

public:
    int limit;
    Node *head, *tail;
    unordered_map<int, Node*> map;

    LRUCache(int capacity) {
        limit = capacity;
        head = new Node(-1, -1), tail = new Node(-1, -1);
        head->next = tail;
        tail->pre = head;
    }

    void insertHead(Node *node) {
        node->next = head->next;
        node->pre = head;

        head->next->pre = node;
        head->next = node;
    }

    void deleteNode(Node *node) {
        node->pre->next = node->next;
        node->next->pre = node->pre;
    }

    void moveHead(Node *node, int val) {
        deleteNode(node);
        insertHead(node);
        node->value = val;
    }

    int get(int key) {
        auto it = map.find(key);
        if (it == map.end()) return -1;

        Node *node = it->second;
        moveHead(node, node->value);
        return node->value;
    }

    void put(int key, int value) {
        auto it = map.find(key);

        if (it != map.end()) {
            Node *node = it->second;
            moveHead(node, value);
        } else {
            if (map.size() == limit) {
                Node *node = tail->pre;
                deleteNode(node);
                map.erase(node->key);
            }
            Node *newNode = new Node(key, value);
            insertHead(newNode);
            map.insert(make_pair(key, newNode));
        }
    }
};

最不经常使用算法(LFU)

介绍


LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。

思路分析


在LFU中,我们需要根据使用频率和使用时间来对每一个节点的位置进行确定,C++中的set底层为红黑树,可以满足我们的要求

  1. 结构体定义为

    struct Node {
        int freq, time;
        int key, value;
       
        Node(int _freq, int _time, int _key, int _value) : freq(_freq), time(_time), key(_key), value(_value) {}
       
        bool operator<(const Node &rhs) const {
            return freq == rhs.freq ? time < rhs.time : freq < rhs.freq;
        }
    };
    

    key, value为键值以及对应的数据

    time, freq为对应键值的使用次数以及最后一次使用该键值的时间

    下方还定义了一个比较方法:

    • 当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。
  2. 变量:

    • time代表“当前时间”,每进行任意方法的调用,就对其进行+1更新,便于后续调用节点时更新使用时间
    • set存储了当前缓存中的所有节点,并按照LFU进行排序
    • limit为容量上限(这题居然有0空间的缓存)
    • map快速定位到key对应的node
  3. 具体的方法:

    • get方法返回对应key值的value,并更新节点位置;当未找到节点时直接返回-1

      int get(int key) {
              time++;
           
              if (limit == 0) return -1;
               
              auto it = map.find(key);
              if (it == map.end()) return -1;
              Node node = it->second;
           
              set.erase(node);
           
              node.freq++;
              node.time = time;
           
              set.insert(node);
              it->second = node;
              return node.value;
          }
      
    • put方法用于新放置一个键:

      • key存在于缓存中,直接更新对应节点
      • 若不存在于缓存中;如果缓存满了,则先移除掉set的头节点(即最不经常使用的节点);上述判断完成后加入新节点即可
      void put(int key, int value) {
              time++;
           
              if (limit == 0) return;
              auto it = map.find(key);
              if (it == map.end()) {
                  if (map.size() == limit) {
                      auto node = set.begin();
                      map.erase(set.begin()->key);
                      set.erase(node);
                  }
                  Node node = Node(1, time, key, value);
                  set.insert(node);
                  map.insert(make_pair(key, node));
              } else {
                  auto it = map.find(key);
                  Node node = it->second;
           
                  set.erase(node);
           
                  node.freq++;
                  node.time = time;
                  node.value = value;
           
                  set.insert(node);
                  it->second = node;
              }
          }
      

参考代码


struct Node {
    int freq, time;
    int key, value;

    Node(int _freq, int _time, int _key, int _value) : freq(_freq), time(_time), key(_key), value(_value) {}

    bool operator<(const Node &rhs) const {
        return freq == rhs.freq ? time < rhs.time : freq < rhs.freq;
    }
};

class LFUCache {
private:
    int limit;
    int time = 0;
    set<Node> set;
    unordered_map<int, Node> map;
public:
    LFUCache(int capacity) {
        limit = capacity;
    }

    int get(int key) {
        time++;

        if (limit == 0) return -1;
        auto it = map.find(key);
        if (it == map.end()) return -1;
        Node node = it->second;

        set.erase(node);

        node.freq++;
        node.time = time;

        set.insert(node);
        it->second = node;
        return node.value;
    }

    void put(int key, int value) {
        time++;

        if (limit == 0) return;
        auto it = map.find(key);
        if (it == map.end()) {
            if (map.size() == limit) {
                auto node = set.begin();
                map.erase(set.begin()->key);
                set.erase(node);
            }
            Node node = Node(1, time, key, value);
            set.insert(node);
            map.insert(make_pair(key, node));
        } else {
            auto it = map.find(key);
            Node node = it->second;

            set.erase(node);

            node.freq++;
            node.time = time;
            node.value = value;

            set.insert(node);
            it->second = node;
        }
    }
};
]]>
Haruko386[email protected]