LeetCode 668. 乘法表中第k小的数 / 462. 最少移动次数使数组元素相等 II / 436. 寻找右区间

668. 乘法表中第k小的数

2022.5.18 每日一题

题目描述

几乎每一个人都用 乘法表。但是你能在乘法表中快速找到第k小的数字吗?

给定高度m 、宽度n 的一张 m * n的乘法表,以及正整数k,你需要返回表中第k 小的数字。

例 1:

输入: m = 3, n = 3, k = 5
输出: 3
解释:
乘法表:
1 2 3
2 4 6
3 6 9
第5小的数字是 3 (1, 2, 2, 3, 3).

例 2:

输入: m = 2, n = 3, k = 6
输出: 6
解释:
乘法表:
1 2 3
2 4 6
第6小的数字是 6 (1, 2, 2, 3, 4, 6).

注意:

m 和 n 的范围在 [1, 30000] 之间。
k 的范围在 [1, m * n] 之间。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/kth-smallest-number-in-multiplication-table
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

需要想到将问题转换为找小于或大于某个数的数字个数,就有思路了

官解又将中间统计个数的式子简化了一下
在这里插入图片描述

当i<=x/n时,这几行的所有数都算,所以有前半部分的式子
后半部分就是正常的

class Solution {
    public int findKthNumber(int m, int n, int k) {
        //左上角小于右下角,暴力过不了只能考虑二分啥的
        //能不能直接计算小于某个数的个数或者大于某个数的个数,好像是可以的
        //比如对于一个数x,小于它的数可以遍历每一行计算
        //然后二分找第k小就行了

        int left = 1;
        int right = m * n;
        while(left < right){
            int mid = (right - left) / 2 + left;
            //找小于等于num的个数
            int num = 0;
            for(int i = 1; i <= m; i++){
                int t = mid / i;
                num += n < t ? n : t;
            }

            if(num < k){
                left = mid + 1;
            }else{
                right = mid;
            }
        }
        return left;
    }
}

462. 最少移动次数使数组元素相等 II

2022.5.19 每日一题

题目描述

给你一个长度为 n 的整数数组 nums ,返回使所有数组元素相等需要的最少移动数。

在一步操作中,你可以使数组中的一个元素加 1 或者减 1 。

示例 1:

输入:nums = [1,2,3]
输出:2
解释:
只需要两步操作(每步操作指南使一个元素加 1 或减 1):
[1,2,3] => [2,2,3] => [2,2,2]

示例 2:

输入:nums = [1,10,2,9]
输出:16

提示:

n == nums.length
1 <= nums.length <= 10^5
-10^9 <= nums[i] <= 10^9

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/minimum-moves-to-equal-array-elements-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

刚开始想着平均数应该就是最小移动次数的,写了如下代码,结果被1 0 0 8 6这个例子教育了

class Solution {
    public int minMoves2(int[] nums) {
        //到达平均数,移动数最少,四舍五入取最接近的平均数
        int l = nums.length;
        long sum = 0;
        for(int n : nums){
            sum += n;
        }
        long ave1 = sum / l;
        long ave2 = ave1 + 1;
        long step1 = 0, step2 = 0;
        for(int n : nums){
            step1 += Math.abs(ave1 - n);
            step2 += Math.abs(ave2 - n);
        }
        return (int)Math.min(step1, step2);
    }
}

然后想了一下众数,很明显有反例,又试了一下中位数,过了
为什么是中位数呢,因为在排序以后,将前后两个数两两配对,如nums[0]和nums[n-1],nums[1]和nums[n-2],可以发现,要想使得移动次数最小,当这个数取最中间的数时,左边的数变成这个数要加,右边的数变成这个数要减,即每一个区间移动的次数都是区间上限减下限(就两个数,就是大减小);而如果取中间左边的数ll,那么,有的区间就会都是大于ll,那么移动的次数肯定是大于上限减下限,取右边的数rr同理
因此直接取中位数最优

class Solution {
    public int minMoves2(int[] nums) {
        //那到底怎么移动是最小的呢,难道是移动到中位数吗
        int l = nums.length;
        Arrays.sort(nums);
        int mid = nums[l / 2];
        long step = 0;
        for(int n : nums){
            step += Math.abs(mid - n); 
        }
        return (int)step;
    }
}

436. 寻找右区间

2022.5.20 每日一题

题目描述

给你一个区间数组 intervals ,其中 intervals[i] = [starti, endi] ,且每个 starti 都 不同 。

区间 i 的 右侧区间 可以记作区间 j ,并满足 startj >= endi ,且 startj 最小化 。

返回一个由每个区间 i 的 右侧区间 的最小起始位置组成的数组。如果某个区间 i 不存在对应的 右侧区间 ,则下标 i 处的值设为 -1 。

示例 1:

输入:intervals = [[1,2]]
输出:[-1]
解释:集合中只有一个区间,所以输出-1。

示例 2:

输入:intervals = [[3,4],[2,3],[1,2]]
输出:[-1,0,1]
解释:对于 [3,4] ,没有满足条件的“右侧”区间。
对于 [2,3] ,区间[3,4]具有最小的“右”起点;
对于 [1,2] ,区间[2,3]具有最小的“右”起点。

示例 3:

输入:intervals = [[1,4],[2,3],[3,4]]
输出:[-1,2,-1]
解释:对于区间 [1,4] 和 [3,4] ,没有满足条件的“右侧”区间。
对于 [2,3] ,区间 [3,4] 有最小的“右”起点。

提示:

1 <= intervals.length <= 2 * 10^4
intervals[i].length == 2
-10^6 <= starti <= endi <= 10^6
每个间隔的起点都 不相同

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/find-right-interval
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

哈希表,排序+二分

class Solution {
    public int[] findRightInterval(int[][] intervals) {
        //哈希表存,然后排序就行了二分找就行了
        int l = intervals.length;
        
        List<int[]> list = new ArrayList<>();
        Map<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < l; i++){
            list.add(new int[]{intervals[i][0], i});
            map.put(intervals[i][0], i);
        }

        Collections.sort(list, (a, b) -> (a[0] - b[0]));

        int[] res = new int[l];
        for(int i = 0; i < l; i++){
            int[] temp = intervals[i];
            int end = temp[1];
            //找比end大的第一个数
            int left = 0;
            int right = l;
            while(left < right){
                int mid = (right - left) / 2 + left;
                if(list.get(mid)[0] < end)
                    left = mid + 1;
                else
                    right = mid;
            }
            if(left == l)
                res[i] = -1;
            else
                res[i] = map.get(list.get(left)[0]);
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值