`算法知识` 易错点总结, 经验谈

本文从经验谈出发,深入剖析算法中的常见易错点,包括拆分数位、动态规划状态转移、无符号整数溢出等问题。强调在编程时避免靠猜测和运气,而应严谨思考,确保每个细节都有充分的证明。文中详细解释了如何正确处理这些易错点,以提高代码的正确性和可靠性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

经验谈

  • 不要靠猜, 不要靠感觉 直觉 运气, 不要理想当然, 一定要证明 万分的保障;
    比如贪心, 不要带有试试的心态 碰运气的心态, 试错的代价是很大的!
    试错后, 你还是得去证明, 还要花更多的时间精力 去重新来过

易错点总结

拆分数位

对于一个非负整数a >= 0, 拆分得到其所有数位

很自然想到
for( auto i = a; i > 0; i /= 10){ bits.push_back( i % 10);}

但是, 当a == 0时, bits是空的, 应该把0也放到bits里!!!
思路一定要完整全面, 不要追求代码简短效率等, 首先要考虑到所有极限情况;

正确代码是:

vector< int> bits;
for( auto i = a; i > 0; i /= 10){
    bits.push_back( i % 10);
}
reverse( bits.begin(), bits.end());
if( bits.empty()){ bits.push_back( 0);}

此时,a = 123bits = {1, 2, 3}

DP的状态

以线性DP为例子: 123
dp[123] 由 [3] 和 dp[12] 推出, dp[12] 由 [2] 和 dp[1]推出
… 但是, dp[1] 由 [1] 和 dp[0]推出, 而dp[ 0]是什么呢?
… 假如dp[ 0]是空 是非法的, 那么, dp[ 1]也是非法的; 因为, dp[1] 由 [1] + dp[ 0] 推出, dp[ 0]非法, 则dp[ 1]也非法
… 因此, dp[ 0]并不是空, 他其实是代表一个()(空数, 空值), 这个元素是(空)的, 是非法的, 但是, dp[ 0]不是空的, 他里面有一个元素 就是()
… 此时[1] 可以拆分为 [1] + ()的形式,
… 换句话说, 假如dp代表其集合里的元素个数; dp[ 0]必须为1(因为()空元素是他的唯一元素), 而不能是0
… … 否则, 你的dp[ 1]一定为0, 因为无法进行拆分, 导致1是非法的

从元素个数来看, 假如每个位 都有0-9共10种选择,
[1]时, 他有10^1种形态; 在[2]时, 他有10^2种形态; 即在[k]时, 他有10^k种形态
那么, 在[0]时, 他有10^0 = 1种形态; 那么, 问题来了: 这个形态是什么?
… 这个形态, 是一种不存在的状态, 简称为(空状态)! 但他是存在的, 是一种方案! 只是, 他无形…
这个(不存在的数), 也作为一个(元素), 在这个集合里…
… 这个(空状态), 可以 (和任意状态)组合; 比如: 123 = 123 + (), 其中()就表示这个(空状态), 这个(空状态) 是一种方案!

比如在进行数位DP时, 定义Dp[ a]为: [a个0 -- a个9]之间的 这10^a个数 中, 合法数的个数; (即一定会有: Dp[ a] <= 10^a)
那么, 对于Dp[ 0]是代表那些数? 这个问题非常重要
… 按照定义看, 他代表有10^0 = 1个数; (即, 他的集合 不是空的!!)
… 但有一点不同, 这个数, 无法像Dp[ >0]所代表的那些数, 可以显式的写成数的形式; 这个元素, 是无形的… 他是(空)的
… 因此, 这个元素 一定是非法的; … 因为: (合法) 的前提是: 这个元素 是一个 (数), 就可以显式表达; 而它就无法显式表达
… 即, Dp[ k], k>0 代表 10^k个状态, 每个状态 就对应一个字符串 (0-9组成的字符串)
… … 但是, 当Dp[ k], k=0时, 它代表 1个状态 (即空状态), 但不代表 (任何字符串), 即空状态 无法对应为 字符串;
… … 而我们的(合法), 判定的是(字符串); 所以, (空状态) 是非法的; 但它确实是个(状态)
… 即Dp[ 0] = 0, 这是肯定是; 因为(空状态) 是非法的;
… 这个(空状态), 可以与(任意状态)组合; 123 = 123 + (), 其中那个()表示(空状态)
… 但是, 如果涉及(元素个数) 不涉及合法性, 即10^k, Dp[ 0] = 1, 表示它有1个元素, 不可以设置成0

如果你把Dp[ a]定义为: [0 -- (10^a - 1)]这些数, 这和上面的 Dp定义, 是不同的!
上面定义的是(字符串数) , 即Dp[ 2] = [00 -- 99] 不是[0 -- 99], 但是这里, Dp[ 2] = [0 -- 99];
此时, Dp[ 0] = [0 -- 0], 这是一个状态, 这个状态同时也是合法的 (是0, 是个 数字)
但之前的Dp[ 0] = (), 是一个状态, 但是不是合法的 (因为他不是个 字符串数)

auto巧用

void Func( int _a){
	vector< int> bits;
    for( int i = _a; i > 0; i /= 10){
        bits.push_back( i % 10);
    }
}

bits存储: _a的每个数位, 这个一个很经典的算法;

可是, 如果我们后期改进函数, 将_a修改为了Ll_类型 , 此时, int i = _a就出错了!
就必须再手动修改为: Ll_ i = _a; 非常容易遗忘;

好的办法是: for( auto i = _a; ...), 这多好!

size_t无符号整数溢出

对于容器, 通常都会有一个size()的函数, 表示容器里元素个数, 其通常是size_t无符号类型

当容器为空时, 即size() == 0
v.size() - 1, a的值会是: 最大的正数(二进制全1)

当使用for( int i = 0; i <= v.size() - 1; ++i)来遍历元素, 这会导致死循环!


比如:

#define FOR( i, l, r) for( int i = l; i <= r; ++i)
 
FOR( i, 0, v.size() - 1){}

这会死循环的


解决办法:

  • for( int i = 0; i < v.size(); ++i), 左闭右开方式
  • for( int i = 0; i <= int( v.size()) - 1; ++i), 转换为int类型
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值