背包dp|格雷码

lc1959

  1. 定义dpij:前i+1个元素(下标0~i)分成j段,最少浪费的空间

  2. 递推公式:

dpij = min(dpk-1j-1 + gki)(k从1到i),k=0时dp-1j-1视为0

++gki是预处理的"区间i~j按最大值扩容的浪费量"++

  1. 初始化:

dp数组全设为无穷大(INF),表示初始状态无有效解

  1. 遍历顺序:

先遍历元素下标i(0到n-1),再遍历分段数j(1到k+1),最后遍历分割点k(0到i),确保子问题先求解

  1. 结果:dpn-1k+1,即所有元素分成k+1段(对应k次扩容)的最小浪费空间

class Solution {

public:

int minSpaceWastedKResizing(vector<int>& nums, int k) {

const int n = nums.size();

const int m = k + 2;

const int INF = 0x3f3f3f3f;

int gnn; //预处理数组

for (int i = 0; i < n; ++i) {

int best = -INF; //准备用于记录区间内最大值

int total = 0; //区间和

for (int j = i; j < n; ++j) {

best = max(best, numsj); //更新最大值

total += numsj; //更新区间和

gij = best * (j - i + 1) - total; //得到权值

}

}

int dpnm; //表示dpij表示前i个点分成j份的最小权值

memset(dp, INF, sizeof(dp));

for (int i = 0; i < n; ++i) //遍历每一个点

for (int j = 1; j < m; ++j) //遍历切割次数

for (int k = 0; k <= i; ++k) //切割成j段从切割成j - 1段转移而来

++dpij = min(dpij, (k == 0 ? 0 : dpk - 1j - 1) + gki);++

return dpn - 1k + 1;

}

};

lcr20

dp五步

  1. 定义dpij:表示字符串s从下标i到j的子串是否是回文串(true/false)

  2. 确定递推公式:

si==sj时,i==j(单个字符)或i+1==j(相邻字符)直接是回文;否则看dpi+1j-1(内部子串是否回文);

si!=sj则一定不是回文。

  1. 初始化:创建n×n的false数组,后续通过递推填充true情况

  2. 遍历顺序:

从后往前遍历i(保证i+1先算)

j从i开始往后(保证j>=i,子串有效)

  1. 计算结果:统计++dp数组中所有true的个数,++就是回文子串总数。

class Solution {

public:

int countSubstrings(string s) {

int n=s.size();

vector<vector<bool>> dp(n,vector<bool>(n,false));

++for(int i=n-1;i>=0;i--)
{
for(int j=i;j<n;j++)
{
++

if(si!=sj)

dpij=false;

if(si==sj)

{

if(i==j)

dpij=true;//重合

else if(i+1==j)

dpij=true;//相邻

else

dpij=dpi+1j-1;

//不存在越界 因为重合和相邻的情况

//已经给这个二维数组 填好了一部分

}

}

}

int ret=0;

for(auto d:dp)

ret+=reduce(d.begin(),d.end(),0);

return ret;

}

};

lc1611

格雷码:相邻两位做异或,可以得到变化位

格雷码是相邻数二进制表示仅差1位的编码(无多余位变化,防误读

class Solution {

public:

int minimumOneBitOperations(int n) {

int mask = n;

while (mask) {

mask >>= 1;

n ^= mask;

}

return n;

}

};

生成格雷码

class Solution {

public:

//每次把已有的格雷码从后往前取值,加上高位 1

vector<int> grayCode(int n)

{

vector<int> ret;

ret.push_back(0); //初始化

int head=1;

//层数看待

for(int i=0;i<n;i++)

{

for(int j=ret.size()-1;j>=0;j--)

{

ret.push_back(head+retj);

}

head<<=1;

}

return ret;

}

};

lc1155

  1. dpij 表示使用 i 个骰子,总和为 j 时的组合方式数。

  2. 公式

对于第 i 个骰子,它的点数可以是 1~k 中的一个。假设第 i 个骰子掷出的点数是 f ,那么前 i-1 个骰子的总和需要是 j - f 。因此递推公式为:

dpij = (dpij + dpi-1j - f) % MOD (其中 ++f 从 1 遍历到 k ,且 j - f ≥ 0 )。++

  1. 初始化DP数组
  • dp00 = 1 :表示0个骰子,总和为0的情况只有1种(没有骰子时,自然总和为0)。

  • 其他 dp0j ( j>0 )和 dpi0 ( i>0 )都初始化为0,因为这些情况不可能出现

  1. 确定遍历顺序
  • 先遍历骰子的数量 i (从1到 n ),因为每个骰子的状态依赖于前一个骰子的状态。

  • 再遍历目标和 j (从1到 target ),因为当前总和 j 依赖于更小的总和 j - f 。

  • 最后遍历每个骰子的点数 f (从1到 k ),枚举当前骰子可能的点数。

  1. return dpntarget;

class Solution

{

const int MOD = 1e9 + 7;

public:

int numRollsToTarget(int n, int k, int target) {

vector<vector<int>> dp(n + 1, vector<int>(target + 1, 0));

dp00 = 1;

for (int i = 1; i <= n; ++i) {

for (int j = 1; j <= target; ++j) {

++for (int f = 1; f <= k; ++f) {
if (j - f >= 0) {
dpij = (dpij + dpi - 1j - f) % MOD;
++

}

}

}

}

return dpntarget;

}

};

记忆化搜索

class Solution {

public:

int numRollsToTarget(int n, int k, int target) {

if (target < n || target > n * k) {

return 0; // 无法组成 target

}

constexpr int MOD = 1'000'000'007;

vector memo(n + 1, vector<int>(target - n + 1, -1)); // -1 表示没有计算过

auto dfs = \&(this auto&& dfs, int i, int j) -> int {

if (i == 0) {

return j == 0;

}

int& res = memoij; // 注意这里是引用

if (res != -1) { // 之前计算过

return res;

}

res = 0;

for (int x = 0; x < k && x <= j; x++) { // 掷出了 x

res = (res + dfs(i - 1, j - x)) % MOD;

}

return res;

};

return dfs(n, target - n);

}

};

相关推荐
BothSavage9 小时前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法
小林ixn9 小时前
从暴力到KMP:一道题彻底搞懂字符串匹配的前世今生
算法
烬羽11 小时前
字符串算法入门:从反转字符串到回文判断,面试不再慌
算法·面试
先吃饱再说1 天前
判断回文字符串,从一行代码到双指针优化
算法
黄敬峰1 天前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术1 天前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六1 天前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术1 天前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试