685.冗余连接II

目标

在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。

输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。

返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

示例 1:

输入:edges = [[1,2],[1,3],[2,3]]
输出:[2,3]

示例 2:

输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
输出:[4,1]

说明:

  • n == edges.length
  • 3 <= n <= 1000
  • edges[i].length == 2
  • 1 <= ui, vi <= n

思路

有一颗 n 个节点的树,节点编号 1 ~ n。使用 edges 表示向树中两个没有直接连接的节点之间加一条边之后的边的集合,找出一条可以删除的边使得 edges 变为一颗有 n 个节点的树。如果有多种选择,返回 edges 中最后出现的那个,即下标最大的边。与 冗余连接 不同的是 edges有向边 的集合。

如果直接使用昨天无向图寻找环的做法会有两个问题:

  • 无法处理 a -> b, b -> a 的情况,因为在无向图中为了防止环,直接回避了这种情况
  • 并不是删去环上任意一条边都可以的,因为边是有向的,如果某个节点出现两个父节点,那么一定要删去以该节点为终点的边

官网题解使用的还是并查集。// todo

代码


/**
 * @date 2024-10-28 8:51
 */
public class FindRedundantDirectedConnection685 {

    List<Integer>[] g;
    Set<Integer> loop;
    List<Integer> path;
    int start;
    int end;

    public int[] findRedundantDirectedConnection(int[][] edges) {
        int n = edges.length;
        g = new List[n + 1];
        for (int i = 0; i <= n; i++) {
            g[i] = new ArrayList<>();
        }
        int[] degree = new int[n + 1];
        Set<Integer> e = new HashSet<>(n);
        int end = -1;
        int[] self = null;
        for (int[] edge : edges) {
            int from = edge[0];
            int to = edge[1];
            int fromto = from << 10 | to;
            int tofrom = to << 10 | from;
            if (e.contains(fromto)) {
                self = new int[]{from, to};
            }
            e.add(fromto);
            e.add(tofrom);
            g[from].add(to);
            g[to].add(from);
            if (degree[to] == 1) {
                end = to;
            } else {
                degree[to]++;
            }
        }

        if (self != null) {
            if (end == -1) {
                for (int i = n - 1; i >= 0; i--) {
                    if ((self[0] == edges[i][0] && edges[i][1] == self[1])
                            || (self[0] == edges[i][1] && edges[i][0] == self[1])) {
                        return edges[i];
                    }
                }
            } else {
                return new int[]{self[0] == end ? self[1] : self[0], end};
            }

        }

        loop = new HashSet<>(n);
        path = new ArrayList<>();
        loop.add(1);
        path.add(1);
        dfs(0, 1);
        loop = new HashSet<>();
        for (int i = path.size() - 1; i >= 0; i--) {
            loop.add(path.get(i));
            if (start == path.get(i)) {
                break;
            }
        }
        if (end == -1) {
            for (int i = n - 1; i >= 0; i--) {
                if (loop.contains(edges[i][0]) && loop.contains(edges[i][1])) {
                    return edges[i];
                }
            }
        } else {
            for (int i = n - 1; i >= 0; i--) {
                if (edges[i][1] == end && loop.contains(edges[i][0])) {
                    return edges[i];
                }
            }
        }

        return null;
    }

    private boolean dfs(int parent, int current) {
        for (Integer next : g[current]) {
            if (next == parent) {
                continue;
            }
            if (loop.contains(next)) {
                start = next;
                return true;
            } else {
                loop.add(next);
                path.add(next);
                if (dfs(current, next)) {
                    return true;
                }
                path.remove(path.size() - 1);
                loop.remove(next);
            }
        }
        return false;
    }

}

性能

3181.执行操作可获得的最大总奖励II

目标

给你一个整数数组 rewardValues,长度为 n,代表奖励的值。

最初,你的总奖励 x 为 0,所有下标都是 未标记 的。你可以执行以下操作 任意次 :

  • 从区间 [0, n - 1] 中选择一个 未标记 的下标 i。
  • 如果 rewardValues[i] 大于 你当前的总奖励 x,则将 rewardValues[i] 加到 x 上(即 x = x + rewardValues[i]),并 标记 下标 i。

以整数形式返回执行最优操作能够获得的 最大 总奖励。

示例 1:

输入:rewardValues = [1,1,3,3]
输出:4
解释:
依次标记下标 0 和 2,总奖励为 4,这是可获得的最大值。

示例 2:

输入:rewardValues = [1,6,4,3,2]
输出:11
解释:
依次标记下标 0、2 和 1。总奖励为 11,这是可获得的最大值。

说明:

  • 1 <= rewardValues.length <= 5 * 10^4
  • 1 <= rewardValues[i] <= 5 * 10^4

思路

与昨天的题相比,数据范围变大了。

// todo

代码

性能

3193.统计逆序对的数目

目标

给你一个整数 n 和一个二维数组 requirements ,其中 requirements[i] = [endi, cnti] 表示这个要求中的末尾下标和 逆序对 的数目。

整数数组 nums 中一个下标对 (i, j) 如果满足以下条件,那么它们被称为一个 逆序对 :

  • i < j 且 nums[i] > nums[j]

请你返回 [0, 1, 2, ..., n - 1] 的 排列 perm 的数目,满足对 所有 的 requirements[i] 都有 perm[0..endi] 恰好有 cnti 个逆序对。

由于答案可能会很大,将它对 10^9 + 7 取余 后返回。

示例 1:

输入:n = 3, requirements = [[2,2],[0,0]]
输出:2
解释:
两个排列为:
[2, 0, 1]
前缀 [2, 0, 1] 的逆序对为 (0, 1) 和 (0, 2) 。
前缀 [2] 的逆序对数目为 0 个。
[1, 2, 0]
前缀 [1, 2, 0] 的逆序对为 (0, 2) 和 (1, 2) 。
前缀 [1] 的逆序对数目为 0 个。

示例 2:

输入:n = 3, requirements = [[2,2],[1,1],[0,0]]
输出:1
解释:
唯一满足要求的排列是 [2, 0, 1] :
前缀 [2, 0, 1] 的逆序对为 (0, 1) 和 (0, 2) 。
前缀 [2, 0] 的逆序对为 (0, 1) 。
前缀 [2] 的逆序对数目为 0 。

示例 3:

输入:n = 2, requirements = [[0,0],[1,0]]
输出:1
解释:
唯一满足要求的排列为 [0, 1] :
前缀 [0] 的逆序对数目为 0 。
前缀 [0, 1] 的逆序对为 (0, 1) 。

说明:

  • 2 <= n <= 300
  • 1 <= requirements.length <= n
  • requirements[i] = [endi, cnti]
  • 0 <= endi <= n - 1
  • 0 <= cnti <= 400
  • 输入保证至少有一个 i 满足 endi == n - 1 。
  • 输入保证所有的 endi 互不相同。

思路

数组 nums 的每一个子数组 [0, endi] 有一个 cnti,求这些子数组的排列中逆序对个数恰好为对应的 cnti 的个数。注意同一个排列需要满足所有的要求。

// todo

代码

性能

3171.找到按位或最接近K的子数组

目标

给你一个数组 nums 和一个整数 k 。你需要找到 nums 的一个 子数组,满足子数组中所有元素按位或运算 OR 的值与 k 的 绝对差 尽可能 小 。换言之,你需要选择一个子数组 nums[l..r] 满足 |k - (nums[l] OR nums[l + 1] ... OR nums[r])| 最小。

请你返回 最小 的绝对差值。

子数组 是数组中连续的 非空 元素序列。

示例 1:

输入:nums = [1,2,4,5], k = 3
输出:0
解释:
子数组 nums[0..1] 的按位 OR 运算值为 3 ,得到最小差值 |3 - 3| = 0 。

示例 2:

输入:nums = [1,3,1,3], k = 2
输出:1
解释:
子数组 nums[1..1] 的按位 OR 运算值为 3 ,得到最小差值 |3 - 2| = 1 。

示例 3:

输入:nums = [1], k = 10
输出:9
解释:
只有一个子数组,按位 OR 运算值为 1 ,得到最小差值 |10 - 1| = 9 。

说明:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • 1 <= k <= 10^9

思路

寻找子数组使子数组元素按位或运算的值 or 尽可能地接近 k,即求 |k - or| 的最小值。

暴力求解的基本逻辑是,外层枚举右端点,内层从后向前枚举左端点,使用 nums[j] 保存 子数组 [j, i] 的或值,通过判断 (nums[j] | right) != nums[j] 决定 right 是否对或值有贡献,如果没有贡献,那么可以不再继续向前枚举左端点,因为前面的或值已经累加了后面的值,如果对子数组 [j, i] 的或值没有贡献,那么对前面的 [j - 1, i] 包含了 [j, i] 的或值更没有贡献。

代码


/**
 * @date 2024-10-09 8:59
 */
public class MinimumDifference3171 {

    public int minimumDifference_v1(int[] nums, int k) {
        int n = nums.length;
        int res = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            int right = nums[i];
            for (int j = i - 1; j >= 0 && ((nums[j] | right) != nums[j]); j--) {
                nums[j] |= right;
                res = Math.min(res, Math.abs(nums[j] - k));
            }
            res = Math.min(res, Math.abs(right - k));
        }
        return res;
    }

}

性能

871.最低加油次数

目标

汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。

沿途有加油站,用数组 stations 表示。其中 stations[i] = [positioni, fueli] 表示第 i 个加油站位于出发位置东面 positioni 英里处,并且有 fueli 升汽油。

假设汽车油箱的容量是无限的,其中最初有 startFuel 升燃料。它每行驶 1 英里就会用掉 1 升汽油。当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。

为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1 。

注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。

示例 1:

输入:target = 1, startFuel = 1, stations = []
输出:0
解释:可以在不加油的情况下到达目的地。

示例 2:

输入:target = 100, startFuel = 1, stations = [[10,100]]
输出:-1
解释:无法抵达目的地,甚至无法到达第一个加油站。

示例 3:

输入:target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]
输出:2
解释:
出发时有 10 升燃料。
开车来到距起点 10 英里处的加油站,消耗 10 升燃料。将汽油从 0 升加到 60 升。
然后,从 10 英里处的加油站开到 60 英里处的加油站(消耗 50 升燃料),
并将汽油从 10 升加到 50 升。然后开车抵达目的地。
沿途在两个加油站停靠,所以返回 2 。

说明:

  • 1 <= target, startFuel <= 10^9
  • 0 <= stations.length <= 500
  • 1 <= positioni < positioni+1 < target
  • 1 <= fueli < 10^9

思路

已知汽车油耗为 1 英里/升,现在想要开车前往 target 英里外的目的地,出发时车中有 startFuel 升油,沿途有 stations.length 个加油站,stations[i][0] 表示加油站 i 距出发地的距离,stations[i][1] 表示加油站 i 的汽油总量。假设汽车油箱无限大,求到达目的地的最少加油次数,如果无法到达返回 -1

可以将出发点、加油站、目的地画到数轴上,由于油耗为 1 英里/升,有多少升油就可以到达多远的距离。那么问题转化为从 n 个加油站中选取最少的个数,使油箱油量大于等于 target。要使选择的加油站最少,那么应该首选油量多的加油站,前提是能够抵达该加油站。我们可以使用大顶堆维护加油站油量,将能够抵达的加油站放入其中,然后将堆顶的油加入油箱,将新的能够抵达的加油站放入堆中,直到油箱中的油量大于等于 target

代码


/**
 * @date 2024-10-07 21:09
 */
public class MinRefuelStops871 {

    public int minRefuelStops(int target, int startFuel, int[][] stations) {
        PriorityQueue<Integer> q = new PriorityQueue<>((a, b) -> b - a);
        int n = stations.length;
        int i = 0;
        int res = 0;
        int fuel = startFuel;
        while (fuel < target) {
            while (i < n && fuel >= stations[i][0]) {
                q.offer(stations[i++][1]);
            }
            if (!q.isEmpty()) {
                fuel += q.poll();
                res++;
                if (fuel >= target) {
                    return res;
                }
            } else if (i == n || fuel < stations[i][0]) {
                // 没有剩余的加油站或者无法抵达
                return -1;
            }
        }
        return res;
    }

}

性能

1928.规定时间内到达终点的最小花费

目标

一个国家有 n 个城市,城市编号为 0 到 n - 1 ,题目保证 所有城市 都由双向道路 连接在一起 。道路由二维整数数组 edges 表示,其中 edges[i] = [xi, yi, timei] 表示城市 xi 和 yi 之间有一条双向道路,耗费时间为 timei 分钟。两个城市之间可能会有多条耗费时间不同的道路,但是不会有道路两头连接着同一座城市。

每次经过一个城市时,你需要付通行费。通行费用一个长度为 n 且下标从 0 开始的整数数组 passingFees 表示,其中 passingFees[j] 是你经过城市 j 需要支付的费用。

一开始,你在城市 0 ,你想要在 maxTime 分钟以内 (包含 maxTime 分钟)到达城市 n - 1 。旅行的 费用 为你经过的所有城市 通行费之和 (包括 起点和终点城市的通行费)。

给你 maxTime,edges 和 passingFees ,请你返回完成旅行的 最小费用 ,如果无法在 maxTime 分钟以内完成旅行,请你返回 -1 。

示例 1:

输入:maxTime = 30, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]
输出:11
解释:最优路径为 0 -> 1 -> 2 -> 5 ,总共需要耗费 30 分钟,需要支付 11 的通行费。

示例 2:

输入:maxTime = 29, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]
输出:48
解释:最优路径为 0 -> 3 -> 4 -> 5 ,总共需要耗费 26 分钟,需要支付 48 的通行费。
你不能选择路径 0 -> 1 -> 2 -> 5 ,因为这条路径耗费的时间太长。

示例 3:

输入:maxTime = 25, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]
输出:-1
解释:无法在 25 分钟以内从城市 0 到达城市 5 。

说明:

  • 1 <= maxTime <= 1000
  • n == passingFees.length
  • 2 <= n <= 1000
  • n - 1 <= edges.length <= 1000
  • 0 <= xi, yi <= n - 1
  • 1 <= timei <= 1000
  • 1 <= passingFees[j] <= 1000
  • 图中两个节点之间可能有多条路径。
  • 图中不含有自环。

思路

// todo

代码

性能

2286.以组为单位订音乐会的门票

目标

一个音乐会总共有 n 排座位,编号从 0 到 n - 1 ,每一排有 m 个座椅,编号为 0 到 m - 1 。你需要设计一个买票系统,针对以下情况进行座位安排:

  • 同一组的 k 位观众坐在 同一排座位,且座位连续 。
  • k 位观众中 每一位 都有座位坐,但他们 不一定 坐在一起。

由于观众非常挑剔,所以:

  • 只有当一个组里所有成员座位的排数都 小于等于 maxRow ,这个组才能订座位。每一组的 maxRow 可能 不同 。
  • 如果有多排座位可以选择,优先选择 最小 的排数。如果同一排中有多个座位可以坐,优先选择号码 最小 的。

请你实现 BookMyShow 类:

  • BookMyShow(int n, int m) ,初始化对象,n 是排数,m 是每一排的座位数。
  • int[] gather(int k, int maxRow) 返回长度为 2 的数组,表示 k 个成员中 第一个座位 的排数和座位编号,这 k 位成员必须坐在 同一排座位,且座位连续 。换言之,返回最小可能的 r 和 c 满足第 r 排中 [c, c + k - 1] 的座位都是空的,且 r <= maxRow 。如果 无法 安排座位,返回 [] 。
  • boolean scatter(int k, int maxRow) 如果组里所有 k 个成员 不一定 要坐在一起的前提下,都能在第 0 排到第 maxRow 排之间找到座位,那么请返回 true 。这种情况下,每个成员都优先找排数 最小 ,然后是座位编号最小的座位。如果不能安排所有 k 个成员的座位,请返回 false 。

示例 1:

输入:
["BookMyShow", "gather", "gather", "scatter", "scatter"]
[[2, 5], [4, 0], [2, 0], [5, 1], [5, 1]]
输出:
[null, [0, 0], [], true, false]

解释:
BookMyShow bms = new BookMyShow(2, 5); // 总共有 2 排,每排 5 个座位。
bms.gather(4, 0); // 返回 [0, 0]
                  // 这一组安排第 0 排 [0, 3] 的座位。
bms.gather(2, 0); // 返回 []
                  // 第 0 排只剩下 1 个座位。
                  // 所以无法安排 2 个连续座位。
bms.scatter(5, 1); // 返回 True
                   // 这一组安排第 0 排第 4 个座位和第 1 排 [0, 3] 的座位。
bms.scatter(5, 1); // 返回 False
                   // 总共只剩下 1 个座位。

说明:

  • 1 <= n <= 5 * 10^4
  • 1 <= m, k <= 10^9
  • 0 <= maxRow <= n - 1
  • gather 和 scatter 总 调用次数不超过 5 * 10^4 次。

思路

// todo 今天没时间做了

代码

性能

2306.公司命名

目标

给你一个字符串数组 ideas 表示在公司命名过程中使用的名字列表。公司命名流程如下:

  1. 从 ideas 中选择 2 个 不同 名字,称为 ideaA 和 ideaB 。
  2. 交换 ideaA 和 ideaB 的首字母。
  3. 如果得到的两个新名字 都 不在 ideas 中,那么 ideaA ideaB(串联 ideaA 和 ideaB ,中间用一个空格分隔)是一个有效的公司名字。
  4. 否则,不是一个有效的名字。

返回 不同 且有效的公司名字的数目。

示例 1:

输入:ideas = ["coffee","donuts","time","toffee"]
输出:6
解释:下面列出一些有效的选择方案:
- ("coffee", "donuts"):对应的公司名字是 "doffee conuts" 。
- ("donuts", "coffee"):对应的公司名字是 "conuts doffee" 。
- ("donuts", "time"):对应的公司名字是 "tonuts dime" 。
- ("donuts", "toffee"):对应的公司名字是 "tonuts doffee" 。
- ("time", "donuts"):对应的公司名字是 "dime tonuts" 。
- ("toffee", "donuts"):对应的公司名字是 "doffee tonuts" 。
因此,总共有 6 个不同的公司名字。

下面列出一些无效的选择方案:
- ("coffee", "time"):在原数组中存在交换后形成的名字 "toffee" 。
- ("time", "toffee"):在原数组中存在交换后形成的两个名字。
- ("coffee", "toffee"):在原数组中存在交换后形成的两个名字。

示例 2:

输入:ideas = ["lack","back"]
输出:0
解释:不存在有效的选择方案。因此,返回 0 。

说明:

  • 2 <= ideas.length <= 5 * 10^4
  • 1 <= ideas[i].length <= 10
  • ideas[i] 由小写英文字母组成
  • ideas 中的所有字符串 互不相同

思路

将收集到的不同的单词放入 ideas 数组,从中选取两个单词,交换其首字母,如果得到的新单词不在 ideas 中,那么这两个单词的排列是有效的公司名,求不重复的有效的公司名数目。

通过观察示例可以发现,首字母相同是不行的,交换后还是原单词。那么可以建立一个长度为26的数组,元素是以该字母开头的单词 列表。可行的方案是选择两个首字母不同的单词,判断另一方首字母列表中的单词是否包含本方单词的后缀。如果双方都不包含,那么这两个单词的 排列 是有效的公司名。否则,包含该后缀的单词与另一方列表的所有组合都是无效的命名,因为交换后必定重复。比如,coffeetime、 toffee,后缀 offee 在两个列表中存在,那么coffeet 开头的任意单词交换首字母,得到的 toffee 已经存在,所以 coffee 与所有以 t 开头的单词组合都不合法。同理,toffeec 开头的任意单词交换首字母,得到 coffee 已存在,toffee 与所有以 c 开头的单词组合都不合法。

考虑这个问题,按照上面方法取得的有效的公司名是否会重复?也就是说,选取另一对不同的单词组合,是否会出现交换首字母后的单词不重复,而组成的公司名重复。假设该对单词组成的公司名已存在,那么原来组成该公司名的单词对必定也是从这两个首字母单词列表中选取。说明 ideas 数组中的单词有重复,这与题目条件矛盾。所以,只要是有效的公司名就不会重复。

假设 ideas 中出现的首字母种类个数为 10,首字母相同的单词个数为 5 * 10^3。那么枚举所有组合需要循环 C(10,2) * 25 * 10^6 = 45 * 25 * 10^6 = 1125 * 10^6 次,大约 10^9 肯定超时。

思考暴力枚举的瓶颈在哪?或者说有哪些循环是没必要的?能否遍历一次,保存状态?我们在循环中做的事情就是

  1. 判断当前单词的后缀是否在另一首字母单词列表中存在
  2. 判断另一首字母单词的后缀是否在当前首字母单词列表中存在

如果我们可以直接得到当前单词与另一首字母列表中可以结合的单词个数,那么就只需要遍历25个其它首字母就可以了。我们可以记录后缀与首字母集合的映射。然后维护一个 cannotCombine[i][j] 数组,表示以i为首字母的单词,与其它以首字母j开头的单词无法组成有效命名的个数,这样,我们在枚举首字母i内的单词时,可以直接用 words[j].size() - cannotCombine[j][i] 即可得到合法的组合数。

代码


/**
 * @date 2024-09-25 10:42
 */
public class DistinctNames2306 {

    public long distinctNames_v1(String[] ideas) {
        long res = 0L;
        int n = ideas.length;
        Map<String, Set<Integer>> suffixToFirstMap = new HashMap<>();
        List<String>[] words = new List[26];
        for (int i = 0; i < n; i++) {
            String idea = ideas[i];
            int first = idea.charAt(0) - 'a';
            if (words[first] == null) {
                words[first] = new ArrayList<>();
            }
            words[first].add(idea);
            String suffix = idea.substring(1);
            suffixToFirstMap.putIfAbsent(suffix, new HashSet<>());
            suffixToFirstMap.get(suffix).add(first);
        }
        int[][] cannotCombine = new int[26][26];
        for (int i = 0; i < 26; i++) {
            if (words[i] == null) {
                continue;
            }
            for (String word : words[i]) {
                String suffix = word.substring(1);
                Set<Integer> set = suffixToFirstMap.get(suffix);
                if (set != null){
                    for (Integer index : set) {
                        cannotCombine[i][index]++;
                    }
                }
            }
        }

        for (int i = 0; i < 26; i++) {
            List<String> iWord = words[i];
            if (iWord == null) {
                continue;
            }
            for (int j = 0; j < iWord.size(); j++) {
                String word = iWord.get(j);
                String suffix = word.substring(1);
                for (int k = i + 1; k < 26; k++) {
                    Set<Integer> set = suffixToFirstMap.get(suffix);
                    if (set != null && words[k] != null && !set.contains(k)) {
                        res += 2 * (words[k].size() - cannotCombine[k][i]);
                    }
                }
            }
        }
        return res;
    }

}

性能

2376.统计特殊整数

目标

如果一个正整数每一个数位都是 互不相同 的,我们称它是 特殊整数 。

给你一个 正 整数 n ,请你返回区间 [1, n] 之间特殊整数的数目。

示例 1:

输入:n = 20
输出:19
解释:1 到 20 之间所有整数除了 11 以外都是特殊整数。所以总共有 19 个特殊整数。

示例 2:

输入:n = 5
输出:5
解释:1 到 5 所有整数都是特殊整数。

示例 3:

输入:n = 135
输出:110
解释:从 1 到 135 总共有 110 个整数是特殊整数。
不特殊的部分数字为:22 ,114 和 131 。

说明:

  • 1 <= n <= 2 * 10^9

思路

求 1 ~ n 之间的特殊整数,所谓特殊整数指 十进制 下的每一位都不相同的整数。

// todo

代码

性能

815.公交路线[中秋快乐]

目标

给你一个数组 routes ,表示一系列公交线路,其中每个 routes[i] 表示一条公交线路,第 i 辆公交车将会在上面循环行驶。

  • 例如,路线 routes[0] = [1, 5, 7] 表示第 0 辆公交车会一直按序列 1 -> 5 -> 7 -> 1 -> 5 -> 7 -> 1 -> ... 这样的车站路线行驶。

现在从 source 车站出发(初始时不在公交车上),要前往 target 车站。 期间仅可乘坐公交车。

求出 最少乘坐的公交车数量 。如果不可能到达终点车站,返回 -1 。

示例 1:

输入:routes = [[1,2,7],[3,6,7]], source = 1, target = 6
输出:2
解释:最优策略是先乘坐第一辆公交车到达车站 7 , 然后换乘第二辆公交车到车站 6 。

示例 2:

输入:routes = [[7,12],[4,5,15],[6],[15,19],[9,12,13]], source = 15, target = 12
输出:-1

说明:

  • 1 <= routes.length <= 500.
  • 1 <= routes[i].length <= 10^5
  • routes[i] 中的所有值 互不相同
  • sum(routes[i].length) <= 10^5
  • 0 <= routes[i][j] < 10^6
  • 0 <= source, target < 10^6

思路

求不带权的最短路径直接使用BFS即可,但这题求的是换乘次数最少的路径。我们可以将问题转化为带权的最短路径问题,如果需要换乘,将权重设为1,否则权重为0。

// todo

代码

性能