阅读视图

发现新文章,点击刷新页面。

每日一题-距离字典两次编辑以内的单词🟡

给你两个字符串数组 queries 和 dictionary 。数组中所有单词都只包含小写英文字母,且长度都相同。

一次 编辑 中,你可以从 queries 中选择一个单词,将任意一个字母修改成任何其他字母。从 queries 中找到所有满足以下条件的字符串:不超过 两次编辑内,字符串与 dictionary 中某个字符串相同。

请你返回 queries 中的单词列表,这些单词距离 dictionary 中的单词 编辑次数 不超过 两次 。单词返回的顺序需要与 queries 中原本顺序相同。

 

示例 1:

输入:queries = ["word","note","ants","wood"], dictionary = ["wood","joke","moat"]
输出:["word","note","wood"]
解释:
- 将 "word" 中的 'r' 换成 'o' ,得到 dictionary 中的单词 "wood" 。
- 将 "note" 中的 'n' 换成 'j' 且将 't' 换成 'k' ,得到 "joke" 。
- "ants" 需要超过 2 次编辑才能得到 dictionary 中的单词。
- "wood" 不需要修改(0 次编辑),就得到 dictionary 中相同的单词。
所以我们返回 ["word","note","wood"] 。

示例 2:

输入:queries = ["yes"], dictionary = ["not"]
输出:[]
解释:
"yes" 需要超过 2 次编辑才能得到 "not" 。
所以我们返回空数组。

 

提示:

  • 1 <= queries.length, dictionary.length <= 100
  • n == queries[i].length == dictionary[j].length
  • 1 <= n <= 100
  • 所有 queries[i] 和 dictionary[j] 都只包含小写英文字母。

暴力(Python/Java/C++/C/Go/JS/Rust)

对于每个 $q = \textit{queries}[i]$,遍历 $\textit{dictionary}$ 中的字符串 $s$,判断 $q$ 和 $s$ 是否至多有两个位置上的字母不同。

class Solution:
    def twoEditWords(self, queries: List[str], dictionary: List[str]) -> List[str]:
        ans = []
        for q in queries:
            for s in dictionary:
                if sum(x != y for x, y in zip(q, s)) <= 2:
                    ans.append(q)
                    break
        return ans
class Solution {
    public List<String> twoEditWords(String[] queries, String[] dictionary) {
        List<String> ans = new ArrayList<>();
        for (String q : queries) {
            for (String s : dictionary) {
                int cnt = 0;
                for (int i = 0; i < s.length() && cnt <= 2; i++) {
                    if (q.charAt(i) != s.charAt(i)) {
                        cnt++;
                    }
                }
                if (cnt <= 2) {
                    ans.add(q);
                    break;
                }
            }
        }
        return ans;
    }
}
class Solution {
public:
    vector<string> twoEditWords(vector<string>& queries, vector<string>& dictionary) {
        vector<string> ans;
        for (auto& q : queries) {
            for (auto& s : dictionary) {
                int cnt = 0;
                for (int i = 0; i < s.size() && cnt <= 2; i++) {
                    if (q[i] != s[i]) {
                        cnt++;
                    }
                }
                if (cnt <= 2) {
                    ans.push_back(q);
                    break;
                }
            }
        }
        return ans;
    }
};
char** twoEditWords(char** queries, int queriesSize, char** dictionary, int dictionarySize, int* returnSize) {
    char** ans = malloc(queriesSize * sizeof(char*));
    *returnSize = 0;

    for (int i = 0; i < queriesSize; i++) {
        char* q = queries[i];
        for (int j = 0; j < dictionarySize; j++) {
            char* s = dictionary[j];
            int cnt = 0;
            for (int k = 0; s[k] && cnt <= 2; k++) {
                if (q[k] != s[k]) {
                    cnt++;
                }
            }
            if (cnt <= 2) {
                ans[(*returnSize)++] = q;
                break;
            }
        }
    }

    return ans;
}
func twoEditWords(queries, dictionary []string) (ans []string) {
for _, q := range queries {
next:
for _, s := range dictionary {
cnt := 0
for i := range s {
if q[i] != s[i] {
cnt++
if cnt > 2 {
continue next
}
}
}
ans = append(ans, q)
break
}
}
return
}
var twoEditWords = function(queries, dictionary) {
    const ans = [];
    for (const q of queries) {
        for (const s of dictionary) {
            let cnt = 0;
            for (let i = 0; i < s.length && cnt <= 2; i++) {
                if (q[i] !== s[i]) {
                    cnt++;
                }
            }
            if (cnt <= 2) {
                ans.push(q);
                break;
            }
        }
    }
    return ans;
};
impl Solution {
    pub fn two_edit_words(queries: Vec<String>, dictionary: Vec<String>) -> Vec<String> {
        let mut ans = vec![];
        for q in queries {
            for s in &dictionary {
                let mut cnt = 0;
                for (a, b) in q.bytes().zip(s.bytes()) {
                    if a != b {
                        cnt += 1;
                        if cnt > 2 {
                            break;
                        }
                    }
                }
                if cnt <= 2 {
                    ans.push(q);
                    break;
                }
            }
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(qdn)$,其中 $q$ 是 $\textit{queries}$ 的长度,$d$ 是 $\textit{dictionary}$ 的长度,$n$ 是 $\textit{queries}[i]$ 的长度。题目保证所有字符串长度相等。
  • 空间复杂度:$\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

class Solution:
    def twoEditWords(self, queries: List[str], dictionary: List[str]) -> List[str]:
        def check(x,y):
            t=0
            for i in range(len(x)):
                if x[i]!=y[i]:
                    t+=1
            return t<=2
        covered=set()
        lst=[]
        for i in dictionary:
            for j in range(len(queries)):
                t=queries[j]
                if j not in covered and check(t,i):
                    covered.add(j)
                    lst.append(j)
        lst.sort()
        return [queries[i] for i in lst]

一行不解释

class Solution:
    def twoEditWords(self, Q: List[str], D: List[str], f = lambda w, D: min(sum(a != b for a, b in zip(w, t)) for t in D)) -> List[str]:
        return [w for w in Q if f(w, D) <= 2]

每日一题-执行交换操作后的最小汉明距离🟡

给你两个整数数组 sourcetarget ,长度都是 n 。还有一个数组 allowedSwaps ,其中每个 allowedSwaps[i] = [ai, bi] 表示你可以交换数组 source 中下标为 aibi下标从 0 开始)的两个元素。注意,你可以按 任意 顺序 多次 交换一对特定下标指向的元素。

相同长度的两个数组 sourcetarget 间的 汉明距离 是元素不同的下标数量。形式上,其值等于满足 source[i] != target[i]下标从 0 开始)的下标 i0 <= i <= n-1)的数量。

在对数组 source 执行 任意 数量的交换操作后,返回 sourcetarget 间的 最小汉明距离

 

示例 1:

输入:source = [1,2,3,4], target = [2,1,4,5], allowedSwaps = [[0,1],[2,3]]
输出:1
解释:source 可以按下述方式转换:
- 交换下标 0 和 1 指向的元素:source = [2,1,3,4]
- 交换下标 2 和 3 指向的元素:source = [2,1,4,3]
source 和 target 间的汉明距离是 1 ,二者有 1 处元素不同,在下标 3 。

示例 2:

输入:source = [1,2,3,4], target = [1,3,2,4], allowedSwaps = []
输出:2
解释:不能对 source 执行交换操作。
source 和 target 间的汉明距离是 2 ,二者有 2 处元素不同,在下标 1 和下标 2 。

示例 3:

输入:source = [5,1,2,4,3], target = [1,5,4,2,3], allowedSwaps = [[0,4],[4,2],[1,3],[1,4]]
输出:0

 

提示:

  • n == source.length == target.length
  • 1 <= n <= 105
  • 1 <= source[i], target[i] <= 105
  • 0 <= allowedSwaps.length <= 105
  • allowedSwaps[i].length == 2
  • 0 <= ai, bi <= n - 1
  • ai != bi

建图 + DFS(Python/Java/C++/Go)

在 $a_i$ 和 $b_i$ 之间连边,得到一个无向图 $G$。

$G$ 的同一个连通块中的节点(下标)可以随意交换。

证明(具体交换方案)

例如 $\textit{source}$ 在某个连通块中的元素为 $1,1,2,3$,$\textit{target}$ 在这个连通块中的元素为 $2,2,4,1$,二者有一个共同的 $1$ 和一个共同的 $2$。但 $\textit{source}$ 多了一个 $1$ 和一个 $3$,$\textit{target}$ 多了一个 $2$ 和一个 $4$,每对多出来的元素,无法匹配,会贡献 $1$ 个汉明距离。所以这个连通块贡献了 $2$ 个汉明距离。

用哈希表(或者数组)统计 $\textit{source}$ 和 $\textit{target}$ 各自多出来的元素个数。总个数除以 $2$,即为无法配对的元素对的个数,即汉明距离。

###py

class Solution:
    def minimumHammingDistance(self, source: List[int], target: List[int], allowedSwaps: List[List[int]]) -> int:
        n = len(source)
        g = [[] for _ in range(n)]
        for i, j in allowedSwaps:
            g[i].append(j)  # 建图
            g[j].append(i)

        def dfs(x: int) -> None:
            vis[x] = True  # 避免重复访问
            # 抵消相同的元素,最终剩下 source 和 target 各自多出来的元素(对称差)
            diff[source[x]] += 1
            diff[target[x]] -= 1
            for y in g[x]:
                if not vis[y]:
                    dfs(y)

        vis = [False] * n
        ans = 0
        for x in range(n):
            if not vis[x]:
                diff = defaultdict(int)
                dfs(x)
                ans += sum(map(abs, diff.values()))
        return ans // 2  # 有 ans // 2 对多出来的元素

###java

class Solution {
    public int minimumHammingDistance(int[] source, int[] target, int[][] allowedSwaps) {
        int n = source.length;
        List<Integer>[] g = new ArrayList[n];
        Arrays.setAll(g, _ -> new ArrayList<>());
        for (int[] e : allowedSwaps) {
            int i = e[0];
            int j = e[1];
            g[i].add(j); // 建图
            g[j].add(i);
        }

        boolean[] vis = new boolean[n];
        int ans = 0;
        for (int x = 0; x < n; x++) {
            if (!vis[x]) {
                Map<Integer, Integer> diff = new HashMap<>();
                dfs(x, source, target, g, vis, diff);
                for (int c : diff.values()) {
                    ans += Math.abs(c);
                }
            }
        }
        return ans / 2; // 有 ans / 2 对多出来的元素
    }

    private void dfs(int x, int[] source, int[] target, List<Integer>[] g, boolean[] vis, Map<Integer, Integer> diff) {
        vis[x] = true; // 避免重复访问
        // 抵消相同的元素,最终剩下 source 和 target 各自多出来的元素(对称差)
        diff.merge(source[x], 1, Integer::sum);  // diff[source[x]]++;
        diff.merge(target[x], -1, Integer::sum); // diff[target[x]]--;
        for (int y : g[x]) {
            if (!vis[y]) {
                dfs(y, source, target, g, vis, diff);
            }
        }
    }
}

###cpp

class Solution {
public:
    int minimumHammingDistance(vector<int>& source, vector<int>& target, vector<vector<int>>& allowedSwaps) {
        int n = source.size();
        vector<vector<int>> g(n);
        for (auto& e : allowedSwaps) {
            int i = e[0], j = e[1];
            g[i].push_back(j); // 建图
            g[j].push_back(i);
        }

        vector<int8_t> vis(n);
        unordered_map<int, int> diff;

        auto dfs = [&](this auto&& dfs, int x) -> void {
            vis[x] = true; // 避免重复访问
            // 抵消相同的元素,最终剩下 source 和 target 各自多出来的元素(对称差)
            diff[source[x]]++;
            diff[target[x]]--;
            for (int y : g[x]) {
                if (!vis[y]) {
                    dfs(y);
                }
            }
        };

        int ans = 0;
        for (int x = 0; x < n; x++) {
            if (!vis[x]) {
                diff.clear();
                dfs(x);
                for (auto& [_, c] : diff) {
                    ans += abs(c);
                }
            }
        }
        return ans / 2; // 有 ans / 2 对多出来的元素
    }
};

###go

func minimumHammingDistance(source []int, target []int, allowedSwaps [][]int) (ans int) {
n := len(source)
g := make([][]int, n)
for _, e := range allowedSwaps {
i, j := e[0], e[1]
g[i] = append(g[i], j) // 建图
g[j] = append(g[j], i)
}

vis := make([]bool, n)
diff := map[int]int{}

var dfs func(int)
dfs = func(x int) {
vis[x] = true // 避免重复访问
// 抵消相同的元素,最终剩下 source 和 target 各自多出来的元素(对称差)
diff[source[x]]++
diff[target[x]]--
for _, y := range g[x] {
if !vis[y] {
dfs(y)
}
}
}

for x, b := range vis {
if !b {
clear(diff)
dfs(x)
for _, c := range diff {
ans += abs(c)
}
}
}
return ans / 2 // 有 ans / 2 对多出来的元素
}

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

复杂度分析

  • 时间复杂度:$\mathcal{O}(n + m)$,其中 $n$ 是 $\textit{source}$ 的长度,$m$ 是 $\textit{allowedSwaps}$ 的长度。
  • 空间复杂度:$\mathcal{O}(n + m)$。

相似题目

分类题单

如何科学刷题?

  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 并查集+哈希表

解题思路

  1. 参数定义
    • parent:并查集数组,每个节点指向根节点
    • dic:哈希表,k,v分别为并查集中每个簇的根节点和簇中所有元素的索引
    • res:返回值
  2. 思路
    • 示例:如果allowedSwaps[[0,1],[1,2]],说明索引0/11/2处的元素可以交换,根据连通性,0/2处的元素同样可以交换,此时0/1/2构成的连通块之间相互可以交换,所以可以利用并查集计算出每一个连通块。
    • 首先确定每个元素对应的根节点,利用哈希表dic存储根节点对应的所有元素的索引。
    • 遍历dick,v分别为根节点和簇元素索引集合,找到索引集合vsourcetarget中对应的元素a,b,利用ab中查找差集,所以b可以为哈希表,记录了每个元素的个数,便于O(1)时间复杂度查找。如果在b中没有找到a中的元素,res+=1。求两个数组的交集也可以用哈希表的&运算,见代码二
  3. 复杂度分析
    • 时间复杂度:O(N+M)Nsourcetarget的长度,MallowedSwaps长度,路径压缩时间α(N)很小可忽略
    • 空间复杂度:O(N)

代码

###python3

class Solution:
    def minimumHammingDistance(self, source: List[int], target: List[int], allowedSwaps: List[List[int]]) -> int:
        n=len(source)
        parent={i:i for i in range(n)}
        # 并查集
        def find(x):
            if x!=parent[x]:
                parent[x]=find(parent[x])
            return parent[x]
        # 搜索根节点
        for l,r in allowedSwaps:
            a,b=find(l),find(r)
            if a!=b:
                parent[b]=a
        # 获取根节点对应的连通块
        dic=collections.defaultdict(list)
        for i in range(n):
            a=find(i)
            dic[a].append(i)
        res=0
        # 计算每个连通块对应的source元素与target的差集
        for k,v in dic.items():
            a=[source[i] for i in v]
            b=Counter([target[i] for i in v])
            for c in a:
                if b[c]>0:
                    b[c]-=1
                else:
                    res+=1
        return res

###python3

class Solution:
    def minimumHammingDistance(self, source: List[int], target: List[int], allowedSwaps: List[List[int]]) -> int:
        n=len(source)
        parent={i:i for i in range(n)}
        # 并查集
        def find(x):
            if x!=parent[x]:
                parent[x]=find(parent[x])
            return parent[x]
        # 搜索根节点
        for l,r in allowedSwaps:
            a,b=find(l),find(r)
            if a!=b:
                parent[b]=a
        # 获取根节点对应的连通块
        dic=collections.defaultdict(list)
        for i in range(n):
            a=find(i)
            dic[a].append(i)
        # 计算每个连通块对应的source元素与target的差集
        count=0
        for k,v in dic.items():
            a=Counter([source[i] for i in v])
            b=Counter([target[i] for i in v])
            count+=len(list((a&b).elements()))
        return n-count

【并查集】要稍微动点脑筋

根据题意不难注意到,如果 $1,2$ 之间可以交换,$2,3$ 之间可以交换,那么即使 $[1,3]$ 未出现在 $\textit{allowedSwaps}$ 数组中,$1,3$ 之间也是可以交换的。

因此,我们使用并查集来维护这一关系:对于 $\textit{source}$ 数组中两个任意的位置 $i,j$,如果 $i,j$ 在同一个联通分支里,那么 $i,j$ 之间就是可以交换的。于是,需要首先遍历 $\textit{allowedSwaps}$ 数组中所有元素,从而构建 $\textit{source}$ 数组中位置之间的联通关系。

对于任意的联通分支 $k$,由于它们内部的位置之间可以任意交换,因此它们初始出现在 $\textit{source}$ 数组中的顺序并不重要。故我们为每个联通分支 $k$ 维护 $\textit{source}$ 中对应位置元素的集合,以及 $\textit{target}$ 中对应位置元素的集合。随后,汉明距离的最小值,就是这两个集合之间不同的元素的数量。

那么两个集合之间不同元素的数量呢?我们遍历第一个集合中的每个元素:

  • 如果该元素出现在第二个集合中,就将该元素从第二个集合中删除
  • 否则,如果没有出现,则将计数器加 $1$

值得注意的是,在本题中,我们需要允许集合中的元素出现重复。在下面的代码中,我们使用 C++ 中的 unordered_multiset 数据结构。

class Solution {
public:
    int getf(vector<int>& f, int x) {
        if (f[x] == x) return x;
        int nf = getf(f, f[x]);
        f[x] = nf;
        return nf;
    }
    
    void add(vector<int>& f, int x, int y) {
        int fx = getf(f, x);
        int fy = getf(f, y);
        f[fx] = fy;
    }
    
    int minimumHammingDistance(vector<int>& source, vector<int>& target, vector<vector<int>>& allowedSwaps) {
        int n = source.size();
        vector<int> f(n);
        for (int i = 0; i < n; i++) {
            f[i] = i;
        }
        for (const auto& e: allowedSwaps) {
            add(f, e[0], e[1]);
        }
        
        unordered_map<int, unordered_multiset<int>> s, t; // 为每个联通分支维护位置的集合
        for (int i = 0; i < n; i++) {
            int fa = getf(f, i);
            s[fa].insert(source[i]);
            t[fa].insert(target[i]);
        }
        
        int ret = 0;
        for (int i = 0; i < n; i++) {
            if (s.find(i) == s.end()) continue;
            for (int x: s[i]) {
                if (t[i].find(x) == t[i].end()) {
                    ret++;
                } else {
                    // 不能使用 t[i].erase(x),不然会删掉所有的 x
                    t[i].erase(t[i].find(x));
                }
            }
        }
        return ret;
    }
};

每日一题-两栋颜色不同且距离最远的房子🟢

街上有 n 栋房子整齐地排成一列,每栋房子都粉刷上了漂亮的颜色。给你一个下标从 0 开始且长度为 n 的整数数组 colors ,其中 colors[i] 表示第  i 栋房子的颜色。

返回 两栋 颜色 不同 房子之间的 最大 距离。

i 栋房子和第 j 栋房子之间的距离是 abs(i - j) ,其中 abs(x)x 的绝对值。

 

示例 1:

输入:colors = [1,1,1,6,1,1,1]
输出:3
解释:上图中,颜色 1 标识成蓝色,颜色 6 标识成红色。
两栋颜色不同且距离最远的房子是房子 0 和房子 3 。
房子 0 的颜色是颜色 1 ,房子 3 的颜色是颜色 6 。两栋房子之间的距离是 abs(0 - 3) = 3 。
注意,房子 3 和房子 6 也可以产生最佳答案。

示例 2:

输入:colors = [1,8,3,8,3]
输出:4
解释:上图中,颜色 1 标识成蓝色,颜色 8 标识成黄色,颜色 3 标识成绿色。
两栋颜色不同且距离最远的房子是房子 0 和房子 4 。
房子 0 的颜色是颜色 1 ,房子 4 的颜色是颜色 3 。两栋房子之间的距离是 abs(0 - 4) = 4 。

示例 3:

输入:colors = [0,1]
输出:1
解释:两栋颜色不同且距离最远的房子是房子 0 和房子 1 。
房子 0 的颜色是颜色 0 ,房子 1 的颜色是颜色 1 。两栋房子之间的距离是 abs(0 - 1) = 1 。

 

提示:

  • n == colors.length
  • 2 <= n <= 100
  • 0 <= colors[i] <= 100
  • 生成的测试数据满足 至少 存在 2 栋颜色不同的房子

[知心猛男] 详解一下贪心

题意就是找数列中距离最远的两个不同的数字, 显然我们有时间复杂度O(N)的解法:

考虑最终答案的长度:
显然答案最大可能长度是n-1,即:首尾不同
如果不是n-1,那就是首尾相同,则最大可能长度就是n-2,即:尾-1和首不同 或者 首+1和尾不同
如果也不是n-2,那就是首尾和首+1和尾-1位置的数都相同,则最大可能长度就是n-3, 即首+2和尾不同 或者 尾-2和首不同
如果也不是n-3,那就是……
……
依次类推,我们发现从最终可能答案考虑,用两个指针 i 和 j 同时从两端向中间移动,每次各移动一步,当出现和首或者尾不同的数字的时候,最大可能长度就出现了:n-1-i 或者 j,因为他们是相等的(i+j==n-1)。所以直接break输出即可。

###cpp

class Solution {
public:
    int maxDistance(vector<int>& colors) {
        int n = colors.size();        
        int i, j;
        for(i = 0, j = n-1; i < j; ++i, --j) //由于一定存在不同的颜色,所以当i==j时候一定就是不同的颜色
            if(colors[i] != colors[n-1] || colors[j] != colors[0]) break;
        return j;
    }
};

两种方法(暴力 / 贪心)【Java】

方法1

  • 暴力
  • 使用两个for,比较判断0~length-1、1~length-1、2~length-1...找到最大的值

代码

###java

class Solution {
    public int maxDistance(int[] colors) {
        int length = colors.length;
        int max = -1;
        
        for (int i = 0; i < length; i++) {
            for (int j = length-1; j > 0; j--) {
                if (colors[i] != colors[j] && j-i > max) {
                    max = j-i;
                }
            }
        }
        
        return max;
    }
}

方法2

  • 贪心
  • 因为要最大,所以有三种情况:
    1. 首尾不相同直接返回,否则说明首尾是相同的颜色
    2. “0~右往左第一个不相同”这一段
    3. “左往右第一个不相同~length-1”这一段
    4. 然后比较这两个谁大就行
      image.png

代码

###java

class Solution {
    public int maxDistance(int[] colors) {
        int length = colors.length;

        // 如果首位颜色不同直接返回
        if (colors[0] != colors[length - 1]) {
            return length - 1;
        }
        
        // 获取左边第一个不相同的位置
        int left = 1;
        while (colors[left] == colors[0]) {
            left += 1;
        }
        // 获取右边第一个不相同的位置
        int right = length - 2;
        while (colors[right] == colors[length - 1]) {
            right -= 1;
        }

        // 0~right 的长度 和 left~length-1 的长度取最大值
        // 因为要最大,所以不可能在中间,要么就是左边,要么就是右边
        return Math.max(right, length - 1 - left);
    }
}

O(n) 做法,脑筋急转弯(Python/Java/C++/C/Go/JS/Rust)

如果 $\textit{colors}[0] \ne \textit{colors}[n-1]$,那么答案是 $n-1$。

否则设 $c = \textit{colors}[0] = \textit{colors}[n-1]$。

设最大距离来自房子 $i$ 和房子 $j$。由于题目要求 $\textit{colors}[i]\ne \textit{colors}[j]$,所以这两个颜色不可能都等于 $c$。如果 $\textit{colors}[i]\ne c$,那么 $j$ 必然是离 $i$ 最远的 $0$ 或者 $n-1$(这两栋房子的颜色与房子 $i$ 不同)。这意味着,$0$ 或者 $n-1$ 必然参与最大距离的计算

  • 对于房子 $0$ 来说,另一栋房子越远(越靠右)越好。从右往左找到颜色不等于 $c$ 的房子 $\textit{colors}[r]$,距离为 $r-0 = r$。
  • 对于房子 $n-1$ 来说,另一栋房子越远(越靠左)越好。从左往右找到颜色不等于 $c$ 的房子 $\textit{colors}[\ell]$,距离为 $n-1-\ell$。

答案为二者的最大值

$$
\max(r, n-1-\ell)
$$

注意题目保证至少有两栋颜色不同的房子。

class Solution:
    def maxDistance(self, colors: List[int]) -> int:
        n = len(colors)
        c = colors[0]
        if c != colors[-1]:
            return n - 1

        # 找最右边的颜色不等于 c 的房子
        # 题目保证至少有两栋颜色不同的房子
        r = n - 2
        while colors[r] == c:
            r -= 1

        # 找最左边的颜色不等于 c 的房子
        l = 1
        while colors[l] == c:
            l += 1

        return max(r, n - 1 - l)
class Solution {
    public int maxDistance(int[] colors) {
        int n = colors.length;
        int c = colors[0];
        if (c != colors[n - 1]) {
            return n - 1;
        }

        // 找最右边的颜色不等于 c 的房子
        // 题目保证至少有两栋颜色不同的房子
        int r = n - 2;
        while (colors[r] == c) {
            r--;
        }

        // 找最左边的颜色不等于 c 的房子
        int l = 1;
        while (colors[l] == c) {
            l++;
        }

        return Math.max(r, n - 1 - l);
    }
}
class Solution {
public:
    int maxDistance(vector<int>& colors) {
        int n = colors.size();
        int c = colors[0];
        if (c != colors[n - 1]) {
            return n - 1;
        }

        // 找最右边的颜色不等于 c 的房子
        // 题目保证至少有两栋颜色不同的房子
        int r = n - 2;
        while (colors[r] == c) {
            r--;
        }

        // 找最左边的颜色不等于 c 的房子
        int l = 1;
        while (colors[l] == c) {
            l++;
        }

        return max(r, n - 1 - l);
    }
};
int maxDistance(int* colors, int colorsSize) {
    int n = colorsSize;
    int c = colors[0];
    if (c != colors[n - 1]) {
        return n - 1;
    }

    // 找最右边的颜色不等于 c 的房子
    // 题目保证至少有两栋颜色不同的房子
    int r = n - 2;
    while (colors[r] == c) {
        r--;
    }

    // 找最左边的颜色不等于 c 的房子
    int l = 1;
    while (colors[l] == c) {
        l++;
    }

    return MAX(r, n - 1 - l);
}
func maxDistance(colors []int) int {
n := len(colors)
c := colors[0]
if c != colors[n-1] {
return n - 1
}

// 找最右边的颜色不等于 c 的房子
// 题目保证至少有两栋颜色不同的房子
r := n - 2
for colors[r] == c {
r--
}

// 找最左边的颜色不等于 c 的房子
l := 1
for colors[l] == c {
l++
}

return max(r, n-1-l)
}
var maxDistance = function(colors) {
    const n = colors.length;
    const c = colors[0];
    if (c !== colors[n - 1]) {
        return n - 1;
    }

    // 找最右边的颜色不等于 c 的房子
    // 题目保证至少有两栋颜色不同的房子
    let r = n - 2;
    while (colors[r] === c) {
        r--;
    }

    // 找最左边的颜色不等于 c 的房子
    let l = 1;
    while (colors[l] === c) {
        l++;
    }

    return Math.max(r, n - 1 - l);
};
impl Solution {
    pub fn max_distance(colors: Vec<i32>) -> i32 {
        let n = colors.len();
        let c = colors[0];
        if c != colors[n - 1] {
            return (n - 1) as _;
        }

        // 找最右边的颜色不等于 c 的房子
        // 题目保证至少有两栋颜色不同的房子
        let mut r = n - 2;
        while colors[r] == c {
            r -= 1;
        }

        // 找最左边的颜色不等于 c 的房子
        let mut l = 1;
        while colors[l] == c {
            l += 1;
        }

        r.max(n - 1 - l) as _
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{colors}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

专题训练

见下面贪心与思维题单的「§5.2 脑筋急转弯」。

分类题单

如何科学刷题?

  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站@灵茶山艾府

每日一题-下标对中的最大距离🟡

给你两个 非递增 的整数数组 nums1nums2 ,数组下标均 从 0 开始 计数。

下标对 (i, j)0 <= i < nums1.length0 <= j < nums2.length 。如果该下标对同时满足 i <= jnums1[i] <= nums2[j] ,则称之为 有效 下标对,该下标对的 距离j - i

返回所有 有效 下标对 (i, j) 中的 最大距离 。如果不存在有效下标对,返回 0

一个数组 arr ,如果每个 1 <= i < arr.length 均有 arr[i-1] >= arr[i] 成立,那么该数组是一个 非递增 数组。

 

示例 1:

输入:nums1 = [55,30,5,4,2], nums2 = [100,20,10,10,5]
输出:2
解释:有效下标对是 (0,0), (2,2), (2,3), (2,4), (3,3), (3,4) 和 (4,4) 。
最大距离是 2 ,对应下标对 (2,4) 。

示例 2:

输入:nums1 = [2,2,2], nums2 = [10,10,1]
输出:1
解释:有效下标对是 (0,0), (0,1) 和 (1,1) 。
最大距离是 1 ,对应下标对 (0,1) 。

示例 3:

输入:nums1 = [30,29,19,5], nums2 = [25,25,25,25,25]
输出:2
解释:有效下标对是 (2,2), (2,3), (2,4), (3,3) 和 (3,4) 。
最大距离是 2 ,对应下标对 (2,4) 。

 

提示:

  • 1 <= nums1.length <= 105
  • 1 <= nums2.length <= 105
  • 1 <= nums1[i], nums2[j] <= 105
  • nums1nums2 都是 非递增 数组

下标对中的最大距离

方法一:双指针

提示 $1$

考虑遍历下标对中的某一个下标,并寻找此时所有有效坐标对中距离最大的另一个下标。暴力遍历另一个下标显然不满足时间复杂度要求,此时是否存在一些可以优化寻找过程的性质?

思路与算法

不失一般性,我们遍历 $\textit{nums}_2$ 中的下标 $j$,同时寻找此时符合要求的 $\textit{nums}_1$ 中最小的下标 $i$。

假设下标 $j$ 对应的最小下标为 $i$,当 $j$ 变为 $j + 1$ 时,由于 $\textit{nums}_2$ 非递增,即 $\textit{nums}_2[j] \ge \textit{nums}_2[j+1]$,那么 $\textit{nums}_1$ 中可取元素的上界不会增加。同时由于 $\textit{nums}_1$ 也非递增,因此 $j + 1$ 对应的最小下标 $i'$ 一定满足 $i' \ge i$。

那么我们就可以在遍历 $j$ 的同时维护对应的 $i$,并用 $\textit{res}$ 来维护下标对 $(i, j)$ 的最大距离。我们将 $\textit{res}$ 初值置为 $0$,这样即使存在 $\textit{nums}_1[i] \le \textit{nums}_2[j]$ 但 $i > j$ 这种不符合要求的情况,由于此时距离为负因而不会对结果产生影响(不存在时也返回 $0$)。

另外,在维护最大距离的时候要注意下标 $i$ 的合法性,即 $i < n_1$,其中 $n_1$ 为 $\textit{nums}_1$ 的长度。

代码

###C++

class Solution {
public:
    int maxDistance(vector<int>& nums1, vector<int>& nums2) {
        int n1 = nums1.size();
        int n2 = nums2.size();
        int i = 0;
        int res = 0;
        for (int j = 0; j < n2; ++j){
            while (i < n1 && nums1[i] > nums2[j]){
                ++i;
            }
            if (i < n1){
                res = max(res, j - i);
            }
        }
        return res;
    }
};

###Python

class Solution:
    def maxDistance(self, nums1: List[int], nums2: List[int]) -> int:
        n1, n2 = len(nums1), len(nums2)
        i = 0
        res = 0
        for j in range(n2):
            while i < n1 and nums1[i] > nums2[j]:
                i += 1
            if i < n1:
                res = max(res, j - i)
        return res

###Java

class Solution {
    public int maxDistance(int[] nums1, int[] nums2) {
        int n1 = nums1.length;
        int n2 = nums2.length;
        int i = 0;
        int res = 0;
        
        for (int j = 0; j < n2; j++) {
            while (i < n1 && nums1[i] > nums2[j]) {
                i++;
            }
            if (i < n1) {
                res = Math.max(res, j - i);
            }
        }
        
        return res;
    }
}

###C#

public class Solution {
    public int MaxDistance(int[] nums1, int[] nums2) {
        int n1 = nums1.Length;
        int n2 = nums2.Length;
        int i = 0;
        int res = 0;
        
        for (int j = 0; j < n2; j++) {
            while (i < n1 && nums1[i] > nums2[j]) {
                i++;
            }
            if (i < n1) {
                res = Math.Max(res, j - i);
            }
        }
        
        return res;
    }
}

###Go

func maxDistance(nums1 []int, nums2 []int) int {
    n1 := len(nums1)
    n2 := len(nums2)
    i := 0
    res := 0
    
    for j := 0; j < n2; j++ {
        for i < n1 && nums1[i] > nums2[j] {
            i++
        }
        if i < n1 {
            if j-i > res {
                res = j - i
            }
        }
    }
    
    return res
}

###C

int maxDistance(int* nums1, int nums1Size, int* nums2, int nums2Size) {
    int i = 0;
    int res = 0;
    
    for (int j = 0; j < nums2Size; j++) {
        while (i < nums1Size && nums1[i] > nums2[j]) {
            i++;
        }
        if (i < nums1Size) {
            if (j - i > res) {
                res = j - i;
            }
        }
    }
    
    return res;
}

###JavaScript

var maxDistance = function(nums1, nums2) {
    const n1 = nums1.length;
    const n2 = nums2.length;
    let i = 0;
    let res = 0;
    
    for (let j = 0; j < n2; j++) {
        while (i < n1 && nums1[i] > nums2[j]) {
            i++;
        }
        if (i < n1) {
            res = Math.max(res, j - i);
        }
    }
    
    return res;
};

###TypeScript

function maxDistance(nums1: number[], nums2: number[]): number {
    const n1 = nums1.length;
    const n2 = nums2.length;
    let i = 0;
    let res = 0;
    
    for (let j = 0; j < n2; j++) {
        while (i < n1 && nums1[i] > nums2[j]) {
            i++;
        }
        if (i < n1) {
            res = Math.max(res, j - i);
        }
    }
    
    return res;
};

###Rust

impl Solution {
    pub fn max_distance(nums1: Vec<i32>, nums2: Vec<i32>) -> i32 {
        let n1 = nums1.len();
        let n2 = nums2.len();
        let mut i = 0;
        let mut res = 0;
        
        for j in 0..n2 {
            while i < n1 && nums1[i] > nums2[j] {
                i += 1;
            }
            if i < n1 {
                res = res.max((j as i32) - (i as i32));
            }
        }
        
        res
    }
}

复杂度分析

  • 时间复杂度:$O(n_1 + n_2)$,其中 $n_1, n_2$ 分别为 $\textit{nums}_1$ 与 $\textit{nums}_2$ 的长度。在双指针寻找最大值的过程中,我们最多会遍历两个数组各一次。

  • 空间复杂度:$O(1)$,我们使用了常数个变量进行遍历。

java 经典双指针

双指针p1、p2指向两数组的首元素,从左向右遍历。
因为i <= j 且 nums1[i] <= nums2[j]才有效,所以nums1[p1] > nums2[p2]无效,并且p1要始终保持<=p2,
所以如果p1 == p2的时候,两个指针都向后移动一格,否则p2不动p1向后移动

class Solution {
   public int maxDistance(int[] nums1, int[] nums2) {
        int p1 = 0;
        int p2 = 0;
        int res = 0;
        while (p1 < nums1.length && p2 <nums2.length){
            if(nums1[p1] > nums2[p2]){  //无效
                if(p1 == p2){
                    p1++;
                    p2++;
                }else p1++;
            }else {     //有效
                res =Math.max(res,p2-p1);
                p2++;
            }
        }
        return res;
    }
}

双指针(Python/Java/C++/C/Go/JS/Rust)

枚举 $j$,随着 $j$ 的变大,$\textit{nums}_2[j]$ 变小,满足要求的最小的 $i$ 随之变大。

所以可以用双指针做:如果 $j$ 变大后,发现 $\textit{nums}_1[i] > \textit{nums}_2[j]$,那么增大 $i$,直到 $i=n$ 或者 $\textit{nums}_1 \le \textit{nums}_2[j]$ 为止。然后用 $j-i$ 更新答案的最大值(答案初始为 $0$)。

无需担心 $j-i < 0$,这不会影响答案。

class Solution:
    def maxDistance(self, nums1: List[int], nums2: List[int]) -> int:
        ans = i = 0
        for j, y in enumerate(nums2):
            while i < len(nums1) and nums1[i] > y:
                i += 1
            if i == len(nums1):
                break
            ans = max(ans, j - i)
        return ans
class Solution {
    public int maxDistance(int[] nums1, int[] nums2) {
        int ans = 0;
        int i = 0;
        for (int j = 0; j < nums2.length; j++) {
            while (i < nums1.length && nums1[i] > nums2[j]) {
                i++;
            }
            if (i == nums1.length) {
                break;
            }
            ans = Math.max(ans, j - i);
        }
        return ans;
    }
}
class Solution {
public:
    int maxDistance(vector<int>& nums1, vector<int>& nums2) {
        int ans = 0;
        int i = 0;
        for (int j = 0; j < nums2.size(); j++) {
            while (i < nums1.size() && nums1[i] > nums2[j]) {
                i++;
            }
            if (i == nums1.size()) {
                break;
            }
            ans = max(ans, j - i);
        }
        return ans;
    }
};
int maxDistance(int* nums1, int nums1Size, int* nums2, int nums2Size) {
    int ans = 0;
    int i = 0;
    for (int j = 0; j < nums2Size; j++) {
        int y = nums2[j];
        while (i < nums1Size && nums1[i] > y) {
            i++;
        }
        if (i == nums1Size) {
            break;
        }
        ans = MAX(ans, j - i);
    }
    return ans;
}
func maxDistance(nums1, nums2 []int) (ans int) {
i := 0
for j, y := range nums2 {
for i < len(nums1) && nums1[i] > y {
i++
}
if i == len(nums1) {
break
}
ans = max(ans, j-i)
}
return
}
var maxDistance = function(nums1, nums2) {
    let ans = 0;
    let i = 0;
    for (let j = 0; j < nums2.length; j++) {
        while (i < nums1.length && nums1[i] > nums2[j]) {
            i++;
        }
        if (i === nums1.length) {
            break;
        }
        ans = Math.max(ans, j - i);
    }
    return ans;
};
impl Solution {
    pub fn max_distance(nums1: Vec<i32>, nums2: Vec<i32>) -> i32 {
        let mut ans = 0;
        let mut i = 0;
        for (j, y) in nums2.into_iter().enumerate() {
            while i < nums1.len() && nums1[i] > y {
                i += 1;
            }
            if i == nums1.len() {
                break;
            }
            ans = ans.max(j as i32 - i as i32);
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n+m)$,其中 $n$ 是 $\textit{nums}_1$ 的长度,$m$ 是 $\textit{nums}_2$ 的长度。虽然我们写了个二重循环,但 $i$ 最多自增 $n$ 次,所以二重循环的循环次数至多为 $n+m$。
  • 空间复杂度:$\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站@灵茶山艾府

每日一题-整数的镜像距离🟢

给你一个整数 n

定义它的 镜像距离 为:abs(n - reverse(n)),其中 reverse(n) 表示将 n 的数字反转后形成的整数。

返回表示 n 的镜像距离的整数。

其中,abs(x) 表示 x 的绝对值。

 

示例 1:

输入: n = 25

输出: 27

解释:

  • reverse(25) = 52
  • 因此,答案为 abs(25 - 52) = 27

示例 2:

输入: n = 10

输出: 9

解释:

  • reverse(10) = 01,即 1。
  • 因此,答案为 abs(10 - 1) = 9

示例 3:

输入: n = 7

输出: 0

解释:

  • reverse(7) = 7
  • 因此,答案为 abs(7 - 7) = 0

 

提示:

  • 1 <= n <= 109

3783. 整数的镜像距离

解法

思路和算法

整数 $n$ 反转的结果为 $\textit{reverse}(n)$,计算 $|n - \textit{reverse}(n)|$ 即可。

计算 $\textit{reverse}(n)$ 的方法为:从低到高遍历整数 $n$ 的每一位,将每一位根据遍历顺序从高到低填入反转后的数字。

代码

###Java

class Solution {
    public int mirrorDistance(int n) {
        return Math.abs(n - reverse(n));
    }

    public int reverse(int n) {
        int reversed = 0;
        while (n != 0) {
            reversed = reversed * 10 + n % 10;
            n /= 10;
        }
        return reversed;
    }
}

###C#

public class Solution {
    public int MirrorDistance(int n) {
        return Math.Abs(n - Reverse(n));
    }

    public int Reverse(int n) {
        int reversed = 0;
        while (n != 0) {
            reversed = reversed * 10 + n % 10;
            n /= 10;
        }
        return reversed;
    }
}

###C++

class Solution {
public:
    int mirrorDistance(int n) {
        return abs(n - reverse(n));
    }

    int reverse(int n) {
        int reversed = 0;
        while (n) {
            reversed = reversed * 10 + n % 10;
            n /= 10;
        }
        return reversed;
    }
};

###Python

class Solution:
    def mirrorDistance(self, n: int) -> int:
        def reverse(n: int) -> int:
            reversed = 0
            while n:
                reversed = reversed * 10 + n % 10
                n //= 10
            return reversed
        return abs(n - reverse(n))

###C

int reverse(int n) {
    int reversed = 0;
    while (n) {
        reversed = reversed * 10 + n % 10;
        n /= 10;
    }
    return reversed;
}

int mirrorDistance(int n) {
    return abs(n - reverse(n));
}

###Go

func mirrorDistance(n int) int {
    return abs(n - reverse(n))
}

func abs(n int) int {
    if n >= 0 {
        return n
    } else {
        return -n
    }
}

func reverse(n int) int {
    reversed := 0
    for n != 0 {
        reversed = reversed * 10 + n % 10
        n /= 10
    }
    return reversed
}

###JavaScript

var mirrorDistance = function(n) {
    return Math.abs(n - reverse(n));
};

var reverse = function(n) {
    let reversed = 0;
    while (n) {
        reversed = reversed * 10 + n % 10;
        n = Math.floor(n / 10);
    }
    return reversed;
};

###TypeScript

function mirrorDistance(n: number): number {
    return Math.abs(n - reverse(n));
};

function reverse(n: number): number {
    let reversed = 0;
    while (n) {
        reversed = reversed * 10 + n % 10;
        n = Math.floor(n / 10);
    }
    return reversed;
};

复杂度分析

  • 时间复杂度:$O(\log_{10} n)$,其中 $n$ 是给定的数字。整数 $n$ 的位数是 $O(\log_{10} n)$,需要遍历整数 $n$ 的每一位。

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

❌