数据结构与算法:单调栈(monotonic stack)

文章目录

    • 背景
    • 小窍门
    • LeftBigger
      • 例子
      • 代码
    • LeftSmaller
      • 例子
      • 代码
    • RightBigger
      • 例子
      • 代码
    • RightSmaller
      • 例子
      • 代码
    • 测试
    • LeetCode题目

背景

我们经常会遇到这种类型的问题:

  1. 给定一个数组,针对数组中每个元素,找出其左边第一个比它大的数(LeftBigger);
  2. 给定一个数组,针对数组中每个元素,找出其左边第一个比它小的数(LeftSmaller);
  3. 给定一个数组,针对数组中每个元素,找出其右边第一个比它大的数(RightBigger);
  4. 给定一个数组,针对数组中每个元素,找出其右边第一个比它小的数(RightSmaller);

针对这种问题,有两种方式进行求解:

  1. 单调栈求解;时间复杂度为O(N)
  2. 暴力求解;可通过两重循环进行求解,时间复杂度为O(N的平方);

本文主要介绍单调栈的方法求解。

注:为方便问题叙述,我们这里返回的都是目标数的下标。


小窍门

  1. 如果是找左边的数,数组遍历时从右往左遍历;
  2. 如果是找右边的数,数组遍历时从左往右遍历;
  3. 如果是找小于当前的数,则我们使用单调递增栈;
  4. 如果是找大于当前的数,则我们使用单调递减栈;

LeftBigger

给定一个数组,针对数组中每个元素,找出其左边第一个比它大的数的下标。

例子

如下图所示,针对元素黄色5,其左边第一个比它大的数为绿色8,则应该返回1(绿色8的下标

数据结构与算法:单调栈(monotonic stack)_第1张图片

代码

根据小窍门,很容易写出如下代码(注:如果左边没找到比它大的数,返回-1):


public int[] getLeftBiggerIndexByMethodStack(int[] nums) {
    Stack<Integer> stack = new Stack<>();
    int[] ans = new int[nums.length];
    for (int i = nums.length - 1; i >= 0; i--) {
        if (stack.isEmpty()) {
            stack.push(i);
            ans[i] = -1;
        } else {
            while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
                int x = stack.pop();
                ans[x] = i;
            }
            stack.push(i);
            ans[i] = -1;
        }
    }
    return ans;
}

LeftSmaller

给定一个数组,针对数组中每个元素,找出其左边第一个比它小的数的下标。

例子

如下图所示,针对元素黄色5,其左边第一个比它小的数为绿色1,则应该返回3(绿色1的下标)。

数据结构与算法:单调栈(monotonic stack)_第2张图片

代码

根据小窍门,很容易写出如下代码(注:如果左边没找到比它小的数,返回-1):


public int[] getLeftSmallerIndexByMethodStack(int[] nums) {
    Stack<Integer> stack = new Stack<>();
    int[] ans = new int[nums.length];
    for (int i = nums.length - 1; i >= 0; i--) {
        if (stack.isEmpty()) {
            stack.push(i);
            ans[i] = -1;
        } else {
            while (!stack.isEmpty() && nums[stack.peek()] > nums[i]) {
                int x = stack.pop();
                ans[x] = i;
            }
            stack.push(i);
            ans[i] = -1;
        }
    }
    return ans;
}

RightBigger

给定一个数组,针对数组中每个元素,找出其右边第一个比它大的数的下标。

例子

如下图所示,针对元素黄色5,其右边第一个比它大的数为绿色6,则应该返回5(绿色6的下标)。
数据结构与算法:单调栈(monotonic stack)_第3张图片

代码

根据小窍门,很容易写出如下代码(注:如果右边没找到比它大的数,返回数组长度):


public int[] getRightBiggerIndexByMethodStack(int[] nums) {
    Stack<Integer> stack = new Stack<>();
    int[] ans = new int[nums.length];
    for (int i =  0; i < nums.length; i++) {
        if (stack.isEmpty()) {
            stack.push(i);
            ans[i] = nums.length;
        } else {
            while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
                int x = stack.pop();
                ans[x] = i;
            }
            stack.push(i);
            ans[i] = nums.length;
        }
    }
    return ans;
}

RightSmaller

给定一个数组,针对数组中每个元素,找出其右边第一个比它小的数的下标。

例子

如下图所示,针对元素黄色5,其右边第一个比它小的数为绿色2,则应该返回7(绿色2的下标)。

数据结构与算法:单调栈(monotonic stack)_第4张图片

代码

根据小窍门,很容易写出如下代码(注:如果右边没找到比它的数,返回数组长度):


public int[] getRightSmallerIndexByMethodStack(int[] nums) {
    Stack<Integer> stack = new Stack<>();
    int[] ans = new int[nums.length];
    for (int i =  0; i < nums.length; i++) {
        if (stack.isEmpty()) {
            stack.push(i);
            ans[i] = nums.length;
        } else {
            while (!stack.isEmpty() && nums[stack.peek()] > nums[i]) {
                int x = stack.pop();
                ans[x] = i;
            }
            stack.push(i);
            ans[i] = nums.length;
        }
    }
    return ans;
}

测试

为验证代码的正确性,采用如下方法进行验证:

  1. 随机生成长度为100的数组;
  2. 根据单调栈算法,得出结果下标数组a;
  3. 根据暴力算法,得出结果下标数组b;
  4. 验证数组a与b的每个元素值都相同;
import java.util.*;

public class Test {

    public static void main(String[] args) {
        Solution solution = new Solution();

        for (int i = 0; i < 1000; i++) {
            // 生成长度为100的随机数组
            int[] nums = new Random().ints(100).toArray();
            // 针对数组中某个数,找到左边第一个比当前数大的数的下标
            int[] a = solution.getLeftBiggerIndexByMethodLoop(nums);
            int[] b = solution.getLeftBiggerIndexByMethodStack(nums);
            boolean ans = solution.check(a, b);
            if (!ans) {
                System.out.println("error left Closest bigger");
            }

            a = solution.getLeftSmallerIndexByMethodLoop(nums);
            b = solution.getLeftSmallerIndexByMethodStack(nums);
            ans = solution.check(a, b);
            if (!ans) {
                System.out.println("error left Closest smaller");
            }

            a = solution.getRightBiggerIndexByMethodLoop(nums);
            b = solution.getRightBiggerIndexByMethodStack(nums);
            ans = solution.check(a, b);
            if (!ans) {
                System.out.println("error right Closest bigger");
            }

            a = solution.getRightSmallerIndexByMethodLoop(nums);
            b = solution.getRightSmallerIndexByMethodStack(nums);
            ans = solution.check(a, b);
            if (!ans) {
                System.out.println("error right Closest smaller");
            }
        }

        System.out.println("Success");

    }
}

import java.util.*;

class Solution {

    public int[] getLeftBiggerIndexByMethodStack(int[] nums) {
        Stack<Integer> stack = new Stack<>();
        int[] ans = new int[nums.length];
        for (int i = nums.length - 1; i >= 0; i--) {
            if (stack.isEmpty()) {
                stack.push(i);
                ans[i] = -1;
            } else {
                while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
                    int x = stack.pop();
                    ans[x] = i;
                }
                stack.push(i);
                ans[i] = -1;
            }
        }
        return ans;
    }

    public int[] getLeftBiggerIndexByMethodLoop(int[] nums) {
        int[] ans = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            boolean flag = false;
            for (int j = i - 1; j >= 0; j--) {
                if (nums[j] > nums[i]) {
                    ans[i] = j;
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                ans[i] = -1;
            }
        }
        return ans;
    }

    public int[] getLeftSmallerIndexByMethodStack(int[] nums) {
        Stack<Integer> stack = new Stack<>();
        int[] ans = new int[nums.length];
        for (int i = nums.length - 1; i >= 0; i--) {
            if (stack.isEmpty()) {
                stack.push(i);
                ans[i] = -1;
            } else {
                while (!stack.isEmpty() && nums[stack.peek()] > nums[i]) {
                    int x = stack.pop();
                    ans[x] = i;
                }
                stack.push(i);
                ans[i] = -1;
            }
        }
        return ans;
    }

    public int[] getLeftSmallerIndexByMethodLoop(int[] nums) {
        int[] ans = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            boolean flag = false;
            for (int j = i - 1; j >= 0; j--) {
                if (nums[j] < nums[i]) {
                    ans[i] = j;
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                ans[i] = -1;
            }
        }
        return ans;
    }

    public int[] getRightBiggerIndexByMethodStack(int[] nums) {
        Stack<Integer> stack = new Stack<>();
        int[] ans = new int[nums.length];
        for (int i =  0; i < nums.length; i++) {
            if (stack.isEmpty()) {
                stack.push(i);
                ans[i] = nums.length;
            } else {
                while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
                    int x = stack.pop();
                    ans[x] = i;
                }
                stack.push(i);
                ans[i] = nums.length;
            }
        }
        return ans;
    }

    public int[] getRightBiggerIndexByMethodLoop(int[] nums) {
        int[] ans = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            boolean flag = false;
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] > nums[i]) {
                    ans[i] = j;
                    flag = true;
                    break;

                }
            }
            if (!flag) {
                ans[i] = nums.length;
            }
        }
        return ans;
    }

    public int[] getRightSmallerIndexByMethodStack(int[] nums) {
        Stack<Integer> stack = new Stack<>();
        int[] ans = new int[nums.length];
        for (int i =  0; i < nums.length; i++) {
            if (stack.isEmpty()) {
                stack.push(i);
                ans[i] = nums.length;
            } else {
                while (!stack.isEmpty() && nums[stack.peek()] > nums[i]) {
                    int x = stack.pop();
                    ans[x] = i;
                }
                stack.push(i);
                ans[i] = nums.length;
            }
        }
        return ans;
    }

    public int[] getRightSmallerIndexByMethodLoop(int[] nums) {
        int[] ans = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            boolean flag = false;
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] < nums[i]) {
                    ans[i] = j;
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                ans[i] = nums.length;
            }
        }
        return ans;
    }

    public boolean check(int[] a, int[] b) {
        for (int i = 0; i < a.length; i++) {
            if (a[i] != b[i])
                return false;
        }
        return true;
    }
}

LeetCode题目

以下是LeetCode中遇到的单调栈的一些题目,持续更新中……

  1. 2289. Steps to Make Array Non-decreasing

你可能感兴趣的:(leetcode,算法题,算法,单调栈,monotonic,stack)