当前位置: 首页 > article >正文

LeetCode430周赛T3

题目描述

给定一个只包含正整数的数组 nums,我们需要找到其中的特殊子序列。特殊子序列是一个长度为4的子序列,用下标 (p, q, r, s) 表示,它们满足以下条件:

  1. 索引顺序p < q < r < s,且相邻坐标之间至少间隔一个数字,即 q - p > 1r - q > 1s - r > 1
  2. 乘积关系nums[p] * nums[r] == nums[q] * nums[s]

子序列指的是从原数组中删除零个或多个元素后,剩下元素不改变顺序组成的序列。

示例 1:

输入:nums = [1,2,3,4,3,6,1]
输出:1
解释:
nums 中只有一个特殊子序列。
(p, q, r, s) = (0, 2, 4, 6) :对应的元素为 (1, 3, 3, 1)。
nums[p] * nums[r] = 1 * 3 = 3
nums[q] * nums[s] = 3 * 1 = 3

示例 2:

输入:nums = [3,4,3,4,3,4,3,4]
输出:3
解释:
nums 中共有三个特殊子序列。
1. (p, q, r, s) = (0, 2, 4, 6) :对应元素为 (3, 3, 3, 3)。
   nums[p] * nums[r] = 3 * 3 = 9
   nums[q] * nums[s] = 3 * 3 = 9
2. (p, q, r, s) = (1, 3, 5, 7) :对应元素为 (4, 4, 4, 4)。
   nums[p] * nums[r] = 4 * 4 = 16
   nums[q] * nums[s] = 4 * 4 = 16
3. (p, q, r, s) = (0, 2, 5, 7) :对应元素为 (3, 3, 4, 4)。
   nums[p] * nums[r] = 3 * 4 = 12
   nums[q] * nums[s] = 3 * 4 = 12

提示:

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

解题思路

为了高效地解决这个问题,我们需要利用数学和数据结构的结合。以下是详细的解题步骤:

  1. 问题转换

    题目要求 nums[p] * nums[r] == nums[q] * nums[s],我们用a,b,c,d定义nums[p],nums[q],nums[r],num[s],可以将其转换为分数的形式:
    a b = d c \frac{a}{b} = \frac{d}{c} ba=cd

    这样,a b 都在 cd 的左边,便于使用前缀和后缀的方法进行分解和统计。

  2. 统计后缀中的最简分数

    首先,统计下标 [4, n−1] 中所有满足间隔至少一个数的数对 (c, d) 的最简分数,并记录到一个哈希表 suf 中。最简分数是指分子和分母互质的分数。如果分子和分母不互质,可以通过它们的最大公约数(GCD)进行约简。例如,分数 4/6 的最简分数是 2/3

  3. 枚举并统计前缀中的最简分数

    然后,枚举 b 的位置,以及 b 左边满足间隔条件的 a,计算 a/b 的最简分数,并在 suf 中查找相等的最简分数的数量,将其累加到结果中。

  4. 动态更新后缀哈希表

    在从左向右枚举 b 的过程中,动态地维护和更新 suf 中的最简分数的个数,以保证统计的准确性。

代码实现

以下是基于上述思路的C++代码实现:

typedef long long LL;
class Solution {
public:
    long long numberOfSubsequences(vector<int>& nums) {
        int n = nums.size();
        LL res = 0;
        unordered_map<int,int> mp;
        // 预先统计所有 (c, d) 的最简分数
        for(int c = 4; c < n - 2; c ++) {
            for(int d = c + 2; d < n; d ++) {
                int gcd_val = __gcd(nums[c], nums[d]);
                int x = nums[c] / gcd_val;
                int y = nums[d] / gcd_val;
                mp[y << 16 | x ] ++;
            }
        }
        // 枚举 b,并统计满足条件的 a
        for(int b = 2; b <= n - 5; b ++) {
            for(int a = 0; a <= b - 2; a ++) {
                int gcd_val = __gcd(nums[a], nums[b]);
                int x = nums[a] / gcd_val;
                int y = nums[b] / gcd_val;
                res += mp[x << 16 | y];
            }

            // 更新后缀哈希表,移除已经不满足间隔条件的 (c, d)
            for(int d = b + 4; d < n; d ++) {
                int gcd_val = __gcd(nums[b + 2], nums[d]);
                int x = nums[b + 2] / gcd_val;
                int y = nums[d] / gcd_val;
                mp[y << 16 | x] --;
            }
        }
        return res;
    }
};

代码解析

  1. 哈希表 mp 的使用

    我们使用一个 unordered_map<int, int> 来存储后缀中所有 (c, d) 对应的最简分数。为了高效存储分数,我们将分子和分母合并成一个整数,其中分母占高16位,分子占低16位。

  2. 预处理后缀部分

    在初始阶段,我们遍历数组中满足条件的 (c, d) 对,并将其最简分数存入哈希表 mp

  3. 枚举 b 并统计符合条件的 a

    对于每一个可能的 b,我们枚举所有满足条件的 a,计算其最简分数,并在哈希表 mp 中查找相等的最简分数的数量,将其累加到结果 res 中。

  4. 动态更新哈希表 mp

    随着 b 的增大,一些 (c, d) 对将不再满足间隔条件,需要从哈希表中移除。这通过减小相应分数的计数来实现。

复杂度分析

  • 时间复杂度:O(n²),其中 n 是数组的长度。主要来自于两层嵌套循环,分别用于预处理后缀和枚举 b 以及 a
  • 空间复杂度:O(n²),用于存储哈希表 mp 中可能的所有 (c, d) 对的最简分数。

总结

通过巧妙地将乘积关系转换为分数关系,并利用哈希表高效地统计和查找最简分数,我们成功地将原本可能的四重循环优化为两重循环,大大提升了算法的效率。这种方法不仅适用于本题,也为解决类似的组合类问题提供了有益的思路。


http://www.kler.cn/a/457890.html

相关文章:

  • BAPI_BATCH_CHANGE在更新后不自动更新批次特征
  • 区块链安全常见的攻击——ERC777 重入漏洞 (ERC777 Reentrancy Vulnerability)【5】
  • 【每日学点鸿蒙知识】WebView事件监听、构建工具校验规则、避让区域问题、Grid布局对齐、字符串转base64
  • 【网络安全】Web安全基础- 第一节:web前置基础知识
  • ubuntu NVIDIA RTX4000 双屏显示不了
  • 2023 年 3 月 GESP C++ 二级试卷解析
  • c++入门 LESSON0
  • win系统B站播放8k视频启用HEVC编码
  • flask后端开发(2):URL与视图
  • RSA公钥私钥对在线生成工具--可生成pem,xml,raw等密钥格式
  • .net core 的循环实现
  • 2469. 温度转换
  • Docker安装体验kuboard-k8s多集群管理工具
  • 关于Promise的小测验
  • 【Rust自学】7.5. use关键字 Pt.2 :重导入与换国内镜像源教程
  • Pytest基础01: 入门demo脚本
  • Bagging方法和Dropout方法的简单解释
  • Scala语言的函数实现
  • 【Java基础面试题038】栈和队列在Java中的区别是什么?
  • JVM内存结构详解