使用贪心算法解决问题时,通过对每一步求局部最优解,最终一定能找到全局最优解。
错。局部最优组合起来不一定得到全局最优,须证明贪心选择性质与最优子结构;如 0-1 背包按价值密度贪心只能得次优解,故不能保证一定找到全局最优解。
若一个问题满足最优子结构性质,则一定可以用贪心算法得到最优解。
错。最优子结构只是动态规划的必要条件,贪心还要求贪心选择性质,即局部最优能推出全局最优;如 0/1 背包满足最优子结构,按价值密度贪心却得不到最优解。
贪心算法只要每一步选择当前最优解,就一定能得到全局最优解。
错。贪心只保证每步局部最优,不保证全局最优,需额外证明贪心选择性质与最优子结构;如 0-1 背包每步贪拿单位价值最高的物品就得不到最优解,是反例。
下面的 fiboA() 和 fiboB() 两个函数分别实现斐波那契数列,该数列第 、第 项值为 ,其余各项分别为前两项之和。下面有关说法错误的是( )。
01int fiboA(int n) 02{ 03 if(n==0) 04 return 1; 05 if(n==1) 06 return 1; 07 else 08 { 09 return fiboA(n-1)+fiboA(n-2); 10 } 11} 12int fiboB(int n) 13{ 14 if( (n==0) || (n==1) ){ 15 fiboB[n]=n; 16 return n; 17 } 18 else{ 19 if(fiboB[n] == 0){ 20 fiboB[n]=FiboB(n-1)+FiboB(n-2); 21 } 22 return fiboB[n]; 23 } 24}
D:错误项。fiboA 递归实现,n 较大时 fiboA(n-1) 与 fiboA(n-2) 大量重叠计算,是指数级低效,代码短不代表效率高;fiboB 用数组记忆化,是动态规划实现。
在求解最优化问题时,动态规划常常涉及到两个重要性质,即最优子结构和( )。
A:重叠子问题。DP 求解最优化问题依赖两个性质:最优子结构(最优解含子问题最优解)与重叠子问题(子问题被反复求解,用表记忆化);分治、贪心、回溯不是 DP 的性质。
在动态规划中,状态转移方程的作用是定义状态之间的关系。
A:正确。状态转移方程描述当前状态如何由前面的状态递推得到,如 dp[i]=dp[i-1]+dp[i-2],正是动态规划定义状态间关系的核心。
应用动态规划算法时,识别并存储重叠子问题的解是必须的。
A:正确。重叠子问题使同一子问题被反复求解,DP 必须用数组等存储已算出的子问题解(记忆化),避免重复计算,这是 DP 高效的关键。
关于动态规划的描述,正确的是()。
B:动态规划要求最优子结构与重叠子问题两个性质;A 复杂度不一定总低于贪心,C 递归 DP 同样要存中间结果,D 子问题相互重叠而非互不重叠。
以下关于动态规划算法特性的描述,正确的是()。
B:动态规划问题的两个关键特性是重叠子问题(子问题被反复求解)和最优子结构(最优解由子问题最优解构成);A 说子问题不重叠错,C、D 说法过于绝对。
动态规划问题通常要求具有最优子结构,并且常常存在重叠子问题。
A:正确。动态规划要求问题具有最优子结构(最优解由子问题最优解构成),且常存在重叠子问题(子问题被反复求解,用表存储避免重复计算)。
状态转移方程是动态规划的核心,可以通过递推方式表示问题状态的变化。
A:正确。状态转移方程描述状态间如何递推转化,如 dp[i]=max(dp[i-1], nums[i]+dp[i-2]),是动态规划的核心。
动态规划通常用于解决( )。
B:动态规划适合可分解为相互依赖子问题(子问题重叠、有最优子结构)的问题;无法分解的问题只能直接求解,贪心可解的未必需要 DP。
下面递归实现的斐波那契数列的时间复杂度为 。
01long long fib_memo(int n, long long memo[]) { 02 if (n <= 1) return n; 03 if (memo[n] != -1) return memo[n]; 04 memo[n] = fib_memo(n - 1, memo) + fib_memo(n - 2, memo); 05 return memo[n]; 06} 07 08int main() { 09 int n = 40; 10 long long memo[100]; 11 fill_n(memo, 100, -1); 12 long long result2 = fib_memo(n, memo); 13 return 0; 14}
错。memo 数组被 fill_n 全部置 -1,每个 n 只算一次即缓存,之后直接返回 memo[n],总工作量 O(n),不是朴素递归的 O(2^n)。
下面程序的输出为( )。
01#include <iostream> 02using namespace std; 03int rec_fib[100]; 04int fib(int n) { 05 if (n <= 1) 06 return n; 07 if (rec_fib[n] == 0) 08 rec_fib[n] = fib(n - 1) + fib(n - 2); 09 return rec_fib[n]; 10} 11int main() { 12 cout << fib(6) << endl; 13 return 0; 14}
A:记忆化斐波那契:fib(0)=0、fib(1)=1,递推 fib(2)=1、fib(3)=2、fib(4)=3、fib(5)=5、fib(6)=8。
在动态规划问题中,状态空间相同且没有重复计算的情况下,“状态转移方程+递推”与“递归+记忆化搜索”的时间复杂度通常相同。
A:正确。递推按序填表与记忆化递归都保证每个状态只计算一次,状态空间相同且无重复计算时,两者的时间复杂度相同。,复杂度相同。
青蛙每次能跳 或 步。下面是青蛙跳到第 步台阶 C++ 实现代码。该段代码采用的算法是( )。
01int jumpFrog(int N) { 02 if (N <= 3) 03 return N; 04 return jumpFrog(N - 1) + jumpFrog(N - 2); 05}
C:动态规划。f(N)=f(N-1)+f(N-2) 是典型状态转移方程,把跳 N 阶拆成先跳 1 步或 2 步两个子问题,子问题相互重叠,符合 DP 的最优子结构;本题用递归实现(未记忆化),故不是贪心、分治或简单递推。
元宵节晚上,小朋友沿着一条发光石板路前进,每次可向前走 块或 块石板。动态规划定义如下:dp[i] = dp[i - 1] + dp[i - 2],下面关于 dp[i] 的含义最合适的是()。
A:dp[i]=dp[i-1]+dp[i-2] 表示走到第 i 块石板的不同走法数量(最后一步走 1 块或 2 块两种来源相加);B 是走法累加误解,C、D 方向相反。
以下动态规划算法的含义与目的是( )。
01int function(vector<int>& nums) { 02 int n = nums.size(); 03 if (n == 0) 04 return 0; 05 if (n == 1) 06 return nums[0]; 07 vector<int> dp(n, 0); 08 dp[0] = nums[0]; 09 dp[1] = max(nums[0], nums[1]); 10 for (int i = 2; i < n; ++i) { 11 dp[i] = max(dp[i - 1], nums[i] + dp[i - 2]); 12 } 13 return dp[n - 1]; 14}
C:dp[i]=max(dp[i-1], nums[i]+dp[i-2]):不选第 i 个继承 dp[i-1],选它则加上跳过一个的 dp[i-2],即求数组中不相邻元素的最大和(打家劫舍问题)。
小朋友们去邻里拜年,每个家里有不同数量的糖果。规则是:不能连续进入两个相邻的房子(即不能同时取相邻两家的糖果)。目标是拿到最多糖果。以下代码实现,请补全横线。
01int visit(vector<int>& nums) { 02 if (nums.empty()) { 03 return 0; 04 } 05 int size = nums.size(); 06 if (size == 1) 07 return nums[0]; 08 09 vector<int> dp = vector<int>(size, 0); 10 dp[0] = nums[0]; 11 dp[1] = max(nums[0], nums[1]); 12 13 for (int i = 2; i < size; i++) { 14 dp[i] = ____________; // 在此处填写代码 15 } 16 17 return dp[size - 1]; 18}
C:不能取相邻两家,dp[i]=max(dp[i-1], dp[i-2]+nums[i]):不取第 i 家继承 dp[i-1],取则跳过前一家加 nums[i];A、D 只取一种情况,B 用乘法错误。
在动态规划解决一维硬币找零问题时,若硬币面额为 [1, 3, 4],目标金额为 ,则最少需要 枚硬币(3+3)。
A:正确。6=3+3 只需 2 枚硬币;1+1+4 需 3 枚、全用 1 元需 6 枚,均多于 2 枚,故最少硬币数为 2。
小杨在玩一个闯关游戏,从第 关走到第 关。每一关的体力消耗如下(下标表示关卡编号):cost = [0, 3, 5, 2, 4],其中 cost[i] 表示到达第 关需要消耗的体力,cost[0]=0 表示在开始状态,体力消耗为 。小杨每次可以从当前关卡前进 步或 步。按照上述规则,从第 关到第 所需消耗的最小体力为 。
B:错误。最优路径为第 1 关跳到第 3 关再跳到第 4 关:cost[3]+cost[4]=2+4=6,小于题目给出的 7;其他路径 1→2→4 为 9、逐关走为 11。
给定一个整数数组 nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。下面横线处应该填入的是( )
01#include <iostream> 02#include <vector> 03#include <algorithm> 04using namespace std; 05int maxSubArray(vector<int>& nums) { 06 int n = nums.size(); 07 if (n == 0) return 0; 08 vector<int> dp(n, 0); 09 dp[0] = nums[0]; 10 int maxSum = dp[0]; 11 for (int i = 1; i < n; i++) { 12 ____________ 13 maxSum = max(maxSum, dp[i]); 14 } 15 return maxSum; 16} 17int main() { 18 int n; 19 cin >> n; 20 vector<int> nums(n); 21 for (int i = 0; i < n; i++) { 22 cin >> nums[i]; 23 } 24 int result = maxSubArray(nums); 25 cout << result << endl; 26 return 0; 27}
B:最大子段和转移 dp[i]=max(nums[i], dp[i-1]+nums[i]):要么从 nums[i] 重新开始,要么接上以 i−1 结尾的段。
给定一个 的二维网格 grid,每个格子中有一个非负整数。请找出一条从左上角 到右下角 的路径,使得路径上的数字总和最小。每次只能向右或向下移动。横线处应该填入的是( )
01#include <iostream> 02#include <vector> 03#include <algorithm> 04using namespace std; 05int minPathSum(vector<vector<int>>& grid) { 06 int m = grid.size(); 07 int n = grid[0].size(); 08 vector<vector<int>> dp(m, vector<int>(n, 0)); 09 dp[0][0] = grid[0][0]; 10 for (int j = 1; j < n; j++) { 11 dp[0][j] = dp[0][j - 1] + grid[0][j]; 12 } 13 for (int i = 1; i < m; i++) { 14 dp[i][0] = dp[i - 1][0] + grid[i][0]; 15 } 16 for (int i = 1; i < m; i++) { 17 for (int j = 1; j < n; j++) { 18 ____________ 19 } 20 } 21 return dp[m - 1][n - 1]; 22} 23int main() { 24 int m, n; 25 cin >> m >> n; 26 vector<vector<int>> grid(m, vector<int>(n)); 27 for (int i = 0; i < m; i++) { 28 for (int j = 0; j < n; j++) { 29 cin >> grid[i][j]; 30 } 31 } 32 int result = minPathSum(grid); 33 cout << result << endl; 34 return 0; 35}
B:最小路径和转移 dp[i][j]=min(dp[i-1][j], dp[i][j-1])+grid[i][j],加上当前格子的值,故选 B。
给定一个整数数组 nums,找到其中最长的严格上升子序列的长度。子序列是指从原数组中删除一些元素(或不删除)后,剩余元素保持原有顺序的序列。下面的程序横线处应该填入的是( )
01#include <iostream> 02#include <vector> 03#include <algorithm> 04using namespace std; 05int lengthOfLIS(vector<int>& nums) { 06 int n = nums.size(); 07 if (n == 0) return 0; 08 vector<int> dp(n, 1); 09 for (int i = 1; i < n; i++) { 10 for (int j = 0; j < i; j++) { 11 if (nums[i] > nums[j]) { 12 ____________ 13 } 14 } 15 } 16 return *max_element(dp.begin(), dp.end()); 17} 18int main() { 19 int n; 20 cin >> n; 21 vector<int> nums(n); 22 for (int i = 0; i < n; i++) { 23 cin >> nums[i]; 24 } 25 int result = lengthOfLIS(nums); 26 cout << result << endl; 27 return 0; 28}
D:LIS 转移:若 nums[j]<nums[i],用 dp[j]+1 更新 dp[i],即 dp[i]=max(dp[i], dp[j]+1)。
已知三个序列:s1={3,1,8,2,5,6,7,4},s2={1,5,1,8,6,4,7,5,6},s3={1,8,3,5,7,6,2,4}。以下哪个序列是它们的最长公共子序列()。
A:{1,8,5,6} 在三个序列中都按下标递增出现且长度 4;B 中 7 在 s3 里位于 6 之前、D 中 4 在 s2 里位于 7 之前,均不成立。
假设背包的最大容量 ,共有 个物品可供选择, 个物品重量分别为 weights = [2, 3, 5, 7],价值 values = [30, 40, 60, 80],则该0/1背包问题中最大价值为()。
C:100。容量 8 下枚举组合:2+3+5=10 超重,3+5=8 得 40+60=100 最大;2+5=7 得 90、单取 7 得 80,故最大价值为 100。
在解决简单背包问题时,动态规划的状态转移方程如下:
dp[i][w] = max(dp[i-1][w], dp[i-1][w - weights[i-1]] + values[i-1]);
该方程表示:在考虑第 个物品时,当前背包容量为 ,如果不放物品 ,则最大价值是 dp[i-1][w];如果放入物品 ,则最大价值是 dp[i-1][w - weights[i-1]] + values[i-1],其中数组 weights 和 values 分别表示所有物品的重量和值,数组下标从 开始。
A:正确。方程准确刻画 01 背包决策:不放第 i 件取 dp[i-1][w],放入则先腾出 weights[i-1] 容量再加 values[i-1],二者取最大值。
给定 个物品和一个最大承重为 的背包,每个物品有一个重量 wt[i] 和价值 val[i],每个物品只能选择放或不放。目标是选择若干个物品放入背包,使得总价值最大,且总重量不超过 ,则横线上应填写()。
01int knapsack(int W, vector<int>& wt, vector<int>& val, int n) { 02 vector<int> dp(W+1, 0); 03 for (int i = 0; i < n; ++i) { 04 for (int w = W; w >= wt[i]; --w) { 05 ____________ // 在此处填写代码 06 } 07 } 08 return dp[W]; 09}
D:01 背包转移取「不放 dp[w]」与「放入 dp[w-wt[i]]+val[i]」的较大值;A 加自身无意义,B 少了与不放的比较,C 用 dp[w-1] 是错误的容量转移。
下列代码实现了一个 0-1 背包的一维动态规划代码,内层循环是经典的逆序写法。若将内层循环改成正序遍历(即 for (int j = w[i]; j <= W; j++)),仍能得到正确答案。
01int main() { 02 int W = 5; 03 int w[] = {2, 3, 4}; 04 int v[] = {10, 1, 1}; 05 int n = 3; 06 int dp[6] = {0}; 07 08 for (int i = 0; i < n; i++) { 09 for (int j = W; j >= w[i]; j--) { 10 dp[j] = max(dp[j], dp[j - w[i]] + v[i]); 11 } 12 } 13 cout << dp[W]; 14}
B:错误。正序遍历会让同一物品被重复放入(变成完全背包):如 w=2、v=10 时正序下 dp[4] 会再叠加一次该物品得 20,答案错误,必须保持逆序。
下面代码是一维数组优化 背包的核心片段,其中 表示第 件物品的重量, 表示第 件物品的价值。横线处应填入( )。
01for (int i = 1; i <= n; i++) { 02 for (int c = W; c >= w[i]; c--) { 03 __________; 04 } 05}
D:0/1 背包一维转移 dp[c]=max(dp[c], dp[c-w[i]]+v[i]),容量倒序枚举保证每件物品最多选一次,故选 D。
下面代码可以用来求最长上升子序列(LIS)的长度,如果输入是:5 1 7 3 5 9,则输出是( )。
01int a[2023], f[2023]; 02int main() 03{ 04 int n,i,j,ans = -1; 05 06 cin>>n; 07 for( i=1; i<=n; i++){ 08 cin >> a[i]; 09 f[i] = 1; 10 } 11 12 for( i=1; i<=n; i++) 13 for( j=1; j<i; j++) 14 if(a[j] < a[i]) 15 f[i] = max(f[i], f[j]+1); 16 for( i=1; i<=n; i++){ 17 ans = max(ans, f[i]); 18 cout << f[i] << " "; 19 } 20 21 cout << ans << endl; 22 return 0; 23}
B:n=5,a=[1,7,3,5,9],f 初值全 1。f[2]=2(1<7);f[3]=2(1<3);f[4]=3(1<5、3<5);f[5]=4(7<9、5<9),输出 f 序列 1 2 2 3 4,ans=4。
已知两个序列 s1={1,3,4,5,6,7,7,8,1}、s2={3,5,7,4,8,2,9,5,1},则它们的最长公共子序列是( )。
A:LCS 要求同时在两个序列中按下标递增出现:{3,5,7,8,1} 在 s1、s2 中都按序出现且长度为 5;B 中 4 在 s2 里位于 5 之后,D 含 s1 没有的 9,均不成立。
动态规划只要推导出状态转移方程,就可以写出递归程序来求出最优解。( )
错。仅有状态转移方程还不够:还需确定边界条件与初始化值,且直接递归会重复计算大量子问题,要配合记忆化或按阶段递推才能高效求解,配合记忆化才行。
下⾯最长公共⼦序列程序中,横线处应该填⼊的是( )。
01#define MAX(A, B) (((A) > (B)) ? (A) : (B)) 02#define MIN(A, B) (((A) < (B)) ? (A) : (B)) 03int dp[MAX_L + 1][MAX_L + 1]; 04int LCS(char str1[], char str2[]) { 05 int len1 = strlen(str1); 06 int len2 = strlen(str2); 07 for (int i = 0; i < len1; i++) 08 for(int j = 0; j < len2; j++) 09 if (str1[i] == str2[j]) 10 dp[i + 1][j + 1] = dp[i][j] + 1; 11 else 12 ___________; // 在此处填入选项 13 return dp[len1][len2]; 14}
C:str1[i]!=str2[j] 时 LCS 取 max(dp[i][j+1], dp[i+1][j]),即分别舍弃 str1[i] 或 str2[j] 后的最优值;A 是求和,B 取小值,D 多加了 1。
下面LIS函数试图求出最长上升子序列的长度,横线处应该填入的是( )
01int max(int a, int b) { 02 return (a > b) ? a : b; 03} 04int LIS(vector<int> & nums) { 05 int n = nums.size(); 06 if (n == 0) return 0; 07 vector<int> dp(n, 1); 08 int maxLen = 1; 09 for (int i = 1; i < n; i++) { 10 for (int j = 0; j < i; j++) 11 if (nums[j] < nums[i]) 12 ; // 在此处填入选项 13 maxLen = max(maxLen, dp[i]); 14 } 15 return maxLen; 16}
D:dp[i]=max(dp[i],dp[j]+1)。以 nums[i] 结尾的最长上升子序列可由任意满足 nums[j]<nums[i] 的前缀转移;A、B 去更新 dp[j] 方向反了。