月度归档: 2022年6月

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));
        }
    }

    LeetCode刷题【1447】最简分数

    给你一个整数 n ,请你返回所有 0 到 1 之间(不包括 0 和 1)满足分母小于等于  n 的 最简 分数 。分数可以以 任意 顺序返回。

     

    示例 1:

    输入:n = 2
    输出:["1/2"]
    解释:"1/2" 是唯一一个分母小于等于 2 的最简分数。

    示例 2:

    输入:n = 3
    输出:["1/2","1/3","2/3"]
    

    示例 3:

    输入:n = 4
    输出:["1/2","1/3","1/4","2/3","3/4"]
    解释:"2/4" 不是最简分数,因为它可以化简为 "1/2" 。

    示例 4:

    输入:n = 1
    输出:[]
    

     

    提示:

    • 1 <= n <= 100
    Related Topics
    • 数学
    • 字符串
    • 数论

  • 👍 84
  • 👎 0
  • GCD辗转相除法【欧几里德算法(Euclidean algorithm)】
    相关知识链接碾转相除法

    这是一个求最大公约数的方法

    1. 实现一个gcd(int a, int b)辗转相除方法,判断两个数的最大公约数是否为1
    2. 如果为1则表明这两个数互质
    3. 剩下的就是枚举从in和从i+1n的分子分母关系了
    class Solution {
        public List<String> simplifiedFractions(int n) {
            List<String> list = new ArrayList<>();
            for (int i = 1; i <= n; i++) {
                for (int j = i+1; j <=n ; j++) {
                    if (gcd(i,j)==1){
                        list.add(i+"/"+j);
                    }
                }
            }
            return list;
        }
    
        int gcd(int a, int b){
            if (b==0){
                return a;
            }
            return gcd(b,a%b);
        }
    }

    LeetCode刷题【114】二叉树展开为链表

    给你二叉树的根结点 root ,请你将它展开为一个单链表:

    • 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null
    • 展开后的单链表应该与二叉树 先序遍历 顺序相同。

     

    示例 1:

    输入:root = [1,2,5,3,4,null,6]
    输出:[1,null,2,null,3,null,4,null,5,null,6]
    

    示例 2:

    输入:root = []
    输出:[]
    

    示例 3:

    输入:root = [0]
    输出:[0]
    

     

    提示:

    • 树中结点数在范围 [0, 2000]
    • -100 <= Node.val <= 100

     

    进阶:你可以使用原地算法(O(1) 额外空间)展开这棵树吗?

    Related Topics
    • 深度优先搜索
    • 链表
    • 二叉树

  • 👍 1219
  • 👎 0
  • 前序遍历,原地递归修改
    按照题意为前序遍历的结果,声明一个TreeNode pre记录前一个访问的节点

    这样直接进行前序遍历,把当前节点挂载到pre节点的right上,记得要清除left节点

    遍历当前节点的时候leftright节点的值记得先存下来,会在递归的时候被修改掉

    代码

    class Solution {
        /*
            1
        2       5
      3   4   n   6
    
            1
          n   2
            n   3
              n   4
                n   5
                  n   6
    
         */
        TreeNode pre;
        public void flatten(TreeNode root) {
            dfs(root);
        }
    
    
        public void dfs(TreeNode node){
            if (null == node){
                return;
            }
            if (pre!=null){
                pre.right = node;
            }
            pre = node;
            TreeNode left = node.left;
            TreeNode right = node.right;
            node.left = null;
            dfs(left);
            dfs(right);
        }
    }