牛客剑指offer共81道题目汇总
题号 | 题目 | 知识点 | 难度 | 通过率 | |
---|---|---|---|---|---|
JZ3 | 数组中重复的数字 | 数组 | 简单 | 53.87% | |
JZ4 | 二维数组中的查找 | 数组 | 中等 | 26.30% | |
JZ11 | 旋转数组的最小数字 | 二分 | 简单 | 34.28% | |
JZ17 | 打印从1到最大的n位数 | 数组 | 简单 | 59.61% | |
JZ21 | 调整数组顺序使奇数位于偶数前面(一) | 数组 | 中等 | 53.15% | |
JZ81 | 调整数组顺序使奇数位于偶数前面(二) | 数组 排序 | 简单 | 59.82% | |
JZ29 | 顺时针打印矩阵 | 数组 | 简单 | 19.71% | |
JZ10 | 斐波那契数列 | 数组 递归 动态规划 快速幂 记忆化搜索 | 入门 | 35.75% | |
JZ39 | 数组中出现次数超过一半的数字 | 哈希 | 简单 | 32.19% | |
JZ45 | 把数组排成最小的数 | 数组 排序 贪心 | 中等 | 30.37% | |
JZ47 | 礼物的最大价值 | 数组 动态规划 | 中等 | 58.01% | |
JZ53 | 数字在升序数组中出现的次数 | 数组 二分 | 简单 | 33.04% | |
JZ49 | 丑数 | 基础数学 二分 | 中等 | 23.41% | |
JZ51 | 数组中的逆序对 | 数组 归并 | 中等 | 17.05% | |
JZ66 | 构建乘积数组 | 数组 | 简单 | 41.12% | |
JZ42 | 连续子数组的最大和 | 贪心 动态规划 | 简单 | 39.96% | |
JZ85 | 连续子数组的最大和(二) | 数组 贪心 动态规划 双指针 | 中等 | 35.56% | |
JZ56 | 数组中只出现一次的两个数字 | 哈希 位运算 | 中等 | 57.31% | |
JZ57 | 和为S的两个数字 | 数组 双指针 | 中等 | 30.87% | |
JZ5 | 替换空格 | 字符串 | 简单 | 58.79% | |
JZ19 | 正则表达式匹配 | 字符串 递归 动态规划 | 较难 | 32.28% | |
JZ20 | 表示数值的字符串 | 字符串 | 较难 | 26.40% | |
JZ38 | 字符串的排列 | 字符串 递归 | 中等 | 23.72% | |
JZ48 | 最长不含重复字符的子字符串 | 字符串 哈希 双指针 | 中等 | 39.05% | |
JZ50 | 第一个只出现一次的字符 | 字符串 | 简单 | 31.64% | |
JZ58 | 左旋转字符串 | 字符串 | 中等 | 33.45% | |
JZ67 | 把字符串转换成整数(atoi) | 字符串 | 中等 | 23.52% | |
JZ73 | 翻转单词序列 | 字符串 双指针 | 简单 | 21.40% | |
JZ74 | 和为S的连续正数序列 | 穷举 | 中等 | 29.59% | |
JZ75 | 字符流中第一个不重复的字符 | 字符串 | 中等 | 33.48% | |
JZ9 | 用两个栈实现队列 | 栈 | 简单 | 40.70% | |
JZ12 | 矩阵中的路径 | dfs | 中等 | 38.92% | |
JZ13 | 机器人的运动范围 | 递归 | 较难 | 24.64% | |
JZ14 | 剪绳子 | 基础数学 | 中等 | 35.19% | |
JZ83 | 剪绳子(进阶版) | 快速幂 基础数学 | 较难 | 23.32% | |
JZ15 | 二进制中1的个数 | 基础数学 | 简单 | 35.68% | |
JZ16 | 数值的整数次方 | 基础数学 | 中等 | 33.66% | |
JZ62 | 圆圈中最后剩下的数 | 基础数学 | 中等 | 32.82% | |
JZ63 | 买卖股票的最好时机(一) | 贪心 动态规划 | 简单 | 51.72% | |
JZ64 | 求1+2+3+···+n | 基础数学 | 中等 | 42.97% | |
JZ65 | 不用加减乘除做加法 | 基础数学 | 简单 | 45.62% | |
JZ43 | 从1到n整数中1出现的次数 | 基础数学 | 中等 | 35.89% | |
JZ6 | 从尾到头打印链表 | 链表 | 简单 | 28.67% | |
JZ18 | 删除链表的节点 | 链表 | 简单 | 60.16% | |
JZ76 | 删除链表中重复的结点 | 链表 | 中等 | 21.93% | |
JZ22 | 链表中倒数第K个结点 | 链表 | 简单 | 38.72% | |
JZ23 | 链表中环的入口结点 | 链表 哈希 双指针 | 中等 | 36.34% | |
JZ24 | 反转链表 | 链表 | 简单 | 38.23% | |
JZ25 | 合并两个排序的链表 | 链表 | 简单 | 31.33% | |
JZ35 | 复杂链表的复制 | 链表 | 较难 | 23.14% | |
JZ52 | 两个链表的第一个公共结点 | 链表 | 简单 | 37.65% | |
JZ26 | 树的子结构 | 树 | 中等 | 25.48% | |
JZ27 | 二叉树的镜像 | 树 | 简单 | 67.43% | |
JZ28 | 对称的二叉树 | 树 | 简单 | 33.25% | |
JZ30 | 包含min函数的栈 | 栈 | 简单 | 34.57% | |
JZ31 | 栈的压入弹出序列 | 栈 | 中等 | 31.42% | |
JZ7 | 重建二叉树 | 数组 树 dfs | 中等 | 26.97% | |
JZ8 | 二叉树的下一个结点 | 树 | 中等 | 31.28% | |
JZ32 | 从上往下打印二叉树 | 队列 树 | 简单 | 29.63% | |
JZ33 | 二叉搜索树的后序遍历序列 | 栈 树 | 中等 | 25.33% | |
JZ82 | 判断二叉树中是否存在和为某一值的路径 | 树 dfs | 简单 | 42.26% | |
JZ34 | 二叉树中从根到叶子节点和为某一值的所有路径 | 树 | 中等 | 28.48% | |
JZ84 | 二叉树中任意节点路径和为某一值的所有路径 | 树 | 中等 | 51.39% | |
JZ36 | 二叉搜索树与双向链表 | 分治 | 中等 | 30.54% | |
JZ37 | 序列化二叉树 | 队列 树 | 较难 | 24.49% | |
JZ40 | 最小的K个数 | 堆 排序 分治 | 中等 | 27.01% | |
JZ41 | 数据流中的中位数 | 堆 排序 | 中等 | 28.38% | |
JZ44 | 数字序列中某一位的数字 | 模拟 | 简单 | 32.38% | |
JZ46 | 把数字翻译成字符串 | 动态规划 | 中等 | 24.12% | |
JZ54 | 二叉搜索树的第k个节点 | 树 递归 dfs | 中等 | 44.24% | |
JZ55 | 二叉树的深度 | 树 | 简单 | 49.88% | |
JZ59 | 滑动窗口的最大值 | 队列 堆 双指针 | 较难 | 27.15% | |
JZ61 | 扑克牌顺子 | 模拟 | 简单 | 27.89% | |
JZ69 | 跳台阶 | 递归 动态规划 记忆化搜索 | 简单 | 40.24% | |
JZ70 | 矩形覆盖 | 递归 动态规划 | 中等 | 36.58% | |
JZ71 | 跳台阶扩展问题 | 递归 动态规划 记忆化搜索 | 简单 | 42.44% | |
JZ77 | 按之字形顺序打印二叉树 | 栈 队列 树 | 中等 | 28.18% | |
JZ78 | 把二叉树打印成多行 | 树 广度优先搜索(BFS) | 中等 | 33.65% | |
JZ79 | 判断是不是平衡二叉树 | 树 dfs | 简单 | 38.78% | |
JZ68 | 二叉搜索树的最近公共祖先 | 树 递归 | 简单 | 64.18% | |
JZ86 | 在二叉树中找到两个节点的最近公共祖先 | 树 | 中等 | 47.85% |
数组中重复的数字
public int duplicate (int[] numbers) {
if (numbers == null || numbers.length == 0) {
return -1;
}
HashSet<Integer> hashset = new HashSet<>();
for (int i : numbers) {
if (hashset.contains(i)) {
return i;
} else {
hashset.add(i);
}
}
return -1;
}
打印从1到最大的n位数
public int[] printNumbers (int n) {
int max = 9;
for(int i=1; i<n; i++){
max = max*10 + 9;
}
int[] result = new int[max];
for(int i=0; i<max; i++){
result[i] = i+1;
}
return result;
}
1.二维数组中的查找
public boolean Find(int target, int [][] array) {
for(int i=0;i<array.length;i++){
for(int j=0;j<array[0].length;j++){
if(array[i][j] == target){
return true;
}
}
}
return false;
}
解法2:从左下找
给定规则:每一行从左到右递增,每一列从上到下递增
public boolean Find(int target, int [][] array) {
int rows = array.length;
if (rows == 0) {
return false;
}
int cols = array[0].length;
if(cols == 0){
return false;
}
// 左下
int row = rows-1;
int col = 0;
while(row>=0 && col<cols){
if(array[row][col] < target){
col++;
}else if(array[row][col] > target){
row--;
}else{
return true;
}
}
return false;
}
解法3:从右上找
public boolean Find(int target, int [][] array) {
int rows = array.length;
if(rows == 0){
return false;
}
int cols = array[0].length;
if(cols == 0){
return false;
}
// 右上
int row = 0; //注意
int col = cols-1; //注意
while(row<rows && col>=0){ //注意
if(array[row][col] < target){
row++; //注意
}else if(array[row][col] > target){
col--; //注意
}else{
return true;
}
}
return false;
}
6.旋转数组的最小数字
public int minNumberInRotateArray(int [] array) {
// 如果数组无元素,那么返回0
if (array.length <= 0)
return 0;
// 定义边界
int left = 0;
int right = array.length - 1;
while (left <= right){
// 计算左右区间最中间的索引
int mid = left + ((right - left)>>1);
// 如果中间的值小于右边的值,说明此时数组最小值在左半部,
// 挪动右边界指针到中间索引,为了避免此时的中间索引值就是最小的值,所以mid不能够减1
if (array[mid] < array[right]) {
right = mid;
}
else if (array[mid] > array[right]) {
left = mid + 1;
} else{
// 如果中间值与右边界值相同,那么挪动右边界向左靠一位,这样就可以在下次循环时重新计算出中间索引值
right--;
}
}
// 左边界永远小于或等于右边界,那么就直接返回左边界所对应的数组值
return array[left];
}
13.调整数组顺序使奇数位于偶数前面(一)
public int[] reOrderArray (int[] array) {
int[] arr=new int[array.length];
int i=0;
for(int a:array){
if((a&1) == 1){ //奇数
arr[i++] = a;
}
}
for(int a:array){
if((a&1) == 0){ //偶数
arr[i++]=a;
}
}
return arr;
}
调整数组顺序使奇数位于偶数前面(二)
public int[] reOrderArrayTwo (int[] array) {
int low = 0;
int high = array.length - 1;
while (low < high) {
while (low < high && array[low] % 2 == 1) low++;
while (low < high && array[high] % 2 == 0) high--;
if (low < high) {
int temp = array[low];
array[low] = array[high];
array[high] = temp;
}
}
return array;
}
19.顺时针打印矩阵
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> list = new ArrayList<Integer>();
if(matrix.length==0) return list;
int l=0, r=matrix[0].length-1;
int top=0, down=matrix.length-1;
while( l<=r && top<=down ){
for(int i=l;i<=r;i++){
list.add(matrix[top][i]);
}
for(int i=top+1;i<=down;i++){
list.add(matrix[i][r]);
}
if(top<down){
for(int i=r-1;i>=l;i--){
list.add(matrix[down][i]);
}
}
if(l<r){
for(int i=down-1;i>=top+1;i--){
list.add(matrix[i][l]);
}
}
l++;r--;top++;down--;
}
return list;
}
28.数组中出现次数超过一半的数字
public int MoreThanHalfNum_Solution(int [] array) {
int res = array[0];
for(int i=1, idx = 1; i<array.length; i++){
if(array[i] == res){
idx++;
}else{
idx--;
if(idx == 0){
idx = 1;
res = array[i];
}
}
}
return res;
}
30.连续子数组的最大和
public int FindGreatestSumOfSubArray(int[] array) {
int sum = 0;
int max = array[0];
for (int i = 0; i < array.length; i++) {
// 优化动态规划,确定sum的最大值
sum = Math.max(sum + array[i], array[i]);
// 每次比较,保存出现的最大值
max = Math.max(max, sum);
}
return max;
}
连续子数组的最大和(二)
public int[] FindGreatestSumOfSubArray (int[] array) {
//动态规划
int sum = array[0],num = array[0];
//当前遍历位置子串首尾位置
int start_tmp = 0, end_tmp = 1;
//最大和子串首尾位置
int start = 0, end = 1;
for(int i = 1; i < array.length; i++){
//数组中包含i位置的连续串最大值(比较当前数组值与之前累加值大小)
if(array[i] > num + array[i]){
num = array[i];
start_tmp = i;
end_tmp = i + 1;
}else{
num = num + array[i];
end_tmp++;
}
//记录并更新当前遍历数组的最大子串和
if(num > sum || (num == sum) && (end_tmp - start_tmp) > (end - start)){
sum = num;
start = start_tmp;
end = end_tmp;
}
}
return Arrays.copyOfRange(array,start,end);
}
数组中只出现一次的两个数字
public int[] FindNumsAppearOnce (int[] array) {
HashMap<Integer, Integer> map = new HashMap<>();
int[] arr = new int[2];
int j = 0;
// 先遍历一遍,将每个元素,及对应的个数装入map中
for (int i = 0; i < array.length; i++) {
if (!map.containsKey(array[i])) {
map.put(array[i], 1);
} else {
map.put(array[i], map.get(array[i]) + 1);
}
}
// 遍历map。取出两个个数为1的元素
for (int i : map.keySet()) {
if (map.get(i) == 1) {
arr[j++] = i;
}
if (j == 2) break;
}
// 对两个元素进行升序排列
if (arr[0] > arr[1]) {
int t = arr[0];
arr[0] = arr[1];
arr[1] = t;
}
return arr;
}
解法2:
public int[] FindNumsAppearOnce (int[] array) {
int eor = 0;//任何数和0异或都等于它本身
for (int i = 0; i < array.length; i++) {
eor ^= array[i];//这样结束了就是两个不一样的数相异或
}
int eor2 = 0;//再搞一个eor
int rightOne = eor & (~eor +
1);//固定写法,一个不为0的数最右边的1就这么求
//自己与上自己取反加1的值就是最右边的一个1
for (int j = 0; j < array.length; j++) {
if ((array[j] & rightOne) ==
0) { //如果某个数在这一位上面是1,相与结果才是1,注意优先级
eor2 ^= array[j]; //这样异或下来eor2就是其中一个数
}
}
int a = eor2;
int b = eor ^ eor2;
return a < b ? new int[] {a, b} : new int[] {b, a};
}
32.把数组排成最小的数
public String PrintMinNumber(int [] numbers) {
if(numbers.length == 0){return "";}
String[] str1 = new String[numbers.length];
for(int i = 0; i < numbers.length; i++){
str1[i] = numbers[i] + "";
}
//贪心算法:相邻两者的字典序最小
Arrays.sort(str1, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return (o1 + o2).compareTo(o2 + o1);
}
});
String res = new String();
for (String tmp : str1) {
res += tmp;
}
return res;
}
礼物的最大价值
public int maxValue (int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
}
}
return dp[m][n];
}
数字在升序数组中出现的次数
public int GetNumberOfK(int [] array, int k) {
if (array == null || array.length == 0) return 0;
int left = 0, right = array.length - 1;
int mid = 0, cnt = 0;
while (left < right) {
mid = left + (right - left) / 2;
if (array[mid] > k)
right = mid - 1;
else if (array[mid] < k)
left = mid + 1;
else {
break;
}
}
for (int i = left; i <= right; i++)
if (array[i] == k)
cnt++;
return cnt;
}
35.数组中的逆序对
public class Solution {
public int MOD = 1000000007;
public int count = 0;
public int InversePairs(int [] array) {
mergeSort(array, 0, array.length - 1);
return count;
}
private void mergeSort(int[] array, int l, int r) {
if (l < r) {
int mid = (l + r) / 2;
mergeSort(array, l, mid);
mergeSort(array, mid + 1, r);
merge(array, l, mid, mid + 1, r);
}
}
private void merge(int[] array, int l1, int r1, int l2, int r2) {
int i = l1;
int j = l2;
List<Integer> list = new ArrayList<>();
while (i <= r1 && j <= r2) {
if (array[i] > array[j]) {
list.add(array[j++]);
count += (r1 - i + 1);
count = count % MOD;
} else {
list.add(array[i++]);
}
}
while (i <= r1) {
list.add(array[i++]);
}
while (j <= r2) {
list.add(array[j++]);
}
for (int k = l1; k <= r2; k++) {
array[k] = list.get(k - l1);
}
}
}
构建乘积数组
public int[] multiply(int[] A) {
int[] B = new int[A.length];
int temp = 0;
for (int i = 0; i < A.length; i++) {
temp = A[i];
A[i] = 1;
B[i] = 1;
for (int j = 0; j < A.length; j++) {
B[i] *= A[j];
}
A[i] = temp;
}
return B;
}
2.替换空格
public String replaceSpace (String s) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < s.length();i++){
char temp = s.charAt(i);
if(temp == ' '){
sb.append("%20");
}else{
sb.append(temp);
}
}
return sb.toString();
}
27.字符串的排列格
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> list = new ArrayList<>();
if (str == null || str.length() < 1) return list;
recur(str, "", list);
return list;
}
public void recur(String str, String cur, ArrayList<String> list) {
if ( str.length() == 0 && !list.contains(cur) ) {
list.add(cur);
}
for (int i = 0; i < str.length(); i++) {
recur(str.substring(0, i) + str.substring(i + 1, str.length()),
cur + str.charAt(i),
list);
}
}
}
最长不含重复字符的子字符串
public int lengthOfLongestSubstring (String s) {
int start = -1, sub = 1;
//哈希表存储字符和字符位置的对应关系
HashMap<Character, Integer> map = new HashMap<Character, Integer>();
//遍历字符串
for (int i = 0; i < s.length(); i++) {
if (map.containsKey(s.charAt(i))) {
//更新最近的相同字符作为长度计算起点
start = Math.max(start, map.get(s.charAt(i)));
}
map.put(s.charAt(i), i);
//更新最长不同字符字串长度值
sub = Math.max(sub, i - start);
}
return sub;
}
解法2:
public int lengthOfLongestSubstring (String s) {
int max = 0;
boolean[] sign = new boolean[256];
int left = 0, right = 0;
while (right < s.length()) {
char ch = s.charAt(right);
if (sign[ch]) {
while (s.charAt(left) != ch) {
sign[s.charAt(left++)] = false;
}
sign[s.charAt(left++)] = false;
}
sign[ch] = true;
right++;
max = Math.max(max, right - left);
}
return max;
}
34.第一个只出现一次的字符
public int FirstNotRepeatingChar(String str) {
for (int i = 0; i < str.length(); i++) {
if (str.indexOf(str.charAt(i)) != str.lastIndexOf(str.charAt(i))) continue;
else return i;
}
return -1;
}
解法2:
public int FirstNotRepeatingChar(String str) {
if (str.length() == 0 || str == null) return -1;
HashMap<Character, Integer> map = new LinkedHashMap<>();
for (int i = 0; i < str.length(); i++) {
if (!map.keySet().contains(str.charAt(i))) {
map.put(str.charAt(i), 1);
} else {
map.put(str.charAt(i), map.get(str.charAt(i)) + 1);
}
}
for (int i = 0; i < str.length(); i++) {
if (map.get(str.charAt(i)) == 1) {
return i;
}
}
return -1;
}
43.左旋转字符串
public String LeftRotateString(String str,int n) {
if (str == null || str.length() == 0) return "";
int m = n%str.length();
return str.substring(m, str.length()) + str.substring(0, m);
}
把字符串转换成整数(atoi)
public int StrToInt (String s) {
s = s.trim();
if (s.equals("") || (s.charAt(0) < '0' || s.charAt(0) > '9') && (s.charAt(0) != '+' && s.charAt(0) != '-')) {
return 0;
}
int index = 0;
boolean over = false;
int res = 0;
if (s.charAt(0) == '+' || s.charAt(0) == '-') {
index++;
}
while (index < s.length()) {
if (s.charAt(index) > '9' || s.charAt(index) < '0') break;
else {
res = res * 10 + s.charAt(index++) - '0';
if (res < 0) {
over = true;
break;
}
}
}
if (over) {
return s.charAt(0) == '-' ? Integer.MIN_VALUE : Integer.MAX_VALUE;
} else {
return s.charAt(0) == '-' ? res * -1 : res;
}
}
44.翻转单词序列
public String ReverseSentence(String str) {
String[] slice = str.split(" ");
StringBuilder sb = new StringBuilder();
for(int i=slice.length-1; i>=0; i--) {
sb.append(slice[i]);
if(i!=0){
sb.append(" ");
}
}
return sb.toString();
}
52.正则表达式匹配
public boolean match (String ss, String pp) {
// 往原字符头部插入空格,这样得到char数组是从 1 开始,而且可以使得 f[0][0] = true,可以将 true 这个结果滚动下去
int n = ss.length(), m = pp.length();
ss = " " + ss;
pp = " " + pp;
char[] s = ss.toCharArray();
char[] p = pp.toCharArray();
// f(i,j) 代表考虑 s 中的 1~i 字符和 p 中的 1~j 字符 是否匹配
boolean[][] f = new boolean[n + 1][m + 1];
f[0][0] = true;
for (int i = 0; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// 如果下一个字符是 '*',则代表当前字符不能被单独使用,跳过
if (j + 1 <= m && p[j + 1] == '*') continue;
// 对应了 p[j] 为普通字符和 '.' 的两种情况
if (i - 1 >= 0 && p[j] != '*') {
f[i][j] = f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '.');
} else if (p[j] == '*') {
// 对应了 p[j] 为 '*' 的情况
f[i][j] = (j - 2 >= 0 && f[i][j - 2]) || (i - 1 >= 0 && f[i - 1][j] && (s[i] == p[j - 1] || p[j - 1] == '.'));
}
}
}
return f[n][m];
}
53.表示数值的字符串
public boolean isNumeric (String s) {
if (s == null || s.length() == 0) return false;
//去掉首位空格
s = s.trim();
boolean numFlag = false;
boolean dotFlag = false;
boolean eFlag = false;
for (int i = 0; i < s.length(); i++) {
//判定为数字,则标记numFlag
if (Character.isDigit(s.charAt(i))) {
numFlag = true;
//判定为. 需要没出现过.并且没出现过e// 点在e前面
} else if (s.charAt(i) == '.' && !dotFlag && !eFlag) {
dotFlag = true;
//判定为e,需要没出现过e,并且出过数字了
} else if ((s.charAt(i) == 'e' || s.charAt(i) == 'E') && !eFlag && numFlag) {
eFlag = true;
numFlag = false;//为了避免123e这种请求,出现e之后就标志为false
//判定为+-符号,只能出现在第一位或者紧接e后面
} else if ((s.charAt(i) == '+' || s.charAt(i) == '-') && (i == 0 ||
s.charAt(i - 1) == 'e' || s.charAt(i - 1) == 'E')) {
//其他情况,都是非法的
} else {
return false;
}
}
return numFlag;
}
54.字符流中第一个不重复的字符串
public class Solution {
private int[] cnt = new int[256];
private Queue<Character> queue = new LinkedList<>();
public void Insert(char ch) {
cnt[ch]++;
queue.add(ch);
//关键:队列不为空,且队首字符个数大于1的全部出队
while (!queue.isEmpty() && cnt[queue.peek()] > 1)
queue.poll();
}
public char FirstAppearingOnce() {
return queue.isEmpty() ? '#' : queue.peek();
}
}
3.从尾到头打印链表
迭代:
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<>();
ListNode tmp = listNode;
while(tmp!=null){
list.add(0,tmp.val);
tmp = tmp.next;
}
return list;
}
递归:
public void reversePrint(ListNode head) {
if (head == null)
return;
reversePrint(head.next);
System.out.println(head.val);
}
删除链表中的节点
public ListNode deleteNode (ListNode head, int val) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode node = dummy;
while(node.next!=null){
if(node.next.val==val){
node.next = node.next.next;
break;
}
node = node.next;
}
return dummy.next;
}
14.链表中倒数第K个结点
双指针:
public ListNode FindKthToTail(ListNode pHead, int k) {
if (pHead == null)
return pHead;
ListNode first = pHead;
ListNode second = pHead;
//第一个指针先走k步
while (k-- > 0) {
if (first == null)
return null;
first = first.next;
}
//然后两个指针在同时前进
while (first != null) {
first = first.next;
second = second.next;
}
return second;
}
使用栈:
public ListNode FindKthToTail(ListNode pHead, int k) {
Stack<ListNode> stack = new Stack<>();
//链表节点压栈
int count = 0;
while (pHead != null) {
stack.push(pHead);
pHead = pHead.next;
count++;
}
if (count < k || k == 0)
return null;
//在出栈串成新的链表
ListNode firstNode = stack.pop();
while (--k > 0) {
ListNode temp = stack.pop();
temp.next = firstNode;
firstNode = temp;
}
return firstNode;
}
15.反转链表
public ListNode ReverseList(ListNode head) {
ListNode pre = null;
ListNode next = head;
while(head != null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
递归:
public ListNode ReverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode newHead = ReverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
16.合并两个排序的链表
递归:
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null) return list2;
if(list2==null) return list1;
if(list2.val>list1.val){
list1.next = Merge(list1.next,list2);
return list1;
} else {
list2.next = Merge(list1,list2.next);
return list2;
}
}
迭代:
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
// 注意下面的与不能是短路与,必须保证两个list都不为空
while(list1 != null && list2 != null) {
if(list1.val > list2.val) {
p.next = list2;
list2 = list2.next;
p = p.next;
} else if(list1.val <= list2.val) {
p.next = list1;
list1 = list1.next;
p = p.next;
}
}
// list1后面还有,就把剩下的全部拿走
if(list1 != null) {
p.next = list1;
}
if(list2 != null) {
p.next = list2;
}
return dummy.next;
}
25.复杂链表的复制
public RandomListNode Clone(RandomListNode head) {
if (head == null) return null;
RandomListNode dummy = new RandomListNode(-1);
dummy.next = head;
while (head != null) {
RandomListNode node = new RandomListNode(head.label);
node.next = head.next;
head.next = node;
head = node.next;
}
head = dummy.next;
while (head != null) {
if (head.random != null) {
head.next.random = head.random.next;
}
head = head.next.next;
}
head = dummy.next;
RandomListNode p = head.next;
while (head != null) {
RandomListNode tmp = head.next;
if (head.next != null) head.next = head.next.next;
head = tmp;
}
return p;
}
36.两个链表的第一个公共结点
public ListNode FindFirstCommonNode(ListNode a, ListNode b) {
ListNode ta = a, tb = b;
while (ta != tb) {
ta = ta == null ? b : ta.next;
tb = tb == null ? a : tb.next;
}
return ta;
}
55.链表中环的入口结点
public ListNode EntryNodeOfLoop(ListNode pHead) {
if (pHead == null || pHead.next == null) {
return null;
}
ListNode fast = pHead;
ListNode slow = pHead;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
ListNode p = pHead;
while (p != slow) {
p = p.next;
slow = slow.next;
}
return p;
}
}
return null;
}
56.删除链表中重复的结点
public ListNode deleteDuplication(ListNode pHead) {
if (pHead == null || pHead.next == null) {
return pHead;
}
// 自己构建辅助头结点
ListNode head = new ListNode(Integer.MIN_VALUE);
head.next = pHead;
ListNode pre = head;
ListNode cur = head.next;
while (cur != null) {
if (cur.next != null && cur.next.val == cur.val) {
// 相同结点一直前进
while (cur.next != null && cur.next.val == cur.val) {
cur = cur.next;
}
// 退出循环时,cur 指向重复值,也需要删除,而 cur.next 指向第一个不重复的值
// cur 继续前进
cur = cur.next;
// pre 连接新结点
pre.next = cur;
} else {
pre = cur;
cur = cur.next;
}
}
return head.next;
}
4.重建二叉树
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if (pre.length == 0 || in.length == 0) {
return null;
}
TreeNode root = new TreeNode(pre[0]);
// 在中序中找到前序的根
for (int i = 0; i < in.length; i++) {
if (in[i] == pre[0]) {
// 左子树,注意 copyOfRange 函数,左闭右开
root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(in, 0, i));
// 右子树,注意 copyOfRange 函数,左闭右开
root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(in, i + 1, in.length));
break;
}
}
return root;
}
17.树的子结构
public class Solution {
public boolean isSame(TreeNode root1,TreeNode root2){
//如果root2为空,则为true(不需要考虑root1的状况)
if(root2 == null) return true;
if(root1 == null) return false;
//判断首节点的值,然后root1与root2的左子树,然后root1与root2的右子树
return root1.val == root2.val && isSame(root1.left, root2.left) && isSame(root1.right, root2.right);
}
//方法一:递归的方式(利用深度优先遍历的思想)
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
//判断root1和root2是否为null(空树不是任意一个树的子结构)
if(root1 == null || root2 == null) return false;
//如果首结点不相等,则依次比较左子树、右子树与root2的关系
return isSame(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right,root2);
}
}
非递归:
public class Solution {
//判断结构相同必须需要的函数
public boolean isSame(TreeNode root1,TreeNode root2){
//如果root2为空,则为true(不需要考虑root1的状况)
if(root2 == null) return true;
if(root1 == null) return false;
//判断首节点的值,然后root1与root2的左子树,然后root1与root2的右子树
return root1.val == root2.val && isSame(root1.left, root2.left) && isSame(root1.right, root2.right);
}
//方法二:层次遍历的方式(利用广度优先遍历的思想)
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
//判断root1和root2是否为null(空树不是任意一个树的子结构)
if(root1 == null || root2 == null){
return false;
}
//队列(先进先出的特性)
Queue<TreeNode> queue = new LinkedList<>();
//把root1放入队列中
queue.offer(root1);
//判断队列是否为null
while(!queue.isEmpty()){
//取出队列中的结点
TreeNode cur = queue.poll();
//判断该结点是否和root相等
if(isSame(cur, root2)) return true;
else{
//不相等则把该结点的不为空的左右子节点放入队列中
if(cur.left != null) queue.offer(cur.left);
if(cur.right != null) queue.offer(cur.right);
}
}
return false;
}
}
18.二叉树的镜像
BFS解决:
public TreeNode Mirror(TreeNode root) {
//如果为空直接返回
if (root == null)
return null;
//队列
final Queue<TreeNode> queue = new LinkedList<>();
//首先把根节点加入到队列中
queue.add(root);
while (!queue.isEmpty()) {
//poll方法相当于移除队列头部的元素
TreeNode node = queue.poll();
//交换node节点的两个子节点
TreeNode left = node.left;
node.left = node.right;
node.right = left;
//如果当前节点的左子树不为空,就把左子树
//节点加入到队列中
if (node.left != null) {
queue.add(node.left);
}
//如果当前节点的右子树不为空,就把右子树
//节点加入到队列中
if (node.right != null) {
queue.add(node.right);
}
}
return root;
}
DFS解决:
public TreeNode Mirror(TreeNode root) {//DFS
//如果为空直接返回
if (root == null)
return null;
//栈
Stack<TreeNode> stack = new Stack<>();
//根节点压栈
stack.push(root);
//如果栈不为空就继续循环
while (!stack.empty()) {
//出栈
TreeNode node = stack.pop();
//子节点交换
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
//左子节点不为空入栈
if (node.left != null)
stack.push(node.left);
//右子节点不为空入栈
if (node.right != null)
stack.push(node.right);
}
return root;
}
递归中序:
public TreeNode Mirror(TreeNode root) {
if (root == null) return null;
Mirror(root.left);
//子节点交换
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
//上面交换过了,这里root.right要变成root.left
Mirror(root.left);
return root;
}
递归后续:
public TreeNode Mirror(TreeNode root) {
if (root == null)
return null;
TreeNode left = Mirror(root.left);
TreeNode right = Mirror(root.right);
root.left = right;
root.right = left;
return root;
}
22.从上往下打印二叉树(空节点不打印,跳过)
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> list = new ArrayList<Integer>();
if(root==null){//注意:空树返回一个默认构造的空List,而不是一个空指针null
return list;
}
//queue用来保存当前遍历到了哪个节点,一次性把一个节点的左右子都入队
Queue<TreeNode> queue = new LinkedList<TreeNode>();
TreeNode cur = root;
queue.offer(cur);
//只要队列中还有节点就说明还没遍历完,继续。
//每次从队列出队,然后将这个节点左右子入队列(FIFO,故能完成广度/层级遍历),再将这个节点记录在list中即可。
while(!queue.isEmpty()){
cur = queue.poll();
list.add(cur.val);
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.right!=null){
queue.offer(cur.right);
}
}
return list;
}
23.二叉搜索树的后序遍历序列
public boolean VerifySquenceOfBST(int[] sequence) {
if (sequence == null || sequence.length == 0)
return false;
return verify(sequence, 0, sequence.length - 1);
}
private boolean verify(int[] sequence, int first, int last) {
if (last - first <= 1) return true;
int rootVal = sequence[last];
int cutIndex = first;
while (cutIndex < last && sequence[cutIndex] <= rootVal)
cutIndex++;
for (int i = cutIndex; i < last; i++)
if (sequence[i] < rootVal)
return false;
return verify(sequence, first, cutIndex - 1) && verify(sequence, cutIndex, last - 1);
}
24.判断二叉树中是否存在和为某一值的路径
- 注明:从根节点到叶子节点的节点值之和等于 sum 的路径
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) return false;
//如果到叶子节点了,并且剩余值等于叶子节点的值,说明找到了这样的结果,直接返回true
if (root.left == null && root.right == null && sum - root.val == 0)
return true;
//分别沿着左右子节点走下去,然后顺便把当前节点的值减掉,左右子节点只要有一个返回true,
//说明存在这样的结果
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
二叉树中从根到叶子节点和为某一值的所有路径
public class Solution {
private ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
private LinkedList<Integer> path = new LinkedList<>();
void dfs(TreeNode root, int number) {
// 处理树为空
if (root == null) return;
// 路径更新
path.add(root.val);
// number更新
number -= root.val;
// 如果递归当前节点为叶子节点且该条路径的值已经达到了expectNumber,则更新ret
if(root.left == null && root.right == null && number == 0) {
ret.add(new ArrayList<>(path));
}
// 左右子树递归
dfs(root.left, number);
dfs(root.right, number);
path.removeLast();
}
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int expectNumber) {
dfs(root, expectNumber);
return ret;
}
}
二叉树中任意节点路径和为某一值的所有路径
public class Solution {
public int key = 0;
public void dfs(TreeNode root, int sum) {
if (null == root) return;
sum -= root.val;
// 注意这里为什么不return?
// - 题目定义不需要从根节点开始,也不需要在叶子节点结束;
// - 因此需要从根节点开始,一个节点一个节点的进行深度遍历;
// - 也就是从根节点到叶子节点可能有多条可能的路径;
// - 可能是无序的小于等于0的Val;
// - 因此只要保证后继连续节点Value和为0,就又是一条新路径;
if (sum == 0) key++;
dfs(root.left, sum);
dfs(root.right, sum);
}
public int FindPath (TreeNode root, int sum) {
if (null == root) return key;
// write code here
// 题目定义不需要从根节点开始,也不需要在叶子节点结束;
// 因此需要从根节点开始,一个节点一个节点的进行深度遍历;
dfs(root, sum);
FindPath(root.left, sum);
FindPath(root.right, sum);
return key;
}
}
写法2:
public int FindPath (TreeNode root, int sum) {
// write code here
if(root==null)
return 0;
return help(root,sum) + FindPath(root.left,sum) + FindPath(root.right,sum);
}
private int help(TreeNode root, int sum){
if(root==null){
return 0;
}
int count=0;
if(root.val==sum)
count++;
return count + help(root.left,sum-root.val) + help(root.right,sum-root.val);
}
写法3:
public class Solution {
HashMap<Integer,Integer> map = new HashMap<>();
public int FindPath (TreeNode root, int sum) {
if(root == null) return 0;
map.put(0,1);
return dfs(root,0,sum);
}
int dfs(TreeNode root, int sum, int target){
if(root == null) return 0;
sum += root.val;
int count = 0;
if(map.containsKey(sum - target)){
count += map.get(sum - target);
}
map.put(sum,map.getOrDefault(sum,0) + 1);
count += dfs(root.left,sum,target);
count += dfs(root.right,sum,target);
return count;
}
}
26.二叉搜索树与双向链表
迭代:
public TreeNode Convert(TreeNode root) {
if(root==null)
return null;
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode p = root;
TreeNode pre = null;// 用于保存中序遍历序列的上一节点
boolean isFirst = true;
while(p!=null||!stack.isEmpty()){
while(p!=null){
stack.push(p);
p = p.left;
}
p = stack.pop();
if(isFirst){
root = p;// 将中序遍历序列中的第一个节点记为root
pre = root;
isFirst = false;
}else{
pre.right = p;
p.left = pre;
pre = p;
}
p = p.right;
}
return root;
}
递归:
public TreeNode leftLast = null;
public TreeNode Convert(TreeNode root) {
if (root == null)
return null;
if (root.left == null && root.right == null) {
leftLast = root;// 最后的一个节点可能为最右侧的叶节点
return root;
}
// 1.将左子树构造成双链表,并返回链表头节点
TreeNode left = Convert(root.left);
// 3.如果左子树链表不为空的话,将当前root追加到左子树链表
if (left != null) {
leftLast.right = root;
root.left = leftLast;
}
leftLast = root;// 当根节点只含左子树时,则该根节点为最后一个节点
// 4.将右子树构造成双链表,并返回链表头节点
TreeNode right = Convert(root.right);
// 5.如果右子树链表不为空的话,将该链表追加到root节点之后
if (right != null) {
right.left = root;
root.right = right;
}
return left != null ? left : root;
}
写法2:
TreeNode head, pre;
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null) return null;
dfs(pRootOfTree);
return head;
}
public void dfs(TreeNode root) {
if (root.left != null) dfs(root.left);
if (pre != null) pre.right = root;
else head = root;
root.left = pre;
pre = root;
if (root.right != null) dfs(root.right);
}
38.二叉树的深度
// DFS 深度遍历
public int TreeDepth(TreeNode root) {
//空节点没有深度
if(root == null) return 0;
//返回子树深度+1
return Math.max(TreeDepth(root.left), TreeDepth(root.right)) + 1;
}
39.判断是不是平衡二叉树
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null){
return true;
}
if( (Math.abs(maxDepth(root.left) - maxDepth(root.right)) ) > 1 ) {
return false;
}
return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
}
// 最大深度
private int maxDepth(TreeNode root) {
if(root == null) {
return 0;
}
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
}
BM38在二叉树中找到两个节点的最近公共祖先
public class Solution {
public TreeNode commonAncestor (TreeNode root, int p, int q) {
if (null == root) return null;
if (root.val == p || root.val == q) return root;
// 通过递归假设我们知道了运算结果 题目含义是不会出现重复节点
TreeNode left = commonAncestor(root.left, p, q);
TreeNode right = commonAncestor(root.right, p, q);
if (left == null) return right;
else if (right == null) return left;
else return root;
}
public int lowestCommonAncestor (TreeNode root, int p, int q) {
return commonAncestor(root, p, q).val;
}
}
迭代:
public int lowestCommonAncestor(TreeNode root, int o1, int o2) {
//记录遍历到的每个节点的父节点。
Map<Integer, Integer> map = new HashMap<>();
Queue<TreeNode> queue = new LinkedList<>();
map.put(root.val, Integer.MIN_VALUE);//根节点没有父节点,给他默认一个值
queue.add(root);
//直到两个节点都找到为止。
while (!map.containsKey(o1) || !map.containsKey(o2)) {
//队列是一边进一边出,这里poll方法是出队,
TreeNode node = queue.poll();
if (node.left != null) {
//左子节点不为空,记录下他的父节点
map.put(node.left.val, node.val);
//左子节点不为空,把它加入到队列中
queue.add(node.left);
}
//右节点同上
if (node.right != null) {
map.put(node.right.val, node.val);
queue.add(node.right);
}
}
Set<Integer> ancestors = new HashSet<>();
//记录下o1和他的祖先节点,从o1节点开始一直到根节点。
while (map.containsKey(o1)) {
ancestors.add(o1);
o1 = map.get(o1);
}
//查看o1和他的祖先节点是否包含o2节点,如果不包含再看是否包含o2的父节点……
while (!ancestors.contains(o2))
o2 = map.get(o2);
return o2;
}
JZ68二叉搜索树的最近公共祖先
public class Solution {
//求根节点到目标节点的路径
public ArrayList<Integer> getPath(TreeNode root, int target) {
ArrayList<Integer> path = new ArrayList<Integer>();
TreeNode node = root;
//节点值都不同,可以直接用值比较
while(node.val != target){
path.add(node.val);
//小的在左子树
if(target < node.val)
node = node.left;
//大的在右子树
else
node = node.right;
}
path.add(node.val);
return path;
}
public int lowestCommonAncestor (TreeNode root, int p, int q) {
//求根节点到两个节点的路径
ArrayList<Integer> pList = getPath(root, p);
ArrayList<Integer> qList = getPath(root, q);
int res = 0;
//比较两个路径,找到第一个不同的点
for(int i = 0; i < pList.size() && i < qList.size(); i++){
int x = pList.get(i);
int y = qList.get(i);
//最后一个相同的节点就是最近公共祖先
if(x == y)
res = pList.get(i);
else
break;
}
return res;
}
}
利用二叉搜索树的性质递归:
public class Solution {
public TreeNode commonAncestor (TreeNode root, int p, int q) {
if (null == root) return null;
if (root.val == p || root.val == q) return root;
// 通过递归假设我们知道了运算结果 题目含义是不会出现重复节点
if (p < root.val && q < root.val) return commonAncestor(root.left, p, q);
else if (p > root.val && q > root.val) return commonAncestor(root.right, p, q);
else return root;
}
public int lowestCommonAncestor (TreeNode root, int p, int q) {
return commonAncestor(root, p, q).val;
}
}
57.二叉树的下一个结点
public TreeLinkNode GetNext(TreeLinkNode pNode) {
// 1.
if (pNode.right != null) {
TreeLinkNode pRight = pNode.right;
while (pRight.left != null) {
pRight = pRight.left;
}
return pRight;
}
// 2.
if (pNode.next != null && pNode.next.left == pNode) {
return pNode.next;
}
// 3.
if (pNode.next != null) {
TreeLinkNode pNext = pNode.next;
while (pNext.next != null && pNext.next.right == pNext) {
pNext = pNext.next;
}
return pNext.next;
}
return null;
}
58.对称的二叉树
public class Solution {
public boolean isSymmetrical(TreeNode root) {
return check(root, root);
}
boolean check(TreeNode a, TreeNode b) {
if (a == null && b == null) return true;
if (a == null || b == null) return false;
if (a.val != b.val) return false;
return check(a.left, b.right) && check(a.right, b.left);
}
}
59.按之字形顺序打印二叉树
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
LinkedList<TreeNode> q = new LinkedList<>();
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
boolean rev = true;
q.add(pRoot);
while(!q.isEmpty()){
int size = q.size();
ArrayList<Integer> list = new ArrayList<>();
for(int i=0; i<size; i++){
TreeNode node = q.poll();
if(node == null){
continue;
}
if(rev){
list.add(node.val);
}else{
list.add(0, node.val);
}
q.offer(node.left);
q.offer(node.right);
}
if(list.size()!=0){
res.add(list);
}
rev = !rev;
}
return res;
}
60.把二叉树打印成多行
// 递归:
public class Solution {
//层次遍历
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer> > res = new ArrayList<ArrayList<Integer> >();
//树的层级从1开始递归计数
traverse(pRoot, res, 1);
return res;
}
private void traverse(TreeNode root, ArrayList<ArrayList<Integer> > res, int depth) {
if (root == null) return ;
//数组长度小于当前层数,新开一层
if (res.size() < depth)
res.add(new ArrayList<Integer>());
//数组从0开始计数因此减1,在节点当前层的数组中插入节点
res.get(depth - 1).add(root.val);
//递归左右时节点深度记得加1
traverse(root.left, res, depth + 1);
traverse(root.right, res, depth + 1);
}
}
迭代:
//层次遍历
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
TreeNode head = pRoot;
ArrayList<ArrayList<Integer> > res = new ArrayList<ArrayList<Integer> >();
if(head == null)
//如果是空,则直接返回空数组
return res;
//队列存储,进行层次遍历
Queue<TreeNode> temp = new LinkedList<TreeNode>();
temp.offer(head);
TreeNode p;
while(!temp.isEmpty()){
//记录二叉树的某一行
ArrayList<Integer> row = new ArrayList<Integer>();
int n = temp.size();
//因先进入的是根节点,故每层节点多少,队列大小就是多少
for(int i = 0; i < n; i++){
p = temp.poll();
row.add(p.val);
//若是左右孩子存在,则存入左右孩子作为下一个层次
if(p.left != null)
temp.offer(p.left);
if(p.right != null)
temp.offer(p.right);
}
res.add(row);
}
return res;
}
61.序列化二叉树
public class Solution {
int INF = 0x3f3f3f3f;
TreeNode emptyNode = new TreeNode(INF);
public String Serialize(TreeNode root) {
if (root == null) return "";
StringBuilder sb = new StringBuilder();
// 使用队列进行层序遍历,起始先将 root 放入队列
Deque<TreeNode> d = new ArrayDeque<>();
d.addLast(root);
while (!d.isEmpty()) {
// 每次从队列中取出元素进行「拼接」,包括「正常节点」和「叶子节点对应的首位空节点」
TreeNode poll = d.pollFirst();
sb.append(poll.val + "_");
// 如果取出的节点不为「占位节点」,则继续往下拓展,同时防止「占位节点」不继续往下拓展
if (!poll.equals(emptyNode)) {
d.addLast(poll.left != null ? poll.left : emptyNode);
d.addLast(poll.right != null ? poll.right : emptyNode);
}
}
return sb.toString();
}
public TreeNode Deserialize(String data) {
if (data.equals("")) return null;
// 根据分隔符进行分割
String[] ss = data.split("_");
int n = ss.length;
// 怎么序列化就怎么反序列化
// 使用队列进行层序遍历,起始先将 root 构建出来,并放入队列
TreeNode root = new TreeNode(Integer.parseInt(ss[0]));
Deque<TreeNode> d = new ArrayDeque<>();
d.addLast(root);
for (int i = 1; i < n - 1; i += 2) {
TreeNode poll = d.pollFirst();
// 每次从中取出左右节点对应 val
int a = Integer.parseInt(ss[i]), b = Integer.parseInt(ss[i + 1]);
// 如果左节点对应的值不是 INF,则构建「真实节点」
if (a != INF) {
poll.left = new TreeNode(a);
d.addLast(poll.left);
}
// 如果右节点对应的值不是 INF,则构建「真实节点」
if (b != INF) {
poll.right = new TreeNode(b);
d.addLast(poll.right);
}
}
return root;
}
}
递归:
public class Solution {
public String Serialize(TreeNode root) {
if(root == null) return "#"; // 对所有的空节点也要存储占位符号"#"
// 递归前序遍历返回字符串
return root.val + "," + Serialize(root.left) + "," + Serialize(root.right);
}
public TreeNode Deserialize(String str) {
String[] s = str.split(","); // 切分字符串
Queue<String> q = new LinkedList<String>();
for(int i = 0; i != s.length; i++)
q.offer(s[i]); // 将分割后的字符串数组顺序入队
return de(q); // 按照新的递归函数进行返回
}
public TreeNode de(Queue<String> queue) {
String s = queue.poll(); // 递归函数每次从队首读出一个元素,因此读出的顺序也是前序
if(s.equals("#")) return null; // 对递归推出条件之一进行处理
TreeNode head = new TreeNode(Integer.valueOf(s)); // 前序首先建立一个节点
head.left = de(queue); // 然后递归左右子节点
head.right = de(queue);
return head;
}
}
62.二叉搜索树的第K个节点
public class Solution {
int count = 0; //标记遍历的节点数
public int KthNode (TreeNode proot, int k) {
if(proot == null) return -1;
int l = KthNode(proot.left, k);
if (l != -1) return l;
++count;
if(count == k) return proot.val;
int r = KthNode(proot.right, k);
return r;
}
}
65.矩阵中的路径
public class Solution {
public boolean hasPath (char[][] matrix, String word) {
char[] words = word.toCharArray();
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
//从[i,j]这个坐标开始查找
if (dfs(matrix, words, i, j, 0))
return true;
}
}
return false;
}
boolean dfs(char[][] matrix, char[] word, int i, int j, int index) {
if (i >= matrix.length || i < 0 || j >= matrix[0].length || j < 0 || matrix[i][j] != word[index])
return false;
if (index == word.length - 1) return true;
char tmp = matrix[i][j];
matrix[i][j] = '.';
//走递归,沿着当前坐标的上下左右4个方向查找
boolean res = dfs(matrix, word, i + 1, j, index + 1)
|| dfs(matrix, word, i - 1, j, index + 1)
|| dfs(matrix, word, i, j + 1, index + 1)
|| dfs(matrix, word, i, j - 1, index + 1);
//递归之后再把当前的坐标复原
matrix[i][j] = tmp;
return res;
}
}
66.机器人的运动范围
public int movingCount(int threshold, int rows, int cols) {
//临时变量visited记录格子是否被访问过
boolean[][] visited = new boolean[rows][cols];
return dfs(0, 0, rows, cols, threshold, visited);
}
public int dfs(int i, int j, int rows, int cols, int threshold, boolean[][] visited) {
//i >= rows || j >= cols是边界条件的判断,threshold < sum(i, j)判断当前格子坐标是否
// 满足条件,visited[i][j]判断这个格子是否被访问过
if (i >= rows || j >= cols || threshold < sum(i, j) || visited[i][j])
return 0;
//标注这个格子被访问过
visited[i][j] = true;
//沿着当前格子的右边和下边继续访问
return 1 + dfs(i + 1, j, rows, cols, threshold, visited) +
dfs(i, j + 1, rows, cols, threshold, visited);
}
//计算两个坐标数字的和
private int sum(int i, int j) {
int sum = 0;
//计算坐标i所有数字的和
while (i != 0) {
sum += i % 10;
i /= 10;
}
//计算坐标j所有数字的和
while (j != 0) {
sum += j % 10;
j /= 10;
}
return sum;
}
剪绳子
public int cutRope (int target) {
int max = Integer.MIN_VALUE;
int sum = 1;
for(int i=2; i<=target; i++){
int num = target;
for(int j=i; j>0; j--){
if(target < j){
continue;
}
sum = sum * (num/j);
num = num - num/j;
}
max = Math.max(max, sum);
sum =1;
}
return max;
}
剪绳子(进阶版)
public class Solution {
public long cutRope (long number) {
if (number <= 3) return number - 1;
long a = number / 3;
long b = number % 3;
long c = 998244353L;
if (b == 0) {
return pow3WithMod(a, c) % c;
} else if (b == 1) {
return pow3WithMod(a-1, c) * 4 % c;
} else {
return pow3WithMod(a, c) * 2 % c;
}
}
public long pow3WithMod(long n, long mod) {
if (n == 0) return 1;
if (n == 1) return 3;
long part = pow3WithMod(n/2, mod);
if (n % 2 == 0) return part * part % mod;
else return 3 * part * part % mod;
}
}
5.用两个栈实现队列
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
//入栈操作
public void push(int node) {
stack1.push(node);
}
//出栈操作
public int peek() {
if (stack2.isEmpty())
while (!stack1.isEmpty())
stack2.push(stack1.pop());
return stack2.isEmpty() ? -1 : stack2.peek();
}
public int pop() {
int res = peek();
stack2.pop();
return res;
}
public boolean empty() {
return stack1.empty() && stack2.empty();
}
}
20.包含min函数的栈
public class Solution {
Stack<Integer> stackmin = new Stack<>();
Stack<Integer> stack = new Stack<>();
public void push(int node) {
stack.push(node);
if (stackmin.empty()) {
stackmin.push(node);
} else {
if (stackmin.peek() < node)
stackmin.push(stackmin.peek());
else
stackmin.push(node);
}
}
public void pop() {
stack.pop();
stackmin.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return stackmin.peek();
}
}
21.栈的压入弹出序列
public boolean IsPopOrder(int [] pushA, int [] popA) {
if (pushA.length == 0 || popA.length == 0) return false;
Stack<Integer> stack = new Stack<>();
int popIndex = 0;
for (int i = 0; i < pushA.length; i++) {
stack.push(pushA[i]);
while (!stack.isEmpty() && stack.peek() == popA[popIndex]) {
stack.pop();
popIndex++;
}
}
return stack.isEmpty();
}
7.斐波那契数列
// 递归
public int Fibonacci(int n) {
if(n<=1){
return n;
}
return Fibonacci(n-1) + Fibonacci(n-2);
}
// 迭代
public int Fibonacci(int n) {
if(n == 0) return 0;
if(n == 1) return 1;
int a = 1;
int b = 0;
for(int i=2;i<=n;i++){
a = a + b;
b = a - b;
}
return a;
}
8.跳台阶
// 会超时
public static int JumpFloor(int n) {
if (n <= 1) return 1;
if (n < 3) return n;
return JumpFloor(n - 1) + JumpFloor(n - 2);
}
尾递归方式:
public int jumpFloor(int n) {
return Fibonacci(n, 1, 1);
}
public int Fibonacci(int n, int a, int b) {
if (n <= 1) return b;
return Fibonacci(n - 1, b, a + b);
}
迭代:
public int jumpFloor(int n) {
if (n <= 2) return n;
int first = 1, second = 2, sum = 0;
while (n-- > 2) {
sum = first + second;
first = second;
second = sum;
}
return sum;
}
9.跳台阶扩展问题
//f(n)=2^(n−1)
public int jumpFloorII(int target) {
return 1<<(target-1);
}
10.矩形覆盖
public int rectCover(int target) {
if (target <= 2){
return target;
}
int pre1 = 2; // n 最后使用一块,剩下 n-1 块的写法
int pre2 = 1; // n 最后使用两块,剩下 n-2 块的写法
for (int i = 3; i <= target; i++){
int cur = pre1 + pre2;
pre2 = pre1;
pre1 = cur;
}
return pre1; //相对于 n+1 块来说,第 n 种的方法
}
递归:
public int rectCover(int target) {
if (target <= 2) return target;
return rectCover(target - 1) + rectCover(target - 2);
}
12.数值的整数次方
public class Solution {
public double Power(double base, int exponent) {
return exponent > 0 ? quickPow(base, exponent) : quickPow(1/base, -exponent);
}
public double quickPow(double base, int exp) {
if(exp == 0) {
return 1;
}
if(exp == 1) {
return base;
}
return Power(base, exp/2)*Power(base, exp - exp/2);
}
}
47.求1+2+3+···+n数字之和
public int Sum_Solution(int n) {
return (1+n)*n/2;
}
48.不用加减乘除做加法
迭代解法:
public int Add(int a, int b) {
while (b != 0) {
int temp = a ^ b;
b = (a & b) << 1;
a = temp;
}
return a;
}
递归:
public int Add(int a, int b) {
if (a == 0 || b == 0)
return a ^ b;
return Add(a ^ b, (a & b) << 1);
}
11.二进制中1的个数
public int NumberOf1(int n) {
int res = 0;
while(n != 0){
if((n & 1) == 1) res++;
//无符号右移,正数负数最高位都补0
n >>>= 1;
}
return res;
}
29.最小的K个数
public class Solution {
// 使用快速排序取前k个
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<Integer>();
if(input.length < k){
return list;
}
quickSort(input,0,input.length - 1);
for(int i = 0;i < k;i++){
list.add(input[i]);
}
return list;
}
public static void quickSort(int[] array,int start,int tail) {
if(start >= tail) {
return;
}
//将第一个元素作为比较元素,从第二个开始到最后一个执行快速排序算法
int begin = start;
int end = tail;
int key = array[start];
while(begin < end) {
while(array[end] >= key && begin < end) {
end = end - 1;
}
while(array[begin] <= key && begin < end) {
begin = begin + 1;
}
if(end > begin) {
int temp = array[begin];
array[begin] = array[end];
array[end] = temp;
}
}
array[start] = array[begin];
array[begin] = key;
quickSort(array,start,begin - 1);
quickSort(array,begin + 1,tail);
}
}
解法2:优先队列(大根堆)
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
PriorityQueue<Integer> queue=new PriorityQueue<Integer>((a, b)->{
return a.intValue()-b.intValue();
});
for(int a:input)
queue.offer(a);
ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<k;i++)
list.add(queue.poll());
return list;
}
实现大根堆:
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
if (input == null || input.length == 0 || k > input.length || k == 0)
return list;
int[] arr = new int[k + 1];//数组下标0的位置作为哨兵,不存储数据
//初始化数组
for (int i = 1; i < k + 1; i++)
arr[i] = input[i - 1];
buildMaxHeap(arr, k + 1);//构造大根堆
for (int i = k; i < input.length; i++) {
if (input[i] < arr[1]) {
arr[1] = input[i];
adjustDown(arr, 1, k + 1);//将改变了根节点的二叉树继续调整为大根堆
}
}
for (int i = 1; i < arr.length; i++) {
list.add(arr[i]);
}
return list;
}
public void buildMaxHeap(int[] arr, int length) {
if (arr == null || arr.length == 0 || arr.length == 1)
return;
for (int i = (length - 1) / 2; i > 0; i--) {
adjustDown(arr, i, arr.length);
}
}
public void adjustDown(int[] arr, int k, int length) {
arr[0] = arr[k];//哨兵
for (int i = 2 * k; i <= length; i *= 2) {
if (i < length - 1 && arr[i] < arr[i + 1])
i++;//取k较大的子结点的下标
if (i > length - 1 || arr[0] >= arr[i])
break;
else {
arr[k] = arr[i];
k = i; //向下筛选
}
}
arr[k] = arr[0];
}
}
31.从1到n整数中1出现的次数
public int NumberOf1Between1AndN_Solution(int n) {
// 公式
// cur = 1 count = high * i + (low + 1);
// cur = 0 count = high * i;
// cur = 其他数 count = (high + 1) * i;
// 一位一位的算
int count = 0;
for (int i = 1; i <= n; i *= 10) {
int high = n/(i*10);
int low = n%i;
int cur = (n/i) % 10;
if (cur == 0) {
count += high*i;
} else if (cur == 1) {
count += high*i + (low + 1);
} else {
count += (high + 1) * i;
}
}
return count;
}
解法2:
public int NumberOf1Between1AndN_Solution(int n) {
int cnt = 0;
for (int i = 1; i <= n; i++) {
String str = String.valueOf(i);
for (int j = 0; j < str.length(); j++) {
if (str.charAt(j) == '1') cnt++;
}
}
return cnt;
}
33.丑数
public int GetUglyNumber_Solution(int n) {
if (n <= 0) return 0;
int p2 = 0, p3 = 0, p5 = 0; //初始化三个指向三个潜在成为最小丑数的位置
int[] result = new int[n];
result[0] = 1;
for (int i = 1; i < n; i++) { //每一次乘以2、3、5的最小值当做本次的丑数
result[i] = Math.min(result[p2] * 2, Math.min(result[p3] * 3, result[p5] * 5));
if (result[i] == result[p2] * 2) p2++; //如果本次的丑数是乘2得到的,则下次就用紧接着的丑数result[p2++]乘2生成丑数
if (result[i] == result[p3] * 3) p3++; //否则,下次乘2生成丑数,还用该丑数生成result[p2]
if (result[i] == result[p5] * 5) p5++; //
}
return result[n - 1];
}
41.和为S的连续正数序列
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
//双指针
int start = 1;
int end = 2;
int curSum = 3; //当前的和
while ( start < end ){
if( curSum < sum ){
end++;
curSum += end;
}else if( curSum > sum ){
curSum -= start;
start++;
}else{
ArrayList<Integer> list = new ArrayList<Integer>();
for( int i = start; i <= end; i++ ){
list.add(i);
}
result.add(list);
//这里要移动start否则while旋死 curSum的值也要继续处理
//否则继续while的时候就算错了
curSum -= start;
start++;
//下面移动end 也可以不用管 交给下一次while的时候判断
//但是这里移动了到最后可以少进一次while
end++;
curSum +=end;
}
}
return result;
}
42.和为S的两个数字
public ArrayList<Integer> FindNumbersWithSum(int[] array,int sum) {
int l = 0;
int r = array.length-1;
ArrayList<Integer> arr = new ArrayList<Integer>();
while (l < r){
if (array[l] + array[r] == sum){
arr.add(array[l]);
arr.add(array[r]);
return arr;
}else if (array[l] + array[r] > sum){
r--;
}else{
l++;
}
}
return arr;
}
45.扑克牌顺子
public boolean IsContinuous(int [] numbers) {
Set<Integer> set = new HashSet<>();
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] == 0) continue;
if (set.contains(numbers[i])) {
return false;
}
set.add(numbers[i]);
}
ArrayList<Integer> list = new ArrayList<>(set);
if (list.get(list.size() - 1) - list.get(0) < 5) return true;
return false;
}
实现2:
public boolean IsContinuous(int [] numbers) {
int low = Integer.MAX_VALUE;
int high = Integer.MIN_VALUE;
int[] cnt = new int[14];
for(int num : numbers){
cnt[num]++;
if((num != 0 && cnt[num] > 1) || (num == 0 && cnt[num] > 4)) return false;
if(num == 0) continue;
low = Math.min(num,low);
high = Math.max(num,high);
}
return high - low < 5 ? true : false;
}
46.圆圈中最后剩下的数
public int LastRemaining_Solution(int n, int m) {
if(n <= 0 || m <= 0) return -1;
int ans = 0;
for(int i = 2; i <= n; i++){
ans = (ans + m) % i;
}
return ans;
}
买卖股票的最好时机(一)
public int maxProfit (int[] prices) {
int min = prices[0];
int max = 0;
for(int i=0;i<prices.length;i++){
min=Math.min(prices[i], min);
max=Math.max(max, prices[i]-min);
}
return max;
}
63.数据流中的中位数
public class Solution {
//小顶堆,元素数值都比大顶堆大
private PriorityQueue<Integer> max = new PriorityQueue<>();
//大顶堆,元素数值较小
private PriorityQueue<Integer> min = new PriorityQueue<>((a, b) -> b.compareTo(a));
//维护两个堆,取两个堆顶部即与中位数相关
public void Insert(Integer num) {
//先加入较小部分
min.offer(num);
//将较小部分的最大值取出,送入到较大部分
max.offer(min.poll());
//平衡两个堆的数量
if(min.size() < max.size())
min.offer(max.poll());
}
public Double GetMedian() {
//奇数个
if(min.size() > max.size()) return (double)min.peek();
else return (double)(min.peek() + max.peek()) / 2;
}
}
解法2:
public class Solution {
private static List<Integer> list = new ArrayList<>();
public void Insert(Integer num) {
if (num == null) return;
if (list.size() == 0) {
list.add(num);
return;
}
int left = 0;
int right = list.size() - 1;
int mid = 0;
while (left <= right) {
mid = (left + right) >> 1;
if (list.get(mid) > num) {
right = mid - 1;
} else {
left = mid + 1;
}
}
list.add(left, num);
}
public Double GetMedian() {
int size = list.size();
if ((size & 1) == 1) {
return (double) list.get(size >> 1);
}
return (list.get(size >> 1) + list.get((size - 1) >> 1)) / 2.0;
}
}
64.滑动窗口的最大值
public class Solution {
public ArrayList<Integer> maxInWindows(int [] num, int size) {
ArrayList<Integer> res = new ArrayList<>();
int len = num.length;
//左闭右开
int preMaxIndex = -1;
for(int p=0,q = size; q <= len; p++,q++){
int maxIndex = preMaxIndex;
if(preMaxIndex >= p && preMaxIndex< q){
// 前置窗口最大值还在当前窗口内,只需比较前置最大值和窗口末尾值
if(num[q-1] > num[preMaxIndex]){
maxIndex = q-1;
}
}else{
maxIndex = getMax(num,p,q);
}
res.add(num[maxIndex]);
preMaxIndex = maxIndex;
}
return res;
}
/**
* 返回最大值的索引
*/
public int getMax(int[] num, int p, int q){
//初始化为第一个
int maxIndex = p;
for(int i = p+1; i< q; i++){
if(num[i]>num[maxIndex]){
maxIndex = i;
}
}
return maxIndex;
}
}
解法2:
public ArrayList<Integer> maxInWindows(int[] num, int size) {
Deque<Integer> queue = new ArrayDeque<>();
int left = 0;
int right = 0;
ArrayList<Integer> res = new ArrayList<>();
while (right < num.length) {
if (right - left < size) {
int val = num[right];
while (!queue.isEmpty() && val > queue.peekLast()) {
queue.pollLast();
}
queue.offerLast(val);
right++;
} else {
// len >= size
int leftVal = num[left];
int minVal = queue.peekFirst();
res.add(minVal);
if (leftVal == minVal) {
queue.pollFirst();
}
left++;
}
}
res.add(queue.peekFirst());
return res;
}
JZ44数字序列中某一位的数字
public int findNthDigit (int n) {
// 0
// 1 ~ 9 | digit = 1 start = 1 * 1 count = 1 * 9 * 1
// 10 ~ 99 | digit = 2 start = 1 * 10 count = 10 * 9 * 2
// 100 ~ 999 | digit = 3 start = 1 * 10 * 10 count = 100 * 9 * 3
if (n <= 0) return 0;
long start = 1, digit = 1, count = 9;
while (n > count) {
n -= count; // 减去当前位数的总长度
start *= 10;
digit += 1;
count = start * 9 * digit;
}
// 找到当前位数的区间了
String num = (start + (n - 1) / digit) + ""; // 减去第0号元素0
int idx = (int)((n - 1) % digit);
return Integer.parseInt(num.charAt(idx) + "");
}
JZ46把数字翻译成字符串
public int solve (String nums) {
//用来保留前面两个字符位置对应的可能译码结果数
int first = 1, second = 1;
for(int i = 0;i < nums.length();i++){
//遇到0不能译码,second清零
if(nums.charAt(i) == '0'){
second = 0;
}
//更新前两个字符对应的值
//符合条件则可以译1个或者2个数字
if(i >= 1 && Integer.parseInt(nums.substring(i-1,i+1)) <= 26){
second = first + second;
first = second - first;
}
//只能译1个数字
else{
first = second;
}
}
return second;
}