数据结构与算法(Java) -单调队列单调栈题单

该博客围绕单调队列和单调栈展开,记录了多道LeetCode题目,如单调队列涉及滑动窗口最大值、预算内最多机器人数目等题目;单调栈涵盖每日温度、接雨水等题目,还提及记忆化搜索、递推及单调队列优化等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单调队列(灵神笔记)

239 滑动窗口最大值

239. 滑动窗口最大值 - 力扣(LeetCode)

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

示例 2:

输入:nums = [1], k = 1
输出:[1]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        /*示例1 队列的变化
        * 1. 1  2. 3    3. 3 -1 -3  4. 5    5. 5 3  6. 6    7. 7
        * 注意如果是第3步 将2加入队列 滑动窗口长度(i-队首下标)大于3 删除队首元素
        * 保证队首元素为滑动窗口最大值 队列单调递减
        */ 
        int n = nums.length;
        int[] ans = new int[n - k + 1];
        Deque<Integer> d = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            //进
            while (!d.isEmpty() && nums[i] >= nums[d.peekLast()]) {
                d.pollLast();
            }
            d.addLast(i);
            //出
            if (i - d.peekFirst() >= k) {
                d.pollFirst();
            }
            //更新答案
            if (i >= k - 1) {
                ans[i - k + 1] = nums[d.peekFirst()];
            }
        }
        return ans;
    }
}

1438 绝对差不超过限制的最长连续子数组

1438. 绝对差不超过限制的最长连续子数组 - 力扣(LeetCode)

给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit

如果不存在满足条件的子数组,则返回 0

示例 1:

输入:nums = [8,2,4,7], limit = 4
输出:2 
解释:所有子数组如下:
[8] 最大绝对差 |8-8| = 0 <= 4.
[8,2] 最大绝对差 |8-2| = 6 > 4. 
[8,2,4] 最大绝对差 |8-2| = 6 > 4.
[8,2,4,7] 最大绝对差 |8-2| = 6 > 4.
[2] 最大绝对差 |2-2| = 0 <= 4.
[2,4] 最大绝对差 |2-4| = 2 <= 4.
[2,4,7] 最大绝对差 |2-7| = 5 > 4.
[4] 最大绝对差 |4-4| = 0 <= 4.
[4,7] 最大绝对差 |4-7| = 3 <= 4.
[7] 最大绝对差 |7-7| = 0 <= 4. 
因此,满足题意的最长子数组的长度为 2 。

示例 2:

输入:nums = [10,1,2,4,7,2], limit = 5
输出:4 
解释:满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5 。

示例 3:

输入:nums = [4,2,2,2,4,4,2,2], limit = 0
输出:3

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • 0 <= limit <= 10^9
class Solution {
    public int longestSubarray(int[] nums, int limit) {
        //利用两个双端队列,max从左到右单调递增,队首为最大值,min相反
        Deque<Integer> maxdeque = new ArrayDeque<>();
        Deque<Integer> mindeque = new ArrayDeque<>();
        int n = nums.length;
        int right = 0, left = 0, ans = 0;
        //枚举right
        while (right < n) {
            while (!maxdeque.isEmpty() && nums[right] > maxdeque.peekLast()) {
                maxdeque.removeLast();
            }
            while (!mindeque.isEmpty() && nums[right] < mindeque.peekLast()) {
                mindeque.removeLast();
            }
            maxdeque.addLast(nums[right]);
            mindeque.addLast(nums[right]);
            //移动left
            while (maxdeque.peekFirst() - mindeque.peekFirst() > limit) {
                //更新队内元素
                if (maxdeque.peekFirst() == nums[left]) {
                    maxdeque.removeFirst();
                }
                if (mindeque.peekFirst() == nums[left]) {
                    mindeque.removeFirst();
                }
                left++;
            }
            ans = Math.max(ans, right - left + 1);
            right++;
        }
        return ans;
    }
}

2398 预算内的最多机器人数目

2398. 预算内的最多机器人数目 - 力扣(LeetCode)

你有 n 个机器人,给你两个下标从 0 开始的整数数组 chargeTimesrunningCosts ,两者长度都为 n 。第 i 个机器人充电时间为 chargeTimes[i] 单位时间,花费 runningCosts[i] 单位时间运行。再给你一个整数 budget

运行 k 个机器人 总开销max(chargeTimes) + k * sum(runningCosts) ,其中 max(chargeTimes) 是这 k 个机器人中最大充电时间,sum(runningCosts) 是这 k 个机器人的运行时间之和。

请你返回在 不超过 budget 的前提下,你 最多 可以 连续 运行的机器人数目为多少。

示例 1:

输入:chargeTimes = [3,6,1,3,4], runningCosts = [2,1,3,4,5], budget = 25
输出:3
解释:
可以在 budget 以内运行所有单个机器人或者连续运行 2 个机器人。
选择前 3 个机器人,可以得到答案最大值 3 。总开销是 max(3,6,1) + 3 * sum(2,1,3) = 6 + 3 * 6 = 24 ,小于 25 。
可以看出无法在 budget 以内连续运行超过 3 个机器人,所以我们返回 3 。

示例 2:

输入:chargeTimes = [11,12,19], runningCosts = [10,8,7], budget = 19
输出:0
解释:即使运行任何一个单个机器人,还是会超出 budget,所以我们返回 0 。

提示:

  • chargeTimes.length == runningCosts.length == n
  • 1 <= n <= 5 * 104
  • 1 <= chargeTimes[i], runningCosts[i] <= 105
  • 1 <= budget <= 1015
class Solution {
    /**
        chargeTimes  3 6 1 3 4
        runningCosts 2 1 3 4 5
        与239滑动窗口最大值一样 队首依然是维护chargeTimes的最大元素
        仅仅将固定大小的滑动窗口改为了不固定大小的双指针
     */

    public int maximumRobots(int[] chargeTimes, int[] runningCosts, long budget) {
        int ans = 0;
        Deque<Integer> q = new ArrayDeque<Integer>();
        long sum = 0L;
        for (int left = 0, right = 0; right < chargeTimes.length; right++) {
            //进
            while (!q.isEmpty() && chargeTimes[right] >= chargeTimes[q.peekLast()]) {
                q.pollLast();
            }
            q.addLast(right);
            sum += runningCosts[right];
            //出 
            while (!q.isEmpty() && chargeTimes[q.peekFirst()] + (right - left + 1) * sum > budget) {
                //及时清除无用的数据
                if (q.peekFirst() == left) {
                    q.pollFirst();
                }
                sum -= runningCosts[left++];
            }
            ans = Math.max(ans, right - left + 1);
        }
        return ans;
    }
}

将[子数组]改为[子序列]便是接下来的题目

1383 最大的团队表现值(附加)(堆)

1383. 最大的团队表现值 - 力扣(LeetCode)

给定两个整数 nk,以及两个长度为 n 的整数数组 speed efficiency。现有 n 名工程师,编号从 1n。其中 speed[i]efficiency[i] 分别代表第 i 位工程师的速度和效率。

从这 n 名工程师中最多选择 k 名不同的工程师,使其组成的团队具有最大的团队表现值。

团队表现值 的定义为:一个团队中「所有工程师速度的和」乘以他们「效率值中的最小值」。

请你返回该团队的最大团队表现值,由于答案可能很大,请你返回结果对 10^9 + 7 取余后的结果。

示例 1:

输入:n = 6, speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 2
输出:60
解释:
我们选择工程师 2(speed=10 且 efficiency=4)和工程师 5(speed=5 且 efficiency=7)。他们的团队表现值为 performance = (10 + 5) * min(4, 7) = 60 。

示例 2:

输入:n = 6, speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 3
输出:68
解释:
此示例与第一个示例相同,除了 k = 3 。我们可以选择工程师 1 ,工程师 2 和工程师 5 得到最大的团队表现值。表现值为 performance = (2 + 10 + 5) * min(5, 4, 7) = 68 。

示例 3:

输入:n = 6, speed = [2,10,3,1,5,8], efficiency = [5,4,3,9,7,2], k = 4
输出:72

提示:

  • 1 <= k <= n <= 10^5
  • speed.length == n
  • efficiency.length == n
  • 1 <= speed[i] <= 10^5
  • 1 <= efficiency[i] <= 10^8
class Solution {
    public int maxPerformance(int n, int[] speed, int[] efficiency, int k) {
       long maxP = 0;
       int[][] engineers = new int[n][2];
       for (int i = 0; i < n; i++) {
           engineers[i][0] = speed[i];
           engineers[i][1] = efficiency[i];
       } 
       //排序
       Arrays.sort(engineers, (a, b) -> b[1] - a[1]);
       PriorityQueue<Integer> pq = new PriorityQueue<>();
       long sumSpeed = 0;
       int minEfficiency = Integer.MAX_VALUE;
       for (int i = 0; i < n; i++) {
           //取出队首元素(即原有工程师中的最低速度)
           if (i >= k) {
               int preS = pq.poll();
               sumSpeed -= preS;
           }
           int[] engineer = engineers[i];
           pq.add(engineer[0]);
           sumSpeed += engineer[0];
           minEfficiency = engineer[1];
           long curP = sumSpeed * minEfficiency;
           maxP = Math.max(maxP, curP);
       }
       return (int) (maxP % (1000000007));
    }
}

862 和至少为K的最短子数组

862. 和至少为 K 的最短子数组 - 力扣(LeetCode)

给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1

子数组 是数组中 连续 的一部分。

示例 1:

输入:nums = [1], k = 1
输出:1

示例 2:

输入:nums = [1,2], k = 4
输出:-1

示例 3:

输入:nums = [2,-1,2], k = 3
输出:3

提示:

  • 1 <= nums.length <= 105
  • -105 <= nums[i] <= 105
  • 1 <= k <= 109
class Solution {
    public int shortestSubarray(int[] nums, int k) {
        int n = nums.length;
        long[] pre = new long[n + 1];
        pre[0] = 0;
        for (int i = 0; i < n; i++) {
            pre[i + 1] = pre[i] + nums[i];
        }
        Deque<Integer> q = new ArrayDeque<>();
        int ans = n + 1;
        for (int i = 0; i <= n; i++) {
            long cur = pre[i];
            while (!q.isEmpty() && cur - pre[q.peekFirst()] >= k) {
                ans = Math.min(ans, i - q.pollFirst());
            }
            while (!q.isEmpty() && cur <= pre[q.peekLast()]) {
                q.pollLast();
            }
            q.addLast(i);
        }
        return ans > n ? -1 : ans;
    }
}

1499 满足不等式的最大值

参考题解:1499. 满足不等式的最大值 - 力扣(LeetCode)

给你一个数组 points 和一个整数 k 。数组中每个元素都表示二维平面上的点的坐标,并按照横坐标 x 的值从小到大排序。也就是说 points[i] = [xi, yi] ,并且在 1 <= i < j <= points.length 的前提下, xi < xj 总成立。

请你找出 yi + yj + |xi - xj|最大值,其中 |xi - xj| <= k1 <= i < j <= points.length

题目测试数据保证至少存在一对能够满足 |xi - xj| <= k 的点。

示例 1:

输入:points = [[1,3],[2,0],[5,10],[6,-10]], k = 1
输出:4
解释:前两个点满足 |xi - xj| <= 1 ,代入方程计算,则得到值 3 + 0 + |1 - 2| = 4 。第三个和第四个点也满足条件,得到值 10 + -10 + |5 - 6| = 1 。
没有其他满足条件的点,所以返回 4 和 1 中最大的那个。

示例 2:

输入:points = [[0,0],[3,0],[9,2]], k = 3
输出:3
解释:只有前两个点满足 |xi - xj| <= 3 ,代入方程后得到值 0 + 0 + |0 - 3| = 3 。

提示:

  • 2 <= points.length <= 10^5
  • points[i].length == 2
  • -10^8 <= points[i][0], points[i][1] <= 10^8
  • 0 <= k <= 2 * 10^8
  • 对于所有的1 <= i < j <= points.lengthpoints[i][0] < points[j][0] 都成立。也就是说,xi 是严格递增的。
class Solution {
    /** yi+yj+abs(xi-xj)=yi+yj+xj-xi=(yi-xi)+(xj+yj)
        不满足abs(xi-xj)<=k 即xj-xi>k 弹他 
        想要得到xi我们需要用队列来保存
        将(xj,yj-xj)入队 保证队列始终是单调递减
        yj-xj不低于队尾的数据 弹他
     */
    public int findMaxValueOfEquation(int[][] points, int k) {
        int ans = Integer.MIN_VALUE;
        Deque<int[]> q = new ArrayDeque<>();
        for (var p : points) {
            int x = p[0], y = p[1];
            while (!q.isEmpty() && x - q.peekFirst()[0] > k) {
                q.pollFirst();
            }
            if (!q.isEmpty()) {
                ans = Math.max(ans, x + y + q.peekFirst()[1]);
            }
            while (!q.isEmpty() && q.peekLast()[1] <= y - x) {
                q.pollLast();
            }
            q.addLast(new int[]{x, y - x});
        }
        return ans;
    }
}

2944 购买水果需要的最少金币数

参考视频:单调队列优化 DP【力扣双周赛 118】_哔哩哔哩_bilibili

你在一个水果超市里,货架上摆满了玲琅满目的奇珍异果。

给你一个下标从 1 开始的数组 prices ,其中 prices[i] 表示你购买第 i 个水果需要花费的金币数目。

水果超市有如下促销活动:

  • 如果你花费 price[i] 购买了水果 i ,那么接下来的 i 个水果你都可以免费获得。

注意 ,即使你 可以 免费获得水果 j ,你仍然可以花费 prices[j] 个金币去购买它以便能免费获得接下来的 j 个水果。

请你返回获得所有水果所需要的 最少 金币数。

示例 1:

输入:prices = [3,1,2]
输出:4
解释:你可以按如下方法获得所有水果:
- 花 3 个金币购买水果 1 ,然后免费获得水果 2 。
- 花 1 个金币购买水果 2 ,然后免费获得水果 3 。
- 免费获得水果 3 。
注意,虽然你可以免费获得水果 2 ,但你还是花 1 个金币去购买它,因为这样的总花费最少。
购买所有水果需要最少花费 4 个金币。

示例 2:

输入:prices = [1,10,1,1]
输出:2
解释:你可以按如下方法获得所有水果:
- 花 1 个金币购买水果 1 ,然后免费获得水果 2 。
- 免费获得水果 2 。
- 花 1 个金币购买水果 3 ,然后免费获得水果 4 。
- 免费获得水果 4 。
购买所有水果需要最少花费 2 个金币。

提示:

  • 1 <= prices.length <= 1000
  • 1 <= prices[i] <= 105

记忆化搜索

class Solution {
    /** 哪些水果是免费的
        i+1,i+2...2i+1
        下一个购买的水果 j [i+1,2i+1]
        i+1到j-1都是免费获得的
     */

    public int minimumCoins(int[] prices) {
        int n = prices.length;
        int[] cache = new int[(n + 1) / 2];
        return dfs(prices, cache, 1);
    }

    private int dfs(int[] prices, int[] cache, int i) {
        int n = prices.length;
        // if (i > n) {
        //     return 0; // 递归边界
        // }
        if (i * 2 >= n) {
            return prices[i - 1]; // i从1开始 优化递归边界
        }
        if (cache[i] != 0) {
            return cache[i];
        }
        int ans = Integer.MAX_VALUE;
        for (int j = i + 1; j <= 2 * i + 1; j++) {
            ans = Math.min(ans, dfs(prices, cache, j));
        }
        return cache[i] = prices[i - 1] + ans;
    }
}

1:1翻译成递推

class Solution {
    /** 哪些水果是免费的
        i+1,i+2...2i+1
        下一个购买的水果 j [i+1,2i+1]
        i+1到j-1都是免费获得的
     */

    public int minimumCoins(int[] prices) {
        int n = prices.length;
        for (int i = (n + 1) / 2 - 1; i > 0; i--) {
            int mn = Integer.MAX_VALUE;
            for (int j = i; j <= i * 2; j++) {
                mn = Math.min(mn, prices[j]);
            }
            prices[i - 1] += mn;
        }
        return prices[0];
        //int[] f = new int[n + 1];
        // for (int i = n; i > 0; i--) {
        //     if (i * 2 >= n) {
        //         f[i] = prices[i - 1];
        //     } else {
        //         int mn = Integer.MAX_VALUE;
        //         for (int j = i + 1; j <= 2 * i + 1; j++) {
        //             mn = Math.min(mn, f[j]);
        //         }
        //         f[i] = mn + prices[i - 1];
        //     }
        // }
        // return f[1];
    }
}

单调队列优化

class Solution {
    /** 求滑动窗口最小值min(f[i+1:i*2+2])
        左边的小 淘汰掉 右边的大
        保证队尾元素始终最小 即队列单调递减
     */
    public int minimumCoins(int[] prices) {
        int n = prices.length;
        Deque<int[]> q = new ArrayDeque<>();
        q.addLast(new int[]{n + 1, 0});// 哨兵
        for (int i = n; i > 0; i--) {
            while (q.peekLast()[0] > 2 * i + 1) {
                q.pollLast();// 右边离开窗口
            }
            int f = prices[i - 1] + q.peekLast()[1];
            while (f <= q.peekFirst()[1]) {
                q.pollFirst();
            }
            q.addFirst(new int[]{i, f});// 左边进入窗口
        }
        return q.peekFirst()[1];
    }
}

单调栈(灵神笔记)

739 每日温度

739. 每日温度 - 力扣(LeetCode)

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

示例 2:

输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]

示例 3:

输入: temperatures = [30,60,90]
输出: [1,1,0]

提示:

  • 1 <= temperatures.length <= 105
  • 30 <= temperatures[i] <= 100
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[] ans = new int[n];
        int[] st = new int[n];
        int top = -1;
        for (int i = 0; i < n; i++) {
            int t = temperatures[i];
            //遍历到更大的元素就弹出栈顶元素 
            while (top >= 0 && t > temperatures[st[top]]) {
                int j = st[top--];
                ans[j] = i - j;
            }
            st[++top] = i;
        }
        return ans;
    }
}

42 接雨水

42. 接雨水 - 力扣(LeetCode)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

img

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

提示:

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105
class Solution {
    public int trap(int[] height) {
        int ans = 0;
        Deque<Integer> st = new ArrayDeque<>();
        for (int i = 0; i < height.length; i++) {
            //遍历到右边的板子 可以接水
            while (!st.isEmpty() && height[i] >= height[st.peek()]) {
                //需要三个下标 栈顶元素(右边板子) 栈顶下面的元素(中间板子) 左边的板子
                //例如 5 2 1 0 4 1 1 6
                //遍历到4 栈从下到上为 5 4 bottomHeight=2
                int bottomHeight = height[st.pop()];
                if (st.isEmpty()) {
                    break;
                }
                //返回队首元素(左板子)
                int left = st.peek();
                //雨水面积的高
                int dh = Math.min(height[left], height[i]) - bottomHeight;
                ans += dh * (i - left - 1);
            }
            st.push(i);
        }
        return ans;
    }
}

1475 商品折扣后的最终价格

1475. 商品折扣后的最终价格 - 力扣(LeetCode)

给你一个数组 prices ,其中 prices[i] 是商店里第 i 件商品的价格。

商店里正在进行促销活动,如果你要买第 i 件商品,那么你可以得到与 prices[j] 相等的折扣,其中 j 是满足 j > iprices[j] <= prices[i]最小下标 ,如果没有满足条件的 j ,你将没有任何折扣。

请你返回一个数组,数组中第 i 个元素是折扣后你购买商品 i 最终需要支付的价格。

示例 1:

输入:prices = [8,4,6,2,3]
输出:[4,2,4,2,3]
解释:
商品 0 的价格为 price[0]=8 ,你将得到 prices[1]=4 的折扣,所以最终价格为 8 - 4 = 4 。
商品 1 的价格为 price[1]=4 ,你将得到 prices[3]=2 的折扣,所以最终价格为 4 - 2 = 2 。
商品 2 的价格为 price[2]=6 ,你将得到 prices[3]=2 的折扣,所以最终价格为 6 - 2 = 4 。
商品 3 和 4 都没有折扣。

示例 2:

输入:prices = [1,2,3,4,5]
输出:[1,2,3,4,5]
解释:在这个例子中,所有商品都没有折扣。

示例 3:

输入:prices = [10,1,1,6]
输出:[9,0,1,6]

提示:

  • 1 <= prices.length <= 500
  • 1 <= prices[i] <= 10^3
class Solution {
    public int[] finalPrices(int[] prices) {
        /** 题目让我们找到i右边最近的满足prices[j]<=prices[i]的元素
            相当于 找到j左边最近的比起本身更大的元素
         */
        Deque<Integer> st = new ArrayDeque<>();
        int n = prices.length;
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            //遍历到的prices[i]<=栈顶元素 弹他
            while (!st.isEmpty() && prices[i] <= prices[st.peekLast()]) {
                int temp = st.peekLast();
                st.removeLast();
                ans[temp] = prices[temp] - prices[i];
            }
            st.add(i);
            ans[i] = prices[i];
        }
        return ans;
    }
}

901 股票价格跨度

901. 股票价格跨度 - 力扣(LeetCode)

设计一个算法收集某些股票的每日报价,并返回该股票当日价格的 跨度

当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。

  • 例如,如果未来 7 天股票的价格是 [100,80,60,70,60,75,85],那么股票跨度将是 [1,1,1,2,1,4,6]

实现 StockSpanner 类:

  • StockSpanner() 初始化类对象。
  • int next(int price) 给出今天的股价 price ,返回该股票当日价格的 跨度

示例:

输入:
["StockSpanner", "next", "next", "next", "next", "next", "next", "next"]
[[], [100], [80], [60], [70], [60], [75], [85]]
输出:
[null, 1, 1, 1, 2, 1, 4, 6]

解释:
StockSpanner stockSpanner = new StockSpanner();
stockSpanner.next(100); // 返回 1
stockSpanner.next(80);  // 返回 1
stockSpanner.next(60);  // 返回 1
stockSpanner.next(70);  // 返回 2
stockSpanner.next(60);  // 返回 1
stockSpanner.next(75);  // 返回 4 ,因为截至今天的最后 4 个股价 (包括今天的股价 75) 都小于或等于今天的股价。
stockSpanner.next(85);  // 返回 6

提示:

  • 1 <= price <= 105
  • 最多调用 next 方法 104
class StockSpanner {
    private final Deque<int[]> stack = new ArrayDeque<>();
    private int curDay = -1;

    public StockSpanner() {
        //无需判断栈为空
        stack.push(new int[]{-1, Integer.MAX_VALUE});
    }
    
    public int next(int price) {
        while (price >= stack.peek()[1]) {
            stack.pop();
        }
        int ans = ++curDay - stack.peek()[0];
        stack.push(new int[]{curDay, price});
        return ans;
    }
}

/**
 * Your StockSpanner object will be instantiated and called as such:
 * StockSpanner obj = new StockSpanner();
 * int param_1 = obj.next(price);
 */

1019 链表中的下一个更大节点

1019. 链表中的下一个更大节点 - 力扣(LeetCode)

给定一个长度为 n 的链表 head

对于列表中的每个节点,查找下一个 更大节点 的值。也就是说,对于每个节点,找到它旁边的第一个节点的值,这个节点的值 严格大于 它的值。

返回一个整数数组 answer ,其中 answer[i] 是第 i 个节点( 从1开始 )的下一个更大的节点的值。如果第 i 个节点没有下一个更大的节点,设置 answer[i] = 0

示例 1:

img

输入:head = [2,1,5]
输出:[5,5,0]

示例 2:

img

输入:head = [2,7,4,3,5]
输出:[7,0,5,5,0]

提示:

  • 链表中节点数为 n
  • 1 <= n <= 104
  • 1 <= Node.val <= 109
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public int[] nextLargerNodes(ListNode head) {
        int n = 0;
        for (ListNode cur = head; cur != null; cur = cur.next) {
            n++;
        }
        int[] ans = new int[n];
        Deque<Integer> st = new ArrayDeque<>();// 保存节点下标
        int i = 0;
        for (ListNode cur = head; cur != null; cur = cur.next) {
            while (!st.isEmpty() && ans[st.peek()] < cur.val) {
                ans[st.pop()] = cur.val;// 更新答案
            }
            st.push(i);
            ans[i++] = cur.val;
        }
        for (var index : st) {
            ans[index] = 0;
        }
        return ans;
    }
}

84 柱状图中最大的矩形

84. 柱状图中最大的矩形 - 力扣(LeetCode)

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

示例 1:

img

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

示例 2:

img

输入: heights = [2,4]
输出: 4

提示:

  • 1 <= heights.length <=105
  • 0 <= heights[i] <= 104
class Solution {
    public int largestRectangleArea(int[] heights) {
        int ans = 0;
        Deque<Integer> st = new ArrayDeque<>();
        int n = heights.length;
        //l[i]为左边最近的比其小的下标 r[i]为右边最近的比其小的下标
        int[] l = new int[n], r = new int[n];
        Arrays.fill(l, -1);// 初始化-1
        Arrays.fill(r, n);// 初始化n
        for (int i = 0; i < n; i++) {
            while (!st.isEmpty() && heights[i] < heights[st.peekLast()]) {
                r[st.pollLast()] = i;
            }
            st.addLast(i);
        }
        st.clear();
        for (int i = n - 1; i >= 0; i--) {
            while (!st.isEmpty() && heights[i] < heights[st.peekLast()]) {
                l[st.pollLast()] = i;
            }
            st.addLast(i);
        }
        for (int i = 0; i < n; i++) {
            int h = heights[i];
            ans = Math.max(ans, (r[i] - l[i] - 1) * h);
        }
        return ans;
    }
}
<think>好的,我需要帮助用户了解如何在Java中实现单调栈单调队列。首先,我得回顾一下单调栈单调队列的基本概念,确保自己理解正确。根据提供的引用资料,单调栈和队列都是用来处理元素间调关系的结构,通常用于优化算法,比如寻找下一个更大元素或滑动窗口最大值。 接下来,我需要考虑用户的具体需求:用Java实现这两种结构。用户可能希望看到清晰的代码示例和解释。我需要确保代码符合Java的语法规范,并且结构清晰,容易理解。同时,根据系统指令,数学公式需要用LaTeX格式,行内公式用$...$,独立公式用$$...$$。不过这个问主要涉及代码实现,可能数学公式较少,但如果有涉及算法复杂度的部分,可能需要使用数学表达式。 然后,分析用户可能的背景。用户可能有一定的编程基础,但对单调栈和队列的实现细节不太熟悉。因此,在解释时应该循序渐进,先介绍概念,再给出实现步骤,最后提供代码示例。同时,需要指出实现中的关键点,比如如何维护调性,如何处理元素的入队和出队操作等。 现在,根据引用资料中的信息,引用[1]提到了用数组实现单调队列,引用[3]和[4]解释了单调栈的基本概念和应用。引用[5]提到了单调队列的应用场景,比如优化DP或获取最大值/最小值。这些信息可以用来支持代码实现的正确性,并在解释时引用。 接下来,我需要组织回答的结构。首先介绍单调栈的实现,包括示例代码和步骤解释,然后介绍单调队列,同样提供代码和步骤。每个部分都需要说明如何维护调性,例如在单调栈中,当新元素小于栈顶时弹出栈顶元素,直到满足调递减的条件。对于单调队列,则需要处理队首和队尾的操作,确保队列中的元素调递减,并且在滑动窗口移动时移除超出范围的元素。 在代码示例部分,需要注意Java的语法,比如使用Deque接口实现栈和队列,或者使用数组手动维护。引用[1]提到用数组实现更高效,所以可能需要两种实现方式:一种使用集合类,另一种使用数组。不过用户可能更倾向于使用标准的集合类,如Deque,因为更直观和易于理解。不过根据引用[1],数组实现效率更高,所以可以同时提供两种方式,但可能优先展示数组实现,特别是对于队列来说,因为滑动窗口问通常涉及固定大小的数组操作。 另外,需要确保代码注释充分,解释每一步的作用。例如,在单调栈的实现中,遍历数组时,比较当前元素栈顶元素,决定是否弹出栈顶元素以保持调性。对于单调队列,在滑动窗口问中,每次移动窗口时,需要检查队首元素是否还在窗口范围内,否则移除。 然后,生成相关问。根据系统指令,需要生成三个相关问。可能的问包括性能分析、应用场景、其他数据结构的比较等。例如,如何分析单调栈的时间复杂度?单调队列在哪些算法中常用?或者如何用单调栈解决下一个更大元素的问? 最后,检查是否符合所有系统指令:行内数学公式是否正确使用,独立公式是否独成段,LaTeX语法正确,使用中文回答,生成相关问,并在引用处正确添加标识。需要确认所有引用都来自提供的资料,并且正确标注。 现在,开始组织内容。首先,用简洁的中文介绍单调栈和队列的概念,然后分步骤讲解实现方法,提供代码示例,并在适当的地方引用提供的资料。例如,在解释单调栈时,引用[4]提到单调栈是普通栈的使用方式,元素调递增或递减。在代码中,可以使用Deque来模拟栈结构,并在处理元素时维护调性。对于单调队列,引用[1]中的数组实现方法可以作为示例,或者使用Deque来实现。 可能遇到的疑问:是否需要处理元素相等的情况?例如,在调递减栈中,如果当前元素等于栈顶元素,是否需要弹出?这取决于具体问需求,但通常可以保留,因为某些问允许相等的情况。在代码中需要明确这一点,或者根据问调整条件。 总结步骤: 1. 定义单调栈的实现步骤,用Java代码示例展示。 2. 定义单调队列的实现步骤,同样提供代码示例。 3. 解释关键操作,如维护调性、处理出栈/出队的条件。 4. 引用相关文献支持说明。 5. 生成相关问,如应用场景、复杂度分析、其他结构对比等。 在编写代码时,例如,单调栈的示例可以针对“下一个更大元素”问,而队列的示例可以是滑动窗口的最大值。这些是常见的应用场景,能帮助用户理解实现的实际用途。 最后检查所有引用是否恰当,例如在提到维护调性时引用[3]或[4],在数组实现队列时引用[1]。确保回答结构清晰,分点明确,代码正确无误,并且相关问相关。</think>### 如何用 Java 实现单调栈单调队列? #### 一、单调栈的实现 **单调栈**是一种保持栈内元素调递增或递减的数据结构,常用于解决“下一个更大元素”或“前一个更小元素”等问。以下是基于数组的单调栈实现步骤: 1. **初始化栈结构**:使用 `Deque` 或数组模拟栈。 2. **遍历元素**:对数组中的每个元素进行比较。 3. **维护调性**:若新元素破坏调性,则弹出栈顶元素,直到满足条件。 4. **记录结果**:在弹出过程中记录相关信息(如索引或值)。 **示例代码(调递减栈,寻找右侧第一个更大元素)**: ```java int[] findNextGreaterElement(int[] nums) { int[] res = new int[nums.length]; Arrays.fill(res, -1); Deque<Integer> stack = new ArrayDeque<>(); // 存储元素索引 for (int i = 0; i < nums.length; i++) { // 维护调性:当前元素 > 栈顶元素时,更新结果并弹出栈顶 while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) { res[stack.pop()] = nums[i]; } stack.push(i); } return res; } ``` **关键点**:栈中存储元素索引,通过比较当前元素栈顶元素的值,决定是否弹出栈顶以保持调递减[^4]。 --- #### 二、单调队列的实现 **单调队列**保持队列内元素的调性,常用于滑动窗口最大值等场景。以下是基于数组的实现步骤: 1. **初始化队列**:使用双端队列 `Deque` 存储元素索引。 2. **维护窗口范围**:窗口移动时,移除超出范围的队首元素。 3. **维护调性**:新元素入队前,从队尾移除比它小的元素。 **示例代码(滑动窗口最大值)**: ```java int[] maxSlidingWindow(int[] nums, int k) { int[] res = new int[nums.length - k + 1]; Deque<Integer> deque = new ArrayDeque<>(); for (int i = 0; i < nums.length; i++) { // 移除超出窗口范围的队首元素 while (!deque.isEmpty() && deque.peek() < i - k + 1) { deque.pollFirst(); } // 维护调递减:移除队尾小于当前元素的元素 while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) { deque.pollLast(); } deque.offerLast(i); // 记录当前窗口最大值(队首元素) if (i >= k - 1) { res[i - k + 1] = nums[deque.peekFirst()]; } } return res; } ``` **关键点**:队列中存储索引,既保证窗口范围的有效性,又通过调性直接获取最大值[^1][^5]。 --- #### 三、性能应用场景 1. **时间复杂度**:单调栈和队列的每个元素最多入栈/队和出栈/队一次,因此时间复杂度为 $O(n)$。 2. **应用场景**: - **单调栈**:柱状图中最大矩形、每日温度[^2]。 - **单调队列**:滑动窗口最大值、最短子数组和问---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值