这里记录一些动态规划的例题,不断积累,不断补充。
1、不等式数列
题目描述:
对1到n这n个整数进行全排列,在每一个排列中每两个相邻的整数之间插入>或<,使得不等式成立。现要求<的个数为k,即>的个数为n-k-1,求问满足要求的排列个数。例如,现在给定n=3,k=1,则满足要求的排列有1<3>2,2>1<3,2<3>1,3>1<2这四种情况,即结果为4。参数范围K < n <= 1000。
解题思路:
暴力搜索复杂度为n!,肯定是不行的。虽然可以进行一定程度的剪枝,比如在遍历每一个位置的数字时,判断当前<是否已经用完,但这只是杯水车薪。遇到这种问题,第一反应肯定是需要使用动态规划,当然难点就在于如何构造递推公式。针对本题目,我们设定dp[i][j]为在n=i,k=j的情况下的排列数,则递推公式为
dp[i][j] = dp[i - 1][j - 1] * (i - j) + dp[i - 1][j] * (j + 1)
怎么理解这个公式呢?其实就要考虑如何从i-1过渡到i。当n从i-1增加到i,新增的数字i要比之前的1到i-1都大,所以无论把i插入到哪个位置,它都会比左右两个数字a和b都大。假如a<b,将i插入到两数之间,不等式变为a<i>b,小于号<数量不变;如果a>b,不等式变为a<i>b,小于号<数量加1。所以为了求解dp[i][j],就要考虑dp[i-1][j]和dp[i-1][j-1]两种情况。对于dp[i-1][j],小于号数量已经足够,因此数字i只能插入到之前排列中前数小于后数的位置,即小于号插入的位置,再加上第一个数前面的位置,所以总数为j+1。对于dp[i-1][j-1],小于号还差一个,所以数字i应该插入到先前排列中大于号的位置,在加上最后一个数后面一个位置,所以总数为i-j-1+1,即i-j。
示例代码:
#include <iostream> #include <vector> using namespace std; int main() { int numCount, K; cin >> numCount >> K; vector<vector<int>> dp(numCount + 1, vector<int>(K + 1)); //当k=0时,只有递增序这一种排列。 for (int i = 1; i <= numCount; i++) { dp[i][0] = 1; } for (int i = 2; i <= numCount; i++) { for (int j = 1; j <= K; j++) { dp[i][j] = dp[i - 1][j] * (j + 1) + dp[i - 1][j - 1] * (i - j); } } cout << dp[numCount][K]; return 0; }
2、Ugly Number
题目描述:
Write a program to find the nth super ugly number.
Super ugly numbers are positive numbers whose all prime factors are in the given prime list primes
of size k
. For example, [1, 2, 4, 7, 8, 13, 14, 16, 19, 26, 28, 32]
is the sequence of the first 12 super ugly numbers given primes
= [2, 7, 13, 19]
of size 4.
Note:
(1) 1
is a super ugly number for any given primes
.
(2) The given numbers in primes
are in ascending order.
(3) 0 < k
≤ 100, 0 < n
≤ 106, 0 < primes[i]
< 1000.
(4) The nth super ugly number is guaranteed to fit in a 32-bit signed integer.
题目来源:
LeetCode 313
解题思路:
题目要求找到第n个符合要求的正整数,一个很明显的规律就是,如果已知前i-1个ugly number,则第i个ugly number一定是由前面i-1个数其中一个再乘以某一个素数而得到。所以,我们只要从第一个开始按序求出所有ugly number,直到求出第n个即可。
为了求第i个ugly number,一个无脑的办法就是,将前面所有的i-1个数依次跟所有的素数求乘积,然后取出比前i-1个ugly number都大的数中最小的一个即可。这样做很显然浪费了很多计算,但浪费在哪里?对于每一个素数,我们没有必要跟所有的前i-1个ugly number都做乘积,我们的目的是求出最小的一个,所以对于每一个素数,我们也应该只求出一个最符合要求的乘积(比前i-1个ugly number大,且是最小的一个),然后对比所有素数求出来的乘积,找出最小一个即可。
假设素数集合primes的大小为k,我们维护一个k大小的数组next,next[i]表示我们前面所说的针对第i个素数最符合要求的那个乘积,每次求下一个ugly number时,从next数组中找出最小值即可,假设为minValue,下标为p。这时,我们需要更新next数组,注意到新的ugly number是目前next中最小的,所以我们只需要更新next数组中等于minValue的值。怎样更新?minValue一定等于primes[p]乘以前面某一个ugly number,假设是第d个,我们只要把它更新为primes[p]乘以第d+1个ugly number就可以了。最后还要注意,next宿主中是可能存在相同值的。
示例代码:
int nthSuperUglyNumber(int n, vector<int>& primes) { vector<int> index(primes.size()); vector<int> next(primes.size(), 1); vector<int> ret; ret.reserve(n); while(ret.size() < n){ int tmpmin = next[0]; for(int i = 1; i < primes.size(); i++){ tmpmin = tmpmin < next[i] ? tmpmin : next[i]; } ret.push_back(tmpmin); for(int i = 0; i < primes.size(); i++){ if(next[i] == tmpmin)next[i] = primes[i] * ret[index[i]++]; } } return ret[n-1]; }
3、Burst Balloons
题目描述:
Given n
balloons, indexed from 0
to n-1
. Each balloon is painted with a number on it represented by array nums
. You are asked to burst all the balloons. If the you burst balloon i
you will get nums[left] * nums[i] * nums[right]
coins. Here left
and right
are adjacent indices of i
. After the burst, the left
and right
then becomes adjacent.
Find the maximum coins you can collect by bursting the balloons wisely.
Note:
(1) You may imagine nums[-1] = nums[n] = 1
. They are not real therefore you can not burst them.
(2) 0 ≤ n
≤ 500, 0 ≤ nums[i]
≤ 100
Example:
Given [3, 1, 5, 8]
Return 167
题目来源:
LeetCode 312
解题思路:
这道题的中心思想其实是分治法,只是使用了动态规划作为解题工具。我们把nums[-1]和nums[n]都当做气球,只是它们不能被打破,因此可以把它们当做边界,那么现在问题就是在已知左右边界的情况下,求边界内所有气球的一个最优解。接下来拆分子问题,就要找到子问题的左右边界。什么样的气球可以当做边界?注意最后一个被打破的气球,假设为nums[i],在其他气球都被打破之前,nums[i]都不会被打破,就相当于它也跟nums[-1]和nums[n]一样是不可被打破的,因此它就是子问题的边界。也就是说nums[-1]、nums[i]、nums[n]将原问题划分为左右两个子问题。由于nums[i]划分了界限,则左右两边的气球无论以什么样的顺序求乘积,都不会互相影响。在解决了两个子问题之后,接下来的归并步骤只需要将两边加起来,再加上打破nums[i]的得分即可。总结起来,得到如下公式:f(left,right) = f(left, i - 1) + f(i + 1, right) + nums[left - 1] * nums[i] * nums[right + 1].
接下来是对于子问题求最优解。我们依次将每一个气球当做最后一个被打破的,这样将子问题又划分为更小的子问题,找到使得得分最大的划分方法即作为最优解。这样看起来是一个递归的过程,而过程中存在大量的重复运算,因此显而易见要用到动态规划。
示例代码:
int maxCoins(vector<int>& nums) { vector<int> bullons(nums.size() + 2); bullons[0] = bullons[nums.size() + 1] = 1; for(int i = 0; i < nums.size(); i++){ bullons[i + 1] = nums[i]; } vector<vector<int>> dp(nums.size() + 2, vector<int>(nums.size() + 2)); for(int count = 1; count <= nums.size(); count++){ for(int begin = 1; begin <= nums.size() - count + 1; begin++){ int end = begin + count - 1; for(int index = begin; index <= end; index++){ dp[begin][end] = max(dp[begin][end], dp[begin][index - 1] + dp[index + 1][end] + bullons[index] * bullons[begin - 1] * bullons[end + 1]); } } } return dp[1][nums.size()]; }
来源:https://www.cnblogs.com/zkycaesar-tech/p/6781134.html