普通视图

发现新文章,点击刷新页面。
今天 — 2025年2月22日首页

线性做法:枚举右,维护左(Python/Java/C++/Go/JS/Rust)

作者 endlesscheng
2022年12月18日 12:07

推荐先把 1512. 好数对的数目 做了。

为方便统计,把字符串 $s$ 中出现过的字母视作一个集合,把这个集合压缩成一个二进制数 $\textit{mask}$。其中 $\textit{mask}$ 第 $i$ 位为 $1$ 表示第 $i$ 个小写字母在 $s$ 中,为 $0$ 表示不在。这个技巧的详细解释见 从集合论到位运算,常见位运算技巧分类总结!

遍历 $\textit{words}$ 的同时,用一个哈希表 $\textit{cnt}$ 维护 $\textit{words}[i]$ 对应的 $\textit{mask}$ 的出现次数。和 1512 题一样,先把 $\textit{cnt}[\textit{mask}]$ 加到答案中,然后把 $\textit{cnt}[\textit{mask}]$ 加一。这个顺序可以保证我们只会统计 $i<j$ 的下标对,不会把 $i=j$ 的情况也统计进去。

class Solution:
    def similarPairs(self, words: List[str]) -> int:
        ans = 0
        cnt = defaultdict(int)
        for s in words:
            mask = 0
            for c in s:
                mask |= 1 << (ord(c) - ord('a'))
            ans += cnt[mask]
            cnt[mask] += 1
        return ans
class Solution {
    public int similarPairs(String[] words) {
        Map<Integer, Integer> cnt = new HashMap<>();
        int ans = 0;
        for (String s : words) {
            int mask = 0;
            for (char c : s.toCharArray()) {
                mask |= 1 << (c - 'a');
            }
            int c = cnt.getOrDefault(mask, 0);
            ans += c;
            cnt.put(mask, c + 1);
        }
        return ans;
    }
}
class Solution {
public:
    int similarPairs(vector<string>& words) {
        unordered_map<int, int> cnt;
        int ans = 0;
        for (auto& s : words) {
            int mask = 0;
            for (char c : s) {
                mask |= 1 << (c - 'a');
            }
            ans += cnt[mask]++;
        }
        return ans;
    }
};
func similarPairs(words []string) (ans int) {
    cnt := map[int]int{}
    for _, s := range words {
        mask := 0
        for _, c := range s {
            mask |= 1 << (c - 'a')
        }
        ans += cnt[mask]
        cnt[mask]++
    }
    return
}
var similarPairs = function(words) {
    const cnt = new Map();
    let ans = 0;
    for (const s of words) {
        let mask = 0;
        for (const c of s) {
            mask |= 1 << (c.charCodeAt(0) - 'a'.charCodeAt(0));
        }
        const c = cnt.get(mask) ?? 0
        ans += c;
        cnt.set(mask, c + 1);
    }
    return ans;
};
use std::collections::HashMap;

impl Solution {
    pub fn similar_pairs(words: Vec<String>) -> i32 {
        let mut cnt = HashMap::new();
        let mut ans = 0;
        for s in words {
            let mut mask = 0;
            for c in s.bytes() {
                mask |= 1 << (c - b'a');
            }
            ans += *cnt.get(&mask).unwrap_or(&0);
            *cnt.entry(mask).or_insert(0) += 1;
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(L)$,其中 $L$ 为 $\textit{words}$ 中所有字符串的长度之和。
  • 空间复杂度:$\mathcal{O}(n)$,其中 $n$ 为 $\textit{words}$ 的长度。哈希表需要 $\mathcal{O}(n)$ 的空间。

更多相似题目,见下面数据结构题单中的「§0.1 枚举右,维护左」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 【本题相关】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

昨天 — 2025年2月21日首页

教你一步步思考 DP:从记忆化搜索到递推到空间优化!(Python/Java/C++/Go)

作者 endlesscheng
2022年3月20日 09:23

一、寻找子问题

在示例 1 中,我们要解决的问题(原问题)是:

  • 有 $2$ 条地毯和 $8$ 块砖,计算没被覆盖的白色砖块的最少数目。

考虑从右往左覆盖砖。讨论最后一块砖是否覆盖(选或不选):

  • 如果不覆盖(不选)最后一块砖,那么需要解决的子问题为:有 $2$ 条地毯和 $8-1=7$ 块砖,计算没被覆盖的白色砖块的最少数目。
  • 如果覆盖(选)最后一块砖,那么末尾连续的 $\textit{carpetLen}=2$ 块砖都会被覆盖,需要解决的子问题为:有 $1$ 条地毯和 $8-\textit{carpetLen}=6$ 块砖,计算没被覆盖的白色砖块的最少数目。

由于选或不选都会把原问题变成一个和原问题相似的、规模更小的子问题,所以可以用递归解决。

注:从右往左思考,主要是为了方便把递归翻译成递推。从左往右思考也是可以的。

二、状态定义与状态转移方程

根据上面的讨论,我们需要在递归过程中跟踪以下信息:

  • $i$:还剩下 $i$ 条地毯。
  • $j$:剩余砖块为 $\textit{floor}[0]$ 到 $\textit{floor}[j]$,即 $j+1$ 块砖。

因此,定义状态为 $\textit{dfs}(i,j)$,表示用 $i$ 条地毯覆盖下标在 $[0,j]$ 中的砖,没被覆盖的白色砖块的最少数目。

接下来,思考如何从一个状态转移到另一个状态。

考虑 $\textit{floor}[j]$ 是否覆盖(选或不选):

  • 不覆盖(不选):接下来要解决的问题是,用 $i$ 条地毯覆盖下标在 $[0,j-1]$ 中的砖,没被覆盖的白色砖块的最少数目,再加上 $\texttt{int}(\textit{floor}[j])$(刚好白色是 $1$),得 $\textit{dfs}(i,j) = \textit{dfs}(i,j-1) + \texttt{int}(\textit{floor}[j])$。
  • 覆盖(选):如果 $i>0$,接下来要解决的问题是,用 $i-1$ 条地毯覆盖下标在 $[0,j-\textit{carpetLen}]$ 中的砖,没被覆盖的白色砖块的最少数目,即 $\textit{dfs}(i,j) = \textit{dfs}(i-1,j-\textit{carpetLen})$。

这两种情况取最小值,就得到了 $\textit{dfs}(i,j)$,即

$$
\textit{dfs}(i,j) =
\begin{cases}
\min(\textit{dfs}(i,j-1) + \texttt{int}(\textit{floor}[j]), \textit{dfs}(i-1,j-\textit{carpetLen})), & i>0 \
\textit{dfs}(i,j-1) + \texttt{int}(\textit{floor}[j]), & i=0 \
\end{cases}
$$

递归边界:如果 $j<\textit{carpetLen}\cdot i$,那么 $\textit{dfs}(i,j)=0$,因为剩余砖块可以全部覆盖。

递归入口:$\textit{dfs}(\textit{numCarpets},m-1)$,其中 $m$ 是 $\textit{floor}$ 的长度。这是原问题,也是答案。

三、递归搜索 + 保存递归返回值 = 记忆化搜索

考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:

  • 如果一个状态(递归入参)是第一次遇到,那么可以在返回前,把状态及其结果记到一个 $\textit{memo}$ 数组中。
  • 如果一个状态不是第一次遇到($\textit{memo}$ 中保存的结果不等于 $\textit{memo}$ 的初始值),那么可以直接返回 $\textit{memo}$ 中保存的结果。

注意:$\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\textit{dfs}(i,j)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。

Python 用户可以无视上面这段,直接用 @cache 装饰器。

具体请看视频讲解 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。

class Solution:
    def minimumWhiteTiles(self, floor: str, numCarpets: int, carpetLen: int) -> int:
        floor = list(map(int, floor))  # 避免在 dfs 中频繁调用 int()
        @cache  # 缓存装饰器,避免重复计算 dfs 的结果(一行代码实现记忆化)
        def dfs(i: int, j: int) -> int:
            if j < carpetLen * i:  # 剩余砖块可以全部覆盖
                return 0
            if i == 0:
                return dfs(i, j - 1) + floor[j]
            return min(dfs(i, j - 1) + floor[j], dfs(i - 1, j - carpetLen))
        return dfs(numCarpets, len(floor) - 1)
class Solution {
    public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) {
        int m = floor.length();
        int[][] memo = new int[numCarpets + 1][m];
        for (int[] row : memo) {
            Arrays.fill(row, -1); // -1 表示没有计算过
        }
        return dfs(numCarpets, m - 1, floor.toCharArray(), memo, carpetLen);
    }

    private int dfs(int i, int j, char[] floor, int[][] memo, int carpetLen) {
        if (j < carpetLen * i) { // 剩余砖块可以全部覆盖
            return 0;
        }
        if (memo[i][j] != -1) { // 之前计算过
            return memo[i][j];
        }
        int res = dfs(i, j - 1, floor, memo, carpetLen) + (floor[j] - '0');
        if (i > 0) {
            res = Math.min(res, dfs(i - 1, j - carpetLen, floor, memo, carpetLen));
        }
        return memo[i][j] = res; // 记忆化
    }
}
class Solution {
public:
    int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {
        int m = floor.size();
        vector memo(numCarpets + 1, vector<int>(m, -1)); // -1 表示没有计算过
        auto dfs = [&](this auto&& dfs, int i, int j) -> int {
            if (j < carpetLen * i) { // 剩余砖块可以全部覆盖
                return 0;
            }
            int& res = memo[i][j]; // 注意这里是引用
            if (res != -1) { // 之前计算过
                return res;
            }
            if (i == 0) {
                return res = dfs(i, j - 1) + (floor[j] - '0');
            }
            return res = min(dfs(i, j - 1) + (floor[j] - '0'), dfs(i - 1, j - carpetLen));
        };
        return dfs(numCarpets, m - 1);
    }
};
func minimumWhiteTiles(floor string, numCarpets, carpetLen int) int {
    m := len(floor)
    memo := make([][]int, numCarpets+1)
    for i := range memo {
        memo[i] = make([]int, m)
        for j := range memo[i] {
            memo[i][j] = -1 // -1 表示没有计算过
        }
    }
    var dfs func(int, int) int
    dfs = func(i, j int) (res int) {
        if j < carpetLen*i { // 剩余砖块可以全部覆盖
            return
        }
        p := &memo[i][j]
        if *p != -1 { // 之前计算过
            return *p
        }
        defer func() { *p = res }() // 记忆化
        res = dfs(i, j-1) + int(floor[j]-'0')
        if i > 0 {
            res = min(res, dfs(i-1, j-carpetLen))
        }
        return
    }
    return dfs(numCarpets, m-1)
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(\textit{numCarpets}\cdot m)$,其中 $m$ 是 $\textit{floor}$ 的长度。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\times$ 单个状态的计算时间。本题状态个数等于 $\mathcal{O}(\textit{numCarpets}\cdot m)$,单个状态的计算时间为 $\mathcal{O}(1)$,所以总的时间复杂度为 $\mathcal{O}(\textit{numCarpets}\cdot m)$。
  • 空间复杂度:$\mathcal{O}(\textit{numCarpets}\cdot m)$。保存多少状态,就需要多少空间。

四、1:1 翻译成递推

我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。

具体来说,$f[i][j]$ 的定义和 $\textit{dfs}(i,j)$ 的定义是完全一样的,都表示用 $i$ 条地毯覆盖下标在 $[0,j]$ 中的砖,没被覆盖的白色砖块的最少数目。

相应的递推式(状态转移方程)也和 $\textit{dfs}$ 一样:

$$
f[i][j] =
\begin{cases}
\min(f[i][j-1] + \texttt{int}(\textit{floor}[j]), f[i-1][j-\textit{carpetLen}]), & i>0 \
f[i][j-1] + \texttt{int}(\textit{floor}[j]), & i=0 \
\end{cases}
$$

初始值 $f[i][j]=0$,翻译自递归边界 $\textit{dfs}(i,j)=0$。

答案为 $f[\textit{numCarpets}][m-1]$,翻译自递归入口 $\textit{dfs}(\textit{numCarpets},m-1)$。

class Solution:
    def minimumWhiteTiles(self, floor: str, numCarpets: int, carpetLen: int) -> int:
        floor = list(map(int, floor))
        m = len(floor)
        f = [[0] * m for _ in range(numCarpets + 1)]
        f[0] = list(accumulate(floor))  # 单独计算 i=0 的情况,本质是 floor 的前缀和
        for i in range(1, numCarpets + 1):
            for j in range(carpetLen * i, m):
                f[i][j] = min(f[i][j - 1] + floor[j], f[i - 1][j - carpetLen])
        return f[-1][-1]
class Solution {
    public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) {
        char[] s = floor.toCharArray();
        int m = s.length;
        int[][] f = new int[numCarpets + 1][m];
        // 单独计算 i=0 的情况,本质是 s 的前缀和
        f[0][0] = s[0] - '0';
        for (int j = 1; j < m; j++) {
            f[0][j] = f[0][j - 1] + (s[j] - '0');
        }
        for (int i = 1; i <= numCarpets; i++) {
            for (int j = carpetLen * i; j < m; j++) {
                f[i][j] = Math.min(f[i][j - 1] + (s[j] - '0'), f[i - 1][j - carpetLen]);
            }
        }
        return f[numCarpets][m - 1];
    }
}
class Solution {
public:
    int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {
        int m = floor.size();
        vector f(numCarpets + 1, vector<int>(m));
        // 单独计算 i=0 的情况,本质是 floor 的前缀和
        f[0][0] = floor[0] - '0';
        for (int j = 1; j < m; j++) {
            f[0][j] = f[0][j - 1] + (floor[j] - '0');
        }
        for (int i = 1; i <= numCarpets; i++) {
            for (int j = carpetLen * i; j < m; j++) {
                f[i][j] = min(f[i][j - 1] + (floor[j] - '0'), f[i - 1][j - carpetLen]);
            }
        }
        return f[numCarpets][m - 1];
    }
};
func minimumWhiteTiles(floor string, numCarpets, carpetLen int) int {
    m := len(floor)
    f := make([][]int, numCarpets+1)
    for i := range f {
        f[i] = make([]int, m)
    }
    // 单独计算 i=0 的情况,本质是 floor 的前缀和
    f[0][0] = int(floor[0] - '0')
    for j := 1; j < m; j++ {
        f[0][j] = f[0][j-1] + int(floor[j]-'0')
    }
    for i := 1; i <= numCarpets; i++ {
        for j := carpetLen * i; j < m; j++ {
            f[i][j] = min(f[i][j-1]+int(floor[j]-'0'), f[i-1][j-carpetLen])
        }
    }
    return f[numCarpets][m-1]
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(\textit{numCarpets}\cdot m)$,其中 $m$ 是 $\textit{floor}$ 的长度。
  • 空间复杂度:$\mathcal{O}(\textit{numCarpets}\cdot m)$。

五、空间优化

由于计算 $f[i]$ 只需要知道 $f[i-1]$ 中的数据,我们可以改用两个长为 $m$ 的数组滚动计算。

此外,可以在计算 DP 之前,特判 $\textit{numCarpets}\cdot \textit{carpetLen}\ge m$ 的情况,此时所有砖块都能被覆盖,直接返回 $0$。

class Solution:
    def minimumWhiteTiles(self, floor: str, numCarpets: int, carpetLen: int) -> int:
        m = len(floor)
        if numCarpets * carpetLen >= m:
            return 0

        floor = list(map(int, floor))
        f = list(accumulate(floor))
        for i in range(1, numCarpets + 1):
            nf = [0] * m
            for j in range(carpetLen * i, m):
                nf[j] = min(nf[j - 1] + floor[j], f[j - carpetLen])
            f = nf
        return f[-1]
class Solution:
    def minimumWhiteTiles(self, floor: str, numCarpets: int, carpetLen: int) -> int:
        m = len(floor)
        if numCarpets * carpetLen >= m:
            return 0

        floor = list(map(int, floor))
        f = list(accumulate(floor))
        for i in range(1, numCarpets + 1):
            nf = [0] * m
            for j in range(carpetLen * i, m):
                x = nf[j - 1] + floor[j]
                y = f[j - carpetLen]
                nf[j] = x if x < y else y
            f = nf
        return f[-1]
class Solution {
    public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) {
        int m = floor.length();
        if (numCarpets * carpetLen >= m) {
            return 0;
        }

        char[] s = floor.toCharArray();
        int[] f = new int[m];
        f[0] = s[0] - '0';
        for (int j = 1; j < m; j++) {
            f[j] = f[j - 1] + (s[j] - '0');
        }
        for (int i = 1; i <= numCarpets; i++) {
            int[] nf = new int[m];
            for (int j = carpetLen * i; j < m; j++) {
                nf[j] = Math.min(nf[j - 1] + (s[j] - '0'), f[j - carpetLen]);
            }
            f = nf;
        }
        return f[m - 1];
    }
}
class Solution {
public:
    int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {
        int m = floor.size();
        if (numCarpets * carpetLen >= m) {
            return 0;
        }

        vector<int> f(m);
        f[0] = floor[0] - '0';
        for (int j = 1; j < m; j++) {
            f[j] = f[j - 1] + (floor[j] - '0');
        }
        for (int i = 1; i <= numCarpets; i++) {
            vector<int> nf(m);
            for (int j = carpetLen * i; j < m; j++) {
                nf[j] = min(nf[j - 1] + (floor[j] - '0'), f[j - carpetLen]);
            }
            f = move(nf);
        }
        return f[m - 1];
    }
};
func minimumWhiteTiles(floor string, numCarpets, carpetLen int) int {
    m := len(floor)
    if numCarpets*carpetLen >= m {
        return 0
    }

    f := make([]int, m)
    f[0] = int(floor[0] - '0')
    for j := 1; j < m; j++ {
        f[j] = f[j-1] + int(floor[j]-'0')
    }
    for i := 1; i <= numCarpets; i++ {
        nf := make([]int, m)
        for j := carpetLen * i; j < m; j++ {
            nf[j] = min(nf[j-1]+int(floor[j]-'0'), f[j-carpetLen])
        }
        f = nf
    }
    return f[m-1]
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(\textit{numCarpets}\cdot m)$,其中 $m$ 是 $\textit{floor}$ 的长度。
  • 空间复杂度:$\mathcal{O}(m)$。

更多相似题目,见 动态规划题单 中的「§6.3 约束划分个数」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 【本题相关】动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

昨天以前首页

两种方法:从 O(logn) 到 O(1)(Python/Java/C++/Go)

作者 endlesscheng
2023年3月19日 12:13

方法一:遍历二进制数

把 $n$ 当成一个二进制数来遍历。

遍历的顺序是从低位到高位。具体来说,通过 n & 1 取二进制的最低位,然后把 $n$ 右移一位,继续计算 n & 1,这样可以取到次低位。如此循环,直到 $n=0$ 为止。

在遍历的过程中,统计奇偶下标比特位中的 $1$ 的个数。

class Solution:
    def evenOddBit(self, n: int) -> List[int]:
        ans = [0, 0]
        i = 0
        while n:
            ans[i] += n & 1
            n >>= 1
            i ^= 1  # 切换奇偶
        return ans
class Solution {
    public int[] evenOddBit(int n) {
        int[] ans = new int[2];
        for (int i = 0; n > 0; n >>= 1) {
            ans[i] += n & 1;
            i ^= 1; // 切换奇偶
        }
        return ans;
    }
}
class Solution {
public:
    vector<int> evenOddBit(int n) {
        vector<int> ans(2);
        for (int i = 0; n; n >>= 1) {
            ans[i] += n & 1;
            i ^= 1; // 切换奇偶
        }
        return ans;
    }
};
func evenOddBit(n int) []int {
    ans := make([]int, 2)
    for i := 0; n > 0; n >>= 1 {
        ans[i] += n & 1
        i ^= 1 // 切换奇偶
    }
    return ans
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(\log n)$。
  • 空间复杂度:$\mathcal{O}(1)$。

方法二:位掩码 + 库函数

利用位掩码 0x55555555(二进制 $0101\cdots 01$),与 $n$ 计算 AND,即可取出所有偶数下标比特,然后用库函数统计其中 $1$ 的个数。

0x55555555 左移一位(二进制 $1010\cdots 10$),与 $n$ 计算 AND,即可取出所有奇数下标比特,然后用库函数统计其中 $1$ 的个数。

注:因为 $n$ 比较小,你也可以用 0x555 作为位掩码。

class Solution:
    def evenOddBit(self, n: int) -> List[int]:
        MASK = 0x55555555
        return [(n & MASK).bit_count(), (n & (MASK << 1)).bit_count()]
class Solution {
    public int[] evenOddBit(int n) {
        final int MASK = 0x55555555;
        return new int[]{Integer.bitCount(n & MASK), Integer.bitCount(n & (MASK << 1))};
    }
}
class Solution {
public:
    vector<int> evenOddBit(int n) {
        const unsigned MASK = 0x55555555u;
        return {popcount(n & MASK), popcount(n & (MASK << 1))};
    }
};
func evenOddBit(n int) []int {
    const mask = 0x55555555
    return []int{bits.OnesCount(uint(n & mask)), bits.OnesCount(uint(n & (mask << 1)))}
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(1)$。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

❌
❌