标签: 数组

LeetCode刷题【941】有效的山脉数组

给定一个整数数组 arr,如果它是有效的山脉数组就返回 true,否则返回 false

让我们回顾一下,如果 arr 满足下述条件,那么它是一个山脉数组:

  • arr.length >= 3
  • 在 0 < i < arr.length - 1 条件下,存在 i 使得:
    • arr[0] < arr[1] < ... arr[i-1] < arr[i]
    • arr[i] > arr[i+1] > ... > arr[arr.length - 1]

 

示例 1:

输入:arr = [2,1]
输出:false

示例 2:

输入:arr = [3,5,5]
输出:false

示例 3:

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

 

提示:

  • 1 <= arr.length <= 104
  • 0 <= arr[i] <= 104
Related Topics
  • 数组

  • 👍 178
  • 👎 0
  • 双指针从两头往中间遍历
    双指针从两头往中间遍历
    最终判断两个指针是否在中间相遇,相遇点不能是左边界也不能是又边界

    代码

    class Solution {
        public boolean validMountainArray(int[] arr) {
            int l = 0;
            int r = arr.length-1;
            while (l<r && arr[l+1] > arr[l]){
                l++;
            }
            while (r>0 && arr[r-1] > arr[r]){
                r--;
            }
            return l==r && l!=0 && l != arr.length-1;
        }
    }

    LeetCode刷题【475】供暖器

    冬季已经来临。 你的任务是设计一个有固定加热半径的供暖器向所有房屋供暖。

    在加热器的加热半径范围内的每个房屋都可以获得供暖。

    现在,给出位于一条水平线上的房屋 houses 和供暖器 heaters 的位置,请你找出并返回可以覆盖所有房屋的最小加热半径。

    说明:所有供暖器都遵循你的半径标准,加热的半径也一样。

     

    示例 1:

    输入: houses = [1,2,3], heaters = [2]
    输出: 1
    解释: 仅在位置2上有一个供暖器。如果我们将加热半径设为1,那么所有房屋就都能得到供暖。
    

    示例 2:

    输入: houses = [1,2,3,4], heaters = [1,4]
    输出: 1
    解释: 在位置1, 4上有两个供暖器。我们需要将加热半径设为1,这样所有房屋就都能得到供暖。
    

    示例 3:

    输入:houses = [1,5], heaters = [2]
    输出:3
    

     

    提示:

    • 1 <= houses.length, heaters.length <= 3 * 104
    • 1 <= houses[i], heaters[i] <= 109
    Related Topics
    • 数组
    • 双指针
    • 二分查找
    • 排序

  • 👍 406
  • 👎 0
  • 简单粗暴点的二分

    代码

    class Solution {
        public int findRadius(int[] houses, int[] heaters) {
            Arrays.sort(heaters);
            int ans = 0;
            for (int house : houses) {
                int l = 0;
                int r = heaters.length-1;
                int tmp = (int) 1e9+1;
                while (l<=r){
                    int mid = (l + r)/2;
                    //怕遗漏边缘情况不好处理就每次二分的结果都比较下
                    tmp = Math.min(tmp, Math.abs(heaters[mid] - house));
                    if (heaters[mid] > house){
                        r = mid-1;
                    }else if (heaters[mid] < house){
                        l = mid+1;
                    }else {
                        break;
                    }
                }
                ans = Math.max(ans,tmp);
            }
            return ans;
        }
    }

    LeetCode刷题【1610】可见点的最大数目

    给你一个点数组 points 和一个表示角度的整数 angle ,你的位置是 location ,其中 location = [posx, posy]points[i] = [xi, yi] 都表示 X-Y 平面上的整数坐标。

    最开始,你面向东方进行观测。你 不能 进行移动改变位置,但可以通过 自转 调整观测角度。换句话说,posxposy 不能改变。你的视野范围的角度用 angle 表示, 这决定了你观测任意方向时可以多宽。设 d 为你逆时针自转旋转的度数,那么你的视野就是角度范围 [d - angle/2, d + angle/2] 所指示的那片区域。

    对于每个点,如果由该点、你的位置以及从你的位置直接向东的方向形成的角度 位于你的视野中 ,那么你就可以看到它。

    同一个坐标上可以有多个点。你所在的位置也可能存在一些点,但不管你的怎么旋转,总是可以看到这些点。同时,点不会阻碍你看到其他点。

    返回你能看到的点的最大数目。

     

    示例 1:

    输入:points = [[2,1],[2,2],[3,3]], angle = 90, location = [1,1]
    输出:3
    解释:阴影区域代表你的视野。在你的视野中,所有的点都清晰可见,尽管 [2,2] 和 [3,3]在同一条直线上,你仍然可以看到 [3,3] 。

    示例 2:

    输入:points = [[2,1],[2,2],[3,4],[1,1]], angle = 90, location = [1,1]
    输出:4
    解释:在你的视野中,所有的点都清晰可见,包括你所在位置的那个点。

    示例 3:

    输入:points = [[1,0],[2,1]], angle = 13, location = [1,1]
    输出:1
    解释:如图所示,你只能看到两点之一。

     

    提示:

    • 1 <= points.length <= 105
    • points[i].length == 2
    • location.length == 2
    • 0 <= angle < 360
    • 0 <= posx, posy, xi, yi <= 100
    Related Topics
    • 几何
    • 数组
    • 数学
    • 排序
    • 滑动窗口

  • 👍 113
  • 👎 0
  • 相对坐标信息转换为弧度信息,排序后滑窗统计
    前置基本知识

    1. 弧度,360° = π * 2 的弧度,180° = π 弧度
    2. 滑动窗口

    基本步骤

    1. List<List<Integer>> points数组和List<Integer> location数组分别计算,得到各个点对应的弧度值,如果你直接用角度计算的话也一样
    2. 如果这个点和List<Integer> location位置相同,则这个点永远可见,不做计算,并计数永远可见点数量加1
    3. Math.atan2计算的弧度值是从π范围的,直接用于后续计算不行,所以我们将0范围内的数据统一加,即将最终结果映射到0区间中
    4. 前面有永远可见点占位清理下,同时记得对现在已经计算出来的弧度数组重新排序,因为题面中给的List<List<Integer>> points数组位置顺序并非按照弧度顺序排序的
    5. 转链为环,因为这边得到结果是一个数组,不方便计算从结尾到开头时候的情况,即滑窗开始在结尾,滑窗结束在开头的情况,所以我们使用了常用的作法之一,数组复制双倍,这样环的数据就可以处理了,不过不需要整个数组复制双倍,只需把弧度0到窗口弧度内的数据重新接在数组结尾即可
    6. 接下来就是常规的滑动窗口统计窗口内最大数量的问题了,比较简单,不做赘述了
    7. 记得最终结果要加上之前统计到的永远可见点数量

    没做过画图或者3d之类的开发的话这题拿到手可能有点懵,有幸之前学习研究过一段时间ThreeJS,空间、平面信息的转换上能稍微有点感觉,拿到手之后思路还是非常明确的。就是各种边边角角的细节逻辑采坑略多,想想下location点是一个相机Camera,这个angle就是对应的相机镜头的视角应该还是比较好理解的

    从坐标转换为弧度,

    最终将弧度信息映射在从0区间的数据

    代码

    class Solution {
        public int visiblePoints(List<List<Integer>> points, int angle, List<Integer> location) {
            double x1 = (double) location.get(0);
            double y1 = (double) location.get(1);
    
            double[] radianList = new double[points.size()];
            double radianWin = angle * Math.PI / 180;
            int alwaysSee = 0;
    
            int idx = -1;
            for (List<Integer> point : points) {
                double x2 = (double) point.get(0);
                double y2 = (double) point.get(1);
                if (x1 == x2 && y1 == y2){
                    alwaysSee++;
                    continue;
                }
                radianList[++idx] = Math.atan2(y2-y1,x2-x1);
                if (radianList[idx] < 0){
                    radianList[idx] += Math.PI * 2;
                }
            }
            Arrays.sort(radianList);
    
            if (alwaysSee>0){
                double[] tmp = new double[radianList.length-alwaysSee];
                System.arraycopy(radianList,alwaysSee,tmp,0,radianList.length-alwaysSee);
                radianList = tmp;
            }
    
            //开始滑窗统计
            int l = 0;
            int r = 0;
            int max = 0 ;
            //窗口初始化
            while (r + 1 < radianList.length && radianList[r+1] <= radianList[l] + radianWin ){
                r++;
                max = r-l+1;
            }
    
            //在原来的radianList数组之后,在拼上一段radianWin弧度的数据,这段数据为从弧度0开始到弧度radianWin之内的所有数据
            //而这里得0到滑窗弧度范围内的点的值
            if (radianList.length>0 && radianList[l] <= radianWin){
                double[] tmp = new double[radianList.length + max];
                System.arraycopy(radianList,0,tmp,0,radianList.length);
                System.arraycopy(radianList,0,tmp,radianList.length,max);
                for (int i = radianList.length; i < tmp.length; i++) {
                    tmp[i] += Math.PI * 2;
                }
                radianList = tmp;
            }
            //开始滑动
            while (l<radianList.length && radianList[l]+ radianWin <= radianList[radianList.length-1]){
                l++;
                while (r + 1 < radianList.length && radianList[r+1] <= radianList[l]+radianWin ){
                    r++;
                }
                max = Math.max(max,r-l+1);
            }
           return max+alwaysSee;
        }
    }

    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刷题【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刷题【807】保持城市天际线

    给你一座由 n x n 个街区组成的城市,每个街区都包含一座立方体建筑。给你一个下标从 0 开始的 n x n 整数矩阵 grid ,其中 grid[r][c] 表示坐落于 rc 列的建筑物的 高度

    城市的 天际线 是从远处观察城市时,所有建筑物形成的外部轮廓。从东、南、西、北四个主要方向观测到的 天际线 可能不同。

    我们被允许为 任意数量的建筑物 的高度增加 任意增量(不同建筑物的增量可能不同) 。 高度为 0 的建筑物的高度也可以增加。然而,增加的建筑物高度 不能影响 从任何主要方向观察城市得到的 天际线

    不改变 从任何主要方向观测到的城市 天际线 的前提下,返回建筑物可以增加的 最大高度增量总和

     

    示例 1:

    输入:grid = [[3,0,8,4],[2,4,5,7],[9,2,6,3],[0,3,1,0]]
    输出:35
    解释:建筑物的高度如上图中心所示。
    用红色绘制从不同方向观看得到的天际线。
    在不影响天际线的情况下,增加建筑物的高度:
    gridNew = [ [8, 4, 8, 7],
                [7, 4, 7, 7],
                [9, 4, 8, 7],
                [3, 3, 3, 3] ]
    

    示例 2:

    输入:grid = [[0,0,0],[0,0,0],[0,0,0]]
    输出:0
    解释:增加任何建筑物的高度都会导致天际线的变化。
    

     

    提示:

    • n == grid.length
    • n == grid[r].length
    • 2 <= n <= 50
    • 0 <= grid[r][c] <= 100
    Related Topics
    • 贪心
    • 数组
    • 矩阵

  • 👍 229
  • 👎 0
  • 题面的示例已经给出了基本实现思路
    题面的示例已经给出了基本实现思路,顺着这个思路写代码就对了、

    代码

    class Solution {
        public int maxIncreaseKeepingSkyline(int[][] grid) {
            int[] rowMin = new int[grid.length];
            int[] colMin = new int[grid[0].length];
            for (int row = 0; row < grid.length; row++) {
                for (int col = 0; col < grid[row].length; col++) {
                    int height = grid[row][col];
                    rowMin[row] = Math.max(rowMin[row],height);
                    colMin[col] = Math.max(colMin[col],height);
                }
            }
            int ans = 0;
            for (int row = 0; row < grid.length; row++) {
                for (int col = 0; col < grid[row].length; col++) {
                    ans += Math.min(rowMin[row],colMin[col]) - grid[row][col];
                }
            }
            return ans;
        }
    }