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

leetcode-3405 统计恰好有k个相等相邻数组的个数

题目描述

给你三个整数 n ,m ,k 。长度为 n 的 好数组 arr 定义如下:

  • arr 中每个元素都在闭区间 [1, m] 中。
  • 恰好有k个下标 i(其中 1 <= i < n)满足 arr[i - 1] == arr[i] 。

请你返回可以构造出的好数组数目。

由于答案可能会很大,请你将它对 10^9 + 7 取余后返回。

过程

阶段

一开始当成一个动态规划问题如何理解

假定容纳n元素数组可选值范围m需要k满足题目条件子序列数组个数dp(n,m,k)

我们可以n-1情况递推n情况因为无非就是多了一个容纳元素格子这个格子可以前者相同元素这种元素我们称之A元素可以构造题目需要k子序列一个另一种情况就是不选A元素而是选择普通元素不会创造相等邻接子序列元素我们称之X元素除了数组一个元素(必定为X元素,因为第一个元素前面没有任何元素可以接)m取值以外其他X元素只有m-1取值因为不能前面元素相同(否则就变成了A类元素)

所以当我们开始递推

当新加入的格子选择A类元素时,不同的数组个数有 dp(n - 1,m,k - 1) 个,因为A类元素只有一种取值。

新加入格子选择X元素不同数组个数dp(n - 1, m , k) * (m - 1)

所以递推公式

dp(n,k) = dp(n-1,k-1) + dp(n - 1, k) * (m - 1)

这里因为m始终不变所以省略不写本质是一个二维dp

k = 0dp(n,0) m * pow(m - 1, n -1)数组排列组合知识得到

因此便可以按顺序

于是有了第一种代码

int countGoodArrays(int n, int m, int k) {
	int search[n][k + 1] = {};
	unsigned long long resolves = m;for(int i = 0;i < n; i++){
		search[i][0] = resolves;
		resolves = (resolves * (m - 1)) % K;}for(int i = 1;i < n;i ++)for(int q = 1;q < k + 1;q ++){
		search[i][q] = (search[i-1][q] * (m - 1) + search[i-1][q-1]) % K;}
	return search[n-1][k];
}

阶段

代码内存可以优化

因为dp数组前面每一行完了后面用不到了所以可以删掉所以引入轮转优化代码

int countGoodArrays2(int n, int m, int k) {
	vector<int> tmp(k + 1, 0),ret;unsigned long long resolves = m;
	tmp[0] = resolves;	for(int i = 1;i < n; i++){
		resolves = (resolves * (m - 1)) % K;
		ret.emplace_back(resolves);for(int q = 0;q < k;q ++)
		ret.emplace_back(((unsigned long long)tmp[q + 1] * (m - 1) + tmp[q]) % K);
		tmp.clear();
		ret.swap(tmp);
		ret.reserve(k + 1);}return tmp[k];
}

通过每一次swap使得最终dp数组占用只有两行

阶段三

后面提测发现题目n,m,k都会很大导致时间空间基本怎么样都会这里怀疑方法有问题,又仔细一下

新思路

给定大小n数组除了数组第一个元素必须X元素以外剩下可以A元素可以X元素那么这里可以组合Cn - 1位置里面选择k位置填充A元素剩下都是X元素但是关键组合需要计算阶乘这里数值计算很大所以相当于本题考察大数组合数计算注意题目有说

由于答案可能会很大,请你将它对 10^9 + 7 取余后返回。

这里知道要用费马小定理结合逆元计算组合数

运算除法可以改成逆元乘法,这是模运算的性质。

由于题目MOD素数所以可以费马小定理计算一个逆元

当p是素数时,a逆元可以转换a高阶所以幂运算我们通过快速幂运算完成这一点

long long qpow(long long a, long long b) {long long res = 1;
    a %= K;while (b > 0) {if (b & 1) res = res * a % K;
        a = a * a % K;
        b >>= 1;}return res;
}

这样我们就能求出每个逆元由于输入n > m所以最高需要阶乘就是n!n最大1e5所以我们通过数组保存计算阶乘

for(int i = 0; i <= MX; i ++){if(i == 0){
			pw.emplace_back(1);
			continue;}
		pw.emplace_back((unsigned long long)pw[pw.size() - 1] * i % K);}

然后通过逆元递推公式

得到对应每个阶乘逆元数组

anv[MX] = qpow(pw[MX],K-2);for(int i = MX - 1;i >= 0; i--){
		anv[i] = (i + 1) * (unsigned long long)anv[i + 1] % K;}

最后代入即可求得组合

组合出来之后对于每个A元素取值只有一种可能对于第一个X元素取值有m种可能后面每个X元素m - 1可能起来即可这里涉及又可以qpow快速函数

x = (x * qpow(m-1,n - k -1)) % K;

最终代码

#include <vector>
vector<int> anv,pw;
class Solution {
public:unsigned long long K = 1000000000 + 7;int MX = 1e5;
long long qpow(long long a, long long b) {long long res = 1;
    a %= K;while (b > 0) {if (b & 1) res = res * a % K;
        a = a * a % K;
        b >>= 1;}return res;
}int C(int m, int n)
{if(!n || m == n) return 1;if(n > m) return 0;return (unsigned long long)pw[m] * anv[n] % K * anv[m - n] % K;
}
int countGoodArrays(int n, int m, int k) {if(pw.empty()){
        pw.reserve(MX + 1);
        anv.reserve(MX + 1);for(int i = 0; i <= MX; i ++){if(i == 0){
			pw.emplace_back(1);continue;}
		pw.emplace_back((unsigned long long)pw[pw.size() - 1] * i % K);}
	anv[MX] = qpow(pw[MX],K-2);for(int i = MX - 1;i >= 0; i--){
		anv[i] = (i + 1) * (unsigned long long)anv[i + 1] % K;}}int ret = 0;unsigned long long resolves = C(n - 1, k);unsigned long long x = m;
	x = (x * qpow(m-1,n - k -1)) % K;
	ret = (resolves * x) % K;return ret;
}
};

http://www.xdnf.cn/news/14618.html

相关文章:

  • Greenplum/PostgreSQL pg_hba.conf 认证方法详解
  • 【Node.js 的底层实现机制】从事件驱动到异步 I/O
  • TradingAgents:基于多智能体的大型语言模型(LLM)金融交易框架
  • vue | vue 插件化机制,全局注册 和 局部注册
  • 【音视频】PJSIP库——pjsua命令使用详解
  • 【C语言极简自学笔记】重讲运算符
  • LeetCode 632.最小区间
  • ChangeNotifierProvider 本质上也是 Widget
  • 利用tkinter函数构造MD5加密的可视化操作界面
  • 【创龙瑞芯微 RK3576 全国产 ARM 八核 2.2GHz 工业开发板-硬件说明书】
  • 注意力机制、自注意力机制、多头注意力机制、通道注意力机制、空间注意力机制超详细讲解
  • 二分K-means:让聚类更高效、更精准!
  • CAD旋转包围盒_有向包围盒_obb_最小外包矩形——CAD c#二次开发
  • 【对比】DeepAR 和 N-Beats
  • 【CUDA编程】OptionalCUDAGuard详解
  • 质量小议55 - 搜索引擎与AI
  • C语言——结构体
  • 深入剖析Spring Cloud Sentinel,如何实现熔断降级,请求限流
  • C++ 学习 网络编程 2025年6月17日19:56:47
  • MySQL的Sql优化经验总结
  • 浅谈开发者重构的时机选择
  • 如何确定驱动480x320分辨率的显示屏所需的MCU主频
  • DBeaver数据库管理工具的简介、下载安装与优化配置
  • [IMX][UBoot] 02.源码目录
  • Python格式化工具推荐
  • Java中final修饰符
  • 第五章:执行计划分析 - 读懂MySQL的执行策略
  • 一款完美适配mobile、pad、web三端的博客网站UI解决方案
  • 《单光子成像》第六章 预习2025.6.15
  • 【驱动设计的硬件基础】I²C