系列汇总:《刷题系列汇总》
文章目录
——————《剑指offeer》———————
1. 重建二叉树(二叉树 数组 DFS)
- 题目描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列
{1,2,4,7,3,5,6,8}
和中序遍历序列{4,7,2,1,5,3,8,6}
,则重建二叉树并返回。 - 优秀思路:【递归】根据前序列确定根节点,再根据根节点在中序序列中的位置划分左、右子数,切割出对应的中序子序列和前序子序列作为参数,递归该过程。
import java.util.Arrays;
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
//边界条件
if(pre == null || in == null || pre.length == 0 || in.length == 0) return null;
TreeNode tree = new TreeNode(pre[0]);
for(int i = 0;i< in.length;i++){
if(in[i] == pre[0]){
tree.left = reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(in,0,i));
tree.right = reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,pre.length),Arrays.copyOfRange(in,i+1,in.length));
break;
}
}
return tree;
}
}
2. 平衡二叉树
- 题目描述:输入一棵二叉树,判断该二叉树是否是平衡二叉树。在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树。
平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 - 优秀思路:【递归】递归计算左右子数的高度,最后比较两边的高度差。
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null) return true;
int leftHeight = getHeight(root.left); // 左子树高度
int rightHeight = getHeight(root.right);// 右子树高度
if(Math.abs(leftHeight-rightHeight) <= 1) return true;
return false;
}
// 计算数的高度
private int getHeight(TreeNode node){
if(node == null) return 0;
int leftHeight = getHeight(node.left);
int rightHeight = getHeight(node.right);
// 核心:某子树的高度由长的一边决定
return 1 + (leftHeight > rightHeight ? leftHeight :rightHeight);
}
}
3. 矩阵中的路径
- 题目描述:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如下图矩阵中包含一条字符串
"bcced"
的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
- 我的思路(70% - 70%):【递归】首先找出所有的起始点,对每个起始点,判断当前起始点的未被访问的近邻是否存在=当前字符串首元素的,知道string长度为1(注意二维数组不能直接用"="赋值,否则一个改变另一个也会改变)
import java.util.*;
public class Solution {
public boolean hasPath (char[][] matrix, String word) {
if(matrix.length*matrix[0].length < word.length()) return false;
boolean[][] visited;
// 找到首字母在 matrix 中的位置
for(int i = 0;i < matrix.length;i++){
for(int j = 0;j < matrix[0].length;j++){
if(matrix[i][j] == word.charAt(0)){
if(word.length() >= 2){
// 这个得每次重新创建
visited = new boolean[matrix.length][matrix[0].length];
visited[i][j] = true;
if(move(matrix,visited,i,j,word.substring(1,word.length()))) return true;
}else{
return true;
}
}
}
}
return false;
}
// 根据当前位置判断其未被访问的近邻中是否存在符合要求的字母
private boolean move(char[][] matrix,boolean[][] visited,int curRow,int curCol,String restWord){
if(restWord == null) return false;
boolean[][] tempVisited;
String tempWord;
boolean tag1=false, tag2=false, tag3=false, tag4=false;
int flag = 0; //当前位置是否有未被访问的近邻 == restWord.charAt(0)
if(curCol+1 <= matrix[0].length-1){ // 可右移
if(!visited[curRow][curCol+1]){
if(matrix[curRow][curCol+1] == restWord.charAt(0)){
flag = 1;
if(restWord.length() >= 2){ // 还有未找到的字母
tempVisited = copy(visited);
tempVisited[curRow][curCol+1] = true;
tempWord = restWord.substring(1,restWord.length());
tag1 = move(matrix,tempVisited,curRow,curCol+1,tempWord);
}else{
return true;
}
}
}
}
if(curCol-1 >= 0){ // 可左移
if(!visited[curRow][curCol-1]){
if(matrix[curRow][curCol-1] == restWord.charAt(0)){
flag = 1;
if(restWord.length() >= 2){ // 还有未找到的字母
tempVisited = copy(visited);
tempVisited[curRow][curCol-1] = true;
tempWord = restWord.substring(1,restWord.length());
tag2 = move(matrix,tempVisited,curRow,curCol-1,tempWord);
}else{
return true;
}
}
}
}
if(curRow-1 >= 0){ // 可上移
if(!visited[curRow-1][curCol]){
if(matrix[curRow-1][curCol] == restWord.charAt(0)){
flag = 1;
if(restWord.length() >= 2){ // 还有未找到的字母
tempVisited = copy(visited);
tempVisited[curRow-1][curCol] = true;
tempWord = restWord.substring(1,restWord.length());
tag3 = move(matrix,tempVisited,curRow-1,curCol,tempWord);
}else{
return true;
}
}
}
}
if(curRow+1 <= matrix.length-1){ // 可下移
if(!visited[curRow+1][curCol]){
if(matrix[curRow+1][curCol] == restWord.charAt(0)){
flag = 1;
if(restWord.length() >= 2){ // 还有未找到的字母
tempVisited = copy(visited);
tempVisited[curRow+1][curCol] = true;
tempWord = restWord.substring(1,restWord.length());
tag4 = move(matrix,tempVisited,curRow+1,curCol,tempWord);
}else{
return true;
}
}
}
}
if(flag == 0) return false;
return tag1 || tag2 || tag3 || tag4; // 有一个路线走的通就行
}
// 二维数组复制
private boolean[][] copy(boolean[][] visited){
boolean[][] res = new boolean[visited.length][visited[0].length];
for(int i = 0;i < visited.length;i++){
for(int j = 0;j < visited[0].length;j++){
res[i][j] = visited[i][j];
}
}
return res;
}
}
- 优秀思路:思路类似,代码精简,不用上下左右每部分各写一个,写成一个即可
import java.util.*;
public class Solution {
public boolean hasPath (char[][] matrix, String word) {
if(matrix.length * matrix[0].length < word.length()) return false;
boolean[][] visited = new boolean[matrix.length][matrix[0].length];
for(int i = 0;i < matrix.length;i++){
for(int j = 0;j < matrix[0].length;j++){
if(matrix[i][j] == word.charAt(0)){ // 起点
if(move(matrix,visited,i,j,word)) return true;
}
}
}
return false;
}
// 根据当前位置判断其未被访问的近邻中是否存在符合要求的字母
private boolean move(char[][] matrix,boolean[][] visited,int curRow ,int curCol ,String restWord){
// 写成这种形式,以应对各种情况
if(curRow < 0||curCol < 0 || curRow >= matrix.length || curCol >= matrix[0].length || visited[curRow][curCol]) return false;
if(matrix[curRow][curCol] == restWord.charAt(0)){
if(restWord.length()==1) return true; // 比较到最后一位
else{ // 比较其他位
visited[curRow][curCol] = true;
// 上、下、左、右有一条线路走得通即可
if(move(matrix,visited,curRow+1,curCol,restWord.substring(1,restWord.length()))
|| move(matrix,visited,curRow-1,curCol,restWord.substring(1,restWord.length()))
|| move(matrix,visited,curRow,curCol+1,restWord.substring(1,restWord.length()))
|| move(matrix,visited,curRow,curCol-1,restWord.substring(1,restWord.length()))){// 有其中一个方向走的通
return true;
}else{ // 当前线路四个方向都走不通,恢复访问状态
visited[curRow][curCol] = false;
return false;
}
}
}else{
return false;
}
}
}
——————《LeectCode》———————
1. 岛屿数量
- 题目描述:给你一个由
'1'
(陆地)和'0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。 - 我的思路(优秀):【深度优先搜索】每发现一个新岛屿,就采用递归的方式标记其所有相连陆地
class Solution {
public int numIslands(char[][] grid) {
if(grid.length == 0 || grid[0].length == 0) return 0;
int count = 0; // 岛屿数量
boolean[][] vis = new boolean[grid.length][grid[0].length]; // 访问状态
for(int i = 0;i < grid.length;i++){
for(int j = 0;j < grid[0].length;j++){
if(grid[i][j]=='1' && !vis[i][j]){ // 发现新岛屿起点
count++;
vis = visUpdate(grid,vis,i,j);// 标记该岛屿所有位置
}
}
}
return count;
}
// 标记岛屿
private boolean[][] visUpdate(char[][] grid,boolean[][] vis,int i,int j){
if(i<0 || j<0 || i>=grid.length || j>= grid[0].length) return vis;
if(grid[i][j] == '1' && !vis[i][j]){ // 发现新的相连陆地
vis[i][j] = true;
// 继续寻找该陆地的相邻陆地
vis = visUpdate(grid,vis,i,j+1); // 右边
vis = visUpdate(grid,vis,i+1,j); // 下边
vis = visUpdate(grid,vis,i,j-1); // 左边
vis = visUpdate(grid,vis,i-1,j); // 上边
}
return vis;
}
}
- 优秀思路2(不如1):【广度优先搜索】主循环和DFS类似,不同点是在于搜索某岛屿边界的方法不同。借用一个队列
queue
,判断队列首部节点(i, j)
是否未越界且为 1:若是则置零(删除岛屿节点),并将此节点上下左右节点(i+1,j),(i-1,j),(i,j+1),(i,j-1)
加入队列;若不是则跳过此节点;循环pop
队列首节点,直到整个队列为空,此时已经遍历完此岛屿。
class Solution {
public int numIslands(char[][] grid) {
int count = 0;
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[0].length; j++) {
if(grid[i][j] == '1'){
bfs(grid, i, j);
count++;
}
}
}
return count;
}
private void bfs(char[][] grid, int i, int j){
Queue<int[]> list = new LinkedList<>();
list.add(new int[] { i, j });
while(!list.isEmpty()){ // 停止条件是删除完该岛屿所有陆地
int[] cur = list.remove(); // 移除当前陆地
i = cur[0]; j = cur[1]; // 并取出其位置
if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') { // 若其为陆地
grid[i][j] = '0'; // 删除该陆地
// 放入其4个邻居,重复上述过程
list.add(new int[] { i + 1, j });
list.add(new int[] { i - 1, j });
list.add(new int[] { i, j + 1 });
list.add(new int[] { i, j - 1 });
}
}
}
}
2. 二叉树的最大深度
- 题目描述:给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
- 优秀思路(我的):【深度优先搜索】递归
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
return Math.max(leftDepth,rightDepth) + 1;
}
}
3. 查找集群内的关键连接(困难)
- 题目描述:力扣数据中心有
n
台服务器,分别按从0
到n-1
的方式进行了编号。它们之间以「服务器到服务器」点对点的形式相互连接组成了一个内部集群,其中连接connections
是无向的。从形式上讲,connections[i] = [a, b]
表示服务器a 和 b
之间形成连接。任何服务器都可以直接或者间接地通过网络到达任何其他服务器。关键连接是在该集群中的重要连接,也就是说,假如我们将它移除,便会导致某些服务器无法访问其他服务器。请你以任意顺序返回该集群内的所有关键连接。 - 优秀思路:使用
tarjan
算法找出图中非强连通部分和强连通部分,过程中如果某连接两边的节点的id
不同,则加入ans
。参考:tarjan算法讲解视频
class Solution {
/* 参数定义在主函数中,可被各个子函数访问到 */
int time; // 时间戳
int[] visTime; // 节点的访问时间
int[] minTime; // 节点能回溯到的最早时间(相当于id,若该值相同则为同一ID)
List<List<Integer>> ans = new ArrayList<>(); // 结果
List<Integer>[] conn; // 存储服务器之间的连接关系(给出的格式太复杂了)
public List<List<Integer>> criticalConnections(int n, List<List<Integer>> connections) {
/* 参数初始化 */
conn = new ArrayList[n];
visTime = new int[n];
minTime = new int[n];
time = 1;
/* 找出服务器之间的连接关系并存储 */
for (int i = 0; i < n; i++) {
conn[i] = new ArrayList<>(); // 为每个服务器建立一个序列,方便add于其相连的服务器
}
for (List<Integer> connection : connections) {
int v1 = connection.get(0), v2 = connection.get(1); // 找出所有连接上的2个服务器
conn[v1].add(v2);
conn[v2].add(v1);
}
/* 遍历服务器0 */
dfs(0, -1);
return ans;
}
/* 遍历各服务器,寻找其连通关系 */
public void dfs(int cur, int pre) { // 无向图需要记录上个节点(pre),防止子节点访问父节点
visTime[cur] = minTime[cur] = time++; // 可连续赋值
for (int next : conn[cur]) { // 遍历可能访问的下个节点
if (next == pre) continue; // 如果访问的是父节点,跳过
if (visTime[next] == 0) { // 如果未访问(数组默认值为0)
dfs(next, cur);
minTime[cur] = Math.min(minTime[cur], minTime[next]); // 更新minTime,即ID
if (minTime[next] > visTime[cur]) { // 下一节点id不可回溯到当前节点
ans.add(Arrays.asList(cur, next));
}
} else { // 如果访问过
minTime[cur] = Math.min(minTime[cur], visTime[next]);
}
}
}
}
4.将有序数组转换为二叉搜索树
- 题目描述:给你一个整数数组 nums ,其中元素已经按升序排列,请你将其转换为一棵高度平衡二叉搜索树。高度平衡二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
- 优秀思路(我的):【递归】升序排列即为二叉搜索树的中序遍历(左根右),因此递归的寻找中间节点作为根节点,根节点左边的作为左子树,右边的为右子树
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
if(nums == null || nums.length == 0) return null;
int mid = nums.length/2;
TreeNode tree = new TreeNode(nums[mid]);
tree.left = sortedArrayToBST(Arrays.copyOfRange(nums,0,mid));
tree.right = sortedArrayToBST(Arrays.copyOfRange(nums,mid+1,nums.length));
return tree;
}
}
5. 删除无效的括号(困难)
- 题目描述:给你一个由若干括号和字母组成的字符串
s
,删除最小数量的无效括号,使得输入的字符串有效。返回所有可能的结果。答案可以按 任意顺序 返回。 - 优秀思路:判断字符串类型
- 如果
闭合括号>开始括号
,则递归逐个找到不符合条件的第一个闭合处,从0到第一个闭合处,每个闭合括号都进行删除尝试(这个区间的每个闭合括号都是有可能可以被删除的)。 - 如果
开始括号 > 闭合括号
,则交换两括号、颠倒字符串,从而又变成了第一种情况。
- 如果
class Solution {
public List<String> removeInvalidParentheses(String s) {
List<String> res = new ArrayList<>();
dfs(s, res, '(', ')', 0, 0);
return res;
}
// i 当前遍历到的字符位置
private void dfs(String s, List<String> res, char opening, char closing, int i, int lastRemoved) {
int count = 0;
// 找到第一个不合法的closing处
while (i < s.length() && count >= 0) { // 注意count<0会跳出
if (s.charAt(i) == opening) {
count++;
} else if (s.charAt(i) == closing) {
count--;
}
i++;
}
if (count < 0) { // 闭合括号更多,删除
for (int j = lastRemoved; j < i; j++) {
// 该括号为闭合括号 且 该括号要么位于首位,要么该括号前一位不是闭合括号
// 即 ① 闭合括号位于首位必删
// ② 为避免取得相同结果,连续几个闭合括号,删一个即可
if (s.charAt(j) == closing && (j == 0 || s.charAt(j - 1) != closing)) {
// i 即本次搜寻到的第一个无效括号处,i-1 是下次搜索无效括号的起点
// j 即本次删除无效闭合括号的地方,也是下次删除的起点
dfs(s.substring(0, j) + s.substring(j + 1), res, opening, closing, i - 1, j);
}
}
} else if (count > 0) { //开始括号更多,交换开始和闭合括号,并且颠倒字符串再来一遍
dfs(new StringBuilder(s).reverse().toString(), res, closing, opening, 0, 0);
} else { // 平衡,加入结果
if (opening == '(') { // 说明是此时为正向
res.add(s);
} else { // 反向
res.add(new StringBuilder(s).reverse().toString());
}
}
}
}
6. 字符串解码
- 题目描述:给定一个经过编码的字符串,返回它解码后的字符串。编码规则为:
k[encoded_string]
,表示其中方括号内部的encoded_string
正好重复k
次。注意k
保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数k
,例如不会出现像3a
或2[4]
的输入。 - 我的思路(7% - 25 %):需要反复的从头开始遍历,效率低
- 1、找到最里层完整码段(
数字 + [ + 字母 + ]
)先进行变换 - 2、将s更新为:
码段前 + 变换码段 +码段后
- 3、递归上述过程,直至s内无数字
- 1、找到最里层完整码段(
class Solution {
public String decodeString(String s) {
// 找到最里面的括号
int flag = 0; // 是否寻到完整码段:数字【字母】
StringBuilder strNum = new StringBuilder();
StringBuilder sGernerate = new StringBuilder();
int lenNum = 0; // 数字的位数
int endNum = 0; // 数字的结束位置
int index2 = 0; // ]的后一位置
int stateNum = 0;
String strChar = ""; // 存储每段字母串
for(int i = 0;i < s.length();i++){
flag = 0;
if(s.charAt(i) <= '9' && s.charAt(i) >= '0'){
strNum.append(s.charAt(i));
endNum = i;
lenNum++;
stateNum = 1;
}else if(s.charAt(i)=='['){
strChar = "";
for(int j = i+1;j < s.length();j++){
if((s.charAt(j) <= '9' && s.charAt(j) >= '0') || s.charAt(j)=='['){ // 遇到数字或者新括号作废
strNum.delete( 0, strNum.length());
lenNum = 0;
break;
}else if(s.charAt(j) <= 'z' && s.charAt(j) >= 'a'){
strChar += s.charAt(j);
}else if(s.charAt(j)==']'){
index2 = j+1;
flag = 1;
break;
}
}
}
// 根据【前数字重复该字符串
if(flag == 1){
sGernerate.delete( 0, sGernerate.length());
for(int ind = 0;ind<Integer.valueOf(strNum.toString());ind++){
sGernerate.append(strChar);
}
s = s.substring(0,endNum-lenNum+1) + sGernerate.toString() + s.substring(index2); //
lenNum = 0;
strNum.delete( 0, strNum.length());
}
}
if(stateNum == 0) return s; // 没有数字了
return decodeString(s);
}
}
- 优秀思路:递归
- 发现数字位:对数字为后面的字符串递归得到需要复制的字符串,对字符串进行复制
- 发现字母位:存储起来
- 返回 未复制的字母+字符串
class Solution {
String str;
int index;
public String decodeString(String s) {
str = s;
index = 0;
return getString();
}
private String getString(){
if(index == str.length() || str.charAt(index) == ']') return "";
char cur = str.charAt(index); //当前字符
int times = 1; // 复制倍数
String Res = "";
if(Character.isDigit(cur)){
times = getTimes(); //解析数字
index++; // 跳过左[
String tempString = getString(); //解析括号内部字符串,即需复制字符串
index++; // 跳过右[
while(times-- > 0){ //构造字符串
Res += tempString;
}
}else if(Character.isLetter(cur)){
Res = String.valueOf(str.charAt(index++)); // 速度比c+""快多了
}
return Res + getString(); // 返回注意
}
// 解析多位数字
private int getTimes(){
int num = 0;
while(index < str.length() && Character.isDigit(str.charAt(index))){
num = 10*num + str.charAt(index++) - '0';
}
return num;
}
}
7. 相同的树
- 题目描述:给你两棵二叉树的根节点
p
和q
,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 - 优秀思路(我的):【递归】递归寻找左右子树比较根节点
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null) return true;
if(p == null || q == null || p.val != q.val) return false;
boolean x1 = isSameTree(p.left,q.left);
boolean x2 = isSameTree(p.right,q.right);
return x1 && x2;
}
}
8. 从前序与中序遍历序列构造二叉树
- 题目描述:根据一棵树的前序遍历与中序遍历构造二叉树(树中无重复元素)
- 我的思路(7% - 8%):【递归】根据前序遍历找到根节点,根据根节点在中序遍历中的位置找到左子树和右子树
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || preorder.length==0 || inorder == null || inorder.length == 0) return null;
// 根据前序遍历找到根节点
TreeNode newNode = new TreeNode(preorder[0]);
for(int i = 0;i<inorder.length;i++){
if(inorder[i] == preorder[0]){
// 根据根节点在中序遍历中的位置找到左子树和右子树
newNode.left = buildTree(Arrays.copyOfRange(preorder,1,i+1),
Arrays.copyOfRange(inorder,0,i));
newNode.right = buildTree(Arrays.copyOfRange(preorder,i+1,preorder.length),
Arrays.copyOfRange(inorder,i+1,inorder.length));
}
}
return newNode;
}
}
- 优秀思路:思路和我的类似,主要不同是
- ① 因为得重复的寻找根节点,采用循环遍历法在中序遍历中寻找根节点效率太低,故建立
HashMap
存储各个节点的位置,从而方便查找。 - ② 事实上,我们不需要真的把
preorder
和inorder
切分了,只需要用分别用两个指针指向开头和结束位置即可。注意下边的两个指针指向的数组范围是包括左边界,不包括右边界。
class Solution {
HashMap<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length);
}
private TreeNode buildTreeHelper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
if (p_start == p_end) return null;
TreeNode newNode = new TreeNode(preorder[p_start]);
int i_rootInMid = map.get(preorder[p_start]); // 根节点在中序遍历中的位置
int lenLeft = i_rootInMid - i_start; // 本次的左子树长度
// 两个遍历的左右指标更新是最核心的!尤其是left部分
newNode.left = buildTreeHelper(preorder,p_start+1,p_start+lenLeft+1,inorder,i_start,i_rootInMid);
newNode.right = buildTreeHelper(preorder,p_start+lenLeft+1,p_end,inorder,i_rootInMid+1,i_end);
return newNode;
}
}
9. 岛屿的最大面积
- 题目描述:给定一个包含了一些
0
和1
的非空二维数组grid
。一个 岛屿 是由一些相邻的1
(代表土地) 构成的组合,这里的「相邻」要求两个1
必须在水平或者竖直方向上相邻。你可以假设grid
的四个边缘都被0
(代表水)包围着。找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为0
) - 我的思路(优秀 75%- 94%):定义访问状态,每找到一个没被搜索过的1(没搜索过是重点),沿其上下左右四个方向进行搜索
class Solution {
boolean[][] vis;
int area = 0; // 定义全局变量,节省储存空间
public int maxAreaOfIsland(int[][] grid) {
if(grid==null || grid.length == 0 || grid[0]==null ||grid[0].length == 0) return 0;
int maxArea = 0;
vis = new boolean[grid.length][grid[0].length];
for(int i = 0;i<grid.length;i++){
for(int j = 0;j<grid[0].length;j++){
if(grid[i][j]==1 && !vis[i][j]){ // 这里可加快效率
area = 0;
findIsland(grid,i,j);
maxArea = Math.max(maxArea,area);
}
}
}
return maxArea;
}
private void findIsland(int[][] grid,int i,int j){
if(i<0 || i>=grid.length || j<0 || j>=grid[0].length || vis[i][j] || grid[i][j] !=1 ) return;
area++;
vis[i][j] = true;
findIsland(grid,i+1,j); // 下
findIsland(grid,i,j+1); // 右
findIsland(grid,i-1,j); // 上
findIsland(grid,i,j-1); // 左
}
}
- 思路代码改进(100% - 80%):略去
vis
矩阵,直接用grid
兼职记录访问状态,从而减少了对vis
的操作时间和存储空间
class Solution {
int area = 0; // 定义全局变量,节省储存空间
public int maxAreaOfIsland(int[][] grid) {
if(grid==null || grid.length == 0 || grid[0]==null ||grid[0].length == 0) return 0;
int maxArea = 0;
for(int i = 0;i<grid.length;i++){
for(int j = 0;j<grid[0].length;j++){
if(grid[i][j]==1){ // 这里可加快效率
area = 0;
findIsland(grid,i,j);
maxArea = Math.max(maxArea,area);
}
}
}
return maxArea;
}
private void findIsland(int[][] grid,int i,int j){
if(i<0 || i>=grid.length || j<0 || j>=grid[0].length || grid[i][j] !=1) return;
area++;
grid[i][j] = 0;
findIsland(grid,i+1,j); // 下
findIsland(grid,i,j+1); // 右
findIsland(grid,i-1,j); // 上
findIsland(grid,i,j-1); // 左
}
}
10. 由斜杠划分区域(困难,优秀思路待补充)
- 题目描述:在由
1 x 1
方格组成的N x N
网格grid
中,每个1 x 1
方块由/、\
或空格构成。这些字符会将方块划分为一些共边的区域。(请注意,反斜杠字符是转义的,因此\
用 “\\
” 表示。)。返回区域的数目。 - 我的思路(5% - 5%):利用公共边对各区域进行搜索合并
- 为每条单位边分配id
- map记录每块方形区域(" “)或三角形区域(”“或”/")的边的id
- map2记录每条边相关的小块id:map2
- 根据map和map2构造dfs合并函数,递归进行区域搜索合并
class Solution {
public int regionsBySlashes(String[] grid) {
if(grid==null) return 0;
// 确定N的数量
int N = grid.length;
// 给N*N方格的内部的每条单位边赋id,并记录每个小区域的边界单位边id
HashMap<Integer,ArrayList<Integer>> map = new HashMap<Integer,ArrayList<Integer>>(); //
HashMap<Integer,ArrayList<Integer>> map2 = new HashMap<Integer,ArrayList<Integer>>();
int count = 0; // 小块数量
ArrayList<Integer> indexEdge;
ArrayList<Integer> indexPiece;
for(int i = 0;i<N;i++){
for(int j = 0;j<N;j++){
indexEdge = new ArrayList<Integer>();
indexPiece = new ArrayList<Integer>();
/* 空格:只有一个小方块,上下左右四条边id都放进去 */
if(grid[i].charAt(j)==' '){
map2 = myPut(map2,(N+1)*j+i,count);// 上
map2 = myPut(map2,(N+1)*j+i+1,count);// 下
map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+1,count); // 左
map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+2,count);// 右
indexEdge.add((N+1)*j+i); // 上
indexEdge.add((N+1)*j+i+1); // 下
indexEdge.add(((N+1)*N-1)+(N+1)*i+j+1); // 左
indexEdge.add(((N+1)*N-1)+(N+1)*i+j+2); // 右
map.put(count++,indexEdge);
}
/* /:两个小斜块 */
else if(grid[i].charAt(j)=='/'){
map2 = myPut(map2,(N+1)*j+i,count);// 上
map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+1,count); // 左
// 斜块1加入其上方和左方边界id
indexEdge.add((N+1)*j+i); // 上
indexEdge.add(((N+1)*N-1)+(N+1)*i+j+1); // 左
map.put(count++,indexEdge);
map2 = myPut(map2,(N+1)*j+i+1,count);// 下
map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+2,count);// 右
// 斜块2加入其右方和下方边界id
indexEdge = new ArrayList<Integer>();
indexEdge.add(((N+1)*N-1)+(N+1)*i+j+2); // 右
indexEdge.add((N+1)*j+i+1); // 下
map.put(count++,indexEdge);
}
/* /:两个小斜块 */
else if(grid[i].charAt(j)==92){
map2 = myPut(map2,(N+1)*j+i,count);// 上
map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+2,count);// 右
// 斜块1加入其上方和右方边界id
indexEdge.add((N+1)*j+i); // 上
indexEdge.add(((N+1)*N-1)+(N+1)*i+j+2); // 右
map.put(count++,indexEdge);
map2 = myPut(map2,(N+1)*j+i+1,count);// 下
map2 = myPut(map2,((N+1)*N-1)+(N+1)*i+j+1,count); // 左
// 斜块2加入其左方和下方边界id
indexEdge = new ArrayList<Integer>();
indexEdge.add(((N+1)*N-1)+(N+1)*i+j+1); // 左
indexEdge.add((N+1)*j+i+1); // 下
map.put(count++,indexEdge);
}
}
}
if(count<=2) return count;
// 寻找同一区域的小块
int[] vis = new int[count];
int countFinal = 0;
for(int i = 0;i<map.size();i++){
if(vis[i] == 0){
vis[i] = 1;
countFinal++;
for(Integer c1 : map.get(i)){// 遍历当前小块的边
for(Integer c2 : map2.get(c1)){ // 遍历当前小块的边的对应的小块
if(vis[c2] == 0){
merge(map,map2,vis,c2);
}
}
}
}
}
return countFinal;
}
// 根据map和map2合并小块
// map:记录每个小块的id
// map:记录每条边的小块
private void merge(HashMap<Integer,ArrayList<Integer>> map,HashMap<Integer,ArrayList<Integer>> map2,int[] vis,int cur){
// 根据map找到当前节点
for(Integer c:vis){
if(c==0){
if(vis[cur] == 0){
vis[cur] = 1;
for(Integer c1 : map.get(cur)){// 遍历当前小块的边
for(Integer c2 : map2.get(c1)){ // 遍历当前小块的边的对应的小块
if(vis[c2] == 0){
merge(map,map2,vis,c2);
}
}
}
}
}
}
return;
}
private HashMap<Integer,ArrayList<Integer>> myPut(HashMap<Integer,ArrayList<Integer>> map2,int key,int piece){
if(map2.size()==0 || !map2.containsKey(key)){
ArrayList<Integer> temp = new ArrayList<Integer>();
temp.add(piece);
map2.put(key,temp);
}else{
map2.get(key).add(piece);
}
return map2;
}
}