【力扣周赛】第 352 场周赛

这篇具有很好参考价值的文章主要介绍了【力扣周赛】第 352 场周赛。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


第 352 场周赛

Q1:2760. 最长奇偶子数组

2760. 最长奇偶子数组

【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数
提示:

1 <= nums.length <= 100
1 <= nums[i] <= 100
1 <= threshold <= 100

解法1——纯纯暴力

因为数据范围特别小,所以怎么暴力都是无所谓的。

解法2——枚举左端点,尝试右端点

class Solution {
    public int longestAlternatingSubarray(int[] nums, int threshold) {
        int n = nums.length, ans = 0;
        for (int l = 0; l < n - ans; ++l) {
            // 不能作为起始端点
            if (nums[l] % 2 != 0 || nums[l] > threshold) continue;
            int r = l + 1;
            // 只要右端点满足条件就进行移动
            while (r < n && nums[r] <= threshold && nums[r] % 2 != nums[r - 1] % 2) ++r;
            ans = Math.max(ans, r - l);     // 更新答案
        }
        return ans;
    }
}

继续优化

可以发现,每个满足条件的子数组是不会重叠的,
【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数

所以在枚举 l 的时候,每次可以将下一个 l 设置成 r。
代码如下:

class Solution {
    public int longestAlternatingSubarray(int[] nums, int threshold) {
        int n = nums.length, ans = 0;
        for (int l = 0; l < n - ans; ++l) {
            // 不能作为起始端点
            if (nums[l] % 2 != 0 || nums[l] > threshold) continue;
            int r = l + 1;                  // 初始化右端点
            // 只要右端点满足条件就进行移动
            while (r < n && nums[r] <= threshold && nums[r] % 2 != nums[r - 1] % 2) ++r;
            ans = Math.max(ans, r - l);     // 更新答案
            l = r - 1;                      // 小优化
        }
        return ans;
    }
}

Q2:2761. 和等于目标值的质数对

2761. 和等于目标值的质数对
【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数
提示:
1 <= n <= 10^6

这道题实际上考察的知识点是 筛质数,相关的知识点可见:【算法】数学相关知识总结 的对应部分。(包括:朴素筛,埃氏筛,欧式筛)

下面代码使用的是欧式筛。

class Solution {
    private final static int MX = (int)1e6;
    private final static int[] primes = new int[78498];     // 1e6以内有78498个质数
    private final static boolean[] np = new boolean[MX + 1];    

    // 预先处理计算出所有的质数
    static {
        int cnt = 0;
        for (int i = 2; i <= MX; ++i) {
            if (!np[i]) {   // 如果这个数没有被筛掉
                primes[cnt++] = i;
            }
            for (int j = 0; primes[j] * i <= MX; ++j) {
                np[primes[j] * i] = true;
                if (i % primes[j] == 0) break;
            }
        }
    }

    public List<List<Integer>> findPrimePairs(int n) {
        List<List<Integer>> ans = new ArrayList();
        for (int x: primes) {
            // 将所有符合答案的质数对加入答案列表
            int y = n - x;
            if (y < x) break;
            if (!np[y]) ans.add(List.of(x, y));
        }
        return ans;
    }
}

筛出质数之后枚举判断就可以了。

一个小优化

如果 n 是奇数,由于只有奇数+偶数=奇数,而偶数中只有 2 是质数,所以如果 n 是奇数时,至多只有一个质数对 (2, n−2)。

那么就在代码里主函数的开头加上:

if (n % 2 == 1) {
    return n > 4 && !np[n - 2]? List.of(List.of(2, n - 2)): List.of();
}

Q3:2762. 不间断子数组

2762. 不间断子数组

【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数
提示:

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

使用滑动窗口来解决,(通常滑动窗口是枚举右端点,然后按条件移动左端点

Q:为什么要枚举右端点更好写?
A:因为枚举右端点,这样移动左端点时信息是枚举过的,是已知的;移动左端点是在缩小范围,通常更好写。

解法1——滑动窗口+维护哈希表

在滑动窗口滑的过程中,我们需要维护当前窗口内的最大值和最小值,当最大值和最小值的差大于 2 时,就需要移动左指针。

普通HashMap

class Solution {
    public long continuousSubarrays(int[] nums) {
        long ans = 0;
        int n = nums.length;
        Map<Integer, Integer> m = new HashMap();
        for (int i = 0, j = 0; i < n; ++i) {    // 双指针,i是右指针,j是左指针
            m.merge(nums[i], 1, Integer::sum);
            while (!check(m)) {                 // 判断哈希表里存的东西是否合理
                m.merge(nums[j], -1, Integer::sum);
                if (m.get(nums[j]) == 0) m.remove(nums[j]);
                ++j;
            }
            ans += i - j + 1;
        }
        return ans;
    }
    
    public boolean check(Map<Integer, Integer> m) {
        int mn = (int)1e9 + 1, mx = 0;
        for (int k: m.keySet()) {
            if (k > mn + 2 || k < mx - 2) return false;
            mn = Math.min(mn, k);
            mx = Math.max(mx, k);
        }
        return true;
    }
}

使用TreeMap

TreeMap是对key排列的HashMap,所以在判断窗口内值是否合理时会方便一些。

class Solution {
    public long continuousSubarrays(int[] nums) {
        long ans = 0;
        // TreeMap是对key排列的HashMap
        TreeMap<Integer, Integer> m = new TreeMap<Integer, Integer>();
        for (int l = 0, r = 0; r < nums.length; ++r) {
            m.merge(nums[r], 1, Integer::sum);
            // 只需比较最大的和最小的key
            while (m.lastKey() - m.firstKey() > 2) {
                int x = nums[l++];
                if (m.get(x) == 1) m.remove(x);
                else m.merge(x, -1, Integer::sum);
            }
            ans += r - l + 1;
        }
        return ans;
    }
}

方便之处在于可以使用 m.firstKey()m.lastKey() 或者键中的最小值和最大值。

补充:Java中的TreeMap

【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数

相关资料:14.集合|Java学习笔记

解法2——滑动窗口+维护单调队列

我们需要维护窗口内的最大值和最小值,很容易就可以联想到单调队列这个数据结构来进行维护。

一个相关的题目是:239. 滑动窗口最大值 ,可以先通过这道题目学习一下如何使用单调队列维护滑动窗口的最大/小值。

这道题目使用两个双端队列,分别维护当前窗口中的最大值和最小值,当最大值和最小值的差不合条件时移动左端点。

class Solution {
    public long continuousSubarrays(int[] nums) {
        long ans = 0;
        // dq1从大到小,dq2从小到大
        Deque<Integer> dq1 = new ArrayDeque(), dq2 = new ArrayDeque();
        for (int i = 0, j = 0; i < nums.length; ++i) {
            // 处理两个单调队列
            while (!dq1.isEmpty() && nums[i] > nums[dq1.peekLast()]) dq1.pollLast();
            while (!dq2.isEmpty() && nums[i] < nums[dq2.peekLast()]) dq2.pollLast();
            dq1.offerLast(i);
            dq2.offerLast(i);
            // 队列里的最大值和最小值不符合条件了
            while (nums[dq1.peekFirst()] > nums[dq2.peekFirst()] + 2) {
                if (dq1.peekFirst() < dq2.peekFirst()) {
                    j = dq1.peekFirst() + 1;
                    dq1.pollFirst();
                }
                else {
                    j = dq2.peekFirst() + 1;
                    dq2.pollFirst();
                }
            }
            ans += i - j + 1;
        }
        return ans;
    }
}

注意这边移动左端点时,需要使用 j = dq1.peekFirst() + 1;j = dq2.peekFirst() + 1;,而不能直接使用 Math.min(dq1.peekFirst(), dq2.peekFirst()) 作为左端点。

补充:相似题目——1438. 绝对差不超过限制的最长连续子数组

1438. 绝对差不超过限制的最长连续子数组
【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数

这道题目和周赛题几乎一模一样,除了 2 换成了 limit 。

class Solution {
    public int longestSubarray(int[] nums, int limit) {
        int n = nums.length, ans = 1;
        TreeMap<Integer, Integer> m = new TreeMap<Integer, Integer>();  // 编译类型和运行类型都得是TreeMap
        for (int l = 0, r = 0; r < n; ++r) {
            m.merge(nums[r], 1, Integer::sum);
            while (m.firstKey() < m.lastKey() - limit) {
                int x = nums[l++];
                if (m.get(x) == 1) m.remove(x);
                else m.merge(x, -1, Integer::sum);
            }
            ans = Math.max(ans, r - l + 1);
        }
        return ans;
    }
}

Q4:2763. 所有子数组中不平衡数字之和⭐⭐⭐⭐⭐

2763. 所有子数组中不平衡数字之和

【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数

提示:

1 <= nums.length <= 1000
1 <= nums[i] <= nums.length

方法1—— O ( n 2 ) O(n^2) O(n2) 枚举

根据数据范围,可以使用 O ( n 2 ) O(n^2) O(n2) 的算法。
枚举左右端点。

class Solution {
    public int sumImbalanceNumbers(int[] nums) {
        int n = nums.length, ans = 0;
        boolean[] st = new boolean[n + 2];      // 记录某个数是否出现过
        for (int i = 0; i < n; ++i) {           // 枚举左端点
            Arrays.fill(st, false);
            st[nums[i]] = true;
            int cnt = 0;
            for (int j = i + 1; j < n; ++j) {   // 枚举右端点
                int x = nums[j];
                if (!st[x]) {
                    ++cnt;
                    if (st[x - 1]) --cnt;       // x 会让 x - 1 失效
                    if (st[x + 1]) --cnt;       // x 会让 x + 1 失效
                    st[x] = true;
                }
                ans += cnt;
            }
        }
        return ans;
    }
}

其实就是枚举每个子数组的不平衡数字。
在对 j 的枚举过程中,每来一个新的数字 nums[j],不平衡的数字数量就先 + 1,然后判断是否存在 nums[j] - 1 和 nums[j] + 1,因为新来的数字会让 nums[j] - 1 失效,以及nums[j] + 1 会让新来的 nums[j] 失效

方法2—— O ( n ) O(n) O(n) 贡献法

单独计算每个数字有多少贡献。

重要何为贡献?
A:在本题中,贡献指的是一个数字,在它的所有子数组中,当它作为较大的那个数字时,有几个比它小得大于1的数字。

只讨论 x=nums[i] 与 x 和 x - 1
左边可以有 x,右边没有 x ,且整个子数组都不包含 x - 1 的子数组的个数(不能包含 x - 1,但是需要有比 x 小的数字,这样才能组成组合产生贡献。这里计算时会计算进去,所以下面会减去 (n + 1) * n / 2)
减去 x 作为子数组最小值的情况 (n + 1) * n / 2。(作为最小值时是没有贡献的,因为它前面没有数字比它小且差 > 1)

以 2 3 1 4 为例,
为了避免重复讨论,我们就只看每个数字 x = nums[i] 与 x、x - 1 的关系,(比如这个例子里的 2 和 3,当我枚举到 2 的时候,就不考虑 3 了,当枚举到 3 时才考虑 2,这样就不会重复考虑 2 和 3 之间的关系)。

下面具体来看这个例子中的结果:
【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数
对于2,我们找有2无1的子数组,有:【2】,【2,3】。其中这两个都是不合法的,因为2是这两个子数组的最小值。
对于3,我们找有3无2的子数组,有:【3】,【3,1】,【3,1,4】。其中【3】是不合法的,因为3是其中的最小值;剩下两个都是合法的。
对于1,一共有3 * 2 = 6 个子数组,最小值都是 1 ,都不合法。
对于4,我们找有4无3的子数组,有:【1,4】,【4】。其中【1,4】合法。

枚举完毕,最终得到 3 个合法的子数组,答案为 3。

思路

枚举每个数字 x = nums[i] ,对于每个数字,往左边看看第一个 x - 1 出现的位置,往右边看看第一个 x - 1 出现的位置,这样就可以计算出包含数字 x 但不包含数字 x - 1 的子数组的数量有多少个了。(这就是下标 i 的贡献)

最后还需要减去 x 作为子数组最小值的情况。
Q:这样的需要被删去的子数组有多少个呢?
A:汇总来看,每个子数组都必定有属于这个子数组的最小值,因此我们要减去所有子数组的个数,即 n ∗ ( n + 1 ) 2 \frac{n*(n+1)}{2} 2n(n+1) = 1+…+ n

class Solution {
    public int sumImbalanceNumbers(int[] nums) {
        int n = nums.length;
        int[] right = new int[n], idx = new int[n + 1];
        Arrays.fill(idx, n);		// 如果右侧没有 x 和 x - 1,那么right[i] = n
        // 从左向右遍历
        for (int i = n - 1; i >= 0; i--) {
            int x = nums[i];
            // right[i] 表示 nums[i] 右侧的 x 和 x-1 的最近下标(不存在时为 n)
            right[i] = Math.min(idx[x], idx[x - 1]);
            idx[x] = i;		// 更新idx[x]表示枚举到的最左侧x的坐标
        }

        int ans = 0;
        Arrays.fill(idx, -1);
        // 从右向左遍历
        for (int i = 0; i < n; i++) {
            int x = nums[i];
            // 统计 x 能产生多少贡献
            ans += (i - idx[x - 1]) * (right[i] - i); // 子数组左端点个数 * 子数组右端点个数
            idx[x] = i;		// 更新idx[x]表示枚举到的最右侧x的坐标
        }
        // 上面计算的时候,每个子数组的最小值必然可以作为贡献,而这是不合法的
        // 所以每个子数组都多算了 1 个不合法的贡献
        return ans - n * (n + 1) / 2;
    }
}

Q:为什么计算 right[i] 时考虑 idx[x] 和 idx[x - 1],而在考虑 left[i] 时直接设置为 idx[x - 1]?
A:考虑样例1,3,3。在枚举第一个3时,有【3】,【1,3】;在枚举第二个3时,有【3】,【3,3】,【1,3,3】。由于设置了向右看时,看到 x 本身也会停下来,所以在枚举第一个3时才不会重复计算【1,3,3】这个子数组。(因为这两个3排序后只能有1个数字3和数字1挨边,即产生贡献。)

时间复杂度和空间复杂度都是 O ( n ) O(n) O(n)

补充:相关题目——2681. 英雄的力量

2681. 英雄的力量
【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数

一个标标准准的贡献题。

【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数
提示:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^9

根据数据范围,这道题目必须使用 O ( n ) O(n) O(n) 时间复杂度的算法。

由于是任选一部分英雄,因此数据的顺序不影响最后的结果,所以可以先排序

从前向后进行枚举,每次枚举到一个数字,计算其作为最大值的贡献

下面举一个例子:
考虑 a, b, c, d, e 五个数字,当前枚举到了 d。
此时 a, b, c 分别作为最小值的贡献为: a ∗ 2 2 + b ∗ 2 1 + c ∗ 2 0 a*2^2 + b*2^1 + c*2^0 a22+b21+c20,记为 s s s。(因为选a的时候b和c都是可选可不选,选b的时候c可选可不选,选c的时候a和b都不能选)
那么此时对答案的贡献为: d 3 + d 2 ∗ s = d 2 ∗ ( d + s ) d^3+d^2*s = d^2*(d+s) d3+d2s=d2(d+s)

继续枚举到 e e e
此时 a, b, c, d 分别作为最小值的贡献为: a ∗ 2 3 + b ∗ 2 2 + c ∗ 2 1 + d ∗ 2 0 = 2 ∗ ( a ∗ 2 2 + b ∗ 2 1 + c ∗ 2 0 ) + d ∗ 2 0 = 2 ∗ s + d a*2^3 + b*2^2 + c*2^1 + d*2^0 = 2*(a*2^2 + b*2^1 + c*2^0) + d*2^0 = 2 *s + d a23+b22+c21+d20=2(a22+b21+c20)+d20=2s+d
得到了新的 s = 2 ∗ s + n u m s [ i ] s = 2 * s + nums[i] s=2s+nums[i]

此时我们就得到了两个重要的递推式
a n s + = n u m s [ i ] ∗ n u m s [ i ] ∗ ( n u m s [ i ] + s ) ans += nums[i] * nums[i] * (nums[i] + s) ans+=nums[i]nums[i](nums[i]+s)
s = 2 ∗ s + n u m s [ i ] s = 2 * s + nums[i] s=2s+nums[i]

class Solution {
    private static final long MOD = (int)1e9 + 7;

    public int sumOfPower(int[] nums) {
        long ans = 0, sum = 0;
        // 元素的顺序不影响答案,所以先排序
        Arrays.sort(nums);
        // 枚举每个英雄,计算其作为最大值时的力量贡献
        for (long x: nums) {
            ans = (ans + x * x % MOD * (x + sum)) % MOD;	// 更新答案
            sum = (sum * 2 + x) % MOD;						// 更新 s
        }
        return (int)ans;
    }
}

更多相关题目见:【算法】贡献法相关题目练习

成绩记录

【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数

很垃圾!最后一题其实很简单但可惜没做出来!

【力扣周赛】第 352 场周赛,算法,leetcode,算法,贡献法,筛质数文章来源地址https://www.toymoban.com/news/detail-526660.html

到了这里,关于【力扣周赛】第 352 场周赛的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 力扣周赛日记350

    早上兴高采烈起床写周赛,结果写完两题开始坐牢。菜的很。 LeetCode 第 350 场周赛 LeetCode 6901. 总行驶距离 2.1.1 题意 卡车两个油箱,耗油1L行驶10km。油箱A耗5L,油箱B给邮箱A油1L。油箱A空后停止行驶,求可行使距离。 2.1.2 分析 开始想O(1)解法,发现这题主要问题在油箱B给了油箱

    2024年02月10日
    浏览(38)
  • 【力扣周赛】第 113 场双周赛(贪心&异或性质&换根DP)

    https://leetcode.cn/contest/biweekly-contest-113/ https://leetcode.cn/problems/minimum-right-shifts-to-sort-the-array/ 提示: 1 = nums.length = 100 1 = nums[i] = 100 nums 中的整数互不相同。 因为数据范围很小,所以可以从小到大枚举可能的答案。 https://leetcode.cn/problems/minimum-array-length-after-pair-removals/ 提示: 1

    2024年02月07日
    浏览(32)
  • 力扣第357场周赛补题

    6925. 故障键盘 - 力扣(LeetCode) 思路:模拟 6953. 判断是否能拆分数组 - 力扣(LeetCode) 思路:脑筋急转弯 2812. 找出最安全路径 - 力扣(LeetCode) 思路:多源bfs+倒序枚举+并查集

    2024年02月14日
    浏览(37)
  • 【LeetCode周赛】LeetCode第358场周赛

    给你一个下标从0开始的整数数组nums。请你从nums中找出和最大的一对数,且这两个数数位上最大的数字相等。 返回最大和,如果不存在满足题意的数字对,返回 -1 。 示例 1: 输入:nums = [51,71,17,24,42] 输出:88 解释: i = 1 和 j = 2 ,nums[i] 和 nums[j] 数位上最大的数字相等,且这

    2024年02月12日
    浏览(41)
  • 【LeetCode周赛】LeetCode第359场周赛

    给你一个字符串数组 words 和一个字符串 s ,请你判断 s 是不是 words 的 首字母缩略词 。 如果可以按顺序串联 words 中每个字符串的第一个字符形成字符串 s ,则认为 s 是 words 的首字母缩略词。例如,“ab” 可以由 [“apple”, “banana”] 形成,但是无法从 [“bear”, “aardvark”

    2024年02月10日
    浏览(41)
  • 【LeetCode周赛】LeetCode第370场周赛

    一场比赛中共有 n 支队伍,按从 0 到 n - 1 编号。 给你一个下标从 0 开始、大小为 n * n 的二维布尔矩阵 grid 。对于满足 0 = i, j = n - 1 且 i != j 的所有 i, j :如果 grid[i][j] == 1,那么 i 队比 j 队 强 ;否则,j 队比 i 队 强 。 在这场比赛中,如果不存在某支强于 a 队的队伍,则认为

    2024年02月05日
    浏览(48)
  • [LeetCode周赛复盘] 第 359 场周赛20230820

    T1 模拟。 T2 数学贪心。 T3 dp。 T4 分组+滑窗。 2828. 判别首字母缩略词 1. 题目描述 2. 思路分析 按题意模拟即可。 3. 代码实现 2829. k-avoiding 数组的最小总和 1. 题目描述 2. 思路分析 贪心 1~k-1中,选了1就不能选k-1;选了2就不能选k-2… 因此可以选1~k//2 剩余的从k开始向上选。 可以

    2024年02月11日
    浏览(34)
  • [LeetCode周赛复盘] 第 353 场周赛20230709

    感觉有奖品大家都来了。 T1 数学。 T2 dp。 T3 dp。 T4 差分/BIT RUPQ。 6451. 找出最大的可达成数字 1. 题目描述 2. 思路分析 为了使x num在t步内相同,需要相向而行,每步最大缩短距离是2,那么t步距离是2t。 3. 代码实现 6899. 达到末尾下标所需的最大跳跃次数 1. 题目描述 2. 思路分

    2024年02月15日
    浏览(31)
  • [LeetCode周赛复盘] 第 348场周赛20230604

    这场可惜了。 T1 模拟。 T2 模拟。 T3 倒序计算。 T4 同时限制上下界的数位DP。 6462. 最小化字符串长度 1. 题目描述 2. 思路分析 题意仔细想一下就会发现,其实会将每个字符仅留1个。 3. 代码实现 6424. 半有序排列 1. 题目描述 2. 思路分析 由于只能相邻交换来移动,因此每次只能

    2024年02月08日
    浏览(38)
  • LeetCode第354场周赛

    给你一个下标从 1 开始、长度为 n 的整数数组 nums 。 对 nums 中的元素 nums[i] 而言,如果 n 能够被 i 整除,即 n % i == 0 ,则认为 num[i] 是一个 特殊元素 。 返回 nums 中所有 特殊元素 的 平方和 。 直接模拟就好了 给你一个下标从 0 开始的整数数组 nums 和一个 非负 整数 k 。 在一

    2024年02月16日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包