标签: 排序

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刷题【面试题40】最小的k个数

    输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

     

    示例 1:

    输入:arr = [3,2,1], k = 2
    输出:[1,2] 或者 [2,1]
    

    示例 2:

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

     

    限制:

    • 0 <= k <= arr.length <= 10000
    • 0 <= arr[i] <= 10000
    Related Topics
    • 数组
    • 分治
    • 快速选择
    • 排序
    • 堆(优先队列)

  • 👍 450
  • 👎 0
  • 堆排序

    解题思路

    此处撰写解题思路

    代码

    class Solution {
        public int[] getLeastNumbers(int[] arr, int k) {
            if (k == 0){
                return new int[0];
            }
            PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o2 - o1;
                }
            });
            for (int i : arr) {
                if (queue.size() < k){
                    queue.offer(i);
                }else if(i < queue.peek()){
                    queue.poll();
                    queue.offer(i);
                }
            }
            int[] res = new int[k];
            for (int i = 0; i < res.length; i++) {
                res[i] = queue.poll();
            }
            return res;
        }
    }

    LeetCode刷题【1005】K 次取反后最大化的数组和

    给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

    • 选择某个下标 i 并将 nums[i] 替换为 -nums[i]

    重复这个过程恰好 k 次。可以多次选择同一个下标 i

    以这种方式修改数组后,返回数组 可能的最大和

     

    示例 1:

    输入:nums = [4,2,3], k = 1
    输出:5
    解释:选择下标 1 ,nums 变为 [4,-2,3] 。
    

    示例 2:

    输入:nums = [3,-1,0,2], k = 3
    输出:6
    解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。
    

    示例 3:

    输入:nums = [2,-3,-1,5,-4], k = 2
    输出:13
    解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。
    

     

    提示:

    • 1 <= nums.length <= 104
    • -100 <= nums[i] <= 100
    • 1 <= k <= 104
    Related Topics
    • 贪心
    • 数组
    • 排序

  • 👍 258
  • 👎 0
  • 马马虎虎优先队列

    class Solution {
        public int largestSumAfterKNegations(int[] nums, int k) {
            int sum = 0;
            PriorityQueue<Integer> queue = new PriorityQueue<>();
    
            int absMin = 101;
            for (int num : nums) {
                absMin = Math.min(absMin,Math.abs(num));
                if (num >= 0){
                    sum += num;
                }else{
                    queue.offer(num);
                }
            }
            if (queue.size()>0){
                //把绝对值较大的负数尽量翻转
                while (!queue.isEmpty() && k > 0){
                    sum -= queue.poll();
                    k--;
                }
                if (k==0){
                    //翻转次数用完了,还有数字没翻转
                    while (!queue.isEmpty()){
                        sum += queue.poll();
                    }
                    return sum;
                }
            }
            if (k%2 == 0){
                return sum;
            }else{
                return sum - 2*absMin;
            }
        }
    }

    LeetCode刷题【506】相对名次

    给你一个长度为 n 的整数数组 score ,其中 score[i] 是第 i 位运动员在比赛中的得分。所有得分都 互不相同

    运动员将根据得分 决定名次 ,其中名次第 1 的运动员得分最高,名次第 2 的运动员得分第 2 高,依此类推。运动员的名次决定了他们的获奖情况:

    • 名次第 1 的运动员获金牌 "Gold Medal"
    • 名次第 2 的运动员获银牌 "Silver Medal"
    • 名次第 3 的运动员获铜牌 "Bronze Medal"
    • 从名次第 4 到第 n 的运动员,只能获得他们的名次编号(即,名次第 x 的运动员获得编号 "x")。

    使用长度为 n 的数组 answer 返回获奖,其中 answer[i] 是第 i 位运动员的获奖情况。

     

    示例 1:

    输入:score = [5,4,3,2,1]
    输出:["Gold Medal","Silver Medal","Bronze Medal","4","5"]
    解释:名次为 [1st, 2nd, 3rd, 4th, 5th] 。

    示例 2:

    输入:score = [10,3,8,9,4]
    输出:["Gold Medal","5","Bronze Medal","Silver Medal","4"]
    解释:名次为 [1st, 5th, 3rd, 2nd, 4th] 。
    

     

    提示:

    • n == score.length
    • 1 <= n <= 104
    • 0 <= score[i] <= 106
    • score 中的所有值 互不相同
    Related Topics
    • 数组
    • 排序
    • 堆(优先队列)

     

    • 👍 180
    • 👎 0

    优先队列

    解题思路

    此处撰写解题思路

    代码

    class Solution {
        public String[] findRelativeRanks(int[] score) {
            PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
                @Override
                public int compare(int[] o1, int[] o2) {
                    return o2[0] - o1[0];
                }
            });
            for (int i = 0; i < score.length; i++) {
                queue.add(new int[]{score[i],i});
            }
            String[] arr = new String[score.length];
            String[] top = new String[]{"Gold Medal","Silver Medal","Bronze Medal"};
            int i = -1;
            while (!queue.isEmpty()){
                int[] cur = queue.poll();
                if (++i < top.length){
                    arr[cur[1]] = top[i];
                }else{
                    arr[cur[1]] = (i + 1) + "";
                }
            }
            return arr;
        }
    }

    LeetCode刷题【786】第 K 个最小的素数分数

    给你一个按递增顺序排序的数组 arr 和一个整数 k 。数组 arr1 和若干 素数  组成,且其中所有整数互不相同。

    对于每对满足 0 <= i < j < arr.lengthij ,可以得到分数 arr[i] / arr[j]

    那么第 k 个最小的分数是多少呢?  以长度为 2 的整数数组返回你的答案, 这里 answer[0] == arr[i] 且 answer[1] == arr[j]

     

    示例 1:

    输入:arr = [1,2,3,5], k = 3
    输出:[2,5]
    解释:已构造好的分数,排序后如下所示: 
    1/5, 1/3, 2/5, 1/2, 3/5, 2/3
    很明显第三个最小的分数是 2/5
    

    示例 2:

    输入:arr = [1,7], k = 1
    输出:[1,7]
    

     

    提示:

    • 2 <= arr.length <= 1000
    • 1 <= arr[i] <= 3 * 104
    • arr[0] == 1
    • arr[i] 是一个 素数i > 0
    • arr 中的所有数字 互不相同 ,且按 严格递增 排序
    • 1 <= k <= arr.length * (arr.length - 1) / 2
    Related Topics
    • 数组
    • 二分查找
    • 排序
    • 堆(优先队列)

  • 👍 221
  • 👎 0
  • 优先队列两种实现

    首先,直接上来一个优先队列,自定义排序。

    为了实现这个自定义的排序,我们需要记录几个东西

    1. 分子
    2. 分母
    3. 为了便于比较,相除后的小数值也可以事先计算好,比较的时候就直接比较这个数字即可
    4. 把所有的都放进去之后,我们就开始从头部取出K个即可
    5. 最终得到第K个的分子分母即可

    理解起来也相对比较简单

    代码

    class Solution {
        public int[] kthSmallestPrimeFraction(int[] arr, int k) {
            PriorityQueue<double[]> queue = new PriorityQueue<>(new Comparator<double[]>() {
                @Override
                public int compare(double[] o1, double[] o2) {
                    if (o1[2] >= o2[2]){
                        return 1;
                    }
                    return -1;
                }
            });
            for (int i : arr) {
                for (int j : arr) {
                    queue.offer(new double[]{(double)i,(double)j,((double)i)/j});
                }
            }
            while (--k > 0){
                queue.poll();
            }
            return new int[]{(int)queue.peek()[0],(int)queue.peek()[1]};
        }
    }

    写完这个之后思考一下,我们真的需要把所有的值都算一遍全都存进优先队列么?答案是否定的,

    1. 我们可以把数据分别按照分母分成arr[]数组长度个分组,
    2. 分别维护一个指针,指向当前对应的分子,
    3. 这样我们的优先队列只要维护比较arr[]数组长度个值即可,每当从队列头部取出一个对象时,只需把分子位置往后挪动一位,重新放入队列
    4. 如果分子指针位置已经到达了arr[]数组尾部,则不再需要往队列中添加

    代码

    class Solution {
        public int[] kthSmallestPrimeFraction(int[] arr, int k) {
            PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
                @Override
                public int compare(int[] o1, int[] o2) {
                    if (((double)arr[o1[0]])/arr[o1[1]] >= ((double)arr[o2[0]])/arr[o2[1]]){
                        return 1;
                    }
                    return -1;
                }
            });
            for (int i = 0; i < arr.length; i++) {
                queue.offer(new int[]{0,i});
            }
            while (--k > 0) {
                int[] top = queue.poll();
                if (top[0] < arr.length){
                    top[0]++;
                    queue.offer(top);
                }
            }
            return new int[]{arr[queue.peek()[0]],arr[queue.peek()[1]]};
        }
    
    }

    LeetCode刷题【剑指 Offer 61】扑克牌中的顺子

    若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。

     

    示例 1:

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

     

    示例 2:

    输入: [0,0,1,2,5]
    输出: True

     

    限制:

    数组长度为 5 

    数组的数取值为 [0, 13] .

    Related Topics
  • 数组
  • 排序

  • 👍 250
  • 👎 0
  • 简单统计遍历替代处理

    简单、思路比较清晰

    1. 建一个int[] arr = new int[14];统计次数
    2. 遍历arr数组,从下标arr[1]开始,arr[0]作为特殊情况暂时留着后面有用
    3. 先遍历找到统计次数不是1的下标,表示顺子可能开始的地方,当然也可能不是从这开始的,后面会说,并开始记录连续区间的长度count
    4. 遇到的最基本的情况当前数字统计出现了1次,连续区间长度count加1
    5. 如果某个数字出现了超过1次,必然不能组成长度为5的顺子,发牌员总共就给了5张牌
    6. 如果遇到了某个数字出现次数为0,表示顺子断开了,从原来的arr[0]位置,拿一张通配牌来顶一下
    7. 如果王不够了,已经无计可施了,则直接判断此时的连续区间长度是否达到了5
    8. 如果已经遍历结束了,看下已经统计到的连续区间的长度count是否为5,如果不为5的话再看看有没有多余的通配牌,可以拿来放在当前连续区间的前面,也就是上面的第3条说的,连续区间的开始位置可能不是匹配到的位置,可能在这个位置之前拿来抵用
    9. 当然你也可能摸了5张王,而连续区间长度count为0,而arr[0] = 5,那你就是天选之人 邱森旺 了
    10. 综上最终判断其实是(count + arr[0]) == 5

    代码

    class Solution {
        public boolean isStraight(int[] nums) {
            int[] arr = new int[14];
            for (int num : nums) {
                arr[num]++;
            }
            int idx = 0;
            //从1开始 可匹配任意的 0 先不管
            while (++idx < arr.length && arr[idx]==0){}
            idx--;
            int count = 0;
            while (++idx < arr.length){
                if (arr[idx]>1){
                    //如果有一个数字出现的次数大于1了,则必然不能
                    return false;
                }
                if (arr[idx]==0){
                    if (arr[0]>0){
                        //拿 通配牌  `王` 来顶一个
                        count++;
                        arr[0]--;
                    }else{
                        // `王` 也不够顶了
                        return count==5;
                    }
                }else{
                    //这里是等于1的情况
                    count++;
                }
            }
            //可能有剩余的`王` + 已知的连续个数合起来看看能不能凑齐五个
            return (count+arr[0]) == 5;
        }
    }