1431.拥有最多糖果的孩子
class Solution {
/**
* 题目:拥有最多糖果的孩子
* 核心思路:
* 1. 先找到数组中糖果的最大值(即当前拥有最多糖果的数量)
* 2. 遍历每个孩子的糖果数,判断「当前糖果数 + 额外糖果数」是否 ≥ 最大值
* 3. 用布尔列表记录每个孩子的判断结果
* 时间复杂度:O(n),n为孩子数量,仅需两次线性遍历(找最大值+判断结果)
* 空间复杂度:O(n),用于存储结果列表(不计入输出的话为O(1))
*
* @param candies 每个孩子当前拥有的糖果数数组
* @param extraCandies 额外可分配的糖果数(所有孩子共享这部分额外糖果)
* @return 布尔列表:第i个元素为true表示第i个孩子获得额外糖果后拥有最多糖果
*/
public List<Boolean> kidsWithCandies(int[] candies, int extraCandies) {
// 记录孩子的总数(数组长度)
int n = candies.length;
// 初始化当前最大糖果数为0,用于后续遍历求解最大值
int maxCandies = 0;
// 第一遍遍历:找到数组中最大的糖果数(当前拥有最多糖果的数量)
for (int i = 0; i < n; ++i) {
// 每次比较当前孩子的糖果数与已记录的最大值,更新最大值
maxCandies = Math.max(maxCandies, candies[i]);
}
// 初始化结果列表:ArrayList是List接口的高效实现,适合动态添加元素
List<Boolean> ret = new ArrayList<Boolean>();
// 第二遍遍历:判断每个孩子获得额外糖果后是否能拥有最多糖果
for (int i = 0; i < n; ++i) {
// 核心判断:当前糖果数 + 额外糖果数 ≥ 最大值 → 该孩子能拥有最多糖果
// 直接将布尔判断结果添加到列表中
ret.add(candies[i] + extraCandies >= maxCandies);
}
// 返回最终的判断结果列表
return ret;
}
}
605.种花问题
class Solution {
/**
* 题目:种花问题
* 核心规则:花不能种植在相邻地块(1和1不能相邻),花坛初始状态已满足该规则
* 核心思路(贪心):
* 1. 遍历花坛,记录每个已种花地块(1)的位置,计算相邻花之间的空地块(0)能种多少花
* 2. 处理花坛两端的空地块(开头无花、结尾无花的情况)
* 3. 总可种花数量 ≥ n 则返回true,否则返回false
* 时间复杂度:O(m),m为花坛长度,仅需一次线性遍历
* 空间复杂度:O(1),仅用常数额外空间
*
* @param flowerbed 花坛数组(0=空地块,1=已种花)
* @param n 想要种植的花的数量
* @return 是否能在不打破规则的情况下种n朵花
*/
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int count = 0; // 记录最多能种植的花的数量
int m = flowerbed.length; // 花坛的总长度
int prev = -1; // 记录上一个已种花地块的索引(初始为-1,表示未遇到任何花)
// 遍历整个花坛,计算相邻花之间的可种花数量
for (int i = 0; i < m; i++) {
// 遇到已种花的地块(1),计算当前花与上一个花之间的空地块能种花的数量
if (flowerbed[i] == 1) {
// 情况1:prev=-1(当前是第一个遇到的花)
// 此时空地块是从花坛开头(索引0)到当前花的前一个位置(i-1)
// 可种花数量 = 空地块长度 / 2(例如:0、0、0 可种1朵;0、0 可种1朵;0 可种0朵)
if (prev < 0) {
count += i / 2;
} else {
// 情况2:prev≠-1(之前遇到过花)
// 空地块长度 = 当前花索引 - 上一个花索引 - 1(中间的空地块)
// 可种花数量 = (空地块长度 - 1) / 2(需减去1避免与两边花相邻,例如:0、0、0 可种1朵;0、0、0、0 可种1朵)
count += (i - prev - 2) / 2;
}
// 更新上一个花的索引为当前花的位置
prev = i;
}
}
// 遍历结束后,处理花坛末尾的空地块(最后一个花之后到花坛结束的区域)
if (prev < 0) {
// 情况1:花坛中没有任何花(prev始终为-1)
// 可种花数量 = (花坛长度 + 1) / 2(例如:长度3可种2朵;长度2可种1朵;长度1可种1朵)
count += (m + 1) / 2;
} else {
// 情况2:花坛中有花,处理最后一个花之后的空地块
// 空地块长度 = 花坛长度 - 最后一个花的索引 - 1
// 可种花数量 = 空地块长度 / 2(例如:0、0 可种1朵;0、0、0 可种1朵;0 可种0朵)
count += (m - prev - 1) / 2;
}
// 若最多可种花数量 ≥ 想要种的n朵,返回true;否则返回false
return count >= n;
}
}
345.反转字符串中的元音字母
aeiou
class Solution {
/**
* 题目:反转字符串中的元音字母
* 核心思路:用双指针从字符串两端向中间遍历,
* 左指针找左侧元音,右指针找右侧元音,找到后交换两者,
* 直到指针相遇(完成所有元音反转)
* 时间复杂度:O(n),n为字符串长度,每个字符仅遍历一次(双指针总移动次数为n)
* 空间复杂度:O(n),将字符串转为字符数组(String不可变,需数组辅助修改)
*
* @param s 输入字符串(包含大小写字母及其他可打印ASCII字符)
* @return 反转元音后的字符串
*/
public String reverseVowels(String s) {
// 字符串长度(避免重复调用length()方法)
int n = s.length();
// 将字符串转为字符数组:String是不可变对象,数组可直接修改字符(核心操作载体)
char[] arr = s.toCharArray();
// 双指针初始化:i从左(0索引)开始,j从右(n-1索引)开始
int i = 0, j = n - 1;
// 双指针遍历终止条件:左指针 >= 右指针(所有元音已处理)
while (i < j) {
// 左指针向右移动:跳过非元音字符,直到找到元音或指针越界
// 条件1:i < n(防止指针超出数组左边界)
// 条件2:!isVowel(arr[i])(当前字符不是元音)
while (i < n && !isVowel(arr[i])) {
++i; // 左指针右移
}
// 右指针向左移动:跳过非元音字符,直到找到元音或指针越界
// 条件1:j > 0(防止指针超出数组右边界)
// 条件2:!isVowel(arr[j])(当前字符不是元音)
while (j > 0 && !isVowel(arr[j])) {
--j; // 右指针左移
}
// 若此时左指针仍在右指针左侧:说明找到一对元音,执行交换
if (i < j) {
swap(arr, i, j); // 交换左右指针指向的元音字符
++i; // 左指针右移,继续找下一个元音
--j; // 右指针左移,继续找下一个元音
}
}
// 将修改后的字符数组转回字符串,返回结果
return new String(arr);
}
/**
* 辅助函数:判断字符是否为元音字母(含大小写)
* 元音字母定义:a、e、i、o、u、A、E、I、O、U
*
* @param ch 待判断的字符
* @return true表示是元音,false表示非元音
*/
public boolean isVowel(char ch) {
// 利用字符串indexOf()方法:存在则返回索引(>=0),不存在返回-1
// "aeiouAEIOU"是所有元音的集合,直接判断字符是否在该字符串中
return "aeiouAEIOU".indexOf(ch) >= 0;
}
/**
* 辅助函数:交换字符数组中两个索引位置的字符
*
* @param arr 字符数组
* @param i 第一个字符的索引
* @param j 第二个字符的索引
*/
public void swap(char[] arr, int i, int j) {
char temp = arr[i]; // 临时变量存储arr[i]的值
arr[i] = arr[j]; // 将arr[j]的值赋给arr[i]
arr[j] = temp; // 将临时存储的arr[i]值赋给arr[j]
}
}
151.反转字符串中的单词
import java.util.Arrays;
import java.util.List;
import java.util.Collections;
class Solution {
/**
* 题目:反转字符串中的单词
* 核心要求:
* 1. 反转单词顺序(而非反转单词内部字符)
* 2. 去除前导、尾随空格
* 3. 单词间仅保留单个空格(合并多余空格)
* 核心思路:利用Java字符串API简化操作,三步完成:
* 1. 去除首尾空格 → 2. 按任意长度空格分割单词 → 3. 反转单词列表 → 4. 用单个空格拼接单词
* 时间复杂度:O(n),n为字符串长度(trim、split、reverse、join均为线性时间操作)
* 空间复杂度:O(n),用于存储分割后的单词列表(进阶可优化为O(1)原地解法,但该解法简洁高效)
*
* @param s 输入字符串(可能含前导/尾随空格、单词间多空格)
* @return 单词顺序反转、格式规范的结果字符串
*/
public String reverseWords(String s) {
// 第一步:去除字符串开头和末尾的所有空白字符(解决前导/尾随空格问题)
// trim() 作用:删除字符串前后的Unicode空白字符(包括空格、制表符等,本题仅需处理空格)
s = s.trim();
// 第二步:按「连续空白字符」分割字符串,得到单词列表(解决单词间多空格问题)
// 正则表达式 \\s+:匹配一个或多个空白字符(空格、制表符等,本题对应多个空格)
// Arrays.asList():将分割后的字符串数组转为List,方便后续反转
List<String> wordList = Arrays.asList(s.split("\\s+"));
// 第三步:反转单词列表的顺序(核心操作,实现单词顺序颠倒)
// Collections.reverse():原地反转List(修改原List顺序,无需额外空间)
Collections.reverse(wordList);
// 第四步:将反转后的单词列表用「单个空格」拼接为字符串(保证单词间仅一个空格)
// String.join(separator, list):将集合元素按指定分隔符拼接为字符串
return String.join(" ", wordList);
}
}
334.递增的三元子序列
class Solution {
/**
* 题目:递增的三元子序列
* 核心要求:判断数组中是否存在 i<j<k 且 nums[i]<nums[j]<nums[k] 的三元组
* 核心思路(预处理+中心判断):
* 1. 预处理左最小数组:leftMin[i] 表示 nums[0..i] 中的最小值(即 j 左侧的最小元素)
* 2. 预处理右最大数组:rightMax[i] 表示 nums[i..n-1] 中的最大值(即 j 右侧的最大元素)
* 3. 遍历每个中间位置 j(1<=j<=n-2),判断是否满足 leftMin[j-1] < nums[j] < rightMax[j+1]
* 满足则存在递增三元子序列(i=leftMin[j-1]的对应索引,k=rightMax[j+1]的对应索引)
* 时间复杂度:O(n),三次线性遍历(左最小+右最大+中心判断)
* 空间复杂度:O(n),存储两个预处理数组(进阶可优化为 O(1) 贪心解法)
*
* @param nums 输入整数数组(可能含正负值,长度最大 5e5)
* @return 是否存在递增三元子序列
*/
public boolean increasingTriplet(int[] nums) {
int n = nums.length;
// 边界条件:数组长度小于3,不可能存在三元子序列,直接返回false
if (n < 3) {
return false;
}
// 左最小数组:leftMin[i] 存储 nums[0] 到 nums[i] 中的最小值
// 作用:快速查询任意位置 j 左侧(0..j-1)的最小元素,判断是否存在 nums[i] < nums[j]
int[] leftMin = new int[n];
leftMin[0] = nums[0]; // 第一个元素的左侧最小就是自身(无左侧元素)
// 从左到右遍历,递推计算左最小数组
for (int i = 1; i < n; i++) {
// 第i位的左最小 = 前一位的左最小 和 当前元素的较小值
leftMin[i] = Math.min(leftMin[i - 1], nums[i]);
}
// 右最大数组:rightMax[i] 存储 nums[i] 到 nums[n-1] 中的最大值
// 作用:快速查询任意位置 j 右侧(j+1..n-1)的最大元素,判断是否存在 nums[k] > nums[j]
int[] rightMax = new int[n];
rightMax[n - 1] = nums[n - 1]; // 最后一个元素的右侧最大就是自身(无右侧元素)
// 从右到左遍历,递推计算右最大数组
for (int i = n - 2; i >= 0; i--) {
// 第i位的右最大 = 后一位的右最大 和 当前元素的较大值
rightMax[i] = Math.max(rightMax[i + 1], nums[i]);
}
// 遍历每个可能的中间位置 j(j必须有左侧和右侧元素,即 1<=j<=n-2)
for (int i = 1; i < n - 1; i++) {
// 核心判断:j左侧的最小元素 < nums[j] < j右侧的最大元素
// 若满足,则存在 i<j<k 使得 nums[i]=leftMin[j-1] < nums[j] < rightMax[j+1]=nums[k]
if (nums[i] > leftMin[i - 1] && nums[i] < rightMax[i + 1]) {
return true; // 找到符合条件的三元组,直接返回true
}
}
// 遍历完所有中间位置都不满足,返回false
return false;
}
}