二叉树
1. 遍历 (Traversal)
-  
前序遍历 (Preorder Traversal): 先访问根节点,再访问左子树,最后访问右子树。
void preorderTraversal(Node root) { if (root == null) return; System.out.print(root.value + " "); preorderTraversal(root.left); preorderTraversal(root.right); } -  
中序遍历 (Inorder Traversal): 先访问左子树,再访问根节点,最后访问右子树。
void inorderTraversal(Node root) { if (root == null) return; inorderTraversal(root.left); System.out.print(root.value + " "); inorderTraversal(root.right); } -  
后序遍历 (Postorder Traversal): 先访问左子树,再访问右子树,最后访问根节点。
void postorderTraversal(Node root) { if (root == null) return; postorderTraversal(root.left); postorderTraversal(root.right); System.out.print(root.value + " "); } -  
层序遍历 (Level Order Traversal): 按层级顺序从上到下、从左到右访问节点,通常使用队列实现。
void levelOrderTraversal(Node root) { if (root == null) return; Queue<Node> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { Node current = queue.poll(); System.out.print(current.value + " "); if (current.left != null) queue.add(current.left); if (current.right != null) queue.add(current.right); } } 
2. 插入节点 (Insertion)
- 向二叉树中插入节点的操作,通常涉及递归地找到合适的空位置,然后将新节点插入其中。 
Node insert(Node root, int value) { if (root == null) { return new Node(value); } if (value < root.value) { root.left = insert(root.left, value); } else { root.right = insert(root.right, value); } return root; } 
3. 查找节点 (Search)
- 在二叉树中查找一个特定值,通常也使用递归或迭代的方法。 
boolean search(Node root, int value) { if (root == null) { return false; } if (root.value == value) { return true; } else if (value < root.value) { return search(root.left, value); } else { return search(root.right, value); } } 
4. 删除节点 (Deletion)
- 删除一个节点后,可能需要调整二叉树以保持其性质。删除操作通常要处理三种情况: 
  
- 删除的节点没有子节点。
 - 删除的节点有一个子节点。
 - 删除的节点有两个子节点(通常需要找到右子树中的最小值节点替代)。
 
Node delete(Node root, int value) { if (root == null) return null; if (value < root.value) { root.left = delete(root.left, value); } else if (value > root.value) { root.right = delete(root.right, value); } else { if (root.left == null) return root.right; if (root.right == null) return root.left; Node minNode = findMin(root.right); root.value = minNode.value; root.right = delete(root.right, minNode.value); } return root; } Node findMin(Node root) { while (root.left != null) { root = root.left; } return root; } 
5. 计算高度 (Height Calculation)
- 计算二叉树的高度,即从根节点到最远叶子节点的最长路径的节点数。  java 
复制代码
int height(Node root) { if (root == null) return 0; int leftHeight = height(root.left); int rightHeight = height(root.right); return Math.max(leftHeight, rightHeight) + 1; } 
6. 判断是否是二叉搜索树 (Check if a Tree is a Binary Search Tree)
例子:
假设你有一个树形结构,level 表示当前节点的层级:
markdown
复制代码
NodeValue 
在这个例子中," ".repeat(4) 生成了 4 个空格,然后与 "NodeValue" 连接,最终输出了带有 4 个空格缩进的节点值。
java
复制代码
int level = 2; System.out.println(" ".repeat(level * 2) + "NodeValue"); 
输出将是:
- 验证一棵树是否符合二叉搜索树的性质(即对于每个节点,其左子树的所有节点值都小于该节点值,右子树的所有节点值都大于该节点值)。
 -  
boolean isBST(Node root, Integer min, Integer max) { if (root == null) return true; if (min != null && root.value <= min) return false; if (max != null && root.value >= max) return false; return isBST(root.left, min, root.value) && isBST(root.right, root.value, max); }import java.util.Stack; class Node { int value; Node left, right; Node(int value) { this.value = value; left = right = null; } } public class BinaryTree { // 检查二叉树是否为 BST 的中序遍历方法 boolean isBST(Node root) { Stack<Node> stack = new Stack<>(); Node current = root; Integer prev = null; while (current != null || !stack.isEmpty()) { // 走到左子树的最下方 while (current != null) { stack.push(current); current = current.left; } current = stack.pop(); // 如果当前节点值小于等于前一个节点值,则不满足 BST 条件 if (prev != null && current.value <= prev) { return false; } // 更新 prev 为当前节点值 prev = current.value; // 处理右子树 current = current.right; } return true; } public static void main(String[] args) { BinaryTree tree = new BinaryTree(); Node root = new Node(10); root.left = new Node(5); root.right = new Node(20); root.left.left = new Node(3); root.left.right = new Node(7); if (tree.isBST(root)) { System.out.println("这棵树是二叉搜索树"); } else { System.out.println("这棵树不是二叉搜索树"); } } }在 Java 中,
" ".repeat(level * 2)是用来生成一个包含多个空格的字符串。这个方法的作用是重复特定数量的空格,并在输出中形成缩进效果。下面是详细解释:详细解释:
 -  
" ":- 这是一个包含单个空格字符的字符串。
 
 -  
repeat(int count):repeat(int count)是String类的一个方法,它将当前字符串重复count次,并返回一个新的字符串。- Java 11 及以上版本引入了这个方法。
 - 如果 
count是 0,则返回一个空字符串。 
 -  
level * 2:level是一个整数,表示树的当前层次(或深度)。level * 2表示将level乘以 2,得到重复空格的次数。- 这个倍数(2)可以调整,决定了每层缩进的程度。
 
 -  
组合:
- 当 
level为 1 时," ".repeat(level * 2)生成" "(两个空格)。 - 当 
level为 2 时," ".repeat(level * 2)生成" "(四个空格)。 - 依此类推,随着 
level增加,生成的空格数量也增加,这会使打印出来的树结构更清晰地表现出层次。 
 - 当 
 - 根节点 
level = 0,没有缩进。 - 第一层子节点 
level = 1,缩进两个空格。 - 第二层子节点 
level = 2,缩进四个空格。 
7. 求树中节点的个数 (Count Nodes)
- 计算二叉树中所有节点的数量。 
int countNodes(Node root) { if (root == null) return 0; return 1 + countNodes(root.left) + countNodes(root.right); } 
8. 求树中叶子节点的个数 (Count Leaf Nodes)
- 计算二叉树中所有叶子节点的数量。 
int countLeafNodes(Node root) { if (root == null) return 0; if (root.left == null && root.right == null) return 1; return countLeafNodes(root.left) + countLeafNodes(root.right); } 
这些是与二叉树操作相关的一些基本方法,涵盖了遍历、插入、查找、删除、计算高度、验证二叉搜索树、以及统计节点数等常见操作。在实际应用中,可以根据具体需求进行组合和扩展。
目标一:进行中序遍历

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;
    }
}
// structure class definition with inorderTraversal method
public class Solution{
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        TreeNode cur = null;
        while (root != null) {
            if (root.left != null) {
                //保证当前节点进入root左侧二叉树,然后一直向右走到尽头
                cur = root.left;
                while (cur.right != null && cur.right != root) {
                    cur = cur.right;
                }
                //让当前指针cur的右指针指向root即记录当前中间的节点
                if (cur.right == null) {
                    cur.right = root;
                    root = root.left;
                }
                //左子树遍历完成需要断开链接
                else {
                    list.add(root.val);
                    cur.right = null;
                    root = root.right;
                }
            } 
            //如果没有左孩子,就访问右孩子
            else {
                list.add(root.val);
                root = root.right;
            }
        }
        return list;
    }
    public static void main(String[] args) {
        // Construct a sample binary tree
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);
        root.right.left = new TreeNode(6);
        root.right.right = new TreeNode(7);
        // Create an instance of Structure
        Solution structure = new Solution();
        // Perform inorder traversal
        List<Integer> result = structure.inorderTraversal(root);
        // Print the result
        System.out.println("Inorder Traversal: " + result);
    }
} 
Inorder Traversal: [4, 2, 5, 1, 6, 3, 7]
Process finished with exit code 0 
public class Solution{
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        TreeNode cur = null;
        Deque<TreeNode> stk = new LinkedList<>();
        while (root!=null || !stk.isEmpty()){
            while (root!=null){
                stk.push(root);
                root = root.left;
            }
            root = stk.pop();
            list.add(root.val);
            root = root.right;
            
            //这里有一点设计的很巧妙,就是如果在左子树的最后一个节点,由于没有子节点,while循环不会执行,root会重新接受一个新弹出的上级元素并开始寻找右子节点
        }
        return list;
    }
} 
先序遍历
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
// 定义树节点
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}
public class Solution {
    // 前序遍历方法
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>(); // 存储遍历结果
        if (root == null) {
            return res; // 如果根节点为空,直接返回空列表
        }
        Deque<TreeNode> stack = new LinkedList<>(); // 创建栈来帮助遍历
        TreeNode node = root; // 从根节点开始遍历
        while (!stack.isEmpty() || node != null) { // 当栈不为空或当前节点不为空时
            while (node != null) { // 处理当前节点及其左子树
                res.add(node.val); // 访问当前节点,添加到结果列表中
                stack.push(node); // 将当前节点压入栈中
                node = node.left; // 继续遍历左子树
            }
            node = stack.pop(); // 从栈中弹出一个节点(左子树遍历完成)
            node = node.right; // 继续遍历右子树
        }
        return res; // 返回最终的前序遍历结果
    }
}
 
     1
     / \
    2   3
   / \
  4   5
- 初始状态:`node = 1`, `stack = []`, `res = []`
 - `node = 1`, `stack = [1]`, `res = [1]`
 - `node = 2`, `stack = [1, 2]`, `res = [1, 2]`
 - `node = 4`, `stack = [1, 2, 4]`, `res = [1, 2, 4]`
 - `node = null`, 弹出 `4`, `node = null`
 - 弹出 `2`, `node = 5`
 - `node = null`, `stack = [1, 5]`, `res = [1, 2, 4, 5]`
 - `node = null`, 弹出 `5`, `node = null`
 - 弹出 `1`, `node = 3`
 - `node = null`, `stack = []`, `res = [1, 2, 4, 5, 3]`
最终结果是 `[1, 2, 4, 5, 3]`。
力扣 3174 是个讨厌的字符串,看看相关方法吧,这里不想放
20, 有效括号的代码:这里主要是学习建立指定值的HashMap
class Solution {
    public boolean isValid(String s) {
        int n = s.length();
        if (n % 2 == 1)
        {
            return false;
        }
        Map<Character,Character> parirs = new HashMap<Character,Character>(){{
            put(')','(');
            put(']','[');
            put('}','{');
        }};
        Deque<Character> stack = new LinkedList<Character>();
        for(int i = 0; i<n ; i++){
            char ch = s.charAt(i);
            if(parirs.containsKey(ch)){
                if(stack.isEmpty() || stack.peek() != parirs.get(ch)){
                    return false;
                }
                stack.pop();
            }else{
                stack.push(ch);
            }
        }
        return stack.isEmpty();
    }
} 
在Java中,Deque(双端队列)和Stack(栈)都可以用于实现栈的功能,但它们在设计、使用和推荐的场景上有所不同。以下是它们的主要区别:
1. 设计意图和历史
- Stack:
Stack是Java中最早引入的栈类,继承自Vector。它是一个同步的类,也就是说,它在多线程环境中是线程安全的。但是,由于Stack继承了Vector,它带有Vector的一些不必要的方法,比如insertElementAt(),这与栈的基本操作无关。这使得Stack被认为是一种设计不良的选择。 - Deque:
Deque接口是在Java 1.6中引入的,代表了一个双端队列,可以用于栈的实现。Deque接口比Stack更灵活,可以作为栈(LIFO)或队列(FIFO)使用。常见的实现类如ArrayDeque和LinkedList。 
2. 推荐使用
- Stack:由于
Stack类继承自Vector,它的同步性带来了一些不必要的开销。在现代Java编程中,Stack通常被认为是过时的,不推荐在新代码中使用。 - Deque:
Deque接口是实现栈功能的推荐选择。尤其是ArrayDeque,它比Stack性能更高,也没有同步开销。在单线程环境中使用Deque的实现类来实现栈功能是更好的选择。 
3. 操作方法
- Stack: 
  
push(E item):将元素压入栈顶。pop():移除并返回栈顶的元素。peek():查看栈顶的元素,但不移除。
 - Deque: 
  
push(E item):将元素压入栈顶。pop():移除并返回栈顶的元素。peek():查看栈顶的元素,但不移除。addFirst(E e)/removeFirst():也可以用这些方法来模拟栈操作。
 
虽然两者的方法名称略有不同,但Deque的实现类(如ArrayDeque)提供的功能与Stack基本一致,并且在性能和设计上更优。
4. 性能
- Stack:由于是同步的,性能可能会比非同步的
Deque慢,尤其是在不需要线程安全的情况下。 - Deque:通常情况下,
ArrayDeque的性能要优于Stack,因为它是非同步的,并且没有Vector所带来的额外开销。 
结论:
在现代Java开发中,使用Deque(特别是ArrayDeque)来实现栈的功能是推荐的做法,而不是使用过时的Stack类。
Map<Character,Character> parirs = new HashMap<Character,Character>(){{
            put(')','(');
            put(']','[');
            put('}','{');
        }};这里的put方法里面内容可以写成这样吗put('(',')'); 
在你给出的代码中,put(')', '('); 的意思是将右括号 ) 作为键,左括号 ( 作为值存储在 Map 中。这个 Map 的用法是将闭合括号(右括号)映射到对应的开括号(左括号),通常用来验证字符串中括号是否匹配。
如果你写成 put('(', ')');,则会将左括号 ( 作为键,右括号 ) 作为值,这样的映射关系就反过来了。
区别在于使用场景:
-  
put(')', '(');:- 适用于你想通过右括号查找对应的左括号的场景。
 - 例如,用于检查括号匹配时,弹出栈顶元素并对比当前的右括号,看它是否与预期的左括号匹配。
 
 -  
put('(', ')');:- 适用于你想通过左括号查找对应的右括号的场景。
 - 例如,在某些括号匹配算法中,你可能需要根据当前左括号找到预期的右括号。
 
 
总结:
你可以写成 put('(', ')');,但这取决于你要实现的逻辑。如果你想通过左括号来查找右括号,这样写是合理的;如果你想通过右括号来查找左括号,那就应该用 put(')', '(');。
队列相关方法与函数
在算法题中,队列是一种非常常用的数据结构,主要用于广度优先搜索(BFS)、滑动窗口、任务调度等场景。以下是一些常用的队列相关方法和它们的典型应用:
1. 基本操作
offer(E e)/add(E e): 将元素添加到队列尾部。offer()在添加失败时返回false,而add()在失败时会抛出异常。- 用途:将新元素加入到处理队列中。
 
poll(): 移除并返回队列头部的元素,如果队列为空,则返回null。- 用途:处理或访问队列头部元素,同时将其从队列中移除。
 
remove(): 移除并返回队列头部的元素,如果队列为空,则抛出异常。- 用途:和 
poll()类似,但在队列为空时会抛出异常。 
- 用途:和 
 peek(): 查看队列头部的元素但不移除它,如果队列为空,则返回null。- 用途:查看即将处理的元素,而不移除它。
 
element(): 查看队列头部的元素但不移除它,如果队列为空,则抛出异常。- 用途:和 
peek()类似,但在队列为空时会抛出异常。 
- 用途:和 
 
2. 双端队列操作
addFirst(E e): 在队列的头部添加元素。addLast(E e): 在队列的尾部添加元素(等同于add(E e))。removeFirst(): 移除并返回队列头部的元素。removeLast(): 移除并返回队列尾部的元素。peekFirst(): 查看但不移除队列头部的元素。peekLast(): 查看但不移除队列尾部的元素。pollFirst(): 移除并返回队列头部的元素,如果队列为空,则返回null。pollLast(): 移除并返回队列尾部的元素,如果队列为空,则返回null。
双端队列(Deque)的这些操作在处理滑动窗口问题时非常有用,例如求最大值、最小值等。
3. 常见应用场景
-  
广度优先搜索(BFS):
- 使用队列来实现层次遍历或最短路径搜索。
 - 常用方法:
offer(E e),poll(),peek()。 
 -  
滑动窗口问题:
- 例如,滑动窗口最大值问题。
 - 常用方法:
addLast(E e),pollFirst(),peekFirst(),配合双端队列Deque使用。 
 -  
循环队列:
- 用来处理循环任务,模拟环形结构。
 - 常用方法:
offer(E e),poll(),peek()。 
 -  
任务调度:
- 使用队列管理任务执行的顺序,尤其是在处理依赖关系或资源调度问题时。
 - 常用方法:
offer(E e),poll(),peek()。 
 
4. 具体实现类
LinkedList: 实现了Queue和Deque接口,适合需要频繁插入和删除操作的场景。ArrayDeque: 比LinkedList更高效的实现,特别适合用于栈和队列操作。PriorityQueue: 基于优先级的队列,通常用于贪心算法、Dijkstra 算法等场景。
5. 线程安全的队列
ConcurrentLinkedQueue: 适用于多线程环境的无界非阻塞队列。BlockingQueue: 提供阻塞操作的队列接口,适用于生产者-消费者模式。
常用的实现:
ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueueDelayQueue
这些方法和场景涵盖了大多数常见的算法题中的队列使用情况。了解这些操作的具体应用有助于更好地解题和优化代码。
字符串相关
Java 中的 String 类是不可变的(即一旦创建,内容无法改变),并且是最常用的类之一。以下是 String 类的一些常用方法及其典型用途:
1. 字符串创建与初始化
String s = "Hello";: 直接赋值创建字符串。String s = new String("Hello");: 使用构造函数创建字符串。
2. 字符串长度
length(): 返回字符串的长度。 java复制代码
String s = "Hello"; int len = s.length(); // len = 5
3. 字符串比较
equals(Object obj): 比较字符串的内容是否相等(区分大小写)。s1.equals(s2);equalsIgnoreCase(String anotherString): 比较字符串的内容是否相等(不区分大小写)。s1.equalsIgnoreCase(s2);compareTo(String anotherString): 按字典顺序比较两个字符串。s1.compareTo(s2); // 返回负数、零或正数compareToIgnoreCase(String str): 按字典顺序比较两个字符串(不区分大小写)。s1.compareToIgnoreCase(s2);
4. 查找与定位
charAt(int index): 返回指定索引处的字符。char c = s.charAt(1); // c = 'e'indexOf(String str): 查找子字符串首次出现的位置。int index = s.indexOf("lo"); // index = 3lastIndexOf(String str): 查找子字符串最后一次出现的位置。int index = s.lastIndexOf("l"); // index = 3contains(CharSequence s): 判断字符串是否包含某子字符串。boolean contains = s.contains("He"); // contains = true
5. 字符串替换
replace(char oldChar, char newChar): 替换所有出现的指定字符。String replaced = s.replace('l', 'p'); // replaced = "Heppo"replace(CharSequence target, CharSequence replacement): 替换所有出现的指定字符串。String replaced = s.replace("He", "We"); // replaced = "Wello"replaceAll(String regex, String replacement): 使用正则表达式替换。String replaced = s.replaceAll("l+", "p"); // replaced = "Hepo"replaceFirst(String regex, String replacement): 使用正则表达式替换首个匹配。String replaced = s.replaceFirst("l+", "p"); // replaced = "Hepo"
6. 字符串分割
split(String regex): 按指定正则表达式分割字符串,返回一个数组。String[] parts = s.split("l"); // parts = ["He", "", "o"]split(String regex, int limit): 按指定正则表达式分割字符串,并限制结果数组的长度。String[] parts = s.split("l", 2); // parts = ["He", "lo"]
7. 字符串截取
substring(int beginIndex): 返回从指定索引开始到字符串末尾的子字符串。String sub = s.substring(2); // sub = "llo"substring(int beginIndex, int endIndex): 返回从beginIndex开始到endIndex(不包括)之间的子字符串。String sub = s.substring(1, 4); // sub = "ell"
8. 字符串转换
toLowerCase(): 将字符串转换为小写。String lower = s.toLowerCase(); // lower = "hello"toUpperCase(): 将字符串转换为大写。String upper = s.toUpperCase(); // upper = "HELLO"trim(): 去除字符串首尾的空白字符。String trimmed = s.trim(); // trimmed = "Hello"(如果 s 包含前后空格)valueOf(int i): 将其他数据类型转换为字符串。String str = String.valueOf(123); // str = "123"
9. 字符串拼接
concat(String str): 拼接两个字符串。String concatenated = s.concat(" World"); // concatenated = "Hello World"+运算符: 直接使用+拼接字符串。String concatenated = s + " World"; // concatenated = "Hello World"
10. 字符串判断
isEmpty(): 判断字符串是否为空(长度为0)。boolean empty = s.isEmpty(); // empty = falseisBlank(): 判断字符串是否为空或仅包含空白字符(Java 11+)。boolean blank = s.isBlank(); // blank = falsestartsWith(String prefix): 判断字符串是否以指定前缀开头。boolean starts = s.startsWith("He"); // starts = trueendsWith(String suffix): 判断字符串是否以指定后缀结尾。boolean ends = s.endsWith("lo"); // ends = true
11. 格式化字符串
String.format(String format, Object... args): 返回格式化后的字符串。 java复制代码
String formatted = String.format("Hello %s, your age is %d", "John", 25); // formatted = "Hello John, your age is 25"
12. 字符串内容判断
matches(String regex): 判断字符串是否匹配正则表达式。 java复制代码
boolean match = s.matches("[A-Za-z]+"); // match = true
13. 字符数组操作
toCharArray(): 将字符串转换为字符数组。 java复制代码
char[] chars = s.toCharArray(); // chars = ['H', 'e', 'l', 'l', 'o']getBytes(): 将字符串转换为字节数组。 java复制代码
byte[] bytes = s.getBytes(); // 根据平台的默认字符编码转换-  
char与字符串的区别 -  
char是单个字符:char类型表示单个字符,例如'A'、'3'、'@'。它只能存储一个字符,而不是一组字符。
 -  
字符串是多个字符的组合:
- 字符串通常是由多个字符组成的字符序列。字符串可以由多个 
char组成,通常用双引号括起来,例如"Hello"、"12345"、"@!$"。 - 在 C 语言中,字符串通常是由字符数组(例如 
char str[] = "Hello";)或指向字符的指针(例如char *str = "Hello";)表示。 
 - 字符串通常是由多个字符组成的字符序列。字符串可以由多个 
 
. - 力扣(LeetCode)
public class Solution {
    public int numIslands(char[][] grid) {
        // 将 char[][] 转换为 int[][]
        int[][] intGrid = new int[grid.length][grid[0].length];
        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[0].length; c++) {
                intGrid[r][c] = grid[r][c] - '0';  // '1' 转换为 1, '0' 转换为 0
            }
        }
        
        // 调用原有的 numIslands 方法
        return numIslands(intGrid);
    }
    private int numIslands(int[][] grid) {
        int islandCount = 0;
        
        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[0].length; c++) {
                if (grid[r][c] == 1) {
                    dfs(grid, r, c);
                    islandCount++;
                }
            }
        }
        
        return islandCount;
    }
    private void dfs(int[][] grid, int r, int c) {
        if (!inArea(grid, r, c)) {
            return;
        }
        if (grid[r][c] != 1) {
            return;
        }
        grid[r][c] = 2;
        
        dfs(grid, r - 1, c);
        dfs(grid, r + 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);
    }
    private boolean inArea(int[][] grid, int r, int c) {
        return 0 <= r && r < grid.length 
            	&& 0 <= c && c < grid[0].length;
    }
} 
                


















