普通视图

发现新文章,点击刷新页面。
今天 — 2025年2月22日LeetCode 每日一题题解

每日一题-统计相似字符串对的数目🟢

2025年2月22日 00:00

给你一个下标从 0 开始的字符串数组 words

如果两个字符串由相同的字符组成,则认为这两个字符串 相似

  • 例如,"abca""cba" 相似,因为它们都由字符 'a''b''c' 组成。
  • 然而,"abacba""bcfd" 不相似,因为它们不是相同字符组成的。

请你找出满足字符串 words[i] words[j] 相似的下标对 (i, j) ,并返回下标对的数目,其中 0 <= i < j <= words.length - 1

 

示例 1:

输入:words = ["aba","aabb","abcd","bac","aabc"]
输出:2
解释:共有 2 对满足条件:
- i = 0 且 j = 1 :words[0] 和 words[1] 只由字符 'a' 和 'b' 组成。 
- i = 3 且 j = 4 :words[3] 和 words[4] 只由字符 'a'、'b' 和 'c' 。 

示例 2:

输入:words = ["aabb","ab","ba"]
输出:3
解释:共有 3 对满足条件:
- i = 0 且 j = 1 :words[0] 和 words[1] 只由字符 'a' 和 'b' 组成。 
- i = 0 且 j = 2 :words[0] 和 words[2] 只由字符 'a' 和 'b' 组成。 
- i = 1 且 j = 2 :words[1] 和 words[2] 只由字符 'a' 和 'b' 组成。 

示例 3:

输入:words = ["nba","cba","dba"]
输出:0
解释:不存在满足条件的下标对,返回 0 。

 

提示:

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 100
  • words[i] 仅由小写英文字母组成

【C/C++】哈希表 + 位运算(状态压缩) 简洁明了

作者 aixpis
2022年12月18日 20:49

解题思路

1.哈希表 + 位运算(状态压缩)

由于单词只由小写字母构成,所以可以把单词中字符是否出现的状态用int的低26位表示,不同单词经过位运算操作后得到相同的值,
那么就可以说明这两个单词由相同类型的字符组成。我们可以维护一个哈希表,key记录单词压缩后的int值, value记录单词个数。
答案要求的是满足条件的对数!所以应该把它们依次加起来,注意一定是先加入答案 再自增!
比如某个状态的value为4 表示该字符组合的单词有4个 共有 3 + 2 + 1 + 0 = 6对!不会把4加入答案!

代码

###c

typedef struct {
    int key;
    int value;
    UT_hash_handle hh;
} el;

int similarPairs(char ** words, int wordsSize){
    el* head = NULL;
    int ans = 0;
    for (int i = 0; i < wordsSize; ++i) {
        int bit = 0, len = strlen(words[i]);
        for (int j = 0; j < len; ++j) bit |= (1 << (words[i][j] - 'a'));
        el* cur = NULL;
        HASH_FIND_INT(head, &bit, cur);
        if (NULL == cur) {
            cur = (el*)malloc(sizeof(el));
            cur->key = bit; cur->value = 0;
            HASH_ADD_INT(head, key, cur);
        }
        ans += cur->value++;
    }
    el* cur, *tmp;
    HASH_ITER(hh, head, cur, tmp) {
        HASH_DEL(head, cur);
        free(cur);
    }
    return ans;
}

###c++

class Solution {
public:
    int similarPairs(vector<string>& words) {
unordered_map<int, int> mp;
int n = words.size(), ans = 0;
for (auto& word : words) {
int bit = 0;
for (auto& ch : word) bit |= (1 << (ch - 'a'));
ans += mp[bit]++;
}
return ans;
    }
};

时间复杂度 O(nm) n=words的长度 m=所有字符和
空间复杂度 O(n) 主要是哈希表消耗的空间

数学

作者 tsreaper
2022年12月18日 12:10

解法:数学

设包含相同字符集的字符串共有 $x$ 个,则答案就是 $\frac{x(x - 1)}{2}$ 之和。复杂度 $\mathcal{O}(\sum|s|)$。

参考代码(c++)

###c++

class Solution {
public:
    int similarPairs(vector<string>& words) {
        unordered_map<int, int> mp;
        for (string &s : words) {
            int msk = 0;
            for (char c : s) msk |= 1 << (c - 'a');
            mp[msk]++;
        }
        int ans = 0;
        for (auto it = mp.begin(); it != mp.end(); it++) {
            int x = it->second;
            ans += x * (x - 1) / 2;
        }
        return ans;
    }
};

线性做法:枚举右,维护左(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日LeetCode 每日一题题解

每日一题-用地毯覆盖后的最少白色砖块🔴

2025年2月21日 00:00

给你一个下标从 0 开始的 二进制 字符串 floor ,它表示地板上砖块的颜色。

  • floor[i] = '0' 表示地板上第 i 块砖块的颜色是 黑色 。
  • floor[i] = '1' 表示地板上第 i 块砖块的颜色是 白色 。

同时给你 numCarpets 和 carpetLen 。你有 numCarpets 条 黑色 的地毯,每一条 黑色 的地毯长度都为 carpetLen 块砖块。请你使用这些地毯去覆盖砖块,使得未被覆盖的剩余 白色 砖块的数目 最小 。地毯相互之间可以覆盖。

请你返回没被覆盖的白色砖块的 最少 数目。

 

示例 1:

输入:floor = "10110101", numCarpets = 2, carpetLen = 2
输出:2
解释:
上图展示了剩余 2 块白色砖块的方案。
没有其他方案可以使未被覆盖的白色砖块少于 2 块。

示例 2:

输入:floor = "11111", numCarpets = 2, carpetLen = 3
输出:0
解释:
上图展示了所有白色砖块都被覆盖的一种方案。
注意,地毯相互之间可以覆盖。

 

提示:

  • 1 <= carpetLen <= floor.length <= 1000
  • floor[i] 要么是 '0' ,要么是 '1' 。
  • 1 <= numCarpets <= 1000

教你一步步思考 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站@灵茶山艾府

wqs 二分 O(n log n)

作者 zerotrac2
2022年3月20日 01:37

容易发现当 k 增加时,覆盖的 1 的数量的增量是单调不增的,因此覆盖的 1 的数量关于 k 是一个上凸函数,可以使用 wqs 二分。

贴两个链接,感兴趣的可以学一学。

wqs 二分学习笔记

一种基于 wqs 二分的优秀做法

###C++

class Solution {
public:
    int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {
        int n = floor.size();
        vector<int> pre(n);
        pre[0] = (floor[0] == '1');
        for (int i = 1; i < n; ++i) {
            pre[i] = pre[i - 1] + (floor[i] == '1');
            if (i >= carpetLen) {
                pre[i] -= (floor[i - carpetLen] == '1');
            }
        }

        vector<int> f(n), g(n);
        int l = 0, r = n, ans = 0;
        while (l <= r) {
            f[0] = g[0] = 0;
            int mid = (l + r) / 2;
            for (int i = 0; i < n; ++i) {
                if (i != 0) {
                    f[i] = f[i - 1];
                    g[i] = g[i - 1];
                }
                if (i >= carpetLen) {
                    int use = f[i - carpetLen] + pre[i] - mid;
                    if (use > f[i]) {
                        f[i] = use;
                        g[i] = g[i - carpetLen] + 1;
                    }
                }
                else {
                    int use = pre[i] - mid;
                    if (use > f[i]) {
                        f[i] = use;
                        g[i] = 1;
                    }
                }
            }
            
            if (g[n - 1] <= numCarpets) {
                ans = f[n - 1] + mid * numCarpets;
                r = mid - 1;
            }
            else {
                l = mid + 1;
            }
        }

        int cnt1 = 0;
        for (char ch: floor) {
            cnt1 += (ch == '1');
        }
        return cnt1 - ans;
    }
};

贪心 && DP C++

作者 Cyber-Space
2022年3月20日 00:13

dp[i][j] 表示考虑前面长度为 j 的地板中,用了 i 条地毯,最后剩余最少白色砖块。
状态转移方程

  1. i == 0 时,此时不用地毯, dp[0][j] 表示前 j 个砖块中白色砖块数目。
  2. i != 0 时,dp[i][j] = min(dp[i][j - 1] + count[j], dp[i - 1][j - carpetLen]);
    其中 count[j] 表示第 j 块地板是否为白色砖块,
  • dp[i][j-1] + count[j] 表示前 j - 1 块地板用了i 条地毯,剩余最少的白色砖块,加上第 j 块地板是否为白色砖块。(即第 j 个位置不用地毯)
  • dp[i - 1][j - carpetLen] 表示在下标 j 这里用了地毯,那么 dp[i][j] 就和前 (j - carpetLen) 块地板中用了 i - 1 条地毯剩余的白色砖块数目一致。(即第 j 个位置用了地毯)

时间复杂度: O(nm)
空间复杂度: O(n
m)
其中 n 为 floor 长度, m 为numcarpets

代码如下:

class Solution {
public:
    int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {
        int len = floor.size();
        vector<vector<int>> dp(numCarpets + 1, vector<int>(len, 0));
        vector<int> count(len); //第j块地板是否为白色
        dp[0][0] = floor[0] == '1' ? 1 : 0;
        for(int i = 1; i < len; ++i) {
            dp[0][i] = dp[0][i - 1];
            if(floor[i] == '1') dp[0][i]++, count[i] = 1;
        }
        for(int i = 1; i <= numCarpets; ++i) {
            for(int j = 0; j < len; ++j) {
                if(j < carpetLen) dp[i][j] = 0; //少于carpetLen块砖块用了地毯最终都会剩余0块白色砖块
                else dp[i][j] = min(dp[i][j - 1] + count[j], dp[i - 1][j - carpetLen]);
            }
        }
        return dp[numCarpets][len - 1];
    }
};
昨天以前LeetCode 每日一题题解

[Python3/Java/C++/Go/TypeScript] 一题双解:枚举 & 位运算(清晰题解)

作者 lcbin
2025年2月20日 08:16

方法一:枚举

我们根据题意,枚举 $n$ 的二进制表示中从低位到高位的每一位,如果该位为 $1$,则根据该位的下标是奇数还是偶数,将对应的计数器加 $1$ 即可。

###python

class Solution:
    def evenOddBit(self, n: int) -> List[int]:
        ans = [0, 0]
        i = 0
        while n:
            ans[i] += n & 1
            i ^= 1
            n >>= 1
        return ans

###java

class Solution {
    public int[] evenOddBit(int n) {
        int[] ans = new int[2];
        for (int i = 0; n > 0; n >>= 1, i ^= 1) {
            ans[i] += n & 1;
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    vector<int> evenOddBit(int n) {
        vector<int> ans(2);
        for (int i = 0; n > 0; n >>= 1, i ^= 1) {
            ans[i] += n & 1;
        }
        return ans;
    }
};

###go

func evenOddBit(n int) []int {
ans := make([]int, 2)
for i := 0; n != 0; n, i = n>>1, i^1 {
ans[i] += n & 1
}
return ans
}

###ts

function evenOddBit(n: number): number[] {
    const ans = Array(2).fill(0);
    for (let i = 0; n > 0; n >>= 1, i ^= 1) {
        ans[i] += n & 1;
    }
    return ans;
}

###rust

impl Solution {
    pub fn even_odd_bit(mut n: i32) -> Vec<i32> {
        let mut ans = vec![0; 2];

        let mut i = 0;
        while n != 0 {
            ans[i] += n & 1;

            n >>= 1;
            i ^= 1;
        }

        ans
    }
}

时间复杂度 $O(\log n)$,空间复杂度 $O(1)$。其中 $n$ 为给定的整数。


方法二:位运算

我们可以定义一个掩码 $\textit{mask} = \text{0x5555}$,它的二进制表示为 $\text{0101 0101 0101 0101}_2$。那么 $n$ 与 $\textit{mask}$ 进行按位与运算,就可以得到 $n$ 的二进制表示中偶数下标的位,而 $n$ 与 $\textit{mask}$ 取反后再进行按位与运算,就可以得到 $n$ 的二进制表示中奇数下标的位。统计这两个结果中 $1$ 的个数即可。

###python

class Solution:
    def evenOddBit(self, n: int) -> List[int]:
        mask = 0x5555
        even = (n & mask).bit_count()
        odd = (n & ~mask).bit_count()
        return [even, odd]

###java

class Solution {
    public int[] evenOddBit(int n) {
        int mask = 0x5555;
        int even = Integer.bitCount(n & mask);
        int odd = Integer.bitCount(n & ~mask);
        return new int[] {even, odd};
    }
}

###cpp

class Solution {
public:
    vector<int> evenOddBit(int n) {
        int mask = 0x5555;
        int even = __builtin_popcount(n & mask);
        int odd = __builtin_popcount(n & ~mask);
        return {even, odd};
    }
};

###go

func evenOddBit(n int) []int {
mask := 0x5555
even := bits.OnesCount32(uint32(n & mask))
odd := bits.OnesCount32(uint32(n & ^mask))
return []int{even, odd}
}

###ts

function evenOddBit(n: number): number[] {
    const mask = 0x5555;
    const even = bitCount(n & mask);
    const odd = bitCount(n & ~mask);
    return [even, odd];
}

function bitCount(i: number): number {
    i = i - ((i >>> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
    i = (i + (i >>> 4)) & 0x0f0f0f0f;
    i = i + (i >>> 8);
    i = i + (i >>> 16);
    return i & 0x3f;
}

###rust

impl Solution {
    pub fn even_odd_bit(n: i32) -> Vec<i32> {
        let mask: i32 = 0x5555;
        let even = (n & mask).count_ones() as i32;
        let odd = (n & !mask).count_ones() as i32;
        vec![even, odd]
    }
}

时间复杂度 $O(1)$,空间复杂度 $O(1)$。其中 $n$ 为给定的整数。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

C++ 位运算/位掩码/BitSet

作者 vijaysue
2023年3月27日 21:21

###C++

class Solution {
public:
    vector<int> evenOddBit(int n) {
        return {__builtin_popcount(n & 0x5555), __builtin_popcount(n & (0x5555 << 1))};
    }
};

###C++

class Solution {
public:
    vector<int> evenOddBit(int n) {
        vector<int> ans(2);
        bitset<32> bs(n);
        string s = bs.to_string();
        for (int i = 32; i >= 0; --i) ans[!(i & 1)] += s[i] == '1';
        return ans;
    }
};

###C++

class Solution {
public:
    vector<int> evenOddBit(int n) {
        vector<int> ans(2);
        for (int i = 0; n != 0; i ^= 1, n >>= 1)
            ans[i] += n & 1;
        return ans;
    }
};

每日一题-奇偶位数🟢

2025年2月20日 00:00

给你一个 整数 n

even 表示在 n 的二进制形式(下标从 0 开始)中值为 1 的偶数下标的个数。

odd 表示在 n 的二进制形式(下标从 0 开始)中值为 1 的奇数下标的个数。

请注意,在数字的二进制表示中,位下标的顺序 从右到左

返回整数数组 answer ,其中 answer = [even, odd]

 

示例 1:

输入:n = 50

输出:[1,2]

解释:

50 的二进制表示是 110010

在下标 1,4,5 对应的值为 1。

示例 2:

输入:n = 2

输出:[0,1]

解释:

2 的二进制表示是 10

只有下标 1 对应的值为 1。

 

提示:

  • 1 <= n <= 1000

2595. 奇偶位数

作者 stormsunshine
2024年1月17日 20:28

解法一

思路和算法

最直观的思路是遍历正整数 $n$ 的二进制表示的从低到高的每一位,对于每个 $1$ 所在位,根据其所在下标的奇偶性计算位数,遍历结束之后即可得到答案。

代码

###Java

class Solution {
    public int[] evenOddBit(int n) {
        int[] answer = new int[2];
        int index = 0;
        while (n != 0) {
            answer[index % 2] += n % 2;
            n /= 2;
            index++;
        }
        return answer;
    }
}

###C#

public class Solution {
    public int[] EvenOddBit(int n) {
        int[] answer = new int[2];
        int index = 0;
        while (n != 0) {
            answer[index % 2] += n % 2;
            n /= 2;
            index++;
        }
        return answer;
    }
}

复杂度分析

  • 时间复杂度:$O(\log n)$,其中 $n$ 是给定的正整数。正整数 $n$ 的二进制表示的位数是 $O(\log n)$,需要遍历二进制表示的每一位。

  • 空间复杂度:$O(1)$。

解法二

思路和算法

为了得到正整数 $n$ 的二进制表示的值为 $1$ 的偶数下标个数和奇数下标个数,可以根据正整数 $n$ 生成两个正整数,第一个正整数保留正整数 $n$ 的偶数下标不变并将奇数下标全部置为 $0$,第二个正整数保留正整数 $n$ 的奇数下标不变并将偶数下标全部置为 $0$,然后分别计算两个正整数的二进制表示的 $1$ 的个数即可得到答案。计算两个正整数的方法如下:第一个正整数等于 $n ~&~ 55555555_{(16)}$,第二个正整数等于 $n ~&~ \text{aaaaaaaa}_{(16)}$。

计算一个整数 $m$ 的二进制表示的 $1$ 的个数的方法有多种,此处介绍一种基于分治的解法。

将整数 $m$ 看成二进制位 $1$ 的个数的表示形式,初始时 $m$ 分成 $32$ 组,每组有 $1$ 位,表示该组二进制位 $1$ 的个数。当 $m$ 的分组个数大于 $1$ 时,每次将 $m$ 的相邻两组合并,合并后每组中的数表示该组二进制位 $1$ 的个数,直到 $m$ 的分组个数等于 $1$ 时结束。分组情况具体变化如下。

  1. $16$ 组,每组有 $2$ 位。

  2. $8$ 组,每组有 $4$ 位。

  3. $4$ 组,每组有 $8$ 位。

  4. $2$ 组,每组有 $16$ 位。

  5. $1$ 组,每组有 $32$ 位。

由于 $k$ 位二进制数可以表示的最大值是 $2^k - 1$,对于任意正整数 $k$ 都有 $k \le 2^k - 1$,因此可以使用 $k$ 位的分组表示 $k$ 位中位 $1$ 的个数。

将 $1$ 位分组合并成 $2$ 位分组时,由于 $2$ 位二进制数的可能取值有 $00$、$01$、$10$、$11$,对应的位 $1$ 个数的二进制表示分别是 $00$、$01$、$01$、$10$,因此 $2$ 位二进制数 $x$ 的位 $1$ 个数是 $x - (x >> 1)$。对于整数 $m$,在计算 $m - (m >> 1)$ 时,为了避免跨组运算对结果的影响,每组 $2$ 位二进制数只保留低位,因此需要将 $m >> 1$ 和 $55555555_{(16)}$(十六进制数)做按位与运算。合并之后,$m$ 的值变成 $m - ((m >> 1) ~&~ 55555555_{(16)})$。

将 $2$ 位分组合并成 $4$ 位分组时,需要将每对相邻的 $2$ 位分组的值相加得到 $4$ 位分组的值。对于需要合并的相邻两对 $2$ 位分组,将 $4$ 位二进制数记为 $x$,则合并后的 $4$ 位分组是 $x$ 的最低 $2$ 位与 $x$ 的最高 $2$ 位之和,因此合并后的 $4$ 位分组是 $(x ~&~ 3) + ((x >> 2) ~&~ 3)$。合并之后,$m$ 的值变成 $(m ~&~ 33333333_{(16)}) + ((m >> 2) ~&~ 33333333_{(16)})$。

后续合并过程相似。

  • 将 $4$ 位分组合并成 $8$ 位分组之后,$m$ 的值变成 $(m + (m >> 4)) ~&~ \text{0f0f0f0f}_{(16)}$。

  • 将 $8$ 位分组合并成 $16$ 位分组之后,$m$ 的值变成 $(m + (m >> 8)) ~&~ \text{00ff00ff}_{(16)}$。

  • 将 $16$ 位分组合并成 $32$ 位分组之后,$m$ 的值变成 $(m + (m >> 16)) ~&~ \text{0000ffff}_{(16)}$。

此时 $m$ 的分组个数等于 $1$,$m$ 即为答案。

Java 的 $\texttt{Imteger.bitCount}$ 的实现原理即为上述计算方式,只是细节有所不同。Java 的 $\texttt{Imteger.bitCount}$ 的实现中,将 $8$ 位分组合并成 $16$ 位分组以及将 $16$ 位分组合并成 $32$ 位分组的过程中没有按位与运算的操作,而是在最后计算 $m ~&~ \text{3f}_{(16)}$,该结果和上述计算方式是等价的。由于原始的 $m$ 的位 $1$ 的个数最多是 $32$,即不会超过 $6$ 位二进制数,因此这两步合并过程的按位与运算可以用最后一步按位与运算代替。

上述计算方式对于 $m$ 是正整数、$0$ 和负整数的情况都适用。当 $m$ 可能是负整数时,由于 Java 没有无符号整数类型,因此在实现方面,上述右移运算应使用逻辑右移。

对于 $k$ 位二进制数,$k = O(\log m)$,分治解法的分组合并操作共需要 $O(\log k)$ 次,每次分组合并操作的时间都是 $O(1)$,因此分治解法的时间复杂度是 $O(\log k) = O(\log \log m)$。

代码

###Java

class Solution {
    static final int EVEN_MASK = 0x55555555, ODD_MASK = 0xaaaaaaaa;

    public int[] evenOddBit(int n) {
        return new int[]{bitCount(n & EVEN_MASK), bitCount(n & ODD_MASK)};
    }

    public int bitCount(int m) {
        m = m - ((m >>> 1) & 0x55555555);
        m = (m & 0x33333333) + ((m >>> 2) & 0x33333333);
        m = (m + (m >>> 4)) & 0x0f0f0f0f;
        m = (m + (m >>> 8)) & 0x00ff00ff;
        m = (m + (m >>> 16)) & 0x0000ffff;
        return m;
    }
}

###C#

public class Solution {
    const uint EVEN_MASK = 0x55555555, ODD_MASK = 0xaaaaaaaa;

    public int[] EvenOddBit(int n) {
        return new int[]{BitCount((uint) n & EVEN_MASK), BitCount((uint) n & ODD_MASK)};
    }

    public int BitCount(uint m) {
        m = m - ((m >> 1) & 0x55555555);
        m = (m & 0x33333333) + ((m >> 2) & 0x33333333);
        m = (m + (m >> 4)) & 0x0f0f0f0f;
        m = (m + (m >> 8)) & 0x00ff00ff;
        m = (m + (m >> 16)) & 0x0000ffff;
        return (int) m;
    }
}

复杂度分析

  • 时间复杂度:$O(\log \log n)$,其中 $n$ 是给定的正整数。对于 $k$ 位二进制数,$k = O(\log n)$,分治解法的分组合并操作次数是 $O(\log k)$,每次分组合并操作的时间都是 $O(1)$,因此分治解法的时间复杂度是 $O(\log k) = O(\log \log n)$。

  • 空间复杂度:$O(1)$。

【6319. 奇偶位数】题解:按位与

2023年3月20日 16:10

解题思路

1、每次把n右移1位,把n的当前位和1做与运算。

执行结果

捕获.JPG{:width=400}

代码

###java

class Solution {
    public int[] evenOddBit(int n) {
        int[] rs = new int[2];
        // 32位整数
        for (int j = 0; j < 32; j++) {
            // 把n的当前位和1做与运算
            if ((n & 1) == 1) {
                // 当前位为1
                if (j % 2 == 0) {
                    // 偶数下标+1
                    rs[0] += 1;
                } else {
                    // 奇数下标+1
                    rs[1] += 1;
                }
            }
            // 把n右移1位
            n >>= 1;
        }
        return rs;
    }
}

两种方法:从 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站@灵茶山艾府

[Python3/Java/C++/Go/TypeScript] 一题一解:维护最大值和最小值(清晰题解)

作者 lcbin
2025年2月19日 09:01

方法一:维护最大值和最小值

我们注意到,最大距离一定是两个数组中的一个最大值和另一个最小值之间的距离。因此,我们可以维护两个变量 $\textit{mi}$ 和 $\textit{mx}$,分别表示已经遍历过的数组中的最小值和最大值。初始时 $\textit{mi}$ 和 $\textit{mx}$ 分别为第一个数组的第一个元素和最后一个元素。

接下来,我们从第二个数组开始遍历,对于每个数组,我们首先计算当前数组的第一个元素和 $\textit{mx}$ 之间的距离,以及当前数组的最后一个元素和 $\textit{mi}$ 之间的距离,然后更新最大距离。同时,我们更新 $\textit{mi} = \min(\textit{mi}, \textit{arr}[0])$ 和 $\textit{mx} = \max(\textit{mx}, \textit{arr}[\textit{size} - 1])$。

遍历结束后,即可得到最大距离。

###python

class Solution:
    def maxDistance(self, arrays: List[List[int]]) -> int:
        ans = 0
        mi, mx = arrays[0][0], arrays[0][-1]
        for arr in arrays[1:]:
            a, b = abs(arr[0] - mx), abs(arr[-1] - mi)
            ans = max(ans, a, b)
            mi = min(mi, arr[0])
            mx = max(mx, arr[-1])
        return ans

###java

class Solution {
    public int maxDistance(List<List<Integer>> arrays) {
        int ans = 0;
        int mi = arrays.get(0).get(0);
        int mx = arrays.get(0).get(arrays.get(0).size() - 1);
        for (int i = 1; i < arrays.size(); ++i) {
            var arr = arrays.get(i);
            int a = Math.abs(arr.get(0) - mx);
            int b = Math.abs(arr.get(arr.size() - 1) - mi);
            ans = Math.max(ans, Math.max(a, b));
            mi = Math.min(mi, arr.get(0));
            mx = Math.max(mx, arr.get(arr.size() - 1));
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int maxDistance(vector<vector<int>>& arrays) {
        int ans = 0;
        int mi = arrays[0][0], mx = arrays[0][arrays[0].size() - 1];
        for (int i = 1; i < arrays.size(); ++i) {
            auto& arr = arrays[i];
            int a = abs(arr[0] - mx), b = abs(arr[arr.size() - 1] - mi);
            ans = max({ans, a, b});
            mi = min(mi, arr[0]);
            mx = max(mx, arr[arr.size() - 1]);
        }
        return ans;
    }
};

###go

func maxDistance(arrays [][]int) (ans int) {
mi, mx := arrays[0][0], arrays[0][len(arrays[0])-1]
for _, arr := range arrays[1:] {
a, b := abs(arr[0]-mx), abs(arr[len(arr)-1]-mi)
ans = max(ans, max(a, b))
mi = min(mi, arr[0])
mx = max(mx, arr[len(arr)-1])
}
return ans
}

func abs(x int) int {
if x < 0 {
return -x
}
return x
}

###ts

function maxDistance(arrays: number[][]): number {
    let ans = 0;
    let [mi, mx] = [arrays[0][0], arrays[0].at(-1)!];
    for (let i = 1; i < arrays.length; ++i) {
        const arr = arrays[i];
        const a = Math.abs(arr[0] - mx);
        const b = Math.abs(arr.at(-1)! - mi);
        ans = Math.max(ans, a, b);
        mi = Math.min(mi, arr[0]);
        mx = Math.max(mx, arr.at(-1)!);
    }
    return ans;
}

###rust

impl Solution {
    pub fn max_distance(arrays: Vec<Vec<i32>>) -> i32 {
        let mut ans = 0;
        let mut mi = arrays[0][0];
        let mut mx = arrays[0][arrays[0].len() - 1];

        for i in 1..arrays.len() {
            let arr = &arrays[i];
            let a = (arr[0] - mx).abs();
            let b = (arr[arr.len() - 1] - mi).abs();
            ans = ans.max(a).max(b);

            mi = mi.min(arr[0]);
            mx = mx.max(arr[arr.len() - 1]);
        }

        ans
    }
}

###js

/**
 * @param {number[][]} arrays
 * @return {number}
 */
var maxDistance = function (arrays) {
    let ans = 0;
    let [mi, mx] = [arrays[0][0], arrays[0].at(-1)];
    for (let i = 1; i < arrays.length; ++i) {
        const arr = arrays[i];
        const a = Math.abs(arr[0] - mx);
        const b = Math.abs(arr.at(-1) - mi);
        ans = Math.max(ans, a, b);
        mi = Math.min(mi, arr[0]);
        mx = Math.max(mx, arr.at(-1));
    }
    return ans;
};

时间复杂度 $O(m)$,其中 $m$ 为数组的个数。空间复杂度 $O(1)$。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

❌
❌