双指针+贪心思想

题目考点

这道题主要考的是:

  1. 双指针
  2. 贪心思想
  3. 列表两端取值
  4. 审题后把“文字规则”翻译成“序列单调性”

这题表面上像“堆方块”,本质上其实是在问:

能不能从数组两端依次取数,取出的序列始终保持“不变大”。

也就是:后拿出来放上去的方块,边长不能比下面那个更大。


审题

输入是什么

有 T 组测试数据。

每组数据里:

  • 第一行是方块数量 n
  • 第二行是 n 个整数,表示一排方块的边长

输出是什么

对于每组数据:

  • 如果能按题目要求堆起来,输出 Yes
  • 否则输出 No

题目真正要求我们判断什么

每次只能拿:

  • 最左边的方块
  • 或最右边的方块

而且新堆出来的竖直柱子要满足:

  • 上面的方块不能比下面的大

换句话说,如果我们把“拿方块的顺序”记下来,这个顺序必须是一个**从大到小(允许相等)**的序列。

容易忽略的点

这题最容易卡住的地方有两个:

第一,题目不是让你排序

你不能随便重排,只能从两端拿。

第二,不是去模拟“整个柱子长什么样”

你只需要保证:

当前拿出来的这个方块,不能比前一个已经放好的方块更大。

所以我们只需要记住“上一个放上去的方块大小”。


思路提示

先不要急着看代码,先抓住这题最核心的一句话:

每一步都应该优先拿两端中更大的那个。

为什么?

因为你要让整个取出的序列保持“不变大”。

假设两端分别是 4 和 7:

  • 如果你先拿 4
  • 那么 7 以后就只能放在 4 上面
  • 但 7 > 4,肯定不行

所以更安全的做法是:

  • 先拿大的
  • 小的留到后面

这就是这道题的贪心核心。

你可以先自己记住这个判断模板:

  1. 看左端和右端
  2. 取较大的那个
  3. 判断它是否还能放在当前堆顶上
  4. 如果能,继续
  5. 如果不能,直接 No

完整设计思路

我们把“已经堆好的最上面那个方块”记成 top

一开始还没堆,所以 top 可以看成无穷大。
也就是第一个方块无论多大都能放。

然后不断做下面这件事:

第一步:看两端哪个更大

设:

  • 左端是 blocks[left]
  • 右端是 blocks[right]

因为我们最后想形成一个不递增序列,所以优先选较大的那个:

  • 如果左边 >= 右边,就选左边
  • 否则选右边

第二步:判断能不能放

如果选出来的这个值 candidate 满足:

candidate <= top

说明它可以放到当前堆顶上面。

那么:

  • 更新 top = candidate
  • 对应那一侧指针向中间移动

第三步:一旦出现 candidate > top

说明你现在无论怎么选,已经破坏规则了,直接输出 No

第四步:如果所有方块都处理完了

说明整个过程一直合法,输出 Yes


为什么这个贪心是对的

这里一定要把道理想透。

我们每一步都在两端里选一个。
假设当前两端是 ab,并且 a < b

如果你现在选了较小的 a,那么以后 b 还在那里。
可因为 b > a,它以后要放到 a 上面,就不合法了。

所以:

  • 小的可以放在大的上面
  • 大的不能放在小的上面

因此两端都可选时,优先选更大的那个,才是更稳妥的。

这就是这题的贪心依据。


代码实现

下面给你一个适合初学者理解的写法,用双指针,不用真的从列表头部删除元素,因为那样效率低。

t = int(input())

for _ in range(t):
    n = int(input())
    blocks = list(map(int, input().split()))

    left = 0
    right = n - 1
    top = float('inf')   # 当前堆顶,开始时设为无穷大
    possible = True

    while left <= right:
        if blocks[left] >= blocks[right]:
            candidate = blocks[left]
            left += 1
        else:
            candidate = blocks[right]
            right -= 1

        if candidate <= top:
            top = candidate
        else:
            possible = False
            break

    if possible:
        print("Yes")
    else:
        print("No")

运行演示

样例 1

输入:

blocks = [4, 3, 2, 1, 3, 4]

初始:

  • left 指向 4
  • right 指向 4
  • top = 无穷大

第一次

两端是 4 和 4,任选较大,取左边 4

  • 4 <= inf,可以放
  • top = 4

第二次

两端是 3 和 4,取 4

  • 4 <= 4,可以放
  • top = 4

第三次

两端是 3 和 3,取 3

  • 3 <= 4,可以放
  • top = 3

第四次

两端是 2 和 3,取 3

  • 3 <= 3,可以放
  • top = 3

第五次

两端是 2 和 1,取 2

  • 2 <= 3,可以放
  • top = 2

第六次

只剩 1

  • 1 <= 2,可以放

全部成功,所以输出:

Yes

样例 2

输入:

blocks = [1, 3, 2]

初始:

  • 左端 1
  • 右端 2
  • top = inf

第一次

取较大的一端 2

  • 2 <= inf,可以放
  • top = 2

第二次

现在两端是 1 和 3,取较大的一端 3

但:

  • 3 > 2

说明 3 不能放到 2 上面,失败。

所以输出:

No

方法总结

这类题以后你可以这样识别:

一看到这些特征,就要想到“双指针 + 贪心”

题目如果同时出现下面几个关键词:

  • 只能从两端取
  • 每次取一个
  • 要满足某种大小顺序
  • 问能不能做到

那么大概率就是这类题。

这类题的通用思考套路

先把题目翻译成“序列规则”:

  • 这里的规则就是:取出的顺序必须不递增

然后再想:

  • 每次只能从两端选
  • 为了不让后面更难放,应该优先拿较大的那个

最后用双指针模拟整个过程。

这题最关键的不是代码,而是这句转换

“堆起来合法” = “取出来的序列不能变大”。

一旦你把这句话想明白,代码其实很直。


易错点提醒

1. 想去排序

不能排序。
因为题目限制了只能从两端拿。

2. 用 pop(0)

很多初学者会写:

blocks.pop(0)

这个在 Python 里效率很低,因为前面的元素删掉后,后面都要整体前移。

这题数据很大,最好用双指针:

  • left
  • right

3. 不是选较小的那个

有些同学会想:“上面要小,那我每次拿小的不就好了?”

这是错的。

因为小的放上去以后,大的可能就再也放不上去了。
所以应该优先拿两端中较大的那个。


本节小结

这题本质不是“堆方块模拟”,而是一个很经典的贪心判断题。

核心只有三步:

  1. 维护当前堆顶 top
  2. 每次从两端选较大的那个
  3. 判断它是否 <= top

能一直放下去就是 Yes,中途有一步放不下就是 No


练习

你先自己试试这道同类型小练习,不要急着看答案。

练习题

判断下面这组方块能否成功堆起来:

blocks = [5, 4, 3, 2, 1, 6]

输出应该是 Yes 还是 No

提示

按这题的方法来:

  1. 两端比较
  2. 每次取较大的那个
  3. 记录当前堆顶
  4. 看会不会出现“新拿出来的方块比堆顶还大”

你先自己手动模拟一遍。
你要是愿意,我下一条可以继续带你把这题里的这句核心逻辑单独讲透:

if blocks[left] >= blocks[right]:

也可以继续补一节:为什么这题看起来像栈题,但其实更像双指针贪心题。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇