LeetCode:“剑指 Offer”

刷题小菜鸡,花了几天时间做了一遍 LeetCode 上给出的 “剑指 Offer” 在此做一下记录

LeetCode主页:贤余超

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

  1. // 方法1:
  2. // hash表来做:空间换时间的思想
  3. // 时间复杂度 O(n)
  4. // 空间复杂度 O(n)
  5. public int findRepeatNumber(int[] nums) {
  6. Set<Integer> hashset = new HashSet<>();
  7. for(int i=0; i<nums.length; i++){
  8. if(hashset.contains(Integer.valueOf(nums[i]))){
  9. return nums[i];
  10. }
  11. hashset.add(Integer.valueOf(nums[i]));
  12. }
  13. return -1;
  14. }
  15. // 方法2:
  16. // 先排序再去比较
  17. // 时间复杂度 O(nlogn + n)
  18. // 空间复杂度 O(1)
  19. public int findRepeatNumber(int[] nums) {
  20. Arrays.sort(nums);
  21. for(int i=0; i<nums.length-1; i++){
  22. if(nums[i] == nums[i+1]){
  23. return nums[i];
  24. }
  25. }
  26. return -1;
  27. }
  28. // 方法3:
  29. // 原地交换算法:一个萝卜对应一个坑
  30. // 时间复杂度:O(n)
  31. // 空间复杂度:O(1)
  32. public int findRepeatNumber(int[] nums) {
  33. int tmp;
  34. for(int i=0; i<nums.length; i++){
  35. while(nums[i] != i){
  36. if(nums[i] == nums[nums[i]]){
  37. return nums[i];
  38. }
  39. tmp = nums[i];
  40. nums[i] = nums[tmp];
  41. nums[tmp] = tmp;
  42. }
  43. }
  44. return -1;
  45. }

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

  1. // 方法1:
  2. // 先来个暴力法
  3. // 时间复杂度:O(m*n)
  4. // 空间复杂度:O(1)
  5. public boolean findNumberIn2DArray(int[][] matrix, int target) {
  6. for(int m=0; m<matrix.length; m++){
  7. for(int n=0; n<matrix[m].length; n++){
  8. if(target == matrix[m][n]){
  9. return true;
  10. }
  11. }
  12. }
  13. return false;
  14. }
  15. // 方法2:
  16. // 从右上角开始往左下角走,类似二叉搜索树,右上角为根节点
  17. // 时间复杂度:O(m+n)
  18. // 空间复杂度:O(1)
  19. public boolean findNumberIn2DArray(int[][] matrix, int target) {
  20. if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
  21. return false;
  22. }
  23. int rows = matrix.length, cols = matrix[0].length;
  24. int row = 0, col = cols-1; // 从右上角开始
  25. while(row < rows && col >=0){
  26. if(target == matrix[row][col]){
  27. return true;
  28. }else if(target < matrix[row][col]){
  29. // 当前值大于target则往左边:左子树方向
  30. col --;
  31. }else{
  32. // 当前值小于target则往右边:右子树方向
  33. row ++;
  34. }
  35. }
  36. return false;
  37. }

剑指 Offer 05. 替换空格

  1. // 方法1:正常解法
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n)
  4. public String replaceSpace(String s) {
  5. StringBuffer sb = new StringBuffer();
  6. for(int i=0; i<s.length(); i++){
  7. char c = s.charAt(i);
  8. if(c == ' '){
  9. sb.append("%20");
  10. }else{
  11. sb.append(c);
  12. }
  13. }
  14. return sb.toString();
  15. }
  16. // 方法2:库函数
  17. public String replaceSpace(String s) {
  18. return s.replace(" ", "%20");
  19. }

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

  1. // 方法1:两次遍历
  2. // 时间复杂度:O(2n)
  3. // 空间复制度:O(n)
  4. public int[] reversePrint(ListNode head) {
  5. if(head == null){
  6. return new int[0];
  7. }
  8. // 第一次遍历
  9. List<Integer> tmp = new ArrayList<>();
  10. while(head != null){
  11. tmp.add(head.val);
  12. head = head.next;
  13. }
  14. // 第二次遍历
  15. int[] res = new int[tmp.size()];
  16. for(int i=tmp.size()-1, j=0; i>=0; i--){
  17. res[j++] = tmp.get(i);
  18. }
  19. return res;
  20. }
  21. // 方法2:使用栈
  22. // 时间复杂度:O(n)
  23. // 空间复制度:O(n)
  24. public int[] reversePrint(ListNode head) {
  25. Stack<Integer> stack = new Stack<>();
  26. while(head != null){
  27. stack.push(head.val);
  28. head = head.next;
  29. }
  30. int[] res = new int[stack.size()];
  31. int size = stack.size();
  32. for(int i=0; i<size; i++){
  33. res[i] = stack.pop();
  34. }
  35. return res;
  36. }
  37. // 方法3:递归,其实也是栈
  38. // 时间复杂度:O(n)
  39. // 空间复制度:O(n)
  40. List<Integer> tmp = new ArrayList<Integer>();
  41. public int[] reversePrint(ListNode head) {
  42. recur(head);
  43. int[] res = new int[tmp.size()];
  44. for(int i = 0; i < res.length; i++){
  45. res[i] = tmp.get(i);
  46. }
  47. return res;
  48. }
  49. private void recur(ListNode head) {
  50. if(head == null) return;
  51. // 递归调用
  52. recur(head.next);
  53. // 递归回来的时候加入,最后的一个节点最先到这里
  54. tmp.add(head.val);
  55. }

剑指 Offer 07. 重建二叉树

树专题中对重建二叉树的题目有涉及

  1. // 复杂度分析:
  2. // 空间复杂度:建了一个hash表,递归过程中也需要空间 O(n)
  3. // 时间复杂度:还是相当于遍历了一棵树 O(n)
  4. // 加速inorder中对于的根节点的下标寻找
  5. private Map<Integer, Integer> indexMap;
  6. public TreeNode buildTree(int[] preorder, int[] inorder) {
  7. // 前序遍历:[root [root.left] [root.right]]
  8. // 中序遍历:[[root.left] root [root.right]]
  9. if (preorder.length == 0 || inorder.length ==0 || preorder.length != inorder.length){
  10. return null;
  11. }
  12. indexMap = new HashMap<>();
  13. // 加速根据前序遍历中根节点在中序遍历的结果中找该节点的过程
  14. for(int i=0; i<preorder.length; i++){
  15. indexMap.put(inorder[i], i);
  16. }
  17. return helper(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1);
  18. }
  19. private TreeNode helper(int[] preorder, int pre_s, int pre_e, int[] inorder, int in_s, int in_e){
  20. if(pre_s > pre_e){
  21. // 递归结束条件
  22. return null;
  23. }
  24. // 很显然,前序遍历的第一个元素就是根节点
  25. TreeNode root = new TreeNode(preorder[pre_s]);
  26. // 找到根节点在中序遍历的位置,这样就能分左右子树了
  27. int index = indexMap.get(preorder[pre_s]);
  28. // 左子树的长度
  29. int len = index - in_s;
  30. root.left = helper(preorder, pre_s+1, pre_s+len, inorder, in_s, index-1);
  31. root.right = helper(preorder, pre_s+1+len, pre_e, inorder, index+1, in_e);
  32. return root;
  33. }

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

  1. // 复杂度分析:
  2. // 时间复杂度 入队:O(1) 出队:O(n)
  3. // 空间复杂度:O(n)
  4. LinkedList<Integer> stack1; // 入队都在这里
  5. LinkedList<Integer> stack2; // 出队的时候都从这里出
  6. public CQueue() {
  7. // // stack1
  8. // stack1 = new Stack<>();
  9. // // stack2
  10. // stack2 = new Stack<>();
  11. // Stack能做的事LinkedList都能做
  12. // stack1
  13. stack1 = new LinkedList<>();
  14. // stack2
  15. stack2 = new LinkedList<>();
  16. }
  17. public void appendTail(int value) {
  18. stack1.push(value);
  19. }
  20. public int deleteHead() {
  21. // 弹出做特殊处理,都是从2弹出
  22. if(stack2.isEmpty()){
  23. // 当2为空,则把1中的全部给2
  24. while(!stack1.isEmpty()){
  25. stack2.push(stack1.pop());
  26. }
  27. }
  28. if(stack2.isEmpty()){
  29. return -1;
  30. }else{
  31. return stack2.pop();
  32. }
  33. }

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

  1. // 求余运算规则: (a+b)%c ---> (a%c + b%c)%c
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1)
  4. public int fib(int n) {
  5. if(n==0){
  6. return 0;
  7. }
  8. if(n == 1){
  9. return 1;
  10. }
  11. int a = 0, b = 1;
  12. for(int i=2; i<=n; i++){
  13. int tmp = b;
  14. // 这里就相当于用了上面的公式
  15. b = (a+b) % 1000000007;
  16. a = tmp;
  17. }
  18. return b;
  19. }

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

  1. // 和上一题基本一样,不做分析
  2. public int numWays(int n) {
  3. if(n == 0 || n == 1){
  4. return 1;
  5. }
  6. int a=1, b=1;
  7. for(int i=2; i<=n; i++){
  8. int tmp = b;
  9. b = (a+b) % 1000000007;
  10. a = tmp;
  11. }
  12. return b;
  13. }

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

  1. // 方法0:啊?这?
  2. // 时间复杂度:O(nlogn)
  3. // 空间复杂度:O(1)
  4. public int minArray(int[] numbers) {
  5. if(numbers.length == 0){
  6. return 0;
  7. }
  8. Arrays.sort(numbers);
  9. return numbers[0];
  10. }
  11. // 正确解法:二分查找
  12. // 时间复杂度:O(logn),但是由于后续使用了线性查找,最坏的情况下会到O(n)
  13. // 空间复杂度:O(1)
  14. public int minArray(int[] numbers) {
  15. if(numbers.length <= 0){
  16. return 0;
  17. }
  18. // 开始二分
  19. int left = 0, right = numbers.length-1;
  20. while(left <= right){
  21. int mid = left + (right - left) / 2;
  22. if(numbers[mid] > numbers[right]){
  23. left = mid+1;
  24. }else if(numbers[mid] < numbers[right]){
  25. right = mid;
  26. }else if(numbers[mid] == numbers[right]){
  27. // 直接线性查找好了
  28. return findMin(numbers, left, right);
  29. }
  30. }
  31. return left >= numbers.length ? numbers[numbers.length-1] : numbers[left];
  32. }
  33. private int findMin(int[] numbers,int start,int end){
  34. int result = numbers[start];
  35. for(int i = start;i <= end;i++){
  36. if (numbers[i] < result) result = numbers[i];
  37. }
  38. return result;
  39. }

剑指 Offer 12. 矩阵中的路径

  1. // dfs+回溯的思想
  2. // 时间复杂度: 不太好分析
  3. // 空间复杂度:即递归的深度,也不太好分析
  4. public boolean exist(char[][] board, String word) {
  5. char[] words = word.toCharArray();
  6. for(int i=0; i<board.length; i++){
  7. for(int j=0; j<board[0].length; j++){
  8. if(dfs(board, words, i, j, 0)) return true;
  9. }
  10. }
  11. return false;
  12. }
  13. private boolean dfs(char[][] board, char[] words, int i, int j, int k){
  14. if(i >= board.length || i < 0 || j >=board[0].length || j < 0 || board[i][j] != words[k]){
  15. return false;
  16. }
  17. if(k == words.length - 1){
  18. return true;
  19. }
  20. // 回溯:
  21. // 到这里说明的是第k个已经相等了,接下来要判断第k+1个字符了
  22. board[i][j] = '\0'; // 用于剪枝操作
  23. boolean res = dfs(board, words, i+1, j, k+1) || dfs(board, words, i-1, j, k+1) ||
  24. dfs(board, words, i, j+1, k+1) || dfs(board, words, i, j-1, k+1);
  25. board[i][j] = words[k];
  26. return res;
  27. }

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

  1. // 方法1:dfs+回溯思想
  2. // 时间复杂度 O(mn)
  3. // 空间复杂度:O(mn)
  4. public int movingCount(int m, int n, int k) {
  5. boolean[][] visited = new boolean[m][n];
  6. return dfs(visited, m, n, k, 0, 0);
  7. }
  8. private int dfs(boolean[][] visited, int m, int n, int k, int i, int j){
  9. // 判断是否能到这个坐标
  10. if(i >= m || j >= n || visited[i][j] || (bitSum(i) + bitSum(j) > k)) {
  11. return 0;
  12. }
  13. // 能到这里,则继续向左边/下边走
  14. visited[i][j] = true;
  15. return dfs(visited, m, n, k, i+1, j) + dfs(visited, m, n, k, i, j+1) + 1;
  16. }
  17. private int bitSum(int n){
  18. int sum = 0;
  19. while(n != 0){
  20. sum += n%10;
  21. n /= 10;
  22. }
  23. return sum;
  24. }
  25. // 方法2:bfs
  26. // 时间复杂度 O(mn)
  27. // 空间复杂度:O(mn)
  28. public int movingCount(int m, int n, int k) {
  29. boolean[][] visited = new boolean[m][n];
  30. int res = 0;
  31. // bfs求解
  32. Queue<int[]> queue = new LinkedList<>();
  33. // 坐标 i j 数位和: i的数位和 j的数位和
  34. queue.add(new int[]{0, 0, 0, 0});
  35. while(!queue.isEmpty()){
  36. int[] point = queue.poll();
  37. int i=point[0], j=point[1], s_i=point[2], s_j=point[3];
  38. if( i>=m || j>=n || k<s_i+s_j || visited[i][j]){
  39. continue;
  40. }
  41. visited[i][j] = true;
  42. res ++;
  43. queue.add(new int[]{i+1, j, bitSum(i+1), bitSum(j)});
  44. queue.add(new int[]{i, j+1, bitSum(i), bitSum(j+1)});
  45. }
  46. return res;
  47. }
  48. private int bitSum(int n){
  49. int sum = 0;
  50. while(n != 0){
  51. sum += n%10;
  52. n /= 10;
  53. }
  54. return sum;
  55. }

剑指 Offer 14- I. 剪绳子

  1. // 动态规划:
  2. // 时间复杂度 O(n^2)
  3. // 空间复杂度:O(n)
  4. public int cuttingRope(int n) {
  5. // 明确状态: d[i] 绳子长为i时的最大值
  6. int[] dp = new int[n+1];
  7. // base case
  8. dp[0] = 0; // 长度为0当然为0
  9. dp[1] = 0; // 长度为1时,不能切分
  10. // 状态转移
  11. for(int i=2; i<=n; i++){ // 状态1的转移:绳子的长度增大
  12. for(int j=1; j<i; j++){ // 状态2的转移:把绳切成 i*(i-j)
  13. int cut_more = dp[i-j] * j; // i-j这段能切的最大值*j这段的最大值
  14. int cut_two = (i-j) * j; // 不继续切了,切成两段
  15. dp[i] = Math.max(dp[i], Math.max(cut_more, cut_two));
  16. }
  17. }
  18. return dp[n];
  19. }

剑指 Offer 14- II. 剪绳子 II

  1. // 和上一题不同,次数已经不能再使用动态规划了
  2. // 参考:Java贪心 思路讲解 —— fanhua
  3. // 最优:3 次优:2 等价于 4 最差:1
  4. // 结合公式:(x*y)%p = ((x%p) * (y%p))%p
  5. // 时间复杂度:O(n)
  6. // 空间复杂度:O(1)
  7. public int cuttingRope(int n) {
  8. // base case
  9. if(n == 2){
  10. return 1;
  11. }
  12. if(n == 3){
  13. return 2;
  14. }
  15. long res = 1;
  16. while(n > 4){ // 每次都拆一个3,3是最优的情况
  17. res = res * 3;
  18. res %= 1000000007;
  19. n -= 3;
  20. }
  21. // 最后还剩下长度4/2这种长度,直接乘上就行
  22. return (int) (res * n % 1000000007);
  23. }

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

  1. // 方法1:与运算
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1)
  4. public int hammingWeight(int n) {
  5. int ans = 1; // 标志位
  6. int res = 0;
  7. for(int i=1; i<=32; i++){
  8. if((n & ans) != 0){
  9. res++;
  10. }
  11. ans = ans << 1;
  12. }
  13. return res;
  14. }
  15. // 方法2:另一种方式进行与运算
  16. // 时间复杂度:O(n)
  17. // 空间复杂度:O(1)
  18. public int hammingWeight(int n) {
  19. int res = 0;
  20. while(n != 0) {
  21. res += n & 1;
  22. // java中的无符号右移
  23. n >>>= 1;
  24. }
  25. return res;
  26. }

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

  1. // 方法1:暴力法
  2. // 直接超出时间限制.... 1.00000 2147483647
  3. // 时间复杂度:O(n)
  4. // 空间复杂度:O(1)
  5. public double myPow(double x, int n) {
  6. double res = 1;
  7. boolean flag = false;
  8. if(n < 0){
  9. flag = true;
  10. n = -n;
  11. }
  12. for(int i=n; i>=1; i--){
  13. res *= x;
  14. }
  15. if(flag){
  16. res = 1/res;
  17. }
  18. return res;
  19. }
  20. // 快速幂算法:递归形式
  21. // 依靠了公式: 偶数:x^n = (x^2)^(n/2) 奇数:x^n = x(x^2)^(n/2)
  22. // 时间复杂度:O(logn)
  23. // 空间复杂度:O(1),这里忽略了递归占用的空间
  24. public double myPow(double x, int n) {
  25. // 如果n等于0,直接返回1
  26. if (n == 0)
  27. return 1;
  28. // 如果n小于0,把它改为正数,并且把1/x提取出来一个
  29. // 这是因为最小值溢出的问题
  30. if (n < 0)
  31. return 1/x * myPow(1 / x, -n - 1);
  32. // 根据n是奇数还是偶数来做不同的处理
  33. return (n % 2 == 0) ? myPow(x * x, n / 2) : x * myPow(x * x, n / 2);
  34. }
  35. // 快速幂算法:迭代形式
  36. // 时间复杂度:O(logn)
  37. // 空间复杂度:O(1)
  38. public double myPow(double x, int n) {
  39. double res = 1.0;
  40. long t = n;
  41. if(t < 0){
  42. t = -t;
  43. }
  44. while(t > 0){
  45. if(t % 2 == 1){
  46. // 奇数的情况多*一个x
  47. res *= x;
  48. }
  49. // 变为 x^2 , 则t就可以减半了
  50. x *= x;
  51. t /= 2;
  52. }
  53. return n < 0 ? 1.0 / res : res;
  54. }

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

  1. // 不考虑大数问题时解题:
  2. // 时间复杂度:0(10^n)
  3. // 空间复杂度:0(1) 返回结果不计入
  4. public int[] printNumbers(int n) {
  5. int max = 9;// 最大的n位数,最终肯定是n个9
  6. for(int i=2; i<=n; i++){
  7. max = max * 10;
  8. max += 9;
  9. }
  10. // 打印出结果
  11. int[] res = new int[max];
  12. for(int i=1; i<=max; i++){
  13. res[i-1] = i;
  14. }
  15. return res;
  16. }
  17. // 考虑大数问题解题:分析如下
  18. // 即当n较大时,max会超出int32的取值范围
  19. // 无论是什么类型,都会越界,因此使用String类型
  20. // 因此通过全排列的方式来组成数字,即回溯算法的思想
  21. // 时间复杂度:0(10^n)
  22. // 空间复杂度:0(10^n)
  23. // 代码:
  24. // 用于存储满足条件的字符串
  25. List<Integer> list;
  26. StringBuilder sb;
  27. public int[] printNumbers(int n) {
  28. // 存储符合条件的'数' '数'的类型是字符串
  29. list = new ArrayList<>(); // 用LinkedList的最后一个案例会超时
  30. sb = new StringBuilder();
  31. // 这里进行回溯
  32. dfs(n, 0);
  33. int[] res = new int[list.size()];
  34. // 存入数组
  35. for (int i = 0; i < res.length; i++) {
  36. res[i] = list.get(i);
  37. }
  38. return res;
  39. }
  40. private void dfs(int n, int index) {
  41. // 递归结束的条件
  42. if(index == n){
  43. while (sb.length() != 0 && sb.charAt(0) == '0') {
  44. // 将左边多余的0删除
  45. sb.deleteCharAt(0);
  46. }
  47. // 将字符串形式的'数',转化为整数
  48. if(sb.length() != 0){
  49. list.add(Integer.valueOf(sb.toString()));
  50. }
  51. return;
  52. }
  53. for(int j=0; j<10; j++){
  54. // 深度搜索下一位
  55. sb.append(j);
  56. dfs(n, index + 1);
  57. if(sb.length() != 0){
  58. sb.deleteCharAt(sb.length() - 1);
  59. }
  60. }
  61. }

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

  1. // 正常遍历:
  2. // 时间复杂度:0(n)
  3. // 空间复杂度:0(1)
  4. public ListNode deleteNode(ListNode head, int val) {
  5. if(head == null){
  6. return head;
  7. }
  8. // 特殊处理:头结点就是要删除的节点
  9. if(head.val == val){
  10. return head.next;
  11. }
  12. ListNode ans = new ListNode(-1);
  13. ans.next = head;
  14. while(head.next != null){
  15. if(head.next.val == val){
  16. head.next = head.next.next;
  17. return ans.next;
  18. }
  19. head = head.next;
  20. }
  21. return ans.next;
  22. }

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

  1. // 方法1:递归
  2. public boolean isMatch(String s, String p) {
  3. if(p.length() == 0){
  4. return s.length() == 0;
  5. }
  6. // 匹配首位字符
  7. boolean fist = false;
  8. if(s.length() != 0){
  9. char s1 = s.charAt(0);
  10. char p1 = p.charAt(0);
  11. fist = (s1 == p1);
  12. if(p1 == '.'){
  13. // . 在这里判断了
  14. fist = true;
  15. }
  16. }
  17. if(p.length() >= 2 && p.charAt(1) == '*'){
  18. // 判断是否有*
  19. return isMatch(s, p.substring(2, p.length())) ||
  20. (fist && isMatch(s.substring(1, s.length()), p));
  21. }else{
  22. return fist && isMatch(s.substring(1, s.length()), p.substring(1, p.length()));
  23. }
  24. }
  25. // 方法2:上述方法的改进带个备忘录的dp
  26. int[][] cache; // 0 没算过 1 为true -1 为false
  27. public boolean isMatch(String s, String p) {
  28. cache = new int[s.length()+1][p.length()+1];
  29. for(int i=0; i<=s.length(); i++){
  30. for(int j=0; j<=p.length(); j++){
  31. cache[i][j] = 0;
  32. }
  33. }
  34. return dp(s, 0, p, 0);
  35. }
  36. private boolean dp(String s, int i, String p, int j){
  37. if(cache[i][j] != 0){
  38. return cache[i][j] == 1 ? true : false;
  39. }
  40. if(j == p.length()){
  41. return i == s.length();
  42. }
  43. // 匹配首位字符
  44. boolean fist = false;
  45. if(i < s.length()){
  46. fist = (s.charAt(i) == p.charAt(j));
  47. if(p.charAt(j) == '.'){
  48. fist = true;
  49. }
  50. }
  51. boolean ans = false;
  52. if(p.length() - j >= 2 && p.charAt(j+1) == '*'){
  53. ans = dp(s, i, p, j+2) || // 直接掉过*
  54. (fist && dp(s, i+1, p, j)); // 第一个字符是匹配的,则继续匹配任意多个
  55. }else{
  56. ans = fist && dp(s, i+1, p, j+1);
  57. }
  58. if(ans){
  59. cache[i][j] = 1;
  60. }else{
  61. cache[i][j] = -1;
  62. }
  63. return ans;
  64. }

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

这种题目真的很烦人,直接看答案+CV的

  1. public boolean isNumber(String s) {
  2. if(s == null || s.length() == 0){
  3. return false;
  4. }
  5. // 标记是否遇到相应情况
  6. boolean numSeen = false;
  7. boolean dotSeen = false;
  8. boolean eSeen = false;
  9. // 去除空格转换为字符数组
  10. char[] str = s.trim().toCharArray();
  11. for(int i = 0;i < str.length; i++){
  12. if(str[i] >= '0' && str[i] <= '9'){
  13. // 出现数组了标记一下
  14. numSeen = true;
  15. }else if(str[i] == '.'){
  16. // .之前不能出现.或者e
  17. if(dotSeen || eSeen){
  18. return false;
  19. }
  20. dotSeen = true;
  21. }else if(str[i] == 'e' || str[i] == 'E'){
  22. // e之前不能出现e,必须出现数
  23. if(eSeen || !numSeen){
  24. return false;
  25. }
  26. eSeen = true;
  27. numSeen = false; // 重置numSeen,排除123e或者123e+的情况,确保e之后也出现数
  28. }else if(str[i] == '-' || str[i] == '+'){
  29. //+-出现在0位置或者e/E的后面第一个位置才是合法的
  30. if(i != 0 && str[i-1] != 'e' && str[i-1] != 'E'){
  31. return false;
  32. }
  33. }else{//其他不合法字符
  34. return false;
  35. }
  36. }
  37. return numSeen;
  38. }

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

  1. // 方法1:暴力法
  2. // 时间空间复杂度:O(n)
  3. public int[] exchange(int[] nums) {
  4. int[] res = new int[nums.length];
  5. int left = 0, right = nums.length-1;
  6. for(int i=0; i<nums.length; i++){
  7. if(nums[i] % 2 == 0){
  8. res[right] = nums[i];
  9. right--;
  10. }else{
  11. res[left] = nums[i];
  12. left++;
  13. }
  14. }
  15. return res;
  16. }
  17. // 方法2:双指针
  18. // 时间复杂度:O(n)
  19. // 空间复杂度:O(1)
  20. public int[] exchange(int[] nums) {
  21. int left = 0, right = nums.length-1;
  22. while(left < right){
  23. // 左指针向右遍历找到第一个偶数
  24. while(nums[left]%2==1 && left < right){
  25. left ++;
  26. }
  27. // 右指针向左遍历找到第一个奇数
  28. while(nums[right]%2==0 && left < right){
  29. right --;
  30. }
  31. // 交换左右指针指向的值
  32. if(left < right){
  33. int tmp = nums[left];
  34. nums[left] = nums[right];
  35. nums[right] = tmp;
  36. }
  37. }
  38. return nums;
  39. }

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

  1. // 快慢指针/双指针
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1)
  4. public ListNode getKthFromEnd(ListNode head, int k) {
  5. if(head == null){
  6. return null;
  7. }
  8. ListNode node1 = head;
  9. ListNode node2 = head;
  10. // 先让快指针走个k步
  11. while(k-- != 0){
  12. node1 = node1.next;
  13. }
  14. // 然后快慢指针一起走,当快指针走到头的时候,返回慢指针即可
  15. while(node1 != null){
  16. node2 = node2.next;
  17. node1 = node1.next;
  18. }
  19. return node2;
  20. }

剑指 Offer 24. 反转链表

  1. // 正常迭代反转
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1)
  4. public ListNode reverseList(ListNode head) {
  5. ListNode curr = head;
  6. ListNode pre = null;
  7. ListNode temp = null;
  8. while(curr != null){
  9. temp = curr.next;
  10. curr.next = pre;
  11. pre = curr;
  12. curr = temp;
  13. }
  14. return pre;
  15. }

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

  1. // 正常迭代反转
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1)
  4. public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  5. ListNode res = new ListNode(-1);
  6. ListNode head = new ListNode(-1);
  7. res = head;
  8. while(l1 != null && l2 != null){
  9. if(l1.val <= l2.val){
  10. head.next = l1;
  11. l1 = l1.next;
  12. }else{
  13. head.next = l2;
  14. l2 = l2.next;
  15. }
  16. head = head.next;
  17. }
  18. // 口少口巴
  19. head.next = l1==null ? l2 : l1;
  20. return res.next;
  21. }

剑指 Offer 26. 树的子结构

  1. // 树专题中双递归的技巧
  2. // 时间复杂度:O(mn),其中m为A的节点数量,n为B的节点数量,每次都是以A作为根节点去匹配B数的n个节点
  3. // 空间复杂度:O(m) 最大递归深度
  4. public boolean isSubStructure(TreeNode A, TreeNode B) {
  5. if(A==null || B==null){
  6. return false;
  7. }
  8. // 递归遍历A树与B树进行判断
  9. return dfs(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
  10. }
  11. private boolean dfs(TreeNode A, TreeNode B){
  12. if(A == null && B != null){
  13. // A已经空了,B还有节点,很显然B不是A的子结构
  14. return false;
  15. }else if(B == null){
  16. // B已经空了,A还有节点,能递归到这里,说明B是A的子结构
  17. return true;
  18. }else if(A.val != B.val){
  19. // 值不同,那就不用看了,B必不是A的子结构
  20. return false;
  21. }
  22. // 继续迭代
  23. return dfs(A.left, B.left) && dfs(A.right, B.right);
  24. }

剑指 Offer 27. 二叉树的镜像

  1. // bfs
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n)
  4. public TreeNode mirrorTree(TreeNode root) {
  5. if(root == null){
  6. return null;
  7. }
  8. Queue<TreeNode> queue = new LinkedList<>();
  9. queue.offer(root);
  10. while(!queue.isEmpty()){
  11. int size = queue.size();
  12. for(int i=0; i<size; i++){
  13. TreeNode node = queue.poll();
  14. if(node.right != null){
  15. queue.offer(node.right);
  16. }
  17. if(node.left != null){
  18. queue.offer(node.left);
  19. }
  20. // 这里反转一下就好了,自顶向下,
  21. TreeNode tmp = node.left;
  22. node.left = node.right;
  23. node.right = tmp;
  24. }
  25. }
  26. return root;
  27. }
  28. // dfs
  29. // 时间复杂度:O(n),每个节点都还是要被遍历到
  30. // 空间复杂度:O(n),递归所需的空间
  31. public TreeNode mirrorTree(TreeNode root) {
  32. if(root == null){
  33. return root;
  34. }
  35. // 借助个临时节点来避免覆盖后找不到左(右)节点
  36. TreeNode tmp = root.left;
  37. // 递归反转
  38. root.left = mirrorTree(root.right);
  39. root.right = mirrorTree(tmp);
  40. return root;
  41. }

剑指 Offer 28. 对称的二叉树

  1. // bfs:建立两个队列
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(2n),这里建立了两个队列
  4. public boolean isSymmetric(TreeNode root) {
  5. if(root == null){
  6. return true;
  7. }
  8. // 从左到右层序遍历
  9. Queue<TreeNode> queue1 = new LinkedList<>();
  10. // 从右到左层序遍历
  11. Queue<TreeNode> queue2 = new LinkedList<>();
  12. queue1.offer(root);
  13. queue2.offer(root);
  14. while(!queue1.isEmpty()){
  15. int size1 = queue1.size();
  16. int size2 = queue2.size();
  17. if(size1 != size2){
  18. return false;
  19. }
  20. for(int i=0; i<size1; i++){
  21. TreeNode node1 = queue1.poll();
  22. TreeNode node2 = queue2.poll();
  23. if(node1 == null && node2 == null){
  24. continue;
  25. }
  26. // 对每个节点进行比较
  27. if((node1 == null && node2 != null)||
  28. (node2 == null && node1 != null)||
  29. (node1.val != node2.val)){
  30. return false;
  31. }
  32. queue1.offer(node1.left);
  33. queue1.offer(node1.right);
  34. queue2.offer(node2.right);
  35. queue2.offer(node2.left);
  36. }
  37. }
  38. return true;
  39. }
  40. // dfs:
  41. // 时间复杂度:O(n)
  42. // 空间复杂度:O(n),递归消耗
  43. public boolean isSymmetric(TreeNode root) {
  44. if (root == null)
  45. return true;
  46. // 1.明确:递归的函数要干什么?
  47. // 函数的作用是判断传入的两个树是否镜像
  48. // 输入:TreeNode left, TreeNode right
  49. // 输出:是:true,不是:false
  50. return helper(root.left, root.right);
  51. }
  52. public boolean helper(TreeNode root1, TreeNode root2) {
  53. // 2.明确:递归停止的条件是什么
  54. if (root1 == null && root2 == null){
  55. // 左节点和右节点都为空 -> 倒底了都长得一样 ->true
  56. return true;
  57. }
  58. if (root1 == null || root2 == null){
  59. // 左节点为空的时候右节点不为空,或反之 -> 长得不一样-> false
  60. return false;
  61. }
  62. if (root1.val != root2.val){
  63. // 左右节点值不相等 -> 长得不一样 -> false
  64. return false;
  65. }
  66. // 3.明确:从某层到下一层的关系是什么
  67. // 要想两棵树镜像,那么一棵树左边的左边要和二棵树右边的右边镜像,一棵树左边的右边要和二棵树右边的左边镜像
  68. // 调用递归函数传入左左和右右 调用递归函数传入左右和右左
  69. // 只有左左和右右镜像且左右和右左镜像的时候,我们才能说这两棵树是镜像的
  70. return helper(root1.left, root2.right) && helper(root1.right, root2.left);
  71. }

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

  1. // 直接遍历,最好画图理解一下这个过程
  2. // 时间复杂度:O(m*n)
  3. // 空间复杂度:O(1)
  4. public int[] spiralOrder(int[][] matrix) {
  5. if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
  6. return new int[0];
  7. }
  8. // 存储当前最外层的边界,从最外层开始遍历,每遍历完最外层就对下面的参数修改
  9. int rows = matrix.length-1, cols = matrix[0].length-1, row = 0, col = 0;
  10. int size = (rows+1)*(cols+1);
  11. int[] res = new int[size];
  12. int index = 0;
  13. while(row <= rows && cols <= cols){
  14. // 上
  15. for(int i=col; i <= cols; i++){
  16. res[index++] = matrix[row][i];
  17. // 元素个数相同,则无需再遍历了,直接返回
  18. if(index == size) return res;
  19. }
  20. // 右
  21. for(int i=row+1; i<=rows; i++){
  22. res[index++] = matrix[i][cols];
  23. if(index == size) return res;
  24. }
  25. // 下
  26. for(int i=cols-1; i>=col; i--){
  27. res[index++] = matrix[rows][i];
  28. if(index == size) return res;
  29. }
  30. // 左
  31. for(int i=rows-1; i>row; i--){
  32. res[index++] = matrix[i][col];
  33. if(index == size) return res;
  34. }
  35. // 遍历完最外层就修改下面的参数,开始遍历内层
  36. row++; col++; rows--; cols--;
  37. }
  38. return res;
  39. }

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

  1. // 题目要求的是调用 min、push 及 pop 的时间复杂度都是 O(1)
  2. class MinStack {
  3. /** initialize your data structure here. */
  4. Stack<Integer> stack1, stack2;
  5. public MinStack() {
  6. // 这里建立两个栈来实现 min 为O(1)的复杂度
  7. stack1 = new Stack<>();
  8. stack2 = new Stack<>();
  9. }
  10. public void push(int x) {
  11. // 栈1存储的是正常的元素
  12. stack1.push(x);
  13. // 栈2专门存储最小元素
  14. if(stack2.isEmpty() || x <= stack2.peek()){
  15. // 设法维护好 栈B的元素,使其保持非严格降序
  16. stack2.push(x);
  17. }
  18. }
  19. public void pop() {
  20. // 栈1弹出元素的时候,若栈2的元素相同也需要弹出
  21. if(stack1.pop().equals(stack2.peek())){
  22. stack2.pop();
  23. }
  24. }
  25. public int top() {
  26. // 栈1存储的是正常的元素
  27. return stack1.peek();
  28. }
  29. public int min() {
  30. // 栈2专门存储最小元素
  31. return stack2.peek();
  32. }
  33. }

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

  1. // 复杂度分析:
  2. // 时间:O(n)
  3. // 空间:O(n)
  4. public boolean validateStackSequences(int[] pushed, int[] popped) {
  5. // Stack<Integer> stack = new Stack<>();
  6. // Java里面所有和栈相关的操作都应该用Deque,避免使用Stack
  7. Deque<Integer> stack = new LinkedList<>();
  8. // 判断合不合法,用个栈试一试
  9. int pop_index = 0;
  10. for(int i=0; i<pushed.length; i++){
  11. // 压栈是顺序是固定的
  12. stack.push(pushed[i]);
  13. // 当栈顶的元素和此时出栈序列的元素相同时,则出栈
  14. while(!stack.isEmpty() && stack.peek() == popped[pop_index]){
  15. stack.pop();
  16. pop_index ++;
  17. }
  18. }
  19. // 最后更具栈是否为空来判断出栈序列是否合法
  20. if(stack.isEmpty()){
  21. return true;
  22. }else{
  23. return false;
  24. }
  25. }

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

  1. // 标准的层序遍历
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n)
  4. public int[] levelOrder(TreeNode root) {
  5. if(root == null){
  6. return new int[0];
  7. }
  8. List<Integer> res = new ArrayList<>();
  9. Queue<TreeNode> queue = new LinkedList<>();
  10. queue.offer(root);
  11. while(!queue.isEmpty()){
  12. int size = queue.size();
  13. for(int i=0; i<size; i++){
  14. TreeNode node = queue.poll();
  15. res.add(node.val);
  16. if(node.left != null){
  17. queue.offer(node.left);
  18. }
  19. if(node.right != null){
  20. queue.offer(node.right);
  21. }
  22. }
  23. }
  24. int[] ans = new int[res.size()];
  25. for(int i=0; i<res.size(); i++){
  26. ans[i] = res.get(i);
  27. }
  28. return ans;
  29. }

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

  1. // 和上一题一样的层序遍历
  2. public List<List<Integer>> levelOrder(TreeNode root) {
  3. List<List<Integer>> res = new LinkedList<>();
  4. if(root == null){
  5. return res;
  6. }
  7. Queue<TreeNode> queue = new LinkedList<>();
  8. queue.offer(root);
  9. while(!queue.isEmpty()){
  10. int size = queue.size();
  11. List<Integer> oneLayer = new LinkedList<>();
  12. for(int i=0; i<size; i++){
  13. TreeNode node = queue.poll();
  14. oneLayer.add(node.val);
  15. if(node.left != null){
  16. queue.offer(node.left);
  17. }
  18. if(node.right != null){
  19. queue.offer(node.right);
  20. }
  21. }
  22. res.add(oneLayer);
  23. }
  24. return res;
  25. }

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

  1. // 方法1:bfs
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n)
  4. public List<List<Integer>> levelOrder(TreeNode root) {
  5. List<List<Integer>> res = new LinkedList<>();
  6. if(root == null){
  7. return res;
  8. }
  9. Queue<TreeNode> queue = new LinkedList<>();
  10. queue.offer(root);
  11. boolean flag = true;
  12. while(!queue.isEmpty()){
  13. int size = queue.size();
  14. List<Integer> oneLayer = new LinkedList<>();
  15. for(int i=0; i<size; i++){
  16. TreeNode node = queue.poll();
  17. oneLayer.add(node.val);
  18. if(node.left != null){
  19. queue.offer(node.left);
  20. }
  21. if(node.right != null){
  22. queue.offer(node.right);
  23. }
  24. }
  25. // 区分一下层,做到之字形的效果
  26. if(flag){
  27. res.add(oneLayer);
  28. }else{
  29. List<Integer> tmp = new LinkedList<>();
  30. for(int i=oneLayer.size()-1; i>=0; i--){
  31. tmp.add(oneLayer.get(i));
  32. }
  33. res.add(tmp);
  34. }
  35. flag = !flag;
  36. }
  37. return res;
  38. }
  39. // 方法2:双端队列
  40. // 时间复杂度:O(n)
  41. // 空间复杂度:O(n)
  42. public List<List<Integer>> levelOrder(TreeNode root) {
  43. List<List<Integer>> res = new LinkedList<>();
  44. if(root == null){
  45. return res;
  46. }
  47. Queue<TreeNode> queue = new LinkedList<>();
  48. queue.offer(root);
  49. boolean flag = true;
  50. while(!queue.isEmpty()){
  51. int size = queue.size();
  52. // 使用双端队列
  53. LinkedList<Integer> oneLayer = new LinkedList<>();
  54. for(int i=0; i<size; i++){
  55. TreeNode node = queue.poll();
  56. // 在这里分辨层
  57. if(flag){
  58. // add == addLast 添加到队尾
  59. oneLayer.add(node.val);
  60. }else{
  61. oneLayer.addFirst(node.val);
  62. }
  63. if(node.left != null){
  64. queue.offer(node.left);
  65. }
  66. if(node.right != null){
  67. queue.offer(node.right);
  68. }
  69. }
  70. res.add(oneLayer);
  71. flag = !flag;
  72. }
  73. return res;
  74. }

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

  1. // dfs递归解题:后续遍历有 [[root.left][root.right]root]
  2. // 时间复杂度:O(n^2) 递归深度n,递归函数中还要进行左右子树的判断
  3. // 空间复杂度:O(n) n为数的节点个数,递归的深度
  4. public boolean verifyPostorder(int[] postorder) {
  5. return dfs(postorder, 0, postorder.length-1);
  6. }
  7. private boolean dfs(int[] postorder, int s, int e){
  8. if(s>=e){
  9. // 单节点
  10. return true;
  11. }
  12. // 找到二叉搜索树的根节点
  13. int root = postorder[e];
  14. // 左子树树的跨度
  15. int left_index = s;
  16. while(postorder[left_index] < root) left_index++;
  17. // 右子树的跨度
  18. int right_index = left_index;
  19. while(postorder[right_index] > root) right_index++;
  20. // 判断:最终左子树+右子树的长度是否满足二叉搜索树
  21. if(right_index != e){
  22. return false;
  23. }
  24. // 递归判断左子树是否有效
  25. boolean left = dfs(postorder, s, left_index-1);
  26. // 递归判断右子树是否有效
  27. boolean right = dfs(postorder, left_index, e-1);
  28. return left && right;
  29. }

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

  1. // 正常的dfs/回溯思想
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n)
  4. List<List<Integer>> res;
  5. public List<List<Integer>> pathSum(TreeNode root, int sum) {
  6. res = new LinkedList<>();
  7. if(root == null){
  8. return res;
  9. }
  10. List<Integer> path = new LinkedList<>();
  11. dfs(root, sum, path, 0);
  12. return res;
  13. }
  14. private void dfs(TreeNode root, int sum, List<Integer> path, int target){
  15. if(root==null){
  16. return;
  17. }
  18. // 叶子节点
  19. if(root.left == null && root.right == null){
  20. if(target+root.val == sum){
  21. // 满足一个路径
  22. path.add(root.val);
  23. List<Integer> tmp = new LinkedList(path);
  24. res.add(tmp);
  25. path.remove(path.size()-1);
  26. }
  27. return;
  28. }
  29. // 非叶子节点
  30. target += root.val;
  31. path.add(root.val);
  32. dfs(root.left, sum, path, target);
  33. dfs(root.right, sum, path, target);
  34. path.remove(path.size() - 1);
  35. }

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

  1. // 方法1:建立一个hash表,两次遍历完成复制
  2. // 时间复杂度:O(2n)
  3. // 空间复杂度:O(2n)
  4. public Node copyRandomList(Node head) {
  5. // 虚拟头
  6. Node ans = new Node(-1);
  7. ans.next = head;
  8. Node tmp;
  9. // node1: 原节点 node2:新节点
  10. HashMap<Node, Node> hashMap = new HashMap<>();
  11. // 第一次遍历链表,记录节点
  12. while(head != null){
  13. tmp = new Node(head.val);
  14. hashMap.put(head, tmp);
  15. head = head.next;
  16. }
  17. // 在第二次遍历,完成新链表的指向操作
  18. head = ans.next;
  19. while(head != null){
  20. // 拿出新的节点
  21. tmp = hashMap.get(head);
  22. // next指针的指向
  23. tmp.next = hashMap.get(head.next);
  24. // 随机指针的指向
  25. tmp.random = hashMap.get(head.random);
  26. head = head.next;
  27. }
  28. return hashMap.get(ans.next);
  29. }
  30. // 方法2:原地操作,出去必要的输出占用,只占用常量级别的空间
  31. // 时间复杂度:O(n)
  32. // 空间复杂度:O(1) 除去必要的输出
  33. public Node copyRandomList(Node head) {
  34. if(head == null){
  35. return head;
  36. }
  37. // 虚拟头
  38. Node ans = new Node(-1);
  39. ans.next = head;
  40. // 将拷贝节点放到原节点后面
  41. // 例如1->2->3这样的链表就变成了这样1->1'->2->2'->3->3'
  42. while(head != null){
  43. Node tmp = new Node(head.val);
  44. tmp.next = head.next;
  45. head.next = tmp;
  46. head = tmp.next;
  47. }
  48. // 把拷贝节点的random指针安排上
  49. head = ans.next;
  50. while(head != null){
  51. if(head.random != null){
  52. head.next.random = head.random.next;
  53. }
  54. head = head.next.next;
  55. }
  56. // 分离拷贝节点和原节点,变成1->2->3和1'->2'->3'两个链表,后者就是答案
  57. head = ans.next;
  58. Node res = new Node(-1); // 需要返回的虚拟头
  59. res.next = head.next;
  60. while(head != null){
  61. Node tmp = head.next.next;
  62. if (tmp == null){
  63. // 到末尾了,但还是要回复原链表最后一个节点指向的null
  64. head.next = tmp;
  65. break;
  66. }
  67. // 修改新链表的指向
  68. head.next.next = tmp.next;
  69. // 恢复原链表的指向
  70. head.next = tmp;
  71. // 向后遍历原连表,继续断开接上
  72. head = tmp;
  73. }
  74. return res.next;
  75. }

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

  1. // 复杂度分析:
  2. // 时间:O(n)
  3. // 空间:O(n)
  4. Node head, pre;
  5. public Node treeToDoublyList(Node root) {
  6. if(root == null) return root;
  7. // dfs递归修改指向
  8. dfs(root);
  9. // 进行头节点和尾节点的相互指向
  10. head.left = pre;
  11. pre.right = head;
  12. return head;
  13. }
  14. private void dfs(Node root){
  15. if(root==null) return;
  16. // 递归遍历树的左节点
  17. dfs(root.left);
  18. // 二叉搜索树中序遍历是有序的,处理中间节点,左边的比你小,右边的比你大
  19. if(pre == null){
  20. // pre用于记录双向链表中位于root左侧的节点,即上一次迭代中的root,当pre==null时,root左侧没有节点,即此时root为双向链表中的头节点
  21. // 第一次遍历到最左节点,记录此时的root为head
  22. head = root;
  23. }else{
  24. // 反之,pre!=null时,root左侧存在节点pre,需要进行pre.right=root的操作。
  25. pre.right = root;
  26. }
  27. // 这里只需要指明left即可
  28. root.left = pre;
  29. // 更新pre
  30. pre = root;
  31. // 递归遍历树的右节点
  32. dfs(root.right);
  33. }

剑指 Offer 37. 序列化二叉树

  1. // 复杂度分析:
  2. // 序列化: 层序遍历时间,空间复杂度都为O(n)
  3. // 反序列化: 还是遍历的所有的节点且用到了队列,空间复杂度都为O(n)
  4. public class Codec {
  5. // Encodes a tree to a single string.
  6. public String serialize(TreeNode root) {
  7. StringBuilder sb = new StringBuilder();
  8. if(root == null){
  9. return null;
  10. }
  11. sb.append("[");
  12. // 层序遍历
  13. Queue<TreeNode> queue = new LinkedList<>();
  14. queue.offer(root);
  15. while(!queue.isEmpty()){
  16. int size = queue.size();
  17. for(int i=0; i<size; i++){
  18. TreeNode node = queue.poll();
  19. if(node == null){
  20. sb.append("null,");
  21. continue;
  22. }else{
  23. sb.append(node.val + ",");
  24. }
  25. queue.offer(node.left);
  26. queue.offer(node.right);
  27. }
  28. }
  29. String substring = sb.substring(0, sb.length() - 1);
  30. substring = substring + "]";
  31. return substring;
  32. }
  33. // Decodes your encoded data to tree.
  34. public TreeNode deserialize(String data) {
  35. if(data == null){
  36. return null;
  37. }
  38. String s_data = data.substring(1, data.length() - 1);
  39. String[] datas = s_data.split(",");
  40. Queue<TreeNode> queue = new ArrayDeque<>();
  41. TreeNode root = new TreeNode(Integer.parseInt(datas[0]));
  42. queue.offer(root);
  43. // 第一次做的时候用了两个指针,其实一个指针就行了,更加简洁明了
  44. int index = 1;
  45. while(!queue.isEmpty()){
  46. TreeNode node = queue.poll();
  47. if(!datas[index].equals("null")){
  48. node.left = new TreeNode(Integer.parseInt(datas[index]));
  49. queue.offer(node.left);
  50. }
  51. index++;
  52. if(!datas[index].equals("null")){
  53. node.right = new TreeNode(Integer.parseInt(datas[index]));
  54. queue.offer(node.right);
  55. }
  56. index++;
  57. }
  58. return root;
  59. }
  60. }
  61. // Your Codec object will be instantiated and called as such:
  62. // Codec codec = new Codec();
  63. // codec.deserialize(codec.serialize(root));

剑指 Offer 38. 字符串的排列

  1. // 一个经典的回溯问题
  2. // 时间复杂度:O(n!) n为字符串长度,n*(n-1)*(n-2)*...*1
  3. // 空间复杂度:O(n^2)
  4. List<String> res = new LinkedList<>();
  5. boolean[] visited;
  6. public String[] permutation(String s) {
  7. char[] chars = s.toCharArray();
  8. // 排序一下后面好剪枝
  9. Arrays.sort(chars);
  10. StringBuilder track = new StringBuilder();
  11. visited = new boolean[s.length()];
  12. backtrack(chars, track);
  13. // 输出
  14. return res.toArray(new String[res.size()]);
  15. }
  16. private void backtrack(char[] chars, StringBuilder track){
  17. // 触发结束条件
  18. if(track.length() == chars.length){
  19. res.add(track.toString());
  20. return;
  21. }
  22. for(int i=0; i<chars.length; i++){
  23. // 排除不合法的选择,当有重复的字符串出现时,不允许先访问后面再访问前面
  24. if(visited[i] || (i>0 && chars[i] == chars[i-1] && !visited[i-1])){
  25. continue;
  26. }
  27. // 前序:做选择
  28. visited[i] = true;
  29. track.append(chars[i]);
  30. // 进入下一层决策树
  31. backtrack(chars, track);
  32. // 后序:取消选择
  33. track.deleteCharAt(track.length() - 1);
  34. visited[i] = false;
  35. }
  36. }

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

  1. // 方法1:直接hash表
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n/2)
  4. public int majorityElement(int[] nums) {
  5. Map<Integer, Integer> map = new HashMap<>();
  6. for(int i=0; i<= nums.length; i++){
  7. map.put(Integer.valueOf(nums[i]), map.getOrDefault(Integer.valueOf(nums[i]), 0) + 1);
  8. if(map.get(Integer.valueOf(nums[i])) > nums.length/2){
  9. return nums[i];
  10. }
  11. }
  12. return -1;
  13. }
  14. // 解法2:排序取中位数
  15. // 排序算法时间复杂度O(nlogn),
  16. // 空间复杂度O(1)
  17. public int majorityElement(int[] nums) {
  18. Arrays.sort(nums);
  19. return nums[nums.length/2];
  20. }
  21. // 解法3:摩尔投票法
  22. // 也可以理解成混战极限一换一,不同的两者一旦遇见就同归于尽,最后活下来的值都是相同的,即要求的结果
  23. // 时间复杂度O(n),空间复杂度O(1)
  24. public int majorityElement(int[] nums) {
  25. int res = 0, count = 0;
  26. for(int i=0; i<nums.length; i++){
  27. if(count == 0){
  28. // 假设当前的数为众数,给你投票后续让你去火拼
  29. res = nums[i];
  30. count++;
  31. }else{
  32. if(res == nums[i]) count++; // 票数增加
  33. else count--; // 抵消
  34. }
  35. }
  36. // 最终一定是票数大于一半的获胜
  37. return res;
  38. }

剑指 Offer 40. 最小的k个数

  1. // 方法1:固定堆:大顶堆,固定一个大小为k的大顶堆可以快速求出第k小的数
  2. // 时间复杂度: O(nlog k)
  3. // 空间复杂度:O(k)
  4. public int[] getLeastNumbers(int[] arr, int k) {
  5. if(arr.length <= 0 || k == 0){
  6. return new int[0];
  7. }
  8. // 建立大顶堆:固定大小为k,
  9. PriorityQueue<Integer> maxHeap = new PriorityQueue<>(3, (a, b) -> b-a);
  10. for(int i=0; i<arr.length; i++){
  11. if(maxHeap.size() < k){
  12. maxHeap.offer(arr[i]);
  13. continue;
  14. }
  15. if(maxHeap.peek() > arr[i]){
  16. maxHeap.poll();
  17. maxHeap.offer(arr[i]);
  18. }
  19. }
  20. int size = maxHeap.size();
  21. int[] res = new int[size];
  22. for(int i=0; i<size; i++){
  23. res[i] = maxHeap.poll();
  24. }
  25. return res;
  26. }
  27. // 方法2:快排,用了https://www.cnblogs.com/zhuchengchao/p/14403781.html中快排的代码
  28. // 时间复杂度:根据已经分好的数组与k比较,其实能到O(k)的
  29. // 空间复杂度:原地排序O(1)
  30. public int[] getLeastNumbers(int[] arr, int k) {
  31. if(arr.length == 0 || k == 0){
  32. return new int[0];
  33. }
  34. // 最后一个参数表示我们要找的是下标为k-1的数
  35. return quickSort(arr, 0, arr.length-1, k-1);
  36. }
  37. private int[] quickSort(int[] nums, int start, int end, int k){
  38. int j = partition(nums, start, end);
  39. if(k == j){
  40. // 已经到达个数了 返回即可
  41. return Arrays.copyOf(nums, j + 1);
  42. }
  43. // 优化在此,根据下标j与k的大小关系来决定继续切分左段还是右段
  44. if (j > k){
  45. // 对分区左侧进行快排
  46. return quickSort(nums, start, j-1, k);
  47. }else{
  48. // 对分区右侧进行快排
  49. return quickSort(nums, j+1, end, k);
  50. }
  51. }
  52. // 快排切分,返回下标pivotIndex
  53. // 使得比nums[pivotIndex]小的数都在pivotIndex的左边,比nums[pivotIndex]大的数都在pivotIndex的右边。
  54. private static int partition(int[] nums, int begin, int end){
  55. // 默认数组中待分区区间的最后一个是 pivot 元素
  56. int pivot = nums[end];
  57. // 定义分区后 pivot 元素的下标
  58. int pivotIndex = begin;
  59. for(int i=begin; i<end; i++){
  60. // 判断如果该区间内如果有元素小于 pivot 则将该元素从区间头开始一直向后填充 有点类似选择排序
  61. if(nums[i] < pivot){
  62. if(i > pivotIndex){
  63. // 交换元素
  64. swap(nums, i, pivotIndex);
  65. }
  66. pivotIndex++;
  67. }
  68. }
  69. swap(nums, pivotIndex, end);
  70. return pivotIndex;
  71. }
  72. // 交换数组内下标为 i j 的两个元素
  73. private static void swap(int[] nums,int i,int j){
  74. int temp = nums[j];
  75. nums[j] = nums[i];
  76. nums[i] = temp;
  77. }
  78. // 方法3:数据范围有限时可以用计数排序:O(N)
  79. public int[] getLeastNumbers(int[] arr, int k) {
  80. if(k == 0 || arr.length == 0){
  81. return new int[0];
  82. }
  83. // 统计每个数字出现的次数
  84. // arr[i]的范围是0~10000
  85. int[] counter = new int[10001];
  86. for(int num: arr){
  87. counter[num] ++;
  88. }
  89. // 根据counter数组从头找出k个数作为返回结果
  90. int[] res = new int[k];
  91. int index = 0;
  92. for(int i=0; i<counter.length; i++){
  93. while(counter[i]-- > 0 && index < k){
  94. res[index++] = i;
  95. }
  96. if(index == k){
  97. break;
  98. }
  99. }
  100. return res;
  101. }

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

  1. class MedianFinder {
  2. // 建立两个堆,一个小顶堆 一个大顶堆
  3. private Queue<Integer> minHeap, maxHeap;
  4. /** initialize your data structure here. */
  5. public MedianFinder() {
  6. // 建立两个堆,一个大顶堆,一个小顶堆,只需要平衡两者的大小即可
  7. // 大顶堆中放入 (n+1)/2 个小元素,栈顶就是第(n+1)/2小的元素
  8. // 小顶堆中放入 (n+1)/2 个大元素,栈顶就是第(n+1)/2大的元素
  9. minHeap = new PriorityQueue<>();
  10. maxHeap = new PriorityQueue<>((a, b) -> b-a);
  11. }
  12. public void addNum(int num) {
  13. // 两个堆之间的元素是需要平衡的
  14. // 保证:大顶堆中的元素个数 >= 小顶堆中的元素个数,且最多相差一个元素
  15. maxHeap.offer(num);
  16. minHeap.offer(maxHeap.poll());
  17. if(maxHeap.size() < minHeap.size()){
  18. maxHeap.offer(minHeap.poll());
  19. }
  20. }
  21. public double findMedian() {
  22. if(maxHeap.size() == minHeap.size()){
  23. // 偶数情况下的中位数
  24. return (maxHeap.peek() + minHeap.peek()) / 2.0;
  25. }else{
  26. // 计数情况下的中位数
  27. return maxHeap.peek();
  28. }
  29. }
  30. }

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

  1. // 经典动态规划
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n),是可以降为O(1)的,将dp数组修改为两个变量即可
  4. public int maxSubArray(int[] nums) {
  5. // 明确状态,dp[i]在包含第nums[i]时的最大情况
  6. int[] dp = new int[nums.length + 1];
  7. // base case
  8. dp[0] = 0;
  9. int res = nums[0];
  10. // 状态转移
  11. for(int i=1; i<=nums.length; i++){
  12. // 继承 / 另辟蹊径
  13. dp[i] = Math.max(nums[i-1], dp[i-1] + nums[i-1]);
  14. res = Math.max(res, dp[i]);
  15. }
  16. return res;
  17. }

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

这种题目真的很烦人,直接看答案+CV的;

这题其实也是动态规划类型的题目吧,解答中是有明确的状态转移方程的

  1. // 参考了题解中:xujunyi的答案
  2. public int countDigitOne(int n) {
  3. return helper(n);
  4. }
  5. private int helper(int n){
  6. if(n <= 0){
  7. return 0;
  8. }
  9. String s = String.valueOf(n);
  10. int high = s.charAt(0) - '0';
  11. int pow = (int) Math.pow(10, s.length() - 1);
  12. int last = n - high*pow;
  13. if(high == 1){
  14. return helper(pow-1) + (last+1) + helper(last);
  15. }else{
  16. return pow + high*helper(pow-1) + helper(last);
  17. }
  18. }

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

先空着吧

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

  1. // 这个排序很巧妙,万万没想到还能这么玩
  2. // 时间复杂度:O(NlogN)
  3. // 空间复杂度:O(n), strs需要额外的空间
  4. public String minNumber(int[] nums) {
  5. String[] strs = new String[nums.length];
  6. for(int i=0; i<nums.length; i++){
  7. strs[i] = String.valueOf(nums[i]);
  8. }
  9. // 对string组成的字符串进行排序,是从小到大的排序顺序
  10. // 例如:"30" + "3" < "3" + "30"
  11. // 则,把 “30” 放到 “3" 前面
  12. Arrays.sort(strs, (o1, o2) -> (o1+o2).compareTo(o2+o1));
  13. StringBuilder res = new StringBuilder();
  14. for(String s: strs){
  15. res.append(s);
  16. }
  17. return res.toString();
  18. }

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

  1. // 动态规划解题
  2. // 时间空间复杂度都为O(n)
  3. public int translateNum(int num) {
  4. if(num == 0){
  5. // 特殊处理一下
  6. return 1;
  7. }
  8. // 把num修改为nums数组,便于后续操作
  9. List<Integer> lists = new ArrayList<>();
  10. while(num != 0){
  11. lists.add(num%10);
  12. num /= 10;
  13. }
  14. int[] nums = new int[lists.size()];
  15. for(int i=0; i<lists.size(); i++){
  16. nums[i] = lists.get(lists.size()-i-1);
  17. }
  18. int n = nums.length;
  19. // 明确状态: dp[i] 当有i个字符时可能的组合数
  20. int[] dp = new int[n+1];
  21. // base case
  22. dp[0] = 1;
  23. if(nums[0] >=0 && nums[0] <= 25){
  24. dp[1] = 1;
  25. }else{
  26. return 0;
  27. }
  28. // 开始状态转移
  29. for(int i=2; i<=n; i++){
  30. // 当前的数字能否转换成字符
  31. int one = nums[i-1];
  32. if(one >=0 && one <= 25){
  33. dp[i] += dp[i-1];
  34. }
  35. // 当前的字符和上一个字符能否转换成字符
  36. int two = nums[i-2] * 10 + nums[i-1];
  37. if(nums[i-2] != 0 && two >=0 && two <= 25){
  38. dp[i] += dp[i-2];
  39. }
  40. }
  41. return dp[n];
  42. }

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

  1. // 基础的动态规划题
  2. // 时间/空间复杂度:O(m*n)
  3. public int maxValue(int[][] grid) {
  4. int m = grid.length;
  5. int n = grid[0].length;
  6. // 明确状态: dp[i][j] 就是在ij位置时的最大礼物价值
  7. int[][] dp = new int[m][n];
  8. // base case: 只有一行/只有一列的情况下
  9. dp[0][0] = grid[0][0];
  10. for(int i=1; i<m; i++){
  11. dp[i][0] = dp[i-1][0] + grid[i][0];
  12. }
  13. for(int j=1; j<n; j++){
  14. dp[0][j] = dp[0][j-1] + grid[0][j];
  15. }
  16. // 开始状态转移
  17. for(int i=1; i<m; i++){
  18. for(int j=1; j<n; j++){
  19. // 状态转移方程
  20. dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + grid[i][j];
  21. }
  22. }
  23. return dp[m-1][n-1];
  24. }

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

  1. // 滑动窗口解题
  2. // 时间/空间复杂度:O(n)
  3. public int lengthOfLongestSubstring(String s) {
  4. // 定义窗口,窗口中包含了出现过的字符
  5. Map<Character, Integer> window = new HashMap<>();
  6. int left = 0, right = 0, len = 0;
  7. while(right < s.length()){
  8. // c 是将移入窗口的字符
  9. char c = s.charAt(right);
  10. // 进行窗口内数据的一系列更新
  11. if(window.getOrDefault(c, 0) < 1){
  12. // 没有重复的时候的情况:右移窗口
  13. right++;
  14. window.put(c, window.getOrDefault(c, 0)+1);
  15. // 动态获取最长长度
  16. if(right - left > len){
  17. len = right - left;
  18. }
  19. }else{
  20. // 一旦出现重复的时候:开始移左窗口
  21. char d = s.charAt(left);
  22. left++;
  23. window.put(d, window.get(d) - 1);
  24. }
  25. }
  26. return len;
  27. }

剑指 Offer 49. 丑数

  1. // 方法1:直接上个小顶堆完事
  2. // 空间复杂度:O(3n)
  3. // 时间复杂度:O(nlogn)
  4. public int nthUglyNumber(int n) {
  5. // 建立一个最小堆,每次都从堆中弹出最小的那个元素 * 2 3 5后入堆
  6. PriorityQueue<Long> minHeap = new PriorityQueue<>();
  7. // int有案例过不了
  8. long res = 1;
  9. minHeap.offer(res);
  10. for(int i=0; i<n; i++){
  11. res = minHeap.poll();
  12. while(!minHeap.isEmpty() && res == minHeap.peek()){
  13. // 为了删除重复的元素,如2*3 3*2 就重复了
  14. minHeap.poll();
  15. }
  16. minHeap.offer(res*2);
  17. minHeap.offer(res*3);
  18. minHeap.offer(res*5);
  19. }
  20. return (int)res;
  21. }
  22. // 方法2:三指针法
  23. // 空间复杂度:O(n)
  24. // 时间复杂度:O(1)
  25. public int nthUglyNumber(int n) {
  26. int[] reuslt = new int[n];
  27. reuslt[0] = 1;
  28. // 定义三个指针
  29. int p1=0, p2=0, p3=0;
  30. for(int i=1; i<n; i++){
  31. reuslt[i] = Math.min(reuslt[p1]*2, Math.min(reuslt[p2]*3, reuslt[p3]*5));
  32. if(reuslt[i] == reuslt[p1]*2) p1++;
  33. if(reuslt[i] == reuslt[p2]*3) p2++;
  34. if(reuslt[i] == reuslt[p3]*5) p3++;
  35. }
  36. return reuslt[n-1];
  37. }

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

  1. // 方法1:好家伙我直接就是hash表
  2. // 时间复杂度:O(2n)
  3. // 空间复杂度:O(n)
  4. public char firstUniqChar(String s) {
  5. Map<Character, Integer> map = new HashMap<>();
  6. for(char c: s.toCharArray()){
  7. map.put(c, map.getOrDefault(c, 0) + 1);
  8. }
  9. for(char c: s.toCharArray()){
  10. if(map.get(c) == 1){
  11. return c;
  12. }
  13. }
  14. return ' ';
  15. }
  16. // 方法2:优化方式1:
  17. // 时间复杂度:O(2n)
  18. // 空间复杂度:O(26)
  19. public char firstUniqChar(String s) {
  20. if (s.equals("")) return ' ';
  21. // 创建‘a'-'z'的字典
  22. int[] target = new int[26];
  23. // 第一次遍历,将字符统计到字典数组
  24. for (int i = 0; i < s.length(); i++) {
  25. target[s.charAt(i) - 'a']++;
  26. }
  27. // 第二次遍历,从字典数组获取次数
  28. for (int i = 0; i < s.length(); i++) {
  29. if (target[s.charAt(i) - 'a'] == 1) return s.charAt(i);
  30. }
  31. return ' ';
  32. }
  33. // 优化方式2:
  34. // 时间复杂度:O(2n)
  35. // 空间复杂度:O(26)
  36. public char firstUniqChar(String s) {
  37. // key:字符 value:标记其是否出现过
  38. Map<Character, Boolean> dic = new LinkedHashMap<>();
  39. char[] sc = s.toCharArray();
  40. for(char c : sc){
  41. // 这里的逻辑很巧妙,只要出现过2次及以上就是false
  42. dic.put(c, !dic.containsKey(c));
  43. }
  44. for(Map.Entry<Character, Boolean> d : dic.entrySet()){
  45. if(d.getValue()) return d.getKey();
  46. }
  47. return ' ';
  48. }

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

就很烦

  1. // 回溯:组合问题 虽然超出时间限制,但还是贴一下吧
  2. // 时间复杂度:O(n^2)
  3. // 空间复杂度:O(n^2), 应...该递归的深度和时间复杂度差不多的吧...
  4. int count = 0;
  5. public int reversePairs(int[] nums) {
  6. if(nums.length <= 1){
  7. return 0;
  8. }
  9. List<Integer> track = new LinkedList<>();
  10. backtrack(nums, track, 0);
  11. return count;
  12. }
  13. private void backtrack(int[] nums, List<Integer> track, int index){
  14. if(track.size() == 2){
  15. if(track.get(0) > track.get(1)){
  16. // 满足逆序的条件
  17. count++;
  18. }
  19. return;
  20. }
  21. for(int i=index; i<nums.length; i++){
  22. // 剪枝
  23. if(index>0 && nums[index-1] < nums[i]){
  24. continue;
  25. }
  26. track.add(nums[i]);
  27. backtrack(nums, track, i+1);
  28. track.remove(track.size() - 1);
  29. }
  30. }
  31. // 正确解法:
  32. // 分治算法:归并排序
  33. // 时间复杂度:O(nlogn), 归并排序的时间复杂度
  34. // 空间复杂度:O(n)
  35. int count = 0;
  36. public int reversePairs(int[] nums) {
  37. mergeSort(nums);
  38. return count;
  39. }
  40. private int[] mergeSort(int[] arr){
  41. if(arr.length < 2){
  42. return arr;
  43. }
  44. // 将数组从中间拆分成左右两部分
  45. int mid = arr.length/2;
  46. int[] left = Arrays.copyOfRange(arr, 0, mid);
  47. int[] right = Arrays.copyOfRange(arr, mid, arr.length);
  48. return merge(mergeSort(left), mergeSort(right));
  49. }
  50. // 合并两个有序数组并返回新的数组
  51. private int[] merge(int[] left, int[] right){
  52. // 创建一个新数组,长度为两个有序数组的长度之和
  53. int[] newArray = new int[left.length+right.length];
  54. // 定义两个指针,分别代表两个数组的下标
  55. int lindex = 0;
  56. int rindex = 0;
  57. for(int i=0; i<newArray.length;i++){
  58. // 归并的过程
  59. if(lindex >= left.length){
  60. newArray[i] = right[rindex++];
  61. }else if(rindex >= right.length){
  62. newArray[i] = left[lindex++];
  63. }else if(left[lindex] > right[rindex]){
  64. newArray[i] = right[rindex++];
  65. // ☆☆☆只有这里有区别☆☆☆
  66. // 当左边的数组大于时右边的值时,表示左区间lindex及之后的数都将大于rindex指向的数,所以出现了left.length - lindex个逆序对
  67. count += left.length - lindex;
  68. }else{
  69. newArray[i] = left[lindex++];
  70. }
  71. }
  72. return newArray;
  73. }

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

  1. // 方法1:暴力解法:hash表 走一波
  2. // 空间时间复杂度都是 O(n)
  3. public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
  4. Set<ListNode> set = new HashSet<>();
  5. while(headA != null){
  6. set.add(headA);
  7. headA = headA.next;
  8. }
  9. while(headB != null){
  10. if(set.contains(headB)){
  11. return headB;
  12. }
  13. headB = headB.next;
  14. }
  15. return null;
  16. }
  17. // 方法2:双指针法,浪漫相遇,看呆了
  18. // 贴一个骚评论:两个结点不断的去对方的轨迹中寻找对方的身影,只要二人有交集,就终会相遇
  19. // 时间复杂度:O(n)
  20. // 空间复杂度:O(1)
  21. public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
  22. ListNode h1 = headA, h2 = headB;
  23. while(h1 != h2){
  24. h1 = h1 == null ? headB : h1.next;
  25. h2 = h2 == null ? headA : h2.next;
  26. }
  27. return h1;
  28. }

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

  1. // 方法1:暴力法
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1)
  4. public int search(int[] nums, int target) {
  5. int res = 0;
  6. for(int i=0; i<nums.length; i++){
  7. if(nums[i] == target){
  8. res ++;
  9. }
  10. }
  11. return res;
  12. }
  13. // 方法2:二分法所搜左右边界,主要给了这个数组是排序的这个条件
  14. // 时间复杂度:O(logn)
  15. // 空间复杂度:O(1)
  16. public int search(int[] nums, int target) {
  17. int left_index = 0, right_index = 0;
  18. // 找左边界
  19. int left = 0, right = nums.length-1;
  20. while(left <= right){
  21. int mid = left + (right-left)/2;
  22. if(nums[mid] < target){
  23. left = mid+1;
  24. }else if(nums[mid] > target){
  25. right = mid-1;
  26. }else if(nums[mid] == target){
  27. // 别返回,锁定左侧边界
  28. right = mid - 1;
  29. }
  30. }
  31. // 最后要检查 left 越界的情况
  32. if (left >= nums.length || nums[left] != target)
  33. return 0;
  34. left_index = left;
  35. // 找右侧边界
  36. left = 0;
  37. right = nums.length - 1;
  38. while (left <= right) {
  39. int mid = left + (right - left) / 2;
  40. if (nums[mid] < target) {
  41. left = mid + 1;
  42. } else if (nums[mid] > target) {
  43. right = mid - 1;
  44. } else if (nums[mid] == target) {
  45. // 别返回,锁定右侧边界
  46. left = mid + 1;
  47. }
  48. }
  49. // 最后要检查 right 越界的情况
  50. if (right < 0 || nums[right] != target)
  51. return 0;
  52. right_index = right;
  53. // 最后返回长度即可
  54. return right_index - left_index + 1;
  55. }

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

  1. // 方法1:暴力法,顺序遍历
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1)
  4. public int missingNumber(int[] nums) {
  5. for(int i=0; i<nums.length; i++){
  6. if(nums[i] != i){
  7. return i;
  8. }
  9. }
  10. return nums[nums.length-1] + 1;
  11. }
  12. // 方法2:二分法
  13. // 时间复杂度:O(logn)
  14. // 空间复杂度:O(1)
  15. public int missingNumber(int[] nums) {
  16. int left = 0, right = nums.length - 1;
  17. while (left <= right) {
  18. int mid = left + (right - left) / 2;
  19. if(nums[mid] == mid){
  20. left = mid+1;
  21. }else{
  22. right = mid-1;
  23. }
  24. }
  25. return left;
  26. }

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

  1. // 方法1:第k大的节点:动态极值,用固定k大小的最小堆
  2. // 时间复杂度:O(n), 遍历每一个节点
  3. // 空间复杂度:O(k) 没有算递归的消耗
  4. PriorityQueue<Integer> minHeap;
  5. public int kthLargest(TreeNode root, int k) {
  6. if(root == null){
  7. return 0;
  8. }
  9. // 小顶堆k尺寸,找第K大的值
  10. minHeap = new PriorityQueue<>();
  11. dfs(root, k);
  12. return minHeap.peek();
  13. }
  14. private void dfs(TreeNode root, int k){
  15. if(root == null){
  16. return;
  17. }
  18. if(minHeap.size() < k){
  19. minHeap.offer(root.val);
  20. }else if(minHeap.peek()<root.val){
  21. minHeap.poll();
  22. minHeap.offer(root.val);
  23. }
  24. dfs(root.left, k);
  25. dfs(root.right, k);
  26. }
  27. // 方法2:利用二叉搜索树的中序遍历是有序的这个性质
  28. // 时间复杂度:O(n), 遍历每一个节点
  29. // 空间复杂度:O(n)
  30. public int kthLargest(TreeNode root, int k) {
  31. List<Integer> res = new ArrayList<>();
  32. dfs(root, res);
  33. return res.get(res.size() - k);
  34. }
  35. private void dfs(TreeNode root, List<Integer> res){
  36. if(root == null){
  37. return;
  38. }
  39. dfs(root.left, res);
  40. res.add(root.val);
  41. dfs(root.right, res);
  42. }
  43. // 方法3:二叉搜索树的中序遍历优化: 逆序遍历 “右中左” 的顺序
  44. // 时间复杂度:O(k) k <= n(树节点)
  45. // 空间复杂度:O(k)
  46. int ans = 0, count=0;
  47. public int kthLargest(TreeNode root, int k) {
  48. dfs(root, k);
  49. return ans;
  50. }
  51. private void dfs(TreeNode root, int k){
  52. if(root == null){
  53. return;
  54. }
  55. dfs(root.right, k);
  56. if(++count == k){
  57. ans = root.val;
  58. return;
  59. }
  60. dfs(root.left, k);
  61. }

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

  1. // 正常的dfs即可
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1) 没有算递归的消耗
  4. public int maxDepth(TreeNode root) {
  5. if(root == null){
  6. return 0;
  7. }
  8. return dfs(root, 1);
  9. }
  10. private int dfs(TreeNode root, int layer){
  11. if(root == null){
  12. return layer-1;
  13. }
  14. int left_layer = dfs(root.left, layer+1);
  15. int right_layer = dfs(root.right, layer+1);
  16. return Math.max(left_layer, right_layer);
  17. }
  18. // 优化写法,其实可以一行代码解决的
  19. public int maxDepth(TreeNode root) {
  20. return root == null ?
  21. 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
  22. }

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

  1. // 和上一题一样,dfs计算左右子树的深度
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1) 没有算递归的消耗
  4. boolean res = true;
  5. public boolean isBalanced(TreeNode root) {
  6. if(root == null){
  7. return true;
  8. }
  9. dfs(root, 0);
  10. return res;
  11. }
  12. private int dfs(TreeNode root, int depth){
  13. if(!res){
  14. // 剪枝
  15. return -1;
  16. }
  17. if(root == null){
  18. return depth - 1;
  19. }
  20. int left_depth = dfs(root.left, depth+1);
  21. int right_depth = dfs(root.right, depth+1);
  22. if(Math.abs(left_depth - right_depth) > 1){
  23. res = false;
  24. return -1;
  25. }
  26. return Math.max(left_depth, right_depth);
  27. }

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

  1. // 方法1:排序,但是时间复杂度不满足要求
  2. // 时间复杂度:O(nlogn)
  3. // 空间复杂度:O(1)
  4. public int[] singleNumbers(int[] nums) {
  5. Arrays.sort(nums);
  6. int[] res = new int[2];
  7. int index = 0;
  8. if(nums[0] != nums[1]){
  9. res[index++] = nums[0];
  10. }
  11. if(nums[nums.length-1] != nums[nums.length-2]){
  12. res[index++] = nums[nums.length-1];
  13. }
  14. for(int i=1; i<nums.length-1; i++){
  15. if(index == 2){
  16. break;
  17. }
  18. if(nums[i] != nums[i+1] && nums[i] != nums[i-1]){
  19. res[index++] = nums[i];
  20. }
  21. }
  22. return res;
  23. }
  24. // 方法2:位运算: 参考了题解中eddieVim的解答
  25. // 时间复杂度:O(n)
  26. // 空间复杂度:O(1)
  27. public int[] singleNumbers(int[] nums) {
  28. // 用于将所有的数异或起来
  29. int k = 0;
  30. for(int num: nums) {
  31. // 相同的数都抵消掉了,只有两个不同的数,最终的为k为1的位置就是不同处
  32. k ^= num;
  33. }
  34. // 获得k中最低位的1,以此为依据将nums分为两组
  35. int mask = 1;
  36. while((k & mask) == 0) {
  37. mask <<= 1;
  38. }
  39. int a = 0;
  40. int b = 0;
  41. for(int num: nums) {
  42. // 更具mask分成了两组数据,两个不同数会被分到不同的组中
  43. // 而相同的数会被分到同一组中,并且在异或后会被抵消的即变为0
  44. // 又有0异或任何数就是该数本身
  45. if((num & mask) == 0) {
  46. // 找第一个数
  47. a ^= num;
  48. } else {
  49. // 找第二个数
  50. b ^= num;
  51. }
  52. }
  53. return new int[]{a, b};
  54. }

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

  1. // 论位运算的巧妙
  2. // 时间复杂度:O(32n)
  3. // 空间复杂度:O(1)
  4. public int singleNumber(int[] nums) {
  5. int res = 0;
  6. for(int i=0; i<32; i++){
  7. int mask = 1 << i;
  8. int cnt = 0;
  9. for(int j=0; j<nums.length; j++){
  10. if((nums[j] & mask) != 0){
  11. // 这个位置上的值是1
  12. cnt++;
  13. }
  14. }
  15. if(cnt % 3 != 0){
  16. // 一个数字出现3遍,如果这个位置是1,则%3就没了
  17. // 只有出现1次的数字若是1则能保存下来,若是0那就是0没有区别
  18. res |= mask;
  19. }
  20. }
  21. return res;
  22. }

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

  1. // 递增数列:双指针完事
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(1)
  4. public int[] twoSum(int[] nums, int target) {
  5. if(nums.length < 2){
  6. return new int[0];
  7. }
  8. int left = 0;
  9. int right = nums.length - 1;
  10. while(left < right){
  11. if(nums[left] + nums[right] == target){
  12. return new int[]{nums[left], nums[right]};
  13. }else if(nums[left] + nums[right] > target){
  14. right --;
  15. }else{
  16. left ++;
  17. }
  18. }
  19. return new int[0];
  20. }

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

  1. // 暴力前缀和数组:超出时间限制 24 / 32
  2. // 时间复杂度:O(n^2)
  3. // 空间复杂度:O(n)
  4. public int[][] findContinuousSequence(int target) {
  5. List<List<Integer>> res = new LinkedList<>();
  6. int len = target/2 + 1;
  7. // 数组
  8. int[] nums = new int[len];
  9. // 数组的前缀和
  10. int[] preSum = new int[len+1];
  11. preSum[0] = 0;
  12. for(int i=0; i<len; i++){
  13. nums[i] = i+1;
  14. preSum[i+1] = preSum[i] + nums[i];
  15. }
  16. for(int i=0; i<len; i++){
  17. for(int j=i+1; j<len; j++){
  18. if(preSum[j+1] - preSum[i] == target){
  19. List tmp = new ArrayList<>();
  20. for(int k=i; k<=j; k++){
  21. tmp.add(nums[k]);
  22. }
  23. res.add(tmp);
  24. }
  25. }
  26. }
  27. int[][] ans = new int[res.size()][];
  28. for(int i=0; i<res.size(); i++){
  29. ans[i] = new int[res.get(i).size()];
  30. for(int j=0; j<res.get(i).size(); j++){
  31. ans[i][j] = res.get(i).get(j);
  32. }
  33. }
  34. return ans;
  35. }
  36. // 正确解法:滑动窗口
  37. // 时间复杂度:O(target)
  38. // 空间复杂度:O(1)
  39. public int[][] findContinuousSequence(int target) {
  40. List<int[]> res = new LinkedList<>();
  41. int left = 1, right = 1;
  42. int sum = 0;
  43. while(right <= target/2+1){
  44. // 右移窗口
  45. sum += right;
  46. right++;
  47. while(sum > target){
  48. // 左移窗口
  49. sum -= left;
  50. left++;
  51. }
  52. if(sum == target){
  53. // 条件满足,存储结果
  54. int[] tmp = new int[right - left];
  55. for(int i=left; i<right; i++){
  56. tmp[i-left] = i;
  57. }
  58. res.add(tmp);
  59. }
  60. }
  61. int[][] ans = new int[res.size()][];
  62. for(int i=0; i<res.size(); i++){
  63. if(res.get(i).length <= 1){
  64. continue;
  65. }
  66. ans[i] = res.get(i);
  67. }
  68. return ans;
  69. }

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

  1. // 直接库函数
  2. public String reverseWords(String s) {
  3. // 删除首尾空格
  4. s = s.trim();
  5. // 直接按照空格分隔字符串
  6. String[] words = s.split(" ");
  7. StringBuilder res = new StringBuilder();
  8. for(int i=words.length-1; i>=0; i--){
  9. if(words[i].equals("")){
  10. continue;
  11. }
  12. res.append(words[i]+" ");
  13. }
  14. return res.toString().trim();
  15. }
  16. // 正常解法:双指针
  17. // 空间复杂度:O(n)
  18. // 时间复杂度:O(n)
  19. public String reverseWords(String s) {
  20. // 删除首尾空格
  21. s = s.trim();
  22. int n = s.length();
  23. StringBuilder res = new StringBuilder();
  24. int left = n-1, right = n-1, index = 0;
  25. while(left >= 0){
  26. while(left >= 0 && s.charAt(left) != ' ') left--; // 搜索首个空格
  27. res.append(s.substring(left+1, right+1)+" "); // 添加单词
  28. while(left >= 0 && s.charAt(left) == ' ') left --; // 跳过单词间空格
  29. // 更新右指针
  30. right = left;
  31. }
  32. return res.toString().trim();
  33. }

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

  1. // 暴力做法
  2. // 时间复杂度 O(n)
  3. // 空间复杂度 O(N)
  4. public String reverseLeftWords(String s, int n) {
  5. int len = s.length();
  6. char[] res = new char[len];
  7. for(int i=0; i<len; i++){
  8. if(i<n){
  9. res[len - n + i] = s.charAt(i);
  10. }else{
  11. res[i-n] = s.charAt(i);
  12. }
  13. }
  14. return new String(res);
  15. }

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

  1. // 方法1:暴力法
  2. // 时间复杂度 O(n*k)
  3. // 空间复杂度 O(1) 除去必要的输出
  4. public int[] maxSlidingWindow(int[] nums, int k) {
  5. if(nums.length == 0){
  6. return new int[0];
  7. }
  8. int left = 0, right = k-1;
  9. int res[] = new int[nums.length - k + 1];
  10. while(right < nums.length){
  11. res[left] = nums[left];
  12. for(int i=left; i<=right; i++){
  13. res[left] = Math.max(res[left], nums[i]);
  14. }
  15. left++; right++;
  16. }
  17. return res;
  18. }
  19. // 方法2:单调队列,还是需要画图理解,看的K神的讲解
  20. // 时间复杂度 O(n)
  21. // 空间复杂度 O(k)
  22. public int[] maxSlidingWindow(int[] nums, int k) {
  23. if(nums.length <= 0){
  24. return new int[0];
  25. }
  26. int[] res = new int[nums.length - k + 1];
  27. // res数组的下标
  28. int index = 0;
  29. // 单调队列
  30. Deque<Integer> deque = new ArrayDeque<>();
  31. // 未形成窗口区间
  32. for(int i=0; i<k; i++){
  33. // 队列不为空时,当前值与队列尾部值比较,如果大于,删除队列尾部值
  34. // 一直循环删除到队列中的值都大于当前值,或者删到队列为空
  35. while(!deque.isEmpty() && nums[i] > deque.peekLast()) deque.pollLast();
  36. // 执行完上面的循环后,队列中要么为空,要么值都比当前值大,然后就把当前值添加到队列中
  37. deque.addLast(nums[i]);
  38. }
  39. // 窗口区间刚形成后,把队列首位值添加到队列中
  40. // 因为窗口形成后,就需要把队列首位添加到数组中,而下面的循环是直接跳过这一步的,所以需要我们直接添加
  41. res[index++] = deque.peek();
  42. // 窗口区间形成
  43. for(int i=k; i<nums.length; i++){
  44. // i-k是已经在区间外了,如果首位等于nums[i-k],那么说明此时首位值已经不再区间内了,需要删除
  45. if(deque.peek() == nums[i-k]) deque.poll();
  46. // 删除队列中比当前值小的值
  47. while(!deque.isEmpty() && nums[i] > deque.peekLast()) deque.pollLast();
  48. // 把当前值添加到队列中
  49. deque.addLast(nums[i]);
  50. // 把队列的首位值添加到arr数组中
  51. res[index++] = deque.peek();
  52. }
  53. return res;
  54. }

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

  1. // 参考的是题解中 “腐烂的橘子” 的题解
  2. // 函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)
  3. class MaxQueue {
  4. Queue<Integer> queue; // 正常队列
  5. Deque<Integer> deque; // 存了最大值的队列,是个双端队列
  6. public MaxQueue() {
  7. queue = new LinkedList<>();
  8. deque = new LinkedList<>();
  9. }
  10. public int max_value() {
  11. if(deque.isEmpty()){
  12. return -1;
  13. }else{
  14. // 双端队列存的是最大值
  15. return deque.peek();
  16. }
  17. }
  18. public void push_back(int value) {
  19. queue.offer(value);
  20. // 队列是空的时候就直接添加
  21. if(deque.isEmpty()){
  22. deque.offer(value);
  23. return;
  24. }
  25. // 若列队不为空,则从后比较,是的队列头中存的是最大值
  26. // 即这个deque中按照递减的顺序存了queue中的部分数据
  27. while(!deque.isEmpty() && value > deque.peekLast()){
  28. deque.pollLast();
  29. }
  30. deque.offer(value);
  31. }
  32. public int pop_front() {
  33. if(queue.isEmpty()){
  34. return -1;
  35. }
  36. Integer res = queue.poll();
  37. // 当队列中的值和deque中的相同时,deque也要出队
  38. if(res.equals(deque.peek())){
  39. deque.poll();
  40. }
  41. return res;
  42. }
  43. }

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

  1. // 动态规划解题
  2. public double[] dicesProbability(int n) {
  3. // 明确状态:dp[i][j] 当前有i个骰子时,出现j点数的次数
  4. int[][] dp = new int[n+1][6*n+1];
  5. // base case:只有一个骰子的情况,每种可能都是1次
  6. for(int j=1; j<=6; j++){
  7. dp[1][j] = 1;
  8. }
  9. // 开始状态转移
  10. for(int i=2; i<=n; i++){ // 骰子个数的状态转移
  11. for(int j=i; j<=6*i; j++){ // 出现点数j的状态
  12. for(int k=1; k<=6; k++){ // 然后遍历这个骰子出点的点数可能
  13. if(j-k<=0){
  14. // 不存在的情况,提前结束
  15. break;
  16. }
  17. dp[i][j] += dp[i-1][j-k];
  18. }
  19. }
  20. }
  21. // 由于结果需要的是输出的概率值,进行转换
  22. double[] res = new double[6*n -n + 1];
  23. double all = Math.pow(6, n);
  24. for(int i=n; i<=6*n; i++){
  25. res[i-n] = dp[n][i] / all;
  26. }
  27. return res;
  28. }

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

  1. // 复杂度分析
  2. // 时间:O(nlogn + n)
  3. // 空间:O(1)
  4. public boolean isStraight(int[] nums) {
  5. // 排序一下
  6. Arrays.sort(nums);
  7. // 记录一下0的个数,0可能是有好几个的 TMD
  8. int count = 0;
  9. for(int i=0; i<nums.length; i++){
  10. if(nums[i] == 0){
  11. count++;
  12. }else{
  13. break;
  14. }
  15. }
  16. // 记录一下不连续的情况
  17. int fix = 0;
  18. for(int i=count+1; i<nums.length; i++){
  19. if(nums[i] - nums[i-1] != 1){
  20. // 计算不连续度
  21. fix += nums[i] - nums[i-1] - 1;
  22. }
  23. if(nums[i] == nums[i-1] || fix > count){
  24. // 重复牌的出 || 已经修复不了了
  25. return false;
  26. }
  27. }
  28. return true;
  29. }

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

  1. // 暴力法:模拟这个过程
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n)
  4. public int lastRemaining(int n, int m) {
  5. List<Integer> nums = new ArrayList<>();
  6. for(int i=0; i<n; i++){
  7. nums.add(i);
  8. }
  9. int index = 0;
  10. int res = 0;
  11. while(n > 0){
  12. // 关键逻辑
  13. index = (index + m - 1) % n;
  14. res = nums.remove(index);
  15. n--;
  16. }
  17. return res;
  18. }

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

  1. // 方法1:暴力法 超出时间限制 201/210
  2. // 时间复杂度:O(n^2)
  3. // 空间复杂度:O(1)
  4. public int maxProfit(int[] prices) {
  5. int res=0;
  6. // 遍历所有可能
  7. for(int i=0; i<prices.length; i++){
  8. for(int j=i+1; j<prices.length; j++){
  9. if(prices[j] - prices[i] > res){
  10. res = prices[j] - prices[i];
  11. }
  12. }
  13. }
  14. return res;
  15. }
  16. // 方法2:优化后减去一个循环
  17. // 时间复杂度:O(n)
  18. // 空间复杂度:O(1)
  19. public int maxProfit(int[] prices) {
  20. int res = 0;
  21. // 固定买出时间
  22. int cur_min = prices[0];
  23. for(int j=1; j<prices.length; j++){
  24. // 寻找最低买入价格的时间,遍历一遍肯定能找到最小值
  25. cur_min = Math.min(cur_min, prices[j]);
  26. // 计算最佳时机
  27. res = Math.max(res, prices[j] - cur_min);
  28. }
  29. return res;
  30. }
  31. // 方法3:动态规划
  32. // 时间复杂度:O(n)
  33. // 空间复杂度:O(2n)
  34. public int maxProfit(int[] prices) {
  35. int n = prices.length;:
  36. // 定义dp数组表示第i天的最大收入
  37. // 有2个状态,i:天数 j:持有股票的状态 0-没持有 1-持有
  38. int[][] dp = new int[n][2];
  39. // base case
  40. dp[0][0] = 0;
  41. dp[0][1] = -prices[0]; // 第一天持有,结果当然是负的
  42. // 开始状态转移
  43. for(int i=1; i<n; i++){
  44. // 第i天没持有,可以由 上一天没持有/上一天持有,这一天卖了 转移而来
  45. dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i]);
  46. // 第i天持有,可以由 上一天没持有 这一天买入/上一天持有 转移而来
  47. dp[i][1] = Math.max(-prices[i], dp[i-1][1]);
  48. }
  49. // 最终输出:第n天,没持有股票
  50. return dp[n-1][0];
  51. }

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

  1. // 不能用for:递归解决
  2. // 不能用if判断递归结束条件,用 && 的短路特性实现
  3. public int sumNums(int n) {
  4. boolean bool = n > 1 && (n += sumNums(n-1)) > 0;
  5. return n;
  6. }

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

还没做

剑指 Offer 66. 构建乘积数组

  1. // 妙蛙种子吃着妙脆角走进了米奇妙妙屋
  2. // 时间复杂度:O(2n)
  3. // 空间复杂度:O(1) 除去了必要的输出
  4. public int[] constructArr(int[] a) {
  5. int n = a.length;
  6. int[] res = new int[n];
  7. for (int i = 0, cur = 1; i < a.length; i++) {
  8. res[i] = cur; // 先乘左边的数(不包括自己)
  9. cur *= a[i];
  10. }
  11. for (int i = a.length - 1, cur = 1; i >= 0; i--) {
  12. res[i] *= cur; // 再乘右边的数(不包括自己)
  13. cur *= a[i];
  14. }
  15. return res;
  16. }

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

  1. public int strToInt(String str) {
  2. // 删除空格,转换成字符数组
  3. char[] c = str.trim().toCharArray();
  4. if(c.length == 0) return 0;
  5. // 大数边界,这个边界用的很巧妙
  6. int res = 0, bndry = Integer.MAX_VALUE / 10;
  7. // 符号位的提取
  8. int i = 1, sign = 1;
  9. if(c[0] == '-') sign = -1;
  10. else if(c[0] != '+') i = 0;
  11. // 开始遍历
  12. for(int j = i; j < c.length; j++) {
  13. // 不是数字了 break
  14. if(c[j] < '0' || c[j] > '9') break;
  15. // 关键步骤:防止溢出
  16. // int的最大值为 -2147483648 ~ 2147483647
  17. // 大于边界了,再*10必然溢出 || 刚刚等于边界,但是下一个数大于7加上后也溢出
  18. if(res > bndry || res == bndry && c[j] > '7'){
  19. return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
  20. }
  21. res = res * 10 + (c[j] - '0');
  22. }
  23. return sign*res;
  24. }

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

  1. // 方法1:一般dfs,没有利用二叉搜索树的性质
  2. // 时间复杂度:O(n)
  3. // 空间复杂度:O(n) 加上了递归的消耗
  4. TreeNode res = null;
  5. public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
  6. dfs(root, p, q);
  7. return res;
  8. }
  9. private boolean dfs(TreeNode root, TreeNode p, TreeNode q){
  10. if(res != null){
  11. return false;
  12. }
  13. boolean left = false, right = false;
  14. if(root.left != null){
  15. left = dfs(root.left, p, q);
  16. }
  17. if(root.right != null){
  18. right = dfs(root.right, p, q);
  19. }
  20. if(left && right){
  21. // 左边右边都满足val相等,则root则是这个公共祖先
  22. res = root;
  23. }else if((left || right) && (root.val == p.val || root.val == q.val)){
  24. // 左边或右边满足val相等,且现在的root满足等于p/q,则现在的root就是祖先
  25. res = root;
  26. }
  27. return root.val == p.val || root.val == q.val || left || right;
  28. }
  29. // 方法2:利用二叉搜索树的性质的解法:迭代
  30. // 时间复杂度:O(logn)
  31. // 空间复杂度:O(1)
  32. public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
  33. while(root != null){
  34. if(root.val < p.val && root.val < q.val){
  35. // p,q 都在 root 的右子树中
  36. root = root.right;
  37. }else if(root.val > p.val && root.val > q.val){
  38. // p,q 都在 root 的左子树中
  39. root = root.left;
  40. }else{
  41. // 此时有root的值刚好介于 p q 之间
  42. break;
  43. }
  44. }
  45. return root;
  46. }

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

  1. // 一般解法:做了68-I再做的这题...直接用上述的一般解法解的
  2. TreeNode res = null;
  3. public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
  4. dfs(root, p, q);
  5. return res;
  6. }
  7. private boolean dfs(TreeNode root, TreeNode p, TreeNode q){
  8. if(res != null){
  9. return false;
  10. }
  11. boolean left = false, right = false;
  12. if(root.left != null){
  13. left = dfs(root.left, p, q);
  14. }
  15. if(root.right != null){
  16. right = dfs(root.right, p, q);
  17. }
  18. if(left && right){
  19. res = root;
  20. }else if((left || right) && (root.val == p.val || root.val == q.val)){
  21. res = root;
  22. }
  23. return root.val == p.val || root.val == q.val || left || right;
  24. }

LeetCode:“剑指 Offer”的更多相关文章

  1. LeetCode剑指Offer刷题总结(一)

    LeetCode过程中值得反思的细节 以下题号均指LeetCode剑指offer题库中的题号 本文章将每周定期更新,当内容达到10题左右时将会开下一节. 二维数组越界问题04 public stati ...

  2. Leetcode - 剑指offer 面试题29:数组中出现次数超过一半的数字及其变形(腾讯2015秋招 编程题4)

    剑指offer 面试题29:数组中出现次数超过一半的数字 提交网址: http://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163 ...

  3. [leetcode] 剑指 Offer 专题(一)

    又开了一个笔记专题的坑,未来一两周希望能把<剑指Offer>的题目刷完

  4. LeetCode—剑指 Offer学习计划

    第 1 天 栈与队列(简单) 剑指 Offer 09. 用两个栈实现队列 class CQueue { public: CQueue() { } stack<int>s1,s2; void ...

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

    剑指 Offer 22. 链表中倒数第k个节点 题意 输入一个链表,输出该链表中倒数第k个节点.为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点. ​ 例如,一个链表有 6 个 ...

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

    输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数.比如输入 3,则打印出 1.2.3 一直到最大的 3 位数 999. 示例 1: 输入: n = 1 输出: [1,2,3,4,5,6,7, ...

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

    题解 nodeA走一个链表A(A独有+公共),再走B独有的长度, nodeB走一个链表B(B独有+公共),再走A独有的长度. 结果:两者相遇点即为交点:若没有交点,两者都走到null,会返回null. ...

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

    20207.22 LeetCode 剑指 Offer 11. 旋转数组的最小数字 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个递增排序的数组的一个旋转,输出旋转数组的最小 ...

  9. C++版 - 剑指offer之面试题37:两个链表的第一个公共结点[LeetCode 160] 解题报告

    剑指offer之面试题37 两个链表的第一个公共结点 提交网址: http://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?t ...

  10. C++版 - 剑指offer 面试题39:判断平衡二叉树(LeetCode 110. Balanced Binary Tree) 题解

    剑指offer 面试题39:判断平衡二叉树 提交网址:  http://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId= ...

随机推荐

  1. 服务器安装CentOS7.9系统(U盘启动方式)

    一.安装环境 机房的华为GPU服务器,型号G2500,8张P4显卡,需要安装最小化的CentOS7.9操作系统,利用U盘启动的方式进行安装. 二.安装说明 虽然本环境是GPU服务器,但是安装方式同样适 ...

  2. 使用ogr裁剪矢量数据

    使用ogr裁剪矢量数据 由来: ​ 近期有个需求,内容是这样的:我们有两个矢量数据,现在要求以一个矢量文件为底板,按字段对另一个矢量文件进行分割,生成若干小的shpfile文件 分析: ​ 经过分析之 ...

  3. Python - //和/的区别

    / 表示浮点数除法,返回浮点结果; // 表示整数除法,返回不大于结果的一个最大的整数 print("6 // 4 = " + str(6 // 4)) print("6 ...

  4. Mysql 面试宝典

    实时更新 你用过哪些数据库? mysql redis mysql 和 redis 的区别? 比较点 Mysql Redis 数据库类型 关系型 非关系型 作用 持久化层 存储需要持久化的数据,数据存在 ...

  5. jvm学习笔记:程序计数器

    程序计数器(PC Register) The Java Virtual Machine can support many threads of execution at once (JLS §17). ...

  6. 【HMS Core 6.0全球上线】Toolkit,您的智能辅助编程好帮手

    HMS Core 6.0已于7月15日全球上线.本次版本中,华为HMS Toolkit向广大开发者推出了智能辅助编程助手SmartCoder,帮助开发者轻松高效地集成HMS Core,开发新功能,创建 ...

  7. python中的getpass模块问题,在pycharm中不能继续输入密码

    python中getpass模块   在pycharm中运行下面的代码: 1 import getpass 2 name = input('请输入你的名字:') 3 passwd = getpass. ...

  8. 本地文件名大写,提交到git仓库后变成了小写

    输入改命令即可: git config core.ignorecase false

  9. 取得get参数 从url

    1. getUrlParam.js define(function() { // url参数 var data, index; (function init() { data = []; index ...

  10. php设计模式--生成器模式

    生成器模式 require "D:\\xxx\bild.php"; require "D:\\xxx\cx_bild.php"; require "D ...