从132模式入手,尝试理解单调栈的使用

132模式判断题

给定一个整数序列:a1, a2, ..., an,一个132模式的子序列 ai, aj, ak 被定义为:当 i < j < k 时,ai < ak < aj。设计一个算法,当给定有 n 个数字的序列时,验证这个序列中是否含有132模式的子序列。

注意:n 的值小于15000。
示例1:
输入: [1, 2, 3, 4]
输出: False
解释: 序列中不存在132模式的子序列。

示例 2:
输入: [3, 1, 4, 2]
输出: True
解释: 序列中有 1 个132模式的子序列: [1, 4, 2].

示例 3:
输入: [-1, 3, 2, 0]
输出: True
解释: 序列中有 3 个132模式的的子序列: [-1, 3, 2], [-1, 3, 0] 和 [-1, 2, 0].

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/132-pattern
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

开始分析(开始bb)

前言

新手,还没心意...不会排版,暂时还没那心思学。如有冒犯请多多包涵...

	java是面向对象编程,这里提这个不是为了束缚各位的思路,而是我尝试找到一种解决问题的思路。
此题目为132模式,那么首先,我们这里可以很直白的得到三个对象。我们为了不混淆,重新命名为ACB。输入的可以理解它是一种数组结构,提到数组结构,排序、下标访问。
我们开始给这几个对象建立联系,这点也很直白:1<3,2<3,但如果以我们习惯的单角度顺序结构来做题,那很可能就变成了暴力循环嵌套。所以,我们对对象编程采取多角度观察,找出共性,以不同的排列组合去处理这几个对象。

	废话说了一堆,是为了以后能快速的进入自己的思路,有问题就警惕,没问题就牢记!

	首先是a最小,免不了的第一次循环。我们要让判断语句在任何地方都能随时的获取这个最小值。
	
	其次是cb这个关系,正向寻找时,我们获取c,c比a大,c比b大,但这样并不能得出答案,我们还需要保证b比a大,也就是说我们这次循环并不能解决问题,在此基础上去判断b比a大,那我们需要操控的对象就太多了,并且每走一步都跟着大把的因果线。这个思路放弃。
	正向不行我们试试倒着来,反正正着循环也是,倒着循环也是。
	我们倒着循环先取到b,那么拿到b我们和a作比较(因为a思路已定,则默认我们随时都能拿到a)。保证b比a大,再保障b比c小,题目以符合。既然思路成功,那就细节实施,看看自己的知识储备是否支撑这个写法。
	
	更多解法可以去原题的题解处查询。博主只是学习阶段的自我总结,如果能对你有帮助的话是博主的荣幸了。
同时希望大家不吝赐教,这道题的树形解法一直没想明白。以后遇到再研究吧...

最小值a随时随地取得的实现

int[] arrayIn = {3,2,4,3,1}; //测试用例
// 输入定义为int[] arrayIn
getA(int[] arrayIn){
    
	// 定义与输入等长的min[],储存此次循环指向点的最小值,也就是倒序遍历时,此次作为b的左边最小的值
	int[] min = new int[n]; // 目的是为了以空间换时间,但是数组是一组连续的内存空间,下策,以后考虑下其他办法解决
	
    min[0] = arrayIn[0]; // a是正向遍历,所以第一个位置的最小值就是本身
	for (int i = 1; i < n; ++i) {
		min[i] = Math.min(min[i - 1], arrayIn[i]); // 数组赋值
	}
}

我们倒序遍历,拿起一个就问它:"你是不是B?"

boolean testMethod(int[] arrayIn, int[] min) {
    int n = arrayIn.length;
    // 我们假设,抓到了b
    Integer b = null;
    for (int i = n - 1; i >= 0; i--) {
    	if (nums[i] > min[i]) {
            // b比 min[i] 还小或者相同,那么说明不可能是b,那么null
            if (b != null && (b <= min[i])) {
                b = null;
            }
            // b与c,如果b比c小,那么说明找到了
            if (b != null && (b < nums[i])) {
                return true;
            }
            // 更新b的值
            b = nums[i]; // 此处代码有问题了!!!!!!!!!!!!!!!
        }
    }
}

发现问题开始分析如何解决

这里的b值,发现无法保留,每一次都会更新为当前值的前一个值。
举例说明比较清楚
{3, 5, 0, 3, 4}
输入用例为这一组时
354明显满足acb模式,但是上述代码在取4进行判断时,不符合条件,b被更新为3了,在b为3的循环中,由于c为0与最小值相等,先决条件直接退出,然后到了c为5时,b为3,a在维护的数组种取值为3,不符合a<b。但是注意,此时如果能判断b为4时,也是符合条件的。
也就是说,我们要想办法保留住b之前的4,因为它是符合条件的。也就是我们的上述方法无法覆盖这种需求

那么,我们的b值就应该是一个集合、一种数据结构。我们要选择那种最为合适呢?
由于132虽说中间可间隔,但是依然是需要有特定顺序的。所以我们首先考虑有序的集合。
我们通过测试用例与前面的思路不难发现,我们的a是一个循环外的值,是一个我们即得的论外值。所以我们在进行比较时,优先考虑b与a的关系是否合格。如果不合格,则直接放弃这个b值,#因为随着循环的从后往前,我们的a值不可能会变小了!!也就是说此b值是绝对不合格的。

那么也就是说,过了这一关的b值,哪怕比“循环后面,数组前面”的c值大,是可能符合条件的b值。所以我们要把这个b值储存。
也就是说,我们在bc判定时,不符合条件进入下一回合的时候,当前b值就是上一轮的c值,也就是必定比b小的那个值。
也就是说,此储存b的集合如果是有序存入,它的元素是越来越小的。
也就是说,我们发现的符合条件的b值,被先存入的b值,是最大的b值。
并且,这所有的b值都符合b>a,#(因为他们都过了第一道判断,如果不符合条件可直接移除)
也就是说,后进的先出来做判断,如果这个b值都不符合b与c的条件,那么后面更大的b则绝对不可能符合条件。

所以说,后进先出,先进后出。这个数据结构,我们选什么???
#没错,stack,堆栈。

修改一下b的判断方法

Stack<Integer> stack = new Stack<>();
for (int i = n - 1; i >= 0; --i) {
	if (arrayIn[i] > min[i]) {
    	// 栈顶元素的b,与a的比较
    	while (!stack.isEmpty() && stack.peek() <= min[i]) {
            // acb中,b与a的大小不符合,直接弹出不要这个b值,循环看看下一个
            stack.pop();
        }
    	// 栈顶元素的b,与c的比较
    	if (!stack.isEmpty() && stack.peek() < arrayIn[i]) {
            // 符合条件,那直接返回true
            return true;
        }
    	// 当前c值入栈,迎接下一次循环的ba、bc检验。经过ba成候补,经过bc直接通过。
    	stack.push(arrayIn[i]);
    }
}

我们结合全部代码做一个实现吧。这样才符合咱们cv偷懒工程师的习惯嘛。

class LeetCode_132Test {

    public static void main(String[] args) {
        LeetCode_132Test acb = new LeetCode_132Test();
        int[] array01 = {1,2,3,4,5,6};
        int[] array02 = {3,2,4,3,1};
        int[] array03 = {3,5,0,3,4};
        boolean res = acb.methodTest(array03);
        System.out.println(res);
    }

    public boolean methodTest(int[] arrayIn) {

            int n = arrayIn.length;
            int[] min = new int[n];
            min[0] = arrayIn[0];
            
            for (int i = 1; i < n; ++i) {
                min[i] = Math.min(min[i - 1], arrayIn[i]);
            }
            Stack<Integer> stack = new Stack<>();
            
            for (int i = n - 1; i >= 0; --i) {
                if (arrayIn[i] > min[i]) {
                    
                    while (!stack.isEmpty() && stack.peek() <= min[i]) {
                        stack.pop();   
                    }
                    if (!stack.isEmpty() && stack.peek() < arrayIn[i]) {
                        return true;
                    }
                    stack.push(arrayIn[i]);
                }
            }
            return false;
    }
}

写在最后

这个应该就是”单调栈“的模式了。吧?...这篇博客草率了,后续会有更改,但是不知道是何年何月。哈哈。

posted @ 2021-03-25 18:03  陈烛  阅读(120)  评论(0)    收藏  举报