阅读视图

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

3433. 统计用户被提及情况

解法

思路和算法

由于事件发生顺序为时间戳递增顺序,因此应将数组 $\textit{events}$ 按时间戳升序排序,然后遍历数组 $\textit{events}$,根据每个事件更新用户的相应状态。由于相同时间戳的离线和上线事件在消息事件之前处理,因此当存在多个事件的时间戳相同时,离线事件应在消息事件之前。

由于消息事件的提及用户的情况包括提及给定集合的用户、提及所有用户和提及所有在线用户,因此需要判断消息事件发生时每个用户的在线状态,可以通过维护每个用户的最新在线起始时间判断每个用户的在线状态。创建长度为 $\textit{numberOfUsers}$ 的数组 $\textit{onlineTimes}$,其中 $\textit{onlineTimes}[i]$ 表示编号 $i$ 的用户的最新在线起始时间。由于初始时所有用户都处于在线状态且所有事件的时间戳都是正整数,因此将数组 $\textit{onlineTimes}$ 中的所有元素都初始化为 $0$。

创建长度为 $\textit{numberOfUsers}$ 的结果数组 $\textit{mentions}$。遍历排序后的数组 $\textit{events}$,对于遍历到的每个事件 $[\textit{type}, \textit{timestamp}, \textit{users}]$,判断其类型,执行相应的操作。

  • 如果 $\textit{types} = \text{``MESSAGE"}$,则当前事件是消息事件,根据 $\textit{users}$ 的值决定需要提及的用户,执行提及操作。

    • 当 $\textit{users} = \text{``ALL"}$ 时,提及所有用户,将数组 $\textit{mentions}$ 中的所有元素值都增加 $1$。

    • 当 $\textit{users} = \text{``HERE"}$ 时,提及所有在线用户,遍历 $0 \le i < \textit{numberOfUsers}$ 的每个用户编号 $i$,如果 $\textit{onlineTimes}[i] \le \textit{timestamp}$,则编号 $i$ 的用户被提及,将 $\textit{mentions}[i]$ 的值增加 $1$。

    • 其余情况下,将 $\textit{users}$ 使用空格分隔转换成数组,遍历数组中的每个用户编号,将用户编号对应的数组 $\textit{mentions}$ 中的元素值增加 $1$。

  • 如果 $\textit{types} = \text{``OFFLINE"}$,则当前事件是离线事件,用 $\textit{id}$ 表示 $\textit{users}$ 对应的用户编号,则编号为 $\textit{id}$ 的用户在时间戳 $\textit{timestamp}$ 离线,并将在时间戳 $\textit{timestamp} + 60$ 自动上线,因此将 $\textit{onlineTimes}[\textit{id}]$ 的值更新为 $\textit{timestamp} + 60$。

遍历结束之后,结果数组 $\textit{mentions}$ 即为每个用户被提及的次数。

代码

###Java

class Solution {
    static final String MESSAGE = "MESSAGE", OFFLINE = "OFFLINE", ALL = "ALL", HERE = "HERE";
    static final int OFFLINE_TIME = 60;

    public int[] countMentions(int numberOfUsers, List<List<String>> events) {
        Collections.sort(events, (a, b) -> {
            int aTimestamp = Integer.parseInt(a.get(1)), bTimestamp = Integer.parseInt(b.get(1));
            if (aTimestamp != bTimestamp) {
                return aTimestamp - bTimestamp;
            } else {
                String aType = a.get(0), bType = b.get(0);
                return bType.compareTo(aType);
            }
        });
        int[] mentions = new int[numberOfUsers];
        int[] onlineTimes = new int[numberOfUsers];
        for (List<String> ev : events) {
            String type = ev.get(0);
            int timestamp = Integer.parseInt(ev.get(1));
            String users = ev.get(2);
            if (MESSAGE.equals(type)) {
                if (ALL.equals(users)) {
                    updateMentions(mentions, onlineTimes, Integer.MAX_VALUE);
                } else if (HERE.equals(users)) {
                    updateMentions(mentions, onlineTimes, timestamp);
                } else {
                    String[] ids = users.split(" ");
                    for (String idStr : ids) {
                        int id = Integer.parseInt(idStr.substring(2));
                        mentions[id]++;
                    }
                }
            } else {
                int id = Integer.parseInt(users);
                onlineTimes[id] = timestamp + OFFLINE_TIME;
            }
        }
        return mentions;
    }

    public void updateMentions(int[] mentions, int[] onlineTimes, int timestamp) {
        int numberOfUsers = mentions.length;
        for (int i = 0; i < numberOfUsers; i++) {
            if (onlineTimes[i] <= timestamp) {
                mentions[i]++;
            }
        }
    }
}

###C#

public class Solution {
    const string MESSAGE = "MESSAGE", OFFLINE = "OFFLINE", ALL = "ALL", HERE = "HERE";
    const int OFFLINE_TIME = 60;

    public int[] CountMentions(int numberOfUsers, IList<IList<string>> events) {
        ((List<IList<string>>) events).Sort((a, b) => {
            int aTimestamp = int.Parse(a[1]), bTimestamp = int.Parse(b[1]);
            if (aTimestamp != bTimestamp) {
                return aTimestamp - bTimestamp;
            } else {
                string aType = a[0], bType = b[0];
                return bType.CompareTo(aType);
            }
        });
        int[] mentions = new int[numberOfUsers];
        int[] onlineTimes = new int[numberOfUsers];
        foreach (IList<string> ev in events) {
            string type = ev[0];
            int timestamp = int.Parse(ev[1]);
            string users = ev[2];
            if (MESSAGE.Equals(type)) {
                if (ALL.Equals(users)) {
                    UpdateMentions(mentions, onlineTimes, int.MaxValue);
                } else if (HERE.Equals(users)) {
                    UpdateMentions(mentions, onlineTimes, timestamp);
                } else {
                    string[] ids = users.Split(" ");
                    foreach (string idStr in ids) {
                        int id = int.Parse(idStr.Substring(2));
                        mentions[id]++;
                    }
                }
            } else {
                int id = int.Parse(users);
                onlineTimes[id] = timestamp + OFFLINE_TIME;
            }
        }
        return mentions;
    }

    public void UpdateMentions(int[] mentions, int[] onlineTimes, int timestamp) {
        int numberOfUsers = mentions.Length;
        for (int i = 0; i < numberOfUsers; i++) {
            if (onlineTimes[i] <= timestamp) {
                mentions[i]++;
            }
        }
    }
}

复杂度分析

  • 时间复杂度:$O(n \log n \log t + \textit{numberOfUsers} + L)$,其中 $n$ 是数组 $\textit{events}$ 的长度,$\textit{numberOfUsers}$ 是用户总数,$t$ 是数组 $\textit{events}$ 中的时间戳最大值,$L$ 是数组 $\textit{events}$ 中的所有提及字符串的长度之和。将数组 $\textit{events}$ 排序的时间是 $O(n \log n \log t)$,创建数组 $\textit{mentions}$ 和 $\textit{onlineTimes}$ 的时间是 $O(\textit{numberOfUsers})$,遍历排序后的数组 $\textit{events}$ 计算提及次数的时间是 $O(n + L)$,因此时间复杂度是 $O(n \log n \log t + \textit{numberOfUsers} + L)$。

  • 空间复杂度:$O(n + \textit{numberOfUsers})$,其中 $n$ 是数组 $\textit{events}$ 的长度,$\textit{numberOfUsers}$ 是用户总数。由于待排序的元素是数组,因此排序的空间是 $O(n)$,记录每个用户的最新在线起始时间的空间是 $O(\textit{numberOfUsers})$。

3531. 统计被覆盖的建筑

解法一

思路和算法

根据建筑被覆盖的定义,当一个建筑在四个方向都至少存在一个其他建筑时,该建筑被覆盖。为了计算被覆盖的建筑数量,需要分别判断每个建筑是否被覆盖,因此需要分别统计每个 $x$ 坐标和每个 $y$ 坐标的所有建筑的列表。

使用两个哈希表分别记录每个 $x$ 坐标的所有建筑列表和每个 $y$ 坐标的所有建筑列表,两个哈希表分别为 $x$ 分组哈希表和 $y$ 分组哈希表。遍历数组 $\textit{buildings}$ 将所有建筑加入两个哈希表,然后将两个哈希表中的每个 $x$ 坐标和 $y$ 坐标对应的建筑列表排序,排序方法如下:在 $x$ 分组哈希表中,将每个 $x$ 坐标的所有建筑列表按 $y$ 坐标升序排序;在 $y$ 分组哈希表中,将每个 $y$ 坐标的所有建筑列表按 $x$ 坐标升序排序。

排序结束之后,即可判断每个建筑在 $x$ 坐标方向和 $y$ 坐标方向是否被覆盖。遍历所有建筑,对于位于坐标 $(x, y)$ 的建筑,判断方法如下。

  • 从 $y$ 分组哈希表中得到该建筑的相同 $y$ 坐标的所有建筑的列表,列表已经按 $x$ 坐标升序排序,判断当前 $x$ 坐标是否为列表中的最小值或最大值,如果既不是最小值也不是最大值,则该建筑在 $x$ 坐标方向被覆盖。

  • 从 $x$ 分组哈希表中得到该建筑的相同 $x$ 坐标的所有建筑的列表,列表已经按 $y$ 坐标升序排序,判断当前 $y$ 坐标是否为列表中的最小值或最大值,如果既不是最小值也不是最大值,则该建筑在 $y$ 坐标方向被覆盖。

当一个建筑同时在 $x$ 坐标方向和 $y$ 坐标方向被覆盖时,该建筑被覆盖。

遍历结束之后,即可得到被覆盖的建筑数量。

代码

###Java

class Solution {
    public int countCoveredBuildings(int n, int[][] buildings) {
        Map<Integer, List<Integer>> xToBuildings = new HashMap<Integer, List<Integer>>();
        Map<Integer, List<Integer>> yToBuildings = new HashMap<Integer, List<Integer>>();
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            xToBuildings.putIfAbsent(x, new ArrayList<Integer>());
            xToBuildings.get(x).add(y);
            yToBuildings.putIfAbsent(y, new ArrayList<Integer>());
            yToBuildings.get(y).add(x);
        }
        Set<Map.Entry<Integer, List<Integer>>> xEntries = xToBuildings.entrySet();
        Set<Map.Entry<Integer, List<Integer>>> yEntries = yToBuildings.entrySet();
        for (Map.Entry<Integer, List<Integer>> entry : xEntries) {
            Collections.sort(entry.getValue());
        }
        for (Map.Entry<Integer, List<Integer>> entry : yEntries) {
            Collections.sort(entry.getValue());
        }
        int count = 0;
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            List<Integer> xList = yToBuildings.get(y);
            List<Integer> yList = xToBuildings.get(x);
            int xMin = xList.get(0), xMax = xList.get(xList.size() - 1);
            int yMin = yList.get(0), yMax = yList.get(yList.size() - 1);
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

###C#

public class Solution {
    public int CountCoveredBuildings(int n, int[][] buildings) {
        IDictionary<int, IList<int>> xToBuildings = new Dictionary<int, IList<int>>();
        IDictionary<int, IList<int>> yToBuildings = new Dictionary<int, IList<int>>();
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            xToBuildings.TryAdd(x, new List<int>());
            xToBuildings[x].Add(y);
            yToBuildings.TryAdd(y, new List<int>());
            yToBuildings[y].Add(x);
        }
        foreach (KeyValuePair<int, IList<int>> pair in xToBuildings) {
            ((List<int>) pair.Value).Sort();
        }
        foreach (KeyValuePair<int, IList<int>> pair in yToBuildings) {
            ((List<int>) pair.Value).Sort();
        }
        int count = 0;
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            IList<int> xIList = yToBuildings[y];
            IList<int> yIList = xToBuildings[x];
            int xMin = xIList[0], xMax = xIList[xIList.Count - 1];
            int yMin = yIList[0], yMax = yIList[yIList.Count - 1];
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

复杂度分析

  • 时间复杂度:$O(m \log m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。将所有建筑存入两个哈希表的时间是 $O(m)$,排序的时间是 $O(m \log m)$,排序之后计算被覆盖的建筑数量的时间是 $O(m)$,因此时间复杂度是 $O(m \log m)$。

  • 空间复杂度:$O(m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。哈希表的空间是 $O(m)$。

解法二

思路和算法

判断一个建筑是否被覆盖时,需要知道如下信息。

  • 在相同 $y$ 坐标的所有建筑的列表中,该建筑的 $x$ 坐标是否为列表中的最小值或最大值。

  • 在相同 $x$ 坐标的所有建筑的列表中,该建筑的 $y$ 坐标是否为列表中的最小值或最大值。

因此,不需要维护每个 $x$ 坐标和每个 $y$ 坐标的所有建筑,只需要维护每个 $x$ 坐标的最小 $y$ 坐标和最大 $y$ 坐标,以及每个 $y$ 坐标的最小 $x$ 坐标和最大 $x$ 坐标。遍历数组 $\textit{buildings}$ 之后,将最小坐标与最大坐标的信息存入哈希表。再次遍历数组,即可根据哈希表中的最小坐标与最大坐标的信息判断每个建筑是否被覆盖,计算被覆盖的建筑数量。

代码

###Java

class Solution {
    public int countCoveredBuildings(int n, int[][] buildings) {
        Map<Integer, int[]> xToMinMax = new HashMap<Integer, int[]>();
        Map<Integer, int[]> yToMinMax = new HashMap<Integer, int[]>();
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            xToMinMax.putIfAbsent(x, new int[]{Integer.MAX_VALUE, Integer.MIN_VALUE});
            int[] yMinMax = xToMinMax.get(x);
            yMinMax[0] = Math.min(yMinMax[0], y);
            yMinMax[1] = Math.max(yMinMax[1], y);
            yToMinMax.putIfAbsent(y, new int[]{Integer.MAX_VALUE, Integer.MIN_VALUE});
            int[] xMinMax = yToMinMax.get(y);
            xMinMax[0] = Math.min(xMinMax[0], x);
            xMinMax[1] = Math.max(xMinMax[1], x);
        }
        int count = 0;
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            int[] xMinMax = yToMinMax.get(y);
            int[] yMinMax = xToMinMax.get(x);
            int xMin = xMinMax[0], xMax = xMinMax[1];
            int yMin = yMinMax[0], yMax = yMinMax[1];
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

###C#

public class Solution {
    public int CountCoveredBuildings(int n, int[][] buildings) {
        IDictionary<int, int[]> xToMinMax = new Dictionary<int, int[]>();
        IDictionary<int, int[]> yToMinMax = new Dictionary<int, int[]>();
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            xToMinMax.TryAdd(x, new int[]{int.MaxValue, int.MinValue});
            int[] yMinMax = xToMinMax[x];
            yMinMax[0] = Math.Min(yMinMax[0], y);
            yMinMax[1] = Math.Max(yMinMax[1], y);
            yToMinMax.TryAdd(y, new int[]{int.MaxValue, int.MinValue});
            int[] xMinMax = yToMinMax[y];
            xMinMax[0] = Math.Min(xMinMax[0], x);
            xMinMax[1] = Math.Max(xMinMax[1], x);
        }
        int count = 0;
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            int[] xMinMax = yToMinMax[y];
            int[] yMinMax = xToMinMax[x];
            int xMin = xMinMax[0], xMax = xMinMax[1];
            int yMin = yMinMax[0], yMax = yMinMax[1];
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

复杂度分析

  • 时间复杂度:$O(m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。遍历所有建筑维护两个哈希表的时间是 $O(m)$,计算被覆盖的建筑数量的时间是 $O(m)$,因此时间复杂度是 $O(m)$。

  • 空间复杂度:$O(m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。哈希表的空间是 $O(m)$。

3577. 统计计算机解锁顺序排列数

解法

思路和算法

使用已解锁的计算机密码将未解锁的计算机解锁的条件是:已解锁的计算机的编号和密码复杂度分别小于未解锁的计算机的编号和密码复杂度。由于初始时只有编号为 $0$ 的计算机密码已解锁,编号 $0$ 是最小编号,因此对于其他任意编号 $i$,是否可以使用编号为 $0$ 的计算机密码解锁编号为 $i$ 的计算机的情况如下。

  • 当 $\textit{complexity}[i] > \textit{complexity}[0]$ 时,可以使用编号为 $0$ 的计算机密码解锁编号为 $i$ 的计算机。

  • 当 $\textit{complexity}[i] \le \textit{complexity}[0]$ 时,不能使用编号为 $0$ 的计算机密码解锁编号为 $i$ 的计算机。

如果存在 $1 \le i < n$ 的整数 $i$ 满足 $\textit{complexity}[i] \le \textit{complexity}[0]$,则对于任意可以被编号为 $0$ 的计算机密码解锁的计算机的编号 $j$,必有 $\textit{complexity}[j] > \textit{complexity}[0]$,因此 $\textit{complexity}[j] > \textit{complexity}[i]$,即编号为 $j$ 的计算机密码不能解锁编号为 $i$ 的计算机。因此,编号为 $i$ 的计算机无法被解锁,此时无法解锁所有计算机。

如果 $1 \le i < n$ 的所有整数 $i$ 都满足 $\textit{complexity}[i] > \textit{complexity}[0]$,则所有计算机都能被编号为 $0$ 的计算机密码解锁。由于初始时编号为 $0$ 的计算机密码已解锁,因此其余 $n - 1$ 台计算机可以按任意顺序解锁,排列数是 $(n - 1)!$。

代码

###Java

class Solution {
    static final int MODULO = 1000000007;

    public int countPermutations(int[] complexity) {
        long permutations = 1;
        int n = complexity.length;
        for (int i = 1; i < n; i++) {
            if (complexity[i] <= complexity[0]) {
                return 0;
            }
            permutations = permutations * i % MODULO;
        }
        return (int) permutations;
    }
}

###C#

public class Solution {
    const int MODULO = 1000000007;

    public int CountPermutations(int[] complexity) {
        long permutations = 1;
        int n = complexity.Length;
        for (int i = 1; i < n; i++) {
            if (complexity[i] <= complexity[0]) {
                return 0;
            }
            permutations = permutations * i % MODULO;
        }
        return (int) permutations;
    }
}

###C++

const int MODULO = 1000000007;

class Solution {
public:
    int countPermutations(vector<int>& complexity) {
        long long permutations = 1;
        int n = complexity.size();
        for (int i = 1; i < n; i++) {
            if (complexity[i] <= complexity[0]) {
                return 0;
            }
            permutations = permutations * i % MODULO;
        }
        return permutations;
    }
};

###Python

MODULO = 1000000007

class Solution:
    def countPermutations(self, complexity: List[int]) -> int:
        permutations = 1
        n = len(complexity)
        for i in range(1, n):
            if complexity[i] <= complexity[0]:
                return 0
            permutations = permutations * i % MODULO
        return permutations

###C

const int MODULO = 1000000007;

int countPermutations(int* complexity, int complexitySize) {
    long long permutations = 1;
    for (int i = 1; i < complexitySize; i++) {
        if (complexity[i] <= complexity[0]) {
            return 0;
        }
        permutations = permutations * i % MODULO;
    }
    return permutations;
}

###Go

const MODULO = 1000000007

func countPermutations(complexity []int) int {
    permutations := 1
    n := len(complexity)
    for i := 1; i < n; i++ {
        if complexity[i] <= complexity[0] {
            return 0
        }
        permutations = permutations * i % MODULO
    }
    return permutations
}

###JavaScript

const MODULO = 1000000007;

var countPermutations = function(complexity) {
    let permutations = 1;
    let n = complexity.length;
    for (let i = 1; i < n; i++) {
        if (complexity[i] <= complexity[0]) {
            return 0;
        }
        permutations = permutations * i % MODULO;
    }
    return permutations;
};

###TypeScript

const MODULO = 1000000007;

function countPermutations(complexity: number[]): number {
    let permutations = 1;
    let n = complexity.length;
    for (let i = 1; i < n; i++) {
        if (complexity[i] <= complexity[0]) {
            return 0;
        }
        permutations = permutations * i % MODULO;
    }
    return permutations;
};

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{complexity}$ 的长度。需要遍历数组一次。

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

❌