剑指offerⅢ(Java 持续更新...)

这篇博客汇总了多种算法题目的解决方案,包括数组最小化排序、扑克牌顺子判断、数据流中中位数计算、复杂链表复制、二叉树深度与平衡性判断以及整数次方计算等。通过排序、哈希表、优先队列等数据结构和算法技巧,展示了在不同场景下的高效解题思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

DAY11

45:把数组排成最小的数

61:扑克牌中的顺子(此题思路牛)

DAY12

40:最小的k个数

41:数据流中的中位数(hard 重点看!!!)

35:复杂链表的复制

DAY13

55-Ⅰ:二叉树的深度

55-Ⅱ:平衡二叉树

DAY 14

64:求1+2+...+n

68-Ⅰ:二叉搜索树的最近公共祖先

68-Ⅱ:二叉树的最近公共祖先(多看!好喜欢这道题...)

46:把数字翻译成字符串

DAY15

07:重建二叉树(重点看)

16:数值的整数次方(看思路!!!)

33:二叉搜索树的后序遍历


DAY11

45:把数组排成最小的数

思路:排序,数组中任意两个数字组成的字符串为x,y

  • 若组成的字符串x+y>y+x则y比较“小”,应该排在前面;反之排在后面
  • sort无返回值,动态修改数组
  • (x,y) -> (x + y).compareTo(y + x)
class Solution {
    public String minNumber(int[] nums) {
        String[] strs = new String[nums.length];
        for(int i = 0; i < nums.length; i++)
            strs[i] = String.valueOf(nums[i]); //将nums中数字转换成字符
        Arrays.sort(strs, (x,y) -> (x + y).compareTo(y + x));
        StringBuilder res = new StringBuilder();
        for(String s : strs)
        res.append(s);
        return res.toString();

    }
}

61:扑克牌中的顺子(此题思路牛)

思路:排序(大神的思路真的好牛逼)。每次抽五张牌,数组长度为5。大小王0可以代替任何数字,所以满足顺子的条件就是max-min < 5,而中间空缺的数字可以用0补齐。因为需要5个连续的数字才算是顺子,所以用最大的牌 - 5就是中间所需填补数字的个数,一定要小于大小王的个数才能补齐。

  • 关键就在于max - min < 5 就能构成顺子这个思路
class Solution {
    public boolean isStraight(int[] nums) {
        int joker = 0;
        Arrays.sort(nums);
        for(int i = 0; i < 4; i++){
            if(nums[i] == 0) joker++;
            else if(nums[i] == nums[i + 1]) return false; //有重复的数肯定不是顺子
        }
        return nums[4] - 5 < nums[joker];
    }
}

DAY12

40:最小的k个数

思路一:快速排序。将arr[0]作为第一轮递归的哨兵,i,j两个指针分别从头和尾开始移动,与哨兵作比较,将右部分比哨兵小的和左部分比哨兵大的交换,当i与j相遇时完成第一轮排序。然后分成前后两部分子数组分别进行快排,将每部分的第一个元素作为哨兵进行比较排序。

思路二:堆排序

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        quickSort(arr, 0, arr.length - 1);
        return Arrays.copyOf(arr, k);
    }
    void quickSort(int[] arr, int left, int right){
        if(left >= right) return;
        int i = left, j = right;
        while(i < j){
            while(i < j && arr[j] >= arr[left]) j--;
            while(i < j && arr[i] <= arr[left]) i++;
            swap(arr, i, j);
        }
        swap(arr, i, left); //交换arr[i]和arr[left]
        //递归左右子数组
        quickSort(arr, left, i - 1);
        quickSort(arr, i + 1, right);
    }
    void swap (int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;

    }
}

41:数据流中的中位数(hard 重点看!!!)

思路:大根堆小根堆。向队列中添加元素时为了保证元素的序列,插入位置后元素要移动。规定当元素为奇数时A的size比B的大。PriorityQueue会自动将输入的元素进行排序并弹出最小的。

  • 当元素个数为偶数时:将元素加入A中,再将A中最小的弹到B中(因为num的大小不确定,所以不能直接存入A中)
  • 当为奇数时:因为规定A中比B重元素多,所以将元素添加到B中,在弹出最大的到A中
  • PriorityQueue是优先队列(需要看源码),确保每次弹出的是元素中最小的,所以构建小顶堆(存放较大的一部分)时用这个,而PriorityQueue<>((x,y) -> (y-x))为大顶堆的正则写法

class MedianFinder {
    Queue<Integer> A, B;
    /** initialize your data structure here. */
    public MedianFinder() {
        A = new PriorityQueue<>(); //小顶堆,确保每次弹出都是最小的,所以保存较大的一部分
        B = new PriorityQueue<>((x, y) -> (y - x)); //大顶堆,与上相反
    }
    
    public void addNum(int num) {
        if(A.size() != B.size()){ //奇数个
            A.add(num);
            B.add(A.poll());
        }else{
            B.add(num);
            A.add(B.poll());
        }
    }
    
    public double findMedian() {
        return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) /2.0;
    }
}

35:复杂链表的复制

思路:哈希表。构建哈希表,键值对存放<旧节点,新节点>。然后构建新节点的next和random指向。(这题的意思就是原来的链表只有next指向,现在要构建一个既有next指向又有random指向的新链表,每个节点不仅指向下一个,同时还随机指向一个节点。是这样理解的吧...)

class Solution {
    public Node copyRandomList(Node head) {
        if(head == null) return null;
        Node cur = head;
        Map<Node, Node> hash = new HashMap<>(); //构建hash表
        while(cur != null){
            hash.put(cur, new Node(cur.val));
            cur = cur.next;
        }
        cur = head;
        //构建新链表的next和random指向
        while(cur != null){
            hash.get(cur).next = hash.get(cur.next);
            hash.get(cur).random = hash.get(cur.random);
            cur = cur.next;
        }
        return hash.get(head);
    }
}

DAY13

55-Ⅰ:二叉树的深度

思路一:递归。DFS

  • 当根节点为空时深度为0
  • 分别计算左、右子树的深度,选择深度最大的一条路径返回

思路二:递归。BFS。类似从上到下打印二叉树。把同一层的子节点一起放入queue中,遍历完每一层将计数器加一。

//DFS
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }
}

//BFS
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        List<TreeNode> queue = new LinkedList<>(), temp;
        queue.add(root);
        int count = 0;
        while(!queue.isEmpty()){
            temp = new LinkedList<>();
            for(TreeNode node : queue){
                if(node.left != null) temp.add(node.left);
                if(node.right != null) temp.add(node.right);
            }
            queue = temp;
            count++;
        }
        return count;
    }
}

55-Ⅱ:平衡二叉树

思路一:递归。DFS后序遍历,自下而上。

  • 当root为空则为没有子节点了,返回高度为0
  • 当左/右子树深度为-1则此树的左/右子树不是平衡树,因此剪枝返回-1
  • 当root左/右子树的深度差<=1,则返回当前子树的深度,即root的左/右子树的深度最大值+1。(最后要用根节点左最大深度-右最大深度判断是否平衡)

思路二:递归,先序遍历。同上一题,计算出左右子树的最大深度判断是否平衡。

//思路一:后序遍历
class Solution {
    public boolean isBalanced(TreeNode root) {
        return depth(root) != -1;

    }
    public int depth(TreeNode root){
        if(root == null) return 0;
        int left = depth(root.left);
        if(left == -1) return -1;
        int right = depth(root.right);
        if(right == -1) return -1;
        return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
    }
}

//思路二:先序遍历
class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root == null) return true;
        return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
    }

    public int depth(TreeNode root){
        if(root == null) return 0;
        return Math.max(depth(root.left), depth(root.right)) + 1;
    }
}

DAY 14

64:求1+2+...+n

思路:递归。题目要求不能用乘除,if/while等一系列关键字和判断语句。本题重点是当n = 1时要终止递归,所以设置temp,因为在&&中前面的条件是false后面的就不执行了,所以当n=1时不再执行sumNums(n - 1)了。

class Solution {
    int sum = 0;
    public int sumNums(int n) {
        boolean temp = n > 1 && sumNums(n - 1) > 0;
        sum += n;
        return sum;
    }
}
//简便写法
class Solution {
    public int sumNums(int n) {
        boolean temp = n > 1 && (n += sumNums(n - 1)) > 0;
        return n;
    }
}

68-Ⅰ:二叉搜索树的最近公共祖先

思路:两个要点:深度和祖先。二叉搜索树特征:任意节点的右节点的值小于本身,左节点大。设root为p,q的公共祖先,有三种情况:

  • p,q分别在root的左/右子树中且分布在异侧
  • p=root且q在root的左/右子树中
  • q=root且p在root的左/右子树中

方法一:迭代

方法二:递归

//方法一:迭代
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while(root != null){
            if(root.val > p.val && root.val > q.val) root = root.left;
            else if(root.val < p.val && root.val < q.val) root = root.right;
            else break; //找到了最近的公共节点
        }
        return root;
    }
}
//方法二:递归
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
        else if(root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
        return root;
    }
}

68-Ⅱ:二叉树的最近公共祖先(多看!好喜欢这道题...)

思路:递归+回溯,先序遍历。递归左右子节点,分别记为left和right

  • 若root开始遍历,先遇到p或q则直接返回,自己也能是祖先节点
  • 遍历左子树,若left和right都在左子树,则调用上一条,先找到谁谁就是祖先节点
  • 遍历右子树同2
  • 若left和right同时不为空时,则说明p,q分布在此时root的异侧,则root为最近的公共节点
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root.val == p.val || root.val == q.val) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        //遍历完left没找到p,q则说明都在右子树,返回right即返回在右子树先找到的p或q为祖先
        if(left == null) return right; 
        else if(right == null) return left;
        //若left和right均不为空,则说明他们处于此时root的异侧,此时root为最近的公共祖先
        else return root; 
    }
}

DAY15

07:重建二叉树(重点看)

思路:分治算法,递归。建立哈希表存放中序数组的元素值和索引值的键值对

先序遍历:根节点 | 左子树 | 右子树

中序遍历:左子树 | 根节点 | 右子树

先序遍历根节点索引中序遍历左边界中序遍历右边界
左子树root + 1lefti - 1
右子树root + 1 + i - lefti +1right
  • i 为中序遍历中根节点的索引,右子树的根节点为:根节点 + 1 - 左子树的长度
class Solution {
    HashMap<Integer, Integer> dic = new HashMap<>();
    int[] preorder; //保留先序遍历数组
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        for(int i = 0; i < preorder.length; i++)
            dic.put(inorder[i], i);
        return recur(0, 0, inorder.length - 1);
    }

    public TreeNode recur(int pre_root, int in_left, int in_right){
        if(in_left > in_right) return null;
        TreeNode node = new TreeNode(preorder[pre_root]); //先序遍历的第0个元素为树的根节点
        int i = dic.get(preorder[pre_root]); //找到根节点在中序遍历中对应的索引值
        node.left = recur(pre_root + 1, in_left, i - 1);
        node.right = recur(pre_root + 1 + i - in_left, i + 1, in_right);
        return node;
    }
}

16:数值的整数次方(看思路!!!)

思路:分治算法,递归。计算x^{n}时,先递归计算出y=x^{\left \lfloor n/2 \right \rfloor}\left \lfloor n/2 \right \rfloor为向下取整)

  • 当n为偶数时,x^{n} = y^{2}
  • 当n为奇数时,x^{n} = y^{2}\times x
  • 当n=0时候结果都为1,所以n=0是递归的边界
class Solution {
    public double myPow(double x, int n) {
        long N = n;
        return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
    }

    double quickMul(double x, long N){
        if(N == 0) return 1.0;
        double y = quickMul(x, N / 2);
        return N % 2 == 0 ? y * y : y * y * x;
    }
}

33:二叉搜索树的后序遍历

思路:分治算法,递归。二叉搜索树,任意节点的左子节点小于本身,右子节点大于本身。数组最后一个元素为根节点,比它小的是左子树,比它大的是右子树。先找到左右子树的分界点,m记为右子树的第一个节点。左子树区间为[left,m - 1],右子树区间[m,right - 1],根节点索引为right。判断是否为二叉搜索树:

  • 左子树:[left,m - 1]内所有节点都小于postorder[right]
  • 右子树:[m,right - 1]内所有节点都大于postorder[right],当遇到根节点则跳出循环
  • p = right判断此树是否正确。只有正确的树p才能走到right的位置。
class Solution {
    public boolean verifyPostorder(int[] postorder) {
        return recur(postorder, 0, postorder.length - 1);
    }
    boolean recur(int[] postorder, int left, int right){
        if(left >= right) return true;
        int p = left;
        while(postorder[p] < postorder[right]) p++;
        int m = p; //m为第一个比根节点大的元素索引
        while(postorder[p] > postorder[right]) p++;
        return p == right && recur(postorder, left, m - 1) && recur(postorder, m, right - 1);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值