🌈你好呀!我是 山顶风景独好
💝欢迎来到我的博客,很高兴能够在这里和您见面!
💝希望您在这里可以感受到一份轻松愉快的氛围!
💝不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
🚀 欢迎一起踏上探险之旅,挖掘无限可能,共同成长!
289.【中等】生命游戏
- 根据 百度百科 , 生命游戏 ,简称为 生命 ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。
- 给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1 即为 活细胞 (live),或 0 即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:
- 如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
- 如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
- 如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
- 如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
- 下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 m x n 网格面板 board 的当前状态,返回下一个状态。
示例 1:
- 输入:board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]]
- 输出:[[0,0,0],[1,0,1],[0,1,1],[0,1,0]]
示例 2:
- 输入:board = [[1,1],[1,0]]
- 输出:[[1,1],[1,1]]
解题思路
- 用末位表示当前状态,倒数第二位表示下一时刻状态
- 00:死 01:活变死 10:死变活 11:活
- 当细胞下一时刻活,让其位置的数字+2,死亡不需要操作
- 更新状态时不需要判断,只需要右移一位即可
class Solution {
// 游戏的主函数,负责更新棋盘上的生命状态
public void gameOfLife(int[][] board) {
// 获取棋盘的行数和列数
int n = board.length, m = board[0].length;
// 遍历棋盘上的每一个格子
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
// 计算当前格子周围活着的细胞数量
int cnt = count_life(board, i, j);
// 如果当前格子是活的(值为1)
if (board[i][j] == 1) {
// 根据游戏规则,如果周围活着的细胞数量是2或3,则下一轮它仍然是活的
// 这里通过位运算的方式,将当前格子的状态左移一位,以便在后续步骤中区分原始状态和下一轮的状态
if (cnt == 2 || cnt == 3) board[i][j] |= (1 << 1);
}
// 如果当前格子是死的(值为0)
else {
// 根据游戏规则,如果周围活着的细胞数量是3,则下一轮它会被激活
// 同样使用位运算的方式标记下一轮的状态
if (cnt == 3) board[i][j] |= (1 << 1);
}
}
}
// 遍历棋盘上的每一个格子,将标记的下一轮状态左移回原位,覆盖原始状态
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
board[i][j] >>= 1;
}
}
}
// 辅助函数,用于计算给定格子周围活着的细胞数量
public int count_life(int[][] board, int x, int y) {
int cnt = 0; // 计数器,用于记录活着的细胞数量
int n = board.length, m = board[0].length; // 棋盘的行数和列数
// 遍历当前格子周围的8个格子(上下左右及四个斜角)
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; ++dy) {
// 跳过当前格子本身
if (dx == 0 && dy == 0) continue;
// 计算当前格子周围的格子坐标
int cur_x = x + dx, cur_y = y + dy;
// 如果计算出的坐标超出了棋盘范围,或者该位置没有活着的细胞(值为0),则跳过
if (cur_x < 0 || cur_x >= n || cur_y < 0 || cur_y >= m || (board[cur_x][cur_y] & 1) == 0) continue;
// 如果周围格子有活着的细胞,则计数器加1
++cnt;
}
}
// 返回活着的细胞数量
return cnt;
}
}
49.【中等】字母异位词分组
- 给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
- 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 :
输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]]
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
// 创建一个HashMap,键是排序后的字符串(用于表示变位词),值是一个List,用于存放原始字符串
Map<String, List<String>> map = new HashMap<String, List<String>>();
// 遍历输入的字符串数组
for (String str : strs) {
// 将当前字符串转换为字符数组
char[] array = str.toCharArray();
// 对字符数组进行排序,排序后的字符数组将作为HashMap的键
Arrays.sort(array);
// 将排序后的字符数组转换回字符串,作为HashMap的键
String key = new String(array);
// 如果HashMap中已经存在该键,则获取其对应的值(List),否则返回一个新的空List
List<String> list = map.getOrDefault(key, new ArrayList<String>());
// 将当前字符串添加到对应的List中
list.add(str);
// 将更新后的List放回HashMap中,键保持不变
map.put(key, list);
}
// 将HashMap中的所有值(即List)放入一个新的ArrayList中,并返回这个二维列表
return new ArrayList<List<String>>(map.values());
}
}
128.【中等】最长连续序列
- 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
- 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
- 输入:nums = [100,4,200,1,3,2]
- 输出:4
- 解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
- 输入:nums = [0,3,7,2,5,8,4,6,0,1]
- 输出:9
class Solution {
public int longestConsecutive(int[] nums) {
// 创建一个HashSet来存储数组中的所有数字,HashSet的特点是不允许有重复元素,并且查找操作的时间复杂度接近O(1)
Set<Integer> num_set = new HashSet<Integer>();
// 遍历数组中的每个数字,将其添加到HashSet中
for (int num : nums) {
num_set.add(num);
}
// 初始化最长连续递增子序列的长度为0
int longestStreak = 0;
// 遍历HashSet中的每个数字(此时可以认为遍历的是去重后的数组)
for (int num : num_set) {
// 如果HashSet中不存在num-1(即num不是当前连续序列的最小值),则开始检查以num为起点的连续递增序列
if (!num_set.contains(num - 1)) {
// 当前检查的数字
int currentNum = num;
// 从num开始,当前连续递增序列的长度
int currentStreak = 1;
// 检查是否存在num+1, num+2, ..., 即检查连续递增序列
while (num_set.contains(currentNum + 1)) {
// 将当前数字向后移动一位
currentNum += 1;
// 连续递增序列长度加1
currentStreak += 1;
}
// 更新最长连续递增子序列的长度
longestStreak = Math.max(longestStreak, currentStreak);
}
}
// 返回最长连续递增子序列的长度
return longestStreak;
}
}
56.【中等】合并区间
- 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。
- 请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
- 输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
- 输出:[[1,6],[8,10],[15,18]]
- 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
- 输入:intervals = [[1,4],[4,5]]
- 输出:[[1,5]]
- 解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
class Solution {
// 合并重叠的区间
public int[][] merge(int[][] intervals) {
// 如果输入的区间数组为空,则直接返回一个空的二维数组
if (intervals.length == 0) {
return new int[0][2];
}
// 根据区间的起始位置对区间数组进行排序
Arrays.sort(intervals, new Comparator<int[]>() {
// 比较两个区间的起始位置
public int compare(int[] interval1, int[] interval2) {
return interval1[0] - interval2[0];
}
});
// 创建一个ArrayList来保存合并后的区间
List<int[]> merged = new ArrayList<int[]>();
// 遍历排序后的区间数组
for (int i = 0; i < intervals.length; ++i) {
// 当前区间的起始位置和结束位置
int L = intervals[i][0], R = intervals[i][1];
// 如果merged为空,或者当前区间的起始位置大于merged中最后一个区间的结束位置(即没有重叠)
if (merged.size() == 0 || merged.get(merged.size() - 1)[1] < L) {
// 将当前区间加入merged
merged.add(new int[]{L, R});
} else {
// 否则,当前区间与merged中最后一个区间有重叠,更新merged中最后一个区间的结束位置
// 取两个区间结束位置的最大值
merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], R);
}
}
// 将ArrayList转换为二维数组并返回
return merged.toArray(new int[merged.size()][]);
}
}
57.【中等】插入区间
- 给你一个 无重叠的 ,按照区间起始端点排序的区间列表 intervals,其中 intervals[i] = [starti, endi] 表示第 i 个区间的开始和结束,并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval = [start, end] 表示另一个区间的开始和结束。
- 在 intervals 中插入区间 newInterval,使得 intervals 依然按照 starti 升序排列,且区间之间不重叠(如果有必要的话,可以合并区间)。
- 返回插入之后的 intervals。
- 注意 你不需要原地修改 intervals。你可以创建一个新数组然后返回它。
示例 1:
- 输入:intervals = [[1,3],[6,9]], newInterval = [2,5]
- 输出:[[1,5],[6,9]]
示例 2:
- 输入:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
- 输出:[[1,2],[3,10],[12,16]]
解释:这是因为新的区间 [4,8] 与 [3,5],[6,7],[8,10] 重叠。
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
// 提取新区间的左右边界
int left = newInterval[0];
int right = newInterval[1];
// 用于标记新区间是否已经插入到结果列表中
boolean placed = false;
// 使用列表来动态存储合并后的区间
List<int[]> ansList = new ArrayList<int[]>();
// 遍历给定的区间列表
for (int[] interval : intervals) {
// 如果当前区间在新区间的右侧且与新区间无交集
if (interval[0] > right) {
// 如果新区间还未被放置到结果列表中,则先放置新区间
if (!placed) {
ansList.add(new int[]{left, right});
placed = true;
}
// 将当前区间添加到结果列表中
ansList.add(interval);
}
// 如果当前区间在新区间的左侧且与新区间无交集
else if (interval[1] < left) {
// 直接将当前区间添加到结果列表中
ansList.add(interval);
}
// 如果当前区间与新区间有交集
else {
// 更新新区间的左边界(取两者中的最小值)
left = Math.min(left, interval[0]);
// 更新新区间的右边界(取两者中的最大值)
right = Math.max(right, interval[1]);
}
}
// 如果遍历完所有区间后新区间还未被放置到结果列表中,则添加它
if (!placed) {
ansList.add(new int[]{left, right});
}
// 将结果列表转换为二维数组
int[][] ans = new int[ansList.size()][2];
for (int i = 0; i < ansList.size(); ++i) {
ans[i] = ansList.get(i);
}
// 返回合并后的区间数组
return ans;
}
}