题目1:340. 至多包含 K 个不同字符的最长子串
给你一个字符串
s
和一个整数k
,请你找出 至多 包含k
个 不同 字符的最长子串
,并返回该子串的长度。示例 1:
输入:s = "eceba", k = 2 输出:3 解释:满足题目要求的子串是 "ece" ,长度为 3 。示例 2:
输入:s = "aa", k = 1 输出:2 解释:满足题目要求的子串是 "aa" ,长度为 2 。提示:
1 <= s.length <= 5 * 104
0 <= k <= 50
class Solution {
public int lengthOfLongestSubstringKDistinct(String s, int k) {
if (k == 0) {
return 0;
}
// 使用 HashMap 记录窗口内每个字符出现的次数
Map<Character, Integer> charCount = new HashMap<>();
int maxLength = 0;
int left = 0;
// 右指针遍历字符串
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
// 将当前字符加入 HashMap
charCount.put(c, charCount.getOrDefault(c, 0) + 1);
// 当 HashMap 中不同字符的数量超过 k 时,需要收缩窗口
while (charCount.size() > k) {
char leftChar = s.charAt(left);
charCount.put(leftChar, charCount.get(leftChar) - 1);
if (charCount.get(leftChar) == 0) {
charCount.remove(leftChar);
}
left++;
}
// 更新最大长度
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
}
题目2:724. 寻找数组的中心下标
给你一个整数数组
nums
,请计算数组的 中心下标 。数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为
0
,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回
-1
。示例 1:
输入:nums = [1, 7, 3, 6, 5, 6] 输出:3 解释: 中心下标是 3 。 左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 , 右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。示例 2:
输入:nums = [1, 2, 3] 输出:-1 解释: 数组中不存在满足此条件的中心下标。示例 3:
输入:nums = [2, 1, -1] 输出:0 解释: 中心下标是 0 。 左侧数之和 sum = 0 ,(下标 0 左侧不存在元素), 右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0 。提示:
1 <= nums.length <= 104
-1000 <= nums[i] <= 1000
class Solution {
public int pivotIndex(int[] nums) {
// 计算数组总和
int total = 0;
for (int num : nums) {
total += num;
}
// 从左向右遍历,计算左侧和
int leftSum = 0;
for (int i = 0; i < nums.length; i++) {
// 右侧和 = 总和 - 左侧和 - 当前元素
int rightSum = total - leftSum - nums[i];
// 如果左侧和等于右侧和,找到中心下标
if (leftSum == rightSum) {
return i;
}
// 更新左侧和
leftSum += nums[i];
}
// 没有找到中心下标,返回-1
return -1;
}
}
题目3:776. 拆分二叉搜索树
给你一棵二叉搜索树(BST)的根结点
root
和一个整数target
。请将该树按要求拆分为两个子树:其中第一个子树结点的值都必须小于等于给定的目标值;另一个子树结点的值都必须大于目标值;树中并非一定要存在值为target
的结点。除此之外,树中大部分结构都需要保留,也就是说原始树中父节点
p
的任意子节点c
,假如拆分后它们仍在同一个子树中,那么结点p
应仍为c
的父结点。按顺序返回 两个子树的根结点的数组 。
示例 1:
输入:root = [4,2,6,1,3,5,7], target = 2 输出:[[2,1],[4,3,6,null,null,5,7]]示例 2:
输入: root = [1], target = 1 输出: [[1],[]]提示:
- 二叉搜索树节点个数在
[1, 50]
范围内0 <= Node.val, target <= 1000
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode[] splitBST(TreeNode root, int target) {
// 结果数组:result[0]存储小于等于target的子树,result[1]存储大于target的子树
TreeNode[] result = new TreeNode[]{null, null};
// 处理空树的情况
if (root == null) {
return result;
}
// 如果根节点值小于等于target
if (root.val <= target) {
// 递归处理右子树
result = splitBST(root.right, target);
// 当前节点属于小于等于target的子树
root.right = result[0];
result[0] = root;
return result;
}
// 如果根节点值大于target
else {
// 递归处理左子树
result = splitBST(root.left, target);
// 当前节点属于大于target的子树
root.left = result[1];
result[1] = root;
return result;
}
}
}
题目4:78. 子集
给你一个整数数组
nums
,数组中的元素 互不相同 。返回该数组所有可能的子集
(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]示例 2:
输入:nums = [0] 输出:[[],[0]]提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums
中的所有元素 互不相同
class Solution {
public List<List<Integer>> subsets(int[] nums) {
int resultSize = 1<<nums.length;
List<List<Integer>> result = new ArrayList<>(resultSize);
//init the result set
result.add(Collections.emptyList());
for (int i=0; i<nums.length; i++) {
// if nums[i] not in set, has been existed, skip it.
// if nums[i] in set, we need to copy it and add the element into result
int currentLen = result.size();
for(int j=0; j<currentLen; j++) {
List<Integer> list = result.get(j);
List<Integer> newList = new ArrayList<>(list.size()+1);
newList.addAll(list);
newList.add(nums[i]);
result.add(newList);
}
}
return result;
}
public List<List<Integer>> subsets(int[] nums, int end) {
List<List<Integer>> result = new ArrayList<>();
if (end <= 1) {
// no elements
result.add(Collections.emptyList());
// one element
result.add(List.of(nums[0]));
} else {
List<List<Integer>> resultN1 = subsets(nums, end-1);
// the last element not in set
result.addAll(resultN1);
// the last element in set
int lastEle = nums[end-1];
for(List<Integer> list: resultN1) {
List<Integer> newList = new ArrayList<>(list.size()+1);
newList.addAll(list);
newList.add(lastEle);
result.add(newList);
}
}
return result;
}
}
题目5:394. 字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为:
k[encoded_string]
,表示其中方括号内部的encoded_string
正好重复k
次。注意k
保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数
k
,例如不会出现像3a
或2[4]
的输入。示例 1:
输入:s = "3[a]2[bc]" 输出:"aaabcbc"示例 2:
输入:s = "3[a2[c]]" 输出:"accaccacc"示例 3:
输入:s = "2[abc]3[cd]ef" 输出:"abcabccdcdcdef"示例 4:
输入:s = "abc3[cd]xyz" 输出:"abccdcdcdxyz"提示:
1 <= s.length <= 30
s
由小写英文字母、数字和方括号'[]'
组成s
保证是一个 有效 的输入。s
中所有整数的取值范围为[1, 300]
class Solution {
public String decodeString(String s) {
StringBuilder sb = new StringBuilder();
int len = s.length();
for (int index=0; index<len; index++) {
Character ch = s.charAt(index);
if (ch == ']') {
decode(sb);
} else {
sb.append(ch);
}
}
return sb.toString();
}
/**
* 2str, 2a, 2abc
*/
public void decode(StringBuilder builder) {
int size = builder.length();
int kEnd = -1, index;
String subStr="";
for (index=size-1; index>=0; index--) {
Character ch = builder.charAt(index);
if (ch == '[' && kEnd == -1) {
subStr = builder.substring(index+1);
kEnd = index;
} else if (kEnd != -1 && (ch <'0' || ch > '9')) { // not number
break;
}
}
if (kEnd != -1) {
Integer k = Integer.valueOf(builder.substring(index+1, kEnd));
// replace 2a,2abc with aa, abcabc.
builder.setLength(index+1);
for (int j=1; j<=k; j++) {
builder.append(subStr);
}
}
}
}
题目6: 139. 单词拆分
给你一个字符串
s
和一个字符串列表wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出s
则返回true
。注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"] 输出: true 解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"] 输出: true 解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。 注意,你可以重复使用字典中的单词。示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] 输出: false提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s
和wordDict[i]
仅由小写英文字母组成wordDict
中的所有字符串 互不相同
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
// 将 wordDict 转换为 Set,便于快速查找
Set<String> wordSet = new HashSet<>(wordDict);
// dp[i] 表示字符串 s 的前 i 个字符是否可以被拆分成 wordDict 中的单词
boolean[] dp = new boolean[s.length() + 1];
// 空字符串可以被拆分
dp[0] = true;
// 遍历所有可能的字符串长度
for (int i = 1; i <= s.length(); i++) {
// 遍历所有可能的拆分点
for (int j = 0; j < i; j++) {
// 如果前 j 个字符可以被拆分,并且剩余部分在字典中存在
if (dp[j] && wordSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
题目7:715. Range 模块
Range模块是跟踪数字范围的模块。设计一个数据结构来跟踪表示为 半开区间 的范围并查询它们。
半开区间
[left, right)
表示所有left <= x < right
的实数x
。实现
RangeModule
类:
RangeModule()
初始化数据结构的对象。void addRange(int left, int right)
添加 半开区间[left, right)
,跟踪该区间中的每个实数。添加与当前跟踪的数字部分重叠的区间时,应当添加在区间[left, right)
中尚未跟踪的任何数字到该区间中。boolean queryRange(int left, int right)
只有在当前正在跟踪区间[left, right)
中的每一个实数时,才返回true
,否则返回false
。void removeRange(int left, int right)
停止跟踪 半开区间[left, right)
中当前正在跟踪的每个实数。示例 1:
输入 ["RangeModule", "addRange", "removeRange", "queryRange", "queryRange", "queryRange"] [[], [10, 20], [14, 16], [10, 14], [13, 15], [16, 17]] 输出 [null, null, null, true, false, true] 解释 RangeModule rangeModule = new RangeModule(); rangeModule.addRange(10, 20); rangeModule.removeRange(14, 16); rangeModule.queryRange(10, 14); 返回 true (区间 [10, 14) 中的每个数都正在被跟踪) rangeModule.queryRange(13, 15); 返回 false(未跟踪区间 [13, 15) 中像 14, 14.03, 14.17 这样的数字) rangeModule.queryRange(16, 17); 返回 true (尽管执行了删除操作,区间 [16, 17) 中的数字 16 仍然会被跟踪)提示:
1 <= left < right <= 109
- 在单个测试用例中,对
addRange
、queryRange
和removeRange
的调用总数不超过104
次
class RangeModule {
private TreeMap<Integer, Integer> intervals;
public RangeModule() {
intervals = new TreeMap<>();
}
public void addRange(int left, int right) {
// 找到可能重叠的区间
Integer start = intervals.floorKey(left);
Integer end = intervals.floorKey(right);
if (start != null && intervals.get(start) >= left) {
// 左边界可以和已有区间合并
left = start;
}
if (end != null && intervals.get(end) > right) {
// 右边界可以和已有区间合并
right = intervals.get(end);
}
// 删除范围内的所有区间
intervals.subMap(left, true, right, true).clear();
// 添加新的合并后的区间
intervals.put(left, right);
}
public boolean queryRange(int left, int right) {
// 找到左边最近的区间
Integer start = intervals.floorKey(left);
if (start == null) {
return false;
}
// 检查是否完全包含查询区间
return intervals.get(start) >= right;
}
public void removeRange(int left, int right) {
// 找到可能重叠的区间
Integer start = intervals.floorKey(left);
Integer end = intervals.floorKey(right);
// 处理右侧重叠
if (end != null && intervals.get(end) > right) {
intervals.put(right, intervals.get(end));
}
// 处理左侧重叠
if (start != null && intervals.get(start) > left) {
intervals.put(start, left);
}
// 删除范围内的所有区间
intervals.subMap(left, true, right, false).clear();
}
}
题目8:990. 等式方程的可满足性
给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程
equations[i]
的长度为4
,并采用两种不同的形式之一:"a==b"
或"a!=b"
。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回
true
,否则返回false
。
示例 1:
输入:["a==b","b!=a"] 输出:false 解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。示例 2:
输入:["b==a","a==b"] 输出:true 解释:我们可以指定 a = 1 且 b = 1 以满足满足这两个方程。示例 3:
输入:["a==b","b==c","a==c"] 输出:true示例 4:
输入:["a==b","b!=c","c==a"] 输出:false示例 5:
输入:["c==c","b==d","x!=z"] 输出:true提示:
1 <= equations.length <= 500
equations[i].length == 4
equations[i][0]
和equations[i][3]
是小写字母equations[i][1]
要么是'='
,要么是'!'
equations[i][2]
是'='
class Solution {
class UnionFind {
private int[] parent;
private int[] rank;
public UnionFind(int size) {
parent = new int[size];
rank = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
}
}
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
}
public boolean isConnected(int x, int y) {
return find(x) == find(y);
}
}
public boolean equationsPossible(String[] equations) {
// 创建并查集,26个小写字母
UnionFind uf = new UnionFind(26);
// 第一次遍历:处理所有相等关系
for (String equation : equations) {
if (equation.charAt(1) == '=') {
int x = equation.charAt(0) - 'a';
int y = equation.charAt(3) - 'a';
uf.union(x, y);
}
}
// 第二次遍历:检查所有不等关系
for (String equation : equations) {
if (equation.charAt(1) == '!') {
int x = equation.charAt(0) - 'a';
int y = equation.charAt(3) - 'a';
if (uf.isConnected(x, y)) {
return false;
}
}
}
return true;
}
}
题目9:224. 基本计算器
给你一个字符串表达式
s
,请你实现一个基本计算器来计算并返回它的值。注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如
eval()
。
示例 1:
输入:s = "1 + 1" 输出:2
示例 2:
输入:s = " 2-1 + 2 " 输出:3
示例 3:
输入:s = "(1+(4+5+2)-3)+(6+8)" 输出:23
提示:
1 <= s.length <= 3 * 105
s
由数字、'+'
、'-'
、'('
、')'
、和' '
组成s
表示一个有效的表达式- '+' 不能用作一元运算(例如, "+1" 和
"+(2 + 3)"
无效)- '-' 可以用作一元运算(即 "-1" 和
"-(2 + 3)"
是有效的)- 输入中不存在两个连续的操作符
- 每个数字和运行的计算将适合于一个有符号的 32位 整数
class Solution {
private int index = 0; // 用于遍历字符串的指针
public int calculate(String s) {
Stack<Integer> stack = new Stack<>();
int num = 0;
int sign = 1; // 1表示正号,-1表示负号
int result = 0;
while (index < s.length()) {
char c = s.charAt(index);
if (Character.isDigit(c)) {
// 处理多位数字
num = num * 10 + (c - '0');
} else if (c == '+') {
// 处理前一个数字
result += sign * num;
num = 0;
sign = 1;
} else if (c == '-') {
// 处理前一个数字
result += sign * num;
num = 0;
sign = -1;
} else if (c == '(') {
// 遇到左括号,递归计算括号内的值
index++; // 跳过左括号
int subResult = calculate(s);
result += sign * subResult;
sign = 1;
num = 0;
} else if (c == ')') {
// 遇到右括号,处理最后一个数字并返回
result += sign * num;
return result;
}
index++;
}
// 处理最后一个数字
result += sign * num;
return result;
}
}
题目10:54. 螺旋矩阵
给你一个
m
行n
列的矩阵matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5]示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 输出:[1,2,3,4,8,12,11,10,9,5,6,7]提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 10
-100 <= matrix[i][j] <= 100
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> result = new ArrayList<>();
if (matrix == null || matrix.length == 0) {
return result;
}
int top = 0;
int bottom = matrix.length - 1;
int left = 0;
int right = matrix[0].length - 1;
while (top <= bottom && left <= right) {
// 从左到右遍历上边
for (int i = left; i <= right; i++) {
result.add(matrix[top][i]);
}
top++;
// 从上到下遍历右边
for (int i = top; i <= bottom && left <= right; i++) {
result.add(matrix[i][right]);
}
right--;
// 从右到左遍历下边
for (int i = right; i >= left && top <= bottom; i--) {
result.add(matrix[bottom][i]);
}
bottom--;
// 从下到上遍历左边
for (int i = bottom; i >= top && left <= right; i--) {
result.add(matrix[i][left]);
}
left++;
}
return result;
}
}