• 【动态规划 状态机dp 性能优化】3098. 求出所有子序列的能量和


    本文涉及知识点

    动态规划 状态机dp 性能优化

    LeetCode3098. 求出所有子序列的能量和

    给你一个长度为 n 的整数数组 nums 和一个 正 整数 k 。
    一个子序列的 能量 定义为子序列中 任意 两个元素的差值绝对值的 最小值 。
    请你返回 nums 中长度 等于 k 的 所有 子序列的 能量和 。
    由于答案可能会很大,将答案对 109 + 7 取余 后返回。
    示例 1:
    输入:nums = [1,2,3,4], k = 3
    输出:4
    解释:
    nums 中总共有 4 个长度为 3 的子序列:[1,2,3] ,[1,3,4] ,[1,2,4] 和 [2,3,4] 。能量和为 |2 - 3| + |3 - 4| + |2 - 1| + |3 - 4| = 4 。
    示例 2:
    输入:nums = [2,2], k = 2
    输出:0
    解释:
    nums 中唯一一个长度为 2 的子序列是 [2,2] 。能量和为 |2 - 2| = 0 。
    示例 3:
    输入:nums = [4,3,-1], k = 2
    输出:10
    解释:
    nums 总共有 3 个长度为 2 的子序列:[4,3] ,[4,-1] 和 [3,-1] 。能量和为 |4 - 3| + |4 - (-1)| + |3 - (-1)| = 10 。

    提示:
    2 <= n == nums.length <= 50
    -108 <= nums[i] <= 108
    2 <= k <= n

    动态规划(状态机dp)初版

    动态规划的状态 表示

    pre 表示已经处理完前x个数组符合条件的数量,dp表示已经处理完x+1数组符合条件的数量。

    pre[i][j][end][len] 表示此子序列:
    a,长度为len。
    b,以nums[end]结束。
    c,nums[j]-nums[i]的差最小。如果多个(i,j)符合条件,取最小的。比如:{1,2,3}的(I,j)是{0,1}而不是{1,2}。
    空间复杂度:O(nnnk)
    dp类似。

    动态规划的转移方程

    只需要从x 推导x+1,不需要推导x+2,x+3 ⋯ \cdots ,如果硬要的话需要用前缀和(后缀和)。 
    { d p = p r e 不选择 n u m s [ x ] d p [ i ] [ j ] [ x ] [ l e n + 1 ] + = . . . e l s e 且 n u m s [ j ] − n u m s [ i ] < = n u m s [ x ] − n u m s [ e n d ] d p [ e n d ] [ x ] [ x ] [ l e n + 1 ] + = . . . e l s e {dp=prenums[x]dp[i][j][x][len+1]+=...elsenums[j]nums[i]<=nums[x]nums[end]dp[end][x][x][len+1]+=...else dp=predp[i][j][x][len+1]+=...dp[end][x][x][len+1]+=...else不选择nums[x]elsenums[j]nums[i]<=nums[x]nums[end]
    时间复杂度:O(nnnkn) 估计超时
    剪枝:
    枚举的时候确保 i < j ,且 j <= x。

    动态规划+前缀和

    拆分成若干个子问题,假定序列存在(i,j),且此序列的能力为power = nums[j]-nums[i]。

    动态规划的状态表示

    dp[len][end] 表示 子序列的长度为len,最后一个元素是end。
    空间复杂度:O(kn)

    利用前缀和优化 动态规划的转移方程

    枚举end,end not ∈ \in (i,j) ,否则此序列的能量就不是nums[j]-nums[i]了。
    { o l d E n d ∈ [ 0 , e n d ) 且 n u m s [ e n d ] − n u m s [ o l d E n d ] > p o w e r e n d < = i o e d E n d ∈ ( e n d , n ) 且 n u m s [ e n d ] − n u m s [ o l d E n d ] > = p o w e r e n d > = j {oldEnd[0,end)nums[end]nums[oldEnd]>powerend<=ioedEnd(end,n)nums[end]nums[oldEnd]>=powerend>=j {oldEnd[0,end)nums[end]nums[oldEnd]>poweroedEnd(end,n)nums[end]nums[oldEnd]>=powerend<=iend>=j

    如果不利用前缀和优先,时间复杂度:O(knn),利用前缀和优化O(kn)。
    总时间复杂度:O(knkn)。

    动态规划的初始状态

    枚举所有长度为2

    动态规划的填表顺序

    l e n = 3 n _{len=3}^{n} len=3n

    动态规划的返回值

    len == k 且 end >=j 才是需要统计的子序列数量。

    代码

    没用前缀和优化

    理论上过不了,实际过了。

    template<int MOD = 1000000007>
    class C1097Int
    {
    public:
    	C1097Int(long long llData = 0) :m_iData(llData% MOD)
    	{
    
    	}
    	C1097Int  operator+(const C1097Int& o)const
    	{
    		return C1097Int(((long long)m_iData + o.m_iData) % MOD);
    	}
    	C1097Int& operator+=(const C1097Int& o)
    	{
    		m_iData = ((long long)m_iData + o.m_iData) % MOD;
    		return *this;
    	}
    	C1097Int& operator-=(const C1097Int& o)
    	{
    		m_iData = (m_iData + MOD - o.m_iData) % MOD;
    		return *this;
    	}
    	C1097Int  operator-(const C1097Int& o)
    	{
    		return C1097Int((m_iData + MOD - o.m_iData) % MOD);
    	}
    	C1097Int  operator*(const C1097Int& o)const
    	{
    		return((long long)m_iData * o.m_iData) % MOD;
    	}
    	C1097Int& operator*=(const C1097Int& o)
    	{
    		m_iData = ((long long)m_iData * o.m_iData) % MOD;
    		return *this;
    	}
    	bool operator==(const C1097Int& o)const
    	{
    		return m_iData == o.m_iData;
    	}
    	bool operator<(const C1097Int& o)const
    	{
    		return m_iData < o.m_iData;
    	}
    	C1097Int pow(long long n)const
    	{
    		C1097Int iRet = 1, iCur = *this;
    		while (n)
    		{
    			if (n & 1)
    			{
    				iRet *= iCur;
    			}
    			iCur *= iCur;
    			n >>= 1;
    		}
    		return iRet;
    	}
    	C1097Int PowNegative1()const
    	{
    		return pow(MOD - 2);
    	}
    	int ToInt()const
    	{
    		return m_iData;
    	}
    private:
    	int m_iData = 0;;
    };
    
    class Solution {
    public:
    	int sumOfPowers(vector<int>& nums, const int K) {
    		m_c = nums.size();
    		sort(nums.begin(), nums.end());		
    		C1097Int<> biRet = 0;
    		for (int i = 0; i < m_c; i++) {
    			for (int j = i + 1; j < m_c; j++) {
    				auto cur = Do(nums, i, j, K);
    				biRet += cur;
    				//std::cout << " i :" << i << " j:" << j << " " << cur.ToInt() <<  std::endl;
    			}
    		}
    		return biRet.ToInt();
    	}
    	C1097Int<> Do(const vector<int>& nums,int i,int j, const int K) {
    		const int iDiff = nums[j] - nums[i];
    		vector<vector<C1097Int<>>> dp(K + 1, vector<C1097Int<>>(m_c));
    		for (int end = 0; end <= i; end++) {
    			for (int end1 = 0; end1 < end; end1++) {
    				if (nums[end] - nums[end1] > iDiff) {
    					dp[2][end] += 1;
    				}
    			}
    		}
    		dp[2][j] = 1;
    	
    		for (int len = 3; len <= K; len++) {
    			for (int end = 0; end <= i; end++) {
    				for (int end1 = 0; end1 < end; end1++) {
    					if (nums[end] - nums[end1] > iDiff) {
    						dp[len][end] += dp[len - 1][end1];
    					}
    				}
    			}
    			dp[len][j] = dp[len - 1][i];
    			for (int end = j+1; end < m_c; end++) {
    				for (int end1 = j; end1 < end; end1++) {
    					if (nums[end] - nums[end1] >= iDiff) {
    						dp[len][end] += dp[len - 1][end1];
    					}
    				}
    			}
    		}
    		return std::accumulate(dp.back().begin() + j, dp.back().end(), C1097Int<>())*iDiff;
    	}
    	int m_c;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117

    测试用例

    int main()
    {
    	vector<int> nums;
    	int k;
    	
    	{
    		Solution sln;
    		nums = { 6,14,4,13 }, k = 3;
    		auto res = sln.sumOfPowers(nums, k);
    		Assert(6, res);
    	}
    	{
    		Solution sln;
    		nums = { 1,2,3,4 }, k = 3;
    		auto res = sln.sumOfPowers(nums, k);
    		Assert(4, res);
    	}
    	{
    		Solution sln;
    		nums = { 4,3,-1 }, k = 2;
    		auto res = sln.sumOfPowers(nums, k);
    		Assert(10, res);
    	}
    
    	{
    		Solution sln;
    		nums = { 2,2 }, k = 2;
    		auto res = sln.sumOfPowers(nums, k);
    		Assert(0, res);
    	}
    	{
    		Solution sln;
    		nums = { 2,246006,496910,752786,1013762,1279948,1551454,1828436,2110982,2399316,2693558,2993942,3300640,3613766,3933442,4259696,4592656,4932556,5279494,5633522,5994678,6363102,6739028,7122528,7513792,7913044,8320394,8736004,9160062,9592750,10034184,10484602,10944108,11412852,11891048,12378822,12876346,13383746,13901098,14428528,14966126,15514010,16072380,16641300,17220904,17811360,18412850,19025600,19649778,20285440 }, k = 37;
    		auto res = sln.sumOfPowers(nums, k);
    		Assert(273504325, res);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    利用前缀和优化:用时减少不到50%

    template<int MOD = 1000000007>
    class C1097Int
    {
    public:
    	C1097Int(long long llData = 0) :m_iData(llData% MOD)
    	{
    
    	}
    	C1097Int  operator+(const C1097Int& o)const
    	{
    		return C1097Int(((long long)m_iData + o.m_iData) % MOD);
    	}
    	C1097Int& operator+=(const C1097Int& o)
    	{
    		m_iData = ((long long)m_iData + o.m_iData) % MOD;
    		return *this;
    	}
    	C1097Int& operator-=(const C1097Int& o)
    	{
    		m_iData = (m_iData + MOD - o.m_iData) % MOD;
    		return *this;
    	}
    	C1097Int  operator-(const C1097Int& o)
    	{
    		return C1097Int((m_iData + MOD - o.m_iData) % MOD);
    	}
    	C1097Int  operator*(const C1097Int& o)const
    	{
    		return((long long)m_iData * o.m_iData) % MOD;
    	}
    	C1097Int& operator*=(const C1097Int& o)
    	{
    		m_iData = ((long long)m_iData * o.m_iData) % MOD;
    		return *this;
    	}
    	bool operator==(const C1097Int& o)const
    	{
    		return m_iData == o.m_iData;
    	}
    	bool operator<(const C1097Int& o)const
    	{
    		return m_iData < o.m_iData;
    	}
    	C1097Int pow(long long n)const
    	{
    		C1097Int iRet = 1, iCur = *this;
    		while (n)
    		{
    			if (n & 1)
    			{
    				iRet *= iCur;
    			}
    			iCur *= iCur;
    			n >>= 1;
    		}
    		return iRet;
    	}
    	C1097Int PowNegative1()const
    	{
    		return pow(MOD - 2);
    	}
    	int ToInt()const
    	{
    		return m_iData;
    	}
    private:
    	int m_iData = 0;;
    };
    
    class Solution {
    public:
    	int sumOfPowers(vector<int>& nums, const int K) {
    		m_c = nums.size();
    		sort(nums.begin(), nums.end());
    		C1097Int<> biRet = 0;
    		for (int i = 0; i < m_c; i++) {
    			for (int j = i + 1; j < m_c; j++) {
    				auto cur = Do(nums, i, j, K);
    				biRet += cur;
    				//std::cout << " i :" << i << " j:" << j << " " << cur.ToInt() <<  std::endl;
    			}
    		}
    		return biRet.ToInt();
    	}
    	C1097Int<> Do(const vector<int>& nums, int i, int j, const int K) {
    		const int iDiff = nums[j] - nums[i];
    		vector<vector<C1097Int<>>> dp(K + 1, vector<C1097Int<>>(m_c));
    		for (int end = 0; end <= i; end++) {
    			for (int end1 = 0; end1 < end; end1++) {
    				if (nums[end] - nums[end1] > iDiff) {
    					dp[2][end] += 1;
    				}
    			}
    		}
    		dp[2][j] = 1;
    
    		for (int len = 3; len <= K; len++) {
    			int end1 = 0;
    			C1097Int<> biRet = 0;
    			for (int end = 0; end <= i; end++) {
    				while ((end1 < end) && (nums[end] - nums[end1] > iDiff)) {
    					biRet += dp[len - 1][end1];
    					end1++;
    				}
    				dp[len ][end] = biRet;
    			}
    			dp[len][j] = dp[len - 1][i];
    			C1097Int<> biRet2 = 0;
    			for (int end = j + 1,end1=j ; end < m_c; end++) {
    				while ((end1 < end) && (nums[end] - nums[end1] >= iDiff)) {
    					biRet2 += dp[len - 1][end1];
    					end1++;
    				}
    				dp[len][end] = biRet2;
    			}
    		}
    		return std::accumulate(dp.back().begin() + j, dp.back().end(), C1097Int<>()) * iDiff;
    	}
    	int m_c;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120

    扩展阅读

    视频课程

    有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
    https://edu.csdn.net/course/detail/38771

    如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
    https://edu.csdn.net/lecturer/6176

    相关下载

    想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
    https://download.csdn.net/download/he_zhidan/88348653

    我想对大家说的话
    闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
    子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
    如果程序是一条龙,那算法就是他的是睛

    测试环境

    操作系统:win7 开发环境: VS2019 C++17
    或者 操作系统:win10 开发环境: VS2022 C++17
    如无特殊说明,本算法用**C++**实现。

  • 相关阅读:
    HNU 电子电路 模型机综合设计
    Angular核心-路由和导航
    4.企业快速开发平台Spring Cloud+Spring Boot+Mybatis之Highcharts 配置选项详细说明
    Ubuntu-Ports更新源 ARM64更新源
    **3DSlicer**之Python脚本储存库代码实验: 应用vtkCellLocator定位最近点的cellID
    45位主播带货破亿,单日热销100w+单,8月榜单有哪些看点?
    2022年格尔软件一面-未知
    【JavaSE】面试01
    高手系列!数据科学家私藏pandas高阶用法大全 ⛵
    一次实战压测流程及问题梳理
  • 原文地址:https://blog.csdn.net/he_zhidan/article/details/137734329