力扣 [剑指 offer] 专栏地址:剑指 Offer 力扣 (LeetCode)
class CQueue {
// 栈1用于处理插入操作
Deque<Integer> stack1;
// 栈2用于处理删除操作
Deque<Integer> stack2;
public CQueue() {
stack1 = new LinkedList<>();
stack2 = new LinkedList<>();
}
public void appendTail(int value) {
// 直接将数据插入栈1即可
stack1.push(value);
}
public int deleteHead() {
if(stack2.isEmpty()) {
// 如果栈2是空的,则将栈1的数据全部存入到栈2中
if(stack1.isEmpty()) {
// 如果栈1的也是空的,表明没有可以出栈的数据了,返回-1
return -1;
}
while(!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
// 栈2不为空,直接pop出第一个数据即可
return stack2.pop();
}
}
class MinStack {
// 栈1用于存储数据
Deque<Integer> stack1;
// 栈2用于存储最小值
Deque<Integer> stack2;
public MinStack() {
stack1 = new LinkedList<>();
stack2 = new LinkedList<>();
}
public void push(int x) {
// 栈1直接将x存入即可
stack1.push(x);
// 若x小于最小值min,则栈2存入x,否则存入min
if(x < min()) {
stack2.push(x);
} else {
stack2.push(min());
}
}
public void pop() {
// 出栈时两个栈同时pop元素即可
stack1.pop();
stack2.pop();
}
public int top() {
return stack1.peek();
}
public int min() {
// 若栈2为空,表示还未定义最小值,则使用int的最大值代替
if(stack2.isEmpty()) {
return Integer.MAX_VALUE;
}
return stack2.peek();
}
}
class Solution {
private int[] result;
private int length;
public int[] reversePrint(ListNode head) {
if(head == null) return new int[]{};
dfs(head, 0);
return result;
}
public void dfs(ListNode node, int index) {
if(node == null) {
// 获取到数组长度后初始化数组并回溯,将值填入
result = new int[length];
return;
}
length++;
dfs(node.next, index + 1);
result[length - index - 1] = node.val;
}
}
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null) return null;
// 分别用于存储上一个与下一个元素
ListNode prev = null, next = head.next;
while(next != null) {
// 让当前元素的next指向上一个元素
head.next = prev;
// 上一个元素更新为当前元素
prev = head;
// 当前元素更新为下一个元素
head = next;
// 下一个元素更新为下下个元素
next = head.next;
}
head.next = prev;
return head;
}
}
class Solution {
public Node copyRandomList(Node head) {
// 这题使用哈希表复制即可
if(head == null) return null;
Map<Node, Node> map = new HashMap<>();
for(Node node = head; node != null; node = node.next) {
map.put(node, new Node(node.val));
}
for(Node node = head; node != null; node = node.next) {
map.get(node).next = map.get(node.next);
map.get(node).random = map.get(node.random);
}
return map.get(head);
}
}
class Solution {
public String replaceSpace(String s) {
StringBuilder sb = new StringBuilder();
for(char c : s.toCharArray()) {
if(c == ' ') {
sb.append("%20");
} else {
sb.append(c);
}
}
return sb.toString();
}
}
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n) + s.substring(0, n);
}
}
class Solution {
public int findRepeatNumber(int[] nums) {
// 定义一个哈希表,对数值出现的次数进行计数
int[] hash = new int[nums.length];
for(int i = 0; i < nums.length; i++) {
if(++hash[nums[i]] > 1) {
return nums[i];
}
}
return -1;
}
}
class Solution {
public int search(int[] nums, int target) {
// 此题我们可以直接遍历一遍数组来求出答案
// 但题目给出一个条件——“排序数组”
// 那么,很显然,使用二分法会比直接遍历数组更优
int left = 0, right = nums.length, count = 0;
// 利用二分法查找目标数字第一次出现的位置
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
// 从第一个位置开始数,获取总的出现次数
while(left < nums.length && nums[left++] == target) {
count++;
}
return count;
}
}
// 此题可以使用位运算知识(异或)或者二分法来做,针对两种方式,笔者罗列在下
// 二分法
class Solution {
public int missingNumber(int[] nums) {
int n = nums.length, left = 0, right = n - 1;
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] == mid) {
left = mid + 1;
} else {
right = mid;
}
}
return left == nums[n - 1] ? n : left;
}
}
// 异或
class Solution {
public int missingNumber(int[] nums) {
int result = nums.length;
for(int i = 0; i < nums.length; i++) {
result ^= nums[i];
result ^= i;
}
return result;
}
}
class Solution {
// 题目给出的矩阵我们可以将其化为“二叉树”的问题
// 将矩阵最右上角的元素看成根节点
// 根节点左边的元素看为左子节点
// 根节点下边的元素看为右子节点
// 此时我们就将一个矩阵问题化为了“二叉搜索树”问题
// 只需要通过二叉搜索树的性质,查找对应的值即可
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int n = matrix.length;
if(n == 0) {
return false;
}
// i, j用于记录当前节点的坐标
int i = 0, j = matrix[0].length - 1;
while(i < n && j >= 0) {
if(matrix[i][j] == target) {
return true;
}
if(matrix[i][j] < target) {
i++; // 访问右子节点
} else {
j--; // 访问左子节点
}
}
return false;
}
}
class Solution {
// 此题依旧使用二分法来解题,突破点就在于如何移动左右指针
// 我们不妨先来看看示例给出的数据:
// numbers = [3,4,5,1,2];
// left = 0;
// right = 4;
// mid = (4 - 0) / 2 = 2;
// numbers[mid] = 5;
// numbers[right] = 2;
// 通过上述过程的思路推导多几轮我们不难发现:
// 当 numbers[right] < numbers[mid] 时左指针应该右移
// 当 numbers[right] > numbers[mid] 时右指针应该左移
// 当 numbers[right] == numbers[mid] 时右指针应该左移一位
public int minArray(int[] numbers) {
int left = 0, right = numbers.length - 1;
while(left < right) {
int mid = left + (right - left) / 2;
if(numbers[right] < numbers[mid]) {
left = mid + 1;
continue;
}
if(numbers[right] > numbers[mid]) {
right = mid;
continue;
}
right--;
}
return numbers[left];
}
}
class Solution {
public char firstUniqChar(String s) {
int[] hash = new int[26];
for(char c : s.toCharArray()) {
hash[c - 'a']++;
}
for(char c : s.toCharArray()) {
if(hash[c - 'a'] == 1) {
return c;
}
}
return ' ';
}
}
class Solution {
// 经典的bfs板子题,笔者这里就直接上代码了
public int[] levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> list = new LinkedList<>();
if(root != null) queue.add(root);
while(!queue.isEmpty()) {
TreeNode node = queue.poll();
list.add(node.val);
if(node.left != null) {
queue.offer(node.left);
}
if(node.right != null) {
queue.offer(node.right);
}
}
return list.stream().mapToInt(Integer::intValue).toArray();
}
}
class Solution {
// 仍然是一道标准的bfs板子题,直接上代码
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new LinkedList<>();
Queue<TreeNode> queue = new LinkedList<>();
if(root == null) {
return result;
}
queue.offer(root);
while(!queue.isEmpty()) {
List<Integer> list = new LinkedList<>();
int size = queue.size();
for(int i = 0; i < size; i++) {
TreeNode node = queue.poll();
list.add(node.val);
if(node.left != null) {
queue.offer(node.left);
}
if(node.right != null) {
queue.offer(node.right);
}
}
result.add(list);
}
return result;
}
}
class Solution {
// 此题在bfs的基础上做出了一点小改动,
// 普通的bfs都是从上到下,从左往右打印的
// 但此题要求的是从上到下,向从左往右再从右往左依次交替打印
// 因此我们只需要增加一个用于标识的变量记录当前从左开始还是从右开始即可
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new LinkedList<>();
LinkedList<TreeNode> queue = new LinkedList<>();
// 用于标识,true时从左往右,false时从右往左
boolean flag = true;
if(root == null) {
return result;
}
queue.offer(root);
while(!queue.isEmpty()) {
List<Integer> list = new LinkedList<>();
int size = queue.size();
for(int i = 0; i < size; i++) {
TreeNode node;
if(flag) {
node = queue.pollFirst();
} else {
node = queue.pollLast();
}
list.add(node.val);
if(flag) {
if(node.left != null) {
queue.offerLast(node.left);
}
if(node.right != null) {
queue.offerLast(node.right);
}
} else {
if(node.right != null) {
queue.offerFirst(node.right);
}
if(node.left != null) {
queue.offerFirst(node.left);
}
}
}
flag = !flag;
result.add(list);
}
return result;
}
}
class Solution {
// 此题需要对比A和B的结构相同,因此我们需要将遍历从A根节点开始,
// 遍历其所有的节点,寻找是否有符合所有节点与B相同的情况
// 难点在于多次的递归,如果思路没有整理好可能会被饶晕
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A == null || B == null) return false;
// 从A的根节点开始,若根节点不符合要求,则验证左右子节点
// 直到找到了与B结构相同的节点或整个A树被访问完为止
// 所有节点只要有返回true则表明A包含了B的结构
return dfs(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
}
public boolean dfs(TreeNode A, TreeNode B) {
// 当B被访问完,表明A中包含B,返回true;
if(B == null) return true;
// 当A被访问完,表明A中不包含B,返回false;
if(A == null) return false;
// 当A与B当前节点的值相同时才继续往下遍历;
return A.val == B.val && dfs(A.left, B.left) && dfs(A.right, B.right);
}
}
class Solution {
// 此题dfs深搜一遍所有节点,并将其左右节点进行交换即可达到镜像效果
public TreeNode mirrorTree(TreeNode root) {
dfs(root);
return root;
}
public void dfs(TreeNode root) {
if(root == null) return;
// 进行左右节点交换的操作
TreeNode left = root.right;
TreeNode right = root.left;
root.left = left;
root.right = right;
// 访问下一个左右节点
dfs(root.left);
dfs(root.right);
}
}
class Solution {
// 这是一道多源dfs题目,在写代码之前我们不妨先来观察一下示例给出的树结构
// 1 a
// / \ / \
// 2 2 b c
// / \ / \ / \ / \
// 3 4 4 3 d e f g
// 为了更好的讲解,笔者将数字改用字母表示
// 满足什么条件下,这棵树才满足对称的要求?
// 很显然,当a的左节点b等于右节点c,
// 且b的左节点等于c的右节点,b的右节点等于c的左节点时满足题目的要求,
// 不难发现规律,当两个兄弟节点值相同时,节点一的左节点等于节点二的右节点,节点二的右节点等于节点一的左节点
// 依照这一规律编写出代码即可
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return dfs(root.left, root.right);
}
public boolean dfs(TreeNode left, TreeNode right) {
if(left == null && right == null) {
return true;
}
if(left == null || right == null) {
return false;
}
return left.val == right.val && dfs(left.left, right.right) && dfs(left.right, right.left);
}
}
class Solution {
public int fib(int n) {
if(n <= 1) return n;
int[] dp = new int[n + 1];
dp[1] = 1;
for(int i = 2; i <= n; i++) {
dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007;
}
return dp[n];
}
}
class Solution {
// 这题其实就是“斐波那契数列”问题,不同之处在于n=0时,dp[0] = 1;
public int numWays(int n) {
if(n <= 1) return 1;
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; i++) {
dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007;
}
return dp[n];
}
}
class Solution {
// 使用mi变量维护一个最小值
// 若prices[i] > min,更新ans
// 若prices[i] < min,更新min
public int maxProfit(int[] prices) {
if(prices.length == 0) {
return 0;
}
int ans = 0, min = prices[0];
for(int i = 1; i < prices.length; i++) {
if(min < prices[i]) {
ans = Math.max(ans, prices[i] - min);
} else {
min = prices[i];
}
}
return ans;
}
}
class Solution {
// 1.我们可以从第一个数开始,计算连续的和sum,并记录此范围内和的最大值
// 2.当 sum <= 0 时,当前的和已经不利于后面元素求最大和了,更新sum的值
public int maxSubArray(int[] nums) {
int res = nums[0], sum = 0;
for(int num : nums) {
if(sum <= 0) {
sum = num;
} else {
sum += num;
}
res = Math.max(res, sum);
}
return res;
}
}
class Solution {
// 1.在实际运算的过程中,我们为矩阵围多了一层0,如下
// 0 0 0 0
// 0 1 3 1
// 0 1 5 1
// 0 4 2 1
// 2.因为每次只能向下或向右移动,则可以得知状态转移方程应为
// dp[i][j] = value + max(dp[i - 1][j], dp[i][j - 1]);
public int maxValue(int[][] grid) {
int n = grid.length, m = grid[0].length;
int[][] dp = new int[n + 1][m + 1];
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
}
}
return dp[n][m];
}
}
class Solution {
// 1.仔细观察题目,我们不难发现这是“青蛙跳阶梯”的一道变种题目
// 2.在“青蛙跳阶梯”的基础上,加上数字范围的判断即可解出此题
public int translateNum(int num) {
char[] cs = String.valueOf(num).toCharArray();
int n = cs.length;
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; i++) {
int j = (cs[i - 2] - '0') * 10 + (cs[i - 1] - '0');
if(j >= 10 && j <= 25) {
dp[i] = dp[i - 1] + dp[i - 2];
} else {
dp[i] = dp[i - 1];
}
}
return dp[n];
}
}
class Solution {
// 1.使用set确保连续范围内的字符不重复
// 2.当遇到字符重复时,开始移动左指针,直到连续范围内无重复字符
//
// 例子如字符串:“dvdf”
// 左右指针初始为0,左指针保持不动,移动右指针,当右指针到达下标2时,
// 出现了两个字符“d”,此时我们需要移动左指针,直到左右指针范围内的字符不重复
public int lengthOfLongestSubstring(String s) {
Set<Character> set = new HashSet<>();
char[] cs = s.toCharArray();
int res = 0;
for(int left = 0, right = 0; right < cs.length; right++) {
while(set.contains(cs[right])) {
set.remove(cs[left++]);
}
set.add(cs[right]);
res = Math.max(res, set.size());
}
return res;
}
}
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head == null) {
return null;
}
if(head.val == val) {
return head.next;
}
head.next = deleteNode(head.next, val);
return head;
}
}
class Solution {
// 定义左右指针,让右指针比左指针先走k步,然后再让他们一起一步一步的走
// 当右指针为null时,左指针对应的位置就是倒数第k个节点
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode left = head, right = head;
for(int i = 0; i < k; i++) {
right = right.next;
}
while(right != null) {
left = left.next;
right = right.next;
}
return left;
}
}
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) return l2;
if(l2 == null) return l1;
if(l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
public class Solution {
// 这题笔者一开始没有做出来,但看了下评论区大哥的一句话,笔者悟了~!!
//
// “太浪漫了 两个结点不断的去对方的轨迹中寻找对方的身影,只要二人有交集,就终会相遇❤”
//
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) {
return null;
}
ListNode a = headA, b = headB;
while(a != b) {
a = a == null ? headB : a.next;
b = b == null ? headA : b.next;
}
return a;
}
}
class Solution {
// 遇到奇数就将其放到上一个奇数的下一位即可
public int[] exchange(int[] nums) {
for(int i = 0, j = 0; i < nums.length; i++) {
if((nums[i] & 1) == 1) {
int temp = nums[j];
nums[j] = nums[i];
nums[i] = temp;
j++;
}
}
return nums;
}
}
class Solution {
// 由于数组时递增的,因此我们只需要从最左和最右开始往中间移动就可找到答案
public int[] twoSum(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left < right) {
int sum = nums[left] + nums[right];
if(sum == target) {
return new int[]{nums[left], nums[right]};
}
if(sum < target) {
left++;
} else {
right--;
}
}
return new int[]{};
}
}
class Solution {
// 将字符串按空格切割后,从后往前拼接即可
public String reverseWords(String s) {
StringBuilder sb = new StringBuilder();
String[] strings = s.split(" ");
for(int i = strings.length - 1; i >= 0; i--) {
if("".equals(strings[i].trim())) {
continue;
}
sb.append(strings[i].trim() + " ");
}
return sb.toString().trim();
}
}
class Solution {
// 很明显这是一道深搜的题目,处理好剪枝即可
public boolean exist(char[][] board, String word) {
int n = board.length, m = board[0].length;
// 记录已经走过的位置,防止走回头路
boolean[][] visited = new boolean[n][m];
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(dfs(board, word, visited, i, j, 0)) {
return true;
}
}
}
return false;
}
public boolean dfs(char[][] board, String word, boolean[][] visited, int i, int j, int z) {
int n = board.length, m = board[0].length;
// 越界、位置已被访问或当前字符不符合word要求都直接返回false
if(i < 0 || j < 0 || i >= n || j >= m || visited[i][j] || board[i][j] != word.charAt(z)) {
return false;
}
// 满足word后返回true
if(word.length() - 1 == z) {
return true;
}
int[][] moves = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
// 标记
visited[i][j] = true;
for(int[] move : moves) {
int k = i + move[0];
int v = j + move[1];
if(dfs(board, word, visited, k, v, z + 1)) {
return true;
}
}
// 撤销标记
visited[i][j] = false;
return false;
}
}
class Solution {
// 因为 “1 <= n,m <= 100”,所以当我们判断机器人是否可以移动到某一格时
// 可以使用如下公式 “(i%10 + i/10 + j%10 + j/10) > k”;
// 其余就与普通的dfs题目无异了
public int movingCount(int m, int n, int k) {
boolean[][] visited = new boolean[m][n];
return dfs(visited, k, 0, 0);
}
public int dfs(boolean[][] visited, int k, int i, int j) {
int m = visited.length, n = visited[0].length;
if(i < 0 || j < 0 || i >= m || j >= n || visited[i][j] || (i%10 + i/10 + j%10 + j/10) > k) {
return 0;
}
visited[i][j] = true;
return dfs(visited, k, i + 1, j) + dfs(visited, k, i - 1, j) +
dfs(visited, k, i, j + 1) + dfs(visited, k, i, j - 1) + 1;
}
}
class Solution {
// 存储最终的答案
private List<List<Integer>> list = new LinkedList<>();
// 存储一条路径的值
private List<Integer> track = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
dfs(root, target, 0);
return list;
}
public void dfs(TreeNode root, int target, int count) {
if(root == null) {
return;
}
// 选择
count += root.val;
track.add(root.val);
// 当数值和达到目标值并且是叶子节点,将此路径存入
if(count == target && root.left == null && root.right == null) {
list.add(new LinkedList(track));
track.remove(track.size() - 1);
return;
}
dfs(root.left, target, count);
dfs(root.right, target, count);
// 撤销选择
track.remove(track.size() - 1);
}
}
class Solution {
private Node head, prev;
public Node treeToDoublyList(Node root) {
if(root == null) return null;
dfs(root);
// 因为是中序遍历,遍历到最后一个元素时,prev就是最后一个元素
// 因此,首尾相连只需要将head和prev连接起来即可
head.left = prev;
prev.right = head;
return head;
}
public void dfs(Node node) {
if(node == null) {
return;
}
// 访问左子树
dfs(node.left);
// prev为空时,表明node是第一个元素,将其赋予head
// 否则让其上一个节点指向当前节点
if(prev == null) {
head = node;
} else {
prev.right = node;
}
// 当前节点指向上一个节点
node.left = prev;
prev = node;
// 访问右子树
dfs(node.right);
}
}
class Solution {
// 后续遍历一下即可
private int n = 0, result = 0;
public int kthLargest(TreeNode root, int k) {
traverse(root, k);
return result;
}
public void traverse(TreeNode root, int k) {
if(root == null) {
return;
}
traverse(root.right, k);
if(k == ++n) {
result = root.val;
return;
}
traverse(root.left, k);
}
}
class Solution {
// 此题解是笔者在评论区看到的一个非常简洁的方法
// 最关键的点在于理解 a + b < b + a
// 如:3,34
// 两个数拼接起来可以是 334 和 343
// 很明显,前者会比后者小,因此根据此要点对数组进行排序即可得出答案
public String minNumber(int[] nums) {
List<String> list = new LinkedList<>();
for(int num : nums) {
list.add(String.valueOf(num));
}
list.sort((a, b) -> (a + b).compareTo(b + a));
return String.join("", list);
}
}
class Solution {
// 我们可以先对数组进行排序,然后输出数字“0”的个数
// 然后对于其他的数字,获取他们的“差值-1”的和,即两个数之间间隔了多少个数,
// 如果0的数目可以填补这些空缺,则是顺子,否则不是
public boolean isStraight(int[] nums) {
int zero = 0, diff = 0;
Arrays.sort(nums);
for(int i = 0; i < nums.length - 1; i++) {
if(nums[i] == 0) {
zero++;
continue;
}
if(nums[i + 1] == nums[i]) {
return false;
}
if(nums[i + 1] - nums[i] > 1) {
diff += nums[i + 1] - nums[i] - 1;
}
}
return zero >= diff;
}
}
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(arr.length == k) {
return arr;
}
Arrays.sort(arr);
int[] res = new int[k];
for(int i = 0; i < k; i++) {
res[i] = arr[i];
}
return res;
}
}
class MedianFinder {
// 用于存储数值较大的那一半数据
private PriorityQueue<Integer> queue1;
// 用于存储数值较小的那一半数据
private PriorityQueue<Integer> queue2;
/** initialize your data structure here. */
public MedianFinder() {
// 正序
queue1 = new PriorityQueue<>((a, b) -> (a - b));
// 倒序
queue2 = new PriorityQueue<>((a, b) -> (b - a));
}
public void addNum(int num) {
// num直接存入到存放大数值的队列中
queue1.offer(num);
// 将大数值队列中最小的数存入到小数值队列
queue2.offer(queue1.poll());
// 当小数值的队列的元素个数比大数值队列超过1时,将最大的那个元素存入到大数值的队列中
if(queue2.size() > queue1.size() + 1) {
queue1.offer(queue2.poll());
}
}
public double findMedian() {
if(queue1.size() == queue2.size()) {
return (double) (queue1.peek() + queue2.peek()) / 2;
}
return queue2.peek();
}
}
class Solution {
// 典型的深搜题目,dfs一下即可
public int maxDepth(TreeNode root) {
return dfs(root, 0);
}
public int dfs(TreeNode root, int deep) {
if(root == null) {
return deep;
}
return Math.max(
dfs(root.left, deep + 1),
dfs(root.right, deep + 1)
);
}
}
class Solution {
// 仍然是一道深搜题目,此题我们只需要对整个树进行一次深搜
// 兄弟节点之间的深度差不大于1即可
private boolean flag = true;
public boolean isBalanced(TreeNode root) {
dfs(root);
return flag;
}
public int dfs(TreeNode node) {
if(node == null || !flag) {
return 0;
}
int left = dfs(node.left);
int right = dfs(node.right);
if(Math.abs(left - right) > 1) {
flag = false;
return 0;
}
return Math.max(left, right) + 1;
}
}
class Solution {
public int sumNums(int n) {
int sum = n;
boolean flag = n > 0 && (sum += sumNums(n - 1)) > 0;
return sum;
}
}
class Solution {
// 根据二叉搜索树的性质:左子节点的值 > 父节点的值 > 右子节点的值
// 而这一性质还使用于两个节点及其最近的公共祖先中
// 因此我们只需要找到一个数值处于节点p和节点q之间的节点即是他们最近的公共祖先
// 即:p.val < roo.val < q.val
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return null;
// 当前节点的值都大于p、q两个节点,当前节点需要左移
if(p.val < root.val && q.val < root.val) {
return lowestCommonAncestor(root.left, p, q);
}
// 当前节点的值都小于p、q两个节点,当前节点需要右移
if(p.val > root.val && q.val > root.val) {
return lowestCommonAncestor(root.right, p, q);
}
return root;
}
}
class Solution {
// 上一题“二叉搜索树最近公共祖先”,我们可以通过二叉搜索树的性质来解题,但这一题却不能再用这种思路了
// 首先我们要来分析符合题目的情况右多少种
// 1.当前节点root等于q或者p,表明q是p的祖先或p是q的祖先
// 2.p和q在当前节点root的左右两边
// 3.p和q全在当前节点root的左边
// 4.p和q泉州当前节点root的右边
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) {
return null;
}
// 1.p是q的祖先或q是p的祖先
if(root == p || root == q) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
// 2.p和q在当前节点root的左右两边
if(left != null && right != null) {
return root;
}
// 3.p和q全在当前节点root的左边
if(left != null) return left;
// 3.p和q全在当前节点root的右边
if(right != null) return right;
return null;
}
}
class Solution {
// 用于存储中序遍历的数据,目的是为了方便查找节点在中序遍历总的下标
private Map<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 createTree(preorder, 0, 0, inorder.length - 1);
}
public TreeNode createTree(int[] preorder, int preIndex, int left, int right) {
if(left > right) return null;
TreeNode root = new TreeNode(preorder[preIndex]);
// 获取当前节点在中序遍历中对应的下标值
int inIndex = map.get(preorder[preIndex]);
// 中序遍历中,inIndex左边的节点(left ~ inIndex - 1)都为该节点的左节点
root.left = createTree(preorder, preIndex + 1, left, inIndex - 1);
// 中序遍历中,inIndex右边的节点(preIndex + inIndex - left + 1 ~ inIndex + 1, right)都为该节点的右节点
// 注:inIndex - left + 1 为该节点的左节点数目
root.right = createTree(preorder, preIndex + inIndex - left + 1, inIndex + 1, right);
return root;
}
}
class Solution {
// 标准的快速幂板子题,直接套快速幂模板即可
public double myPow(double x, int n) {
double result = 1;
long v = n;
if(v < 0) {
x = 1 / x;
v = -v;
}
while(v > 0) {
if((v & 1) == 1) {
result *= x;
}
v >>= 1;
x *= x;
}
return result;
}
}
class Solution {
public boolean verifyPostorder(int[] postorder) {
Deque<Integer> stack = new LinkedList<>();
int preValue = Integer.MAX_VALUE;
for(int i = postorder.length - 1; i >= 0; i--) {
if(postorder[i] > preValue) {
return false;
}
while(!stack.isEmpty() && postorder[i] < stack.peek()) {
preValue = stack.pop();
}
stack.push(postorder[i]);
}
return true;
}
}
public class Solution {
// 这题有很多种解题方式,转成字符串数1的字符个数,调用api等
// 因为官方为这题贴的是位运算的标签,笔者这里就贴出使用位运算的代码
// 原理很简单,就和我们之前拆分十进制数一样,只是取模和除数从10变为了2
public int hammingWeight(int n) {
int count = 0;
while(n != 0) {
count += n & 1; // n & 1 等价于 n % 2
n >>>= 1; // n >>> 1 等价于 n / 2
}
return count;
}
}
class Solution {
// 位运算知识,a加b等于a与b左移一位的结果和a异或b的结果之和
// 即:a + b = ((a & b) << 1) + (a ^ b);
public int add(int a, int b) {
if(a == 0) return b;
return add((a & b) << 1, a ^ b);
}
}
class Solution {
public int[] singleNumbers(int[] nums) {、
// 通过将数组中的所有数据进行异或操作,
// 我们可以得到那两个不同数a,b的异或结果
// 剩下的任务就是将他们分开了
int sum = 0;
for(int num : nums) {
sum ^= num;
}
// 获取最低位的1
int flag = sum & (-sum), res = 0;
for(int num : nums) {
// 通过最低位的1,将数据分为两组
if((flag & num) != 0) {
res ^= num;
}
}
// 此时res已经是a,b中的其中一个数,此时再进行一次异或操作即可得到另一值
return new int[]{res, sum ^ res};
}
}
class Solution {
// 这题笔者能力有限,看不懂使用位运算的做法
// 只好用了最笨的方法来解题了
public int singleNumber(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for(int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
for(Map.Entry<Integer, Integer> entry : map.entrySet()) {
if(entry.getValue() == 1) {
return entry.getKey();
}
}
return -1;
}
}
class Solution {
// 我们可以直接对数组进行排序,然后取中间值来完成
// 笔者在这里使用另一种方式来解答此题——摩尔投票法
// 摩尔投票法:两个不同的数相遇就直接同归于尽,最终剩下的那个数一定是数量超过一半的
public int majorityElement(int[] nums) {
int result = 0, count = 0;
for(int i = 0; i < nums.length; i++) {
if(count == 0) {
result = nums[i];
count++;
} else {
if(result == nums[i]) {
count++;
} else {
count--;
}
}
}
return result;
}
}
class Solution {
// 我们可以将所有的数乘起来,然后除以当前的数即可得到答案
// 但题目中有限制,不可以使用除法
// 我们可以换一个思路,既然不能直接将所有数相乘除以当前数
// 那我们可以先将左右两边的数相乘后,再让左右两边相乘的总和再相乘一次,即是题目要求的答案
public int[] constructArr(int[] a) {
int n = a.length;
int[] b = new int[n];
// 先将下标i左边的数全部相乘起来
for(int i = 0, pre = 1; i < n; pre *= a[i], i++) {
b[i] = pre;
}
// 后将小标i右边的数全部相乘起来,并与前面得到的左边相乘的结果再一次相乘
for(int i = n - 1, pre = 1; i >= 0; pre *= a[i], i--) {
b[i] *= pre;
}
return b;
}
}
class Solution {
// 这其实是一道数论题目,如果不懂得相应的数学知识可能很难解答出来
// “任何大于1的数都可由2和3相加组成”
// 因此我们只需要尽可能的将绳子以长度为3逐段减去,即可得到最大乘积
public int cuttingRope(int n) {
if(n <= 3) {
return n - 1;
}
int m = 1;
while(n > 4) {
n -= 3;
m *= 3;
}
// 剪到最后,绳子可能还剩下4、3、2
// 4无需再拆分,因为3*1和2*2都大于4
return n * m;
}
}
class Solution {
// 双指针思路:一直移动右指针,当和大于目标值时,开始移动左指针
public int[][] findContinuousSequence(int target) {
List<int[]> list = new LinkedList<>();
for(int left = 1, right = 1, sum = 0; right < target; right++) {
sum += right;
// 和大于目标值,开始移动左指针
while(sum > target) {
sum -= left++;
}
// 和等于目标值,将其结果存入list中
if(sum == target) {
int[] nums = new int[right - left + 1];
for(int i = 0; i < nums.length; i++) {
nums[i] = left + i;
}
list.add(nums);
}
}
int[][] result = new int[list.size()][];
for(int i = 0; i < list.size(); i++) {
result[i] = list.get(i);
}
return result;
}
}
class Solution {
// 典型的约瑟夫环问题,而后推到即可得到答案
// 具体推导过程可看下面的这篇博客
// https://blog.csdn.net/u011500062/article/details/72855826
public int lastRemaining(int n, int m) {
int res = 0;
for (int i = 2; i <= n; i++) {
res = (res + m) % i;
}
return res;
}
}
class Solution {
public int[] spiralOrder(int[][] matrix) {
if(matrix.length == 0) {
return new int[]{};
}
int n = matrix.length, m = matrix[0].length;
// 给矩阵围上一层true,方便用于判断转角
boolean[][] visited = new boolean[n + 2][m + 2];
List<Integer> list = new LinkedList<>();
int i = 1, j = 1, flag = 0, mul = n * m;
// 初始化外围围墙,设置为true
for(int k = 0; k < n + 2; k++) {
for(int v = 0; v < m + 2; v++) {
if(k == 0 || v == 0 || k == n + 1 || v == m + 1) {
visited[k][v] = true;
}
}
}
while(list.size() < mul) {
// 转角判断
if(visited[i][j + 1] && flag == 0 || visited[i + 1][j] && flag == 1
|| visited[i][j - 1] && flag == 2 || visited[i - 1][j] && flag == 3) {
flag = flag == 3 ? 0 : flag + 1;
}
list.add(matrix[i - 1][j - 1]);
visited[i][j] = true;
if(flag == 0) j++;
if(flag == 1) i++;
if(flag == 2) j--;
if(flag == 3) i--;
}
int[] result = new int[list.size()];
for(int v = 0; v < list.size(); v++) {
result[v] = list.get(v);
}
return result;
}
}
class Solution {
// 使用一个栈来进行模拟,每次压pushed中的元素进栈时,
// 都对popped中j指针指定的元素进行对比,如果相等则直接弹出,并将指针右移
public boolean validateStackSequences(int[] pushed, int[] popped) {
Deque<Integer> stack = new LinkedList<>();
int n = pushed.length;
for(int i = 0, j = 0; i < n; i++) {
stack.push(pushed[i]);
while(j < n && !stack.isEmpty() && stack.peek() == popped[j]) {
stack.pop();
j++;
}
}
return stack.isEmpty();
}
}
class Solution {
// 这题笔者写了好久,始终是无法通过,只能耍点小聪明了
public boolean isNumber(String s) {
if(s.contains("F") || s.contains("f")
|| s.contains("D") || s.contains("d")) {
return false;
}
try {
Double.valueOf(s);
} catch(Exception e) {
return false;
}
return true;
}
}
class Solution {
// 这题按照题目要求每步一判断,主要考的是细心,并没有什么太高的技巧
// 需要注意的是,我们用于存储的变量应该使用long类型的变量,以免出现不必要的溢出错误
// 在判断最小值和最大值时要注意,最大值是-2147483648,最大值是2147483647
public int strToInt(String str) {
// 去除首尾不必要的空格
str = str.trim();
// 用于存储答案
long result = 0;
// 标记正负,1位正,-1为负,0表示还未开始进行判断
int flag = 0;
for(int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if(flag == 0) {
// 开始判断正负数
if(c == '+' || c == '-' || c >= '0' && c <= '9') {
flag = c == '-' ? -1 : 1;
if(!(c == '+' || c == '-')) {
result = c - '0';
}
} else {
// 第一个字符不是+,-或是数值,直接返回0
return 0;
}
} else {
if(c >= '0' && c <= '9') {
result = result * 10 + (c - '0');
// 边界判断
if(flag == 1 && result >= Integer.MAX_VALUE)
return Integer.MAX_VALUE;
if(flag == -1 && result - 1 >= Integer.MAX_VALUE)
return Integer.MIN_VALUE;
} else {
// 当前字符不是数值,无需再查找了,退出循环
break;
}
}
}
return flag == -1 ? (int)-result : (int)result;
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
// 这里有个坑,题目写明“在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小”
// 因此,当数组为空时这个k其实是可以为0的
if(nums.length == 0) return new int[]{};
// 用于存储答案
int[] res = new int[nums.length - k + 1];
// 用于存储当前滑动窗口内最大的值的下标(双端链表)
LinkedList<Integer> queue = new LinkedList<>();
for(int i = 0; i < nums.length; i++) {
// 窗口开始移动,将头元素弹出
if(!queue.isEmpty() && i - queue.peekFirst() >= k) {
queue.pollFirst();
}
// 从尾元素开始,小于当前值的元素都可以弹出了,因为当头元素被弹出后
// 相对于这些被弹出的元素而言,当前元素将是他们的最大值,因此已经没有必要存储他们了
while(!queue.isEmpty() && nums[i] > nums[queue.peekLast()]) {
queue.pollLast();
}
// 将当前元素存入队尾
queue.offerLast(i);
if(i >= k - 1) {
// 将头元素存入res中
res[i - k + 1] = nums[queue.peekFirst()];
}
}
return res;
}
}
class MaxQueue {
Deque<Integer> queue;
Deque<Integer> stack;
public MaxQueue() {
queue = new LinkedList<>();
stack = new LinkedList<>();
}
public int max_value() {
if(stack.isEmpty()) {
return -1;
}
return stack.peek();
}
public void push_back(int value) {
queue.offer(value);
while(!stack.isEmpty() && value > stack.peekLast()) {
stack.pollLast();
}
stack.offer(value);
}
public int pop_front() {
if(queue.isEmpty()) {
return -1;
}
int value = queue.pop();
if(value == stack.peek()) {
stack.pop();
}
return value;
}
}
public class Codec {
private String[] values;
private int i;
public String serialize(TreeNode root) {
if(root == null) {
return "#,";
}
StringBuilder sb = new StringBuilder();
sb.append(root.val + ",");
sb.append(serialize(root.left));
sb.append(serialize(root.right));
return sb.toString();
}
public TreeNode deserialize(String data) {
values = data.split(",");
return helper();
}
public TreeNode helper() {
String value = values[i++];
if("#".equals(value)) {
return null;
}
TreeNode root = new TreeNode(Integer.valueOf(value));
root.left = helper();
root.right = helper();
return root;
}
}
class Solution {
// 看了一眼字符串长度的限制在 ”1~8“,因此可以使用”全排列“来解答
private Set<String> set = new HashSet<>();
private char[] cs;
public String[] permutation(String s) {
cs = s.toCharArray();
dfs(0);
return set.toArray(new String[]{});
}
public void dfs(int i) {
if(i == cs.length) {
set.add(new String(cs));
return;
}
for(int j = i; j < cs.length; j++) {
swap(i, j);
dfs(i + 1);
swap(i, j);
}
}
public void swap(int i, int j) {
char c = cs[i];
cs[i] = cs[j];
cs[j] = c;
}
}
class Solution {
// 这题笔者直接调api摆烂了,实在无能为力~~
public boolean isMatch(String s, String p) {
return s.matches(p);
}
}
class Solution {
// 这个解题思路是笔者从评论区“偷学”而来的
// 丑数是只由2、3、5为因子组成的数,因此当我们通过这三个数可以把丑数分为三组
// 以2为因子分为一组:{2*1, 2*2, 2*3, 2*4, 2*5, ...}
// 以3为因子分为一组:{3*1, 3*2, 3*3, 3*4, 3*5, ...}
// 以5为因子分为一组:{5*1, 5*2, 5*3, 5*4, 5*5, ...}
// 分成的三组都为递增数组,则我们只需要将这三个数组的值合并并除去重复值即可得出答案
public int nthUglyNumber(int n) {
int[] dp = new int[n + 1];
int i = 1, j = 1, k = 1;
dp[1] = 1;
for(int v = 2; v <= n; v++) {
int min = Math.min(dp[i] * 2, Math.min(dp[j] * 3, dp[k] * 5));
if(min == dp[i] * 2) i++;
if(min == dp[j] * 3) j++;
if(min == dp[k] * 5) k++;
dp[v] = min;
}
return dp[n];
}
}
class Solution {
// 具体题解可看如下文章:
// https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/solution/jian-zhi-offer-60-n-ge-tou-zi-de-dian-sh-z36d/
public double[] dicesProbability(int n) {
double[] res = new double[6];
Arrays.fill(res, 1.0 / 6.0);
for(int i = 2; i <= n; i++) {
double[] tmp = new double[i * 6 - (i - 1)];
for(int j = 0; j < res.length; j++) {
for(int k = 0; k < 6; k++) {
tmp[k + j] += res[j] * 1.0 / 6.0;
}
}
res = tmp;
}
return res;
}
}
class Solution {
public int[] printNumbers(int n) {
int m = (int) Math.pow(10, n);
int[] a = new int[m - 1];
for(int i = 0;i < m - 1; i++){
a[i] = i + 1;
}
return a;
}
}
class Solution {
// 此题笔者一开始只能想到暴力解题,毫无疑问,肯定超时了
// 在评论区看到有大佬说其实是个归并排序的问题,才恍然大悟
private int count = 0;
public int reversePairs(int[] nums) {
sort(nums, 0, nums.length - 1);
return count;
}
public int[] sort(int[] nums, int left, int right) {
if(left < right) {
int mid = left + (right - left) / 2;
sort(nums, left, mid);
sort(nums, mid + 1, right);
merge(nums, left, mid, right);
}
return nums;
}
public void merge(int[] nums, int left, int mid, int right) {
int[] memo = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while(i <= mid && j <= right) {
if(nums[i] <= nums[j]) {
memo[k++] = nums[i++];
} else {
count += mid - i + 1;
memo[k++] = nums[j++];
}
}
while(i <= mid) {
memo[k++] = nums[i++];
}
while(j <= right) {
memo[k++] = nums[j++];
}
for(int v = 0; v < memo.length; v++) {
nums[left + v] = memo[v];
}
}
}
class Solution {
// 和“剪绳子”是一样的,只是数据大了些需要取模罢了
// 在“剪绳子”的代码基础上稍作修改即可
public int cuttingRope(int n) {
if(n <= 3) {
return n - 1;
}
long m = 1;
while(n > 4) {
n -= 3;
m *= 3;
m = m > 1000000007 ? m % 1000000007 : m;
}
return (int) (m * n % 1000000007);
}
}
// 这题笔者能力有限,暂时没有搞懂~~
class Solution {
// 具体题解可看如下文章:
// https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/solution/mian-shi-ti-44-shu-zi-xu-lie-zhong-mou-yi-wei-de-6/
public int findNthDigit(int n) {
long start = 1, count = 9;
int digit = 1;
while(n > count) {
start *= 10;
digit += 1;
n -= count;
count = start * digit * 9;
}
long num = start + (n - 1) / digit;
return Long.toString(num).charAt((n - 1) % digit) - '0';
}
}