文章目录

  • 栈与队列(简单)
    • *剑指 Offer 09. 用两个栈实现队列 - 12.27
    • 剑指 Offer 30. 包含min函数的栈 - 12.27
  • 链表(简单)
    • *剑指 Offer 06. 从尾到头打印链表 - 12.28
    • 剑指 Offer 24. 反转链表 - 12.28
    • 剑指 Offer 35. 复杂链表的复制 - 12.28
  • 字符串(简单)
    • *剑指 Offer 05. 替换空格 - 12.29
    • 剑指 Offer 58 - II. 左旋转字符串 -12.29
  • 查找算法(简单)
    • *剑指 Offer 03. 数组中重复的数字 - 12.30
    • *剑指 Offer 53 - I. 在排序数组中查找数字 I - 12.30
    • *剑指 Offer 53 - II. 0~n-1中缺失的数字 - 12.30
  • 查找算法(中等)
    • 剑指 Offer 04. 二维数组中的查找 - 12.31
    • *剑指 Offer 11. 旋转数组的最小数字 - 12.31
    • 剑指 Offer 50. 第一个只出现一次的字符 - 12.31
  • 搜索与回溯算法(简单)- 1.4
    • 剑指 Offer 32 - I. 从上到下打印二叉树
    • 剑指 Offer 32 - II. 从上到下打印二叉树 II
    • 剑指 Offer 32 - III. 从上到下打印二叉树 III
  • 搜索与回溯算法(简单) - 1.5
    • 剑指 Offer 26. 树的子结构
    • 剑指 Offer 27. 二叉树的镜像
    • 剑指 Offer 28. 对称的二叉树
  • 动态规划(简单)- 1.6
    • 剑指 Offer 10- I. 斐波那契数列
    • 剑指 Offer 10- II. 青蛙跳台阶问题
    • 剑指 Offer 63. 股票的最大利润
  • 动态规划(中等)- 1.7
    • 剑指 Offer 42. 连续子数组的最大和
    • 剑指 Offer 47. 礼物的最大价值
  • 动态规划(中等)- 1.10
    • 剑指 Offer 46. 把数字翻译成字符串
    • 剑指 Offer 48. 最长不含重复字符的子字符串
  • 双指针(简单)- 1.11
    • *剑指 Offer 18. 删除链表的节点
    • *剑指 Offer 22. 链表中倒数第k个节点
  • 双指针(简单)- 1.11
    • *剑指 Offer 25. 合并两个排序的链表
    • 剑指 Offer 52. 两个链表的第一个公共节点
  • 双指针(简单)- 1.12
    • *剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
    • *剑指 Offer 57. 和为s的两个数字
    • 剑指 Offer 58 - I. 翻转单词顺序
  • 搜索与回溯算法(中等)- 1.13
    • 剑指 Offer 12. 矩阵中的路径
    • 剑指 Offer 13. 机器人的运动范围
  • 搜索与回溯算法(中等)- 1.14
    • 剑指 Offer 34. 二叉树中和为某一值的路径
    • 剑指 Offer 36. 二叉搜索树与双向链表
    • 剑指 Offer 54. 二叉搜索树的第k大节点
  • 排序(简单)- 1.17
    • 剑指 Offer 45. 把数组排成最小的数
    • 剑指 Offer 61. 扑克牌中的顺子
  • 排序(中等)- 1.18
    • *剑指 Offer 40. 最小的k个数
    • *剑指 Offer 41. 数据流中的中位数
  • 搜索与回溯算法(中等)- 1.18
    • *剑指 Offer 55 - I. 二叉树的深度
    • 剑指 Offer 55 - II. 平衡二叉树
  • 搜索与回溯算法(中等)- 1.19
    • 剑指 Offer 64. 求1+2+…+n
    • 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
    • 剑指 Offer 68 - II. 二叉树的最近公共祖先
  • 分治算法(中等)- 1.19
    • 剑指 Offer 07. 重建二叉树
    • 剑指 Offer 16. 数值的整数次方
    • 剑指 Offer 33. 二叉搜索树的后序遍历序列
  • 位运算(简单)- 1.20
    • 剑指 Offer 15. 二进制中1的个数
    • 剑指 Offer 65. 不用加减乘除做加法
  • 位运算(中等)- 1.20
    • 剑指 Offer 56 - I. 数组中数字出现的次数
    • 剑指 Offer 56 - II. 数组中数字出现的次数 II
  • 数学(简单)- 1.21
    • *剑指 Offer 39. 数组中出现次数超过一半的数字
    • *剑指 Offer 66. 构建乘积数组
  • 数学(中等)- 1.21
    • 剑指 Offer 14- I. 剪绳子
    • 剑指 Offer 57 - II. 和为s的连续正数序列
    • 剑指 Offer 62. 圆圈中最后剩下的数字
  • 模拟(中等)- 1.22
    • 剑指 Offer 29. 顺时针打印矩阵
    • 剑指 Offer 31. 栈的压入、弹出序列
  • 字符串(中等)- 1.24
    • 剑指 Offer 20. 表示数值的字符串
    • 剑指 Offer 67. 把字符串转换成整数
  • 栈与队列(困难)- 1.24
    • *剑指 Offer 59 - I. 滑动窗口的最大值
    • *剑指 Offer 59 - II. 队列的最大值
  • 搜索与回溯算法(困难)- 1.25
    • 剑指 Offer 37. 序列化二叉树
    • 剑指 Offer 38. 字符串的排列
  • 动态规划(困难)- 1.25
    • 剑指 Offer 19. 正则表达式匹配
    • 剑指 Offer 49. 丑数
    • 剑指 Offer 60. n个骰子的点数
  • 分治算法(困难)- 1.26
    • 剑指 Offer 17. 打印从1到最大的n位数
    • 剑指 Offer 51. 数组中的逆序对
  • 数学(困难)-1.27
    • 剑指 Offer 14- II. 剪绳子 II
    • 剑指 Offer 43. 1~n 整数中 1 出现的次数
    • 剑指 Offer 44. 数字序列中某一位的数字

栈与队列(简单)

*剑指 Offer 09. 用两个栈实现队列 - 12.27

剑指 Offer 09. 用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )


解析:先初始化两个栈,appendTail方法负责添加元素到栈1;调用deleteHead方法时,先把栈1的值转移到栈2中,然后取栈2 栈顶值出栈返回,就是结果,注意记录后还需把栈2的值转移回栈1中去。

class CQueue {Stack<Integer> stack1;Stack<Integer> stack2;public CQueue() {stack1 = new Stack<>();stack2 = new Stack<>();}public void appendTail(int value) {stack1.push(value); //添加元素}public int deleteHead() {//当队列中没有元素,返回-1if(stack1.isEmpty() && stack2.isEmpty()) return -1;//将栈1中的元素转移到栈2中while(!stack1.isEmpty()){int temp = stack1.pop();stack2.push(temp);}//记录要删除的值int result = stack2.pop();//把栈2的值再转移到栈1中while(!stack2.isEmpty()){int temp = stack2.pop();stack1.push(temp);}        return result;}
}/*** Your CQueue object will be instantiated and called as such:* CQueue obj = new CQueue();* obj.appendTail(value);* int param_2 = obj.deleteHead();*/


解法二:优化上面解法

class CQueue {Stack<Integer> stack1;Stack<Integer> stack2;public CQueue() {stack1 = new Stack<>();stack2 = new Stack<>();}public void appendTail(int value) {stack1.push(value); //添加元素}public int deleteHead() {//当队列中没有元素,返回-1if(stack1.isEmpty() && stack2.isEmpty()) return -1;//如果栈2不为空,则可直接返回;否则,需要将栈1的元素转移到栈2中;if(!stack2.isEmpty()){return stack2.pop();}else {while(!stack1.isEmpty()){int temp = stack1.pop();stack2.push(temp);}} return stack2.pop();}
}/*** Your CQueue object will be instantiated and called as such:* CQueue obj = new CQueue();* obj.appendTail(value);* int param_2 = obj.deleteHead();*/


剑指 Offer 30. 包含min函数的栈 - 12.27

剑指 Offer 30. 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。


解析:借助辅助栈来存放递减元素

class MinStack {private Stack<Integer> stack;private Stack<Integer> minStack;   /** initialize your data structure here. */public MinStack() {stack = new Stack<>();minStack = new Stack<>();}public void push(int x) {stack.push(x);//如果最小栈为空 或 最小栈的栈顶元素大于等于x,就添加元素if(minStack.isEmpty() || minStack.peek() >= x){minStack.push(x);}}public void pop() {//主栈出栈,然后比较,如果主栈出栈的元素 等于 最小栈的栈顶元素,那么最小栈也要出栈if(stack.pop().equals(minStack.peek())){minStack.pop();}}public int top() {return stack.peek();}public int min() {return minStack.peek();}
}/*** Your MinStack object will be instantiated and called as such:* MinStack obj = new MinStack();* obj.push(x);* obj.pop();* int param_3 = obj.top();* int param_4 = obj.min();*/


链表(简单)

*剑指 Offer 06. 从尾到头打印链表 - 12.28

剑指 Offer 06. 从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。


解法一:遍历链表时,用list存起来,然后反转list,再转为int数组返回。

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode(int x) { val = x; }* }*/
class Solution {public int[] reversePrint(ListNode head) {List<Integer> list = new ArrayList<>();while(head != null){list.add(head.val); //把元素添加进listhead = head.next; //下一个}//反转集合Collections.reverse(list);//list转int数组int[] arr = list.stream().mapToInt(Integer::valueOf).toArray(); return arr;}
}


解法二:先计算出链表长度,然后定义数组,从尾到头开始赋值。

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode(int x) { val = x; }* }*/
class Solution {public int[] reversePrint(ListNode head) {int len = 0;ListNode temp = head;//统计链表长度while (temp != null) {temp = temp.next;len++;}int[] ans = new int[len];//从尾到头 开始赋值for (int i = len - 1; i >= 0; i--) {ans[i] = head.val;head = head.next;}return ans;}
}

剑指 Offer 24. 反转链表 - 12.28

剑指 Offer 24. 反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。


解析:双指针法

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode(int x) { val = x; }* }*/
class Solution {public ListNode reverseList(ListNode head) {ListNode pre = null; //前一个节点ListNode cur = head; //当前节点ListNode temp = null; //临时节点while(cur != null){temp = cur.next; //保存cur的指向cur.next = pre; //反转链表pre = cur; //移动cur = temp; //移动}return pre;}
}

剑指 Offer 35. 复杂链表的复制 - 12.28

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。


解析:使用哈希map来构建从旧节点到新节点的映射关系,再重新构建指向。

/*
// Definition for a Node.
class Node {int val;Node next;Node random;public Node(int val) {this.val = val;this.next = null;this.random = null;}
}
*/
class Solution {public Node copyRandomList(Node head) {if(head == null) return null;Node cur = head;Map<Node,Node> map = new HashMap<>();//构建新旧节点的映射关系while(cur != null){map.put(cur, new Node(cur.val)); //构建键值对cur = cur.next; //移动}//重置cur = head;//构建新链表的 next 和 random 指向while(cur != null){map.get(cur).next = map.get(cur.next);map.get(cur).random = map.get(cur.random);cur = cur.next;}return map.get(head);}
}

字符串(简单)

*剑指 Offer 05. 替换空格 - 12.29

剑指 Offer 05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。


解法一:先扩容空间,空格数量的两倍,再定义左指针:指向原始字符串最后一个位置,右指针:指向扩展字符串的最后一个位置,进行判断赋值操作。

class Solution {public String replaceSpace(String s) {if(s == null || s.length() == 0){return s;}//扩充空间,空格数量2倍StringBuilder str = new StringBuilder();for (int i = 0; i < s.length(); i++) {if(s.charAt(i) == ' '){str.append("  ");}}//若是没有空格直接返回if(str.length() == 0){return s;}//有空格情况 定义两个指针int left = s.length() - 1;//左指针:指向原始字符串最后一个位置s += str.toString();int right = s.length()-1;//右指针:指向扩展字符串的最后一个位置char[] chars = s.toCharArray();while(left>=0){if(chars[left] == ' '){chars[right--] = '0';chars[right--] = '2';chars[right] = '%';}else{chars[right] = chars[left];}left--;right--;}return new String(chars);}
}

*解法二:

class Solution {public String replaceSpace(String s) {if(s == null || s.length() == 0){return s;}StringBuilder sb = new StringBuilder();for(int i=0; i<s.length(); i++){//如果是空格,就追加 %20 ;否则就追加原来的字符。if(s.charAt(i) == ' '){sb.append("%20") ;}else{sb.append(s.charAt(i));}}return new String(sb);}
}

剑指 Offer 58 - II. 左旋转字符串 -12.29

剑指 Offer 58 - II. 左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。


解法一:将字符串分成两部分,然后各自反转,最后再整个反转即可。如abcde,k=2,分别反转得ba edc ,再整体反转得 cdeab。

class Solution {public String reverseLeftWords(String s, int n) {int len=s.length();StringBuilder sb=new StringBuilder(s);reverseString(sb,0,n-1);reverseString(sb,n,len-1);return sb.reverse().toString();}//反转字符串public void reverseString(StringBuilder sb, int start, int end) {while (start < end) {char temp = sb.charAt(start);sb.setCharAt(start, sb.charAt(end));sb.setCharAt(end, temp);start++;end--;}}
}


解法二:创建一个StringBuilder,先添加后半部分字符串,再添加前半部分字符串。

class Solution {public String reverseLeftWords(String s, int n) {if(s == null) return null;StringBuilder sb = new StringBuilder();//字符串转字符数组char[] ch = s.toCharArray();//先添加后半部分for(int i=n; i<s.length(); i++){sb.append(ch[i]);}//再添加前半部分for(int i=0; i<n; i++){sb.append(ch[i]);}        return new String(sb);}
}

查找算法(简单)

*剑指 Offer 03. 数组中重复的数字 - 12.30

剑指 Offer 03. 数组中重复的数字

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。


解析:使用map存储元素,然后查找。

class Solution {public int findRepeatNumber(int[] nums) {if(nums.length < 0) return 0;Map<Integer, Integer> map = new HashMap<>();for(int i=0; i<nums.length; i++){//如果map里有num[i],说明重复了,直接返回if(map.containsKey(nums[i])){return nums[i];}else{map.put(nums[i],1);}}return 0;}
}

*剑指 Offer 53 - I. 在排序数组中查找数字 I - 12.30

剑指 Offer 53 - I. 在排序数组中查找数字 I

统计一个数字在排序数组中出现的次数。


解法一:使用二分查找,对比中间值,找到后分别统计左右两边与目标值相等的次数。

class Solution {public int search(int[] nums, int target) {int len = nums.length;if(len < 0) return 0;int left = 0;int right = len-1;int count = 1;while(left <= right){ //二分查找int mid = (left + right) //中间值if(target > nums[mid]){   //如果目标值 大于 当前中间值,移动左边界left = mid+1;}else if(target < nums[mid]){ //如果目标值 小于 当前中间值,移动右边界right = mid-1;}else{ //相等int l=mid-1,r=mid+1;while(r < len){ //统计右边if(target == nums[r]){count++;}r++;}while(l>=0){ //统计左边if(target == nums[l]){count++;                        }l--;}  return count;              }}return 0;}
}


解法二:二分查找

二分查找模板

class Solution {public int search(int[] nums, int target) {int len = nums.length;if(len < 0) return 0;//左边界int left = searchLeft(nums,target);//右边界int right = searchRight(nums,target);//判断是否合法if(left<=right && right<len && nums[left] == target && nums[right]== target){return right-left+1;}return 0;}//找左边界的二分查找public int searchLeft(int[] nums, int target){int left = -1, right = nums.length;while(left+1 != right){int mid = (left+right)/2;if(nums[mid] >= target){right = mid;}else{left = mid;}}return right;}//找右边界的二分查找public int searchRight(int[] nums, int target){int left = -1, right = nums.length;while(left+1 != right){int mid = (left+right)/2;if(nums[mid] <= target){left = mid;}else{right = mid;}}return left;}
}

*剑指 Offer 53 - II. 0~n-1中缺失的数字 - 12.30

剑指 Offer 53 - II. 0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。


解析:如果当前元素与当前元素的下标不符合时,说明缺失了数字,返回当前元素-1后的值

class Solution {public int missingNumber(int[] nums) {int len = nums.length;if(len < 0) return 0;for(int i=0; i<len; i++){//如果缺失0,则直接返回0if(nums[0] != 0){return 0;}//如果当前元素与当前元素的下标不符合时,说明缺失了数字,返回当前元素-1后的值if(nums[i] != i){return nums[i]-1;}}return nums[len-1]+1;}
}


查找算法(中等)

剑指 Offer 04. 二维数组中的查找 - 12.31

剑指 Offer 04. 二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。


解法一:暴力查找,两重循环遍历

class Solution {public boolean findNumberIn2DArray(int[][] matrix, int target) {if(matrix == null || matrix.length == 0) return false;int x = matrix.length; //列int y = matrix[0].length; //行for(int i=0; i<x; i++){for(int j=0; j<y; j++){//找到相等的整数,返回trueif(matrix[i][j] == target){return true;}}}return false;}
}


解法二:从二维数组的右上角开始查找。如果当前元素等于目标值,则返回 true。如果当前元素大于目标值,则移到左边一列。如果当前元素小于目标值,则移到下边一行。

class Solution {public boolean findNumberIn2DArray(int[][] matrix, int target) {if(matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;int rows = matrix.length; //行int columns = matrix[0].length; //列int row = 0, column = columns-1;while(row < rows && column >= 0){int num = matrix[row][column]; //当前元素if(num == target){ //相等直接返回return true;}else if(num > target){ //如果当前元素大于目标元素,则向左查找column--;}else{ //如果当前元素小于目标元素,则向下查找row++;}}return false;}
}

*剑指 Offer 11. 旋转数组的最小数字 - 12.31

剑指 Offer 11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为1。


解析:从后往前遍历,当发现当前元素小于上一个元素时,就返回当前元素

class Solution {public int minArray(int[] numbers) {int len = numbers.length;if(len <= 0) return 0;if(len == 1) return numbers[0];for(int i=len-1; i>0; i--){//从后往前遍历,当发现当前元素小于上一个元素时,就返回当前元素if(numbers[i] < numbers[i-1]){return numbers[i];}}//已经是排好序时,返回第一个元素return numbers[0];}
}

剑指 Offer 50. 第一个只出现一次的字符 - 12.31

剑指 Offer 50. 第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。


解析:题目说明都是小写字母,可以用数组来统计次数,再按字符串的字符顺序查找出现一次的字符返回。

class Solution {public char firstUniqChar(String s) {if("".equals(s)) return ' ';int[] arr = new int[26];char[] ch = s.toCharArray();for(char c : ch){  //统计字符出现的次数int index = c - 'a';arr[index] += 1;}for(char c : ch){ //按字符串的字符顺序查找出现一次的字符int index = c - 'a';if(arr[index] == 1){return c;}}return ' ';}
}

搜索与回溯算法(简单)- 1.4

剑指 Offer 32 - I. 从上到下打印二叉树

剑指 Offer 32 - I. 从上到下打印二叉树

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。


解析:层序遍历

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public int[] levelOrder(TreeNode root) {int[] arr = {};if(root == null) return new int[0];List<Integer> list = new ArrayList<>();Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);//遍历while(!queue.isEmpty()){int len = queue.size();while(len > 0){//获取元素TreeNode node = queue.poll();//添加元素的值list.add(node.val);if(node.left != null) queue.offer(node.left);if(node.right != null) queue.offer(node.right);len--;}}//list转int数组int[] res = new int[list.size()];for(int i = 0; i < list.size(); i++)res[i] = list.get(i);return res;}
}

剑指 Offer 32 - II. 从上到下打印二叉树 II

剑指 Offer 32 - II. 从上到下打印二叉树 II

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。


解析:层序遍历时添加每一层

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> res = new ArrayList<>();if(root == null) return res;Queue<TreeNode> que = new LinkedList<>();que.offer(root);while(!que.isEmpty()){//保存每一层的结果List<Integer> list = new ArrayList<>();int len = que.size();while(len > 0){TreeNode node = que.poll();list.add(node.val);if(node.left != null) que.offer(node.left);if(node.right != null) que.offer(node.right);len--;}//添加每一层res.add(list);}return res;}
}

剑指 Offer 32 - III. 从上到下打印二叉树 III

剑指 Offer 32 - III. 从上到下打印二叉树 III

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。


解析:层序遍历时,统计层数,如果当前是奇数层,就直接添加list,偶数层则先反转list后再添加。

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> res = new ArrayList<>();if(root == null) return res;Queue<TreeNode> que = new LinkedList<>();que.offer(root);int num = 1; //记录层数while(!que.isEmpty()){//保存每一层的结果List<Integer> list = new ArrayList<>();int len = que.size();while(len > 0){TreeNode node = que.poll();list.add(node.val);if(node.left != null) que.offer(node.left);if(node.right != null) que.offer(node.right);len--;}//添加每一层if(num % 2 != 0){//奇数行直接添加res.add(list);}else{//如果是偶数行,先反转后再添加Collections.reverse(list);res.add(list);}num++;//层数加1}return res;}
}

搜索与回溯算法(简单) - 1.5

剑指 Offer 26. 树的子结构

剑指 Offer 26. 树的子结构


解析:递归

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public boolean isSubStructure(TreeNode A, TreeNode B) {//约定空树不是任意一个树的子结构if(A==null || B==null) return false;//在A树中查找与B树一样的子结构//1.直接考虑A和B是否是一样的if(isSame(A, B)){//如果直接在根节点处,就找到A和B一样的子结构,那直接返回truereturn true;}//2.考虑A的左子树是否和B一样,或者A的右子树是否和B一样return isSubStructure(A.left, B) || isSubStructure(A.right, B);}public boolean isSame(TreeNode t1, TreeNode t2){//假如t1和t2是两棵树的根节点//当t2为空时,说明已经查找完子树t2了,返回trueif(t2==null) return true;//当t2不为空,t1为空时,说明t1不含有t2子树,返回falseif(t1==null) return false;//当子树的节点不相同,说明不相等,返回falseif(t1.val!=t2.val) return false;//否则,继续往下比较,注意这里是 &&return isSame(t1.left, t2.left) && isSame(t1.right, t2.right);}}

剑指 Offer 27. 二叉树的镜像

剑指 Offer 27. 二叉树的镜像

请完成一个函数,输入一个二叉树,该函数输出它的镜像。


解析:后序遍历-递归,遍历左右节点后,反转左右节点。

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public TreeNode mirrorTree(TreeNode root) {if(root == null) return null;//后序遍历mirrorTree(root.left); //左mirrorTree(root.right); //右reverseTree(root); //根  反转return root;}//反转左右节点private void reverseTree(TreeNode root){TreeNode temp = root.left;root.left = root.right;root.right = temp;}
}

剑指 Offer 28. 对称的二叉树

剑指 Offer 28. 对称的二叉树


解析:递归分别比较里外两侧是否相等。

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public boolean isSymmetric(TreeNode root) {if(root == null) return true;return compare(root.left, root.right);}private boolean compare(TreeNode left, TreeNode right) {if (left == null && right != null) {return false;}if (left != null && right == null) {return false;}if (left == null && right == null) {return true;}if (left.val != right.val) {return false;}// 比较外侧boolean compareOutside = compare(left.left, right.right);// 比较内侧boolean compareInside = compare(left.right, right.left);return compareOutside && compareInside;}
}

动态规划(简单)- 1.6

剑指 Offer 10- I. 斐波那契数列

剑指 Offer 10- I. 斐波那契数列


解析:动态规划,构建dp数组,初始化然后推导出递推表达式。

class Solution { public int fib(int n) {if(n <= 1) return n;int[] dp = new int[n+1]; //dp数组dp[0] = 0; //初始化dp[1] = 1;for(int i=2; i<=n; i++){ //注意这里是 <=n//递推表达式dp[i] = (dp[i-1] + dp[i-2]) % 1000000007;}return dp[n];}
}

剑指 Offer 10- II. 青蛙跳台阶问题

剑指 Offer 10- II. 青蛙跳台阶问题


解析:动态规划,当n = 3时,f(3)=f(2)+f(1) 当n = 4时,f(4) = f(3) +f(2);依次类推。

class Solution {public int numWays(int n) {if(n <= 1) return 1;if(n == 2) return 2;int sum = 0;int[] dp = new int[n+1];dp[0] = 1;dp[1] = 1;dp[2] = 2;//动态规划,当n = 3时,f(3)=f(2)+f(1)  当n = 4时,f(4) = f(3) +f(2)for(int i=3; i<=n; i++){dp[i] = (dp[i-1] + dp[i-2]) % 1000000007;}return dp[n];}
}

剑指 Offer 63. 股票的最大利润

剑指 Offer 63. 股票的最大利润


解析:动态规划,动态取买入最小值和卖出最大值


class Solution {public int maxProfit(int[] prices) {int len = prices.length;if(len <= 1) return 0;int minCost = Integer.MAX_VALUE;int max = 0;for(int price : prices){minCost = Math.min(minCost, price); //取买入最小价格max = Math.max(max, price-minCost); //取卖出最大值}return max;}
}

动态规划(中等)- 1.7

剑指 Offer 42. 连续子数组的最大和

剑指 Offer 42. 连续子数组的最大和


解析:


class Solution {public int maxSubArray(int[] nums) {int len = nums.length;//dp[i]表示以元素 nums[i] 为结尾的连续子数组最大和。int[] dp = new int[len+1];dp[0] = nums[0];//存储较大的 子数组最大和int res = dp[0]; for(int i=1; i<=len-1; i++){dp[i] = Math.max(nums[i], dp[i-1] + nums[i]);res = Math.max(res, dp[i]);}return res;}
}

剑指 Offer 47. 礼物的最大价值

剑指 Offer 47. 礼物的最大价值


解析:dp(i,j) 代表从棋盘的左上角开始,到达单元格 (i,j)(i,j) 时能拿到礼物的最大累计价值。



在这里插入图片描述

class Solution {public int maxValue(int[][] grid) {int rows = grid.length; //行int cols = grid[0].length; //列int[][] dp = new int[rows][cols];dp[0][0] = grid[0][0];//行赋值for(int i=1; i<rows; i++){dp[i][0] = dp[i-1][0] + grid[i][0];}//列赋值       for(int j=1; j<cols; j++){dp[0][j] = dp[0][j-1] + grid[0][j];}      //从上到下,从左到右的顺序for(int i=1; i<rows; i++){for(int j=1; j<cols; j++){dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + grid[i][j];}}return dp[rows-1][cols-1];}
}

动态规划(中等)- 1.10

剑指 Offer 46. 把数字翻译成字符串

剑指 Offer 46. 把数字翻译成字符串


解析:先将数字转为字符串,然后定义dp[i]数组,表示前i个数字一共有多少种不同的翻译方法。

参考:把数字翻译成字符串 | 图解DP | 最清晰易懂的题解

class Solution {public int translateNum(int num) {//将数字转为字符串String s = String.valueOf(num);int n = s.length();//dp[i]表示 前i个数字共有多少种不同的翻译int[] dp = new int[n+1];dp[0] = 1;for(int i=1; i<=n; i++){//单独翻译s[i]dp[i] = dp[i-1];if(i > 1){int temp = (s.charAt(i-2) - '0')*10 + (s.charAt(i-1) - '0');if(temp >= 10 && temp <= 25){dp[i] = dp[i] + dp[i-2]; //组合翻译}}}return dp[n];}
}

剑指 Offer 48. 最长不含重复字符的子字符串

剑指 Offer 48. 最长不含重复字符的子字符串


解法一:滑动窗口

class Solution {public int lengthOfLongestSubstring(String s) {if(s == null || s.length()<=0) return 0;int leftIndex = -1; //定义左边界,注意这里是-1int res = 0;Map<Character, Integer> map = new HashMap<>();char[] ch = s.toCharArray();for(int i=0; i<s.length(); i++){if(map.containsKey(ch[i])){ //当前数字 在之前遍历时出现过,就更新左边界leftIndex = Math.max(leftIndex, map.get(ch[i]));}map.put(ch[i], i); //添加元素res = Math.max(res, i-leftIndex); //计算当前最大长度}return res;}
}

解法二:动态规划+哈希表

class Solution {public int lengthOfLongestSubstring(String s) {Map<Character, Integer> dic = new HashMap<>();int res = 0, tmp = 0;for(int j = 0; j < s.length(); j++) {int i = dic.getOrDefault(s.charAt(j), -1); // 获取索引 idic.put(s.charAt(j), j); // 更新哈希表// dp[j - 1] -> dp[j]tmp = tmp < j - i ? tmp + 1 : j - i; // max(dp[j - 1], dp[j])res = Math.max(res, tmp); }return res;}
}

双指针(简单)- 1.11

*剑指 Offer 18. 删除链表的节点

剑指 Offer 18. 删除链表的节点


解析:设置一个虚拟头结点再进行删除操作。

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode(int x) { val = x; }* }*/
class Solution {public ListNode deleteNode(ListNode head, int val) {ListNode dummy  = new ListNode(-1);dummy.next = head;ListNode cur = dummy;while(cur.next != null){if(cur.next.val == val){ //如果相等,就跳到下一个,就是删除cur.next = cur.next.next;}else{cur = cur.next; //移动到下一个}}return dummy.next;}
}

*剑指 Offer 22. 链表中倒数第k个节点

剑指 Offer 22. 链表中倒数第k个节点


解析:先让快指针移动k个节点,再同时移动,最后返回慢指针即可。

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode(int x) { val = x; }* }*/
class Solution {public ListNode getKthFromEnd(ListNode head, int k) {ListNode fast = head; //快指针ListNode slow = head; //慢指针for(int i=0; i<k; i++){fast = fast.next; //先让快指针移动k个节点}while(fast != null){ //再同时移动,最后返回慢指针即可fast = fast.next;slow = slow.next;}return slow;}
}

双指针(简单)- 1.11

*剑指 Offer 25. 合并两个排序的链表

剑指 Offer 25. 合并两个排序的链表


解析:双指针,比较两个链表的节点值,哪个小就把新链表指向它,最后把剩下的直接拼接到新链表末尾。

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode(int x) { val = x; }* }*/
class Solution {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {if(l1 == null) return l2;if(l2 == null) return l1;ListNode dummy = new ListNode(-1); //创建虚拟头节点ListNode res = dummy; while(l1 != null && l2 != null){if(l1.val <= l2.val){ //如果l1的值小于等于l2的值,就添加l1res.next = l1; //赋值l1 = l1.next;   //添加元素后的链接移动}else{  //如果l1的值大于l2的值,就添加l2res.next = l2;l2 = l2.next;}res = res.next; //新链表移动}//剩下的直接拼接到新链表后面if(l1 == null) res.next = l2;if(l2 == null) res.next = l1;return dummy.next;}
}

剑指 Offer 52. 两个链表的第一个公共节点

剑指 Offer 52. 两个链表的第一个公共节点


解析:使用双指针,先计算出两个链表的长度,然后让长的链表先移动“差值”,然后再一起移动,一起移动时碰到两个节点相同就返回,否则就一起移动到下一个节点。

注意:是节点相等,而不是值相等。

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if(headA == null && headB == null) return null;ListNode curr1 = headA;ListNode curr2 = headB;//算出两个链表的长度int len1=0, len2=0;while(curr1 != null){++len1;curr1 = curr1.next;}while(curr2 != null){++len2;curr2 = curr2.next;}//重置头指针curr1 = headA;curr2 = headB;//如果链表1比链表2长if(len1 > len2){//链表1先移动for(int i=0; i<(len1-len2); i++){curr1 = curr1.next;}//再同时移动,如果发现结点相等,就返回for(int j=0; j<len2; j++){if(curr1 == curr2){return curr1;}else{curr1 = curr1.next;curr2 = curr2.next;}}}else{//链表2先移动for(int i=0; i<(len2-len1); i++){curr2 = curr2.next;}//再同时移动,如果发现结点相等,就返回for(int j=0; j<len2; j++){if(curr2 == curr1){return curr2;}else{curr1 = curr1.next;curr2 = curr2.next;}}            }return null; }
}

双指针(简单)- 1.12

*剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面


解析:双指针,分别从左右两边开始,判断如果是 偶奇,交换后都移动;奇偶,都移动;奇奇,移动左;偶偶,移动右。

class Solution {public int[] exchange(int[] nums) {if(nums == null || nums.length <= 0) return nums;int left = 0;int right = nums.length-1;while(left < right){if(nums[left] % 2 == 0 && nums[right] % 2 != 0){  //偶奇,交换后都移动int temp = nums[left];nums[left] = nums[right];nums[right] = temp;left++;right--;}else if(nums[left] % 2 != 0 && nums[right] % 2 == 0){ //奇偶,都移动left++;right--;               }else if(nums[left] % 2 != 0 && nums[right] % 2 != 0){ //奇奇,移动左left++;             }else { //偶偶,移动右right--;               }}return nums;}
}

*剑指 Offer 57. 和为s的两个数字

剑指 Offer 57. 和为s的两个数字


解析:双指针

class Solution {public int[] twoSum(int[] nums, int target) {int left = 0, right = nums.length-1;while(left < right){if(nums[left] + nums[right] == target){ //相等,直接返回return new int[]{nums[left],nums[right]};}else if(nums[left] + nums[right] < target){ //和 小于 目标值,移动左left++;}else{ //和 大于 目标值,移动右right--;}}return new int[]{};}
}

剑指 Offer 58 - I. 翻转单词顺序

剑指 Offer 58 - I. 翻转单词顺序


解法一:分割+倒序

class Solution {public String reverseWords(String s) {String[] new_s = s.trim().split(" "); // 删除首尾空格,分割字符串StringBuilder res = new StringBuilder();//从后往前添加单词数组for(int i=new_s.length-1; i>=0; i--){if(new_s[i].equals("")) continue; //如果遇到空单词,就跳过if(i != 0){res.append(new_s[i] + " ");}else{res.append(new_s[i]); //添加最后一个时,不需要加空格}}return new String(res);}
}

解法二:双指针

class Solution {public String reverseWords(String s) {s = s.trim(); // 删除首尾空格int j = s.length() - 1, i = j;StringBuilder res = new StringBuilder();while(i >= 0) {while(i >= 0 && s.charAt(i) != ' ') i--; // 搜索首个空格res.append(s.substring(i + 1, j + 1) + " "); // 添加单词while(i >= 0 && s.charAt(i) == ' ') i--; // 跳过单词间空格j = i; // j 指向下个单词的尾字符}return res.toString().trim(); // 转化为字符串并返回}
}

搜索与回溯算法(中等)- 1.13

剑指 Offer 12. 矩阵中的路径

剑指 Offer 12. 矩阵中的路径


解析:

参考:矩阵中的路径( DFS + 剪枝 ,清晰图解)

class Solution {public boolean exist(char[][] board, String word) {//将word转换成字串数组char[] words = word.toCharArray();//遍历图for(int i=0; i<board.length; i++){for(int j=0; j<board[0].length; j++){//如果找到了,就返回true,否则继续找if(dfs(board,words,i,j,0)) return true;}}//遍历结束还没找到返回falsereturn false;}//i,j是元素位置下标,k是传入字符串当前索引private boolean dfs(char[][] board, char[] words, int i, int j, int k){//判断传入参数的可行性 i 与图行数row比较,j与图列数col比较//如果board[i][j] == word[k],则表明当前找到了对应的数,就继续执行(标记找过,继续dfs 下上右左)if(i>=board.length || i<0 || j>=board[0].length || j<0 || board[i][j] != words[k]) return false;// 表示找完了,每个字符都找到了// 一开始k=0,而word.length肯定不是0,所以没找到,就执行dfs继续找。if(k == words.length-1) return true;// 访问过的标记空字符串,“ ”是空格 '\0'是空字符串,不一样的!// 比如当前为A,没有标记找过,且A是word中对应元素,则此时应该找A下一个元素,假设是B,在dfs(B)的时候还是-// ->要搜索B左边的元素(假设A在B左边),所以就是ABA(凭空多出一个A,A用了2次,不可以),如果标记为空字符串->// 就不会有这样的问题,因为他们值不相等AB != ABA。 board[i][j] = '\0';//顺序是 下上右左, 上面找到了对应索引的值所以k+1boolean res = dfs(board,words,i+1,j,k+1) || dfs(board,words,i-1,j,k+1) || dfs(board,words,i,j+1,k+1) || dfs(board,words,i,j-1,k+1);// 还原找过的元素,因为之后可能还会访问到(不同路径)board[i][j] = words[k];// 返回结果,如果false,则if(dfs(board, words, i, j, 0)) return true;不会执行,就会继续找return res;}
}

剑指 Offer 13. 机器人的运动范围

剑指 Offer 13. 机器人的运动范围


解析:DFS

参考:机器人的运动范围( 回溯算法,DFS / BFS ,清晰图解)

class Solution {public int movingCount(int m, int n, int k) {boolean[][] visited = new boolean[m][n]; //辅助数组return dfs(m,n,k,0,0,visited);}public int dfs(int m, int n, int k, int i, int j, boolean[][] visited){//下标越界 或 行坐标和列坐标的数位之和大于k 或 标志位为falseif(i>=m || j>=n || k<getNumSum(i)+getNumSum(j) || visited[i][j]){return 0;}//可以到达的格子 设置为truevisited[i][j] = true;//结果 = 1 + 向下可达的格子数 + 向右可达的格子数return 1 + dfs(m,n,k,i+1,j,visited) + dfs(m,n,k,i,j+1,visited);}//数的各个位数之和private int getNumSum(int a){int sum = a % 10;int tmp = a / 10;while(tmp > 0){sum += tmp % 10;tmp /= 10;}return sum;}
}

搜索与回溯算法(中等)- 1.14

剑指 Offer 34. 二叉树中和为某一值的路径

剑指 Offer 34. 二叉树中和为某一值的路径

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。



解析:回溯

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode() {}*     TreeNode(int val) { this.val = val; }*     TreeNode(int val, TreeNode left, TreeNode right) {*         this.val = val;*         this.left = left;*         this.right = right;*     }* }*/
class Solution {LinkedList<List<Integer>> res = new LinkedList<>(); //结果LinkedList<Integer> path = new LinkedList<>(); //路径public List<List<Integer>> pathSum(TreeNode root, int target) {recall(root,target);return res;}public void recall(TreeNode root, int target){if(root == null) return;path.add(root.val); //单条路径添加值target -= root.val; //做减法//如果做减法后结果为0 并且 当前为叶子节点,就添加路径if(target == 0 && root.left == null && root.right == null){res.add(new LinkedList<>(path)); //添加路径}//回溯调用,查找左右子树recall(root.left, target);recall(root.right, target);//撤销已处理的节点path.removeLast();}
}

剑指 Offer 36. 二叉搜索树与双向链表

剑指 Offer 36. 二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

为了让您更好地理解问题,以下面的二叉搜索树为例:



解析:


参考:二叉搜索树与双向链表(中序遍历,清晰图解)

/*
// Definition for a Node.
class Node {public int val;public Node left;public Node right;public Node() {}public Node(int _val) {val = _val;}public Node(int _val,Node _left,Node _right) {val = _val;left = _left;right = _right;}
};
*/
class Solution {Node pre, head; //pre为前一个节点,head为头节点public Node treeToDoublyList(Node root) {if(root == null) return null;dfs(root);//经过dfs处理之后,pre就有指向了,然后进行头尾相连head.left = pre;pre.right = head;//返回头节点return head;}public void dfs(Node cur){//递归结束条件if(cur == null) return;//左dfs(cur.left);//如果pre为空,就说明是第一个节点,头结点,然后用head保存头结点,用于之后的返回if(pre == null) head = cur;//如果pre不为空,那就说明是中间的节点。并且pre保存的是上一个节点,让上一个节点的右指针指向当前节点else pre.right = cur;  //再让当前节点的左指针指向父节点,也就连成了双向链表cur.left = pre;//保存当前节点,用于下层递归创建pre = cur;//右dfs(cur.right);}
}

剑指 Offer 54. 二叉搜索树的第k大节点

剑指 Offer 54. 二叉搜索树的第k大节点


解法一:先使用中序遍历存储元素,然后返回倒数第k大的节点值。

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public int kthLargest(TreeNode root, int k) {if(root == null) return 0;List<Integer> res = new ArrayList<>();inOrder(root,res);return res.get(res.size()-k); //获取第k大的节点值}//中序遍历public void inOrder(TreeNode root, List<Integer> res){if(root == null) return;inOrder(root.left,res);res.add(root.val);inOrder(root.right,res);}
}


优化后:遍历右根左,到倒数第k大值时就直接返回。

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {private int res = 0, n = 0;public int kthLargest(TreeNode root, int k) {this.n = k;inOrder(root);return res;}public void inOrder(TreeNode root){//注意这里是先遍历右子树if(root.right != null && n > 0) inOrder(root.right);n--; //递减if(n == 0) { //找到倒数第k大的值res = root.val;return;}if(root.left != null && n > 0) inOrder(root.left);}
}

排序(简单)- 1.17

剑指 Offer 45. 把数组排成最小的数

剑指 Offer 45. 把数组排成最小的数


解析:本质是排序问题,判断 a+b 与 b+a 的大小。

class Solution {public String minNumber(int[] nums) {String[] strs = new String[nums.length];//整数数组转为字符串数组for(int i=0; i<strs.length; i++){strs[i] = String.valueOf(nums[i]);}//内置排序,定义排序规则 (x,y) -> (x+y).compareTo(y+x)Arrays.sort(strs, (x,y) -> (x+y).compareTo(y+x));//拼接字符串StringBuilder res = new StringBuilder();for(String s: strs){res.append(s);}return res.toString();}
}

剑指 Offer 61. 扑克牌中的顺子

剑指 Offer 61. 扑克牌中的顺子


解析:先进行排序,遍历时统计0的个数,如果有重复数字,提前返回false,遍历完成后如果 最大牌-最小牌<5 则可构成顺子。

class Solution {public boolean isStraight(int[] nums) {Arrays.sort(nums);//排序int n = 0;for(int i=0; i<4; i++){if(nums[i] == 0) n++; //统计0的个数else if(nums[i] == nums[i+1] ) return false; //如果有重复数字,提前返回false}return nums[4] - nums[n] < 5; //最大牌-最小牌<5 则可构成顺子}
}

排序(中等)- 1.18

*剑指 Offer 40. 最小的k个数

剑指 Offer 40. 最小的k个数


解法一:冒泡排序后,返回前k个值。

解法二:内置排序

class Solution {public int[] getLeastNumbers(int[] arr, int k) {//内置排序Arrays.sort(arr);//结果赋值int[] res = new int[k];for(int i=0; i<k; i++){res[i] = arr[i];}return res;}
}


解法三:快速排序

class Solution {public int[] getLeastNumbers(int[] arr, int k) {//快速排序quickSort(arr,0,arr.length-1);int[] res = new int[k];for(int i=0; i<k; i++){res[i] = arr[i];}return res;}public void quickSort(int[] arr, int left, int right){int l = left; //左下标int r = right; //右下标int mid = arr[(l + r) / 2]; //中间值while(l < r){//从左边开始找到比中间值大或相等的while(arr[l] < mid) l++;//从右边开始找到比中间值小或相等的while(arr[r] > mid) r--;//如果重合了,就退出if(l >= r) break;//交换int temp = arr[r]; arr[r] = arr[l]; arr[l] = temp;// 如果交换完后,发现arr[l]==pivot,此时应将r左移一位if(arr[l] == mid) r-=1;// 如果交换完后,发现arr[r]==pivot,此时应将l右移一位if(arr[r] == mid) l+=1;} // 如果l==r,要把这两个下标错开,否则会出现无限递归,导致栈溢出的情况if(l == r){l += 1;r -= 1;}if(left < r) quickSort(arr,left,r); //向左递归if(right > l) quickSort(arr,l,right); //向右递归}
}

*剑指 Offer 41. 数据流中的中位数

剑指 Offer 41. 数据流中的中位数


解法一:创建list集合,调用添加函数时直接添加元素,再排序;调用查询元素时,分别对总数个数奇偶情况进行处理。

class MedianFinder {List<Integer> list;/** initialize your data structure here. */public MedianFinder() {this.list = new ArrayList<>();}public void addNum(int num) {list.add(num); //添加元素Collections.sort(list); //排序}public double findMedian() {int len = list.size();if(len <= 0) return (double)0;if(len == 1) return (double) list.get(0);int midIndex = (len-1) / 2;   //中位数下标if(len % 2 == 0){ //如果总数个数是偶数,就返回排序后中间两个数的平均值return (double)((list.get(midIndex) + list.get(midIndex+1)) / 2.0);}else{  //如果总数个数是奇数,就直接返回中位数return (double)(list.get(midIndex));}}
}/*** Your MedianFinder object will be instantiated and called as such:* MedianFinder obj = new MedianFinder();* obj.addNum(num);* double param_2 = obj.findMedian();*/

解法二:

参考:数据流中的中位数(Java | 大小顶堆)

class MedianFinder {// 大顶堆存储较小一半的值PriorityQueue<Integer> maxHeap;// 小顶堆存储较大一般的值PriorityQueue<Integer> minHeap;public MedianFinder() {maxHeap = new PriorityQueue<Integer>((x, y) -> (y - x)); //定义排序规则minHeap = new PriorityQueue<Integer>();}public void addNum(int num) {// 长度为奇数时先放入小顶堆,重新排序后在插入到大顶堆if(maxHeap.size() != minHeap.size()) {minHeap.add(num);maxHeap.add(minHeap.poll());} else {// 长度为偶数时先放入大顶堆,重新排序后在插入到小顶堆maxHeap.add(num);minHeap.add(maxHeap.poll());}}public double findMedian() {if(maxHeap.size() != minHeap.size()) { //奇数return minHeap.peek(); } else {  //偶数return (maxHeap.peek() + minHeap.peek()) / 2.0;}}
}/*** Your MedianFinder object will be instantiated and called as such:* MedianFinder obj = new MedianFinder();* obj.addNum(num);* double param_2 = obj.findMedian();*/

搜索与回溯算法(中等)- 1.18

*剑指 Offer 55 - I. 二叉树的深度

剑指 Offer 55 - I. 二叉树的深度


解析:层序遍历

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public int maxDepth(TreeNode root) {if(root == null) return 0;Queue<TreeNode> que = new LinkedList<>();que.offer(root);int n = 0; //树的深度while(!que.isEmpty()){n++;int len = que.size();while(len > 0){TreeNode node = que.poll();if(node.left != null) que.offer(node.left);if(node.right != null) que.offer(node.right);len--;}          }return n;}
}


解法二:递归

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public int maxDepth(TreeNode root) {if(root == null) return 0;return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;}
}

剑指 Offer 55 - II. 平衡二叉树

剑指 Offer 55 - II. 平衡二叉树


解析:递归计算出左右子树的高度,然后判断是否相差大于1。

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public boolean isBalanced(TreeNode root) {return dfs(root) >= 0;}public int dfs(TreeNode root){if(root == null) return 0;int leftHeight = dfs(root.left); //左子树高度int rightHeight = dfs(root.right); //右子树高度if(leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1){  //如果左右子树高度为-1,或者两者的差值大于1,就返回-1return -1;}else{return Math.max(leftHeight, rightHeight) + 1; //树的深度}}
}

搜索与回溯算法(中等)- 1.19

剑指 Offer 64. 求1+2+…+n

剑指 Offer 64. 求1+2+…+n


解析:递归,短路&&

class Solution {public int sumNums(int n) {//如果n小于等于0,就不会执行&&后面的语句boolean flag = n > 0 && (n += sumNums(n - 1)) > 0;return n;}
}

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先



解析:

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root == null) return null;if(p.val < root.val && q.val < root.val){ //往左搜索return lowestCommonAncestor(root.left, p, q);}if(p.val > root.val && q.val > root.val){ //往右搜索return lowestCommonAncestor(root.right, p, q);}  return root;  //如果出现p,q在一左一右,就直接返回当前的根节点    }
}

剑指 Offer 68 - II. 二叉树的最近公共祖先

剑指 Offer 68 - II. 二叉树的最近公共祖先


解析:

三种情况
1、p q 一个在左子树 一个在右子树,那么当前节点即是最近公共祖先。
2、p q 都在左子树
3、p q 都在右子树

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if(root == null) return null;//如果当前节点等于p或q,返回当前节点if(root == p || root == q) return root;//在左子树找TreeNode left = lowestCommonAncestor(root.left, p, q);//在右子树找TreeNode right = lowestCommonAncestor(root.right, p, q);// p q 一个在左,一个在右if(left != null && right != null) return root;// p q 都在左子树if(left != null) return left;// p q 都在右子树if(right != null) return right;return null;}
}

分治算法(中等)- 1.19

剑指 Offer 07. 重建二叉树

剑指 Offer 07. 重建二叉树


解析:

参考:重建二叉树(分治算法,清晰图解)

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {HashMap<Integer, Integer> map = new HashMap<>();//标记中序遍历int[] preorder;//保留的先序遍历,方便递归时依据索引查看先序遍历的值public TreeNode buildTree(int[] preorder, int[] inorder) {this.preorder = preorder;//将中序遍历的值及索引放在map中,方便递归时获取左子树与右子树的数量及其根的索引for (int i = 0; i < inorder.length; i++) {map.put(inorder[i], i);}//三个索引分别为//当前根的的索引//递归树的左边界,即数组左边界//递归树的右边界,即数组右边界return recur(0,0,inorder.length-1);}TreeNode recur(int pre_root, int in_left, int in_right){if(in_left > in_right) return null;// 相等的话就是自己TreeNode root = new TreeNode(preorder[pre_root]);//获取root节点int idx = map.get(preorder[pre_root]);//获取在中序遍历中根节点所在索引,以方便获取左子树的数量//左子树的根的索引为先序中的根节点+1 //递归左子树的左边界为原来的中序in_left//递归左子树的右边界为中序中的根节点索引-1root.left = recur(pre_root+1, in_left, idx-1);//右子树的根的索引为先序中的 当前根位置 + 左子树的数量 + 1//递归右子树的左边界为中序中当前根节点+1//递归右子树的右边界为中序中原来右子树的边界root.right = recur(pre_root + (idx - in_left) + 1, idx+1, in_right);return root;}
}

剑指 Offer 16. 数值的整数次方

剑指 Offer 16. 数值的整数次方


解析:

参考:快速幂

class Solution {public double myPow(double x, int n) {if(n == 0) return 1;if(x == 0) return 0;long b = n;double res = 1.0;if(b < 0){x = 1 / x;b = -b;}while(b > 0){//最后一位为1,需要乘上改位上的权重if((b & 1) == 1) res *= x;  // 即 n % 2 == 1x *= x; // 即 x = x ^ 2;b >>= 1; // 即 n //= 2;}return res;}
}

剑指 Offer 33. 二叉搜索树的后序遍历序列

剑指 Offer 33. 二叉搜索树的后序遍历序列


解析:

参考:二叉搜索树的后序遍历序列(递归分治 / 单调栈,清晰图解)

class Solution {public boolean verifyPostorder(int[] postorder) {return recur(postorder, 0, postorder.length-1);}public boolean recur(int[] postorder, int left, int right){//说明此子树节点数量≤1 ,无需判别正确性,因此直接返回 truetrueif(left >= right) return true;int temp = left;while(postorder[temp] < postorder[right]) temp++; //找到第一个比根节点大的节点int m = temp;while(postorder[temp] > postorder[right]) temp++;//temp = right : 判断 此树 是否正确。//recur(postorder, left, m-1): 判断 此树的左子树 是否正确。//recur(postorder, m, right-1) : 判断 此树的右子树 是否正确。return temp == right && recur(postorder, left, m-1) && recur(postorder, m, right-1);}
}

位运算(简单)- 1.20

剑指 Offer 15. 二进制中1的个数

剑指 Offer 15. 二进制中1的个数


解析:


public class Solution {// you need to treat n as an unsigned valuepublic int hammingWeight(int n) {int res = 0;while(n != 0){if((n & 1) == 1) res++; //统计1的次数n >>>= 1; //无符号右移}return res;}
}


解法二:

public class Solution {// you need to treat n as an unsigned valuepublic int hammingWeight(int n) {int res = 0;while(n != 0){res++; //次数+1n &= (n-1); //消去数字 n 最右边的 1。}return res;}
}

剑指 Offer 65. 不用加减乘除做加法

剑指 Offer 65. 不用加减乘除做加法


解析:


参考:不用加减乘除做加法(位运算,清晰图解)

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;}
}

位运算(中等)- 1.20

剑指 Offer 56 - I. 数组中数字出现的次数

剑指 Offer 56 - I. 数组中数字出现的次数


解析:

参考:数组中数字出现的次数(位运算,清晰图解)

注释精简版:

class Solution {public int[] singleNumbers(int[] nums) {int x = 0, y = 0, n = 0, m = 1;for(int num : nums)               // 1. 遍历异或n ^= num;while((n & m) == 0)               // 2. 循环左移,计算 mm <<= 1;for(int num: nums) {              // 3. 遍历 nums 分组if((num & m) != 0) x ^= num;  // 4. 当 num & m != 0else y ^= num;                // 4. 当 num & m == 0}return new int[] {x, y};          // 5. 返回出现一次的数字}
}

注释详细版:

class Solution {public int[] singleNumbers(int[] nums) {//因为相同的数字异或为0,任何数字与0异或结果是其本身。//所以遍历异或整个数组最后得到的结果就是两个只出现一次的数字异或的结果:即 z = x ^ yint z = 0;  for(int i : nums) z ^= i;//我们根据异或的性质可以知道:z中至少有一位是1,否则x与y就是相等的。//我们通过一个辅助变量m来保存z中哪一位为1.(可能有多个位都为1,我们找到最低位的1即可)。//举个例子:z = 10 ^ 2 = 1010 ^ 0010 = 1000,第四位为1.//我们将m初始化为1,如果(z & m)的结果等于0说明z的最低为是0//我们每次将m左移一位然后跟z做与操作,直到结果不为0.//此时m应该等于1000,同z一样,第四位为1.int m = 1;while((z & m) == 0) m <<= 1;//我们遍历数组,将每个数跟m进行与操作,结果为0的作为一组,结果不为0的作为一组//例如对于数组:[1,2,10,4,1,4,3,3],我们把每个数字跟1000做与操作,可以分为下面两组://nums1存放结果为0的: [1, 2, 4, 1, 4, 3, 3]//nums2存放结果不为0的: [10] (碰巧nums2中只有一个10,如果原数组中的数字再大一些就不会这样了)//此时我们发现问题已经退化为数组中有一个数字只出现了一次//分别对nums1和nums2遍历异或就能得到我们预期的x和yint x = 0, y = 0;for(int i : nums) {//这里我们是通过if...else将nums分为了两组,一边遍历一遍异或。//跟我们创建俩数组nums1和nums2原理是一样的。if((i & m) == 0) x ^= i;else y ^= i;}return new int[]{x, y};}
}

剑指 Offer 56 - II. 数组中数字出现的次数 II

剑指 Offer 56 - II. 数组中数字出现的次数 II


解法一:用map存数字和布尔值, 存过的数字 布尔值都是true, 只有那个出现一次的数的布尔值是false,找出来就行了

class Solution {public int singleNumber(int[] nums) {Map<Integer, Boolean> map = new HashMap<>();for(Integer num : nums){//如果已经存在,就存true,否则为falsemap.put(num, map.containsKey(num));}for(Integer num : nums){ //找出为false的就为出现1次的数if(!map.get(num)) return num;}return -1;}
}


解法二:位运算 - 有限状态自动机


class Solution {public int singleNumber(int[] nums) {int ones = 0, twos = 0;for(int num : nums){ones = ones ^ num & ~twos;twos = twos ^ num & ~ones;}return ones;}
}

数学(简单)- 1.21

*剑指 Offer 39. 数组中出现次数超过一半的数字

剑指 Offer 39. 数组中出现次数超过一半的数字


解法一:哈希map,把元素为键,出现的次数为值存入map中,找到出现的次数超过数组长度的一半的数字返回。

class Solution {public int majorityElement(int[] nums) {int len = nums.length;HashMap<Integer, Integer> map = new HashMap<>();for(Integer num : nums){//把元素为键,出现的次数为值存入map中;//getOrDefault(num, 0):如果map不存在该key,则返回默认值;若存在key,则返回key所对应的的value。map.put(num, map.getOrDefault(num, 0) + 1);//如果找到出现的次数超过数组长度的一半的数字就直接返回if(map.get(num) > len/2) return num;}return -1;}
}

解法二:

class Solution {public int majorityElement(int[] nums) {int len = nums.length;int x = 0, sum = 0, count = 0;for(int num: nums){if(sum == 0) x = num; //如果当前的和为0,num为众数if(num != x) sum -= 1; //如果当前数不等于众数,和-1else sum += 1;  //否则当前数等于众数,和+1} //验证 x 是否为众数for(int num : nums){if(num == x) count++;}return count > len/2 ? x : 0;  //当无众数时返回0}
}

*剑指 Offer 66. 构建乘积数组

剑指 Offer 66. 构建乘积数组


解法一:写一个计算除了下标i以外的元素的乘积的函数,然后遍历调用,遍历时当i>=1时,如果当前元素与前一个元素相同,则直接将前一个值赋值给当前值。

class Solution {public int[] constructArr(int[] a) {int[] res = new int[a.length];if(a == null || a.length <= 0) return res;for(int i=0; i<a.length; i++){if(i == 0){  // i为0的情况res[0] = otherSum(a,0);}else{ // 当i>=1时,如果当前元素与前一个元素相同,则直接将前一个值赋值给当前值if(a[i-1] == a[i]) res[i] = res[i-1];else res[i] = otherSum(a,i);}}return res;}//计算除了下标i以外的元素的乘积public int otherSum(int[] arr, int cur){int sum = 1;for(int i=0; i<arr.length; i++){if(cur == i) continue;if(arr[i] != 1){sum *= arr[i];}}return sum;}
}

解法二:

class Solution {public int[] constructArr(int[] a) {int[] ans = new int[a.length];//计算i前for (int i = 0, p = 1; i < a.length; i++) {ans[i] = p;p *= a[i];}//计算i后for (int i = a.length - 1, p = 1; i >= 0; i--) {ans[i] *= p;p *= a[i];}return ans;}}

数学(中等)- 1.21

剑指 Offer 14- I. 剪绳子

剑指 Offer 14- I. 剪绳子


解析:

首先使用动态规划解决问题,最重要的就是理解dp数组的含义。 此处的dp[i] 表示长度为i的绳子剪成m段后的最大乘积。初始化dp[2] = 1 表示如果一个绳子长度为2必然只能分成两段长度为1的绳子,两者的乘积为1。

  1. 然后尝试对绳子进行分割,如果只剪掉长度为1,对最后的乘积无任何增益,所以从长度为2开始剪,代码中的j就是表示尝试剪的长度。
  2. 剪下一段后,剩余部分可以剪也可以不剪。如果不剪则得到的长度乘积为 j * (i - j) 。如果剪得到的长度为j * dp[i - j]两者取最大值
  3. 不断修改剪的长度j (j范围为[2, i -1])。从所有结果中找到最大值即为dp[i]的结果。
  4. 从而有状态转移方程dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
class Solution {public int cuttingRope(int n) {// dp[i]表示长度为i的绳子被剪成m段,每段乘机的最大值int[] dp = new int[n+1];// 因为m>1,所以dp[2] = 1而不是2dp[2] = 1;// i表示绳子长度for (int i=3;i<=n;++i){// i-j >= 2。j代表第一次剪掉的长度,剪掉1没用,所以从2开始,剩余的长度i-j怎么减直接取dp[i-j]for (int j=1;j<=i-2;++j){// 这里因为m>1,所以dp[2] = 1而不是2,dp[3]不能是dp[2] * 1,这样答案是1,错误.// 因此下面要添加Math.max(dp[i-j], i-j)。dp[i] = Math.max(Math.max(dp[i-j], i-j) * j, dp[i]);}}return dp[n];}
}

剑指 Offer 57 - II. 和为s的连续正数序列

剑指 Offer 57 - II. 和为s的连续正数序列


解析:滑动窗口,双指针

参考:和为 s 的连续正数序列(求和公式 / 滑动窗口,清晰图解)

class Solution {public int[][] findContinuousSequence(int target) {int i=1, j=2, sum=3;List<int[]> list = new ArrayList<>();while(i < j){if(sum == target){//新建数组保存int[] ans = new int[j-i+1];for(int k=i; k<=j; k++){ans[k-i] = k;}//添加进集合list.add(ans);}if(sum >= target){sum -= i;i++; //移动左指针}else{j++;  //移动右指针sum += j;}}return list.toArray(new int[0][]);}
}

剑指 Offer 62. 圆圈中最后剩下的数字

剑指 Offer 62. 圆圈中最后剩下的数字


解析:

class Solution {public int lastRemaining(int n, int m) {int res = 0;for(int i=2; i<=n; i++){res = (res + m) % i;}return res;}
}

模拟(中等)- 1.22

剑指 Offer 29. 顺时针打印矩阵

剑指 Offer 29. 顺时针打印矩阵


解析:设置上下左右边界,模拟行走。

class Solution {public int[] spiralOrder(int[][] matrix) {//空值处理if(matrix == null || matrix.length == 0) return new int[0];int left = 0; // 左边界int right = matrix[0].length - 1; //右边界int top = 0; //上边界int bottom = matrix.length - 1; //下边界int[] res = new int[(right+1) * (bottom+1)]; //保存结果int index = 0; //数组下标while(true){//从左到右for(int i=left; i<=right; i++){res[index++] = matrix[top][i];}//上边界下移,如果超过下边界则退出if(++top > bottom) break;//从上到下for(int i=top; i<=bottom; i++){res[index++] = matrix[i][right];}//右边界左移,如果超过左边界则退出if(--right < left) break;//从右到左for(int i=right; i>=left; i--){res[index++] = matrix[bottom][i];}//下边界上移,如果超过上边界则退出if(--bottom < top) break;//从下到上for(int i=bottom; i>=top; i--){res[index++] = matrix[i][left];}//左边界右移,如果超过右边界则退出if(++left > right) break;}return res;}
}

剑指 Offer 31. 栈的压入、弹出序列

剑指 Offer 31. 栈的压入、弹出序列


解析:判断合不合法,用个栈试一试:把压栈的元素按顺序压入,当栈顶元素和出栈的第一个元素相同,则将该元素弹出,出栈列表指针后移并继续判断。最后判断出栈列表指针是否指向出栈列表的末尾即可。

class Solution {public boolean validateStackSequences(int[] pushed, int[] popped) {Deque<Integer> stack = new ArrayDeque();int index = 0; //记录下标for(int item : pushed){stack.push(item); //入栈,把压栈的元素按顺序压入while(index < popped.length && !stack.isEmpty() && stack.peek() == popped[index]){stack.pop(); //出栈,当栈顶元素和出栈的第一个元素相同,则将该元素弹出index++; //出栈列表popped的指针后移并继续判断}}//如果index等于出栈数组的长度,说明该出栈顺序合法,否则返回falsereturn index == popped.length;}
}

字符串(中等)- 1.24

剑指 Offer 20. 表示数值的字符串

剑指 Offer 20. 表示数值的字符串



解析:

参考:表示数值的字符串(有限状态自动机,清晰图解)

//小数表示可省去0,-0.4 = -.4,0.4 = .4;2.、3. = 2、3,小数点前有数,后面可以不跟数代表原数
//注意e8即10的8次幂(8次方),也可以是e-7,但题目要求必须跟整数
//题目规定是数值前后可有空格,中间不能有,这个情况要考虑清楚。s:符号、d:数字
class Solution {public boolean isNumber(String s) {Map[] states = {//0:规定0是初值,字符串表示数值,有4种起始状态,开头空格、符号、数字、前面没有数的小数点//其中 开头空格 还是指向states[0],上一位是 开头空格,下一位可以是 空格、符号、数字、前面没有数的小数点new HashMap<>() {{ put(' ', 0); put('s', 1); put('d', 2); put('.', 4); }}, //1:上一位是符号,符号位后面可以是 数字、前面没有数的小数点new HashMap<>() {{ put('d', 2); put('.', 4); }},//2:上一位是数字,数字的下一位可以是 数字、前面有数的小数点、e、结尾空格new HashMap<>() {{ put('d', 2); put('.', 3); put('e', 5); put(' ', 8); }}, //3:上一位是前面有数的小数点,下一位可以是 数字、e(8.e2 = 8e2,和2的情况一样)、结尾空格new HashMap<>() {{ put('d', 3); put('e', 5); put(' ', 8); }},//4:上一位是前面没有数的小数点,下一位只能是 数字(符号肯定不行,e得前面有数才行)              new HashMap<>() {{ put('d', 3); }},//5:上一位是e,下一位可以是 符号、数字new HashMap<>() {{ put('s', 6); put('d', 7); }},//6::上一位是e后面的符号,下一位只能是 数字new HashMap<>() {{ put('d', 7); }},//7:上一位是e后面的数字,下一位可以是 数字、结尾空格new HashMap<>() {{ put('d', 7); put(' ', 8); }},//8:上一位是结尾空格,下一位只能是 结尾空格new HashMap<>() {{ put(' ', 8); }}};int p = 0;char t;//遍历字符串,每个字符匹配对应属性并用t标记,非法字符标记?for(char c : s.toCharArray()) {if(c >= '0' && c <= '9') t = 'd';else if(c == '+' || c == '-') t = 's';else if(c == 'e' || c == 'E') t = 'e';else if(c == '.' || c == ' ') t = c;else t = '?';//当前字符标记和任何一种当前规定格式都不匹配,直接返回falseif(!states[p].containsKey(t)) return false;//更新当前字符的规定格式,进入下一个规定的Map数组p = (int)states[p].get(t);}//2(正、负整数)、3(正、负小数)、7(科学计数法)、8(前三种形式的结尾加上空格)//只有这四种才是正确的结尾return p == 2 || p == 3 || p == 7 || p == 8;}
}

剑指 Offer 67. 把字符串转换成整数

剑指 Offer 67. 把字符串转换成整数



解析:

class Solution {public int strToInt(String str) {//去前后空格char[] chars = str.trim().toCharArray();if (chars.length == 0) return 0;//记录第一个符合是否为负数int sign = 1;//开始遍历的位置int i = 1;//如果首个非空格字符为负号,那么从位置1开始遍历字符串,并且结果需要变成负数if (chars[0] == '-') {sign = -1;} else if (chars[0] != '+') { //如果首个非空格字符不是负号也不是加号,那么从第一个元素开始遍历i = 0;}int number = Integer.MAX_VALUE / 10;//结果int res = 0;for (int j = i; j < chars.length; j++) {//遇到非数字直接退出if (chars[j] > '9' || chars[j] < '0') break;/*这里这个条件的意思为,因为题目要求不能超过int范围,所以需要判断结果是否越界因为res每次都会 * 10 ,所以外面定义了一个int最大值除以10的数字此时只需要保证本次循环的res * 10 + chars[j] 不超过 int 即可保证不越界res > number 意思是,此时res已经大于number了,他 * 10 一定越界res == number && chars[j] > '7' 的意思是,当res == number时,即:214748364此时res * 10 变成 2147483640 此时没越界,但是还需要 + chars[j],而int最大值为 2147483647,所以当chars[j] > 7 时会越界*/if (res > number || (res == number && chars[j] > '7')) {//根据字符串首负号判断返回最大值还是最小值return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;}//字符获取数字需要 - '0' 的位移res = res * 10 + (chars[j] - '0');}//返回结果,需要判断正负return res * sign;}}

栈与队列(困难)- 1.24

*剑指 Offer 59 - I. 滑动窗口的最大值

剑指 Offer 59 - I. 滑动窗口的最大值1


解法一:

class Solution {public int[] maxSlidingWindow(int[] nums, int k) {if(nums.length == 0) return nums;int left = 0, right = left+k-1; //滑动窗口左右指针List<Integer> list = new ArrayList<>();while(right <= nums.length-1){int max = Integer.MIN_VALUE; //记录最大值for(int i=left; i<=right; i++){if(nums[i] > max) max = nums[i];  //找到当前窗口的最大值}list.add(max); //添加进集合left++; //移动左指针right++; //移动右指针}//集合赋值给数组返回int res[] = new int[list.size()];for(int i=0; i<list.size(); i++){res[i] = list.get(i);}return res;}
}

解法二:

参考:滑动窗口的最大值(单调队列,清晰图解)

class Solution {public int[] maxSlidingWindow(int[] nums, int k) {//单调队列//下面是要注意的点://队列按从大到小放入//如果首位值(即最大值)不在窗口区间,删除首位//如果新增的值小于队列尾部值,加到队列尾部//如果新增值大于队列尾部值,删除队列中比新增值小的值,如果在把新增值加入到队列中//如果新增值大于队列中所有值,删除所有,然后把新增值放到队列首位,保证队列一直是从大到小if (nums.length == 0)   return nums;Deque<Integer> deque = new LinkedList<>();int[] arr = new int[nums.length - k + 1];int index = 0;  //arr数组的下标//未形成窗口区间for (int i = 0; i < k; i++) {//队列不为空时,当前值与队列尾部值比较,如果大于,删除队列尾部值//一直循环删除到队列中的值都大于当前值,或者删到队列为空while (!deque.isEmpty() && nums[i] > deque.peekLast())  deque.removeLast();//执行完上面的循环后,队列中要么为空,要么值都比当前值大,然后就把当前值添加到队列中deque.addLast(nums[i]);}//窗口区间刚形成后,把队列首位值添加到队列中//因为窗口形成后,就需要把队列首位添加到数组中,而下面的循环是直接跳过这一步的,所以需要我们直接添加arr[index++] = deque.peekFirst();//窗口区间形成for (int i = k; i < nums.length; i++) {//i-k是已经在区间外了,如果首位等于nums[i-k],那么说明此时首位值已经不再区间内了,需要删除if (deque.peekFirst() == nums[i - k])   deque.removeFirst();//删除队列中比当前值大的值while (!deque.isEmpty() && nums[i] > deque.peekLast())  deque.removeLast();//把当前值添加到队列中deque.addLast(nums[i]);//把队列的首位值添加到arr数组中arr[index++] = deque.peekFirst();}return arr;}
}

*剑指 Offer 59 - II. 队列的最大值

剑指 Offer 59 - II. 队列的最大值


解法一:集合

class MaxQueue {List<Integer> list;public MaxQueue() {list = new ArrayList<>();}public int max_value() {if(list.size() == 0) return -1;int max = Integer.MIN_VALUE;for(int i=0; i<list.size(); i++){if(list.get(i) > max) max = list.get(i); //找出最大值}return max;}public void push_back(int value) {list.add(value);}public int pop_front() {if(list.size() == 0) return -1;int res = list.get(0);list.remove(0); //删除首元素return res;}
}/*** Your MaxQueue object will be instantiated and called as such:* MaxQueue obj = new MaxQueue();* int param_1 = obj.max_value();* obj.push_back(value);* int param_3 = obj.pop_front();*/

解法二:队列与双向队列

class MaxQueue {Queue<Integer> queue;Deque<Integer> deque;public MaxQueue() {queue = new LinkedList<>();deque = new LinkedList<>();}public int max_value() {//如果双向队列不为空,就返回双向队列首元素return deque.isEmpty() ? -1 : deque.peekFirst();}public void push_back(int value) {queue.offer(value); //队列正常入队while(!deque.isEmpty() && deque.peekLast() < value){deque.pollLast();  //如果双向队列的尾元素小于要添加的值,就移除该尾元素}deque.offerLast(value); //添加进尾部}public int pop_front() {if(queue.isEmpty()) return -1;if(queue.peek().equals(deque.peekFirst())){deque.pollFirst(); //如果两个队列的首元素相等,则双向队列也要弹出首元素}return queue.poll(); //弹出队列的元素}
}/*** Your MaxQueue object will be instantiated and called as such:* MaxQueue obj = new MaxQueue();* int param_1 = obj.max_value();* obj.push_back(value);* int param_3 = obj.pop_front();*/

搜索与回溯算法(困难)- 1.25

剑指 Offer 37. 序列化二叉树

剑指 Offer 37. 序列化二叉树



解析:

参考:序列化二叉树(层序遍历 BFS ,清晰图解)

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
public class Codec {// Encodes a tree to a single string.public String serialize(TreeNode root) {if(root == null) return "[]";StringBuilder res = new StringBuilder("[");  //左大括号Queue<TreeNode> que = new LinkedList<>();que.add(root);while(!que.isEmpty()){TreeNode node = que.poll();if(node != null){ //如果节点不为空res.append(node.val + ","); //添加给节点值que.add(node.left); //添加左孩子que.add(node.right);  //添加右孩子}else{res.append("null,"); //节点为空,添加空值}}res.deleteCharAt(res.length()-1); //删除逗号res.append("]"); //右大括号return res.toString();}// Decodes your encoded data to tree.public TreeNode deserialize(String data) {if(data.equals("[]")) return null;String[] vals = data.substring(1, data.length()-1).split(","); //根据","分割字符串TreeNode root = new TreeNode(Integer.parseInt(vals[0])); //创建根节点Queue<TreeNode> que = new LinkedList<>();que.add(root);int i=1; //下标while(!que.isEmpty()){TreeNode node = que.poll();if(!vals[i].equals("null")){node.left = new TreeNode(Integer.parseInt(vals[i])); //构建左孩子que.add(node.left); //添加左孩子}i++; //移动if(!vals[i].equals("null")){node.right  = new TreeNode(Integer.parseInt(vals[i])); //构建右孩子que.add(node.right); //添加右孩子}i++; //移动}return root;}
}// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));

剑指 Offer 38. 字符串的排列

剑指 Offer 38. 字符串的排列


解析:回溯

class Solution {List<String> res;StringBuilder path;boolean[] visited;public String[] permutation(String s) {//空值处理if(s.equals("")) return new String[]{};//初始化this.res = new ArrayList<>();this.path = new StringBuilder();this.visited = new boolean[s.length()];//字符串转为字符数组char[] ch = s.toCharArray();//排序,方便处理重复的字符Arrays.sort(ch);backtrack(ch,0);return res.toArray(new String[0]);}public void backtrack(char[] ch, int depth){if(depth == ch.length){res.add(path.toString());return;}//遍历字符数组for(int i=0; i<ch.length; i++){if(visited[i]) continue; //跳过if(i > 0 && ch[i-1] == ch[i] && !visited[i-1]) continue; //重复字串的处理path.append(ch[i]); //添加字符visited[i] = true; //标记已访问backtrack(ch, depth+1); //回溯,深度+1visited[i] = false; //重置访问path.deleteCharAt(path.length()-1); //删除刚添加的字符,重新尝试}}
}

动态规划(困难)- 1.25

剑指 Offer 19. 正则表达式匹配

剑指 Offer 19 正则表达式匹配


解析:

参考:正则表达式匹配(动态规划,清晰图解)

class Solution {public boolean isMatch(String s, String p) {int m = s.length() + 1, n = p.length() + 1;boolean[][] dp = new boolean[m][n];dp[0][0] = true;// 初始化首行for(int j = 2; j < n; j += 2)dp[0][j] = dp[0][j - 2] && p.charAt(j - 1) == '*';// 状态转移for(int i = 1; i < m; i++) {for(int j = 1; j < n; j++) {if(p.charAt(j - 1) == '*') {if(dp[i][j - 2]) dp[i][j] = true;                                            // 1.else if(dp[i - 1][j] && s.charAt(i - 1) == p.charAt(j - 2)) dp[i][j] = true; // 2.else if(dp[i - 1][j] && p.charAt(j - 2) == '.') dp[i][j] = true;             // 3.} else {if(dp[i - 1][j - 1] && s.charAt(i - 1) == p.charAt(j - 1)) dp[i][j] = true;  // 1.else if(dp[i - 1][j - 1] && p.charAt(j - 1) == '.') dp[i][j] = true;         // 2.}}}return dp[m - 1][n - 1];}
}

剑指 Offer 49. 丑数

剑指 Offer 49. 丑数


解析:

参考:丑数(动态规划,清晰图解)

class Solution {public int nthUglyNumber(int n) {if(n == 1) return 1;int a = 0, b = 0, c = 0;int[] dp = new int[n]; //dp[i]代表第 i + 1 个丑数;dp[0] = 1;for(int i=1; i<n; i++){int n2 = dp[a] * 2;int n3 = dp[b] * 3;int n5 = dp[c] * 5;dp[i] = Math.min(Math.min(n2, n3), n5); //取最小值if(dp[i] == n2) a++; //更新索引if(dp[i] == n3) b++;if(dp[i] == n5) c++;}return dp[n-1];}
}

剑指 Offer 60. n个骰子的点数

剑指 Offer 60. n个骰子的点数


解析:

参考: n 个骰子的点数(动态规划,清晰图解)


class Solution {public double[] dicesProbability(int n) {//因为最后的结果只与前一个动态转移数组有关,所以这里只需要设置一个一维的动态转移数组//原本dp[i][j]表示的是前i个骰子的点数之和为j的概率,现在只需要最后的状态的数组,所以就只用一个一维数组dp[j]表示n个骰子下每个结果的概率。//初始是1个骰子情况下的点数之和情况,就只有6个结果,所以用dp的初始化的size是6个double[] dp = new double[6];//只有一个数组Arrays.fill(dp,1.0/6.0);//从第2个骰子开始,这里n表示n个骰子,先从第二个的情况算起,然后再逐步求3个、4个···n个的情况//i表示当总共i个骰子时的结果for(int i=2;i<=n;i++){//每次的点数之和范围会有点变化,点数之和的值最大是i*6,最小是i*1,i之前的结果值是不会出现的;//比如i=3个骰子时,最小就是3了,不可能是2和1,所以点数之和的值的个数是6*i-(i-1),化简:5*i+1//当有i个骰子时的点数之和的值数组先假定是tempdouble[] temp = new double[5*i+1];//从i-1个骰子的点数之和的值数组入手,计算i个骰子的点数之和数组的值//先拿i-1个骰子的点数之和数组的第j个值,它所影响的是i个骰子时的temp[j+k]的值for(int j=0;j<dp.length;j++){//比如只有1个骰子时,dp[1]是代表当骰子点数之和为2时的概率,它会对当有2个骰子时的点数之和为3、4、5、6、7、8产生影响,因为当有一个骰子的值为2时,另一个骰子的值可以为1~6,产生的点数之和相应的就是3~8;比如dp[2]代表点数之和为3,它会对有2个骰子时的点数之和为4、5、6、7、8、9产生影响;所以k在这里就是对应着第i个骰子出现时可能出现六种情况,这里可能画一个K神那样的动态规划逆推的图就好理解很多for(int k=0;k<6;k++){//这里记得是加上dp数组值与1/6的乘积,1/6是第i个骰子投出某个值的概率temp[j+k]+=dp[j]*(1.0/6.0);}}//i个骰子的点数之和全都算出来后,要将temp数组移交给dp数组,dp数组就会代表i个骰子时的可能出现的点数之和的概率;用于计算i+1个骰子时的点数之和的概率dp = temp;}return dp;}
}

分治算法(困难)- 1.26

剑指 Offer 17. 打印从1到最大的n位数

剑指 Offer 17. 打印从1到最大的n位数


解法一:先计算出n位的最大数,再遍历添加到数组返回

class Solution {public int[] printNumbers(int n) {if(n == 0) return new int[]{};int num = 0;num = (int)Math.pow(10,n) - 1; //计算n位的最大数int[] res = new int[num];for(int i=0; i<num; i++){res[i] = i+1; //赋值}return res;}
}

解法二:考虑大数情况

参考:打印从 1 到最大的 n 位数(分治算法 / 全排列,清晰图解)

class Solution {int[] res;int nine = 0, count = 0, start, n;char[] num, loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};public int[] printNumbers(int n) {this.n = n;res = new int[(int)Math.pow(10, n) - 1];num = new char[n];start = n - 1;dfs(0);return res;}void dfs(int x) {if(x == n) {String s = String.valueOf(num).substring(start);if(!s.equals("0")) res[count++] = Integer.parseInt(s);if(n - start == nine) start--;return;}for(char i : loop) {if(i == '9') nine++;num[x] = i;dfs(x + 1);}nine--;}
}

剑指 Offer 51. 数组中的逆序对

剑指 Offer 51. 数组中的逆序对


解析:归并排序,在合并时统计逆序对的数量。

class Solution {//利用归并排序解答,在合并的时候,当左边的大于右边,就计算逆序数。//计算公式; mid-left+1//定义一个全局的计数器变量int count = 0;public int reversePairs(int[] nums) {this.count = 0;mergeSort(nums, 0, nums.length-1);return count;}//归并排序public void mergeSort(int[] nums,int left,int right){//当只有一个节点的时候,直接返回,退出递归if(left >= right){return;}int mid = (left+right)/2;//左拆分mergeSort(nums,left,mid);//右拆分mergeSort(nums,mid+1,right);//合并merge(nums,left,mid,right);}//合并public void merge(int[] nums,int left,int mid,int right){//定义一个临时数组int[] temp = new int[right-left+1];//定义一个指针,指向第一个数组的第一个元素int i = left;//定义一个指针,指向第二个数组的第一个元素int j = mid+1;//定义一个指针,指向临时数组的第一个元素int t = 0;//当两个数组都有元素的时候,遍历比较每个元素大小while(i <= mid && j <= right){//比较两个数组的元素,取较小的元素加入到,临时数组中//并将两个指针指向下一个元素if(nums[i] <= nums[j]){temp[t++] = nums[i++];}else{//当左边数组的大与右边数组的元素时,就对当前元素以及后面的元素的个数进行统计,//此时这个数就是,逆序数//定义一个计数器,记下每次合并中存在的逆序数。count += mid-i+1;temp[t++] = nums[j++];}}//当左边的数组没有遍历完成后,直接将剩余元素加入到临时数组中while(i <= mid){temp[t++] = nums[i++];}//当右边的数组没有遍历完成后,直接将剩余元素加入到临时数组中while(j <= right){temp[t++] =nums[j++];}//将新数组中的元素,覆盖nums旧数组中的元素。//此时数组的元素已经是有序的for(int k =0; k< temp.length;k++){nums[left+k] = temp[k];}}
}

数学(困难)-1.27

剑指 Offer 14- II. 剪绳子 II

剑指 Offer 14- II. 剪绳子 II


解析:贪心算法,循环求余

class Solution {public int cuttingRope(int n) {if(n <= 3) return n-1;long res = 1L; //最大乘积int p = (int)1e9+7; //int最大值//贪心算法,优先切三while(n > 4){res = res * 3 % p; //计算当前乘积n -= 3; //减去切掉的长度}//此时出循环只有三种情况,n=2,3,4;将它与前面的结果res相乘即可。return (int)(res * n % p);}
}

剑指 Offer 43. 1~n 整数中 1 出现的次数

剑指 Offer 43. 1~n 整数中 1 出现的次数


解析:

参考:1~n 整数中 1 出现的次数

class Solution {public int countDigitOne(int n) {// mulk 表示 10^k// 在下面的代码中,可以发现 k 并没有被直接使用到(都是使用 10^k)// 但为了让代码看起来更加直观,这里保留了 klong mulk = 1;int ans = 0;for (int k = 0; n >= mulk; ++k) {ans += (n / (mulk * 10)) * mulk + Math.min(Math.max(n % (mulk * 10) - mulk + 1, 0), mulk);mulk *= 10;}return ans;}
}

剑指 Offer 44. 数字序列中某一位的数字

剑指 Offer 44. 数字序列中某一位的数字


解析:

参考:数字序列中某一位的数字(迭代 + 求整 / 求余,清晰图解)

class Solution {public int findNthDigit(int n) {if(n==0) return 0;//由于是n=0时对应开始的0,这里不需要进行减操作n--;,但是如果n=1对应开始的0则需要减操作//排除n=0后,后面n从1开始。int digit = 1;int start = 1;long count = 9; //count的值有可能会超出int的范围,所以变量类型取为longwhile(n>count){//不能带=号,此时n从1开始,n=count时,属于上一个循环的最后一个数字n=(int)(n-count);//这里(int)不能省略digit++;start = start*10;count = (long)start*9*digit;//这里的long不能省略,否则,会先按照int类型进行计算,然后赋值给long型的count,超过int大小限制时,会出现负数}int num = start + (n-1)/digit;int index = (n-1)%digit;//index最大取digit-1,即此时num坐标从左到右为0,1,...,digit-1,共digit位while(index<(digit-1)){//最后的结果是num中的第index个数字,index从左到右从0开始递增,考虑到踢出右侧末尾的数字比较简单,我们从右侧开始依次踢出数字num = num/10;digit--;}return num%10;//此时num的右侧末尾数字即为结果}
}

算法题解(剑指Offer篇)相关推荐

  1. DayDayUp之Job:牛客网—算法工程师—剑指offer之66道在线编程(解决思路及其代码)——1~20

    DayDayUp之Job:牛客网-算法工程师-剑指offer之66道在线编程(解决思路及其代码)--01~20 目录 剑指offer--66道在线编程--01~20 1.二维数组中的查找某个targe ...

  2. DayDayUp之Job:牛客网—算法工程师—剑指offer之66道在线编程(解决思路及其代码)——41~66

    DayDayUp之Job:牛客网-算法工程师-剑指offer之66道在线编程(解决思路及其代码)--41~66 目录 剑指offer之66道在线编程--41~66 42.和为s的两个数字 43.左旋转 ...

  3. DayDayUp之Job:牛客网—算法工程师—剑指offer之66道在线编程(解决思路及其代码)——21~40

    DayDayUp之Job:牛客网-算法工程师-剑指offer之66道在线编程(解决思路及其代码)--21~41 目录 剑指offer之66道在线编程--21~41 21.栈的压入.弹出序列 22.从上 ...

  4. 递增的整数序列链表的插入_每日算法题 | 剑指offer 链表专题 (5)链表中倒数第k个节点...

    点击上方"Jerry的算法和NLP",选择"星标"公众号 重磅干货,第一时间送达 题目 链表中倒数第k个节点 题目要求 输入一个链表的头结点,从尾到头反过来打印 ...

  5. 【大总结3】leetcode解题总览(算法、剑指offer、SQL、多线程、shell)

    3/22更新 剑指offer 题目链接 建议大部分题都会做,都能比较快速且准确的写出来.关于做题方式,我的建议是:一道一道刷即可,因为难度一般,不用系统的学习什么知识,遇到实在不会的就跳过即可. 我这 ...

  6. 【算法】剑指 Offer 12. 矩阵中的路径

    文章目录 1.概述 2.我的算法 2.1 棋盘 2.1 开始节点 2.2 点没被访问 2.3 点是否在棋盘内 2.4 下一步 2.5 主方法 2.6 核心方法 2.7 测试类 3.leecode1 1 ...

  7. 力扣-图解算法数据结构-剑指 Offer 05. 替换空格

    题目要求 力扣题解 代码 /*** @program: mydemo* @description: 剑指 Offer 05. 替换空格* @author: Mr.zeng* @create: 2021 ...

  8. 算法小解--剑指offer(一)

    最近计划每天中午吃完饭后抽一点时间做一下剑指offer上面的题目.总共66题,打算分为6篇文章,每天更新. 1.题目描述 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增 ...

  9. 【刷题】剑指Offer篇

    本文中的部分图片摘自相关题解榜主,如有侵权,请联系删除. 特别感谢k神在剑指Offer刷题路上提供的清晰图解.和堪称完美的思路与方法 小文目录: T3:数组中重复的数字 T3-2:不修改数组找出重复的 ...

最新文章

  1. c++程序的多文件组织
  2. ADO.NET 【属性扩展】性别、年龄、编号
  3. 75. InputStreamReader和OutputStreamWriter(转换流--字节流转换成字符流)
  4. 推荐一款文件搜索神器Everything!
  5. JAVA web 会话技术CookieSession
  6. java进阶 2018_Java Web基础入门2018版
  7. Linux 中执行命令 ls -l 后,文件详细信息(文件属性/文件详情)说明
  8. 阿里程序员双11语录走红
  9. cnpm能用npm install吗_指纹锁一般能用几年? 指纹锁没电了怎么办
  10. linux基础-第十六单元 yum管理RPM包
  11. 关于数据库记录排序问题
  12. 81. $GLOBALS['HTTP_RAW_POST_DATA'] 和$_POST的区别
  13. Keli5 更改任意喜欢的字体
  14. 苹果测试网速软件,Mac 网速测试工具 SpeedTest by Ookla
  15. k8s集群的搭建-云服务器
  16. 潘多拉网吧防火墙 1.0 双线破解
  17. ArcGIS中ObjectID,FID和OID字段区别
  18. Fiddler抓手机包教程
  19. 互联网摸鱼日报(2022-10-24)
  20. Simulink模型生成C语言

热门文章

  1. 王权富贵:Unable to locate package python-pip
  2. 【愚公系列】2022年04月 微信小程序-地图的使用之线聚合
  3. 中国现代书画家——张福民、高俊祥、张晓东、潘江海
  4. Git 教程看这一篇就够了
  5. 【报告分享】 2020中国社媒APP企业白皮书-Meltwater (附下载)
  6. 【SCI征稿】1区计算机算法和概率类,SCIEEI在检,CCF-C类
  7. linux命令行提示工具-fish
  8. C语言递归实现二路归并排序
  9. Lynis安全漏洞扫描工具
  10. 【Java集合 1】java集合有哪些