Page 8 of 61

LeetCode刷题【1359】有效的快递序列数目

给你 n 笔订单,每笔订单都需要快递服务。

请你统计所有有效的 收件/配送 序列的数目,确保第 i 个物品的配送服务 delivery(i) 总是在其收件服务 pickup(i) 之后。

由于答案可能很大,请返回答案对 10^9 + 7 取余的结果。

 

示例 1:

输入:n = 1
输出:1
解释:只有一种序列 (P1, D1),物品 1 的配送服务(D1)在物品 1 的收件服务(P1)后。

示例 2:

输入:n = 2
输出:6
解释:所有可能的序列包括:
(P1,P2,D1,D2),(P1,P2,D2,D1),(P1,D1,P2,D2),(P2,P1,D1,D2),(P2,P1,D2,D1) 和 (P2,D2,P1,D1)。
(P1,D2,P2,D1) 是一个无效的序列,因为物品 2 的收件服务(P2)不应在物品 2 的配送服务(D2)之后。

示例 3:

输入:n = 3
输出:90

 

提示:

  • 1 <= n <= 500
Related Topics
  • 数学
  • 动态规划
  • 组合数学

  • 👍 44
  • 👎 0
  • 数学求解推导过程


    简单来说,第n对快递在原来的排序中插入放置的情况有先PD的依赖性,不妨来分析下摆放情况,如上图,分别确定D的位置为蓝色,P的情况为绿色的可能性,

    那么总和的结果为

    (2n-1) + (2n-2) + (2n-3) + ... + 1

    这就是一个从1到(2n-1)的求和过程,转化之后为

    (2n-1) * 2n / 2

    2 * n^2 - n

    这是第n对快递的情况,而第(n-1)的情况可以由之前求得,这里简单的用f(n-1)表示

    所以最终我们知道

    f(n) = f(n-1) * ( 2 * n^2 - n )

    这样我们从1开始滚动求解得到f(n)的具体数值中途记得模1e9+7

    代码

    class Solution {
        public int countOrders(int n) {
            long ans = 1;
            long mod = (long) (1e9+7);
            for (int i = 2; i <= n; i++) {
                ans *= (2 * i * i - i);
                ans %= mod;
            }
            return (int)ans;
        }
    }

    LeetCode刷题【1155】掷骰子的N种方法

    这里有 n 个一样的骰子,每个骰子上都有 k 个面,分别标号为 1 到 k

    给定三个整数 nk 和 target ,返回可能的方式(从总共 kn 种方式中)滚动骰子的数量,使正面朝上的数字之和等于 target 。

    答案可能很大,你需要对 109 + 7 取模 。

     

    示例 1:

    输入:n = 1, k = 6, target = 3
    输出:1
    解释:你扔一个有6张脸的骰子。
    得到3的和只有一种方法。
    

    示例 2:

    输入:n = 2, k = 6, target = 7
    输出:6
    解释:你扔两个骰子,每个骰子有6个面。
    得到7的和有6种方法1+6 2+5 3+4 4+3 5+2 6+1。
    

    示例 3:

    输入:n = 30, k = 30, target = 500
    输出:222616187
    解释:返回的结果必须是对 109 + 7 取模。

     

    提示:

    • 1 <= n, k <= 30
    • 1 <= target <= 1000
    Related Topics
    • 动态规划

  • 👍 139
  • 👎 0
  • 二维简单DP
    简单DP算法,想要得到结果target,那么我们单独拿出来假定的最后一个骰子,这个骰子有1k种可能性
    继而对应在得到target之前,没有这个骰子的时候需要算出的target-1target-k种情况的总和,
    那么我们就很容易的知道了转移方程

    dp[i][target] = dp[i-1][target-1] + .... + dp[i-1][target-k]

    初始一个情况,只有一个骰子的时候,1k的值都只有1种组合情况

    代码

    class Solution {
    
        /**
         * dp[i][target] = dp[i-1][target-1] + .... + dp[i-1][target-k]
         */
        public int numRollsToTarget(int n, int k, int target) {
            int mod = (int) 1e9 + 7;
            int[][] dp = new int[n][target+1];
            for (int i = 1; i <= k && i <= target; i++) {
                dp[0][i] = 1;
            }
            for (int row = 1; row < n; row++) {
                for (int col = row+1; col <= target; col++) {
                    for (int i = 1; i <= k; i++) {
                        if (i>col){
                            continue;
                        }
                        dp[row][col] += dp[row-1][col-i];
                        dp[row][col] %= mod;
                    }
                }
            }
            return dp[n-1][target];
        }
    }

    LeetCode刷题【289】生命游戏

    根据 百度百科 , 生命游戏 ,简称为 生命 ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。

    给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1 即为 活细胞 (live),或 0 即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:

    1. 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
    2. 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
    3. 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
    4. 如果死细胞周围正好有三个活细胞,则该位置死细胞复活;

    下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 m x n 网格面板 board 的当前状态,返回下一个状态。

     

    示例 1:

    输入:board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]]
    输出:[[0,0,0],[1,0,1],[0,1,1],[0,1,0]]
    

    示例 2:

    输入:board = [[1,1],[1,0]]
    输出:[[1,1],[1,1]]
    

     

    提示:

    • m == board.length
    • n == board[i].length
    • 1 <= m, n <= 25
    • board[i][j]01

     

    进阶:

    • 你可以使用原地算法解决本题吗?请注意,面板上所有格子需要同时被更新:你不能先更新某些格子,然后使用它们的更新后的值再更新其他格子。
    • 本题中,我们使用二维数组来表示面板。原则上,面板是无限的,但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题?
    Related Topics
    • 数组
    • 矩阵
    • 模拟

  • 👍 438
  • 👎 0
  • 数组复制 & 简化易懂版的原地修改
    题意很好理解,直接上来就是淦代码

    顺便自己写了个页面http://next.cheungq.com/ 里面有个生命游戏的演示

    数组复制的方法代码

    class Solution {
        public void gameOfLife(int[][] board) {
            int m = board.length;
            int n = board[0].length;
            int[][] ans = new int[m][n];
            int[][] dir = new int[][]{{1,0},{-1,0},{0,1},{0,-1},{1,-1},{1,1},{-1,1},{-1,-1}};
    
            for (int row = 0; row < m; row++) {
                for (int col = 0; col < n; col++) {
                    int cnt = 0;
                    for (int i = 0; i < dir.length; i++) {
                        int x = row+dir[i][0];
                        int y = col+dir[i][1];
                        if (x<0||y<0||x>=m||y>=n){
                            continue;
                        }
                        cnt += board[x][y];
                    }
    
                    if (board[row][col] == 1){
                        //如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
                        ans[row][col] = (cnt == 2 || cnt == 3)?1:0;
                        //如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
                        //如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
                    }else{
                        //如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
                        ans[row][col] = cnt==3?1:0;
                    }
                }
            }
    
            for (int row = 0; row < m; row++) {
                for (int col = 0; col < n; col++) {
                    board[row][col] = ans[row][col];
                }
            }
        }
    }

    原地修改

    而原地修改大佬们用的方法略高端,用二进制位的状态来标记。我这有个简化版的原理一样。
    思路如下,也许可以帮到你更加容易的来理解这个过程

    1. 因为周围最多只有8个格子,所以周围最多活细胞数量只有8个,这个数字小于10,那么我们可以活用这个关系
    2. 讲当前格子状态进一位,变成10或者00,第一位表示当前格子的状态
    3. 后面一位的0我们更新为周围8个格子的活细胞数量,
    4. 比如更新后当前格子的值为14,第一位1表示当前格子是活细胞,周围8个格子中有4个活细胞
    5. 又比如更新后当前格子的值为5,第一位的10位数上没有,即为0,那么表明当前格子为死细胞,周围有5个活细胞
    6. 一遍更新完了之后,我们就可以根据更新后的结果值来重新换算下当前格子的下一步状态值是死细胞还是活细胞了
    7. 又,因为是从左往右,从上往下的更新的,所以,在计算周围8个格子中活细胞数量的时候需要注意一下以下情况
      • 当前行的上一行对应的3个格子中的结果是已经计算过之后的结果,所以判断细胞死活情况需要判断的是10位数的值
      • 当前格子当前行左边一个格子也是已经计算过的结果,所以也需要根据10位数的值来判断死活情况

    原地修改代码

    class Solution {
        public void gameOfLife(int[][] board) {
            int m = board.length;
            int n = board[0].length;
            int[][] dir = new int[][]{{1,0},{-1,0},{0,1},{0,-1},{1,-1},{1,1},{-1,1},{-1,-1}};
            int cnt = 0;
            for (int row = 0; row < m; row++) {
                for (int col = 0; col < n; col++) {
                    cnt = 0;
                    for (int i = 0; i < dir.length; i++) {
                        int x = row+dir[i][0];
                        int y = col+dir[i][1];
                        if (x<0||y<0||x>=m||y>=n){
                            continue;
                        }
                        if (dir[i][0]<0 || (dir[i][0]==0 && dir[i][1]<0)){
                            cnt += board[x][y]/10;
                        }else{
                            cnt += board[x][y];
                        }
                    }
                    board[row][col] = board[row][col]*10 + cnt;
                }
            }
    
            for (int row = 0; row < m; row++) {
                for (int col = 0; col < n; col++) {
                    cnt = board[row][col] % 10;
                    int status = board[row][col]/10;
                    if (status==1){
                        board[row][col] = (cnt == 2 || cnt == 3)?1:0;
                    }else{
                        board[row][col] =  cnt==3?1:0;
                    }
                }
            }
        }
    }

    LeetCode刷题【1512】好数对的数目

    给你一个整数数组 nums

    如果一组数字 (i,j) 满足 nums[i] == nums[j]i < j ,就可以认为这是一组 好数对

    返回好数对的数目。

     

    示例 1:

    输入:nums = [1,2,3,1,1,3]
    输出:4
    解释:有 4 组好数对,分别是 (0,3), (0,4), (3,4), (2,5) ,下标从 0 开始
    

    示例 2:

    输入:nums = [1,1,1,1]
    输出:6
    解释:数组中的每组数字都是好数对

    示例 3:

    输入:nums = [1,2,3]
    输出:0
    

     

    提示:

    • 1 <= nums.length <= 100
    • 1 <= nums[i] <= 100
    Related Topics
    • 数组
    • 哈希表
    • 数学
    • 计数

  • 👍 92
  • 👎 0
  • 哈希表边统计数字次数边更新结果,一次遍历

    1. 建个哈希表int[] arr = new int[101]统计每个数字出现的次数,题目中已知1 <= nums[i] <= 100
    2. 遍历数组int[] nums,统计同一个数字出现的次数,每统计一个数字num,这个数字可以和之前的统计到的所有数字num分别组成好数对,即新增了arr[num]-1对好数对
    3. 返回最终结果

    代码

    class Solution {
        public int numIdenticalPairs(int[] nums) {
            int[] arr = new int[101];
            int ans = 0;
            for (int num : nums) {
                arr[num]++;
                ans += arr[num]-1;
            }
            return ans;
        }
    }

    LeetCode刷题【754】到达终点数字

    在一根无限长的数轴上,你站在0的位置。终点在target的位置。

    你可以做一些数量的移动 numMoves :

    • 每次你可以选择向左或向右移动。
    • i 次移动(从  i == 1 开始,到 i == numMoves ),在选择的方向上走 i 步。

    给定整数 target ,返回 到达目标所需的 最小 移动次数(即最小 numMoves

     

    示例 1:

    输入: target = 2
    输出: 3
    解释:
    第一次移动,从 0 到 1 。
    第二次移动,从 1 到 -1 。
    第三次移动,从 -1 到 2 。
    

    示例 2:

    输入: target = 3
    输出: 2
    解释:
    第一次移动,从 0 到 1 。
    第二次移动,从 1 到 3 。
    

     

    提示:

    • -109 <= target <= 109
    • target != 0
    Related Topics
    • 数学
    • 二分查找

  • 👍 180
  • 👎 0
  • 数学分析计算

    如图

    1. 假设从左往右走了k次到达M点刚好越过或者达到target点,即k(k+1)/2 ≥ target
    2. 那么我们即需要再这之前往反方向移动距离为D
    3. 又,因为反向移动了,仍需掉头继续往这个方向移动,即假设我们往反向移动了的距离为x
    4. 那么我们就有了2x = D这样的结果,即反向移动加折回到原点0的距离就是我们原先应当到达M点,因为折返损耗了距离D而只能达到位置target的情况
    5. 即有,x = D/2,那么我们知道D必然需要为一个偶数,因为x必然是一个整数

    举个简单的例子
    target = 8
    那么我们知道 k = 4 的时候,能到达M点位置为10,测试相差距离D为2,即需要往反向移动x = D/2 = 1距离,那么我们就知道应当按照- 1 + 2 + 3 + 4 = 8的方法运行

    代码

    class Solution {
        public int reachNumber(int target) {
            if (target==0)return 0;
            target = Math.abs(target);
            int k = (int) Math.sqrt(2 * target);
            k--;
            while ((1 + ++k) * k / 2 < target){}
            int d = (1 + k) * k / 2 - target;
            while (d%2!=0) {
                d += ++k;
            }
            return k;
        }
    }

    LeetCode刷题【851】喧闹和富有

    有一组 n 个人作为实验对象,从 0n - 1 编号,其中每个人都有不同数目的钱,以及不同程度的安静值(quietness)。为了方便起见,我们将编号为 x 的人简称为 “person x “。

    给你一个数组 richer ,其中 richer[i] = [ai, bi] 表示 person ai 比 person bi 更有钱。另给你一个整数数组 quiet ,其中 quiet[i] 是 person i 的安静值。richer 中所给出的数据 逻辑自洽(也就是说,在 person x 比 person y 更有钱的同时,不会出现 person y 比 person x 更有钱的情况 )。

    现在,返回一个整数数组 answer 作为答案,其中 answer[x] = y 的前提是,在所有拥有的钱肯定不少于 person x 的人中,person y 是最安静的人(也就是安静值 quiet[y] 最小的人)。

     

    示例 1:

    输入:richer = [[1,0],[2,1],[3,1],[3,7],[4,3],[5,3],[6,3]], quiet = [3,2,5,4,6,1,7,0]
    输出:[5,5,2,5,4,5,6,7]
    解释: 
    answer[0] = 5,
    person 5 比 person 3 有更多的钱,person 3 比 person 1 有更多的钱,person 1 比 person 0 有更多的钱。
    唯一较为安静(有较低的安静值 quiet[x])的人是 person 7,
    但是目前还不清楚他是否比 person 0 更有钱。
    answer[7] = 7,
    在所有拥有的钱肯定不少于 person 7 的人中(这可能包括 person 3,4,5,6 以及 7),
    最安静(有较低安静值 quiet[x])的人是 person 7。
    其他的答案也可以用类似的推理来解释。
    

    示例 2:

    输入:richer = [], quiet = [0]
    输出:[0]
    
     

    提示:

    • n == quiet.length
    • 1 <= n <= 500
    • 0 <= quiet[i] < n
    • quiet 的所有值 互不相同
    • 0 <= richer.length <= n * (n - 1) / 2
    • 0 <= ai, bi < n
    • ai != bi
    • richer 中的所有数对 互不相同
    • richer 的观察在逻辑上是一致的
    Related Topics
    • 深度优先搜索
    • 拓扑排序
    • 数组

  • 👍 204
  • 👎 0
  • DFS,还可以再优化下

    1. 遍历int[][] richer数组,生成一个map,构建当前节点和比他小的person节点的关联关系
    2. 在遍历的过程中,同时知道了哪些节点没有比他大的节点,存放于HashSet<Integer> starter
    3. starter遍历逐个进行DFS,利用int[] quiet数组中对应的喧嚣值往下深搜的时候代入更新到结果int[] ans数组中
    4. 由于在初始化的时候赋值了Arrays.fill(ans,501)每个值为501,所以如果当前喧嚣值比当前的ans[i]大或者等于的话就不用继续往下更新了
    5. 最终int[] ans数组中存的是当前对应的quiet值,又因为quiet 的所有值 互不相同所以根据反向映射关系重新赋值更新为对应的person
    6. 其实int[] ans中也可以一开始就存对应的person,不用最终再转换一遍,但是一开始用quiet来比较运算比较直观点,所以就先这么写了
    7. 最终结果返映射的时候,如果当前ans[i]依然等于501,说明这个点是独立的,不和其他点有相关联,那么对应依旧为quiet[i]
    8. int[] ans数组也可以一开始就等于int[] quiet数组,但是这样的话再dfs过程中判断大小的时候需要再借助其他手段判断是否需要再往下层更新

    代码

    class Solution {
    
    //     richer = [[1,0],[2,1],[3,1],[3,7],[4,3],[5,3],[6,3]],
    //     quiet = [3,2,5,4,6,1,7,0]
    //        6    5
    //        ↓  ↙
    //    2   3  ←  4
    //    ↓ ↙   ↘
    //    1       7
    //    ↓
    //    0
    
        HashMap<Integer, HashSet<Integer>> map = new HashMap<>();
        int[] ans;
        int[] quiet;
        int[] quietToPerson;
    
        public int[] loudAndRich(int[][] richer, int[] quiet) {
            this.quiet = quiet;
            ans = new int[quiet.length];
            Arrays.fill(ans,501);
            quietToPerson = new int[quiet.length];
            for (int i = 0; i < quiet.length; i++) {
                quietToPerson[quiet[i]] = i;
            }
    
            HashSet<Integer> starter = new HashSet<>();
            for (int[] ints : richer) {
                if (!map.containsKey(ints[0])){
                    map.put(ints[0],new HashSet<>());
                }
                map.get(ints[0]).add(ints[1]);
                starter.remove(ints[1]);
                starter.add(ints[0]);
            }
            starter.forEach(i -> {
                dfs(i,quiet[i]);
            });
    
            for (int i = 0; i < ans.length; i++) {
                ans[i]= quietToPerson[ans[i]>500?quiet[i]:ans[i]];
            }
            return ans;
        }
    
        public void dfs(int num, int min){
            if (min > quiet[num]){
                min = quiet[num];
            }
            if (ans[num] > min){
                ans[num] = min;
            }else{
                return;
            }
            if (map.containsKey(num)){
                int finalMin = min;
                map.get(num).forEach(integer -> {
                    dfs(integer, finalMin);
                });
            }
        }
    }

    LeetCode刷题【878】第 N 个神奇数字

    一个正整数如果能被 ab 整除,那么它是神奇的。

    给定三个整数 na , b ,返回第 n 个神奇的数字。因为答案可能很大,所以返回答案 对 109 + 7 取模 后的值。

     

    示例 1:

    输入:n = 1, a = 2, b = 3
    输出:2
    

    示例 2:

    输入:n = 4, a = 2, b = 3
    输出:6
    

     

    提示:

    • 1 <= n <= 109
    • 2 <= a, b <= 4 * 104

     

    Related Topics
    • 数学
    • 二分查找

  • 👍 100
  • 👎 0
  • 二分查找,最大公约数,最小公倍数
    三个必要知识

    gcd(long a, long b)求最大公约数 辗转相除法,
    lcm(long a, long b)求最小公倍数,
    二分查找

    从简单情况说起,设有一个数字i,从1i过程中能整除a的数字有i/a
    那么同样的能整除b的数字有i/b
    能同时整除ab的数字有i/lcm(a, b)个,lcm(a, b)a``b的最小公倍数,

    那么我们就可以知道,数字n以下,符合题意的数字有n/a + n/b - n/lcm(a,b)个,需要减去多计数一遍的最小公倍数个数

    那么接下来就是用二分法来找到这个恰好能符合题意的数字

    代码

    class Solution {
        public int nthMagicalNumber(int n, int a, int b) {
            long mod = (long)(1e9 + 7);
            long l = 2;
            long r = (long) n * Math.min(a,b);
            while (l < r){
                long mid = l + ((r-l)>>1);
                if(f(a,b,mid) < n){
                    l = mid + 1;
                }else{
                    r = mid;
                }
            }
            return (int)(l % mod);
        }
    
        long gcd(long a, long b) {
            return b == 0 ? a : gcd(b, a % b);
        }
    
        long lcm(long a, long b) {
            return (a * b) / gcd(a, b);
        }
    
        long f(long a, long b, long x) {
            return (x / a) + (x / b) - (x / lcm(a, b));
        }
    }