文章目录
- 剑指 Offer 26. 树的子结构
- 剑指 Offer 28. 对称的二叉树
- 剑指 Offer 27. 二叉树的镜像
- 剑指 Offer 32 - I. 从上到下打印二叉树
- 剑指 Offer 32 - II. 从上到下打印二叉树 II
- 剑指 Offer 32 - III. 从上到下打印二叉树 III
- 剑指 Offer 30. 包含min函数的栈
- 剑指 Offer 29. 顺时针打印矩阵
- 剑指 Offer 31. 栈的压入、弹出序列
- 剑指 Offer 33. 二叉搜索树的后序遍历序列
- 剑指 Offer 34. 二叉树中和为某一值的路径
- 剑指 Offer 35. 复杂链表的复制
- 剑指 Offer 38. 字符串的排列
- 剑指 Offer 39. 数组中出现次数超过一半的数字
- 剑指 Offer 40. 最小的k个数
- 剑指 Offer 42. 连续子数组的最大和
- 剑指 Offer 43. 1~n整数中1出现的次数
- 剑指 Offer 44. 数字序列中某一位的数字
- 剑指 Offer 45. 把数组排成最小的数
- 剑指 Offer 46. 把数字翻译成字符串
- 剑指 Offer 47. 礼物的最大价值
- 剑指 Offer 48. 最长不含重复字符的子字符串
- 剑指 Offer 49. 丑数
- 剑指 Offer 50. 第一个只出现一次的字符
- 剑指 Offer 52. 两个链表的第一个公共节点
- 剑指 Offer 54. 二叉搜索树的第k大节点
- 剑指 Offer 55 - I. 二叉树的深度
- 剑指 Offer 55 - II. 平衡二叉树
- 剑指 Offer 53 - I. 在排序数组中查找数字 I
- 剑指 Offer 53 - II. 0~n-1中缺失的数字
- 剑指 Offer 56 - I. 数组中数字出现的次数
- 剑指 Offer 56 - II. 数组中数字出现的次数 II
- 剑指 Offer 57. 和为s的两个数字
- 剑指 Offer 57 - II. 和为s的连续正数序列
- 剑指 Offer 58 - I. 翻转单词顺序
- 剑指 Offer 58 - II. 左旋转字符串
- 剑指 Offer 60. n个骰子的点数
- 剑指 Offer 61. 扑克牌中的顺子
- 剑指 Offer 65. 不用加减乘除做加法
- 剑指 Offer 66 构建乘积数组
- 剑指 Offer 67. 把字符串转换成整数
- 剑指 Offer 68 - I 二叉搜索树的最近公共祖先
- 剑指 Offer 68 - II 二叉树的最近公共祖先
剑指 Offer 26. 树的子结构
二叉树匹配类问题总结
B 属于 A 的一部分也可以,没必要一直匹配到叶子节点
递归解法
recur的返回条件:
当节点 B 为空:说明树 B 已匹配完成(越过叶子节点),因此返回 true ;
当节点 A 为空:说明已经越过树 A 叶子节点,即匹配失败,返回 false;
剑指 Offer 28. 对称的二叉树
1.递归解法
2.非递归解法(队列)
剑指 Offer 27. 二叉树的镜像
1.递归解法
2.非递归解法(栈或队列)
剑指 Offer 32 - I. 从上到下打印二叉树
非空的不加入队列
非空的也加入队列,出队之后进行判断
剑指 Offer 32 - II. 从上到下打印二叉树 II
队列(BFS)
安安写法
精选答案写法 就是对null的处理不太一样
递归DFS实现
剑指 Offer 32 - III. 从上到下打印二叉树 III
队列(BFS)
linkedlist首尾添加
arraylist首尾添加
递归
双栈法
//Java 安安 2020.3.31
//根节点作为第0行
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
Stack<TreeNode> s1 = new Stack<>(); //存储奇数行
Stack<TreeNode> s2 = new Stack<>(); //存储偶数行
if(root != null) s2.push(root);
while(!s1.empty() || !s2.empty()) {
List<Integer> tmp = new ArrayList<>();
if(!s2.empty()){
while(!s2.empty()){
TreeNode p = s2.pop();
tmp.add(p.val);
if(p.left != null) s1.push(p.left);
if(p.right != null) s1.push(p.right);
}
res.add(tmp);
}
if(!s1.empty()){
tmp = new ArrayList<>();
while(!s1.empty()){
TreeNode p = s1.pop();
tmp.add(p.val);
if(p.right != null) s2.push(p.right);
if(p.left != null) s2.push(p.left);
}
res.add(tmp);
}
}
return res;
}
}
剑指 Offer 30. 包含min函数的栈
同步
不同步
两个 peek() 方法返回的都是 Integer 类型 ,它们的比较不能用 ==,因为 == 用于包装类型(它们都是对象)的比较,比较的是它们的内存地址,解决方法也很简单:(1)改用 equals() 方法,(2)至少把其中一个变量转成 int 型。
剑指 Offer 29. 顺时针打印矩阵
模拟矩阵路径
按层模拟
剑指 Offer 31. 栈的压入、弹出序列
剑指 Offer 33. 二叉搜索树的后序遍历序列
递归分治
剑指 Offer 34. 二叉树中和为某一值的路径
剑指 Offer 35. 复杂链表的复制
哈希表:存放新旧节点的对应关系
原地复制
剑指 Offer 38. 字符串的排列
剑指 Offer 39. 数组中出现次数超过一半的数字
剑指 Offer 40. 最小的k个数
大根堆
快排思想
//快排思想
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(arr == null || arr.length == 0 || k <= 0) return new int[0];
if(k > arr.length) return arr;
quickSearch(arr, 0, arr.length-1, k);
int[] res = new int[k];
for(int i = 0; i < k; i++){
res[i] = arr[i];
}
//System.out.println("最后:" + Arrays.toString(arr));
return res;
}
public void quickSearch(int[] arr, int left, int right, int k){
int index = quickSort(arr, left, right);
if(index == k) return;
else if(index < k) quickSearch(arr, index+1, right, k);
else quickSearch(arr, left, index-1, k);
}
public int quickSort(int[] arr, int left, int right){
if(left >= right) return left;
int i = left;
int j = right;
int x = arr[left];
while(i < j){
while(i < j && arr[j] >= x){
j--;
}
arr[i] = arr[j];
while(i < j && arr[i] <= x){
i++;
}
arr[j] = arr[i];
}
arr[i] = x;
// quickSort(arr, left, i-1);
// quickSort(arr, i+1, right);
//说明:因为quickSearch已经进行了递归,所以qucikSort就不递归啦
return j;
}
}
剑指 Offer 42. 连续子数组的最大和
剑指 Offer 43. 1~n整数中1出现的次数
剑指 Offer 44. 数字序列中某一位的数字
剑指 Offer 45. 把数组排成最小的数
剑指 Offer 46. 把数字翻译成字符串
dp空间优化:
剑指 Offer 47. 礼物的最大价值
初始dp
空间优化,直接在原数组进行修改
代码简洁
剑指 Offer 48. 最长不含重复字符的子字符串
滑动窗口
动态规划
剑指 Offer 49. 丑数
dp
堆
剑指 Offer 50. 第一个只出现一次的字符
哈希表
这个的代码太简洁了
有序哈希表
剑指 Offer 52. 两个链表的第一个公共节点
哈希表法
双指针 浪漫相遇
剑指 Offer 54. 二叉搜索树的第k大节点
剑指 Offer 55 - I. 二叉树的深度
dfs递归
BFS
剑指 Offer 55 - II. 平衡二叉树
递归
剪枝优化
剑指 Offer 53 - I. 在排序数组中查找数字 I
安安自己的题解 三种解法
两端都闭的写法(不存在也返回 返回的刚好是插入点的位置)
1. target+0.5 -0.5 左右都可
最后结果:7-3 = 4 所以4有4个
2. target target-1 只能是右边
最后结果:6-2 = 4 所以4有4个
3.target的左右边界
剑指 Offer 53 - II. 0~n-1中缺失的数字
类似于上题的±0.5 因此都是数组中不存在那个数值的情况
剑指 Offer 56 - I. 数组中数字出现的次数
排序+下标遍历 anan
//anan 2020.8.18
class Solution {
public int[] singleNumbers(int[] nums) {
//System.out.println(Arrays.toString(nums));
Arrays.sort(nums);
//System.out.println(Arrays.toString(nums));
int[] res = new int[2];
int length = nums.length;
int count = 0;
int i = 0;
while(i < length-1){
if(nums[i] == nums[i+1]){
i+=2;
}else{
res[count++] = nums[i];
i+=1;
}
}
if(nums[length-1] != nums[length-2]){
res[count++] = nums[length-1];
}
return res;
}
}
异或
剑指 Offer 56 - II. 数组中数字出现的次数 II
剑指 Offer 57. 和为s的两个数字
双指针
二分法+双指针
//anan 2020.8.18 二分法+双指针
class Solution {
public int[] twoSum(int[] nums, int target) {
int left = 0;
int right = leftBound(nums, target);
/*
利用二分法缩小范围
nums = [2,7,11,15], target = 9 只需要搜索[2,7,11]
nums = [10,26,30,31,47,60], target = 40 只需要搜索[10,26,30,31,47]
*/
int[] res = new int[2];
while(left < right){
if(nums[left] + nums[right] < target){
left++;
}else if(nums[left] + nums[right] > target){
right--;
}else{
res[0] = nums[left];
res[1] = nums[right];
break;
}
}
return res;
}
//若是数组中有target,返回target第一次出现的位置
//若是数组中没有target,返回target该插入的位置
public int leftBound(int[] nums, int target){
int left = 0;
int right = nums.length-1;
while(left <= right){
int mid = left + (right-left)/2;
if(nums[mid] < target){
left = mid+1;
}else if(nums[mid] > target){
right = mid-1;
}else if(nums[mid] == target){
right = mid-1;
}
}
return (left >= nums.length ? nums.length-1 : left);
}
}
剑指 Offer 57 - II. 和为s的连续正数序列
双指针
滑动窗口
剑指 Offer 58 - I. 翻转单词顺序
API
双指针
stack+定位单词边界
//anan 用stack 定位单词边界 不用API
class Solution {
public String reverseWords(String s) {
int left = 0, right = s.length() - 1;
// 去掉字符串开头的空白字符
while (left <= right && s.charAt(left) == ' ') ++left;
// 去掉字符串末尾的空白字符
while (left <= right && s.charAt(right) == ' ') --right;
int i = left;
int j = left;
Deque<String> stack = new LinkedList<>();
StringBuffer sb = new StringBuffer();
while(i <= j && j <= right){
while(i <= right && s.charAt(i) == ' ') i++;
j = i;
while(j <= right && s.charAt(j) != ' ') j++;
stack.push(s.substring(i, j));
i = j;
}
return String.join(" ", stack);
}
}
先反转整个字符串,再反转各个单词
class Solution {
public StringBuilder trimSpaces(String s) {
int left = 0, right = s.length() - 1;
// 去掉字符串开头的空白字符
while (left <= right && s.charAt(left) == ' ') ++left;
// 去掉字符串末尾的空白字符
while (left <= right && s.charAt(right) == ' ') --right;
// 将字符串间多余的空白字符去除
StringBuilder sb = new StringBuilder();
while (left <= right) {
char c = s.charAt(left);
if (c != ' ') sb.append(c);
else if (sb.charAt(sb.length() - 1) != ' ') sb.append(c);
++left;
}
return sb;
}
public void reverse(StringBuilder sb, int left, int right) {
while (left < right) {
char tmp = sb.charAt(left);
sb.setCharAt(left++, sb.charAt(right));
sb.setCharAt(right--, tmp);
}
}
public void reverseEachWord(StringBuilder sb) {
int n = sb.length();
int start = 0, end = 0;
while (start < n) {
// 循环至单词的末尾
while (end < n && sb.charAt(end) != ' ') ++end;
// 翻转单词
reverse(sb, start, end - 1);
// 更新start,去找下一个单词
start = end + 1;
++end;
}
}
public String reverseWords(String s) {
StringBuilder sb = trimSpaces(s);
// 翻转字符串
reverse(sb, 0, sb.length() - 1);
// 翻转每个单词
reverseEachWord(sb);
return sb.toString();
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/reverse-words-in-a-string/solution/fan-zhuan-zi-fu-chuan-li-de-dan-ci-by-leetcode-sol/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
剑指 Offer 58 - II. 左旋转字符串
三次翻转
字符串拼接+求余简化代码
剑指 Offer 60. n个骰子的点数
dp
class Solution {
public double[] twoSum(int n) {
//下标从1开始
int[][] dp = new int[n+1][n*6+1];
for(int i = 1; i <= 6; i++){
dp[1][i] = 1;
}
for(int i = 2; i <= n; i++){
for(int j = i; j <= 6*i; j++){
for(int cur = 1; cur <= 6; cur++){
if(j-cur > 0){
dp[i][j] += dp[i-1][j-cur];
}
}
}
}
double all = Math.pow(6, n);
double[] res = new double[n*6-n+1];
for(int i = n; i <= n*6; i++){
res[i-n] = dp[n][i]/all;
}
return res;
}
}
dp空间优化
class Solution {
public double[] twoSum(int n) {
int[] dp = new int[n*6+1];
for(int i = 1; i <= 6; i++){
dp[i] = 1;
}
for(int i = 2; i <= n; i++){
for(int j = 6*i; j >= i; j--){
dp[j] = 0;
for(int cur = 1; cur <= 6; cur++){
//if(j-cur > 0){ 不能这样
i-1,即是对于只有对于少一个色子时,最小和最少为i-1,不可能再小了
if(j-cur >= i-1){
dp[j] += dp[j-cur];
}
}
}
}
double all = Math.pow(6, n);
double[] res = new double[n*6-n+1];
for(int i = n; i <= n*6; i++){
res[i-n] = dp[i]/all;
}
return res;
}
}
剑指 Offer 61. 扑克牌中的顺子
一般思路
class Solution {
public boolean isStraight(int[] nums) {
Arrays.sort(nums);
int count0 = 0;
int need0 = 0;
for(int i = 0;i<nums.length-1;i++){
if(nums[i]==0){
count0++;
continue;
}
if(nums[i+1]==nums[i]){
return false;
}
if(nums[i+1]-nums[i]>=1){
need0 = need0+(nums[i+1]-nums[i]-1);
}
}
return need0<=count0;
}
}
巧妙思路
剑指 Offer 65. 不用加减乘除做加法
//安安 写两个变量好理解
class Solution {
public int add(int a, int b) {
while(b != 0){
int jinwei = (a & b) << 1;
int benwei = a ^ b ;
b = jinwei;
a = benwei;
}
return a;
}
}
class Solution {
public int add(int a, int b) {
while(b != 0) { // 当进位为 0 时跳出
int c = (a & b) << 1; // c = 进位
a ^= b; // a = 非进位和
b = c; // b = 进位
}
return a;
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/solution/mian-shi-ti-65-bu-yong-jia-jian-cheng-chu-zuo-ji-7/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
剑指 Offer 66 构建乘积数组
暴力法(超时)
class Solution {
public int[] constructArr(int[] a) {
int n = a.length;
int[] b = new int[n];
for(int i = 0; i < n; i++){
int num = 1;
for(int j = 0; j < n; j++){
if(j != i){
num *= a[j];
}
}
b[i] = num;
}
return b;
}
}
两个dp
//本质就是两个dp数组,分别维护 i 左侧、右侧的乘积和
class Solution {
public int[] constructArr(int[] a) {
if(a == null || a.length == 0) return new int[0];
int n = a.length;
int[] b = new int[n];
int[] left = new int[n];
int[] right = new int[n];
left[0] = 1;
right[n-1] = 1;
for(int i = 1; i < n; i++){
left[i] = left[i-1]*a[i-1];
}
for(int i = n-2; i >= 0; i--){
right[i] = right[i+1]*a[i+1];
}
for(int i = 0; i < n; i++){
b[i] = left[i]*right[i];
}
return b;
}
}
剑指 Offer 67. 把字符串转换成整数
正则
原题解
//需要导入包
import java.util.regex.*;
class Solution {
public int myAtoi(String str) {
//清空字符串开头和末尾空格(这是trim方法功能,事实上我们只需清空开头空格)
str = str.trim();
//java正则表达式
Pattern p = Pattern.compile("^[\\+\\-]?\\d+");
Matcher m = p.matcher(str);
int value = 0;
//判断是否能匹配
if (m.find()){
//字符串转整数,溢出
try {
value = Integer.parseInt(str.substring(m.start(), m.end()));
} catch (Exception e){
//由于有的字符串"42"没有正号,所以我们判断'-'
value = str.charAt(0) == '-' ? Integer.MIN_VALUE: Integer.MAX_VALUE;
}
}
return value;
}
}
自己根据题解稍微改了一下
/*
? 零次或一次匹配前面的字符或子表达式
+ 一次或多次匹配前面的字符或子表达式
*/
import java.util.regex.*;
class Solution {
public int myAtoi(String s) {
s = s.trim();
String regex = "^[+-]?\\d+";
Pattern r = Pattern.compile(regex);
Matcher m = r.matcher(s);
int value = 0;
if(m.find( )) {
//System.out.println("Found value: " + m.group());
try{
value = Integer.parseInt(m.group());
}catch(Exception e){
value = s.charAt(0) == '-' ? Integer.MIN_VALUE: Integer.MAX_VALUE;
}
}
return value;
}
}
直接遍历判断
public class Solution {
public int myAtoi(String str) {
char[] chars = str.toCharArray();
int n = chars.length;
int idx = 0;
while (idx < n && chars[idx] == ' ') {
// 去掉前导空格
idx++;
}
if (idx == n) {
//去掉前导空格以后到了末尾了
return 0;
}
boolean negative = false;
if (chars[idx] == '-') {
//遇到负号
negative = true;
idx++;
} else if (chars[idx] == '+') {
// 遇到正号
idx++;
} else if (!Character.isDigit(chars[idx])) {
// 其他符号
return 0;
}
int ans = 0;
while (idx < n && Character.isDigit(chars[idx])) {
int digit = chars[idx] - '0';
if (ans > (Integer.MAX_VALUE - digit) / 10) {
// 本来应该是 ans * 10 + digit > Integer.MAX_VALUE
// 但是 *10 和 + digit 都有可能越界,所有都移动到右边去就可以了。
return negative? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
ans = ans * 10 + digit;
idx++;
}
return negative? -ans : ans;
}
}
作者:sweetiee
链接:https://leetcode-cn.com/problems/string-to-integer-atoi/solution/java-zi-fu-chuan-zhuan-zheng-shu-hao-dong-by-sweet/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
剑指 Offer 68 - I 二叉搜索树的最近公共祖先
迭代
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root != null) {
if(root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中
root = root.right; // 遍历至右子节点
else if(root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中
root = root.left; // 遍历至左子节点
else break;
}
return root;
}
}
迭代2
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(p.val > q.val) { // 保证 p.val < q.val
TreeNode tmp = p;
p = q;
q = tmp;
}
while(root != null) {
if(root.val < p.val) // p,q 都在 root 的右子树中
root = root.right; // 遍历至右子节点
else if(root.val > q.val) // p,q 都在 root 的左子树中
root = root.left; // 遍历至左子节点
else break;
}
return root;
}
}
递归
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root.val < p.val && root.val < q.val)
return lowestCommonAncestor(root.right, p, q);
if(root.val > p.val && root.val > q.val)
return lowestCommonAncestor(root.left, p, q);
return root;
}
}
剑指 Offer 68 - II 二叉树的最近公共祖先
递归
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left == null) return right;
if(right == null) return left;
return root;
}
}