递归网格路径计数方法中局部变量与类字段的运行时行为差异及原因分析
问题根源:类字段的共享性导致递归重入时的变量覆盖
你遇到的问题和堆内存、栈内存的存储位置没有关系,核心是类字段和局部变量的作用域与生命周期差异:
为什么用类字段会报错?
类字段key属于Finder的实例对象,所有递归调用都会共享同一个key变量。当递归嵌套调用时,内层的递归会覆盖这个key的值,等回到外层调用的上下文时,key已经不是外层最初计算的那个值了。
举个你提到的m=3,n=2的场景:
- 初始调用
FindPaths(3,2),计算key为"2,3",然后进入递归。 - 递归调用
FindPaths(2,2),把类字段key更新为"2,2",继续递归。 - 再调用
FindPaths(1,2),key被改成"1,2",接着调用FindPaths(1,1)。 FindPaths(1,1)把key改成"1,1",发现memo里已有这个key,直接返回1。- 回到
FindPaths(1,2)的上下文时,类字段的key已经被FindPaths(1,1)改成了"1,1",这时候执行memo.Add(key, value),就会尝试往字典里添加已经存在的"1,1",自然抛出重复key的异常。
为什么局部变量能正常工作?
局部变量key是每个方法调用栈帧独有的,每个递归调用都会创建自己的key副本,内层递归的修改不会影响外层的变量值。当FindPaths(1,2)执行到memo.Add(key, value)时,用的是自己最初计算的"1,2",完全不会被其他递归调用干扰。
关于“想用类字段更实用”的想法
其实这种递归场景下,共享类字段反而会引入隐藏的bug,因为递归天然是重入的,共享状态很容易被意外覆盖。而且局部变量的创建成本极低(只是栈上的一个字符串引用),完全不用担心性能问题。如果实在不想每次写重复的key生成代码,可以把key生成逻辑抽成一个私有辅助方法,比如:
private string GenerateKey(int m, int n) { return String.Format("{0},{1}", Math.Min(m, n), Math.Max(m, n)); }
然后在方法里用string key = GenerateKey(m, n);,既保持代码简洁,又避免共享变量的问题。
另外你提到的“把Add里的key换成重新计算的表达式”能解决问题,本质是绕开了被覆盖的类字段,直接用当前方法的m、n生成正确的key,但这种写法不如用局部变量清晰直观。
内容的提问来源于stack exchange,提问作者jokloworm
相关产品推荐
相关产品推荐

