LeetCode 676-689. 题解合集
676. Implement Magic Dictionary(实现一个魔法字典)
题目描述
设计一个使用单词列表进行初始化的数据结构,单词列表中的单词互不相同。如果给出一个单词,
请判定能否只将这个单词中一个字幕换成另一个字母,使得所形成的新单词存在于你构建的字典中。
实现 MagicDictionary 类:
MagicDictionary():初始化对象。void buildDict(String[] dictionary):使用字符串数组dictionary设定该数据结构,
dictionary中的字符串互不相同。boolean search(String searchWord):给定一个字符串searchWord,判定能否只将字符串中
一个字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,
返回true;否则,返回false。
示例
- 输入:
["MagicDictionary", "buildDict", "search", "search", "search", "search"]
[[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]] - 输出:
[null,null,false,true,false,false] - 解释:
MagicDictionary magicDictionary = new MagicDictionary();
magicDictionary.buildDict(["hello", "leetcode"]);
magicDictionary.search("hello"); // 返回 False
magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello",返回 True
magicDictionary.search("hell"); // 返回 False
magicDictionary.search("leetcoded"); // 返回 False
提示: 1 <= dictionary.length <= 1001 <= dictionary[i].length <= 100dictionary[i]仅由小写英文字母组成dictionary中的所有字符串互不相同1 <= searchWord.length <= 100searchWord仅由小写英文字母组成buildDict仅在search之前调用一次- 最多调用 100 次
search
解题思路
解法一(字典树 + DFS,主提交):把所有词插入 Trie。
搜索时 DFS:若当前字符在 Trie 中有子节点,先按原字符走(diff 不变);若还未使用改动机会
(diff == 0),可尝试改走任意其他字母的子节点(diff 置 1)。到词尾时要求恰好改过一次
(diff == 1)且到达词尾节点。
时间复杂度 O(words·len + search 次数·词长·26),空间复杂度 O(总字符数)。
解法二(哈希集暴力):buildDict 存原始词集;搜索时枚举每个位置改成 25 种其他字母,判断
新词是否在词集中。时间复杂度 O(len·26),空间复杂度 O(总词长)。
代码实现
class MagicDictionary {
private static class TrieNode {
TrieNode[] children = new TrieNode[26];
boolean isEnd;
}
private TrieNode root = new TrieNode();
public MagicDictionary() {
}
public void buildDict(String[] dictionary) {
for (String w : dictionary) {
insert(w);
}
}
private void insert(String w) {
TrieNode node = root;
for (int i = 0; i < w.length(); ++i) {
int c = w.charAt(i) - 'a';
if (node.children[c] == null) {
node.children[c] = new TrieNode();
}
node = node.children[c];
}
node.isEnd = true;
}
public boolean search(String searchWord) {
return dfs(searchWord, 0, root, false);
}
private boolean dfs(String w, int i, TrieNode node, boolean changed) {
if (i == w.length()) {
return changed && node.isEnd;
}
int c = w.charAt(i) - 'a';
// 原字符走
if (node.children[c] != null && dfs(w, i + 1, node.children[c], changed)) {
return true;
}
// 未改动过:尝试换成其他字母
if (!changed) {
for (int k = 0; k < 26; ++k) {
if (k != c && node.children[k] != null) {
if (dfs(w, i + 1, node.children[k], true)) {
return true;
}
}
}
}
return false;
}
public static void main(String[] args) {
MagicDictionary md = new MagicDictionary();
md.buildDict(new String[] {"hello", "leetcode"});
int pass = 0, total = 0;
++total; if (!md.search("hello")) ++pass; else System.out.println("FAIL test1");
++total; if (md.search("hhllo")) ++pass; else System.out.println("FAIL test2: " + md.search("hhllo"));
++total; if (!md.search("hell")) ++pass; else System.out.println("FAIL test3");
++total; if (!md.search("leetcoded")) ++pass; else System.out.println("FAIL test4");
MagicDictionary md2 = new MagicDictionary();
md2.buildDict(new String[] {"a", "b"});
++total; if (md2.search("a")) ++pass; else System.out.println("FAIL test5: " + md2.search("a"));
++total; if (md2.search("c")) ++pass; else System.out.println("FAIL test6: " + md2.search("c"));
MagicDictionary md3 = new MagicDictionary();
md3.buildDict(new String[] {"ab", "cd"});
++total; if (md3.search("cb")) ++pass; else System.out.println("FAIL test7: " + md3.search("cb"));
++total; if (md3.search("ad")) ++pass; else System.out.println("FAIL test8: " + md3.search("ad"));
if (pass == total) {
System.out.println("== 0676 PASS ==");
} else {
System.out.println("== 0676 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(S·26)(S 为搜索总长度上限) | O(总字符数) |
| 解法二 | O(len·26) | O(总词长) |
677. Map Sum Pairs(键值映射)
题目描述
设计一个 map,满足以下几点:
- 字符串表示键,整数表示值。
- 返回具有前缀等于给定字符串的键的值的总和。
实现一个MapSum类: MapSum():初始化MapSum对象。void insert(String key, int val):插入key-val键值对。如果键key已经存在,那么原来的
键值对key-value将被替代成新的键值对。int sum(String prefix):返回所有以该前缀prefix开头的键key的值的总和。
示例 1
- 输入:
["MapSum", "insert", "sum", "insert", "sum"]
[[], ["apple", 3], ["ap"], ["app", 2], ["ap"]] - 输出:
[null,null, 3,null, 5] - 解释:
MapSum mapSum = new MapSum();
mapSum.insert("apple", 3);
mapSum.sum("ap"); // 返回 3 (apple = 3)
mapSum.insert("app", 2);
mapSum.sum("ap"); // 返回 5 (apple + app = 3 + 2 = 5)
提示: 1 <= key.length, prefix.length <= 50key和prefix仅由小写英文字母组成1 <= val <= 1000- 最多调用 50 次
insert和sum
解题思路
解法一(字典树节点存区间和,主提交):Trie 每个节点维护经过该节点的所有键值的总和 sum。
insert 时用哈希表记录当前 key 的旧值,新值 x = val - oldVal,把 x 加到路径上所有节点;
sum(prefix) 沿前缀走到节点直接返回 sum(若中途缺节点返回 0)。
时间复杂度 O(key/prefix 长度),空间复杂度 O(总字符数)。
解法二(哈希表暴力):d 存每个 key→val,sum 时遍历所有键检查是否以前缀开头。
时间复杂度 O(键数·len),空间复杂度 O(键数)。
代码实现
import java.util.HashMap;
import java.util.Map;
class MapSum {
private static class TrieNode {
TrieNode[] children = new TrieNode[26];
int sum;
}
private TrieNode root = new TrieNode();
private Map<String, Integer> d = new HashMap<>();
public MapSum() {
}
public void insert(String key, int val) {
int x = val - d.getOrDefault(key, 0);
d.put(key, val);
TrieNode node = root;
for (int i = 0; i < key.length(); ++i) {
int c = key.charAt(i) - 'a';
if (node.children[c] == null) {
node.children[c] = new TrieNode();
}
node = node.children[c];
node.sum += x;
}
}
public int sum(String prefix) {
TrieNode node = root;
for (int i = 0; i < prefix.length(); ++i) {
int c = prefix.charAt(i) - 'a';
if (node.children[c] == null) {
return 0;
}
node = node.children[c];
}
return node.sum;
}
public static void main(String[] args) {
MapSum ms = new MapSum();
int pass = 0, total = 0;
ms.insert("apple", 3);
++total; if (ms.sum("ap") == 3) ++pass; else System.out.println("FAIL test1: " + ms.sum("ap"));
ms.insert("app", 2);
++total; if (ms.sum("ap") == 5) ++pass; else System.out.println("FAIL test2: " + ms.sum("ap"));
++total; if (ms.sum("apple") == 3) ++pass; else System.out.println("FAIL test3: " + ms.sum("apple"));
++total; if (ms.sum("a") == 5) ++pass; else System.out.println("FAIL test4: " + ms.sum("a"));
++total; if (ms.sum("x") == 0) ++pass; else System.out.println("FAIL test5");
ms.insert("apple", 1);
++total; if (ms.sum("ap") == 3) ++pass; else System.out.println("FAIL test6: " + ms.sum("ap"));
ms.insert("ap", 10);
++total; if (ms.sum("a") == 13) ++pass; else System.out.println("FAIL test7: " + ms.sum("a"));
++total; if (ms.sum("") == 0) ++pass; else System.out.println("FAIL test8: " + ms.sum(""));
if (pass == total) {
System.out.println("== 0677 PASS ==");
} else {
System.out.println("== 0677 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(len) | O(总字符数) |
| 解法二 | O(键数·len) | O(键数) |
678. Valid Parenthesis String(有效的括号字符串)
题目描述
给你一个只包含三种字符的字符串,支持的字符类型分别是 '('、')' 和 '*'。请你检验这个字符串
是否为有效字符串,如果是有效字符串返回 true。
有效字符串符合如下规则:
- 任何左括号
'('必须有相应的右括号')'。 - 任何右括号
')'必须有相应的左括号'('。 - 左括号
'('必须在对应的右括号之前')'。 '*'可以被视为单个右括号')',或单个左括号'(',或一个空字符串""。
示例 1
- 输入:
s= "()" - 输出:
true
示例 2
- 输入:
s= "(*)" - 输出:
true
示例 3
- 输入:
s= "(*))" - 输出:
true
提示: 1 <= s.length <= 100s[i]为'('、')'或'*'
解题思路
解法一(贪心区间维护,主提交):用 lo 表示未匹配左括号数量的最小值,hi 表示最大值
(* 可当作左/右/空)。遍历时:
'(':lo++,hi++;')':lo--(至少减到 0),hi--;'*':lo--(当右括号),hi++(当左括号)。
若hi < 0说明右括号过多,失败;lo保持不小于 0。结束时lo == 0才合法。
时间复杂度 O(n),空间复杂度 O(1)。
解法二(区间 DP):dp[i][j]表示s[i..j]是否为有效括号串。长度 1 的区间只有*合法;
更长的区间:若两端能配对(s[i]与s[j]为()或含*)且内部合法,或能在k处拆成
两个合法段。时间复杂度 O(n³),空间复杂度 O(n²)。
代码实现
class Solution {
// 解法一:贪心区间 [lo, hi]
public boolean checkValidString(String s) {
int lo = 0, hi = 0;
for (int i = 0; i < s.length(); ++i) {
char c = s.charAt(i);
if (c == '(') {
++lo;
++hi;
} else if (c == ')') {
lo = Math.max(0, lo - 1);
--hi;
} else { // '*'
lo = Math.max(0, lo - 1);
++hi;
}
if (hi < 0) {
return false;
}
}
return lo == 0;
}
// 解法二:区间 DP
public boolean checkValidString2(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n];
for (int i = 0; i < n; ++i) {
if (s.charAt(i) == '*') {
dp[i][i] = true;
}
}
for (int len = 2; len <= n; ++len) {
for (int i = 0; i + len - 1 < n; ++i) {
int j = i + len - 1;
char a = s.charAt(i), b = s.charAt(j);
// 两端可配对((,) 或含*)且内部合法
if ((a == '(' || a == '*') && (b == ')' || b == '*') && (len == 2 || dp[i + 1][j - 1])) {
dp[i][j] = true;
continue;
}
// 拆成两段
for (int k = i; k < j; ++k) {
if (dp[i][k] && dp[k + 1][j]) {
dp[i][j] = true;
break;
}
}
}
}
return dp[0][n - 1];
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
++total; if (sol.checkValidString("()")) ++pass; else System.out.println("FAIL test1: " + sol.checkValidString("()"));
++total; if (sol.checkValidString("(*)")) ++pass; else System.out.println("FAIL test2: " + sol.checkValidString("(*)"));
++total; if (sol.checkValidString("(*))")) ++pass; else System.out.println("FAIL test3: " + sol.checkValidString("(*))"));
++total; if (!sol.checkValidString("(")) ++pass; else System.out.println("FAIL test4: " + sol.checkValidString("("));
++total; if (!sol.checkValidString(")")) ++pass; else System.out.println("FAIL test5");
++total; if (sol.checkValidString("((*)")) ++pass; else System.out.println("FAIL test6: " + sol.checkValidString("((*)"));
++total; if (sol.checkValidString("(*))()*")) ++pass; else System.out.println("FAIL test7");
++total; if (sol.checkValidString("*()*")) ++pass; else System.out.println("FAIL test8: " + sol.checkValidString("*()*"));
// 解法二
++total; if (sol.checkValidString2("()")) ++pass; else System.out.println("FAIL test9");
++total; if (sol.checkValidString2("(*)")) ++pass; else System.out.println("FAIL test10");
++total; if (sol.checkValidString2("(*))")) ++pass; else System.out.println("FAIL test11");
++total; if (!sol.checkValidString2(")")) ++pass; else System.out.println("FAIL test12");
++total; if (sol.checkValidString2("((*)")) ++pass; else System.out.println("FAIL test13");
++total; if (sol.checkValidString2("*()*")) ++pass; else System.out.println("FAIL test14");
if (pass == total) {
System.out.println("== 0678 PASS ==");
} else {
System.out.println("== 0678 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(n) | O(1) |
| 解法二 | O(n³) | O(n²) |
679. 24 Game(24 点游戏)
题目描述
给定一个长度为 4 的整数数组 cards。你有 4 张卡片,每张卡片上都包含一个范围在 [1,9] 的数字。
您应该使用运算符 ['+', '-', '*', '/'] 和括号 '(' 和 ')' 将这些卡片上的数字排列成数学表达式,
以获得值 24。
你须遵守以下规则:
- 除法运算符
'/'表示实数除法,而不是整数除法。例如4 /(1 - 2 / 3)= 4 /(1 / 3)= 12。 - 每个运算都在两个数字之间。特别是,不能使用
"-"作为一元运算符。
例如,如果cards = [1,1,1,1],则表达式"-1 -1 -1 -1"是不允许的。 - 你不能把数字串在一起。例如,如果
cards = [1,2,1,2],则表达式"12 + 12"无效。
如果可以得到这样的表达式,其计算结果为 24,则返回true,否则返回false。
示例 1
- 输入:cards = [4, 1, 8, 7]
- 输出:
true - 解释:(8-4) * (7-1) = 24
示例 2
- 输入:cards = [1, 2, 1, 2]
- 输出:
false
提示: cards.length == 41 <= cards[i] <= 9
解题思路
解法一(回溯选两数计算,主提交):问题等价于:从集合中任取两个数做四则运算得到一个新数,
与剩余数组成更小的集合,递归直到只剩一个数,判断是否等于 24(浮点容差 1e-6)。
每次枚举两个数的顺序(a op b 与 b op a 各算一次,+ * 交换律可省但保留无妨),
除法时要跳过除数为 0。时间复杂度 O(组合数·种数),空间复杂度 O(递归深度)。
解法二(枚举所有括号结构):只对 4 个数,枚举全部 5 种括号结构与 4 种运算符组合,
共 4!×4³×5 种,逐一计算。本质与解法一相同,只是显式枚举;此处以解法一统一实现,
解法二作为相同思想的另一种写法(给出 judgePoint24 主方法)。
代码实现
class Solution {
// 解法一:回溯
public boolean judgePoint24(int[] cards) {
double[] a = new double[] {cards[0], cards[1], cards[2], cards[3]};
return solve(a, 4);
}
private boolean solve(double[] a, int n) {
if (n == 1) {
return Math.abs(a[0] - 24.0) < 1e-6;
}
// 任选两个下标 i, j
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (i == j) {
continue;
}
double[] nxt = new double[n - 1];
int idx = 0;
for (int k = 0; k < n; ++k) {
if (k != i && k != j) {
nxt[idx++] = a[k];
}
}
double x = a[i], y = a[j];
// 四种运算
double[] vals = new double[] {x + y, x - y, x * y};
int cnt = 3;
if (Math.abs(y) > 1e-9) {
vals = new double[] {x + y, x - y, x * y, x / y};
cnt = 4;
}
for (int v = 0; v < cnt; ++v) {
nxt[idx] = vals[v];
if (solve(nxt, n - 1)) {
return true;
}
}
}
}
return false;
}
// 解法二:直接枚举两数合并(与解法一等价,改用操作符显式计算)
public boolean judgePoint24_2(int[] cards) {
double[] a = new double[] {cards[0], cards[1], cards[2], cards[3]};
return dfs2(a);
}
private boolean dfs2(double[] a) {
int n = a.length;
if (n == 1) {
return Math.abs(a[0] - 24.0) < 1e-6;
}
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
double[] rest = new double[n - 1];
int idx = 0;
for (int k = 0; k < n; ++k) {
if (k != i && k != j) {
rest[idx++] = a[k];
}
}
double x = a[i], y = a[j];
double[] cand = {x + y, x - y, y - x, x * y};
int cnt = 4;
if (Math.abs(y) > 1e-9) {
cand = new double[] {x + y, x - y, y - x, x * y, x / y};
cnt = 5;
if (Math.abs(x) > 1e-9) {
cand = new double[] {x + y, x - y, y - x, x * y, x / y, y / x};
cnt = 6;
}
}
for (int v = 0; v < cnt; ++v) {
rest[idx] = cand[v];
if (dfs2(rest)) {
return true;
}
}
}
}
return false;
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
++total; if (sol.judgePoint24(new int[] {4, 1, 8, 7})) ++pass; else System.out.println("FAIL test1: " + sol.judgePoint24(new int[] {4, 1, 8, 7}));
++total; if (!sol.judgePoint24(new int[] {1, 2, 1, 2})) ++pass; else System.out.println("FAIL test2");
++total; if (sol.judgePoint24(new int[] {1, 2, 1, 2}) == false && sol.judgePoint24(new int[] {8, 1, 6, 1})) ++pass; else System.out.println("FAIL test3");
++total; if (!sol.judgePoint24(new int[] {1, 1, 1, 1})) ++pass; else System.out.println("FAIL test4");
++total; if (sol.judgePoint24(new int[] {1, 3, 4, 6})) ++pass; else System.out.println("FAIL test5: " + sol.judgePoint24(new int[] {1, 3, 4, 6}));
++total; if (sol.judgePoint24_2(new int[] {4, 1, 8, 7})) ++pass; else System.out.println("FAIL test6");
++total; if (!sol.judgePoint24_2(new int[] {1, 2, 1, 2})) ++pass; else System.out.println("FAIL test7");
++total; if (!sol.judgePoint24_2(new int[] {1, 1, 1, 1})) ++pass; else System.out.println("FAIL test8");
++total; if (sol.judgePoint24_2(new int[] {1, 3, 4, 6})) ++pass; else System.out.println("FAIL test9");
if (pass == total) {
System.out.println("== 0679 PASS ==");
} else {
System.out.println("== 0679 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(1)(常数级,最大 9216 种) | O(1) |
| 解法二 | O(1) | O(1) |
680. Valid Palindrome II(验证回文串 II)
题目描述
给你一个字符串 s,最多可以从中删除一个字符。
请你判断 s 是否能成为回文字符串:如果能,返回 true;否则,返回 false。
示例 1
- 输入:
s= "aba" - 输出:
true
示例 2
- 输入:
s= "abca" - 输出:
true - 解释:你可以删除字符 '
c'。
示例 3
- 输入:
s= "abc" - 输出:
false
提示: 1 <= s.length <= 10^5s由小写英文字母组成
解题思路
解法一(双指针 + 一次容错,主提交):两端指针向中间移动,遇到第一处失配 s[i] != s[j] 时,
只需分别验证「删掉 s[i] 后 s[i+1..j] 是否回文」与「删掉 s[j] 后 s[i..j-1] 是否回文」,
二者任一成立即可。整体只多扫描一遍。时间复杂度 O(n),空间复杂度 O(1)。
解法二(贪心删除 + 子串判断):同样双指针,失配时利用 s.substring(i+1, j+1) 与
s.substring(i, j) 调用回文判断工具(isPalindrome),逻辑等价但更直观。
时间复杂度 O(n),空间复杂度 O(n)(子串)。
代码实现
class Solution {
// 解法一:双指针 + 一次容错
public boolean validPalindrome(String s) {
int i = 0, j = s.length() - 1;
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
return isPal(s, i + 1, j) || isPal(s, i, j - 1);
}
++i;
--j;
}
return true;
}
private boolean isPal(String s, int l, int r) {
while (l < r) {
if (s.charAt(l) != s.charAt(r)) {
return false;
}
++l;
--r;
}
return true;
}
// 解法二:失配时用子串判断
public boolean validPalindrome2(String s) {
int i = 0, j = s.length() - 1;
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
return isPalindrome(s.substring(i + 1, j + 1)) || isPalindrome(s.substring(i, j));
}
++i;
--j;
}
return true;
}
private boolean isPalindrome(String s) {
return isPal(s, 0, s.length() - 1);
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
++total; if (sol.validPalindrome("aba")) ++pass; else System.out.println("FAIL test1: " + sol.validPalindrome("aba"));
++total; if (sol.validPalindrome("abca")) ++pass; else System.out.println("FAIL test2: " + sol.validPalindrome("abca"));
++total; if (!sol.validPalindrome("abc")) ++pass; else System.out.println("FAIL test3");
++total; if (sol.validPalindrome("abccba")) ++pass; else System.out.println("FAIL test4: " + sol.validPalindrome("abccba"));
++total; if (sol.validPalindrome("a")) ++pass; else System.out.println("FAIL test5: " + sol.validPalindrome("a"));
++total; if (sol.validPalindrome("deeee")) ++pass; else System.out.println("FAIL test6: " + sol.validPalindrome("deeee"));
++total; if (!sol.validPalindrome("abcd")) ++pass; else System.out.println("FAIL test7");
++total; if (sol.validPalindrome2("aba")) ++pass; else System.out.println("FAIL test8");
++total; if (sol.validPalindrome2("abca")) ++pass; else System.out.println("FAIL test9");
++total; if (!sol.validPalindrome2("abc")) ++pass; else System.out.println("FAIL test10");
++total; if (sol.validPalindrome2("deeee")) ++pass; else System.out.println("FAIL test11");
if (pass == total) {
System.out.println("== 0680 PASS ==");
} else {
System.out.println("== 0680 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(n) | O(1) |
| 解法二 | O(n) | O(n) |
681. Next Closest Time(最近时刻)
题目描述
给定一个形如 "HH:MM" 表示的时刻 time,利用当前出现过的数字构造下一个距离当前时间最近的时刻。
每个出现数字都可以被无限次使用。
你可以认为给定的字符串一定是合法的。例如,"01:34" 和 "12:09" 是合法的,"1:34" 和
"12:9" 是不合法的。
示例 1
- 输入:"19:34"
- 输出:"19:39"
- 解释:利用数字 1, 9, 3, 4 构造出来的最近时刻是 19:39,是 5 分钟之后。结果不是 19:33,
因为这个时刻是 23 小时 59 分钟之后。
示例 2
- 输入:"23:59"
- 输出:"22:22"
- 解释:答案一定是第二天的某一时刻,所以选择可构造的最小时刻。
提示: time.length == 5time为有效时间,格式为"HH:MM"0 <= HH < 240 <= MM < 60
解题思路
解法一(DFS 枚举 4 位数字 + 最近时间差,主提交):把时间转成分钟 t;用集合记录出现过的数字。
DFS 枚举四位(时 2 位 + 分 2 位),校验合法的 HH:MM;计算候选分钟 p 与 t 的差值
(p > t 用 p - t,否则跨天 p - t + 1440),取差值最小的候选。
如果没有任何合法候选比当前更大,则取“可构造的最小时刻”(数字集合中的最小字符组成 mm:mm)。
时间复杂度 O(4·4!),空间复杂度 O(1)。
解法二(直接枚举 1440 个分钟):从 t+1 到 t+1440(模 1440)逐个检查每个分钟的所有数字
是否都在原数字集合中,第一个满足的就是答案。直觉清晰,实现最简。
时间复杂度 O(1440),空间复杂度 O(1)。
代码实现
import java.util.HashSet;
import java.util.Set;
class Solution {
// 解法一:DFS 枚举
private int t;
private int bestDiff;
private String best;
private Set<Character> set;
public String nextClosestTime(String time) {
t = Integer.parseInt(time.substring(0, 2)) * 60 + Integer.parseInt(time.substring(3));
set = new HashSet<>();
char mi = 'z';
for (char c : time.toCharArray()) {
if (c != ':') {
set.add(c);
if (c < mi) {
mi = c;
}
}
}
bestDiff = Integer.MAX_VALUE;
best = null;
dfs(new char[4], 0);
if (best == null) {
// 没有比当前更近的,取最小数字组成的最小时刻(次日)
best = "" + mi + mi + ":" + mi + mi;
}
return best;
}
private void dfs(char[] cur, int pos) {
if (pos == 4) {
int h = (cur[0] - '0') * 10 + (cur[1] - '0');
int m = (cur[2] - '0') * 10 + (cur[3] - '0');
if (h > 23 || m > 59) {
return;
}
int p = h * 60 + m;
int diff = p > t ? p - t : p - t + 1440;
if (diff < bestDiff) {
bestDiff = diff;
best = "" + cur[0] + cur[1] + ":" + cur[2] + cur[3];
}
return;
}
for (char c : set) {
cur[pos] = c;
dfs(cur, pos + 1);
}
}
// 解法二:枚举分钟(最简)
public String nextClosestTime2(String time) {
int t = Integer.parseInt(time.substring(0, 2)) * 60 + Integer.parseInt(time.substring(3));
Set<Character> set = new HashSet<>();
for (char c : time.toCharArray()) {
if (c != ':') {
set.add(c);
}
}
for (int i = 1; i <= 1440; ++i) {
int next = (t + i) % 1440;
String cand = String.format("%02d:%02d", next / 60, next % 60);
boolean ok = true;
for (char c : cand.toCharArray()) {
if (c != ':' && !set.contains(c)) {
ok = false;
break;
}
}
if (ok) {
return cand;
}
}
return time;
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
++total; if (sol.nextClosestTime("19:34").equals("19:39")) ++pass; else System.out.println("FAIL test1: " + sol.nextClosestTime("19:34"));
++total; if (sol.nextClosestTime("23:59").equals("22:22")) ++pass; else System.out.println("FAIL test2: " + sol.nextClosestTime("23:59"));
++total; if (sol.nextClosestTime("13:55").equals("15:11")) ++pass; else System.out.println("FAIL test3: " + sol.nextClosestTime("13:55"));
++total; if (sol.nextClosestTime("11:11").equals("11:11")) ++pass; else System.out.println("FAIL test4: " + sol.nextClosestTime("11:11"));
++total; if (sol.nextClosestTime("20:48").equals("22:00")) ++pass; else System.out.println("FAIL test5: " + sol.nextClosestTime("20:48"));
// 解法二
++total; if (sol.nextClosestTime2("19:34").equals("19:39")) ++pass; else System.out.println("FAIL test6: " + sol.nextClosestTime2("19:34"));
++total; if (sol.nextClosestTime2("23:59").equals("22:22")) ++pass; else System.out.println("FAIL test7: " + sol.nextClosestTime2("23:59"));
++total; if (sol.nextClosestTime2("13:55").equals("15:11")) ++pass; else System.out.println("FAIL test8: " + sol.nextClosestTime2("13:55"));
++total; if (sol.nextClosestTime2("11:11").equals("11:11")) ++pass; else System.out.println("FAIL test9: " + sol.nextClosestTime2("11:11"));
++total; if (sol.nextClosestTime2("20:48").equals("22:00")) ++pass; else System.out.println("FAIL test10: " + sol.nextClosestTime2("20:48"));
if (pass == total) {
System.out.println("== 0681 PASS ==");
} else {
System.out.println("== 0681 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(4·4!) | O(1) |
| 解法二 | O(1440) | O(1) |
682. Baseball Game(棒球比赛)
题目描述
你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响
以后几回合的得分。比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中
ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则:
- 整数
x:表示本回合新获得分数x。 "+":表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。"D":表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。"C":表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。
请你返回记录中所有得分的总和。
示例 1
- 输入:
ops= ["5","2","C","D","+"] - 输出:30
- 解释:
"5" - 记录加 5 ,记录现在是 [5]
"2" - 记录加 2 ,记录现在是 [5, 2]
"C" - 移除前一次得分,记录现在是 [5]
"D" - 记录加 2 * 5 = 10 ,记录现在是 [5, 10]
"+" - 记录加 5 + 10 = 15 ,记录现在是 [5, 10, 15]
所有得分的总和 5 + 10 + 15 = 30
示例 2
- 输入:
ops= ["5","-2","4","C","D","9","+","+"] - 输出:27
示例 3
- 输入:
ops= ["1"] - 输出:1
提示: 1 <= ops.length <= 1000ops[i]为"C"、"D"、"+",或者一个表示整数的字符串,整数范围是[-3*10^4, 3*10^4]
解题思路
解法一(栈模拟,主提交):用栈保存每回合的有效得分。
- 数字:解析入栈;
"C":弹出栈顶;"D":新分数 = 栈顶 × 2 入栈;"+":新分数 = 栈顶 + 次栈顶入栈。
最后把栈内所有分数求和。时间复杂度 O(n),空间复杂度 O(n)。
解法二(数组模拟栈):用数组arr与计数器top代替Deque(避免频繁装箱/拆箱,性能略优),
逻辑完全一致。时间复杂度 O(n),空间复杂度 O(n)。
代码实现
import java.util.ArrayDeque;
import java.util.Deque;
class Solution {
// 解法一:双端队列模拟栈
public int calPoints(String[] ops) {
Deque<Integer> st = new ArrayDeque<>();
for (String op : ops) {
if (op.equals("C")) {
st.pop();
} else if (op.equals("D")) {
st.push(st.peek() * 2);
} else if (op.equals("+")) {
int top = st.pop();
int newVal = top + st.peek();
st.push(top);
st.push(newVal);
} else {
st.push(Integer.parseInt(op));
}
}
int sum = 0;
for (int v : st) {
sum += v;
}
return sum;
}
// 解法二:数组模拟栈
public int calPoints2(String[] ops) {
int[] arr = new int[ops.length];
int top = 0;
for (String op : ops) {
if (op.equals("C")) {
--top;
} else if (op.equals("D")) {
arr[top] = arr[top - 1] * 2;
++top;
} else if (op.equals("+")) {
arr[top] = arr[top - 1] + arr[top - 2];
++top;
} else {
arr[top] = Integer.parseInt(op);
++top;
}
}
int sum = 0;
for (int i = 0; i < top; ++i) {
sum += arr[i];
}
return sum;
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
++total; if (sol.calPoints(new String[] {"5", "2", "C", "D", "+"}) == 30) ++pass; else System.out.println("FAIL test1: " + sol.calPoints(new String[] {"5", "2", "C", "D", "+"}));
++total; if (sol.calPoints(new String[] {"5", "-2", "4", "C", "D", "9", "+", "+"}) == 27) ++pass; else System.out.println("FAIL test2: " + sol.calPoints(new String[] {"5", "-2", "4", "C", "D", "9", "+", "+"}));
++total; if (sol.calPoints(new String[] {"1"}) == 1) ++pass; else System.out.println("FAIL test3: " + sol.calPoints(new String[] {"1"}));
++total; if (sol.calPoints(new String[] {"5", "5", "D", "+"}) == 35) ++pass; else System.out.println("FAIL test4: " + sol.calPoints(new String[] {"5", "5", "D", "+"}));
++total; if (sol.calPoints2(new String[] {"5", "2", "C", "D", "+"}) == 30) ++pass; else System.out.println("FAIL test5");
++total; if (sol.calPoints2(new String[] {"5", "-2", "4", "C", "D", "9", "+", "+"}) == 27) ++pass; else System.out.println("FAIL test6");
++total; if (sol.calPoints2(new String[] {"1"}) == 1) ++pass; else System.out.println("FAIL test7");
++total; if (sol.calPoints2(new String[] {"5", "5", "D", "+"}) == 35) ++pass; else System.out.println("FAIL test8");
if (pass == total) {
System.out.println("== 0682 PASS ==");
} else {
System.out.println("== 0682 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(n) | O(n) |
| 解法二 | O(n) | O(n) |
683. K Empty Slots(K 个关闭的灯泡)
题目描述
n 个灯泡排成一行,编号从 1 到 n。最初,所有灯泡都关闭。每天只打开一个灯泡,直到
n 天后所有灯泡都打开。
给你一个长度为 n 的灯泡数组 bulbs,其中 bulbs[i] = x 意味着在第 (i+1) 天,我们会把
在位置 x 的灯泡打开,其中 i 从 0 开始,x 从 1 开始。
给你一个整数 k,请返回恰好有两个打开的灯泡,且它们中间正好有 k 个全部关闭的灯泡的
最小的天数。如果不存在这种情况,返回 -1。
示例 1
- 输入:bulbs = [1,3,2],
k= 1 - 输出:2
- 解释:第一天打开第一个灯泡 [1,0,0];第二天打开第三个灯泡 [1,0,1];第三天打开第二个
灯泡 [1,1,1]。返回 2,因为在第二天,两个打开的灯泡之间恰好有一个关闭的灯泡。
示例 2
- 输入:bulbs = [1,2,3],
k= 1 - 输出:-1
提示: n == bulbs.length1 <= n <= 2 * 10^41 <= bulbs[i] <= nbulbs是一个由从1到n的数字构成的排列0 <= k <= 2 * 10^4
解题思路
解法一(有序集合 TreeSet,主提交):按天数依次打开灯泡,把打开位置加入集合。每次打开
位置 x 后,只需检查它在集合中的前驱 prev 与后继 next:若 x - prev - 1 == k 或
next - x - 1 == k,说明存在一对打开的灯泡中间恰好隔 k 个关闭的灯泡(当 k == 0 时即
相邻)。因为“中间全是关闭”等价于中间没有任何已打开位置,所以相邻已开位置之间的距离
就是中间关闭灯泡的个数。返回第一天的天数。时间复杂度 O(n log n),空间复杂度 O(n)。
解法二(树状数组):用树状数组维护已打开位置的前缀个数。当天打开 x 后,若
x - (k+1) 已打开且区间 (x-k-1, x-1] 内前缀计数之差为 0(即中间全是关闭),或者
x + (k+1) 已打开且区间 (x, x+k] 内前缀计数之差为 0,则当天符合要求,返回天数。
时间复杂度 O(n log n),空间复杂度 O(n)。
代码实现
import java.util.Arrays;
import java.util.TreeSet;
class Solution {
// 解法一:有序集合
public int kEmptySlots(int[] bulbs, int k) {
TreeSet<Integer> set = new TreeSet<>();
for (int day = 1; day <= bulbs.length; ++day) {
int x = bulbs[day - 1];
set.add(x);
Integer prev = set.lower(x);
Integer next = set.higher(x);
if (prev != null && x - prev - 1 == k) {
return day;
}
if (next != null && next - x - 1 == k) {
return day;
}
}
return -1;
}
// 解法二:树状数组
public int kEmptySlots2(int[] bulbs, int k) {
int n = bulbs.length;
BinaryIndexedTree tree = new BinaryIndexedTree(n);
boolean[] vis = new boolean[n + 1];
for (int day = 1; day <= n; ++day) {
int x = bulbs[day - 1];
tree.update(x, 1);
vis[x] = true;
int y = x - k - 1;
if (y >= 1 && vis[y] && tree.query(x - 1) - tree.query(y) == 0) {
return day;
}
y = x + k + 1;
if (y <= n && vis[y] && tree.query(y - 1) - tree.query(x) == 0) {
return day;
}
}
return -1;
}
private static class BinaryIndexedTree {
private int n;
private int[] c;
BinaryIndexedTree(int n) {
this.n = n;
this.c = new int[n + 1];
}
void update(int x, int delta) {
for (; x <= n; x += x & -x) {
c[x] += delta;
}
}
int query(int x) {
int s = 0;
for (; x > 0; x -= x & -x) {
s += c[x];
}
return s;
}
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
++total; if (sol.kEmptySlots(new int[]{1, 3, 2}, 1) == 2) ++pass; else System.out.println("FAIL test1: " + sol.kEmptySlots(new int[]{1, 3, 2}, 1));
++total; if (sol.kEmptySlots(new int[]{1, 2, 3}, 1) == -1) ++pass; else System.out.println("FAIL test2: " + sol.kEmptySlots(new int[]{1, 2, 3}, 1));
++total; if (sol.kEmptySlots(new int[]{3, 1, 5, 4, 2}, 1) == 2) ++pass; else System.out.println("FAIL test3: " + sol.kEmptySlots(new int[]{3, 1, 5, 4, 2}, 1));
++total; if (sol.kEmptySlots(new int[]{1, 4, 2, 3}, 2) == 2) ++pass; else System.out.println("FAIL test4: " + sol.kEmptySlots(new int[]{1, 4, 2, 3}, 2));
++total; if (sol.kEmptySlots(new int[]{1, 3, 2}, 0) == 3) ++pass; else System.out.println("FAIL test5: " + sol.kEmptySlots(new int[]{1, 3, 2}, 0));
// 解法二
++total; if (sol.kEmptySlots2(new int[]{1, 3, 2}, 1) == 2) ++pass; else System.out.println("FAIL test6: " + sol.kEmptySlots2(new int[]{1, 3, 2}, 1));
++total; if (sol.kEmptySlots2(new int[]{1, 2, 3}, 1) == -1) ++pass; else System.out.println("FAIL test7: " + sol.kEmptySlots2(new int[]{1, 2, 3}, 1));
++total; if (sol.kEmptySlots2(new int[]{3, 1, 5, 4, 2}, 1) == 2) ++pass; else System.out.println("FAIL test8: " + sol.kEmptySlots2(new int[]{3, 1, 5, 4, 2}, 1));
++total; if (sol.kEmptySlots2(new int[]{1, 4, 2, 3}, 2) == 2) ++pass; else System.out.println("FAIL test9: " + sol.kEmptySlots2(new int[]{1, 4, 2, 3}, 2));
++total; if (sol.kEmptySlots2(new int[]{1, 3, 2}, 0) == 3) ++pass; else System.out.println("FAIL test10: " + sol.kEmptySlots2(new int[]{1, 3, 2}, 0));
if (pass == total) {
System.out.println("== 0683 PASS ==");
} else {
System.out.println("== 0683 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(n log n) | O(n) |
| 解法二 | O(n log n) | O(n) |
684. Redundant Connection(冗余连接)
题目描述
树可以看成是一个连通且无环的无向图。
给定一个图,该图从一棵 n 个节点(节点值 1~n)的树中添加一条边后获得。添加的边的两个不同顶点
编号在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组
edges,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回
数组 edges 中最后出现的那个。
示例 1
- 输入:
edges= [[1,2], [1,3], [2,3]] - 输出:[2,3]
示例 2
- 输入:
edges= [[1,2], [2,3], [3,4], [1,4], [1,5]] - 输出:[1,4]
提示: n == edges.length3 <= n <= 1000edges[i].length == 21 <= ai < bi <= edges.lengthai != biedges中无重复元素- 给定的图是连通的
解题思路
解法一(并查集,主提交):按输入顺序遍历每条边。
- 若两个端点已经连通(在同一个集合),说明这条边在环上,删掉它即可恢复树,直接返回该边;
- 否则合并两个端点所在集合。
因为输入保证有且仅有一条多余边,第一次遇到“已连通”的边即为答案。
时间复杂度 O(n·α(n)),空间复杂度 O(n)。
解法二(DFS 找环 + 取最后一条环边):先按输入顺序建邻接表,找到任意环,再保留环上
出现次序最靠后的边返回。实现更繁琐,仅作对照说明。
(本文件给出解法一;解法二以注释形式简述。)
代码实现
class Solution {
private int[] parent;
private int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
private boolean union(int a, int b) {
int ra = find(a), rb = find(b);
if (ra == rb) {
return false;
}
parent[ra] = rb;
return true;
}
// 解法一:并查集
public int[] findRedundantConnection(int[][] edges) {
int n = edges.length;
parent = new int[n + 1];
for (int i = 1; i <= n; ++i) {
parent[i] = i;
}
for (int[] e : edges) {
if (!union(e[0], e[1])) {
return e;
}
}
return new int[0];
}
// 解法二等价实现:逆向删边验证(删掉某条边后若仍连通且无环,则是答案)——此处保留为
// 简单校验:从后往前试删边,用并查集检查删后是否成树(无环且连通)。O(n²·α(n))
public int[] findRedundantConnection2(int[][] edges) {
int n = edges.length;
for (int del = n - 1; del >= 0; --del) {
parent = new int[n + 1];
for (int i = 1; i <= n; ++i) {
parent[i] = i;
}
boolean ok = true;
for (int i = 0; i < n; ++i) {
if (i == del) {
continue;
}
if (!union(edges[i][0], edges[i][1])) {
ok = false;
break;
}
}
if (ok) {
int root = find(1);
boolean all = true;
for (int i = 2; i <= n; ++i) {
if (find(i) != root) {
all = false;
break;
}
}
if (all) {
return edges[del];
}
}
}
return new int[0];
}
private static boolean eq(int[] a, int[] b) {
return a[0] == b[0] && a[1] == b[1];
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
int[] r;
r = sol.findRedundantConnection(new int[][] {{1, 2}, {1, 3}, {2, 3}});
++total; if (eq(r, new int[] {2, 3})) ++pass; else System.out.println("FAIL test1: [" + r[0] + "," + r[1] + "]");
r = sol.findRedundantConnection(new int[][] {{1, 2}, {2, 3}, {3, 4}, {1, 4}, {1, 5}});
++total; if (eq(r, new int[] {1, 4})) ++pass; else System.out.println("FAIL test2: [" + r[0] + "," + r[1] + "]");
r = sol.findRedundantConnection(new int[][] {{1, 2}, {2, 3}, {3, 1}});
++total; if (eq(r, new int[] {3, 1})) ++pass; else System.out.println("FAIL test3: [" + r[0] + "," + r[1] + "]");
// 解法二
r = sol.findRedundantConnection2(new int[][] {{1, 2}, {1, 3}, {2, 3}});
++total; if (eq(r, new int[] {2, 3})) ++pass; else System.out.println("FAIL test4");
r = sol.findRedundantConnection2(new int[][] {{1, 2}, {2, 3}, {3, 4}, {1, 4}, {1, 5}});
++total; if (eq(r, new int[] {1, 4})) ++pass; else System.out.println("FAIL test5");
r = sol.findRedundantConnection2(new int[][] {{1, 2}, {2, 3}, {3, 1}});
++total; if (eq(r, new int[] {3, 1})) ++pass; else System.out.println("FAIL test6");
if (pass == total) {
System.out.println("== 0684 PASS ==");
} else {
System.out.println("== 0684 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(n·α(n)) | O(n) |
| 解法二 | O(n) | O(n) |
685. Redundant Connection II(冗余连接 II)
题目描述
在本问题中,有根树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。
该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。
附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组 edges。每个元素是一对 [ui, vi],用以表示有向图中连接顶点
ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。
返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组
的答案。
示例 1
- 输入:
edges= [[1,2],[1,3],[2,3]] - 输出:[2,3]
示例 2
- 输入:
edges= [[1,2],[2,3],[3,4],[4,1],[1,5]] - 输出:[4,1]
提示: n == edges.length3 <= n <= 1000edges[i].length == 21 <= ui, vi <= n
解题思路
有向图多一条边可能造成两种异常:
- 某个点入度为 2(候选边为两条指向该点的边);
- 存在环(候选边为参与成环的边)。
解法一(并查集分类处理,主提交):
- 先统计每个节点的入度,找出唯一入度为 2 的节点,记下指向它的两条边
cand1(较早)、cand2(较晚)。 - 先把
cand2暂时不加入并查集,用其余边跑并查集: - 若并查集无环,说明删
cand2可行,返回cand2; - 若有环,说明
cand2不能删,返回cand1。 - 若没有入度为 2 的节点(纯环情形),用无向并查集找形成环的那条边返回(同 684)。
时间复杂度 O(n·α(n)),空间复杂度 O(n)。
解法二(枚举删除每条边后验证):从后往前枚举删除的边,删除后检查剩余图是否满足
“每个节点入度 ≤1、无环、恰好一个根”。取第一个满足的边返回。
时间复杂度 O(n²·α(n)),空间复杂度 O(n)。
代码实现
import java.util.Arrays;
class Solution {
private int[] parent;
private int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
private boolean union(int a, int b) {
int ra = find(a), rb = find(b);
if (ra == rb) {
return false;
}
parent[ra] = rb;
return true;
}
private void init(int n) {
parent = new int[n + 1];
for (int i = 1; i <= n; ++i) {
parent[i] = i;
}
}
// 解法一:入度 + 并查集
public int[] findRedundantDirectedConnection(int[][] edges) {
int n = edges.length;
// 记录每个节点的入度,找出入度为 2 的点 dup
int[] indeg = new int[n + 1];
int dup = -1;
for (int[] e : edges) {
++indeg[e[1]];
if (indeg[e[1]] == 2) {
dup = e[1];
}
}
if (dup != -1) {
// 收集指向 dup 的两条边(按出现顺序)
int first = -1, second = -1;
for (int i = 0; i < n; ++i) {
if (edges[i][1] == dup) {
if (first == -1) {
first = i;
} else {
second = i;
}
}
}
int[] cand1 = edges[first];
int[] cand2 = edges[second];
// 尝试删掉 cand2(较晚那条),其余边跑并查集
init(n);
boolean circle = false;
for (int i = 0; i < n; ++i) {
if (i == second) {
continue;
}
if (!union(edges[i][0], edges[i][1])) {
circle = true;
break;
}
}
if (!circle) {
return cand2;
}
return cand1;
}
// 无入度为 2:纯环,按无向处理
init(n);
for (int[] e : edges) {
if (!union(e[0], e[1])) {
return e;
}
}
return new int[0];
}
// 解法二:枚举删除每条边验证
public int[] findRedundantDirectedConnection2(int[][] edges) {
int n = edges.length;
for (int del = n - 1; del >= 0; --del) {
init(n);
int[] indeg = new int[n + 1];
boolean ok = true;
for (int i = 0; i < n; ++i) {
if (i == del) {
continue;
}
++indeg[edges[i][1]];
if (indeg[edges[i][1]] > 1) {
ok = false;
break;
}
if (!union(edges[i][0], edges[i][1])) {
ok = false;
break;
}
}
if (!ok) {
continue;
}
// 根:入度为 0 的节点恰一个
int roots = 0;
for (int i = 1; i <= n; ++i) {
if (indeg[i] == 0) {
++roots;
}
}
if (roots == 1) {
return edges[del];
}
}
return new int[0];
}
private static boolean eq(int[] a, int[] b) {
return a[0] == b[0] && a[1] == b[1];
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
int[] r;
r = sol.findRedundantDirectedConnection(new int[][] {{1, 2}, {1, 3}, {2, 3}});
++total; if (eq(r, new int[] {2, 3})) ++pass; else System.out.println("FAIL test1: [" + r[0] + "," + r[1] + "]");
r = sol.findRedundantDirectedConnection(new int[][] {{1, 2}, {2, 3}, {3, 4}, {4, 1}, {1, 5}});
++total; if (eq(r, new int[] {4, 1})) ++pass; else System.out.println("FAIL test2: [" + r[0] + "," + r[1] + "]");
r = sol.findRedundantDirectedConnection(new int[][] {{2, 1}, {3, 1}, {4, 2}, {1, 4}});
++total; if (eq(r, new int[] {2, 1})) ++pass; else System.out.println("FAIL test3: [" + r[0] + "," + r[1] + "]");
r = sol.findRedundantDirectedConnection(new int[][] {{1, 2}, {2, 3}, {3, 1}});
++total; if (eq(r, new int[] {3, 1})) ++pass; else System.out.println("FAIL test4: [" + r[0] + "," + r[1] + "]");
// 解法二
r = sol.findRedundantDirectedConnection2(new int[][] {{1, 2}, {1, 3}, {2, 3}});
++total; if (eq(r, new int[] {2, 3})) ++pass; else System.out.println("FAIL test5");
r = sol.findRedundantDirectedConnection2(new int[][] {{1, 2}, {2, 3}, {3, 4}, {4, 1}, {1, 5}});
++total; if (eq(r, new int[] {4, 1})) ++pass; else System.out.println("FAIL test6");
r = sol.findRedundantDirectedConnection2(new int[][] {{2, 1}, {3, 1}, {4, 2}, {1, 4}});
++total; if (eq(r, new int[] {2, 1})) ++pass; else System.out.println("FAIL test7");
r = sol.findRedundantDirectedConnection2(new int[][] {{1, 2}, {2, 3}, {3, 1}});
++total; if (eq(r, new int[] {3, 1})) ++pass; else System.out.println("FAIL test8");
if (pass == total) {
System.out.println("== 0685 PASS ==");
} else {
System.out.println("== 0685 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(n·α(n)) | O(n) |
| 解法二 | O(n²·α(n)) | O(n) |
686. Repeated String Match(重复叠加字符串匹配)
题目描述
给定两个字符串 a 和 b,寻找重复叠加字符串 a 的最小次数,使得字符串 b 成为叠加后的
字符串 a 的子串,如果不存在则返回 -1。
注意:字符串 "abc" 重复叠加 0 次是 "",重复叠加 1 次是 "abc",重复叠加 2 次是 "abcabc"。
示例 1
- 输入:
a= "abcd",b= "cdabcdab" - 输出:3
- 解释:
a重复叠加三遍后为 "abcdabcdabcd",此时b是其子串。
示例 2
- 输入:
a= "a",b= "aa" - 输出:2
示例 3
- 输入:
a= "a",b= "a" - 输出:1
示例 4
- 输入:
a= "abc",b= "wxyz" - 输出:-1
提示: 1 <= a.length <= 10^41 <= b.length <= 10^4a和b由小写英文字母组成
解题思路
解法一(拼接判断,主提交):a 至少重复 ceil(b.length / a.length) 次才可能包含 b。
为了覆盖跨边界匹配,再多复制 1 次,即检查次数 times 与 times+1 两种情况;
如果 b 在其中一种拼接串中出现,返回对应次数,否则 -1。
直接用 String.indexOf(b) 判断子串。时间复杂度 O((times·|a|)·|b|),空间复杂度 O(|a|·times)。
解法二(KMP 优化):重复叠加字符串是周期串,用 KMP 在周期串中找 b,避免每次 indexOf
线性扫。实现上把 a 拼接 ceil(|b|/|a|)+1 次后跑 KMP 匹配,找到最小叠加次数。
时间复杂度 O(|a|·times + |b|),空间复杂度 O(|b|)。
代码实现
class Solution {
// 解法一:拼接 + indexOf
public int repeatedStringMatch(String a, String b) {
int times = (b.length() + a.length() - 1) / a.length();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < times; ++i) {
sb.append(a);
}
if (sb.indexOf(b) != -1) {
return times;
}
sb.append(a);
if (sb.indexOf(b) != -1) {
return times + 1;
}
return -1;
}
// 解法二:KMP 匹配
public int repeatedStringMatch2(String a, String b) {
int times = (b.length() + a.length() - 1) / a.length();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < times + 1; ++i) {
sb.append(a);
}
String s = sb.toString();
int idx = kmp(s, b);
if (idx == -1) {
return -1;
}
// 匹配起始于 idx,覆盖到 idx + |b|,需要的叠加次数上限为 times+1
int need = (idx + b.length() + a.length() - 1) / a.length();
return need <= times + 1 ? need : -1;
}
private int kmp(String text, String pat) {
int n = text.length(), m = pat.length();
int[] next = new int[m];
int j = 0;
for (int i = 1; i < m; ++i) {
while (j > 0 && pat.charAt(i) != pat.charAt(j)) {
j = next[j - 1];
}
if (pat.charAt(i) == pat.charAt(j)) {
++j;
}
next[i] = j;
}
j = 0;
for (int i = 0; i < n; ++i) {
while (j > 0 && text.charAt(i) != pat.charAt(j)) {
j = next[j - 1];
}
if (text.charAt(i) == pat.charAt(j)) {
++j;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
++total; if (sol.repeatedStringMatch("abcd", "cdabcdab") == 3) ++pass; else System.out.println("FAIL test1: " + sol.repeatedStringMatch("abcd", "cdabcdab"));
++total; if (sol.repeatedStringMatch("a", "aa") == 2) ++pass; else System.out.println("FAIL test2: " + sol.repeatedStringMatch("a", "aa"));
++total; if (sol.repeatedStringMatch("a", "a") == 1) ++pass; else System.out.println("FAIL test3: " + sol.repeatedStringMatch("a", "a"));
++total; if (sol.repeatedStringMatch("abc", "wxyz") == -1) ++pass; else System.out.println("FAIL test4: " + sol.repeatedStringMatch("abc", "wxyz"));
++total; if (sol.repeatedStringMatch("ab", "babab") == 3) ++pass; else System.out.println("FAIL test5: " + sol.repeatedStringMatch("ab", "babab"));
++total; if (sol.repeatedStringMatch("bb", "bbbb") == 2) ++pass; else System.out.println("FAIL test6: " + sol.repeatedStringMatch("bb", "bbbb"));
// KMP 解法
++total; if (sol.repeatedStringMatch2("abcd", "cdabcdab") == 3) ++pass; else System.out.println("FAIL test7");
++total; if (sol.repeatedStringMatch2("a", "aa") == 2) ++pass; else System.out.println("FAIL test8");
++total; if (sol.repeatedStringMatch2("abc", "wxyz") == -1) ++pass; else System.out.println("FAIL test9");
++total; if (sol.repeatedStringMatch2("ab", "babab") == 3) ++pass; else System.out.println("FAIL test10");
if (pass == total) {
System.out.println("== 0686 PASS ==");
} else {
System.out.println("== 0686 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | ||||||
|---|---|---|---|---|---|---|---|---|
| 解法一 | O( | a | ·times· | b | ) | O( | a | ·times) |
| 解法二 | O( | a | ·times + | b | ) | O( | b | ) |
687. Longest Univalue Path(最长同值路径)
题目描述
给定一个二叉树的 root,返回最长的路径的长度,这个路径中的每个节点具有相同值。
这条路径可以经过也可以不经过根节点。
两个节点之间的路径长度由它们之间的边数表示。
示例 1
- 输入:
root= [5,4,5,1,1,5] - 输出:2
示例 2
- 输入:
root= [1,4,5,4,4,5] - 输出:2 提示:
- 树的节点数的范围是
[0, 10^4] -1000 <= Node.val <= 1000- 树的深度将不超过 1000
解题思路
解法一(DFS 返回单侧最长同值边长,主提交):dfs(node) 返回从 node 出发向下延伸的
最长同值路径边数(只走单侧)。对每个节点:
- 左孩子同值则
leftPath = dfs(left) + 1,否则 0; - 右孩子同值则
rightPath = dfs(right) + 1,否则 0; - 用
leftPath + rightPath更新全局答案(路径可在该节点拐弯); - 向上返回
max(leftPath, rightPath)(只能带一侧)。 时间复杂度 O(n),空间复杂度 O(height)。 解法二(后序迭代 + 栈记录):用显式栈模拟后序,同样维护每节点单侧最长值。 时间复杂度 O(n),空间复杂度 O(n)。
代码实现
class Solution {
private static class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
private int ans;
// 解法一:DFS
public int longestUnivaluePath(TreeNode root) {
ans = 0;
dfs(root);
return ans;
}
private int dfs(TreeNode root) {
if (root == null) {
return 0;
}
int left = dfs(root.left);
int right = dfs(root.right);
int leftPath = 0, rightPath = 0;
if (root.left != null && root.left.val == root.val) {
leftPath = left + 1;
}
if (root.right != null && root.right.val == root.val) {
rightPath = right + 1;
}
ans = Math.max(ans, leftPath + rightPath);
return Math.max(leftPath, rightPath);
}
// 解法二:显式后序迭代
public int longestUnivaluePath2(TreeNode root) {
if (root == null) {
return 0;
}
ans = 0;
java.util.Map<TreeNode, int[]> memo = new java.util.HashMap<>();
java.util.Deque<TreeNode> st = new java.util.ArrayDeque<>();
st.push(root);
java.util.Deque<TreeNode> post = new java.util.ArrayDeque<>();
while (!st.isEmpty()) {
TreeNode cur = st.pop();
post.push(cur);
if (cur.left != null) {
st.push(cur.left);
}
if (cur.right != null) {
st.push(cur.right);
}
}
while (!post.isEmpty()) {
TreeNode cur = post.pop();
int lp = 0, rp = 0;
if (cur.left != null && cur.left.val == cur.val) {
lp = memo.getOrDefault(cur.left, new int[] {0})[0] + 1;
}
if (cur.right != null && cur.right.val == cur.val) {
rp = memo.getOrDefault(cur.right, new int[] {0})[0] + 1;
}
ans = Math.max(ans, lp + rp);
memo.put(cur, new int[] {Math.max(lp, rp)});
}
return ans;
}
// 层序构建(测试辅助)
private static TreeNode build(Integer[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
TreeNode root = new TreeNode(arr[0]);
java.util.Queue<TreeNode> q = new java.util.LinkedList<>();
q.offer(root);
int i = 1;
while (!q.isEmpty() && i < arr.length) {
TreeNode cur = q.poll();
if (arr[i] != null) {
cur.left = new TreeNode(arr[i]);
q.offer(cur.left);
}
++i;
if (i < arr.length && arr[i] != null) {
cur.right = new TreeNode(arr[i]);
q.offer(cur.right);
}
++i;
}
return root;
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
TreeNode t1 = build(new Integer[] {5, 4, 5, 1, 1, 5});
++total; if (sol.longestUnivaluePath(t1) == 2) ++pass; else System.out.println("FAIL test1: " + sol.longestUnivaluePath(t1));
TreeNode t2 = build(new Integer[] {1, 4, 5, 4, 4, 5});
++total; if (sol.longestUnivaluePath(t2) == 2) ++pass; else System.out.println("FAIL test2: " + sol.longestUnivaluePath(t2));
TreeNode t3 = build(new Integer[] {1});
++total; if (sol.longestUnivaluePath(t3) == 0) ++pass; else System.out.println("FAIL test3: " + sol.longestUnivaluePath(t3));
TreeNode t4 = build(new Integer[] {1, 1});
++total; if (sol.longestUnivaluePath(t4) == 1) ++pass; else System.out.println("FAIL test4: " + sol.longestUnivaluePath(t4));
TreeNode t5 = build(new Integer[] {1, 1, 1, 1, 1, 1, 1});
++total; if (sol.longestUnivaluePath(t5) == 4) ++pass; else System.out.println("FAIL test5: " + sol.longestUnivaluePath(t5));
// 解法二
++total; if (sol.longestUnivaluePath2(t1) == 2) ++pass; else System.out.println("FAIL test6");
++total; if (sol.longestUnivaluePath2(t2) == 2) ++pass; else System.out.println("FAIL test7");
++total; if (sol.longestUnivaluePath2(t3) == 0) ++pass; else System.out.println("FAIL test8");
++total; if (sol.longestUnivaluePath2(t5) == 4) ++pass; else System.out.println("FAIL test9");
if (pass == total) {
System.out.println("== 0687 PASS ==");
} else {
System.out.println("== 0687 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(n) | O(height) |
| 解法二 | O(n) | O(n) |
688. Knight Probability in Chessboard(骑士在棋盘上的概率)
题目描述
在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始,并尝试进行 k 次移动。
行和列是从 0 开始的,所以左上单元格是 (0,0),右下单元格是 (n-1, n-1)。
象棋骑士有 8 种可能的走法。每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格。
每次骑士要移动时,它都会随机从 8 种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。
骑士继续移动,直到它走了 k 步或离开了棋盘。
返回骑士在棋盘停止移动后仍留在棋盘上的概率。
示例 1
- 输入:
n= 3,k= 2,row= 0, column = 0 - 输出:0.0625
示例 2
- 输入:
n= 1,k= 0,row= 0, column = 0 - 输出:1.00000 提示:
1 <= n <= 250 <= k <= 1000 <= row, column <= n - 1
解题思路
解法一(三维 DP,主提交):f[h][i][j] 表示从 (i, j) 再走 h 步仍留在棋盘上的概率。
边界 f[0][i][j] = 1。递推:f[h][i][j] = sum(f[h-1][ni][nj]) / 8,(ni, nj) 为 8 个
合法落点(越界则不算概率,因为已经离开棋盘)。答案为 f[k][row][column]。
用滚动数组优化为两层。时间复杂度 O(k·n²·8),空间复杂度 O(n²)。
解法二(记忆化递归):memo[step][i][j] 存从 (i,j) 走 step 步留在棋上的概率,递归自顶向下。
时间复杂度 O(k·n²·8),空间复杂度 O(k·n²)。
代码实现
class Solution {
private static final int[][] DIRS = {
{1, 2}, {1, -2}, {-1, 2}, {-1, -2},
{2, 1}, {2, -1}, {-2, 1}, {-2, -1}
};
// 解法一:滚动 DP
public double knightProbability(int n, int k, int row, int column) {
double[][] dp = new double[n][n];
for (int i = 0; i < n; ++i) {
java.util.Arrays.fill(dp[i], 1.0);
}
for (int step = 1; step <= k; ++step) {
double[][] ndp = new double[n][n];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
double p = 0;
for (int[] d : DIRS) {
int ni = i + d[0], nj = j + d[1];
if (ni >= 0 && ni < n && nj >= 0 && nj < n) {
p += dp[ni][nj] / 8.0;
}
}
ndp[i][j] = p;
}
}
dp = ndp;
}
return dp[row][column];
}
// 解法二:记忆化递归
private double[][][] memo;
private int N;
public double knightProbability2(int n, int k, int row, int column) {
N = n;
memo = new double[k + 1][n][n];
for (int s = 0; s <= k; ++s) {
for (int i = 0; i < n; ++i) {
java.util.Arrays.fill(memo[s][i], -1.0);
}
}
return prob(k, row, column);
}
private double prob(int step, int i, int j) {
if (i < 0 || i >= N || j < 0 || j >= N) {
return 0.0;
}
if (step == 0) {
return 1.0;
}
if (memo[step][i][j] >= 0) {
return memo[step][i][j];
}
double p = 0;
for (int[] d : DIRS) {
p += prob(step - 1, i + d[0], j + d[1]) / 8.0;
}
memo[step][i][j] = p;
return p;
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
++total; if (Math.abs(sol.knightProbability(3, 2, 0, 0) - 0.0625) < 1e-9) ++pass; else System.out.println("FAIL test1: " + sol.knightProbability(3, 2, 0, 0));
++total; if (Math.abs(sol.knightProbability(1, 0, 0, 0) - 1.0) < 1e-9) ++pass; else System.out.println("FAIL test2: " + sol.knightProbability(1, 0, 0, 0));
++total; if (Math.abs(sol.knightProbability(3, 0, 0, 0) - 1.0) < 1e-9) ++pass; else System.out.println("FAIL test3");
++total; if (Math.abs(sol.knightProbability(8, 30, 6, 4) - 0.00019) < 1e-4) ++pass; else System.out.println("FAIL test4: " + sol.knightProbability(8, 30, 6, 4));
++total; if (Math.abs(sol.knightProbability(2, 1, 0, 0) - 0.0) < 1e-9) ++pass; else System.out.println("FAIL test5: " + sol.knightProbability(2, 1, 0, 0));
// 解法二
++total; if (Math.abs(sol.knightProbability2(3, 2, 0, 0) - 0.0625) < 1e-9) ++pass; else System.out.println("FAIL test6");
++total; if (Math.abs(sol.knightProbability2(1, 0, 0, 0) - 1.0) < 1e-9) ++pass; else System.out.println("FAIL test7");
++total; if (Math.abs(sol.knightProbability2(8, 30, 6, 4) - 0.00019) < 1e-4) ++pass; else System.out.println("FAIL test8");
if (pass == total) {
System.out.println("== 0688 PASS ==");
} else {
System.out.println("== 0688 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(k·n²) | O(n²) |
| 解法二 | O(k·n²) | O(k·n²) |
689. Maximum Sum of 3 Non-Overlapping Subarrays(三个无重叠子数组的最大和)
题目描述
给你一个整数数组 nums 和一个整数 k,找出三个长度为 k、互不重叠、且全部数字和最大的
子数组,并返回这三个子数组。
以下标的数组形式返回结果,数组中的每一项分别指示每个子数组的起始位置(下标从 0 开始)。
如果有多个结果,返回字典序最小的一个。
示例 1
- 输入:
nums= [1,2,1,2,6,7,5,1],k= 2 - 输出:[0,3,5]
- 解释:子数组 [1,2]、[2,6]、[7,5] 对应的起始下标为 [0,3,5]。也可以取 [2,1],但是结果 [1,3,5] 在字典序上更大。
示例 2
- 输入:
nums= [1,2,1,2,1,2,1,2,1],k= 2 - 输出:[0,2,4] 提示:
1 <= nums.length <= 2 * 10^41 <= nums[i] < 2^161 <= k <= floor(nums.length / 3)
解题思路
解法一(滑动窗口,主提交):把三个长度为 k 的窗口看成三根同步右移的指针。维护:
第一段扫描到的最大值 mx1 及其起点 idx1;前两段组合的最大值 mx12 及其两个起点;
再与当前第三段和 s3 相加得到总最大值。每次只比较不相等时才更新(相等时保留靠左的旧值),
从而保证字典序最小。时间复杂度 O(n),空间复杂度 O(1)。
解法二(前缀和 + 预处理左右最佳段):用前缀和快速求任意长度为 k 的子数组和;从左往右
预处理 pre[i]([0, i] 范围内最佳 k 段的和与起点),从右往左预处理 suf[i]
([i, n) 范围内最佳 k 段的和与起点);枚举中间段起点 i,答案由 pre[i-1] + 中间段 + suf[i+k]
组合而成。时间复杂度 O(n),空间复杂度 O(n)。
代码实现
import java.util.Arrays;
class Solution {
// 解法一:滑动窗口
public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
int n = nums.length;
int[] ans = new int[3];
int s = 0, s1 = 0, s2 = 0, s3 = 0;
int mx1 = 0, mx12 = 0;
int idx1 = 0, idx121 = 0, idx122 = 0;
for (int i = k * 2; i < n; ++i) {
s1 += nums[i - k * 2];
s2 += nums[i - k];
s3 += nums[i];
if (i >= k * 3 - 1) {
if (s1 > mx1) {
mx1 = s1;
idx1 = i - k * 3 + 1;
}
if (mx1 + s2 > mx12) {
mx12 = mx1 + s2;
idx121 = idx1;
idx122 = i - k * 2 + 1;
}
if (mx12 + s3 > s) {
s = mx12 + s3;
ans[0] = idx121;
ans[1] = idx122;
ans[2] = i - k + 1;
}
s1 -= nums[i - k * 3 + 1];
s2 -= nums[i - k * 2 + 1];
s3 -= nums[i - k + 1];
}
}
return ans;
}
// 解法二:前缀和 + 左右最佳段
public int[] maxSumOfThreeSubarrays2(int[] nums, int k) {
int n = nums.length;
int[] s = new int[n + 1];
for (int i = 0; i < n; ++i) {
s[i + 1] = s[i] + nums[i];
}
// pre[i] : [0, i] 内最佳 k 段 [和, 起点]
int[][] pre = new int[n][2];
for (int i = 0, t = 0, idx = 0; i < n - k + 1; ++i) {
int cur = s[i + k] - s[i];
if (cur > t) {
t = cur;
idx = i;
}
pre[i + k - 1][0] = t;
pre[i + k - 1][1] = idx;
}
// suf[i] : [i, n) 内最佳 k 段 [和, 起点]
int[][] suf = new int[n][2];
for (int i = n - k, t = 0, idx = 0; i >= 0; --i) {
int cur = s[i + k] - s[i];
if (cur >= t) {
t = cur;
idx = i;
}
suf[i][0] = t;
suf[i][1] = idx;
}
int[] ans = new int[3];
int best = 0;
for (int i = k; i < n - 2 * k + 1; ++i) {
int cur = s[i + k] - s[i] + pre[i - 1][0] + suf[i + k][0];
if (cur > best) {
best = cur;
ans[0] = pre[i - 1][1];
ans[1] = i;
ans[2] = suf[i + k][1];
}
}
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
int pass = 0, total = 0;
++total; if (Arrays.equals(sol.maxSumOfThreeSubarrays(new int[]{1, 2, 1, 2, 6, 7, 5, 1}, 2), new int[]{0, 3, 5})) ++pass; else System.out.println("FAIL test1: " + Arrays.toString(sol.maxSumOfThreeSubarrays(new int[]{1, 2, 1, 2, 6, 7, 5, 1}, 2)));
++total; if (Arrays.equals(sol.maxSumOfThreeSubarrays(new int[]{1, 2, 1, 2, 1, 2, 1, 2, 1}, 2), new int[]{0, 2, 4})) ++pass; else System.out.println("FAIL test2: " + Arrays.toString(sol.maxSumOfThreeSubarrays(new int[]{1, 2, 1, 2, 1, 2, 1, 2, 1}, 2)));
++total; if (Arrays.equals(sol.maxSumOfThreeSubarrays(new int[]{4, 3, 2, 1}, 1), new int[]{0, 1, 2})) ++pass; else System.out.println("FAIL test3: " + Arrays.toString(sol.maxSumOfThreeSubarrays(new int[]{4, 3, 2, 1}, 1)));
// 解法二
++total; if (Arrays.equals(sol.maxSumOfThreeSubarrays2(new int[]{1, 2, 1, 2, 6, 7, 5, 1}, 2), new int[]{0, 3, 5})) ++pass; else System.out.println("FAIL test4: " + Arrays.toString(sol.maxSumOfThreeSubarrays2(new int[]{1, 2, 1, 2, 6, 7, 5, 1}, 2)));
++total; if (Arrays.equals(sol.maxSumOfThreeSubarrays2(new int[]{1, 2, 1, 2, 1, 2, 1, 2, 1}, 2), new int[]{0, 2, 4})) ++pass; else System.out.println("FAIL test5: " + Arrays.toString(sol.maxSumOfThreeSubarrays2(new int[]{1, 2, 1, 2, 1, 2, 1, 2, 1}, 2)));
++total; if (Arrays.equals(sol.maxSumOfThreeSubarrays2(new int[]{4, 3, 2, 1}, 1), new int[]{0, 1, 2})) ++pass; else System.out.println("FAIL test6: " + Arrays.toString(sol.maxSumOfThreeSubarrays2(new int[]{4, 3, 2, 1}, 1)));
if (pass == total) {
System.out.println("== 0689 PASS ==");
} else {
System.out.println("== 0689 FAIL ==");
System.exit(1);
}
}
}
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 解法一 | O(n) | O(1) |
| 解法二 | O(n) | O(n) |