💻 Python 矩阵操作与控制流(初学者角度)

1. 矩阵(嵌套列表)的读取与维度确定

在 Python 中,面试题中的 M×NM \times NM×N 矩阵通常用嵌套列表(List of Lists)来表示。

概念 Python 实现 解释与算法思想
矩阵的数据结构 matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 这是一个列表,列表的每个元素又是一个列表(代表一行)。
确定行数 MMM M = len(matrix) 算法思想: 行数 MMM 就是最外层列表的长度。这里使用了 Python 的内置函数 len()
确定列数 NNN N = len(matrix[0]) 算法思想: 列数 NNN 就是任一行(通常取第一行 matrix[0])的长度。这里使用了索引操作 matrix[0] 访问第一行,再用 len() 求其长度。
读取坐标 (i,j)(i, j)(i,j) value = matrix[i][j] 算法思想: 这是一个连续的索引操作。第一个索引 [i] 找到第 iii 行的列表,第二个索引 [j] 找到该行列表的第 jjj 个元素。

写法分析:

  • m = len(matrix):这是正确的,得到了行数 MMM
  • matrix[:, 0]:这个写法是 NumPy 库Pandas 库中的切片操作,在纯 Python 的嵌套列表中是不生效的。在纯 Python 中,您必须用 len(matrix[0]) 来求列数 NNN

2. 核心算法逻辑:绝对值列差的计算

您提到不知道如何表示**“减去那个 abs\text{abs}abs 列叉”。这涉及到简单的数学运算和内置函数**。

概念 Python 实现 解释与算法思想
计算绝对值 abs(x) 内置函数 abs() 用于求任何数字(整数或浮点数)的绝对值。这是最常用的数学函数之一。
计算列差惩罚 penalty = abs(j - k) 控制流/算法思想: 在动态规划的迭代中,假设:
* j当前行选取的列坐标。
* k上一行选取的列坐标。
完整减法 new_score = old_dp_score - abs(j - k) 数学运算: old_dp_score 是从上一行 kkk 位置转移过来的最大累积得分。我们将惩罚项直接从这个累积得分中减去。

🧠 栈求和

如果一道算法题确实需要使用栈(Stack)来求和,那么它通常不会是动态规划(DP)求最优值的路径问题。

需要用到栈求和的典型场景是涉及深度优先搜索 (DFS)回溯 (Backtracking)、或者表达式求值数据结构自身特性的题目。

我将以一个经典的栈应用场景——求路径和为例,来讲解正确的栈求和思路和 Python 实现。


🧭 正确的栈求和场景:深度优先搜索 (DFS)

1. 经典应用场景:二叉树中的路径总和

假设题目是:

给定一棵二叉树和一个目标和 SSS。判断是否存在一条从根节点到叶节点的路径,路径上所有节点值的总和等于 SSS

在这个场景下,栈(在 Python 中通常用列表模拟)可以用于存储当前正在探索的路径上的节点值,并进行求和。

2. Python 实现与算法思想

在这个 DFS 场景中,栈的作用是记录状态,而回溯(即出栈)是关键的控制流。

概念 Python 实现 解释与算法思想
数据结构(栈) path_stack = [] 用 Python 列表模拟栈,append() 是入栈,pop() 是出栈。
算法思想(DFS) 递归显式栈迭代 栈用于存储(节点, 当前累积和)或(节点)并在函数外部维护和。
栈求和(回溯) 入栈 (Push): 访问新节点时,将节点值加入栈中。
求和: 在判断是否是目标路径时,对栈求 sum(path_stack)
出栈 (Pop): 离开该节点(回溯)时,将节点值从栈中移除,确保栈只包含当前路径
控制流 递归调用while 循环 递归是隐式的栈;while 循环配合 stack.pop() 是显式的栈操作。

3. 代码示例 (DFS 结合栈求和)

这个例子展示了如何在 DFS 过程中维护和利用栈来计算路径和

# 假设 Node 有 .val, .left, .right 属性
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def has_path_sum_dfs(root: TreeNode, target_sum: int) -> bool:
    if not root:
        return False

    # 使用列表模拟栈,存储当前路径上的节点值
    path_stack = []

    def dfs_helper(node):
        # 算法思想:入栈(Stack Push)
        path_stack.append(node.val)
        
        # 1. 检查是否为叶子节点
        if not node.left and not node.right:
            # 算法思想:栈求和
            # 这是一个关键的条件分支:只有在叶节点才对当前路径(栈)求和
            current_sum = sum(path_stack)
            if current_sum == target_sum:
                # 找到目标路径,返回 True
                return True
        
        # 2. 递归访问子节点(控制流:条件分支和递归调用)
        found = False
        if node.left:
            if dfs_helper(node.left):
                found = True
        if not found and node.right:
            if dfs_helper(node.right):
                found = True

        # 算法思想:回溯 / 出栈 (Stack Pop)
        # 离开当前节点前,将其从路径中移除,这是栈求和正确的关键
        path_stack.pop()
        
        return found

    return dfs_helper(root)

# 注意:在实际的树问题中,为了避免重复求和,更高效的做法是在栈中存储(节点, 累计和)
# 避免在每次到叶节点时都对整个栈求和 O(N^2)

总结:栈 vs. DP 的使用区别

特性 栈求和 (DFS/回溯) 动态规划 (DP)
目标 找到一条满足条件的路径/状态(是或否),或记录特定路径。 找到所有路径中的最优值(最大或最小)。
存储内容 存储路径上的元素/操作 存储到当前状态的最优累积值
求和方式 每次探索完一条路径后,对栈(路径)内容进行一次性求和 状态转移时(递推),不断累加历史最优值。
适用场景 树/图的遍历、括号匹配、表达式求值、回溯搜索。 最优子结构重叠子问题的阶段性决策问题。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐