天天看点

LeetCode第235场周赛第一题:截断句子第二题:查找用户活跃分钟数第三题:绝对差值和第四题:序列中不同最大公约数的数目

周赛地址:https://leetcode-cn.com/contest/weekly-contest-235/

第一题:截断句子

题目保证k的取值范围是[1, s 中单词的数目],所以,split分隔后,取前k个用空格拼接起来即可。

class Solution {
    public String truncateSentence(String s, int k) {
        StringJoiner stringJoiner = new StringJoiner(" ");
        String[] ss = s.split(" ");
        for (int i = 0; i < k; i++) {
            stringJoiner.add(ss[i]);
        }
        return stringJoiner.toString();
    }
}
           

第二题:查找用户活跃分钟数

看懂题意,根据题目描述操作即可。

class Solution {
    public int[] findingUsersActiveMinutes(int[][] logs, int k) {
        Map<Integer, Set<Integer>> map = new HashMap<>();
        int[] active = new int[k];
        for (int[] log : logs) {
            Set<Integer> set = map.get(log[0]);
            if (set == null) {
                set = new HashSet<>();
            }
            set.add(log[1]);
            map.put(log[0], set);
        }
        for (Map.Entry<Integer, Set<Integer>> entry : map.entrySet()) {
            active[entry.getValue().size() - 1]++;
        }
        return active;
    }
}
           

第三题:绝对差值和

根据差值直接贪心做替换是错误的思路,题目数据水了,当时用差值直接贪心AC的,是错误的做法,提供一组测试数据并介绍正确的做法,正确的做法是用二分查找+贪心。

Hack数据:nums1=[95,1,2,3,4],nums2=[100,2,3,4,5]。

已知 S = ∑ i = 0 n − 1 ∣ n u m s 1 [ i ] − n u m s 2 [ i ] ∣ S=\sum\limits_{i=0}^{n-1}|nums1[i]-nums2[i]| S=i=0∑n−1​∣nums1[i]−nums2[i]∣,假设我们任意操作一次,把nums1中的的值a变成nums1中的值b可以使得 S S S变小,原来nums2中和a同下标的值是c,可以知道 ∣ b − c ∣ |b-c| ∣b−c∣是小于 ∣ a − c ∣ |a-c| ∣a−c∣的,那么变化后 S ′ = S − ( ∣ a − c ∣ − ∣ b − c ∣ ) S'=S-(|a-c|-|b-c|) S′=S−(∣a−c∣−∣b−c∣),要想使得操作后的 S ′ S' S′最小,就要 ∣ a − c ∣ − ∣ b − c ∣ |a-c|-|b-c| ∣a−c∣−∣b−c∣最大,对于某个确定的 i i i,因为 ∣ a − c ∣ |a-c| ∣a−c∣是固定的,就要让 ∣ b − c ∣ |b-c| ∣b−c∣最小,b取自nums1中和c最接近的数字,对每个nums2[i],在nums1的副本中进行二分查找,查找大于等于nums2[i]的最小值min和小于等于nums2[i]的最大值max,再取 M a t h . m a x ( ∣ a − c ∣ − ∣ n u m s 2 [ i ] − m i n ∣ , ∣ a − c ∣ − ∣ n u m s 2 [ i ] − m a x ∣ ) Math.max(|a-c|-|nums2[i]-min|, |a-c|-|nums2[i]-max|) Math.max(∣a−c∣−∣nums2[i]−min∣,∣a−c∣−∣nums2[i]−max∣),这就是nums2[i]对应的最大变化代价,对nums2[i]中的所有代价取最大值,让之前的 S S S减去这个最大值,即是答案。

class Solution {
    public int minAbsoluteSumDiff(int[] nums1, int[] nums2) {
        int length = nums1.length, MOD = 1000000007;
        long cost = 0, result = 0;
        int[] temp = nums1.clone();// 用于排序和二分查找
        Arrays.sort(temp);
        for (int i = 0; i < length; i++) {
            result += Math.abs(nums1[i] - nums2[i]);
        }
        // 找cost的最大值
        for (int i = 0; i < length; i++) {
            int leftBound = left_bound(temp, nums2[i], length), rightBound = right_bound(temp, nums2[i], length);
            if (leftBound != -1) {
                cost = Math.max(cost, Math.abs(nums1[i] - nums2[i]) - Math.abs(temp[leftBound] - nums2[i]));
            }
            if (rightBound != -1) {
                cost = Math.max(cost, Math.abs(nums1[i] - nums2[i]) - Math.abs(temp[rightBound] - nums2[i]));
            }
        }
        return (int) ((result - cost) % MOD);
    }

    private int left_bound(int[] array, int key, int length) {
        int left = 0, right = length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (array[mid] > key) {
                right = mid - 1;
            } else if (array[mid] < key) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return left == length ? -1 : left;
    }

    private int right_bound(int[] array, int key, int length) {
        int left = 0, right = length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (array[mid] > key) {
                right = mid - 1;
            } else if (array[mid] < key) {
                left = mid + 1;
            } else {
                left = mid + 1;
            }
        }
        return right == -1 ? -1 : right;
    }
}
           

第四题:序列中不同最大公约数的数目

根据题目描述,最直观,最暴力的解法就是把所有的子序列都找出来,求每个子序列的GCD,然后过一遍set,求set.size(),然而是行不通的。求子序列就是一个指数规模的时间复杂度了,需要换一个角度考虑。

对于原数组中的所有子序列,无论怎么组合,GCD都不会超过数组中的最大值,于是,可以反向考虑,对[1, max]中的每个数字k,试图构造一个子序列,让子序列的GCD=k,如果能找到,计数器累计,继续判断下一个数字,如果找不到,说明不存在这样的子序列,继续判断下一个数字。

那么,什么样的序列,有可能GCD=k呢?子序列中的每个值一定是k的倍数,才有可能GCD=k,当然,也有可能大于k,比如:2k和4k的GCD=2k。所以,要找GCD=k的子序列就找一遍k的倍数,如果能构造出来GCD=k就可以了判断下一个k了。

class Solution {
    public int countDifferentSubsequenceGCDs(int[] nums) {
        BitSet bitSet = new BitSet(200000);
        int max = 0, result = 0;
        // 用于标记nums里有哪些数
        for (int i : nums) {
            bitSet.set(i);
            max = Math.max(i, max);
        }
        // 判断[1, max]范围内每个值,试图找到一个序列,满足GCD==i
        for (int i = 1; i <= max; i++) {
            int gcd = 0;
            // 类似筛法,只有i的整数倍数字的组合,才有可能使得GCD=i,有目的性的查找,缩小查找范围
            for (int j = i; j <= max; j += i) {// j是i的整数倍
                if (bitSet.get(j)) {// j是nums中的元素
                    gcd = gcd(j, gcd);
                    if (gcd == i) {// 已经找到一个GCD==i的组合了
                        result++;
                        break;
                    }
                }
            }
        }
        return result;
    }

    private int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }
}
           

继续阅读