文章目录
36. (必备)二叉树高频题目上
package class036;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
// 二叉树的层序遍历
// 测试链接 : https://leetcode.cn/problems/binary-tree-level-order-traversal/
public class Code01_LevelOrderTraversal {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交时把方法名改为levelOrder,此方法为普通bfs,此题不推荐
public static List<List<Integer>> levelOrder1(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
if (root != null) {
Queue<TreeNode> queue = new LinkedList<>();
HashMap<TreeNode, Integer> levels = new HashMap<>();
queue.add(root);
levels.put(root, 0);
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
int level = levels.get(cur);
if (ans.size() == level) {
ans.add(new ArrayList<>());
}
ans.get(level).add(cur.val);
if (cur.left != null) {
queue.add(cur.left);
levels.put(cur.left, level + 1);
}
if (cur.right != null) {
queue.add(cur.right);
levels.put(cur.right, level + 1);
}
}
}
return ans;
}
// 如果测试数据量变大了就修改这个值
public static int MAXN = 2001;
public static TreeNode[] queue = new TreeNode[MAXN];
public static int l, r;
// 提交时把方法名改为levelOrder,此方法为每次处理一层的优化bfs,此题推荐
public static List<List<Integer>> levelOrder2(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
if (root != null) {
l = r = 0;
queue[r++] = root;
while (l < r) { // 队列里还有东西
int size = r - l;
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < size; i++) {
TreeNode cur = queue[l++];
list.add(cur.val);
if (cur.left != null) {
queue[r++] = cur.left;
}
if (cur.right != null) {
queue[r++] = cur.right;
}
}
ans.add(list);
}
}
return ans;
}
}
package class036;
import java.util.ArrayList;
import java.util.List;
// 二叉树的锯齿形层序遍历
// 测试链接 : https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/
public class Code02_ZigzagLevelOrderTraversal {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交以下的方法
// 用每次处理一层的优化bfs就非常容易实现
// 如果测试数据量变大了就修改这个值
public static int MAXN = 2001;
public static TreeNode[] queue = new TreeNode[MAXN];
public static int l, r;
public static List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
if (root != null) {
l = r = 0;
queue[r++] = root;
// false 代表从左往右
// true 代表从右往左
boolean reverse = false;
while (l < r) {
int size = r - l;
ArrayList<Integer> list = new ArrayList<Integer>();
// reverse == false, 左 -> 右, l....r-1, 收集size个
// reverse == true, 右 -> 左, r-1....l, 收集size个
// 左 -> 右, i = i + 1
// 右 -> 左, i = i - 1
for (int i = reverse ? r - 1 : l, j = reverse ? -1 : 1, k = 0; k < size; i += j, k++) {
TreeNode cur = queue[i];
list.add(cur.val);
}
for (int i = 0; i < size; i++) {
TreeNode cur = queue[l++];
if (cur.left != null) {
queue[r++] = cur.left;
}
if (cur.right != null) {
queue[r++] = cur.right;
}
}
ans.add(list);
reverse = !reverse;
}
}
return ans;
}
}
package class036;
// 二叉树的最大特殊宽度
// 测试链接 : https://leetcode.cn/problems/maximum-width-of-binary-tree/
public class Code03_WidthOfBinaryTree {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交以下的方法
// 用每次处理一层的优化bfs就非常容易实现
// 如果测试数据量变大了就修改这个值
public static int MAXN = 3001;
public static TreeNode[] nq = new TreeNode[MAXN];
public static int[] iq = new int[MAXN];
public static int l, r;
public static int widthOfBinaryTree(TreeNode root) {
int ans = 1;
l = r = 0;
nq[r] = root;
iq[r++] = 1;
while (l < r) {
int size = r - l;
ans = Math.max(ans, iq[r - 1] - iq[l] + 1);
for (int i = 0; i < size; i++) {
TreeNode node = nq[l];
int id = iq[l++];
if (node.left != null) {
nq[r] = node.left;
iq[r++] = id * 2;
}
if (node.right != null) {
nq[r] = node.right;
iq[r++] = id * 2 + 1;
}
}
}
return ans;
}
}
package class036;
// 求二叉树的最大、最小深度
public class Code04_DepthOfBinaryTree {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 测试链接 : https://leetcode.cn/problems/maximum-depth-of-binary-tree/
public static int maxDepth(TreeNode root) {
return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
// 测试链接 : https://leetcode.cn/problems/minimum-depth-of-binary-tree/
public int minDepth(TreeNode root) {
if (root == null) {
// 当前的树是空树
return 0;
}
if (root.left == null && root.right == null) {
// 当前root是叶节点
return 1;
}
int ldeep = Integer.MAX_VALUE;
int rdeep = Integer.MAX_VALUE;
if (root.left != null) {
ldeep = minDepth(root.left);
}
if (root.right != null) {
rdeep = minDepth(root.right);
}
return Math.min(ldeep, rdeep) + 1;
}
}
package class036;
// 二叉树先序序列化和反序列化
// 测试链接 : https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/
public class Code05_PreorderSerializeAndDeserialize {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int v) {
val = v;
}
}
// 二叉树可以通过先序、后序或者按层遍历的方式序列化和反序列化
// 但是,二叉树无法通过中序遍历的方式实现序列化和反序列化
// 因为不同的两棵树,可能得到同样的中序序列,即便补了空位置也可能一样。
// 比如如下两棵树
// __2
// /
// 1
// 和
// 1__
// \
// 2
// 补足空位置的中序遍历结果都是{ null, 1, null, 2, null}
// 提交这个类
public class Codec {
public String serialize(TreeNode root) {
StringBuilder builder = new StringBuilder();
f(root, builder);
return builder.toString();
}
void f(TreeNode root, StringBuilder builder) {
if (root == null) {
builder.append("#,");
} else {
builder.append(root.val + ",");
f(root.left, builder);
f(root.right, builder);
}
}
public TreeNode deserialize(String data) {
String[] vals = data.split(",");
cnt = 0;
return g(vals);
}
// 当前数组消费到哪了
public static int cnt;
TreeNode g(String[] vals) {
String cur = vals[cnt++];
if (cur.equals("#")) {
return null;
} else {
TreeNode head = new TreeNode(Integer.valueOf(cur));
head.left = g(vals);
head.right = g(vals);
return head;
}
}
}
}
package class036;
// 二叉树按层序列化和反序列化
// 测试链接 : https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/
public class Code06_LevelorderSerializeAndDeserialize {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int v) {
val = v;
}
}
// 提交这个类
// 按层序列化
public class Codec {
public static int MAXN = 10001;
public static TreeNode[] queue = new TreeNode[MAXN];
public static int l, r;
public String serialize(TreeNode root) {
StringBuilder builder = new StringBuilder();
if (root != null) {
builder.append(root.val + ",");
l = 0;
r = 0;
queue[r++] = root;
while (l < r) {
root = queue[l++];
if (root.left != null) {
builder.append(root.left.val + ",");
queue[r++] = root.left;
} else {
builder.append("#,");
}
if (root.right != null) {
builder.append(root.right.val + ",");
queue[r++] = root.right;
} else {
builder.append("#,");
}
}
}
return builder.toString();
}
public TreeNode deserialize(String data) {
if (data.equals("")) {
return null;
}
String[] nodes = data.split(",");
int index = 0;
TreeNode root = generate(nodes[index++]);
l = 0;
r = 0;
queue[r++] = root;
while (l < r) {
TreeNode cur = queue[l++];
cur.left = generate(nodes[index++]);
cur.right = generate(nodes[index++]);
if (cur.left != null) {
queue[r++] = cur.left;
}
if (cur.right != null) {
queue[r++] = cur.right;
}
}
return root;
}
private TreeNode generate(String val) {
return val.equals("#") ? null : new TreeNode(Integer.valueOf(val));
}
}
}
package class036;
import java.util.HashMap;
// 利用先序与中序遍历序列构造二叉树
// 测试链接 : https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
public class Code07_PreorderInorderBuildBinaryTree {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int v) {
val = v;
}
}
// 提交如下的方法
public static TreeNode buildTree(int[] pre, int[] in) {
if (pre == null || in == null || pre.length != in.length) {
return null;
}
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < in.length; i++) {
map.put(in[i], i);
}
return f(pre, 0, pre.length - 1, in, 0, in.length - 1, map);
}
public static TreeNode f(int[] pre, int l1, int r1, int[] in, int l2, int r2, HashMap<Integer, Integer> map) {
if (l1 > r1) {
return null;
}
TreeNode head = new TreeNode(pre[l1]);
if (l1 == r1) {
return head;
}
int k = map.get(pre[l1]);
// pre : l1(........)[.......r1]
// in : (l2......)k[........r2]
// (...)是左树对应,[...]是右树的对应
head.left = f(pre, l1 + 1, l1 + k - l2, in, l2, k - 1, map);
head.right = f(pre, l1 + k - l2 + 1, r1, in, k + 1, r2, map);
return head;
}
}
package class036;
// 验证完全二叉树
// 测试链接 : https://leetcode.cn/problems/check-completeness-of-a-binary-tree/
public class Code08_CompletenessOfBinaryTree {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交以下的方法
// 如果测试数据量变大了就修改这个值
public static int MAXN = 101;
public static TreeNode[] queue = new TreeNode[MAXN];
public static int l, r;
public static boolean isCompleteTree(TreeNode h) {
if (h == null) {
return true;
}
l = r = 0;
queue[r++] = h;
// 是否遇到过左右两个孩子不双全的节点
boolean leaf = false;
while (l < r) {
h = queue[l++];
if ((h.left == null && h.right != null) || (leaf && (h.left != null || h.right != null))) {
return false;
}
if (h.left != null) {
queue[r++] = h.left;
}
if (h.right != null) {
queue[r++] = h.right;
}
if (h.left == null || h.right == null) {
leaf = true;
}
}
return true;
}
}
package class036;
// 求完全二叉树的节点个数
// 测试链接 : https://leetcode.cn/problems/count-complete-tree-nodes/
public class Code09_CountCompleteTreeNodes {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交如下的方法
public static int countNodes(TreeNode head) {
if (head == null) {
return 0;
}
return f(head, 1, mostLeft(head, 1));
}
// cur : 当前来到的节点
// level : 当前来到的节点在第几层
// h : 整棵树的高度,不是cur这棵子树的高度
// 求 : cur这棵子树上有多少节点
public static int f(TreeNode cur, int level, int h) {
if (level == h) {
return 1;
}
if (mostLeft(cur.right, level + 1) == h) {
// cur右树上的最左节点,扎到了最深层
return (1 << (h - level)) + f(cur.right, level + 1, h);
} else {
// cur右树上的最左节点,没扎到最深层
return (1 << (h - level - 1)) + f(cur.left, level + 1, h);
}
}
// 当前节点是cur,并且它在level层
// 返回从cur开始不停往左,能扎到几层
public static int mostLeft(TreeNode cur, int level) {
while (cur != null) {
level++;
cur = cur.left;
}
return level - 1;
}
}
37. (必备)二叉树高频题目下
package class037;
// 普通二叉树上寻找两个节点的最近公共祖先
// 测试链接 : https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/
public class Code01_LowestCommonAncestor {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交如下的方法
public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) {
// 遇到空,或者p,或者q,直接返回
return root;
}
TreeNode l = lowestCommonAncestor(root.left, p, q);
TreeNode r = lowestCommonAncestor(root.right, p, q);
if (l != null && r != null) {
// 左树也搜到,右树也搜到,返回root
return root;
}
if (l == null && r == null) {
// 都没搜到返回空
return null;
}
// l和r一个为空,一个不为空
// 返回不空的那个
return l != null ? l : r;
}
}
package class037;
// 搜索二叉树上寻找两个节点的最近公共祖先
// 测试链接 : https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/
public class Code02_LowestCommonAncestorBinarySearch {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交如下的方法
public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// root从上到下
// 如果先遇到了p,说明p是答案
// 如果先遇到了q,说明q是答案
// 如果root在p~q的值之间,不用管p和q谁大谁小,只要root在中间,那么此时的root就是答案
// 如果root在p~q的值的左侧,那么root往右移动
// 如果root在p~q的值的右侧,那么root往左移动
while (root.val != p.val && root.val != q.val) {
if (Math.min(p.val, q.val) < root.val && root.val < Math.max(p.val, q.val)) {
break;
}
root = root.val < Math.min(p.val, q.val) ? root.right : root.left;
}
return root;
}
package class037;
import java.util.ArrayList;
import java.util.List;
// 收集累加和等于aim的所有路径
// 测试链接 : https://leetcode.cn/problems/path-sum-ii/
public class Code03_PathSumII {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交如下的方法
public static List<List<Integer>> pathSum(TreeNode root, int aim) {
List<List<Integer>> ans = new ArrayList<>();
if (root != null) {
List<Integer> path = new ArrayList<>();
f(root, aim, 0, path, ans);
}
return ans;
}
public static void f(TreeNode cur, int aim, int sum, List<Integer> path, List<List<Integer>> ans) {
if (cur.left == null && cur.right == null) {
// 叶节点
if (cur.val + sum == aim) {
path.add(cur.val);
copy(path, ans);
path.remove(path.size() - 1);
}
} else {
// 不是叶节点
path.add(cur.val);
if (cur.left != null) {
f(cur.left, aim, sum + cur.val, path, ans);
}
if (cur.right != null) {
f(cur.right, aim, sum + cur.val, path, ans);
}
path.remove(path.size() - 1);
}
}
public static void copy(List<Integer> path, List<List<Integer>> ans) {
List<Integer> copy = new ArrayList<>();
for (Integer num : path) {
copy.add(num);
}
ans.add(copy);
}
}
package class037;
// 验证平衡二叉树
// 测试链接 : https://leetcode.cn/problems/balanced-binary-tree/
public class Code04_BalancedBinaryTree {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交如下的方法
public static boolean balance;
public static boolean isBalanced(TreeNode root) {
// balance是全局变量,所有调用过程共享
// 所以每次判断开始时,设置为true
balance = true;
height(root);
return balance;
}
// 一旦发现不平衡,返回什么高度已经不重要了
public static int height(TreeNode cur) {
if (!balance || cur == null) {
return 0;
}
int lh = height(cur.left);
int rh = height(cur.right);
if (Math.abs(lh - rh) > 1) {
balance = false;
}
return Math.max(lh, rh) + 1;
}
}
package class037;
// 验证搜索二叉树
// 测试链接 : https://leetcode.cn/problems/validate-binary-search-tree/
public class Code05_ValidateBinarySearchTree {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交以下的方法
public static int MAXN = 10001;
public static TreeNode[] stack = new TreeNode[MAXN];
public static int r;
// 提交时改名为isValidBST
public static boolean isValidBST1(TreeNode head) {
if (head == null) {
return true;
}
TreeNode pre = null;
r = 0;
while (r > 0 || head != null) {
if (head != null) {
stack[r++] = head;
head = head.left;
} else {
head = stack[--r];
if (pre != null && pre.val >= head.val) {
return false;
}
pre = head;
head = head.right;
}
}
return true;
}
public static long min, max;
// 提交时改名为isValidBST
public static boolean isValidBST2(TreeNode head) {
if (head == null) {
min = Long.MAX_VALUE;
max = Long.MIN_VALUE;
return true;
}
boolean lok = isValidBST2(head.left);
long lmin = min;
long lmax = max;
boolean rok = isValidBST2(head.right);
long rmin = min;
long rmax = max;
min = Math.min(Math.min(lmin, rmin), head.val);
max = Math.max(Math.max(lmax, rmax), head.val);
return lok && rok && lmax < head.val && head.val < rmin;
}
}
package class037;
// 修剪搜索二叉树
// 测试链接 : https://leetcode.cn/problems/trim-a-binary-search-tree/
public class Code06_TrimBinarySearchTree {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交以下的方法
// [low, high]
public static TreeNode trimBST(TreeNode cur, int low, int high) {
if (cur == null) {
return null;
}
if (cur.val < low) {
return trimBST(cur.right, low, high);
}
if (cur.val > high) {
return trimBST(cur.left, low, high);
}
// cur在范围中
cur.left = trimBST(cur.left, low, high);
cur.right = trimBST(cur.right, low, high);
return cur;
}
}
package class037;
// 二叉树打家劫舍问题
// 测试链接 : https://leetcode.cn/problems/house-robber-iii/
public class Code07_HouseRobberIII {
// 不提交这个类
public static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
}
// 提交如下的方法
public static int rob(TreeNode root) {
f(root);
return Math.max(yes, no);
}
// 全局变量,完成了X子树的遍历,返回之后
// yes变成,X子树在偷头节点的情况下,最大的收益
public static int yes;
// 全局变量,完成了X子树的遍历,返回之后
// no变成,X子树在不偷头节点的情况下,最大的收益
public static int no;
public static void f(TreeNode root) {
if (root == null) {
yes = 0;
no = 0;
} else {
int y = root.val;
int n = 0;
f(root.left);
y += no;
n += Math.max(yes, no);
f(root.right);
y += no;
n += Math.max(yes, no);
yes = y;
no = n;
}
}
}
38. (必备)常见经典递归过程解析
package class038;
import java.util.HashSet;
// 字符串的全部子序列
// 子序列本身是可以有重复的,只是这个题目要求去重
// 测试链接 : https://www.nowcoder.com/practice/92e6247998294f2c933906fdedbc6e6a
public class Code01_Subsequences {
public static String[] generatePermutation1(String str) {
char[] s = str.toCharArray();
HashSet<String> set = new HashSet<>();
f1(s, 0, new StringBuilder(), set);
int m = set.size();
String[] ans = new String[m];
int i = 0;
for (String cur : set) {
ans[i++] = cur;
}
return ans;
}
// s[i...],之前决定的路径path,set收集结果时去重
public static void f1(char[] s, int i, StringBuilder path, HashSet<String> set) {
if (i == s.length) {
set.add(path.toString());
} else {
path.append(s[i]); // 加到路径中去
f1(s, i + 1, path, set);
path.deleteCharAt(path.length() - 1); // 从路径中移除
f1(s, i + 1, path, set);
}
}
public static String[] generatePermutation2(String str) {
char[] s = str.toCharArray();
HashSet<String> set = new HashSet<>();
f2(s, 0, new char[s.length], 0, set);
int m = set.size();
String[] ans = new String[m];
int i = 0;
for (String cur : set) {
ans[i++] = cur;
}
return ans;
}
public static void f2(char[] s, int i, char[] path, int size, HashSet<String> set) {
if (i == s.length) {
set.add(String.valueOf(path, 0, size));
} else {
path[size] = s[i];
f2(s, i + 1, path, size + 1, set);
f2(s, i + 1, path, size, set);
}
}
}
package class038;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
// 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的组合
// 答案 不能 包含重复的组合。返回的答案中,组合可以按 任意顺序 排列
// 注意其实要求返回的不是子集,因为子集一定是不包含相同元素的,要返回的其实是不重复的组合
// 比如输入:nums = [1,2,2]
// 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
// 测试链接 : https://leetcode.cn/problems/subsets-ii/
public class Code02_Combinations {
public static List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
Arrays.sort(nums);
f(nums, 0, new int[nums.length], 0, ans);
return ans;
}
public static void f(int[] nums, int i, int[] path, int size, List<List<Integer>> ans) {
if (i == nums.length) {
ArrayList<Integer> cur = new ArrayList<>();
for (int j = 0; j < size; j++) {
cur.add(path[j]);
}
ans.add(cur);
} else {
// 下一组的第一个数的位置
int j = i + 1;
while (j < nums.length && nums[i] == nums[j]) {
j++;
}
// 当前数x,要0个
f(nums, j, path, size, ans);
// 当前数x,要1个、要2个、要3个...都尝试
for (; i < j; i++) {
path[size++] = nums[i];
f(nums, j, path, size, ans);
}
}
}
}
package class038;
import java.util.ArrayList;
import java.util.List;
// 没有重复项数字的全排列
// 测试链接 : https://leetcode.cn/problems/permutations/
public class Code03_Permutations {
public static List<List<Integer>> permute(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
f(nums, 0, ans);
return ans;
}
public static void f(int[] nums, int i, List<List<Integer>> ans) {
if (i == nums.length) {
List<Integer> cur = new ArrayList<>();
for (int num : nums) {
cur.add(num);
}
ans.add(cur);
} else {
for (int j = i; j < nums.length; j++) {
swap(nums, i, j);
f(nums, i + 1, ans);
swap(nums, i, j); // 特别重要,课上进行了详细的图解
}
}
}
public static void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
public static void main(String[] args) {
int[] nums = { 1, 2, 3 };
List<List<Integer>> ans = permute(nums);
for (List<Integer> list : ans) {
for (int num : list) {
System.out.print(num + " ");
}
System.out.println();
}
}
}
package class038;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
// 有重复项数组的去重全排列
// 测试链接 : https://leetcode.cn/problems/permutations-ii/
public class Code04_PermutationWithoutRepetition {
public static List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
f(nums, 0, ans);
return ans;
}
public static void f(int[] nums, int i, List<List<Integer>> ans) {
if (i == nums.length) {
List<Integer> cur = new ArrayList<>();
for (int num : nums) {
cur.add(num);
}
ans.add(cur);
} else {
HashSet<Integer> set = new HashSet<>();
for (int j = i; j < nums.length; j++) {
// nums[j]没有来到过i位置,才会去尝试
if (!set.contains(nums[j])) {
set.add(nums[j]);
swap(nums, i, j);
f(nums, i + 1, ans);
swap(nums, i, j);
}
}
}
}
public static void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
package class038;
import java.util.Stack;
// 用递归函数逆序栈
public class Code05_ReverseStackWithRecursive {
public static void reverse(Stack<Integer> stack) {
if (stack.isEmpty()) {
return;
}
int num = bottomOut(stack);
reverse(stack);
stack.push(num);
}
// 栈底元素移除掉,上面的元素盖下来
// 返回移除掉的栈底元素
public static int bottomOut(Stack<Integer> stack) {
int ans = stack.pop();
if (stack.isEmpty()) {
return ans;
} else {
int last = bottomOut(stack);
stack.push(ans);
return last;
}
}
public static void main(String[] args) {
Stack<Integer> stack = new Stack<Integer>();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
reverse(stack);
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}
}
package class038;
import java.util.Stack;
// 用递归函数排序栈
// 栈只提供push、pop、isEmpty三个方法
// 请完成无序栈的排序,要求排完序之后,从栈顶到栈底从小到大
// 只能使用栈提供的push、pop、isEmpty三个方法、以及递归函数
// 除此之外不能使用任何的容器,数组也不行
// 就是排序过程中只能用:
// 1) 栈提供的push、pop、isEmpty三个方法
// 2) 递归函数,并且返回值最多为单个整数
public class Code06_SortStackWithRecursive {
public static void sort(Stack<Integer> stack) {
int deep = deep(stack);
while (deep > 0) {
int max = max(stack, deep);
int k = times(stack, deep, max);
down(stack, deep, max, k);
deep -= k;
}
}
// 返回栈的深度
// 不改变栈的数据状况
public static int deep(Stack<Integer> stack) {
if (stack.isEmpty()) {
return 0;
}
int num = stack.pop();
int deep = deep(stack) + 1;
stack.push(num);
return deep;
}
// 从栈当前的顶部开始,往下数deep层
// 返回这deep层里的最大值
public static int max(Stack<Integer> stack, int deep) {
if (deep == 0) {
return Integer.MIN_VALUE;
}
int num = stack.pop();
int restMax = max(stack, deep - 1);
int max = Math.max(num, restMax);
stack.push(num);
return max;
}
// 从栈当前的顶部开始,往下数deep层,已知最大值是max了
// 返回,max出现了几次,不改变栈的数据状况
public static int times(Stack<Integer> stack, int deep, int max) {
if (deep == 0) {
return 0;
}
int num = stack.pop();
int restTimes = times(stack, deep - 1, max);
int times = restTimes + (num == max ? 1 : 0);
stack.push(num);
return times;
}
// 从栈当前的顶部开始,往下数deep层,已知最大值是max,出现了k次
// 请把这k个最大值沉底,剩下的数据状况不变
public static void down(Stack<Integer> stack, int deep, int max, int k) {
if (deep == 0) {
for (int i = 0; i < k; i++) {
stack.push(max);
}
} else {
int num = stack.pop();
down(stack, deep - 1, max, k);
if (num != max) {
stack.push(num);
}
}
}
// 为了测试
// 生成随机栈
public static Stack<Integer> randomStack(int n, int v) {
Stack<Integer> ans = new Stack<Integer>();
for (int i = 0; i < n; i++) {
ans.add((int) (Math.random() * v));
}
return ans;
}
// 为了测试
// 检测栈是不是从顶到底依次有序
public static boolean isSorted(Stack<Integer> stack) {
int step = Integer.MIN_VALUE;
while (!stack.isEmpty()) {
if (step > stack.peek()) {
return false;
}
step = stack.pop();
}
return true;
}
// 为了测试
public static void main(String[] args) {
Stack<Integer> test = new Stack<Integer>();
test.add(1);
test.add(5);
test.add(4);
test.add(5);
test.add(3);
test.add(2);
test.add(3);
test.add(1);
test.add(4);
test.add(2);
sort(test);
while (!test.isEmpty()) {
System.out.println(test.pop());
}
// 随机测试
int N = 20;
int V = 20;
int testTimes = 20000;
System.out.println("测试开始");
for (int i = 0; i < testTimes; i++) {
int n = (int) (Math.random() * N);
Stack<Integer> stack = randomStack(n, V);
sort(stack);
if (!isSorted(stack)) {
System.out.println("出错了!");
break;
}
}
System.out.println("测试结束");
}
}
package class038;
// 打印n层汉诺塔问题的最优移动轨迹
public class Code07_TowerOfHanoi {
public static void hanoi(int n) {
if (n > 0) {
f(n, "左", "右", "中");
}
}
public static void f(int i, String from, String to, String other) {
if (i == 1) {
System.out.println("移动圆盘 1 从 " + from + " 到 " + to);
} else {
f(i - 1, from, other, to);
System.out.println("移动圆盘 " + i + " 从 " + from + " 到 " + to);
f(i - 1, other, to, from);
}
}
public static void main(String[] args) {
int n = 3;
hanoi(n);
}
}
39. (必备)嵌套类问题的递归解题套路
package class039;
import java.util.ArrayList;
// 含有嵌套的表达式求值
// 测试链接 : https://leetcode.cn/problems/basic-calculator-iii/
public class Code01_BasicCalculatorIII {
public static int calculate(String str) {
where = 0;
return f(str.toCharArray(), 0);
}
public static int where;
// s[i....]开始计算,遇到字符串终止 或者 遇到)停止
// 返回 : 自己负责的这一段,计算的结果
// 返回之间,更新全局变量where,为了上游函数知道从哪继续!
public static int f(char[] s, int i) {
int cur = 0;
ArrayList<Integer> numbers = new ArrayList<>();
ArrayList<Character> ops = new ArrayList<>();
while (i < s.length && s[i] != ')') {
if (s[i] >= '0' && s[i] <= '9') {
cur = cur * 10 + s[i++] - '0';
} else if (s[i] != '(') {
// 遇到了运算符 + - * /
push(numbers, ops, cur, s[i++]);
cur = 0;
} else {
// i (.....)
// 遇到了左括号!
cur = f(s, i + 1);
i = where + 1;
}
}
push(numbers, ops, cur, '+');
where = i;
return compute(numbers, ops);
}
public static void push(ArrayList<Integer> numbers, ArrayList<Character> ops, int cur, char op) {
int n = numbers.size();
if (n == 0 || ops.get(n - 1) == '+' || ops.get(n - 1) == '-') {
numbers.add(cur);
ops.add(op);
} else {
int topNumber = numbers.get(n - 1);
char topOp = ops.get(n - 1);
if (topOp == '*') {
numbers.set(n - 1, topNumber * cur);
} else {
numbers.set(n - 1, topNumber / cur);
}
ops.set(n - 1, op);
}
}
public static int compute(ArrayList<Integer> numbers, ArrayList<Character> ops) {
int n = numbers.size();
int ans = numbers.get(0);
for (int i = 1; i < n; i++) {
ans += ops.get(i - 1) == '+' ? numbers.get(i) : -numbers.get(i);
}
return ans;
}
}
package class039;
// 含有嵌套的字符串解码
// 测试链接 : https://leetcode.cn/problems/decode-string/
public class Code02_DecodeString {
public static String decodeString(String str) {
where = 0;
return f(str.toCharArray(), 0);
}
public static int where;
// s[i....]开始计算,遇到字符串终止 或者 遇到 ] 停止
// 返回 : 自己负责的这一段字符串的结果
// 返回之间,更新全局变量where,为了上游函数知道从哪继续!
public static String f(char[] s, int i) {
StringBuilder path = new StringBuilder();
int cnt = 0;
while (i < s.length && s[i] != ']') {
if ((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z')) {
path.append(s[i++]);
} else if (s[i] >= '0' && s[i] <= '9') {
cnt = cnt * 10 + s[i++] - '0';
} else {
// 遇到 [
// cnt = 7 * ?
path.append(get(cnt, f(s, i + 1)));
i = where + 1;
cnt = 0;
}
}
where = i;
return path.toString();
}
public static String get(int cnt, String str) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < cnt; i++) {
builder.append(str);
}
return builder.toString();
}
}
package class039;
import java.util.TreeMap;
// 含有嵌套的分子式求原子数量
// 测试链接 : https://leetcode.cn/problems/number-of-atoms/
public class Code03_NumberOfAtoms {
public static String countOfAtoms(String str) {
where = 0;
TreeMap<String, Integer> map = f(str.toCharArray(), 0);
StringBuilder ans = new StringBuilder();
for (String key : map.keySet()) {
ans.append(key);
int cnt = map.get(key);
if (cnt > 1) {
ans.append(cnt);
}
}
return ans.toString();
}
public static int where;
// s[i....]开始计算,遇到字符串终止 或者 遇到 ) 停止
// 返回 : 自己负责的这一段字符串的结果,有序表!
// 返回之间,更新全局变量where,为了上游函数知道从哪继续!
public static TreeMap<String, Integer> f(char[] s, int i) {
// ans是总表
TreeMap<String, Integer> ans = new TreeMap<>();
// 之前收集到的名字,历史一部分
StringBuilder name = new StringBuilder();
// 之前收集到的有序表,历史一部分
TreeMap<String, Integer> pre = null;
// 历史翻几倍
int cnt = 0;
while (i < s.length && s[i] != ')') {
if (s[i] >= 'A' && s[i] <= 'Z' || s[i] == '(') {
fill(ans, name, pre, cnt);
name.setLength(0);
pre = null;
cnt = 0;
if (s[i] >= 'A' && s[i] <= 'Z') {
name.append(s[i++]);
} else {
// 遇到 (
pre = f(s, i + 1);
i = where + 1;
}
} else if (s[i] >= 'a' && s[i] <= 'z') {
name.append(s[i++]);
} else {
cnt = cnt * 10 + s[i++] - '0';
}
}
fill(ans, name, pre, cnt);
where = i;
return ans;
}
public static void fill(TreeMap<String, Integer> ans, StringBuilder name, TreeMap<String, Integer> pre, int cnt) {
if (name.length() > 0 || pre != null) {
cnt = cnt == 0 ? 1 : cnt;
if (name.length() > 0) {
String key = name.toString();
ans.put(key, ans.getOrDefault(key, 0) + cnt);
} else {
for (String key : pre.keySet()) {
ans.put(key, ans.getOrDefault(key, 0) + pre.get(key) * cnt);
}
}
}
}
}
40. (必备)N皇后问题(含位运算求解)
package class040;
// N皇后问题
// 测试链接 : https://leetcode.cn/problems/n-queens-ii/
public class NQueens {
// 用数组表示路径实现的N皇后问题,不推荐
public static int totalNQueens1(int n) {
if (n < 1) {
return 0;
}
return f1(0, new int[n], n);
}
// i : 当前来到的行
// path : 0...i-1行的皇后,都摆在了哪些列
// n : 是几皇后问题
// 返回 : 0...i-1行已经摆完了,i....n-1行可以去尝试的情况下还能找到几种有效的方法
public static int f1(int i, int[] path, int n) {
if (i == n) {
return 1;
}
int ans = 0;
// 0 1 2 3 .. n-1
// i j
for (int j = 0; j < n; j++) {
if (check(path, i, j)) {
path[i] = j;
ans += f1(i + 1, path, n);
}
}
return ans;
}
// 当前在i行、j列的位置,摆了一个皇后
// 0...i-1行的皇后状况,path[0...i-1]
// 返回会不会冲突,不会冲突,有效!true
// 会冲突,无效,返回false
public static boolean check(int[] path, int i, int j) {
// 当前 i
// 当列 j
for (int k = 0; k < i; k++) {
// 0...i-1
// 之前行 : k
// 之前列 : path[k]
if (j == path[k] || Math.abs(i - k) == Math.abs(j - path[k])) {
return false;
}
}
return true;
}
// 用位信息表示路径实现的N皇后问题,推荐
public static int totalNQueens2(int n) {
if (n < 1) {
return 0;
}
// n = 5
// 1 << 5 = 0...100000 - 1
// limit = 0...011111;
// n = 7
// limit = 0...01111111;
int limit = (1 << n) - 1;
return f2(limit, 0, 0, 0);
}
// limit : 当前是几皇后问题
// 之前皇后的列影响:col
// 之前皇后的右上 -> 左下对角线影响:left
// 之前皇后的左上 -> 右下对角线影响:right
public static int f2(int limit, int col, int left, int right) {
if (col == limit) {
// 所有皇后放完了!
return 1;
}
// 总限制
int ban = col | left | right;
// ~ban : 1可放皇后,0不能放
int candidate = limit & (~ban);
// 放置皇后的尝试!
int place = 0;
// 一共有多少有效的方法
int ans = 0;
while (candidate != 0) {
// 提取出最右侧的1
// 0 0 1 1 1 0
// 5 4 3 2 1 0
// place :
// 0 0 0 0 1 0
// candidate :
// 0 0 1 1 0 0
// 5 4 3 2 1 0
// place :
// 0 0 0 1 0 0
// candidate :
// 0 0 1 0 0 0
// 5 4 3 2 1 0
// place :
// 0 0 1 0 0 0
// candidate :
// 0 0 0 0 0 0
// 5 4 3 2 1 0
place = candidate & (-candidate);
candidate ^= place;
ans += f2(limit, col | place, (left | place) >> 1, (right | place) << 1);
}
return ans;
}
public static void main(String[] args) {
int n = 14;
long start, end;
System.out.println("测试开始");
System.out.println("解决" + n + "皇后问题");
start = System.currentTimeMillis();
System.out.println("方法1答案 : " + totalNQueens1(n));
end = System.currentTimeMillis();
System.out.println("方法1运行时间 : " + (end - start) + " 毫秒");
start = System.currentTimeMillis();
System.out.println("方法2答案 : " + totalNQueens2(n));
end = System.currentTimeMillis();
System.out.println("方法2运行时间 : " + (end - start) + " 毫秒");
System.out.println("测试结束");
System.out.println("=======");
System.out.println("只有位运算的版本,才能10秒内跑完16皇后问题的求解过程");
start = System.currentTimeMillis();
int ans = totalNQueens2(16);
end = System.currentTimeMillis();
System.out.println("16皇后问题的答案 : " + ans);
System.out.println("运行时间 : " + (end - start) + " 毫秒");
}
}
41. (必备)最大公约数, 同余原理
package class041;
// 求最大公约数、最小公倍数
public class Code01_GcdAndLcm {
// 证明辗转相除法就是证明如下关系:
// gcd(a, b) = gcd(b, a % b)
// 假设a % b = r,即需要证明的关系为:gcd(a, b) = gcd(b, r)
// 证明过程:
// 因为a % b = r,所以如下两个等式必然成立
// 1) a = b * q + r,q为0、1、2、3....中的一个整数
// 2) r = a − b * q,q为0、1、2、3....中的一个整数
// 假设u是a和b的公因子,则有: a = s * u, b = t * u
// 把a和b带入2)得到,r = s * u - t * u * q = (s - t * q) * u
// 这说明 : u如果是a和b的公因子,那么u也是r的因子
// 假设v是b和r的公因子,则有: b = x * v, r = y * v
// 把b和r带入1)得到,a = x * v * q + y * v = (x * q + y) * v
// 这说明 : v如果是b和r的公因子,那么v也是a的公因子
// 综上,a和b的每一个公因子 也是 b和r的一个公因子,反之亦然
// 所以,a和b的全体公因子集合 = b和r的全体公因子集合
// 即gcd(a, b) = gcd(b, r)
// 证明结束
public static long gcd(long a, long b) {
return b == 0 ? a : gcd(b, a % b);
}
public static long lcm(long a, long b) {
return (long) a / gcd(a, b) * b;
}
}
package class041;
// 一个正整数如果能被 a 或 b 整除,那么它是神奇的。
// 给定三个整数 n , a , b ,返回第 n 个神奇的数字。
// 因为答案可能很大,所以返回答案 对 10^9 + 7 取模 后的值。
// 测试链接 : https://leetcode.cn/problems/nth-magical-number/
public class Code02_NthMagicalNumber {
public static int nthMagicalNumber(int n, int a, int b) {
long lcm = lcm(a, b);
long ans = 0;
// l = 0
// r = (long) n * Math.min(a, b)
// l......r
for (long l = 0, r = (long) n * Math.min(a, b), m = 0; l <= r;) {
m = (l + r) / 2;
// 1....m
if (m / a + m / b - m / lcm >= n) {
ans = m;
r = m - 1;
} else {
l = m + 1;
}
}
return (int) (ans % 1000000007);
}
public static long gcd(long a, long b) {
return b == 0 ? a : gcd(b, a % b);
}
public static long lcm(long a, long b) {
return (long) a / gcd(a, b) * b;
}
}
package class041;
import java.math.BigInteger;
// 加法、减法、乘法的同余原理
// 不包括除法,因为除法必须求逆元,后续课讲述
public class Code03_SameMod {
// 为了测试
public static long random() {
return (long) (Math.random() * Long.MAX_VALUE);
}
// 计算 ((a + b) * (c - d) + (a * c - b * d)) % mod 的非负结果
public static int f1(long a, long b, long c, long d, int mod) {
BigInteger o1 = new BigInteger(String.valueOf(a)); // a
BigInteger o2 = new BigInteger(String.valueOf(b)); // b
BigInteger o3 = new BigInteger(String.valueOf(c)); // c
BigInteger o4 = new BigInteger(String.valueOf(d)); // d
BigInteger o5 = o1.add(o2); // a + b
BigInteger o6 = o3.subtract(o4); // c - d
BigInteger o7 = o1.multiply(o3); // a * c
BigInteger o8 = o2.multiply(o4); // b * d
BigInteger o9 = o5.multiply(o6); // (a + b) * (c - d)
BigInteger o10 = o7.subtract(o8); // (a * c - b * d)
BigInteger o11 = o9.add(o10); // ((a + b) * (c - d) + (a * c - b * d))
// ((a + b) * (c - d) + (a * c - b * d)) % mod
BigInteger o12 = o11.mod(new BigInteger(String.valueOf(mod)));
if (o12.signum() == -1) {
// 如果是负数那么+mod返回
return o12.add(new BigInteger(String.valueOf(mod))).intValue();
} else {
// 如果不是负数直接返回
return o12.intValue();
}
}
// 计算 ((a + b) * (c - d) + (a * c - b * d)) % mod 的非负结果
public static int f2(long a, long b, long c, long d, int mod) {
int o1 = (int) (a % mod); // a
int o2 = (int) (b % mod); // b
int o3 = (int) (c % mod); // c
int o4 = (int) (d % mod); // d
int o5 = (o1 + o2) % mod; // a + b
int o6 = (o3 - o4 + mod) % mod; // c - d
int o7 = (int) (((long) o1 * o3) % mod); // a * c
int o8 = (int) (((long) o2 * o4) % mod); // b * d
int o9 = (int) (((long) o5 * o6) % mod); // (a + b) * (c - d)
int o10 = (o7 - o8 + mod) % mod; // (a * c - b * d)
int ans = (o9 + o10) % mod; // ((a + b) * (c - d) + (a * c - b * d)) % mod
return ans;
}
public static void main(String[] args) {
System.out.println("测试开始");
int testTime = 100000;
int mod = 1000000007;
for (int i = 0; i < testTime; i++) {
long a = random();
long b = random();
long c = random();
long d = random();
if (f1(a, b, c, d, mod) != f2(a, b, c, d, mod)) {
System.out.println("出错了!");
}
}
System.out.println("测试结束");
System.out.println("===");
long a = random();
long b = random();
long c = random();
long d = random();
System.out.println("a : " + a);
System.out.println("b : " + b);
System.out.println("c : " + c);
System.out.println("d : " + d);
System.out.println("===");
System.out.println("f1 : " + f1(a, b, c, d, mod));
System.out.println("f2 : " + f2(a, b, c, d, mod));
}
}
42. (必备)对数器打表找规律的技巧
package class042;
// 有装下8个苹果的袋子、装下6个苹果的袋子,一定要保证买苹果时所有使用的袋子都装满
// 对于无法装满所有袋子的方案不予考虑,给定n个苹果,返回至少要多少个袋子
// 如果不存在每个袋子都装满的方案返回-1
public class Code01_AppleMinBags {
public static int bags1(int apple) {
int ans = f(apple);
return ans == Integer.MAX_VALUE ? -1 : ans;
}
// 当前还有rest个苹果,使用的每个袋子必须装满,返回至少几个袋子
public static int f(int rest) {
if (rest < 0) {
return Integer.MAX_VALUE;
}
if (rest == 0) {
return 0;
}
// 使用8规格的袋子,剩余的苹果还需要几个袋子,有可能返回无效解
int p1 = f(rest - 8);
// 使用6规格的袋子,剩余的苹果还需要几个袋子,有可能返回无效解
int p2 = f(rest - 6);
p1 += p1 != Integer.MAX_VALUE ? 1 : 0;
p2 += p2 != Integer.MAX_VALUE ? 1 : 0;
return Math.min(p1, p2);
}
public static int bags2(int apple) {
if ((apple & 1) != 0) {
return -1;
}
if (apple < 18) {
if (apple == 0) {
return 0;
}
if (apple == 6 || apple == 8) {
return 1;
}
if (apple == 12 || apple == 14 || apple == 16) {
return 2;
}
return -1;
}
return (apple - 18) / 8 + 3;
}
public static void main(String[] args) {
for (int apple = 0; apple < 100; apple++) {
System.out.println(apple + " : " + bags1(apple));
}
}
}
package class042;
// 草一共有n的重量,两只牛轮流吃草,A牛先吃,B牛后吃
// 每只牛在自己的回合,吃草的重量必须是4的幂,1、4、16、64....
// 谁在自己的回合正好把草吃完谁赢,根据输入的n,返回谁赢
public class Code02_EatGrass {
// "A" "B"
public static String win1(int n) {
return f(n, "A");
}
// rest : 还剩多少草
// cur : 当前选手的名字
// 返回 : 还剩rest份草,当前选手是cur,按照题目说的,返回最终谁赢
public static String f(int rest, String cur) {
String enemy = cur.equals("A") ? "B" : "A";
if (rest < 5) {
return (rest == 0 || rest == 2) ? enemy : cur;
}
// rest >= 5
// rest == 100
// cur :
// 1) 1 ->99,enemy ....
// 2) 4 ->96,enemy ....
// 3) 16 -> 84,enemy ....
// 4) 64 -> 36,enemy ...
// 没有cur赢的分支,enemy赢
int pick = 1;
while (pick <= rest) {
if (f(rest - pick, enemy).equals(cur)) {
return cur;
}
pick *= 4;
}
return enemy;
}
public static String win2(int n) {
if (n % 5 == 0 || n % 5 == 2) {
return "B";
} else {
return "A";
}
}
public static void main(String[] args) {
for (int i = 0; i <= 50; i++) {
System.out.println(i + " : " + win1(i));
}
}
}
package class042;
// 判断一个数字是否是若干数量(数量>1)的连续正整数的和
public class Code03_IsSumOfConsecutiveNumbers {
public static boolean is1(int num) {
for (int start = 1, sum; start <= num; start++) {
sum = start;
for (int j = start + 1; j <= num; j++) {
if (sum + j > num) {
break;
}
if (sum + j == num) {
return true;
}
sum += j;
}
}
return false;
}
public static boolean is2(int num) {
return (num & (num - 1)) != 0;
}
public static void main(String[] args) {
for (int num = 1; num < 200; num++) {
System.out.println(num + " : " + (is1(num) ? "T" : "F"));
}
}
}
package class042;
// 可以用r、e、d三种字符拼接字符串,如果拼出来的字符串中
// 有且仅有1个长度>=2的回文子串,那么这个字符串定义为"好串"
// 返回长度为n的所有可能的字符串中,好串有多少个
// 结果对1000000007取模, 1 <= n <= 10^9
// 示例:
// n = 1, 输出0
// n = 2, 输出3
// n = 3, 输出18
public class Code04_RedPalindromeGoodStrings {
// 暴力方法
// 为了观察规律
public static int num1(int n) {
char[] path = new char[n];
return f(path, 0);
}
public static int f(char[] path, int i) {
if (i == path.length) {
int cnt = 0;
for (int l = 0; l < path.length; l++) {
for (int r = l + 1; r < path.length; r++) {
if (is(path, l, r)) {
cnt++;
}
if (cnt > 1) {
return 0;
}
}
}
return cnt == 1 ? 1 : 0;
} else {
// i正常位置
int ans = 0;
path[i] = 'r';
ans += f(path, i + 1);
path[i] = 'e';
ans += f(path, i + 1);
path[i] = 'd';
ans += f(path, i + 1);
return ans;
}
}
public static boolean is(char[] s, int l, int r) {
while (l < r) {
if (s[l] != s[r]) {
return false;
}
l++;
r--;
}
return true;
}
// 正式方法
// 观察规律之后变成代码
public static int num2(int n) {
if (n == 1) {
return 0;
}
if (n == 2) {
return 3;
}
if (n == 3) {
return 18;
}
return (int) (((long) 6 * (n + 1)) % 1000000007);
}
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
System.out.println("长度为" + i + ", 答案:" + num1(i));
}
}
}
43. (必备)根据数据量猜解法的技巧-天字第一号重要技巧
package class043;
// 现在有一个打怪类型的游戏,这个游戏是这样的,你有n个技能
// 每一个技能会有一个伤害,
// 同时若怪物小于等于一定的血量,则该技能可能造成双倍伤害
// 每一个技能最多只能释放一次,已知怪物有m点血量
// 现在想问你最少用几个技能能消灭掉他(血量小于等于0)
// 技能的数量是n,怪物的血量是m
// i号技能的伤害是x[i],i号技能触发双倍伤害的血量最小值是y[i]
// 1 <= n <= 10
// 1 <= m、x[i]、y[i] <= 10^6
// 测试链接 : https://www.nowcoder.com/practice/d88ef50f8dab4850be8cd4b95514bbbd
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的所有代码,并把主类名改成"Main"
// 可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Code01_KillMonsterEverySkillUseOnce {
public static int MAXN = 11;
public static int[] kill = new int[MAXN];
public static int[] blood = new int[MAXN];
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
while (in.nextToken() != StreamTokenizer.TT_EOF) {
int t = (int) in.nval;
for (int i = 0; i < t; i++) {
in.nextToken();
int n = (int) in.nval;
in.nextToken();
int m = (int) in.nval;
for (int j = 0; j < n; j++) {
in.nextToken();
kill[j] = (int) in.nval;
in.nextToken();
blood[j] = (int) in.nval;
}
int ans = f(n, 0, m);
out.println(ans == Integer.MAX_VALUE ? -1 : ans);
}
}
out.flush();
br.close();
out.close();
}
// kill[i]、blood[i]
// n : 一共几个技能
// i : 当前来到了第几号技能
// r : 怪兽目前的剩余血量
public static int f(int n, int i, int r) {
if (r <= 0) {
// 之前的决策已经让怪兽挂了!返回使用了多少个节能
return i;
}
// r > 0
if (i == n) {
// 无效,之前的决策无效
return Integer.MAX_VALUE;
}
// 返回至少需要几个技能可以将怪兽杀死
int ans = Integer.MAX_VALUE;
for (int j = i; j < n; j++) {
swap(i, j);
ans = Math.min(ans, f(n, i + 1, r - (r > blood[i] ? kill[i] : kill[i] * 2)));
swap(i, j);
}
return ans;
}
// i号技能和j号技能,参数交换
// j号技能要来到i位置,试一下
public static void swap(int i, int j) {
int tmp = kill[i];
kill[i] = kill[j];
kill[j] = tmp;
tmp = blood[i];
blood[i] = blood[j];
blood[j] = tmp;
}
}
package class043;
import java.util.ArrayList;
import java.util.List;
// 如果一个正整数自身是回文数,而且它也是一个回文数的平方,那么我们称这个数为超级回文数。
// 现在,给定两个正整数 L 和 R (以字符串形式表示),
// 返回包含在范围 [L, R] 中的超级回文数的数目。
// 1 <= len(L) <= 18
// 1 <= len(R) <= 18
// L 和 R 是表示 [1, 10^18) 范围的整数的字符串
//测试链接 : https://leetcode.cn/problems/super-palindromes/
public class Code02_SuperPalindromes {
// [left, right]有多少超级回文数
// 返回数量
public static int superpalindromesInRange1(String left, String right) {
long l = Long.valueOf(left);
long r = Long.valueOf(right);
// l....r long
// x根号,范围limit
long limit = (long) Math.sqrt((double) r);
// seed : 枚举量很小,10^18 -> 10^9 -> 10^5
// seed : 奇数长度回文、偶数长度回文
long seed = 1;
// num : 根号x,num^2 -> x
long num = 0;
int ans = 0;
do {
// seed生成偶数长度回文数字
// 123 -> 123321
num = evenEnlarge(seed);
if (check(num * num, l, r)) {
ans++;
}
// seed生成奇数长度回文数字
// 123 -> 12321
num = oddEnlarge(seed);
if (check(num * num, l, r)) {
ans++;
}
// 123 -> 124 -> 125
seed++;
} while (num < limit);
return ans;
}
// 根据种子扩充到偶数长度的回文数字并返回
public static long evenEnlarge(long seed) {
long ans = seed;
while (seed != 0) {
ans = ans * 10 + seed % 10;
seed /= 10;
}
return ans;
}
// 根据种子扩充到奇数长度的回文数字并返回
public static long oddEnlarge(long seed) {
long ans = seed;
seed /= 10;
while (seed != 0) {
ans = ans * 10 + seed % 10;
seed /= 10;
}
return ans;
}
// 判断ans是不是属于[l,r]范围的回文数
public static boolean check(long ans, long l, long r) {
return ans >= l && ans <= r && isPalindrome(ans);
}
// 验证long类型的数字num,是不是回文数字
public static boolean isPalindrome(long num) {
long offset = 1;
// 注意这么写是为了防止溢出
while (num / offset >= 10) {
offset *= 10;
}
// num : 52725
// offset : 10000
// 首尾判断
while (num != 0) {
if (num / offset != num % 10) {
return false;
}
num = (num % offset) / 10;
offset /= 100;
}
return true;
}
// 打表的方法
// 必然最优解
// 连二分都懒得用
public static int superpalindromesInRange2(String left, String right) {
long l = Long.parseLong(left);
long r = Long.parseLong(right);
int i = 0;
for (; i < record.length; i++) {
if (record[i] >= l) {
break;
}
}
int j = record.length - 1;
for (; j >= 0; j--) {
if (record[j] <= r) {
break;
}
}
return j - i + 1;
}
public static long[] record = new long[] {
1L,
4L,
9L,
121L,
484L,
10201L,
12321L,
14641L,
40804L,
44944L,
1002001L,
1234321L,
4008004L,
100020001L,
102030201L,
104060401L,
121242121L,
123454321L,
125686521L,
400080004L,
404090404L,
10000200001L,
10221412201L,
12102420121L,
12345654321L,
40000800004L,
1000002000001L,
1002003002001L,
1004006004001L,
1020304030201L,
1022325232201L,
1024348434201L,
1210024200121L,
1212225222121L,
1214428244121L,
1232346432321L,
1234567654321L,
4000008000004L,
4004009004004L,
100000020000001L,
100220141022001L,
102012040210201L,
102234363432201L,
121000242000121L,
121242363242121L,
123212464212321L,
123456787654321L,
400000080000004L,
10000000200000001L,
10002000300020001L,
10004000600040001L,
10020210401202001L,
10022212521222001L,
10024214841242001L,
10201020402010201L,
10203040504030201L,
10205060806050201L,
10221432623412201L,
10223454745432201L,
12100002420000121L,
12102202520220121L,
12104402820440121L,
12122232623222121L,
12124434743442121L,
12321024642012321L,
12323244744232321L,
12343456865434321L,
12345678987654321L,
40000000800000004L,
40004000900040004L,
1000000002000000001L,
1000220014100220001L,
1002003004003002001L,
1002223236323222001L,
1020100204020010201L,
1020322416142230201L,
1022123226223212201L,
1022345658565432201L,
1210000024200000121L,
1210242036302420121L,
1212203226223022121L,
1212445458545442121L,
1232100246420012321L,
1232344458544432321L,
1234323468643234321L,
4000000008000000004L
};
public static List<Long> collect() {
long l = 1;
long r = Long.MAX_VALUE;
long limit = (long) Math.sqrt((double) r);
long seed = 1;
long enlarge = 0;
ArrayList<Long> ans = new ArrayList<>();
do {
enlarge = evenEnlarge(seed);
if (check(enlarge * enlarge, l, r)) {
ans.add(enlarge * enlarge);
}
enlarge = oddEnlarge(seed);
if (check(enlarge * enlarge, l, r)) {
ans.add(enlarge * enlarge);
}
seed++;
} while (enlarge < limit);
ans.sort((a, b) -> a.compareTo(b));
return ans;
}
public static void main(String[] args) {
List<Long> ans = collect();
for (long p : ans) {
System.out.println(p + "L,");
}
System.out.println("size : " + ans.size());
}
}
package class043;
// 超级回文数中的一个小函数,本身也是一道题 : 判断一个数字是不是回文数
// 测试链接 : https://leetcode.cn/problems/palindrome-number/
public class Code03_IsPalindrome {
public static boolean isPalindrome(int num) {
if (num < 0) {
return false;
}
int offset = 1;
// 注意这么写是为了防止溢出
while (num / offset >= 10) {
offset *= 10;
}
// 首尾判断
while (num != 0) {
if (num / offset != num % 10) {
return false;
}
num = (num % offset) / 10;
offset /= 100;
}
return true;
}
}
44. (必备)前缀树原理与代码详解
package class044;
import java.util.HashMap;
// 用类描述实现前缀树。不推荐!
// 测试链接 : https://leetcode.cn/problems/implement-trie-ii-prefix-tree/
public class Code01_TrieTree {
// 路是数组实现的
// 提交时把类名、构造方法改为Trie
class Trie1 {
class TrieNode {
public int pass;
public int end;
public TrieNode[] nexts;
public TrieNode() {
pass = 0;
end = 0;
nexts = new TrieNode[26];
}
}
private TrieNode root;
public Trie1() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode node = root;
node.pass++;
for (int i = 0, path; i < word.length(); i++) { // 从左往右遍历字符
path = word.charAt(i) - 'a'; // 由字符,对应成走向哪条路
if (node.nexts[path] == null) {
node.nexts[path] = new TrieNode();
}
node = node.nexts[path];
node.pass++;
}
node.end++;
}
// 如果之前word插入过前缀树,那么此时删掉一次
// 如果之前word没有插入过前缀树,那么什么也不做
public void erase(String word) {
if (countWordsEqualTo(word) > 0) {
TrieNode node = root;
node.pass--;
for (int i = 0, path; i < word.length(); i++) {
path = word.charAt(i) - 'a';
if (--node.nexts[path].pass == 0) {
node.nexts[path] = null;
return;
}
node = node.nexts[path];
}
node.end--;
}
}
// 查询前缀树里,word单词出现了几次
public int countWordsEqualTo(String word) {
TrieNode node = root;
for (int i = 0, path; i < word.length(); i++) {
path = word.charAt(i) - 'a';
if (node.nexts[path] == null) {
return 0;
}
node = node.nexts[path];
}
return node.end;
}
// 查询前缀树里,有多少单词以pre做前缀
public int countWordsStartingWith(String pre) {
TrieNode node = root;
for (int i = 0, path; i < pre.length(); i++) {
path = pre.charAt(i) - 'a';
if (node.nexts[path] == null) {
return 0;
}
node = node.nexts[path];
}
return node.pass;
}
}
// 路是哈希表实现的
// 提交时把类名、构造方法改为Trie
class Trie2 {
class TrieNode {
public int pass;
public int end;
HashMap<Integer, TrieNode> nexts;
public TrieNode() {
pass = 0;
end = 0;
nexts = new HashMap<>();
}
}
private TrieNode root;
public Trie2() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode node = root;
node.pass++;
for (int i = 0, path; i < word.length(); i++) { // 从左往右遍历字符
path = word.charAt(i);
if (!node.nexts.containsKey(path)) {
node.nexts.put(path, new TrieNode());
}
node = node.nexts.get(path);
node.pass++;
}
node.end++;
}
public void erase(String word) {
if (countWordsEqualTo(word) > 0) {
TrieNode node = root;
TrieNode next;
node.pass--;
for (int i = 0, path; i < word.length(); i++) {
path = word.charAt(i);
next = node.nexts.get(path);
if (--next.pass == 0) {
node.nexts.remove(path);
return;
}
node = next;
}
node.end--;
}
}
public int countWordsEqualTo(String word) {
TrieNode node = root;
for (int i = 0, path; i < word.length(); i++) {
path = word.charAt(i);
if (!node.nexts.containsKey(path)) {
return 0;
}
node = node.nexts.get(path);
}
return node.end;
}
public int countWordsStartingWith(String pre) {
TrieNode node = root;
for (int i = 0, path; i < pre.length(); i++) {
path = pre.charAt(i);
if (!node.nexts.containsKey(path)) {
return 0;
}
node = node.nexts.get(path);
}
return node.pass;
}
}
}
package class044;
// 用固定数组实现前缀树,空间使用是静态的。推荐!
// 测试链接 : https://www.nowcoder.com/practice/7f8a8553ddbf4eaab749ec988726702b
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;
public class Code02_TrieTree {
// 如果将来增加了数据量,就改大这个值
public static int MAXN = 150001;
public static int[][] tree = new int[MAXN][26];
public static int[] end = new int[MAXN];
public static int[] pass = new int[MAXN];
public static int cnt;
public static void build() {
cnt = 1;
}
public static void insert(String word) {
int cur = 1;
pass[cur]++;
for (int i = 0, path; i < word.length(); i++) {
path = word.charAt(i) - 'a';
if (tree[cur][path] == 0) {
tree[cur][path] = ++cnt;
}
cur = tree[cur][path];
pass[cur]++;
}
end[cur]++;
}
public static int search(String word) {
int cur = 1;
for (int i = 0, path; i < word.length(); i++) {
path = word.charAt(i) - 'a';
if (tree[cur][path] == 0) {
return 0;
}
cur = tree[cur][path];
}
return end[cur];
}
public static int prefixNumber(String pre) {
int cur = 1;
for (int i = 0, path; i < pre.length(); i++) {
path = pre.charAt(i) - 'a';
if (tree[cur][path] == 0) {
return 0;
}
cur = tree[cur][path];
}
return pass[cur];
}
public static void delete(String word) {
if (search(word) > 0) {
int cur = 1;
for (int i = 0, path; i < word.length(); i++) {
path = word.charAt(i) - 'a';
if (--pass[tree[cur][path]] == 0) {
tree[cur][path] = 0;
return;
}
cur = tree[cur][path];
}
end[cur]--;
}
}
public static void clear() {
for (int i = 1; i <= cnt; i++) {
Arrays.fill(tree[i], 0);
end[i] = 0;
pass[i] = 0;
}
}
public static int m, op;
public static String[] splits;
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
String line = null;
while ((line = in.readLine()) != null) {
build();
m = Integer.valueOf(line);
for (int i = 1; i <= m; i++) {
splits = in.readLine().split(" ");
op = Integer.valueOf(splits[0]);
if (op == 1) {
insert(splits[1]);
} else if (op == 2) {
delete(splits[1]);
} else if (op == 3) {
out.println(search(splits[1]) > 0 ? "YES" : "NO");
} else if (op == 4) {
out.println(prefixNumber(splits[1]));
}
}
clear();
}
out.flush();
in.close();
out.close();
}
}
45. (必备)前缀树的相关题目
package class045;
import java.util.Arrays;
// 牛牛和他的朋友们约定了一套接头密匙系统,用于确认彼此身份
// 密匙由一组数字序列表示,两个密匙被认为是一致的,如果满足以下条件:
// 密匙 b 的长度不超过密匙 a 的长度。
// 对于任意 0 <= i < length(b),有b[i+1] - b[i] == a[i+1] - a[i]
// 现在给定了m个密匙 b 的数组,以及n个密匙 a 的数组
// 请你返回一个长度为 m 的结果数组 ans,表示每个密匙b都有多少一致的密匙
// 数组 a 和数组 b 中的元素个数均不超过 10^5
// 1 <= m, n <= 1000
// 测试链接 : https://www.nowcoder.com/practice/c552d3b4dfda49ccb883a6371d9a6932
public class Code01_CountConsistentKeys {
public static int[] countConsistentKeys(int[][] b, int[][] a) {
build();
StringBuilder builder = new StringBuilder();
// [3,6,50,10] -> "3#44#-40#"
for (int[] nums : a) {
builder.setLength(0);
for (int i = 1; i < nums.length; i++) {
builder.append(String.valueOf(nums[i] - nums[i - 1]) + "#");
}
insert(builder.toString());
}
int[] ans = new int[b.length];
for (int i = 0; i < b.length; i++) {
builder.setLength(0);
int[] nums = b[i];
for (int j = 1; j < nums.length; j++) {
builder.append(String.valueOf(nums[j] - nums[j - 1]) + "#");
}
ans[i] = count(builder.toString());
}
clear();
return ans;
}
// 如果将来增加了数据量,就改大这个值
public static int MAXN = 2000001;
public static int[][] tree = new int[MAXN][12];
public static int[] pass = new int[MAXN];
public static int cnt;
public static void build() {
cnt = 1;
}
// '0' ~ '9' 10个 0~9
// '#' 10
// '-' 11
public static int path(char cha) {
if (cha == '#') {
return 10;
} else if (cha == '-') {
return 11;
} else {
return cha - '0';
}
}
public static void insert(String word) {
int cur = 1;
pass[cur]++;
for (int i = 0, path; i < word.length(); i++) {
path = path(word.charAt(i));
if (tree[cur][path] == 0) {
tree[cur][path] = ++cnt;
}
cur = tree[cur][path];
pass[cur]++;
}
}
public static int count(String pre) {
int cur = 1;
for (int i = 0, path; i < pre.length(); i++) {
path = path(pre.charAt(i));
if (tree[cur][path] == 0) {
return 0;
}
cur = tree[cur][path];
}
return pass[cur];
}
public static void clear() {
for (int i = 1; i <= cnt; i++) {
Arrays.fill(tree[i], 0);
pass[i] = 0;
}
}
}
package class045;
import java.util.HashSet;
// 数组中两个数的最大异或值
// 给你一个整数数组 nums ,返回 nums[i] XOR nums[j] 的最大运算结果,其中 0<=i<=j<=n
// 1 <= nums.length <= 2 * 10^5
// 0 <= nums[i] <= 2^31 - 1
// 测试链接 : https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/
public class Code02_TwoNumbersMaximumXor {
// 前缀树的做法
// 好想
public static int findMaximumXOR1(int[] nums) {
build(nums);
int ans = 0;
for (int num : nums) {
ans = Math.max(ans, maxXor(num));
}
clear();
return ans;
}
// 准备这么多静态空间就够了,实验出来的
// 如果测试数据升级了规模,就改大这个值
public static int MAXN = 3000001;
public static int[][] tree = new int[MAXN][2];
// 前缀树目前使用了多少空间
public static int cnt;
// 数字只需要从哪一位开始考虑
public static int high;
public static void build(int[] nums) {
cnt = 1;
// 找个最大值
int max = Integer.MIN_VALUE;
for (int num : nums) {
max = Math.max(num, max);
}
// 计算数组最大值的二进制状态,有多少个前缀的0
// 可以忽略这些前置的0,从left位开始考虑
high = 31 - Integer.numberOfLeadingZeros(max);
for (int num : nums) {
insert(num);
}
}
public static void insert(int num) {
int cur = 1;
for (int i = high, path; i >= 0; i--) {
path = (num >> i) & 1;
if (tree[cur][path] == 0) {
tree[cur][path] = ++cnt;
}
cur = tree[cur][path];
}
}
public static int maxXor(int num) {
// 最终异或的结果(尽量大)
int ans = 0;
// 前缀树目前来到的节点编号
int cur = 1;
for (int i = high, status, want; i >= 0; i--) {
// status : num第i位的状态
status = (num >> i) & 1;
// want : num第i位希望遇到的状态
want = status ^ 1;
if (tree[cur][want] == 0) { // 询问前缀树,能不能达成
// 不能达成
want ^= 1;
}
// want变成真的往下走的路
ans |= (status ^ want) << i;
cur = tree[cur][want];
}
return ans;
}
public static void clear() {
for (int i = 1; i <= cnt; i++) {
tree[i][0] = tree[i][1] = 0;
}
}
// 用哈希表的做法
// 难想
public int findMaximumXOR2(int[] nums) {
int max = Integer.MIN_VALUE;
for (int num : nums) {
max = Math.max(num, max);
}
int ans = 0;
HashSet<Integer> set = new HashSet<>();
for (int i = 31 - Integer.numberOfLeadingZeros(max); i >= 0; i--) {
// ans : 31....i+1 已经达成的目标
int better = ans | (1 << i);
set.clear();
for (int num : nums) {
// num : 31.....i 这些状态保留,剩下全成0
num = (num >> i) << i;
set.add(num);
// num ^ 某状态 是否能 达成better目标,就在set中找 某状态 : better ^ num
if (set.contains(better ^ num)) {
ans = better;
break;
}
}
}
return ans;
}
}
package class045;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
// 在二维字符数组中搜索可能的单词
// 给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words
// 返回所有二维网格上的单词。单词必须按照字母顺序,通过 相邻的单元格 内的字母构成
// 其中“相邻”单元格是那些水平相邻或垂直相邻的单元格
// 同一个单元格内的字母在一个单词中不允许被重复使用
// 1 <= m, n <= 12
// 1 <= words.length <= 3 * 10^4
// 1 <= words[i].length <= 10
// 测试链接 : https://leetcode.cn/problems/word-search-ii/
public class Code03_WordSearchII {
public static List<String> findWords(char[][] board, String[] words) {
build(words);
List<String> ans = new ArrayList<>();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
dfs(board, i, j, 1, ans);
}
}
clear();
return ans;
}
// board : 二维网格
// i,j : 此时来到的格子位置,i行、j列
// t : 前缀树的编号
// List<String> ans : 收集到了哪些字符串,都放入ans
// 返回值 : 收集到了几个字符串
public static int dfs(char[][] board, int i, int j, int t, List<String> ans) {
// 越界 或者 走了回头路,直接返回0
if (i < 0 || i == board.length || j < 0 || j == board[0].length || board[i][j] == 0) {
return 0;
}
// 不越界 且 不是回头路
// 用tmp记录当前字符
char tmp = board[i][j];
// 路的编号
// a -> 0
// b -> 1
// ...
// z -> 25
int road = tmp - 'a';
t = tree[t][road];
if (pass[t] == 0) {
return 0;
}
// i,j位置有必要来
// fix :从当前i,j位置出发,一共收集到了几个字符串
int fix = 0;
if (end[t] != null) {
fix++;
ans.add(end[t]);
end[t] = null;
}
// 把i,j位置的字符,改成0,后续的过程,是不可以再来到i,j位置的!
board[i][j] = 0;
fix += dfs(board, i - 1, j, t, ans);
fix += dfs(board, i + 1, j, t, ans);
fix += dfs(board, i, j - 1, t, ans);
fix += dfs(board, i, j + 1, t, ans);
pass[t] -= fix;
board[i][j] = tmp;
return fix;
}
public static int MAXN = 10001;
public static int[][] tree = new int[MAXN][26];
public static int[] pass = new int[MAXN];
public static String[] end = new String[MAXN];
public static int cnt;
public static void build(String[] words) {
cnt = 1;
for (String word : words) {
int cur = 1;
pass[cur]++;
for (int i = 0, path; i < word.length(); i++) {
path = word.charAt(i) - 'a';
if (tree[cur][path] == 0) {
tree[cur][path] = ++cnt;
}
cur = tree[cur][path];
pass[cur]++;
}
end[cur] = word;
}
}
public static void clear() {
for (int i = 1; i <= cnt; i++) {
Arrays.fill(tree[i], 0);
pass[i] = 0;
end[i] = null;
}
}
}
46. (必备)构建前缀数量信息的技巧-解决子数组相关问题
package class046;
// 利用前缀和快速得到区域累加和
// 测试链接 : https://leetcode.cn/problems/range-sum-query-immutable/
public class Code01_PrefixSumArray {
class NumArray {
public int[] sum;
public NumArray(int[] nums) {
sum = new int[nums.length + 1];
for (int i = 1; i <= nums.length; i++) {
sum[i] = sum[i - 1] + nums[i - 1];
}
}
public int sumRange(int left, int right) {
return sum[right + 1] - sum[left];
}
}
}
package class046;
// 返回无序数组中累加和为给定值的最长子数组长度
// 给定一个无序数组arr, 其中元素可正、可负、可0
// 给定一个整数aim
// 求arr所有子数组中累加和为aim的最长子数组长度
// 测试链接 : https://www.nowcoder.com/practice/36fb0fd3c656480c92b569258a1223d5
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.HashMap;
public class Code02_LongestSubarraySumEqualsAim {
public static int MAXN = 100001;
public static int[] arr = new int[MAXN];
public static int n, aim;
// key : 某个前缀和
// value : 这个前缀和最早出现的位置
public static HashMap<Integer, Integer> map = new HashMap<>();
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
while (in.nextToken() != StreamTokenizer.TT_EOF) {
n = (int) in.nval;
in.nextToken();
aim = (int) in.nval;
for (int i = 0; i < n; i++) {
in.nextToken();
arr[i] = (int) in.nval;
}
out.println(compute());
}
out.flush();
out.close();
br.close();
}
public static int compute() {
map.clear();
// 重要 : 0这个前缀和,一个数字也没有的时候,就存在了
map.put(0, -1);
int ans = 0;
for (int i = 0, sum = 0; i < n; i++) {
sum += arr[i];
if (map.containsKey(sum - aim)) {
ans = Math.max(ans, i - map.get(sum - aim));
}
if (!map.containsKey(sum)) {
map.put(sum, i);
}
}
return ans;
}
}
package class046;
import java.util.HashMap;
// 返回无序数组中累加和为给定值的子数组个数
// 测试链接 : https://leetcode.cn/problems/subarray-sum-equals-k/
public class Code03_NumberOfSubarraySumEqualsAim {
public static int subarraySum(int[] nums, int aim) {
HashMap<Integer, Integer> map = new HashMap<>();
// 0这个前缀和,在没有任何数字的时候,已经有1次了
map.put(0, 1);
int ans = 0;
for (int i = 0, sum = 0; i < nums.length; i++) {
// sum : 0...i前缀和
sum += nums[i];
ans += map.getOrDefault(sum - aim, 0);
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return ans;
}
}
package class046;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.HashMap;
// 返回无序数组中正数和负数个数相等的最长子数组长度
// 给定一个无序数组arr,其中元素可正、可负、可0
// 求arr所有子数组中正数与负数个数相等的最长子数组的长度
// 测试链接 : https://www.nowcoder.com/practice/545544c060804eceaed0bb84fcd992fb
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
public class Code04_PositivesEqualsNegtivesLongestSubarray {
public static int MAXN = 100001;
public static int[] arr = new int[MAXN];
public static int n;
public static HashMap<Integer, Integer> map = new HashMap<>();
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
while (in.nextToken() != StreamTokenizer.TT_EOF) {
n = (int) in.nval;
for (int i = 0, num; i < n; i++) {
in.nextToken();
num = (int) in.nval;
arr[i] = num != 0 ? (num > 0 ? 1 : -1) : 0;
}
out.println(compute());
}
out.flush();
out.close();
br.close();
}
public static int compute() {
map.clear();
map.put(0, -1);
int ans = 0;
for (int i = 0, sum = 0; i < n; i++) {
sum += arr[i];
if (map.containsKey(sum)) {
ans = Math.max(ans, i - map.get(sum));
} else {
map.put(sum, i);
}
}
return ans;
}
}
package class046;
import java.util.HashMap;
// 表现良好的最长时间段
// 给你一份工作时间表 hours,上面记录着某一位员工每天的工作小时数
// 我们认为当员工一天中的工作小时数大于 8 小时的时候,那么这一天就是 劳累的一天
// 所谓 表现良好的时间段 ,意味在这段时间内,「劳累的天数」是严格 大于 不劳累的天数
// 请你返回 表现良好时间段 的最大长度
// 测试链接 : https://leetcode.cn/problems/longest-well-performing-interval/
public class Code05_LongestWellPerformingInterval {
public static int longestWPI(int[] hours) {
// 某个前缀和,最早出现的位置
HashMap<Integer, Integer> map = new HashMap<>();
// 0这个前缀和,最早出现在-1,一个数也没有的时候
map.put(0, -1);
int ans = 0;
for (int i = 0, sum = 0; i < hours.length; i++) {
sum += hours[i] > 8 ? 1 : -1;
if (sum > 0) {
ans = i + 1;
} else {
// sum <= 0
if (map.containsKey(sum - 1)) {
ans = Math.max(ans, i - map.get(sum - 1));
}
}
if (!map.containsKey(sum)) {
map.put(sum, i);
}
}
return ans;
}
}
package class046;
import java.util.HashMap;
// 使数组和能被P整除
// 给你一个正整数数组 nums,请你移除 最短 子数组(可以为 空)
// 使得剩余元素的 和 能被 p 整除。 不允许 将整个数组都移除。
// 请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1 。
// 子数组 定义为原数组中连续的一组元素。
// 测试链接 : https://leetcode.cn/problems/make-sum-divisible-by-p/
public class Code06_MakeSumDivisibleByP {
public static int minSubarray(int[] nums, int p) {
// 整体余数
int mod = 0;
for (int num : nums) {
mod = (mod + num) % p;
}
if (mod == 0) {
return 0;
}
// key : 前缀和%p的余数
// value : 最晚出现的位置
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
int ans = Integer.MAX_VALUE;
for (int i = 0, cur = 0, find; i < nums.length; i++) {
// 0...i这部分的余数
cur = (cur + nums[i]) % p;
find = cur >= mod ? (cur - mod) : (cur + p - mod);
// find = (cur + p - mod) % p;
if (map.containsKey(find)) {
ans = Math.min(ans, i - map.get(find));
}
map.put(cur, i);
}
return ans == nums.length ? -1 : ans;
}
}
package class046;
import java.util.Arrays;
// 每个元音包含偶数次的最长子字符串
// 给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度
// 每个元音字母,即 'a','e','i','o','u'
// 在子字符串中都恰好出现了偶数次。
// 测试链接 : https://leetcode.cn/problems/find-the-longest-substring-containing-vowels-in-even-counts/
public class Code07_EvenCountsLongestSubarray {
public static int findTheLongestSubstring(String s) {
int n = s.length();
// 只有5个元音字符,状态就5位
int[] map = new int[32];
// map[0...31] = -2
// map[01100] = -2, 这个状态之前没出现过
Arrays.fill(map, -2);
map[0] = -1;
int ans = 0;
for (int i = 0, status = 0, m; i < n; i++) {
// status : 0....i-1字符串上,aeiou的奇偶性
// s[i] = 当前字符
// 情况1 : 当前字符不是元音,status不变
// 情况2 : 当前字符是元音,a~u(0~4),修改相应的状态
m = move(s.charAt(i));
if (m != -1) {
status ^= 1 << m;
}
// status: 0....i字符串上,aeiou的奇偶性
// 同样的状态,之前最早出现在哪
if (map[status] != -2) {
ans = Math.max(ans, i - map[status]);
} else {
map[status] = i;
}
}
return ans;
}
public static int move(char cha) {
switch (cha) {
case 'a': return 0;
case 'e': return 1;
case 'i': return 2;
case 'o': return 3;
case 'u': return 4;
default: return -1;
}
}
}