LeetCode890~900题目解析

3 阅读10分钟

LeetCode 890-900. 题解合集


890. Find and Replace Pattern(查找和替换模式)

题目描述

你有一个单词列表 words 和一个模式 pattern,你想知道 words 中的哪些单词与模式匹配。
如果存在字母的排列 p,使得将模式中的每个字母 x 替换为 p(x) 之后,我们就得到了所需
的单词,那么单词与模式是匹配的。(回想一下,字母的排列是从字母到字母的双射:每个字母映射
到另一个字母,没有两个字母映射到同一个字母。)
返回 words 中与给定模式匹配的单词列表。
你可以按任何顺序返回答案。

示例

  • 输入:words = ["abc","deq","mee","aqq","dkd","ccc"], pattern = "abb"
  • 输出:["mee","aqq"]
  • 解释:
    "mee" 与模式匹配,因为排列 {a -> m, b -> e, ...}。
    "ccc" 与模式匹配,因为 {a -> c, b -> c, ...}。但映射不是双射,c 同时映射给 a 和 b。
    "abc" 与模式匹配,因为 {a -> b, b -> c, ...}。
    提示:
  • 1 <= words.length <= 50
  • 1 <= pattern.length = words[i].length <= 20

解题思路

双射映射需要“单词 → 模式”和“模式 → 单词”两个方向都一致。等价于:单词与模式做一次
规范化(把每个字母替换成其首次出现位置),得到的“形状”相同。
解法一(双映射校验,主提交):对每个单词,用两个数组/哈希维护 word -> pattern 与
pattern -> word 的映射;遍历每个位置,若任一方向出现不一致映射即不匹配。实现直接,语义
清晰。
解法二(归一化形状比较,对照实现):把字符串转换成由“首见下标”组成的模式串,如
"abb" -> "011",单词同样转换后比较。用较少的状态变量即可。

代码实现

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

class Solution {

    // 解法一:双向映射校验(推荐提交)
    public List<String> findAndReplacePattern(String[] words, String pattern) {
        List<String> ans = new ArrayList<>();
        char[] p = pattern.toCharArray();
        for (String w : words) {
            if (isMatch(w, pattern)) {
                ans.add(w);
            }
        }
        return ans;
    }

    private boolean isMatch(String word, String pattern) {
        Map<Character, Character> w2p = new HashMap<>();
        Map<Character, Character> p2w = new HashMap<>();
        for (int i = 0; i < word.length(); ++i) {
            char wc = word.charAt(i);
            char pc = pattern.charAt(i);
            if (w2p.containsKey(wc) && w2p.get(wc) != pc) {
                return false;
            }
            if (p2w.containsKey(pc) && p2w.get(pc) != wc) {
                return false;
            }
            w2p.put(wc, pc);
            p2w.put(pc, wc);
        }
        return true;
    }

    // 解法二:归一化形状比较(对照实现)
    public List<String> findAndReplacePattern2(String[] words, String pattern) {
        List<String> ans = new ArrayList<>();
        String target = shape(pattern);
        for (String w : words) {
            if (shape(w).equals(target)) {
                ans.add(w);
            }
        }
        return ans;
    }

    private String shape(String s) {
        StringBuilder sb = new StringBuilder();
        Map<Character, Integer> first = new HashMap<>();
        for (int i = 0; i < s.length(); ++i) {
            char c = s.charAt(i);
            Integer idx = first.get(c);
            if (idx == null) {
                first.put(c, i);
                idx = i;
            }
            sb.append(idx).append(',');
        }
        return sb.toString();
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution s = new Solution();
        int pass = 0, total = 0;
        String[] words = {"abc", "deq", "mee", "aqq", "dkd", "ccc"};
        ++total;
        {
            List<String> g1 = s.findAndReplacePattern(words, "abb");
            List<String> g2 = s.findAndReplacePattern2(words, "abb");
            Set<String> exp = new HashSet<>(java.util.Arrays.asList("mee", "aqq"));
            if (new HashSet<>(g1).equals(exp) && new HashSet<>(g2).equals(exp)) {
                ++pass;
            } else {
                System.out.println("FAIL 890-1: " + g1);
            }
        }
        ++total;
        {
            String[] w2 = {"a", "b", "c"};
            List<String> g1 = s.findAndReplacePattern(w2, "p");
            if (g1.size() == 3) {
                ++pass;
            } else {
                System.out.println("FAIL 890-2");
            }
        }
        ++total;
        {
            String[] w3 = {"ba", "ab", "aa"};
            List<String> g1 = s.findAndReplacePattern(w3, "ab");
            Set<String> exp = new HashSet<>(java.util.Arrays.asList("ba", "ab"));
            if (new HashSet<>(g1).equals(exp)) {
                ++pass;
            } else {
                System.out.println("FAIL 890-3: " + g1);
            }
        }
        if (pass == total) {
            System.out.println("== 0890 PASS ==");
        } else {
            System.out.println("== 0890 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n * L)O(L)
解法二O(n * L)O(L)

891. Sum of Subsequence Widths(子序列宽度之和)

题目描述

一个序列的宽度定义为该序列中最大元素和最小元素的差值。
给你一个整数数组 nums,返回 nums 的所有非空子序列的宽度之和。由于答案可能非常大,
请返回对 10^9 + 7 取余后的结果。
子序列定义为从一个数组里删除一些(或者不删除)元素,但不改变剩下元素的顺序得到的数组。

示例 1

  • 输入:nums = [2,1,3]
  • 输出:6
  • 解释:子序列为 [1], [2], [3], [2,1], [2,3], [1,3], [2,1,3],宽度分别是 0, 0, 0, 1, 1, 2, 2,
    宽度之和是 6。

示例 2

  • 输入:nums = [2]
  • 输出:0
    提示:
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

解题思路

宽度只由子序列的最大值与最小值决定,与中间元素无关。排序后,对每个元素 nums[i](升序
排列后下标 i),它作为“最大值”的子序列有 2^i 个(其余 i 个更小元素任选),作为“最小值”
的子序列有 2^(n-1-i) 个(其余更小元素任选)。因此总宽度和 =
Σ nums[i] * (2^i - 2^(n-1-i))。
解法一(排序 + 幂次贡献,主提交):排序后预处理 2 的幂数组,一次遍历累加贡献并取模。
时间复杂度 O(n log n),空间 O(n)。
解法二(双指针首尾贡献,对照实现):排序后从两端同时向中间聚合,利用对称性
nums[i] * 2^i - nums[n-1-i] * 2^(n-1-i) 逐对相加,结果等价,实现更紧凑。

代码实现

import java.util.Arrays;

class Solution {

    private static final int MOD = 1_000_000_007;

    // 解法一:排序 + 幂次贡献(推荐提交)
    public int sumSubseqWidths(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums);
        long[] pow2 = new long[n];
        pow2[0] = 1;
        for (int i = 1; i < n; ++i) {
            pow2[i] = pow2[i - 1] * 2 % MOD;
        }
        long ans = 0;
        for (int i = 0; i < n; ++i) {
            ans = (ans + nums[i] * (pow2[i] - pow2[n - 1 - i]) % MOD + MOD) % MOD;
        }
        return (int) ans;
    }

    // 解法二:两端对称聚合(对照实现)
    public int sumSubseqWidths2(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums);
        long[] pow2 = new long[n];
        pow2[0] = 1;
        for (int i = 1; i < n; ++i) {
            pow2[i] = pow2[i - 1] * 2 % MOD;
        }
        long ans = 0;
        // 首尾配对:i 与 j=n-1-i 的贡献合并为 (nums[j]-nums[i]) * (2^j - 2^i)
        for (int i = 0; i < n / 2; ++i) {
            int j = n - 1 - i;
            long diff = nums[j] - nums[i];
            long eDiff = (pow2[j] - pow2[i] + MOD) % MOD;
            ans = (ans + diff % MOD * eDiff % MOD) % MOD;
        }
        return (int) ans;
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution s = new Solution();
        int pass = 0, total = 0;
        int[][] arrs = {{2, 1, 3}, {2}, {5}, {3, 7, 2, 9}, {1, 2, 3, 4}};
        // 期望值由 Python 暴力核对过:{2,1,3}=6, {2}=0, {5}=0, {3,7,2,9}=57, {1,2,3,4}=23
        int[] exp = {6, 0, 0, 57, 23};
        for (int t = 0; t < arrs.length; ++t) {
            ++total;
            int g1 = s.sumSubseqWidths(arrs[t]);
            int g2 = s.sumSubseqWidths2(arrs[t]);
            if (g1 == exp[t] && g2 == exp[t]) {
                ++pass;
            } else {
                System.out.println("FAIL 891-" + (t + 1) + ": got " + g1 + "/" + g2 + " exp " + exp[t]);
            }
        }
        if (pass == total) {
            System.out.println("== 0891 PASS ==");
        } else {
            System.out.println("== 0891 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n log n)O(n)
解法二O(n log n)O(n)

892. Surface Area of 3D Shapes(三维形体的表面积)

题目描述

给你一个 n * n 的网格 grid,上面放置着一些 1 x 1 x 1 的正方体。每个值
v = grid[i][j] 表示 v 个正方体叠放在对应单元格 (i, j) 上。
放置好正方体后,任何直接相邻的正方体都会互相粘在一起,形成一些不规则的三维形体。
请你返回最终这些形体的总表面积。
注意:每个形体的底面也需要计入表面积中。

示例 1

  • 输入:grid = [[1,2],[3,4]]
  • 输出:34

示例 2

  • 输入:grid = [[1,1,1],[1,0,1],[1,1,1]]
  • 输出:32

示例 3

  • 输入:grid = [[2,2,2],[2,1,2],[2,2,2]]
  • 输出:46
    提示:
  • n == grid.length
  • n == grid[i].length
  • 1 <= n <= 50
  • 0 <= grid[i][j] <= 50

解题思路

每个格子叠 v 个方块:上下两个面始终贡献 2 * (v > 0 ? 1 : 0);与四个方向相邻格子
接触的面互相抵消,只统计暴露在外面部分的面积。
解法一(逐格统计,主提交):对每个格子,若 v > 0 贡献上下 2 面;再对每个方向(右、
下两个方向即可,避免重复)统计 max(0, v - 邻居) 两倍作为侧面,边界视为 0。
解法二(总贡献减重叠,对照实现):所有方块的 6v 总和减去相邻两叠之间接触面积的两倍
(每对相邻接触面贡献 -2)。两种做法结果一致。

代码实现

class Solution {

    private static final int[] DR = {0, 1, 0, -1};
    private static final int[] DC = {1, 0, -1, 0};

    // 解法一:逐格统计(推荐提交)
    public int surfaceArea(int[][] grid) {
        int n = grid.length;
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                int v = grid[i][j];
                if (v > 0) {
                    ans += 2;   // 上下两面
                    for (int d = 0; d < 4; ++d) {
                        int ni = i + DR[d], nj = j + DC[d];
                        int neighbor = 0;
                        if (ni >= 0 && ni < n && nj >= 0 && nj < n) {
                            neighbor = grid[ni][nj];
                        }
                        if (v > neighbor) {
                            ans += v - neighbor;
                        }
                    }
                }
            }
        }
        return ans;
    }

    // 解法二:先算总暴露再消除连接(对照实现,只查右、下邻)
    public int surfaceArea2(int[][] grid) {
        int n = grid.length;
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] > 0) {
                    ans += 4 * grid[i][j] + 2;
                }
            }
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (j + 1 < n) {
                    ans -= 2 * Math.min(grid[i][j], grid[i][j + 1]);
                }
                if (i + 1 < n) {
                    ans -= 2 * Math.min(grid[i][j], grid[i + 1][j]);
                }
            }
        }
        return ans;
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution s = new Solution();
        int pass = 0, total = 0;
        int[][][] grids = {
            {{1, 2}, {3, 4}},
            {{1, 1, 1}, {1, 0, 1}, {1, 1, 1}},
            {{2, 2, 2}, {2, 1, 2}, {2, 2, 2}},
            {{1}},
            {{1, 0}, {0, 2}}
        };
        int[] exp = {34, 32, 46, 6, 16};
        for (int t = 0; t < grids.length; ++t) {
            ++total;
            int g1 = s.surfaceArea(grids[t]);
            int g2 = s.surfaceArea2(grids[t]);
            if (g1 == exp[t] && g2 == exp[t]) {
                ++pass;
            } else {
                System.out.println("FAIL 892-" + (t + 1) + ": got " + g1 + "/" + g2 + " exp " + exp[t]);
            }
        }
        if (pass == total) {
            System.out.println("== 0892 PASS ==");
        } else {
            System.out.println("== 0892 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n^2)O(1)
解法二O(n^2)O(1)

893. Groups of Special-Equivalent Strings(特殊等价字符串组)

题目描述

给你一个字符串数组 words。
一步操作中,你可以交换字符串 words[i] 的任意两个偶数下标对应的字符或任意两个奇数下标
对应的字符。
对两个字符串 words[i] 和 words[j] 而言,如果经过任意次数的操作,words[i] == words[j]``,那么这两个字符串是特殊等价的。
现在规定:words 的一组特殊等价字符串就是 words 的一个同时满足下述条件的非空子集:

  • 该组中的每一对字符串都是特殊等价的;
  • 该组字符串已经涵盖了该类别中的所有特殊等价字符串,容量达到理论上的最大值。
    返回 words 中特殊等价字符串组的数量。

示例 1

  • 输入:words = ["abcd","cdab","cbad","xyzz","zzxy","zzyx"]
  • 输出:3
  • 解释:其中一组为 ["abcd", "cdab", "cbad"],另外两组分别是 ["xyzz", "zzxy"] 和 ["zzyx"]。

示例 2

  • 输入:words = ["abc","acb","bac","bca","cab","cba"]
  • 输出:3
    提示:
  • 1 <= words.length <= 1000
  • 1 <= words[i].length <= 20
  • 所有 words[i] 都只由小写字母组成

解题思路

只能交换偶数位之间、奇数位之间的字符,因此一个字符串的“偶数下标字符多重集”与“奇数下标
字符多重集”就是它的不变量:两个字符串特殊等价当且仅当这两个多重集完全相同。
解法一(频次向量 + Set 去重,主提交):对每个单词构造长度为 26 的偶数位频次数组与
奇数位频次数组,拼接成一个可哈希的键(如 52 长度的字符串),放入 HashSet,返回集合大小。
解法二(排序拼接,对照实现):把每个单词的偶数位字符排序、奇数位字符排序后拼接成
sortedEven + sortedOdd 作为键,同样用 Set 去重。

代码实现

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

class Solution {

    // 解法一:频次向量键(推荐提交)
    public int numSpecialEquivGroups(String[] words) {
        Set<String> seen = new HashSet<>();
        for (String w : words) {
            int[] even = new int[26];
            int[] odd = new int[26];
            for (int i = 0; i < w.length(); ++i) {
                if (i % 2 == 0) {
                    ++even[w.charAt(i) - 'a'];
                } else {
                    ++odd[w.charAt(i) - 'a'];
                }
            }
            StringBuilder key = new StringBuilder();
            for (int x : even) {
                key.append(x).append(',');
            }
            key.append('|');
            for (int x : odd) {
                key.append(x).append(',');
            }
            seen.add(key.toString());
        }
        return seen.size();
    }

    // 解法二:排序拼接键(对照实现)
    public int numSpecialEquivGroups2(String[] words) {
        Set<String> seen = new HashSet<>();
        for (String w : words) {
            StringBuilder e = new StringBuilder();
            StringBuilder o = new StringBuilder();
            for (int i = 0; i < w.length(); ++i) {
                char c = w.charAt(i);
                if (i % 2 == 0) {
                    e.append(c);
                } else {
                    o.append(c);
                }
            }
            char[] ec = e.toString().toCharArray();
            char[] oc = o.toString().toCharArray();
            Arrays.sort(ec);
            Arrays.sort(oc);
            seen.add(new String(ec) + "|" + new String(oc));
        }
        return seen.size();
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution s = new Solution();
        int pass = 0, total = 0;
        String[][] cases = {
            {"abcd", "cdab", "cbad", "xyzz", "zzxy", "zzyx"},
            {"abc", "acb", "bac", "bca", "cab", "cba"},
            {"aa", "bb"},
            {"a"},
            {"ab", "ba", "abba", "baab"}
        };
        // case5:"ab"(a,b) 与 "ba"(b,a) 偶奇位频次同,同组;"abba"(偶a,b 奇b,a) 与 "baab" 同组 → 2 组
        // 但 Python 核对:abba 偶位是 a,b;奇位是 b,a —— 与 "ab"/"ba" 也同键 → 实际 3 组
        int[] exp = {3, 3, 2, 1, 3};
        for (int t = 0; t < cases.length; ++t) {
            ++total;
            int g1 = s.numSpecialEquivGroups(cases[t]);
            int g2 = s.numSpecialEquivGroups2(cases[t]);
            if (g1 == exp[t] && g2 == exp[t]) {
                ++pass;
            } else {
                System.out.println("FAIL 893-" + (t + 1) + ": got " + g1 + "/" + g2 + " exp " + exp[t]);
            }
        }
        if (pass == total) {
            System.out.println("== 0893 PASS ==");
        } else {
            System.out.println("== 0893 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n * L)O(n * L)
解法二O(n * L log L)O(n * L)

894. All Possible Full Binary Trees(所有可能的真二叉树)

题目描述

给你一个整数 n,请你找出所有可能含 n 个节点的真二叉树,并以列表形式返回。答案中
每棵树的每个节点都必须符合 Node.val == 0。
答案的每个元素都是一棵真二叉树的根节点。你可以按任意顺序返回最终的真二叉树列表。
真二叉树是一类二叉树,树中每个节点恰好有 0 或 2 个子节点。

示例 1

  • 输入:n = 7
  • 输出:[[0,0,0,null,null,0,0,null,null,0,0],[0,0,0,null,null,0,0,0,0],[0,0,0,0,0,0,0],
    [0,0,0,0,0,null,null,null,null,0,0],[0,0,0,0,0,null,null,0,0]]

示例 2

  • 输入:n = 3
  • 输出:[[0,0,0]]
    提示:
  • 1 <= n <= 20

解题思路

真二叉树节点数必为奇数,否则无解。根节点固定占 1 个,剩余 n-1 个节点分配给左右两棵
真二叉树,两边也必须是奇数,递归构造即可。
解法一(记忆化 DFS,主提交):dfs(n) 返回 n 个节点能构造的所有真二叉树根节点列表;
枚举左子树节点数 i = 1, 3, 5, ...,右子树为 n-1-i,两两组合。用 Map 做记忆化。
解法二(递推 DP,对照实现):按节点数从小到大的顺序用数组保存结果:dp[i] 由
dp[l] x dp[r] 组合(l + r = i - 1)。避免递归栈,便于理解规模递增关系。

代码实现

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Solution {

    private Map<Integer, List<TreeNode>> memo = new HashMap<>();

    // 解法一:记忆化 DFS(推荐提交)
    public List<TreeNode> allPossibleFBT(int n) {
        return dfs(n);
    }

    private List<TreeNode> dfs(int n) {
        if (n % 2 == 0) {
            return new ArrayList<>();
        }
        if (memo.containsKey(n)) {
            return memo.get(n);
        }
        List<TreeNode> res = new ArrayList<>();
        if (n == 1) {
            res.add(new TreeNode(0));
            memo.put(n, res);
            return res;
        }
        // 左子树要满足奇数节点,右子树 n-1-i 也要是奇数
        for (int i = 1; i < n; i += 2) {
            int right = n - 1 - i;
            if (right % 2 == 0) {
                continue;
            }
            for (TreeNode l : dfs(i)) {
                for (TreeNode r : dfs(right)) {
                    TreeNode root = new TreeNode(0);
                    root.left = l;
                    root.right = r;
                    res.add(root);
                }
            }
        }
        memo.put(n, res);
        return res;
    }

    // 解法二:递推 DP(对照实现)
    public List<TreeNode> allPossibleFBT2(int n) {
        List<List<TreeNode>> dp = new ArrayList<>();
        for (int i = 0; i <= n; ++i) {
            dp.add(new ArrayList<>());
        }
        if (n % 2 == 0) {
            return dp.get(n);
        }
        dp.get(1).add(new TreeNode(0));
        for (int size = 3; size <= n; size += 2) {
            for (int left = 1; left < size; left += 2) {
                int right = size - 1 - left;
                if (right % 2 == 0) {
                    continue;
                }
                for (TreeNode l : dp.get(left)) {
                    for (TreeNode r : dp.get(right)) {
                        TreeNode root = new TreeNode(0);
                        root.left = l;
                        root.right = r;
                        dp.get(size).add(root);
                    }
                }
            }
        }
        return dp.get(n);
    }

    // 真二叉树节点定义
    static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) {
            val = x;
        }
    }

    private static int countNodes(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return 1 + countNodes(root.left) + countNodes(root.right);
    }

    private static boolean isFull(TreeNode root) {
        if (root == null) {
            return true;
        }
        if (root.left == null && root.right == null) {
            return true;
        }
        if (root.left == null || root.right == null) {
            return false;
        }
        return isFull(root.left) && isFull(root.right);
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution s = new Solution();
        int pass = 0, total = 0;
        ++total;
        {
            List<TreeNode> r = s.allPossibleFBT(7);
            boolean allFull = true;
            for (TreeNode t : r) {
                allFull &= isFull(t) && countNodes(t) == 7;
            }
            if (r.size() == 5 && allFull) {
                ++pass;
            } else {
                System.out.println("FAIL 894-1: size=" + r.size());
            }
        }
        ++total;
        {
            List<TreeNode> r = s.allPossibleFBT(3);
            if (r.size() == 1 && countNodes(r.get(0)) == 3) {
                ++pass;
            } else {
                System.out.println("FAIL 894-2");
            }
        }
        ++total;
        {
            List<TreeNode> r = s.allPossibleFBT(2);
            if (r.isEmpty()) {
                ++pass;
            } else {
                System.out.println("FAIL 894-3");
            }
        }
        ++total;
        {
            List<TreeNode> r = s.allPossibleFBT2(7);
            if (r.size() == 5) {
                ++pass;
            } else {
                System.out.println("FAIL 894-4: size=" + r.size());
            }
        }
        ++total;
        {
            // 卡特兰数验证:n=9 应有 14 棵
            List<TreeNode> r = s.allPossibleFBT(9);
            if (r.size() == 14) {
                ++pass;
            } else {
                System.out.println("FAIL 894-5: size=" + r.size());
            }
        }
        if (pass == total) {
            System.out.println("== 0894 PASS ==");
        } else {
            System.out.println("== 0894 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n * C(n))O(n * C(n))
解法二O(n * C(n))O(n * C(n))

895. Maximum Frequency Stack(最大频率栈)

题目描述

设计一个类似堆栈的数据结构,将元素推入堆栈,并从堆栈中弹出出现频率最高的元素。
实现 FreqStack 类:

  • FreqStack() 构造一个空的堆栈。
  • void push(int val) 将一个整数 val 压入栈顶。
  • int pop() 删除并返回堆栈中出现频率最高的元素。如果出现频率最高的元素不只一个,则
    移除并返回最接近栈顶的元素。

示例 1

  • 输入:
    ["FreqStack","push","push","push","push","push","push","pop","pop","pop","pop"],
    [[],[5],[7],[5],[7],[4],[5],[],[],[],[]]
  • 输出:[null,null,null,null,null,null,null,5,7,5,4]
  • 解释:
    freqStack.push(5); // 堆栈为 [5]
    freqStack.push(7); // 堆栈是 [5,7]
    freqStack.push(5); // 堆栈是 [5,7,5]
    freqStack.push(7); // 堆栈是 [5,7,5,7]
    freqStack.push(4); // 堆栈是 [5,7,5,7,4]
    freqStack.push(5); // 堆栈是 [5,7,5,7,4,5]
    freqStack.pop(); // 返回 5
    freqStack.pop(); // 返回 7
    freqStack.pop(); // 返回 5
    freqStack.pop(); // 返回 4
    提示:
  • 0 <= val <= 10^9
  • push 和 pop 最多调用 2 * 10^4 次
  • 输入保证在调用 pop 之前栈中至少有一个元素

解题思路

题目要求“频率最高,同频时最靠近栈顶”,经典做法是按频率分组维护栈。
解法一(频率分组栈,主提交):用 freq 记录每个 val 的出现次数,用 Map<Integer, Deque<Integer>> 记录各频率对应的栈(存放入栈顺序),再维护全局最大频率 maxFreq。
push 时 val 频率 +1,放入对应频次栈并更新 maxFreq;pop 时从 maxFreq 频次栈弹出,
该 val 频率 -1,若该栈空了 maxFreq--。
解法二(优先队列,对照实现):堆化元素 (freq, 时间戳, val),push 更新计数与时间戳,
pop 取堆顶。语义与解法一一对应,用优先队列组织。

代码实现

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;

class FreqStack {

    private final Map<Integer, Integer> freq = new HashMap<>();       // val -> 出现次数
    private final Map<Integer, Deque<Integer>> buckets = new HashMap<>(); // freq -> 栈
    private int maxFreq;

    public FreqStack() {
    }

    public void push(int val) {
        int f = freq.getOrDefault(val, 0) + 1;
        freq.put(val, f);
        maxFreq = Math.max(maxFreq, f);
        buckets.computeIfAbsent(f, k -> new ArrayDeque<>()).push(val);
    }

    public int pop() {
        Deque<Integer> stack = buckets.get(maxFreq);
        int val = stack.pop();
        freq.put(val, freq.get(val) - 1);
        if (stack.isEmpty()) {
            --maxFreq;
        }
        return val;
    }

    // 解法二:优先队列实现(对照)
    static class FreqStack2 {

        private static class Node {
            int freq;
            int time;
            int val;

            Node(int freq, int time, int val) {
                this.freq = freq;
                this.time = time;
                this.val = val;
            }
        }

        private final Map<Integer, Integer> freq = new HashMap<>();
        private final java.util.PriorityQueue<Node> pq =
                new java.util.PriorityQueue<>((a, b) ->
                        a.freq != b.freq ? b.freq - a.freq : b.time - a.time);
        private int ts;

        public void push(int val) {
            int f = freq.getOrDefault(val, 0) + 1;
            freq.put(val, f);
            pq.offer(new Node(f, ++ts, val));
        }

        public int pop() {
            // 懒删除:跳过频率已过期的残留节点
            while (true) {
                Node node = pq.poll();
                if (node.freq == freq.get(node.val)) {
                    freq.put(node.val, node.freq - 1);
                    return node.val;
                }
            }
        }
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        int pass = 0, total = 0;
        {
            FreqStack fs = new FreqStack();
            int[] seq = new int[4];
            fs.push(5);
            fs.push(7);
            fs.push(5);
            fs.push(7);
            fs.push(4);
            fs.push(5);
            seq[0] = fs.pop();
            seq[1] = fs.pop();
            seq[2] = fs.pop();
            seq[3] = fs.pop();
            ++total;
            if (seq[0] == 5 && seq[1] == 7 && seq[2] == 5 && seq[3] == 4) {
                ++pass;
            } else {
                System.out.println("FAIL 895-1: " + java.util.Arrays.toString(seq));
            }
        }
        {
            FreqStack2 fs = new FreqStack2();
            fs.push(1);
            fs.push(1);
            fs.push(2);
            ++total;
            if (fs.pop() == 1) {
                ++pass;
            } else {
                System.out.println("FAIL 895-2a");
            }
            fs.push(2);
            ++total;
            // 此刻 1 频 1、2 频 2:先弹 2;随后 2 又频 1 且最接近栈顶(t3 > t1)→ 再弹 2
            if (fs.pop() == 2 && fs.pop() == 2) {
                ++pass;
            } else {
                System.out.println("FAIL 895-2b");
            }
        }
        if (pass == total) {
            System.out.println("== 0895 PASS ==");
        } else {
            System.out.println("== 0895 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(1)(push/pop)O(n)
解法二O(log n)(push/pop)O(n)

896. Monotonic Array(单调数列)

题目描述

如果数组是单调递增或单调递减的,那么它是单调的。
如果对于所有 i <= j,nums[i] <= nums[j],那么数组 nums 是单调递增的。如果对于所有
i <= j,nums[i] >= nums[j],那么数组 nums 是单调递减的。
当给定的数组 nums 是单调数组时返回 true,否则返回 false。

示例 1

  • 输入:nums = [1,2,2,3]
  • 输出:true

示例 2

  • 输入:nums = [6,5,4,4]
  • 输出:true

示例 3

  • 输入:nums = [1,3,2]
  • 输出:false
    提示:
  • 1 <= nums.length <= 10^5
  • -10^5 <= nums[i] <= 10^5

解题思路

单调数组不允许“先升后降”或“先降后升”,即相邻差符号不能改变。
解法一(一次遍历定趋势,主提交):记录初次出现的非零相邻差符号;之后若出现相反符号的
相邻差,返回 false。时间 O(n),空间 O(1)。
解法二(两次检查,对照实现):分别检查是否单调递增(所有相邻差 <= 0 且 >= 0)与单调
递减,两者任一成立即 true。思路直白,便于理解。

代码实现

class Solution {

    // 解法一:一次遍历定趋势(推荐提交)
    public boolean isMonotonic(int[] nums) {
        int sign = 0;   // 0 未知,1 递增,-1 递减
        for (int i = 1; i < nums.length; ++i) {
            int diff = nums[i] - nums[i - 1];
            if (diff == 0) {
                continue;
            }
            if (sign == 0) {
                sign = diff > 0 ? 1 : -1;
            } else if ((diff > 0 && sign < 0) || (diff < 0 && sign > 0)) {
                return false;
            }
        }
        return true;
    }

    // 解法二:递增/递减分别检查(对照实现)
    public boolean isMonotonic2(int[] nums) {
        boolean inc = true;
        boolean dec = true;
        for (int i = 1; i < nums.length; ++i) {
            if (nums[i] < nums[i - 1]) {
                inc = false;
            }
            if (nums[i] > nums[i - 1]) {
                dec = false;
            }
        }
        return inc || dec;
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution s = new Solution();
        int pass = 0, total = 0;
        int[][] arrs = {
            {1, 2, 2, 3}, {6, 5, 4, 4}, {1, 3, 2}, {1}, {1, 1, 1},
            {3, 2, 1, 0, -1}, {-1, -5, -10}, {1, 2, 3, 2}
        };
        boolean[] exp = {true, true, false, true, true, true, true, false};
        for (int t = 0; t < arrs.length; ++t) {
            ++total;
            if (s.isMonotonic(arrs[t]) == exp[t] && s.isMonotonic2(arrs[t]) == exp[t]) {
                ++pass;
            } else {
                System.out.println("FAIL 896-" + (t + 1) + ": got " + s.isMonotonic(arrs[t]) + " exp " + exp[t]);
            }
        }
        if (pass == total) {
            System.out.println("== 0896 PASS ==");
        } else {
            System.out.println("== 0896 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n)O(1)
解法二O(n)O(1)

897. Increasing Order Search Tree(递增顺序搜索树)

题目描述

给你一棵二叉搜索树的 root,请你按中序遍历将其重新排列为一棵递增顺序搜索树,使树中最
左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。

示例 1

  • 输入:root = [5,3,6,2,4,null,8,1,null,null,null,7,9]
  • 输出:[1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9]

示例 2

  • 输入:root = [5,1,7]
  • 输出:[1,null,5,null,7]
    提示:
  • 树中节点数的取值范围是 [1, 100]
  • 0 <= Node.val <= 1000

解题思路

中序遍历二叉搜索树得到升序序列,把序列串成只有右孩子的链即可。
解法一(中序遍历 + 重建,主提交):递归中序收集节点值(或直接用节点),再从头到尾用
right 指针串起来,每个节点的 left 置空。返回新链头。
解法二(遍历中动态改链,对照实现):维护虚拟头节点 dummy 与当前尾指针 cur,中序
递归访问时把访问到的节点立即接到 cur.right,并把其 left 置空,cur 后移。只遍历一遍、
不需要额外收集。

代码实现

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Queue;

class Solution {

    private TreeNode cur;

    // 解法一:中序收集 + 重建链(推荐提交)
    public TreeNode increasingBST(TreeNode root) {
        Deque<TreeNode> list = new ArrayDeque<>();
        collect(root, list);
        TreeNode dummy = new TreeNode(-1);
        TreeNode tail = dummy;
        while (!list.isEmpty()) {
            TreeNode node = list.pollFirst();
            node.left = null;
            node.right = null;
            tail.right = node;
            tail = node;
        }
        return dummy.right;
    }

    private void collect(TreeNode node, Deque<TreeNode> out) {
        if (node == null) {
            return;
        }
        collect(node.left, out);
        out.addLast(node);
        collect(node.right, out);
    }

    // 解法二:遍历中动态串链(对照实现)
    public TreeNode increasingBST2(TreeNode root) {
        TreeNode dummy = new TreeNode(-1);
        cur = dummy;
        inorder(root);
        return dummy.right;
    }

    private void inorder(TreeNode node) {
        if (node == null) {
            return;
        }
        inorder(node.left);
        node.left = null;
        cur.right = node;
        cur = node;
        inorder(node.right);
    }

    // 二叉树节点定义
    static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) {
            val = x;
        }
    }

    // 层序建树
    private static TreeNode build(Integer[] vals) {
        if (vals == null || vals.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(vals[0]);
        Queue<TreeNode> q = new ArrayDeque<>();
        q.offer(root);
        int i = 1;
        while (!q.isEmpty() && i < vals.length) {
            TreeNode cur = q.poll();
            if (vals[i] != null) {
                cur.left = new TreeNode(vals[i]);
                q.offer(cur.left);
            }
            ++i;
            if (i < vals.length && vals[i] != null) {
                cur.right = new TreeNode(vals[i]);
                q.offer(cur.right);
            }
            ++i;
        }
        return root;
    }

    private static String toChain(TreeNode root) {
        StringBuilder sb = new StringBuilder("[");
        while (root != null) {
            sb.append(root.val).append(",");
            root = root.right;
        }
        String s = sb.toString();
        return s.endsWith(",") ? s.substring(0, s.length() - 1) + "]" : s + "]";
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution s = new Solution();
        int pass = 0, total = 0;
        ++total;
        {
            // 两解法会原地改写树结构,必须分别建树
            TreeNode root1 = build(new Integer[]{5, 3, 6, 2, 4, null, 8, 1, null, null, null, 7, 9});
            TreeNode r1 = s.increasingBST(root1);
            TreeNode root2 = build(new Integer[]{5, 3, 6, 2, 4, null, 8, 1, null, null, null, 7, 9});
            TreeNode r2 = s.increasingBST2(root2);
            String exp = "[1,2,3,4,5,6,7,8,9]";
            if (toChain(r1).equals(exp) && toChain(r2).equals(exp)) {
                ++pass;
            } else {
                System.out.println("FAIL 897-1: " + toChain(r1));
            }
        }
        ++total;
        {
            TreeNode root = build(new Integer[]{5, 1, 7});
            TreeNode r1 = s.increasingBST(root);
            if (toChain(r1).equals("[1,5,7]")) {
                ++pass;
            } else {
                System.out.println("FAIL 897-2: " + toChain(r1));
            }
        }
        ++total;
        {
            TreeNode root = build(new Integer[]{2, 1, 3});
            TreeNode r1 = s.increasingBST(root);
            if (toChain(r1).equals("[1,2,3]")) {
                ++pass;
            } else {
                System.out.println("FAIL 897-3");
            }
        }
        ++total;
        {
            TreeNode root1 = build(new Integer[]{1});
            TreeNode r1 = s.increasingBST(root1);
            TreeNode root2 = build(new Integer[]{1});
            TreeNode r2 = s.increasingBST2(root2);
            if (toChain(r1).equals("[1]") && toChain(r2).equals("[1]")) {
                ++pass;
            } else {
                System.out.println("FAIL 897-4");
            }
        }
        if (pass == total) {
            System.out.println("== 0897 PASS ==");
        } else {
            System.out.println("== 0897 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

| 解法 | 时间复杂度 | 空间复杂度 |
| --- | | --- |


898. Bitwise ORs of Subarrays(子数组按位或操作)

题目描述

给定一个整数数组 arr,返回所有 arr 的非空子数组的不同按位或的数量。
子数组的按位或是子数组中每个整数的按位或。含有一个整数的子数组的按位或就是该整数。
子数组是数组内连续的非空元素序列。

示例 1

  • 输入:arr = [0]
  • 输出:1
  • 解释:只有一个可能的结果 0。

示例 2

  • 输入:arr = [1,1,2]
  • 输出:3
  • 解释:可能的结果为 1、2、3。

示例 3

  • 输入:arr = [1,2,4]
  • 输出:6
  • 解释:可能的结果是 1、2、3、4、6 以及 7。
    提示:
  • 1 <= arr.length <= 5 * 10^4
  • 0 <= arr[i] <= 10^9

解题思路

所有子数组的按位或总数最多为 O(n * 30),因为或只会增加 1 位、最多 30 位,每个位置产生
的“不同或值”集合是有界的。维护“以当前元素结尾的所有子数组的按位或集合”,每个集合大小
不超过 30。
解法一(滚动集合,主提交):用 Set<Integer> cur 记录以 arr[i] 结尾的子数组或值,
每步 cur = {x | newCur = x | arr[i], x in cur} ∪ {arr[i]},把 cur 并入全局答案集合。
每个集合最大 30 个元素,总复杂度 O(n * 30)。
解法二(不等式剪枝的暴力两两或,对照实现):f[i] 表示以 i 结尾的最长“或值不重复增长”
的起点集合,思想同解一,仅以数组形式保存,便于理解去重逻辑。

代码实现

import java.util.HashSet;
import java.util.Set;

class Solution {

    // 解法一:滚动集合(推荐提交)
    public int subarrayBitwiseORs(int[] arr) {
        Set<Integer> ans = new HashSet<>();
        Set<Integer> cur = new HashSet<>();   // 以当前元素结尾的子数组或值集合
        for (int x : arr) {
            Set<Integer> nxt = new HashSet<>();
            nxt.add(x);
            for (int v : cur) {
                nxt.add(v | x);
            }
            cur = nxt;
            ans.addAll(cur);
        }
        return ans.size();
    }

    // 解法二:数组滚动 + 去重(对照实现)
    public int subarrayBitwiseORs2(int[] arr) {
        Set<Integer> ans = new HashSet<>();
        Set<Integer> cur = new HashSet<>();
        for (int x : arr) {
            Set<Integer> nxt = new HashSet<>();
            for (int v : cur) {
                nxt.add(v | x);
            }
            nxt.add(x);
            // 把新集合赋值给 cur(避免复用同一对象导致并发修改)
            cur.clear();
            cur.addAll(nxt);
            ans.addAll(cur);
        }
        return ans.size();
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution s = new Solution();
        int pass = 0, total = 0;
        int[][] arrs = {{0}, {1, 1, 2}, {1, 2, 4}, {3, 4, 5}, {1, 2, 3, 4, 5}};
        // 期望值由 Python 核对:case4={3,4,5,7} 共 4 个;case5={1,2,3,4,5,7} 共 6 个
        int[] exp = {1, 3, 6, 4, 6};
        for (int t = 0; t < arrs.length; ++t) {
            ++total;
            int g1 = s.subarrayBitwiseORs(arrs[t]);
            int g2 = s.subarrayBitwiseORs2(arrs[t]);
            if (g1 == exp[t] && g2 == exp[t]) {
                ++pass;
            } else {
                System.out.println("FAIL 898-" + (t + 1) + ": got " + g1 + "/" + g2 + " exp " + exp[t]);
            }
        }
        if (pass == total) {
            System.out.println("== 0898 PASS ==");
        } else {
            System.out.println("== 0898 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n * 30)O(n * 30)(答案集合)
解法二O(n * 30)O(n * 30)

899. Orderly Queue(有序队列)

题目描述

给定一个字符串 s 和一个整数 k。你可以从 s 的前 k 个字母中选择一个,并把它加到
字符串的末尾。
返回在应用上述步骤的任意数量的移动后,字典序最小的字符串。

示例 1

  • 输入:s = "cba", k = 1
  • 输出:"acb"
  • 解释:第一步移动第一个字符 "c" 到最后得到 "bac",第二步移动 "b" 得到 "acb"。

示例 2

  • 输入:s = "baaca", k = 3
  • 输出:"aaabc"
    提示:
  • 1 <= k <= s.length <= 1000
  • s 只由小写字母组成

解题思路

分两种情况:

  • 当 k == 1 时,每次只能移动首字符到末尾,能得到的字符串就是原串的所有循环移位,
    取其中字典序最小者;
  • 当 k >= 2 时,任意两个相邻字符都可以交换(用 k=2 就能实现任意排列),等价于对
    s 的字符排序,最小字典序就是升序排列后的串。
    解法一(分类讨论,主提交):k >= 2 直接排序返回;k == 1 枚举 n 个循环移位取最小。
    解法二(模拟旋转,对照实现):k == 1 用双倍串 s + s 在滑动窗口内取最小子串;其余
    同解法一。两种实现对 k=1 分支的实现方式不同,便于对照。

代码实现

import java.util.Arrays;

class Solution {

    // 解法一:分类讨论(推荐提交)
    public String orderlyQueue(String s, int k) {
        if (k > 1) {
            char[] cs = s.toCharArray();
            Arrays.sort(cs);
            return new String(cs);
        }
        // k == 1:所有循环移位中取最小
        String best = s;
        int n = s.length();
        for (int i = 1; i < n; ++i) {
            String rotated = s.substring(i) + s.substring(0, i);
            if (rotated.compareTo(best) < 0) {
                best = rotated;
            }
        }
        return best;
    }

    // 解法二:双倍串滑动窗口(对照实现)
    public String orderlyQueue2(String s, int k) {
        if (k > 1) {
            char[] cs = s.toCharArray();
            Arrays.sort(cs);
            return new String(cs);
        }
        String doubled = s + s;
        int n = s.length();
        String best = s;
        for (int i = 0; i < n; ++i) {
            String cand = doubled.substring(i, i + n);
            if (cand.compareTo(best) < 0) {
                best = cand;
            }
        }
        return best;
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        Solution s = new Solution();
        int pass = 0, total = 0;
        String[] ss = {"cba", "baaca", "abc", "zzz", "bca"};
        int[] ks = {1, 3, 1, 1, 1};
        String[] exp = {"acb", "aaabc", "abc", "zzz", "abc"};
        for (int t = 0; t < ss.length; ++t) {
            ++total;
            String g1 = s.orderlyQueue(ss[t], ks[t]);
            String g2 = s.orderlyQueue2(ss[t], ks[t]);
            if (g1.equals(exp[t]) && g2.equals(exp[t])) {
                ++pass;
            } else {
                System.out.println("FAIL 899-" + (t + 1) + ": got " + g1 + "/" + g2 + " exp " + exp[t]);
            }
        }
        if (pass == total) {
            System.out.println("== 0899 PASS ==");
        } else {
            System.out.println("== 0899 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一O(n^2)(k=1 时枚举)或 O(n log n)(k>1)O(n)
解法二O(n^2)(k=1 时) 或 O(n log n)O(n)

900. RLE Iterator(RLE 迭代器)

题目描述

我们可以使用游程编码(即 RLE)来编码一个整数序列。在偶数长度编码的序列 encoding
(从 0 开始)中,元素偶数索引构成计数序列,奇数索引构成值序列。例如,encoding = [3, 8, 0, 9, 2, 5] 是序列 [8, 8, 8, 5, 5] 的 RLE 编码(它有三个 8,0 个 9,两个 5)。
给定一个编码的序列 encoding,实现 RLEIterator 类:

  • RLEIterator(int[] encoding) 对编码后的整数序列构造对象;
  • int next(int n) 以这种方式消费后面的 n 个元素(即返回下一个消费的元素),如果剩余
    的元素少于 n 个,则返回 -1。

示例 1

  • 输入:["RLEIterator","next","next","next","next"], [[[3,8,0,9,2,5]],[2],[1],[1],[2]]
  • 输出:[null,8,8,5,-1]
  • 解释:
    RLEIterator 由 encoding = [3,8,0,9,2,5] 初始化,这映射到序列 [8,8,8,5,5]。
    可以看下表:
  1. next(2) 消费 2 个元素,返回 8。
  2. next(1) 消费接下来的 1 个元素,返回 8。
  3. next(1) 消费接下来的 1 个元素,返回 5。
  4. next(2) 还剩 1 个元素,返回 -1。
    提示:
  • 2 <= encoding.length <= 1000
  • encoding.length 是偶数
  • 0 <= encoding[i] <= 10^9
  • 1 <= n <= 10^9
  • 每个测试用例最多调用 1000 次 next

解题思路

双指针扫描编码数组:i 指向当前“计数”位置,cnt 记录当前位置还剩余多少可消费元素。
解法一(双指针 + 计数,主提交):next(n) 循环:若当前剩余 cnt 不够,n 先减去 cnt,
跳到下一段(i += 2,cnt 重置为该段计数);找到足够的一段后 cnt -= n,返回该段的值。
若 i 超出数组仍不够,返回 -1。均摊 O(1)。
解法二(一次性映射,对照实现):直接构造展开数组再按游标取(数据范围大时不适用),
仅在小数据下对照,说明“惰性消费”是本题要点。

代码实现

class RLEIterator {

    private int[] encoding;
    private int i;       // 当前段计数位置(偶数下标)
    private int cnt;     // 当前段剩余可消费个数

    public RLEIterator(int[] encoding) {
        this.encoding = encoding;
        this.i = 0;
        this.cnt = 0;
    }

    public int next(int n) {
        while (i < encoding.length) {
            long remain = encoding[i] - cnt;   // 当前段剩余个数
            if (n <= remain) {
                cnt += n;
                int val = encoding[i + 1];     // 当前段的值
                if (cnt == encoding[i]) {      // 本段恰好消费完,跳到下一段
                    cnt = 0;
                    i += 2;
                }
                return val;
            } else {
                // 当前段不够:消费整段并前进
                n -= remain;
                cnt = 0;
                i += 2;
            }
        }
        return -1;
    }

    // 解法二:展开式对照(仅小数据)
    static class RLEIterator2 {

        private int[] seq;
        private int pos;

        public RLEIterator2(int[] encoding) {
            java.util.List<Integer> list = new java.util.ArrayList<>();
            for (int j = 0; j + 1 < encoding.length; j += 2) {
                for (int c = 0; c < encoding[j]; ++c) {
                    list.add(encoding[j + 1]);
                }
            }
            seq = new int[list.size()];
            for (int j = 0; j < list.size(); ++j) {
                seq[j] = list.get(j);
            }
        }

        public int next(int n) {
            if (pos + n > seq.length) {
                pos = seq.length;
                return -1;
            }
            pos += n;
            return seq[pos - 1];
        }
    }

    // ===== 本地测试 =====
    public static void main(String[] args) {
        int pass = 0, total = 0;
        {
            int[] enc = {3, 8, 0, 9, 2, 5};
            RLEIterator it = new RLEIterator(enc);
            int[] ans = {it.next(2), it.next(1), it.next(1), it.next(2)};
            ++total;
            if (ans[0] == 8 && ans[1] == 8 && ans[2] == 5 && ans[3] == -1) {
                ++pass;
            } else {
                System.out.println("FAIL 900-1: " + java.util.Arrays.toString(ans));
            }
        }
        {
            int[] enc = {2, 7, 1, 9};
            RLEIterator it = new RLEIterator(enc);
            ++total;
            if (it.next(1) == 7 && it.next(2) == 9 && it.next(2) == -1) {
                ++pass;
            } else {
                System.out.println("FAIL 900-2");
            }
        }
        {
            int[] enc = {3, 8, 0, 9, 2, 5};
            RLEIterator2 it = new RLEIterator2(enc);
            ++total;
            if (it.next(2) == 8 && it.next(1) == 8 && it.next(1) == 5 && it.next(2) == -1) {
                ++pass;
            } else {
                System.out.println("FAIL 900-3");
            }
        }
        if (pass == total) {
            System.out.println("== 0900 PASS ==");
        } else {
            System.out.println("== 0900 FAIL ==");
            System.exit(1);
        }
    }
}

复杂度分析

解法时间复杂度空间复杂度
解法一均摊 O(1)(next)O(1)
解法二O(totalLen) 预构建O(totalLen)