二刷剑指offer 22-40

面试题39. 数组中出现次数超过一半的数字

二刷剑指offer 22-40_第1张图片
核心理念为 “正负抵消” ;时间和空间复杂度分别为 O(N) 和 O(1);是本题的最佳解法。

class Solution {
    public int majorityElement(int[] nums) {
        //摩尔投票
        int count = 0;
        Integer card = null;
        for(int num:nums){
            if(count == 0) 
            	card = num;
            count += (card == num)?1:-1;
        }
        return card;
    }
}

哈希表统计法:遍历数组 nums,用 HashMap 统计各数字的数量,最终超过数组长度一半的数字则为众数。此方法时间和空间复杂度均为 O(N)。

 //HashMap方法:不是双百解法,但是容易理解,且普适性强,并考虑了数组中不存在满足条件的众数和数组为空的情况
    public int majorityElement(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap<>();
        int length = nums.length/2;
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(nums[i])) {
                //这里不能直接map.get(nums[i])++;
                map.put(nums[i],map.get(nums[i])+1);
            }else{
                map.put(nums[i],1);
            }
            //注意:这里if不能放在第一个if中,否则会在数组长度为1时出错,无法返回正确的nums[i]的值
            //这里i>=length,之所以带等号,也是为了满足长度为1的情况,因为i从0开始
            //按照题目要求,必须众数次数超过长度的一半,则有第一个判断条件,相当于剪枝,当然下面的第一个判断条件也可以不加
            if(i>=length&&map.get(nums[i])>length) return nums[i];
        }
        return 0;//当不存在满足要求的数字或者数组长度为0时
    }

需要的数字出现次数多于一半 那么排序后必定在中间 排序解法O(nlogn)

面试题40. 最小的k个数

用快排最最最高效解决 TopK 问题:O(N)。直接通过快排切分排好第 K 小的数(下标为 K-1),那么它左边的数就是比它小的另外 K-1 个数啦~

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        quickselect(arr, 0, arr.length - 1, k);
        return Arrays.copyOfRange(arr, 0, k);
    }
    private void quickselect(int[] nums, int start, int end, int k) {
        while (start < end) {
            // 这边做了了小优化,三数取中~前两步先确定最后一个一定是最大的,然后只要把中间那个数放第一个位置即可
            // 如果你嫌麻烦可以直接 int pivot = nums[start]; 
            int mid = start + (end - start) / 2;
            if (nums[start] > nums[end]) swap(nums, start, end);
            if (nums[mid] > nums[end]) swap(nums, mid, end);
            if (nums[mid] > nums[start]) swap(nums, mid, start);
            int pivot = nums[start];
            int i = start, j = end;
            while (i <= j) {
                //升序~
                while (i <= j && nums[i] < pivot) i++;
                while (i <= j && nums[j] > pivot) j--;
                if (i <= j) {
                    swap(nums,i,j);
                    i++;
                    j--;
                }
            }
            if (i >= k) {
                end = i - 1;
            } else {
                start = i;
            }
        }
    }
    private void swap(int[] nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

本题是求前 K 小,因此用一个容量为 K 的大根堆,每次 poll 出最大的数,那堆中保留的就是前 K 小啦(注意不是小根堆!小根堆的话需要把全部的元素都入堆,那是 O(NlogN),就不是 O(NlogK)啦~~)

// 保持堆的大小为K,然后遍历数组中的数字,遍历的时候做如下判断:
// 1. 若目前堆的大小小于K,将当前数字放入堆中。
// 2. 否则判断当前数字与大根堆堆顶元素的大小关系,如果当前数字比大根堆堆顶还大,这个数就直接跳过;
//    反之如果当前数字比大根堆堆顶小,先poll掉堆顶,再将该数字放入堆中。
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        // 默认是小根堆,实现大根堆需要重写一下比较器。
        Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1);
        for (int num: arr) {
            if (pq.size() < k) {
                pq.offer(num);
            } else if (num < pq.peek()) {
                pq.poll();
                pq.offer(num);
            }
        }
        // 返回堆中的元素
        int[] res = new int[pq.size()];
        int idx = 0;
        for(int num: pq) {
            res[idx++] = num;
        }
        return res;
    }
}

面试题42. 连续子数组的最大和

二刷剑指offer 22-40_第2张图片
二刷剑指offer 22-40_第3张图片
dp[i] sum

class Solution {
    public int maxSubArray(int[] nums) {
        int result=nums[0];
        int sum=0;
        for(int i=0;i<nums.length;i++){
            sum=sum>0?sum+nums[i]:nums[i];
            result=Math.max(sum,result);
        }
        return result;
    }
}

面试题50. 第一个只出现一次的字符

二刷剑指offer 22-40_第4张图片
时间复杂度 O(N) : N为字符串 s 的长度;需遍历 s 两轮,使用 O(N) ;HashMap 查找的操作复杂度为 O(1);
空间复杂度 O(N) : HashMap 需存储 N个字符的键值对,使用 O(N)大小的额外空间

class Solution {
    public char firstUniqChar(String s) {
        HashMap<Character, Boolean> dic = new HashMap<>();
        char[] sc = s.toCharArray();
        for(char c : sc)
            dic.put(c, !dic.containsKey(c));
        for(char c : sc)
            if(dic.get(c)) return c;
        return ' ';
    }
}

面试题52. 两个链表的第一个公共节点

二刷剑指offer 22-40_第5张图片

  • 时间复杂度:O(M+N)。
  • 空间复杂度:O(1)。
/**
 * 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) {
        //思路:双指针法。
        ListNode pA = headA, pB = headB;
        
        while(pA != pB){
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }  

        return pA;
    }
}

面试题53 - I. 在排序数组中查找数字 I

思路:二分查找
(1) 定义双指针遍历两次数组分别找出等于目标值的开始和结束位置。
(2) 计算出等于目标值的位置区间,就是要求的次数。

class Solution {
    public int search(int[] nums, int target) {
        if(nums == null || nums.length < 1 
                || target < nums[0] || target > nums[nums.length - 1]){
            return 0;
        }
        // 找到升序数组最后一个目标值下一位和第一个目标值位置,差就是目标值个数。
        return getLast(nums, target) - getLast(nums, target - 1);
    }
    int getLast(int[] nums, int target) {
        // 定义双指针,初始化指向数组的两端。
        int left = 0, right = nums.length - 1;
        // 当 left 大于 right 则跳出循环,left 指向为查找位置。
        while (left <= right) {
            // 找到中间位置。
            int mid = left + (right - left) / 2;
            // 根据每次中间位置值和目标值比较移动指针。
            if (nums[mid] <= target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
}

面试题53 - II. 0~n-1中缺失的数字

二刷剑指offer 22-40_第6张图片
有序就要二分 ,异或只是为了炫技
时间复杂度 O(logN): 二分法为对数级别复杂度。
空间复杂度 O(1): 几个变量使用常数大小的额外空间。

class Solution {
    public int missingNumber(int[] nums) {
        int i = 0, j = nums.length - 1;
        while(i <= j) {
            int m = (i + j) / 2;
            if(nums[m] == m) i = m + 1;
            else j = m - 1;
        }
        return i;
    }
}

面试题54. 二叉搜索树的第k大节点

二刷剑指offer 22-40_第7张图片

二叉搜索树的一个特性:通过中序遍历所得到的序列,就是有序的。二叉搜索树的 中序遍历倒序 为 递减序列
中序遍历 + 提前返回
时间复杂度 O(N) : 当树退化为链表时(全部为右子节点),无论 kkk 的值大小,递归深度都为 N,占用 O(N) 时间。
空间复杂度 O(N) : 当树退化为链表时(全部为右子节点),系统使用 O(N) 大小的栈空间。

class Solution {
    int res, k;
    public int kthLargest(TreeNode root, int k) {
        this.k = k;
        dfs(root);
        return res;
    }
    void dfs(TreeNode root) {
        if(root == null) return;
        dfs(root.right);
        if(k == 0) return;
        if(--k == 0) res = root.val;
        dfs(root.left);
    }
}

面试题55 - I. 二叉树的深度

二刷剑指offer 22-40_第8张图片
二刷剑指offer 22-40_第9张图片

面试题55 - II. 平衡二叉树

如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
二刷剑指offer 22-40_第10张图片
二刷剑指offer 22-40_第11张图片
时间复杂度 O(N): N 为树的节点数;最差情况下,需要递归遍历树的所有节点。
空间复杂度 O(N): 最差情况下(树退化为链表时),系统递归需要使用 O(N) 的栈空间。

class Solution {
    public boolean isBalanced(TreeNode root) {
        return recur(root) != -1;
    }

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

面试题57. 和为s的两个数字

对撞双指针while(i 时间复杂度 O(N): NN 为数组 numsnums 的长度;双指针共同线性遍历整个数组。
空间复杂度 O(1): 变量 ii, jj 使用常数大小的额外空间。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int i = 0, j = nums.length - 1;
        while(i < j) {
            int s = nums[i] + nums[j];
            if(s < target) i++;
            else if(s > target) j--;
            else return new int[] { nums[i], nums[j] };
        }
        return new int[0];
    }
}

面试题57 - II. 和为s的连续正数序列

这个算法是这样的:先找出2个连续的和为target的数,再找出连续的3个、4个…怎么找呢?其实是算出来的。这里我每次算出序列中最小的那个数。假设有2个数的和等于target,设最小的那个数的值为n,显然n+(n+1)=target,则n=(target-1)/2;如果没有2个数的和为target,那么假设有3个,则有n+(n+1)+(n+2)=target,n=(target-1-2)/3;以此类推,如果我们用i来表示序列长度(代码中我设置初值为1,方便对target做减法),那么长度为i的序列中最小的那个数n(target-(1+2+3+…+i-1))/i.
还有就是,显然,在和相等的情况下,序列越长它的头部的那个数就越小。由于我们从序列最短的情况开始求,因此最后我们只要把ArrayList倒序复制(其实只是复制了地址)到二维数组中即可。

class Solution {
    public int[][] findContinuousSequence(int target) {
        List<int[]> result = new ArrayList<>();
        int i = 1;
        
        while(target>0)
        {
            //target -= i;
            //i++;
            target -= i++;
            if(target>0 && target%i == 0)
            {
                int[] array = new int[i];
                for(int k = target/i, j = 0; k < target/i+i; k++,j++)
                {
                    array[j] = k;
                }
                result.add(array);
            }
        }
        Collections.reverse(result);
        return result.toArray(new int[0][]);       
    }
}

面试题58 - I. 翻转单词顺序

需要注意的是 如果字符串前面有空格 split() 会产生一个 “”
如果中间有连续的三个空格 会产生两个""
另外 String类型 用"=="无效 要使用equals() 方法判断
二刷剑指offer 22-40_第12张图片

class Solution {
    public String reverseWords(String s) {
            String[] a = s.split(" ");
            StringBuffer sb = new StringBuffer();
            //倒序遍历数组
            for(int i = a.length-1;i>=0;i--){
                //不是空字符
                if(!a[i].equals(""))  {
                	sb.append(a[i]);
                	sb.append(" ");
                }
            }
            return sb.toString().trim();           
    }
}

面试题58 - II. 左旋转字符串

时间复杂度 O(N): 线性遍历 s并添加,使用线性时间;
空间复杂度 O(N): 新建的辅助 res使用 O(N)大小的额外空间。

class Solution {
    public String reverseLeftWords(String s, int n) {
        StringBuilder res = new StringBuilder();
        for(int i = n; i < n + s.length(); i++)
        	//不然就要两个for循环
            res.append(s.charAt(i % s.length()));
        return res.toString();
    }
}
class Solution {
    public String reverseLeftWords(String s, int n) {
        return s.substring(n, s.length()) + s.substring(0, n);
    }
}

面试题59 - I. 滑动窗口的最大值

二刷剑指offer 22-40_第13张图片
二刷剑指offer 22-40_第14张图片

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 0 || k == 0) return new int[0];
        Deque<Integer> deque = new LinkedList<>();
        int[] res = new int[nums.length - k + 1];
        for(int j = 0, i = 1 - k; j < nums.length; i++, j++) {
            if(i > 0 && deque.peekFirst() == nums[i - 1]) 
            	deque.removeFirst(); // 删除 deque 中对应的 nums[i-1]
            while(!deque.isEmpty() && deque.peekLast() < nums[j]) 
            	deque.removeLast(); // 保持 deque 递减
            deque.addLast(nums[j]);
            if(i >= 0) 
            	res[i] = deque.peekFirst();  // 记录窗口最大值
        }
        return res;
    }
}

面试题60. n个骰子的点数

二刷剑指offer 22-40_第15张图片

class Solution {
    public double[] twoSum(int n) {
        //动态规划:设F(n,s)F(n,s)F(n,s)为当骰子数为n,和为s的情况数量。
        //当n=1时,F(1,s)=1,其中s=1,2,3,4,5,6
        //当n≥2时,F(n,s)=F(n−1,s−1)+F(n−1,s−2)+F(n−1,s−3)+F(n−1,s−4)+F(n−1,s−5)+F(n−1,s−6)
        //所以,P(n,s)=F(n,s)/6^n
        int [][]dp=new int[n+1][6*n+1];//点数和最大是6*n
        //初始化边界条件
        for(int s=1;s<=6;s++) dp[1][s]=1;//1个筛子 任何点数都是1种
        for(int i=2;i<=n;i++){ //骰子个数
            for(int s=i;s<=6*i;s++){ //点数和 有哪些
                //求dp[i][s]
                for(int d=1;d<=6;d++){ //最后一个骰子 分别投 1 2 3 4 5 6
                    if(s-d==0)break;//为0了 (s-d==0了)
                    dp[i][s]+=dp[i-1][s-d];
                }
            }
        }
        double total =Math.pow((double)6,(double)n);//总的点数次数是6^n
        double[] ans =new double[5*n+1];//点数和大小:从n---到--6*n 遍历
        for(int i=n;i<=6*n;i++){
            ans[i-n]=dp[n][i]/total;
        }
        return ans;
    }
}

面试题61. 扑克牌中的顺子

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
二刷剑指offer 22-40_第16张图片
时间复杂度 O(N)=O(5)=O(1) : 其中 NNN 为 nums长度,本题中 N≡5N \equiv 5N≡5 ;遍历数组使用 O(N)时间。
空间复杂度 O(N)=O(5)=O(1): 用于判重的辅助 Set 使用 O(N) 额外空间。

class Solution {
    public boolean isStraight(int[] nums) {
        Set<Integer> repeat = new HashSet<>();
        int max = 0, min = 14;
        for(int num : nums) {
            if(num == 0) continue; // 跳过大小王
            max = Math.max(max, num); // 最大牌
            min = Math.min(min, num); // 最小牌
            if(repeat.contains(num)) return false; // 若有重复,提前返回 false
            repeat.add(num); // 添加此牌至 Set
        }
        return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
    }
}

面试题62. 圆圈中最后剩下的数字

二刷剑指offer 22-40_第17张图片
二刷剑指offer 22-40_第18张图片

class Solution {
    public int lastRemaining(int n, int m) {
        //最后被删除的那个人的索引一定为零,因为删除一个人之后就会将气候的索引重置
        int ans = 0;
        // 最后一轮剩下2个人,所以从2开始反推
        for (int i = 2; i <= n; i++) {
            ans = (ans + m % i)%i;
        }
        return ans;
    }
}

面试题65. 不用加减乘除做加法

二刷剑指offer 22-40_第19张图片二刷剑指offer 22-40_第20张图片
^ 亦或 ----相当于 无进位的求和, 想象10进制下的模拟情况:(如:19+1=20;无进位求和就是10,而非20;因为它不管进位情况)
& 与 ----相当于求每位的进位数, 先看定义:1&1=1;1&0=0;0&0=0;即都为1的时候才为1,正好可以模拟进位数的情况,还是想象10进制下模拟情况:(9+1=10,如果是用&的思路来处理,则9+1得到的进位数为1,而不是10,所以要用<<1向左再移动一位,这样就变为10了);

class Solution {
    public int add(int a, int b) {
        //a^b是不考虑进位的加法
        //a&b<<1是进位
        //一直递归,运算&最终会导致进位为0,递归结束
        return b == 0 ? a : add(a^b, (a&b)<<1);
    }
}

面试题66. 构建乘积数组

二刷剑指offer 22-40_第21张图片

class Solution {
    public int[] constructArr(int[] a) {
        if(a.length == 0) return new int[0];
        int[] b = new int[a.length];
        b[0] = 1;
        int tmp = 1;
        for(int i = 1; i < a.length; i++) {
            b[i] = b[i - 1] * a[i - 1];
        }
        for(int i = a.length - 2; i >= 0; i--) {
            tmp *= a[i + 1];
            b[i] *= tmp;
        }
        return b;
    }
}

面试题68 - I. 二叉搜索树的最近公共祖先

最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。
所有节点的值都是唯一的。

二刷剑指offer 22-40_第22张图片
二刷剑指offer 22-40_第23张图片

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while(root != null) {
            if(root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中
                root = root.right; // 遍历至右子节点
            else if(root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中
                root = root.left; // 遍历至左子节点
            //p,q各在一边,说明当前的根就是最近共同祖先
            else break;
        }
        return root;
    }
}

面试题68 - II. 二叉树的最近公共祖先

在这里插入图片描述
二刷剑指offer 22-40_第24张图片
二刷剑指offer 22-40_第25张图片

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left == null && right == null) return null; // 1.
        if(left == null) return right; // 3.
        if(right == null) return left; // 4.
        return root; // 2. if(left != null and right != null)
    }
}

难度提升到中等

面试题07. 重建二叉树

//利用原理,先序遍历的第一个节点就是根。在中序遍历中通过根 区分哪些是左子树的,哪些是右子树的
    //左右子树,递归
    HashMap<Integer, Integer> map = new HashMap<>();//标记中序遍历
    int[] preorder;//保留的先序遍历
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        for (int i = 0; i < preorder.length; i++) {
            map.put(inorder[i], i);
        }
        return recursive(0,0,inorder.length-1);
    }
    /**
     * @param pre_root_idx  先序遍历的索引
     * @param in_left_idx  中序遍历的索引
     * @param in_right_idx 中序遍历的索引
     */
    public TreeNode recursive(int pre_root_idx, int in_left_idx, int in_right_idx) {
        //相等就是自己
        if (in_left_idx > in_right_idx) {
            return null;
        }
        //root_idx是在先序里面的
        TreeNode root = new TreeNode(preorder[pre_root_idx]);
        // 有了先序的,再根据先序的,在中序中获 当前根的索引
        int idx = map.get(preorder[pre_root_idx]);
        //左子树的根节点就是 左子树的(前序遍历)第一个,就是+1,左边边界就是left,右边边界是中间区分的idx-1
        root.left = recursive(pre_root_idx + 1, in_left_idx, idx - 1);
        //由根节点在中序遍历的idx 区分成2段,idx 就是根
        //右子树的根,就是右子树(前序遍历)的第一个,就是当前根节点 加上左子树的数量
        // pre_root_idx 当前的根  左子树的长度 = 左子树的左边-右边 (idx-1 - in_left_idx +1) 。最后+1就是右子树的根了
        root.right = recursive(pre_root_idx + (idx-1 - in_left_idx +1)  + 1, idx + 1, in_right_idx);
        return root;
    }

面试题12. 矩阵中的路径

二刷剑指offer 22-40_第26张图片
二刷剑指offer 22-40_第27张图片
二刷剑指offer 22-40_第28张图片

下上右左

class Solution {
    public boolean exist(char[][] board, String word) {
        char[] words = word.toCharArray();
        for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board[0].length; j++) {
                if(dfs(board, words, i, j, 0)) return true;
            }
        }
        return false;
    }
    boolean dfs(char[][] board, char[] word, int i, int j, int k) {
        if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;
        if(k == word.length - 1) return true;
        char tmp = board[i][j];
        board[i][j] = '/';
        boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || 
                      dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
        board[i][j] = tmp;
        return res;
    }
}

面试题13. 机器人的运动范围

二刷剑指offer 22-40_第29张图片
二刷剑指offer 22-40_第30张图片
二刷剑指offer 22-40_第31张图片
二刷剑指offer 22-40_第32张图片

class Solution {
    int m, n, k;
    boolean[][] visited;
    public int movingCount(int m, int n, int k) {
        this.m = m; this.n = n; this.k = k;
        this.visited = new boolean[m][n];
        return dfs(0, 0, 0, 0);
    }
    public int dfs(int i, int j, int si, int sj) {
        if(i >= m || j >= n || k < si + sj || visited[i][j]) return 0;
        visited[i][j] = true;
        return 1 + dfs(i + 1, j, (i + 1) % 10 != 0 ? si + 1 : si - 8, sj) + dfs(i, j + 1, si, (j + 1) % 10 != 0 ? sj + 1 : sj - 8);
    }
}

面试题14- I. 剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
二刷剑指offer 22-40_第33张图片
二刷剑指offer 22-40_第34张图片
在这里插入图片描述
二刷剑指offer 22-40_第35张图片

class Solution {
    public int cuttingRope(int n) {
        if(n<=3) return n-1;
        int res=1;
        while(n>4){
            res*=3;
            n-=3;
        }
        return res*n;
    }
}

面试题14- II. 剪绳子 II

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m] 。请问 k[0]k[1]k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
此题与14-I也就是上一题的区别在于此题n的范围变大,可能会造成大数越界的情况,用寻常的dp很可能会超时或出错,故使用贪心思想解决,剪绳子数学问题其实就是尽可能多地切3的片段,我们可以以n>4来作为循环跳出点。
循环结束的结果分为三种:
1.n=2,等于说无限除以3,最后余下绳子长度为2,此时将res乘以2即可
2.n=3,绳子全部用完,直接所有3相乘即可
3.n=4,等于说余下绳子长度为1,因为4%3=1,但是3<2
2,也就是4本身,故最后乘4

class Solution {
    public int cuttingRope(int n) {
        if(n<=3) return n-1;
        long res=1;
        while(n>4){
            res*=3;
            res=res%1000000007;
            n-=3;
        }
        //这里必须取括号,否则会优先计算res*n的值,报错会超出范围
        return (int)(res*n%1000000007);
    }
}

面试题16. 数值的整数次方

二刷剑指offer 22-40_第36张图片
二刷剑指offer 22-40_第37张图片
二刷剑指offer 22-40_第38张图片

class Solution {
    public double myPow(double x, int n) {
        if(x == 0) return 0;
        //幂
        long b = n;
        double res = 1.0;
        if(b < 0) {
            x = 1 / x;
            b = -b;
        }
        while(b > 0) {
            if((b & 1) == 1) 
            	res *= x;
            x *= x;
            b >>= 1;
        }
        return res;
    }
}

面试题20. 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、“±5”、"-1E-16"及"12e+5.4"都不是。

class Solution {
    public boolean isNumber(String s) {
        if(s == null || s.length() == 0){
            return false;
        }
        //标记是否遇到相应情况
        boolean numSeen = false;
        boolean dotSeen = false;
        boolean eSeen = false;
        char[] str = s.trim().toCharArray();
        for(int i = 0;i < str.length; i++){
            if(str[i] >= '0' && str[i] <= '9'){
                numSeen = true;
            }else if(str[i] == '.'){
                //.之前不能出现.或者e
                if(dotSeen || eSeen){
                    return false;
                }
                dotSeen = true;
            }else if(str[i] == 'e' || str[i] == 'E'){
                //e之前不能出现e,必须出现数
                if(eSeen || !numSeen){
                    return false;
                }
                eSeen = true;
                numSeen = false;//重置numSeen,排除123e或者123e+的情况,确保e之后也出现数
            }else if(str[i] == '-' || str[i] == '+'){
                //+-出现在0位置或者e/E的后面第一个位置才是合法的
                if(i != 0 && str[i-1] != 'e' && str[i-1] != 'E'){
                    return false;
                }
            }else{//其他不合法字符
                return false;
            }
        }
        return numSeen;
    }
}

面试题31. 栈的压入、弹出序列

将原始栈压入栈 for while i=0;避免for循环
二刷剑指offer 22-40_第39张图片

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Stack<Integer> stack = new Stack<>();
        int i = 0;
        for(int num : pushed) {
            stack.push(num); // num 入栈
            while(!stack.isEmpty() && stack.peek() == popped[i]) { // 循环判断与出栈
                stack.pop();
                i++;
            }
        }
        return stack.isEmpty();
    }
}

面试题32 - I. 从上到下打印二叉树

二刷剑指offer 22-40_第40张图片
在这里插入图片描述

class Solution {
    public int[] levelOrder(TreeNode root) {
        if(root == null) return new int[0];
        Queue<TreeNode> queue = new LinkedList<>(){{ add(root); }};
        ArrayList<Integer> ans = new ArrayList<>();
        while(!queue.isEmpty()) {
            TreeNode node = queue.poll();
            ans.add(node.val);
            if(node.left != null) queue.add(node.left);
            if(node.right != null) queue.add(node.right);
        }
        int[] res = new int[ans.size()];
        for(int i = 0; i < ans.size(); i++)
            res[i] = ans.get(i);
        return res;
    }
}

面试题32 - III. 从上到下打印二叉树 III

二刷剑指offer 22-40_第41张图片
二刷剑指offer 22-40_第42张图片

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        if(root != null) queue.add(root);
        while(!queue.isEmpty()) {
            LinkedList<Integer> tmp = new LinkedList<>();
            for(int i = queue.size(); i > 0; i--) {
                TreeNode node = queue.poll();
                if(res.size() % 2 == 0) tmp.addLast(node.val); // 偶数层 -> 队列头部
                else tmp.addFirst(node.val); // 奇数层 -> 队列尾部
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            res.add(tmp);
        }
        return res;
    }
}

面试题33. 二叉搜索树的后序遍历序列

在这里插入图片描述
在这里插入图片描述
二刷剑指offer 22-40_第43张图片

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        Stack<Integer> stack = new Stack<>();
        int root = Integer.MAX_VALUE;
        for(int i = postorder.length - 1; i >= 0; i--) {
            if(postorder[i] > root) return false;
            while(!stack.isEmpty() && stack.peek() > postorder[i])
            	root = stack.pop();
            stack.add(postorder[i]);
        }
        return true;
    }
}

面试题34. 二叉树中和为某一值的路径

在这里插入图片描述
二刷剑指offer 22-40_第44张图片

class Solution {
    LinkedList<List<Integer>> res = new LinkedList<>();
    LinkedList<Integer> path = new LinkedList<>(); 
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        recur(root, sum);
        return res;
    }
    void recur(TreeNode root, int tar) {
        if(root == null) return;
        path.add(root.val);
        tar -= root.val;
        if(tar == 0 && root.left == null && root.right == null)
 //记录路径时若直接执行 res.append(path) ,则是将 path 列表对象 加入了 res ;后续 path 对象改变时, res 中的 path 对象 也会随之改变(因此肯定是不对的,本来存的是正确的路径 path ,后面又 append 又 pop 的,就破坏了这个正确路径)。list(path) 相当于新建并复制了一个 path 列表,因此不会受到 path 变化的影响。
            res.add(new LinkedList(path));
        recur(root.left, tar);
        recur(root.right, tar);
        path.removeLast();
    }
}

面试题35. 复杂链表的复制

二刷剑指offer 22-40_第45张图片

//推荐双百方法二:不需要辅助空间,但是需要额外将两个链表进行拆分,但理解难度较大
    public Node copyRandomList2(Node head) {
        if(head==null) return null;
        copy2(head);
        randomAdd2(head);
        return build2(head);
    }
    public void copy2(Node head){
         while(head!=null){
            Node copy = new Node(head.val);
            copy.next = head.next;
            head.next =copy;
            head = copy.next;
        }
    }
    public void randomAdd2(Node head){
        while(head!=null){
           if(head.random!=null) head.next.random = head.random.next;
           head=head.next.next; 
        }
    }
    public Node build2(Node head){
        //将链表拆成两个,注意要恢复原有的链表
        Node res = head.next;
        Node tmp = res;

        head.next = head.next.next;//这一步不可缺少,保证第一个复制节点对N N'的分离操作
        head = head.next;
        while(head!=null){
            tmp.next = head.next;
            head.next = head.next.next;
            tmp=tmp.next;
            head = head.next;
         }
        return res;
    }

面试题36. 二叉搜索树与双向链表

在这里插入图片描述
二刷剑指offer 22-40_第46张图片
二刷剑指offer 22-40_第47张图片

class Solution {
    Node pre, head;
    public Node treeToDoublyList(Node root) {
        if(root == null) return null;
        dfs(root);
        head.left = pre;
        pre.right = head;
        return head;
    }
    void dfs(Node cur) {
        if(cur == null) return;
        dfs(cur.left);
        if(pre != null) pre.right = cur;
        else head = cur;
        cur.left = pre;
        pre = cur;
        dfs(cur.right);
    }
}

面试题38. 字符串的排列

在这里插入图片描述
二刷剑指offer 22-40_第48张图片
二刷剑指offer 22-40_第49张图片
二刷剑指offer 22-40_第50张图片

class Solution {
    List<String> res = new LinkedList<>();
    char[] c;
    public String[] permutation(String s) {
        c = s.toCharArray();
        dfs(0);
        return res.toArray(new String[res.size()]);
    }
    void dfs(int x) {
        if(x == c.length - 1) {
            res.add(String.valueOf(c)); // 添加排列方案
            return;
        }
        HashSet<Character> set = new HashSet<>();
        for(int i = x; i < c.length; i++) {
            if(set.contains(c[i])) continue; // 重复,因此剪枝
            set.add(c[i]);
            swap(i, x); // 交换,将 c[i] 固定在第 x 位 
            dfs(x + 1); // 开启固定第 x + 1 位字符
            swap(i, x); // 恢复交换
        }
    }
    void swap(int a, int b) {
        char tmp = c[a];
        c[a] = c[b];
        c[b] = tmp;
    }
}

面试题43. 1~n整数中1出现的次数

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。二刷剑指offer 22-40_第51张图片
二刷剑指offer 22-40_第52张图片
二刷剑指offer 22-40_第53张图片
在这里插入图片描述

class Solution {
    public int countDigitOne(int n) {
        int digit = 1, res = 0;
        int high = n / 10, cur = n % 10, low = 0;
        while(high != 0 || cur != 0) {
            if(cur == 0) res += high * digit;
            else if(cur == 1) res += high * digit + low + 1;
            else res += (high + 1) * digit;
            low += cur * digit;
            cur = high % 10;
            high /= 10;
            digit *= 10;
        }
        return res;
    }
}

面试题44. 数字序列中某一位的数字

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
二刷剑指offer 22-40_第54张图片
二刷剑指offer 22-40_第55张图片

class Solution {
    public int findNthDigit(int n) {
        int digit = 1;
        long start = 1;
        long count = 9;
        while (n > count) { // 1.
            n -= count;
            digit += 1;
            start *= 10;
            count = digit * start * 9;
        }
        long num = start + (n - 1) / digit; // 2.
        return Long.toString(num).charAt((n - 1) % digit) - '0'; // 3.
    }
}

面试题45. 把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
二刷剑指offer 22-40_第56张图片
二刷剑指offer 22-40_第57张图片

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]);
        Arrays.sort(strs, (x, y) -> (x + y).compareTo(y + x));
        StringBuilder res = new StringBuilder();
        for(String s : strs)
            res.append(s);
        return res.toString();
    }
}

面试题46. 把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
二刷剑指offer 22-40_第58张图片
二刷剑指offer 22-40_第59张图片

class Solution {
    public int translateNum(int num) {
        int a = 1, b = 1, x, y = num % 10;
        while(num != 0) {
            num /= 10;
            x = num % 10;
            int tmp = 10 * x + y;
            int c = (tmp >= 10 && tmp <= 25) ? a + b : a;
            b = a;
            a = c;
            y = x;
        }
        return a;
    }
}

面试题47. 礼物的最大价值

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
二刷剑指offer 22-40_第60张图片
在这里插入图片描述
当 grid矩阵很大时, i=0 或 j=0的情况仅占极少数,相当循环每轮都冗余了一次判断。因此,可先初始化矩阵第一行和第一列,再开始遍历递推。

class Solution {
    public int maxValue(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        for(int j = 1; j < n; j++) // 初始化第一行
            grid[0][j] += grid[0][j - 1];
        for(int i = 1; i < m; i++) // 初始化第一列
            grid[i][0] += grid[i - 1][0];
        for(int i = 1; i < m; i++)
            for(int j = 1; j < n; j++) 
                grid[i][j] += Math.max(grid[i][j - 1], grid[i - 1][j]);
        return grid[m - 1][n - 1];
    }
}

面试题48. 最长不含重复字符的子字符串

二刷剑指offer 22-40_第61张图片

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.containsKey(s.charAt(j)) ? dic.get(s.charAt(j)) : -1; // 获取索引 i
            dic.put(s.charAt(j), j); // 更新哈希表
            tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j]
            res = Math.max(res, tmp); // max(dp[j - 1], dp[j])
        }
        return res;
    }
}

面试题49. 丑数

我们把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
在这里插入图片描述
在这里插入图片描述
二刷剑指offer 22-40_第62张图片

class Solution {
    public int nthUglyNumber(int n) {
        int a = 0, b = 0, c = 0;
        int[] dp = new int[n];
        dp[0] = 1;
        for(int i = 1; i < n; i++) {
            int n2 = dp[a] * 2, n3 = dp[b] * 3, 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];
    }
}

面试题56 - I. 数组中数字出现的次数

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
二刷剑指offer 22-40_第63张图片

class Solution {
    public int[] singleNumbers(int[] nums) {
        int[] ans = new int[2];
        if (nums == null || nums.length < 2 || nums.length % 2 == 1) {
            return ans;
        }
        // 0 和任何数异或都等于该数本身。
        int a = 0;
        // 用 0 和数组中所有数依次进行异或运算。
        for (int num : nums) {
            a ^= num;
        }
        // 得到当前 a 的二进制表达式中最低位的 1 对应的值(负数存储形式为正数取反加一)。
        int lowbit = a & (-a);
        // 用得到的 lowbit 和数组中所有数依次进行与运算将数组分成两组数。
        for (int num : nums) {
            // 使得两个只出现一次的数不在同一组,每组分别异或运算得到唯一的数。
            if ((num & lowbit) == 0) {
                ans[0] ^= num;
            } else {
                ans[1] ^= num;
            } 
        }
        return ans;
    }
}

面试题56 - II. 数组中数字出现的次数 II

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
二刷剑指offer 22-40_第64张图片
二刷剑指offer 22-40_第65张图片

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

面试题63. 股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
二刷剑指offer 22-40_第66张图片

class Solution {
    public int maxProfit(int[] prices) {
        int cost = Integer.MAX_VALUE, profit = 0;
        for(int price : prices) {
            cost = Math.min(cost, price);
            profit = Math.max(profit, price - cost);
        }
        return profit;
    }
}

面试题59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1

class MaxQueue {
    Queue<Integer> que;
    Deque<Integer> deq;
    public MaxQueue() {
        que = new LinkedList<>();  //队列:插入和删除
        deq = new LinkedList<>();  //双端队列:获取最大值
    }    
    public int max_value() {
        return deq.size()>0?deq.peek():-1;  //双端队列的队首为que的最大值
    }    
    public void push_back(int value) {
        que.offer(value);  //value入队
        while(deq.size()>0 && deq.peekLast()<value){
            deq.pollLast();  //将deq队尾小于value的元素删掉
        }
        deq.offerLast(value);  //将value放在deq队尾
    }    
    public int pop_front() {
        int tmp = que.size()>0?que.poll():-1;  //获得队首元素
        //如果是直接peek两个队列的数比较,请注意这样就是在比较两个Integer对象而不是基本类型int,要用equals不要用==,否则对于非缓存数字(-128 - 127)可能会出现明明值一样 == 却返回false的情况!
        if(deq.size()>0 && tmp.equals(deq.peek())){
            deq.poll();  //如果出队的元素是当前最大值,将deq的队首出队
        }
        return tmp;
    }
}

面试题64. 求1+2+…+n

使用递归解法最重要的是指定返回条件,但是本题无法直接使用 if 语句来指定返回条件。
条件与 && 具有短路原则,即在第一个条件语句为 false 的情况下不会去执行第二个条件语句。利用这一特性,将递归的返回条件取非然后作为 && 的第一个条件语句,递归的主体转换为第二个条件语句,那么当递归的返回条件为 true 的情况下就不会执行递归的主体部分,递归返回。
本题的递归返回条件为 n <= 0,取非后就是 n > 0;递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。

class Solution {
    public int sumNums(int n) {
        int sum = n;
        boolean b = (n > 0) && ((sum += sumNums(n - 1)) > 0);
        return sum;
    }
}

面试题67. 把字符串转换成整数

二刷剑指offer 22-40_第67张图片

class Solution {
    public int strToInt(String str) {
        char[] c = str.trim().toCharArray();
        if(c.length == 0) return 0;
        int res = 0, bndry = Integer.MAX_VALUE / 10;
        int i = 1, sign = 1;
        if(c[0] == '-') sign = -1;
        else if(c[0] != '+') i = 0;
        for(int j = i; j < c.length; j++) {
            if(c[j] < '0' || c[j] > '9') break;
            if(res > bndry || res == bndry && c[j] > '7') return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            res = res * 10 + (c[j] - '0');
        }
        return sign * (int)res;
    }
}

困难

面试题19. 正则表达式匹配

请实现一个函数用来匹配包含’. ‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但与"aa.a"和"ab*a"均不匹配。

class Solution {
    public boolean isMatch(String s, String p) {
        char[] str = s.toCharArray();
        char[] pstr = p.toCharArray();
        int sLen = str.length, pLen = pstr.length;
        boolean[][] dp = new boolean[sLen + 1][pLen + 1];
        dp[sLen][pLen] = true;
        for (int i = sLen; i >= 0; i --) {
            // 当 i = sLen 时,即在判断 p[j:] 是否与空串匹配
            for (int j = pLen - 1; j >= 0; j --) {
                // 判断当前位置的字符是否匹配
                boolean isCurMatch = i < sLen && (str[i] == pstr[j] || pstr[j] == '.');
                // 如果 p[j + 1] 为 '*',则需要看:
                // 1. p[j + 2] 和 s[i:] 是否匹配,若匹配,则 p[j:] 一定匹配
                //   (因为'*'可以无效化前面的字母)
                // 2. 若当前位置的字符匹配,则看 p[j:] 与 s[i + 1] 是否匹配,若匹配,则一定匹配。
                //   (因为'*'可以重复前面的字母)
                if (j + 1 < pLen && pstr[j + 1] == '*') {
                    dp[i][j] = dp[i][j + 2] || isCurMatch && dp[i + 1][j];
                }
                // 否则,则看当前是否匹配,以及 p[j + 1] 和 s[i + 1] 是否匹配
                else {
                    dp[i][j] = isCurMatch && dp[i + 1][j + 1];
                }
            }
        }
        return dp[0][0];
    }
}

面试题37. 序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {
//原理:我们可以根据前序遍历的顺序来序列化二叉树,因为前序遍历是从根节点开始的。在遍历二叉树碰到 null时,将其序列化为一个特殊的字符(如'$')
//另外,节点的数值之间要用一个特殊字符(如',')隔开,因为节点的值位数不定且正负不定。
//则下面二叉树               1               可以序列化为:
//				 /  \              [1,2,4,$,$,$,3,5,$,$,6,$,$]
//				2    3
//             /    / \
//            4    5   6
//我们接着以上述字符串为例分析如何反序列化二叉树。第一个读出的数字是1。由于前序遍历是从根节点开始的,这是根节点的值。
//接下来读出的数字是2,根据前序遍历的规则,这是根节点的左子节点的值。同样,接下来的数字4是值为2的节点的左子节点。
//接着从字符串里读出两个字符'$',这表明值为4的节点的左、右子节点均不存在,因此它是一个叶节点。接下来回到值为2的节点,重建它的右子节点。
//由于下一个字符是'$',这表明值为2的节点的右子节点不存在, 2这个节点的左、右子树都己经构建完毕,接下来回到根节点,反序列化根节点的右子树
//下一个序列化字符串中的数字是3,因此右子树的根节点的值为3。它的左子节点是一个值为5的叶节点,因为接下来的三个字符是"5,$,$"。
//同样,它的右子节点是值为6的叶节点,因为最后3个字符是"6,$,$"。
    int start=0;//注意这里必须是全局变量,否则后面的迭代过程中start无法正确变化
    public String serialize(TreeNode root) {
        if(root==null) return "$";
        StringBuilder res = new StringBuilder();
        recur(root,res);
        return res.toString();
    }
    public void recur(TreeNode root,StringBuilder res){//前序遍历
        if(root==null){ 
            res.append("$,");//可以append string
            return;}
        res.append(root.val);//append int 由于int位数不定,且可正可负,因此各元素间必须用,分割
        res.append(',');//append char
        recur(root.left,res);
        recur(root.right,res);
    }
    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if(data.equals("$")) return null;//Sting值相等的判别不能用==
        String inputs[] = data.split(",");
        //虽然data中以,结尾,但是上述分割后会默认最后一个,不存在  不会使最后一个分割元素为空
        return build(inputs);
    }
    public TreeNode build(String[] inputs){
        TreeNode res;
        if(inputs[start].equals("$")){ 
            start++;
            return null;//这里说明当前节点为null,自然不存在左右节点了,直接返回
        }
        res = new TreeNode(Integer.parseInt(inputs[start]));
        start++;
        //注意:start不能以形参的形式引入build方法中,build(inputs,start);如果是这样
        //下面res.left = build(inputs,start); res.right = build(inputs,start+1);由于处于同一级迭代中start值连续
        //但实际上res.right中应该是上面res.left迭代完成后才会执行的,start不连续,因此把start作为全局变量较为合适
        res.left = build(inputs);
        res.right = build(inputs);
        return res;
    }
}
// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));

面试题51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。 归并排序
为什么当左边的数比右边的数小时统计逆序对?
左边一直比右边大 那左边的数和当前右边的数都可以组成一个逆序对 那怎么统计一共有多少个或是说直到什么时候为止呢 直到左边比右边小时 ,那左边的数和右边当前角标之前的数 都是逆序的。

/**
*归并排序稍作修改
*O(N*logN)
*O(N)
*/
class Solution {
    //记录答案
    private int res = 0;
    public int reversePairs(int[] nums) {
        int len = nums.length;

        //为了不改变原数组 新copy个数组进行计算
        int[] copy = new int[len];
        for(int i=0;i<len;i++){
             copy[i]=nums[i];
         }

        //归并排序
        mergeSort(copy,0,len-1);
        return res;
    }

    /**
    *nums 待排序数组
    *left 当前待排序区间的左下标
    *right 当前待排序区间的右下标
    */
    private 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);

        //利用一个tmp辅助数组 开始对左右两个排序后的区间合并
        int l = left,r=mid+1,cur=0;
        int[] tmp = new int[right-left+1];
        while(l<=mid&&r<=right){
            //左边区间数小于等于右边 左边先放入tmp 并更新左边指针
            if(nums[l]<=nums[r]){
                tmp[cur]=nums[l++];
                //相对于正常归并排序多出的一个步骤 计算有多少个逆序对
                res+=r-(mid+1);
            }else{
                tmp[cur]=nums[r++];
            }
            cur++;
        }
        //如果右边节点先到右区间边界导致上边while退出 
        while(l<=mid){
            tmp[cur++]=nums[l++];
            //相对于正常归并排序多出的一个步骤 计算有多少个逆序对
            res+=r-(mid+1);
        }
        while(r<=right){
            tmp[cur++]=nums[r++];
        }

        //将待排序数组 当前排好序的left~right区间重新赋值
        for(int i=0;i<tmp.length;i++){
             nums[left+i]=tmp[i];
         }

    }
}

面试题41. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值
二刷剑指offer 22-40_第68张图片
二刷剑指offer 22-40_第69张图片
二刷剑指offer 22-40_第70张图片

class MedianFinder {
    Queue<Integer> A, B;
    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;
    }
}

你可能感兴趣的:(java基础)