Patience Sorting 中“堆栈表示”与“最长递增子序列长度”的等价性证明
题目描述
Patience Sorting(耐心排序)不仅仅是一个排序算法,其核心结构还提供了一个经典应用:在给定一个长度为 n 的整数序列时,通过模拟Patience Sorting的“堆栈”构建过程,可以高效地求出该序列的最长递增子序列(Longest Increasing Subsequence, LIS)的长度。
本题要求:详细解释如何通过Patience Sorting算法构建堆栈,并严谨地证明最终得到的堆栈数量 k 就等于原序列的最长递增子序列的长度。
解题过程
我们循序渐进地解释并证明。
第一步:理解 Patience Sorting 的基本玩法
想象你有一副牌,你需要按照以下规则把它们整理成若干堆:
- 你只能按顺序依次处理每张牌(即序列顺序)。
- 对于当前处理的牌(记为
x):- 查看现有所有堆的堆顶牌(每堆只有最上面的那张牌可见)。
- 如果存在堆顶牌的值 大于等于
x,则将x放入最左边的那个满足条件的堆顶之上(覆盖它)。 - 如果所有堆顶牌都小于
x,则新建一个堆,将x放入这个新堆。
核心原则:始终保持每个堆的堆顶牌是从堆底到堆顶非递减的(新牌x放在第一个堆顶 >= x 的堆上,保证了该堆新堆顶 x <= 旧堆顶)。
第二步:算法执行示例
以序列 [3, 1, 4, 1, 5, 9, 2, 6] 为例:
3: 无堆,新建堆1 →[3]1: 堆顶3>=1,放堆1 →[1](堆1)4: 堆顶1<4,新建堆2 →[1], [4]1: 堆顶1(堆1) >=1,放堆1 →[1], [4](注意:堆1现在是[旧1, 新1],堆顶是1)5: 堆顶1(堆1)<5, 堆顶4(堆2)<5,新建堆3 →[1], [4], [5]9: 所有堆顶(1,4,5) <9,新建堆4 →[1], [4], [5], [9]2: 堆顶1(堆1)<2, 堆顶4(堆2)>=2,放堆2 →[1], [2], [5], [9]6: 堆顶1<6, 堆顶2<6, 堆顶5<6, 堆顶9>=6,放堆4 →[1], [2], [5], [6]
最终有 4个堆。可以验证,原序列的LIS之一为 [1, 4, 5, 6] 或 [1, 4, 5, 9],长度正是4。
观察:在放置牌x时,我们总是把它放在“允许放置的最左边堆”。这个“最左边”的选择是等价性证明的关键。
第三步:证明目标分解
设最终堆的数量为 k。我们需要证明:
- 存在一个长度为
k的递增子序列。(k <= LIS长度) - 不可能存在长度超过
k的递增子序列。(LIS长度 <= k)
综上可得 k = LIS长度。
第四步:关键引理与“反链”概念
定义:在序列中,如果两个元素在不同的堆里,并且后一个元素在序列中的位置先于前一个元素,那么它们构成一个“下降”关系(更准确地说,不能同时存在于同一个递增子序列中)。
引理(堆顶性质):任意时刻,不同堆的堆顶牌从左到右形成一个非递减序列。
- 证明:这是由放置规则保证的。新牌
x放在第一个堆顶>=x的堆上,这使得该堆新堆顶x小于等于原来其右侧所有堆的堆顶(因为原来那些堆顶都>=x才没被选中),因此从左到右的堆顶值不会减少。
关键洞察:从同一堆中垂直方向取牌(从底到顶),它们的时间顺序是递减的!因为后放入的牌(位置靠后)值更小(或相等),才能放在先放入的牌上面。所以同一个堆里的牌,在原始序列中下标递增,但牌面值递减。因此,同一个堆中不可能取出超过1张牌来构成一个递增子序列。
第五步:构造性证明(存在长度为k的递增子序列)
我们可以从最终形成的堆中,逆序构造一个长度为 k 的递增子序列:
- 从最后一张被处理的牌(它在某个堆顶)开始,设为
s_k。 - 向前(向左)找:在它被放入的时刻之前,找一个堆顶刚好小于
s_k的牌,且这张牌位于不同的堆。根据堆顶非递减性质,这个“刚好小于”的牌一定在紧邻左边的堆的堆顶(设为s_{k-1})。 - 重复步骤2,每次向左移动一个堆,找到
s_{k-2}, ..., s_1。
为什么可行?
- 由放置规则,
s_i被放入时,是因为当时所有堆顶都>= s_i,而s_{i-1}是当时左边堆的堆顶且s_{i-1} < s_i(“刚好小于”)。所以值上是递增的:s_1 < s_2 < ... < s_k。 - 时间顺序上,
s_{i-1}成为堆顶的时刻一定早于s_i被放入的时刻(因为s_i是看到了s_{i-1}这个堆顶后才决定新建堆或放到更右边)。所以下标也是递增的。
因此,(s_1, s_2, ..., s_k) 构成了一个长度为 k 的严格递增子序列。所以 k <= LIS长度。
第六步:上界证明(LIS长度不可能超过k)
用反证法。假设存在一个长度为 L > k 的递增子序列 (a_1, a_2, ..., a_L)。
考虑这个子序列中的每个元素 a_i 在Patience Sorting过程中被放入时的情形。
由于子序列是递增的,即 a_1 < a_2 < ... < a_L,并且下标也递增。
断言:对于这个递增子序列中的任意两个元素 a_i 和 a_j(i < j),它们不可能被放入同一个堆中。
- 证明:如果它们在同一个堆,由于堆内从上到下(放置时间从晚到早)值是非递增的,那么后放入的
a_j(时间更晚,位置更高)必须小于等于先放入的a_i。但这与a_i < a_j矛盾。
因此,递增子序列中的每个元素必须占据不同的堆。那么长度为 L 的递增子序列就需要至少 L 个不同的堆。但算法只创建了 k 个堆,且 L > k,这就矛盾了。
所以,不存在长度超过 k 的递增子序列,即 LIS长度 <= k。
第七步:结论
由第五步(k <= LIS长度)和第六步(LIS长度 <= k),我们得到:
通过Patience Sorting算法得到的堆数 k 恰好等于原序列的最长递增子序列(LIS)的长度。
总结
这个证明的精妙之处在于:
- 放置规则(最左边可行堆) 保证了堆顶的有序性,这是构造递增子序列的基础。
- 堆的内部性质(后入者值更小) 保证了同一堆的元素不能同时出现在一个递增子序列中,这给出了LIS长度的上界。
- 构造性方法 通过从后向前、跨堆选取,实际构建出了一个长度为堆数
k的递增子序列,证明了下界。
因此,Patience Sorting不仅是一个排序算法,其堆栈数量直接揭示了序列的“递增潜力”——最长递增子序列的长度。这个联系是组合数学与算法分析中一个优美而深刻的结论。