SWE · VO Coding

Oracle LC 1944 扩展:每个人左右能看到多少人

将 LC 1944 的单侧可见人数扩展为左右两侧,用两次单调栈扫描并明确同身高规则。

题目重点:将 LC 1944 的单侧可见人数扩展为左右两侧,用两次单调栈扫描并明确同身高规则。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练版身高互不相同;i 能看到 j 当且仅当两者之间每个人都矮于 min(height[i],height[j])。
  • 返回每人 (leftCount,rightCount),按原顺序;若身高相等需另定同高是否遮挡及是否互相可见。

解题路线

  1. 先求向右可见人数:从右往左扫,维护单调栈;当前人弹出所有比自己矮的栈顶,每弹一人都可见。若栈仍非空,最近的更高人也可见一次。
  2. 把当前身高压栈。栈中未被弹出的更远人被较近且不矮的挡住;这一不变量保证被计数者恰为可见人。
  3. 向左问题通过反转数组复用向右算法,再反转结果。两次扫描各 O(N),而非对每人往两侧逐一探测的 O(N²)。
  4. 若面试官要求一趟完成左右两侧,可利用同一单调栈在弹出时更新双方,但证明和相等身高处理更复杂;两趟更稳。

手动走一遍

身高 [10,6,8,5,11,9],下标 0 向右可见 6、8、11 共 3 人;向左为 0。对每个位置分别在原数组与反转数组求右侧计数即可。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

"""Visible counts on both sides for pairwise-distinct heights."""


def _count_right(heights: list[int]) -> list[int]:
    answer = [0] * len(heights)
    stack: list[int] = []
    for index in range(len(heights) - 1, -1, -1):
        while stack and heights[index] > stack[-1]:
            answer[index] += 1
            stack.pop()
        if stack:
            answer[index] += 1
        stack.append(heights[index])
    return answer


def visible_both_sides(heights: list[int]) -> list[tuple[int, int]]:
    if len(set(heights)) != len(heights):
        raise ValueError("training variant requires distinct heights")
    right = _count_right(heights)
    left = list(reversed(_count_right(list(reversed(heights)))))
    return list(zip(left, right))

复杂度

每趟每人最多入栈、出栈各一次,时间 O(N),两趟栈及输出 O(N) 空间。训练版主动拒绝相等身高,避免偷偷选未经题目确认的并列规则。

易错点与追问

容易失分的边界

  • 把距离更远但更高的人全都算可见,忽略中间遮挡。
  • 只算右侧却声称完成题目扩展。
  • 未定义相等身高时直接套严格大于的弹栈条件。

继续深挖

  • 若允许相等身高且同高互相可见,栈的分组与计数如何改?
  • 若只需某个位置的可见人数,是否仍值得预处理整个数组?
✓已复制微信号!