一、力扣
1、只出现一次的数字
class Solution {
public int singleNumber(int[] nums) {
int res=0;
for(int x:nums){
res^=x;
}
return res;
}
}
2、全排列 II
class Solution {
List<List<Integer>> res;
List<Integer> path;
int[] nums;
int n;
public List<List<Integer>> permuteUnique(int[] nums) {
res=new ArrayList<>();
path=new ArrayList<>();
Arrays.sort(nums);
this.nums=nums;
n=nums.length;
int[] vis=new int[n];
dfs(vis);
return res;
}
public void dfs(int[] vis){
if(path.size()==n){
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<n;i++){
if(vis[i]==1) continue;
if(i>0&&nums[i]==nums[i-1]&&vis[i-1]==0) continue;
path.add(nums[i]);
vis[i]=1;
dfs(vis);
vis[i]=0;
path.removeLast();
}
}
}
3、移掉 K 位数字-单调栈
class Solution {
public String removeKdigits(String num, int k) {
// 使用双端队列模拟单调栈,存储最终结果的数字字符
ArrayDeque<Character> stack = new ArrayDeque<>();
int count = 0; // 记录已删除的数字个数
for (int i = 0; i < num.length(); i++) {
char temp = num.charAt(i);
// 当还有删除机会,且栈非空,且栈顶元素比当前字符大时
// 弹出栈顶元素(贪心策略:移除高位较大数,使整体更小)
while (count < k && !stack.isEmpty() && stack.peek() > temp) {
stack.pop();
count++;
}
stack.push(temp);
}
// 处理剩余删除次数(例如输入序列本身递增的情况)
while (count < k) {
stack.pop();
count++;
}
// 构建结果字符串,跳过前导零
StringBuilder res = new StringBuilder();
while (!stack.isEmpty()) {
char temp = stack.pollLast(); // 栈底到栈顶是数字的正确顺序
// 跳过前导零,直到遇到第一个非零字符或结果为空
if (temp == '0' && res.length() == 0)
continue;
res.append(temp);
}
// 处理结果全为零的特殊情况
if (res.length() == 0)
res.append('0');
return res.toString();
}
}
4、去除重复字母-单调栈
class Solution {
public String removeDuplicateLetters(String s) {
// 统计每个字符在字符串中剩余的出现次数
int[] count = new int[26];
for (var e : s.toCharArray()) {
count[e - 'a']++;
}
// 记录当前已使用的字符,避免重复
Set<Character> set = new HashSet<>();
// 使用栈来维护结果字符串的字符顺序
ArrayDeque<Character> stack = new ArrayDeque<>();
for (int i = 0; i < s.length(); i++) {
char temp = s.charAt(i);
// 当前字符未被使用过时进行处理
if (!set.contains(temp)) {
// 当栈不为空,且栈顶字符大于当前字符,尝试弹出栈顶以优化字典序
while (!stack.isEmpty() && stack.peek() > temp) {
char out = stack.peek();
// 若栈顶字符在后续字符串中仍存在,则可以弹出(后面还能添加回来)
if (count[out - 'a'] > 0) {
stack.pop();
set.remove(out); // 从已用集合中移除
} else {
break; // 栈顶字符后续不再出现,无法弹出,保持当前字符顺序
}
}
// 将当前字符入栈并标记为已使用
stack.push(temp);
set.add(temp);
}
// 处理当前字符后,剩余的该字符数量减1
count[temp - 'a']--;
}
// 将栈中字符逆序拼接成最终结果(栈顶到栈底为逆序)
StringBuilder res = new StringBuilder();
while (!stack.isEmpty()) {
res.append(stack.pollLast());
}
return res.toString();
}
}
5、零钱兑换
dp[i][j]为以0-i的硬币实现总和为j的最少硬币数。
class Solution {
public int coinChange(int[] coins, int amount) {
int n=coins.length;
int[][] dp=new int[n+1][amount+1];
for(int i=0;i<=n;i++){
Arrays.fill(dp[i],Integer.MAX_VALUE/2);
}
dp[0][0]=0;
for(int j=0;j<=amount;j++){
for(int i=0;i<n;i++){
if(j<coins[i]){
dp[i+1][j]=dp[i][j];
}else{
dp[i+1][j]=Math.min(dp[i][j],dp[i+1][j-coins[i]]+1);
}
}
}
return dp[n][amount]==Integer.MAX_VALUE/2?-1:dp[n][amount];
}
}
6、打家劫舍 II
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
// 情况1:偷第一个房子,因此不能偷最后一个。计算从第2个到倒数第2个的最大值,并加上第一个房子
// 情况2:不偷第一个房子,计算从第1个到最后一个的最大值
// 取两种情况的最大值
return Math.max(nums[0] + count(nums, 2, n - 2), count(nums, 1, n - 1));
}
public int count(int[] nums, int left, int right) {
int pre2 = 0; // 上上最大金额
int pre1 = 0; // 上个最大金额
for (int i = left; i <= right; i++) {
// 当前房屋的最大金额:选择偷(上上个 + 当前金额)或不偷(上个)的较大值
int ans = Math.max(pre1, pre2 + nums[i]);
// 更新状态:前一个不偷的状态变为前一个偷的状态,当前状态变为新计算的ans
pre2 = pre1;
pre1 = ans;
}
return pre1; // 返回最终计算的最大金额
}
}
二、语雀-Tomcat
1、Tomcat的启动流程是怎样的?
2、Tomcat中有哪些类加载器?
3、为什么Tomcat可以把线程数设置为200,而不是N+1?
✅为什么Tomcat可以把线程数设置为200,而不是N+1?
4、Tomcat处理请求的过程是怎么样的?
5、过滤器和拦截器的区别是什么?
6、介绍一下Tomcat的IO模型?
7、Tomcat与Web服务器(如Apache)之间的关系是什么?
✅Tomcat与Web服务器(如Apache)之间的关系是什么?