• 力扣记录:剑指offer(7)——JZ59-68


    JZ59-I 滑动窗口的最大值

    • 使用双端队列(尾入头出)实现单调队列(出队队首为最大值,可自定义类实现),根据窗口长度将数组中数字分别入队出队:入队时如果队尾元素小于当前元素,则弹出队尾元素再将当前元素入队(队尾元素的位置在当前元素之前,一定先出队);出队时如果当前元素等于队首元素,则弹出队首元素,否则不处理(队列中较小值且下标在前的元素已经弹出)
      • 时间复杂度O(n),空间复杂度O(k)
    class Solution {
        public int[] maxSlidingWindow(int[] nums, int k) {
            if(nums.length == 0) return new int[0];
            //使用双端队列(尾入头出)实现单调队列(出队队首为最大值,可自定义类实现)
            Deque<Integer> deque = new LinkedList<>();
            int[] res = new int[nums.length - k + 1];
            int index = 0;
            //初始化
            for(int i = 0; i < k; i++){
                //入队时如果队尾元素小于当前元素,则弹出队尾元素再将当前元素入队
                //(队尾元素的位置在当前元素之前,一定先出队)
                while(!deque.isEmpty() && deque.peekLast() < nums[i]) deque.pollLast();
                deque.offer(nums[i]);
            }
            res[index++] = deque.peek();
            //滑动窗口移动
            for(int i = k; i < nums.length; i++){
                //出队时如果当前元素等于队首元素,则弹出队首元素,否则不处理
                //(队列中较小值且下标在前的元素已经弹出)
                if(nums[i - k] == deque.peek()) deque.poll();
                while(!deque.isEmpty() && deque.peekLast() < nums[i]) deque.pollLast();
                deque.offer(nums[i]);
                res[index++] = deque.peek();
            }
            return res;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    JZ59-II 队列的最大值

    • 使用两个双端队列(尾入头出)实现单调队列(出队队首为最大值),入队时如果队尾元素小于当前元素,则弹出队尾元素再将当前元素入队(队尾元素的位置在当前元素之前,一定先出队),出队列正常入队;出队时如果出队列不为空,且出队列队首元素等于队列队首元素,则弹队列队首元素(队列中较小值且下标在前的元素已经弹出),出队列正常出队
      • 时间复杂度O(1),空间复杂度O(n)
    class MaxQueue {
        Deque<Integer> deque;
        Deque<Integer> dequeOut;
        public MaxQueue() {
            //使用双端队列(尾入头出)实现单调队列(出队队首为最大值)
            deque = new LinkedList<>();
            dequeOut = new LinkedList<>();
        }
        
        public int max_value() {
            if(!deque.isEmpty()) return deque.peek();
            return -1;
        }
        //入队时如果队尾元素小于当前元素,则弹出队尾元素再将当前元素入队
        //(队尾元素的位置在当前元素之前,一定先出队)
        //出队列正常入队
        public void push_back(int value) {
            while(!deque.isEmpty() && deque.peekLast() < value) deque.pollLast();
            deque.offer(value);
            dequeOut.offer(value);
        }
        //出队时如果出队列不为空,且出队列队首元素等于队列队首元素,则弹队列队首元素
        //出队列正常出队
        public int pop_front() {
            if(!dequeOut.isEmpty()){
                int out = dequeOut.peek();  //两个Integer不能直接用==比较,需要手动拆箱
                if(out == deque.peek()) deque.poll();
                return dequeOut.poll();
            }
            return -1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    JZ60 n个骰子的点数

    • 动态规划,定义二维dp数组表示投掷完i个骰子后,点数j的出现次数;投掷完第i个骰子后,点数j的出现次数可以由投掷完i-1个骰子后点数j-1,j-2,……,j-6推出(第i个骰子可能为1-6);注意点数应该 > 0,可以使用一维数组优化
      • 时间复杂度O(n^2),空间复杂度O(n)
    class Solution {
        public double[] dicesProbability(int n) {
            //动态规划
            //定义二维dp数组表示投掷完i个骰子后,点数j的出现次数为dp[i][j]
            int[][] dp = new int[n + 1][n * 6 + 1];
            //初始化
            for(int i = 1; i <= 6; i++){
                dp[1][i] = 1;
            }
            //投掷完第i个骰子后,点数j的出现次数可以由投掷完i-1个骰子后点数j-1,j-2,……,j-6推出
            //(第i个骰子可能为1-6)
            for(int i = 2; i <= n; i++){
                for(int j = i; j <= 6 * i; j++){
                    for(int k = 1; k <= 6; k++){
                        if(j <= k) break;   //注意点数应该 > 0
                        dp[i][j] += dp[i - 1][j - k];
                    }
                }
            }
            double[] res = new double[5 * n + 1];
            int index = 0;
            double sum = Math.pow(6, n);
            for(int i = n; i <= 6 * n; i++){
                res[index++] = dp[n][i] / sum;
            }
            return res;
        }
    }
    //一维数组优化
    class Solution {
        public double[] dicesProbability(int n) {
            //动态规划
            //定义dp数组表示投掷完i个骰子后,点数j的出现次数为dp[j]
            int[] dp = new int[n * 6 + 1];
            //初始化
            for(int i = 1; i <= 6; i++){
                dp[i] = 1;
            }
            //投掷完第i个骰子后,点数j的出现次数可以由投掷完i-1个骰子后点数j-1,j-2,……,j-6推出
            //(第i个骰子可能为1-6)
            for(int i = 2; i <= n; i++){
                for(int j = 6 * i; j >= i; j--){
                    dp[j] = 0;  //需要进行初始化,防止多出上一次的和
                    for(int k = 1; k <= 6; k++){
                        if(j - k < i - 1) break;   //注意点数应该 > 0,且不能超过上一次和的下限
                        dp[j] += dp[j - k];
                    }
                }
            }
            double[] res = new double[5 * n + 1];
            int index = 0;
            double sum = Math.pow(6, n);
            for(int i = n; i <= 6 * n; i++){
                res[index++] = dp[i] / sum;
            }
            return res;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    JZ61 扑克牌中的顺子

    • 先将数组排序,从头开始遍历,记录0的数量,如果前后相差大于1则用0去填补,最后通过0的数量判断是否顺子,注意:当出现两张一样的不为0的数字时必不为顺子
      • 时间复杂度O(nlogn),空间复杂度O(1)
    class Solution {
        public boolean isStraight(int[] nums) {
            //先将数组排序,从头开始遍历,记录0的数量
            Arrays.sort(nums);
            int count = 0;
            for(int i = 0; i < 5; i++){
                if(nums[i] == 0){
                    count++;
                }else{
                    if(i > 0 && nums[i - 1] != 0){
                        //当出现两张一样的不为0的数字时必不为顺子
                        if(nums[i] == nums[i - 1]) return false;
                        //如果前后相差大于1则用0去填补
                        if(nums[i] - nums[i - 1] > 1){
                            count -= nums[i] - nums[i - 1] - 1;
                        }
                    }
                }
            }
            //最后通过0的数量判断是否顺子
            return count >= 0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 使用set,遍历数组,将除了0的数字加入set中(加入前先判断是否重复),记录最大和最小值,若差值小于5则为顺子
      • 时间复杂度O(n),空间复杂度O(1)
    class Solution {
        public boolean isStraight(int[] nums) {
            //使用set
            Set<Integer> set = new HashSet<>();
            //记录最大和最小值,若差值小于5则为顺子
            int max = 0;
            int min = 14;
            //遍历数组,将除了0的数字加入set中
            for(int num : nums){
                if(num == 0) continue;
                if(set.contains(num)) return false; //加入前先判断是否重复
                set.add(num);
                if(num > max) max = num;
                if(num < min) min = num;
            }
            return max - min < 5;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    JZ62 圆圈中最后剩下的数字

    • 反推解决约瑟夫环问题,找规律:首先正推,数组第一位(每次删除后的下一位)的下标变化规律为+m后对当时数组长度取模,直到数组剩最后一位(下标为0);反推得到数组最后下标为0在开始时的下标,从0开始每次+3后对当时数组长度(从2开始)取模,直到回到最开始的数组
      • 时间复杂度O(n),空间复杂度O(1)
    class Solution {
        public int lastRemaining(int n, int m) {
            //定义最后留下的数下标
            int k = 0;
            //反推
            for(int i = 2; i <= n; i++){
                k = (k + m) % i;
            }
            return k;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    JZ63 股票的最大利润

    • 动态规划,定义dp数组表示第i天卖出股票(在0到i-1天买入)的最大利润为dp[i]
      • 时间复杂度O(n),空间复杂度O(n)
    class Solution {
        public int maxProfit(int[] prices) {
            if(prices.length == 0) return 0;
            //动态规划
            int[] dp = new int[prices.length];
            int max = 0;
            //初始化
            dp[0] = 0;
            for(int i = 1; i < prices.length; i++){
                dp[i] = Math.max(dp[i - 1] + prices[i] - prices[i - 1], 0);
                max = Math.max(max, dp[i]);
            }
            return max;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 贪心,收集最小低的买入价和最高的利润
      • 时间复杂度O(n),空间复杂度O(1)
    class Solution {
        public int maxProfit(int[] prices) {
            if(prices.length == 0) return 0;
            //贪心
            int low = prices[0];
            int profit = 0;
            for(int i = 1; i < prices.length; i++){
                low = Math.min(low, prices[i]);
                profit = Math.max(profit, prices[i] - low);
            }
            return profit;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    JZ64 求1+2+…+n

    • 递归,不适用if,使用与或非终止递归(n = 1时)
      • 时间复杂度O(n),空间复杂度O(n)
    class Solution {
        int res = 0;
        public int sumNums(int n) {
            //递归
            boolean flag = n > 0 && sumNums(n - 1) > 0;
            res += n;
            return res;
        }
        //简化
        public int sumNums(int n) {
            //递归
            boolean flag = n > 0 && (n += sumNums(n - 1)) > 0;
            return n;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    JZ65 不用加减乘除做加法

    • 使用异或运算(无进位)和与运算(有进位),注意计算进位时左移一位(进的是下一位),和为无进位+进位(再次循环)
      • 时间复杂度O(1),空间复杂度O(1)
    class Solution {
        public int add(int a, int b) {
            //使用异或运算(无进位)和与运算(有进位)
            while(b != 0){
                int c = (a & b) << 1; //计算进位左移一位(进的是下一位),与运算优先级较低
                a ^= b;  //计算非进位
                b = c;  //当进位不为0时循环计算a+b(非进位+进位)
            }
            return a;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    JZ66 构建乘积数组

    • 定义两个dp数组,分别存储i左边和i右边的乘积,可以不用dp数组直接遍历计算存储结果,优化空间
      • 时间复杂度O(n),空间复杂度O(n),优化为O(1)
    class Solution {
        public int[] constructArr(int[] a) {
            int leng = a.length;
            if(leng == 0) return new int[0];
            //定义两个dp数组表示数组a下标i(不包括i)之前、之后的乘积分别为dpL[i]、dpR[i]
            int[] dpL = new int[leng];
            int[] dpR = new int[leng];
            //初始化
            dpL[0] = 1;
            dpR[leng - 1] = 1;
            for(int i = 1; i < leng; i++){
                dpL[i] = dpL[i - 1] * a[i - 1];
            }
            for(int j = leng - 2; j >= 0; j--){
                dpR[j] = dpR[j + 1] * a[j + 1];
            }
            //左右相乘得到结果
            int[] b = new int[leng];
            for(int i = 0; i < leng; i++){
                b[i] = dpL[i] * dpR[i];
            }
            return b;
        }
        //优化
        public int[] constructArr(int[] a) {
            int leng = a.length;
            if(leng == 0) return new int[0];
            //空间优化
            int[] b = new int[leng];
            b[0] = 1;
            //计算i左边乘积
            for(int i = 1; i < leng; i++){
                b[i] = b[i - 1] * a[i - 1];
            }
            int temp = 1; //计算右边乘积直接乘到b[i]上
            //计算i右边乘积
            for(int j = leng - 2; j >= 0; j--){
                temp *= a[j + 1];   //右边乘积
                b[j] *= temp;   //直接乘到b[i]
            }
            return b;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    JZ67 把字符串转换成整数

    • 直接遍历,如果第一位为非符号非字符直接返回0,如果第一位为符号位则存储符号位(方便后面返回),存储数字拼接的结果(每遍历一位结果乘10),注意:每次拼接前进行判断是否越界。也可以使用状态机(相似题目:JZ20、JZ56-II)
      • 时间复杂度O(n),空间复杂度O(n),不使用trim()则为O(1)
    class Solution {
        public int strToInt(String str) {
            //直接遍历
            //使用trim()去除首尾空格
            str = str.trim();
            if(str.length() == 0) return 0;
            char[] strArr = str.toCharArray();  //转为字符数组
            int border = Integer.MAX_VALUE / 10;    //防止越界
            int i = 1;
            int flag = 1;   //符号位,+为1,-为-1
            if(strArr[0] == '-'){
                flag = -1;
            }else if(strArr[0] != '+'){
                i = 0;  //无符号位从0开始
            }
            int res = 0;
            for(int j = i; j < strArr.length; j++){
                if(strArr[j] < '0' || strArr[j] > '9') break;   //到非字符时终止
                //判断是否越界
                if(res > border || (res == border && strArr[j] > '7')) return flag == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
                res = res * 10 + (strArr[j] - '0'); //拼接
            }
            return flag * res;
        }
    }
    //不适用trim(),不转为字符数组,直接在原字符串上遍历
    class Solution {
        public int strToInt(String str) {
            //直接遍历
            int border = Integer.MAX_VALUE / 10;    //防止越界
            int i = 0;
            //手动跳过空格
            while(i < str.length()){
                if(str.charAt(i) != ' ') break;
                i++;
            }
            if(i == str.length()) return 0; //字符串为空
            int flag = 1;   //符号位,+为1,-为-1
            if(str.charAt(i) == '-'){
                flag = -1;
                i++;
            }else if(str.charAt(i) == '+'){
                i++;  //无符号位从i + 1开始
            }
            int res = 0;
            for(int j = i; j < str.length(); j++){
                if(str.charAt(j) < '0' || str.charAt(j) > '9') break;   //到非字符时终止
                //判断是否越界
                if(res > border || (res == border && str.charAt(j) > '7')) return flag == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
                res = res * 10 + (str.charAt(j) - '0'); //拼接
            }
            return flag * res;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    JZ68-I 二叉搜索树的最近公共祖先

    • 后序遍历,从底向上递归回溯,如果当前节点为要查找的节点或为空则直接返回当前节点,如果递归左(右)子树返回节点不为空,说明有一个目标节点在左(右)子树:若当前节点左右子树均不为空,则返回当前节点(当前节点为最近公共祖先),若当前节点只有一个子树不为空,则返回该子树,否则返回空。
      • 时间复杂度O(n),空间复杂度O(n)
    class Solution {
        public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
            //从底向上递归回溯
            //如果当前节点为要查找的节点或为空则直接返回当前节点
            if(root == p || root == q || root == null) return root;
            //后序遍历
            TreeNode left = lowestCommonAncestor(root.left, p, q);
            TreeNode right = lowestCommonAncestor(root.right, p, q);
            //如果递归左(右)子树返回节点不为空,说明有一个目标节点在左(右)子树
            //若当前节点左右子树均不为空,则返回当前节点(当前节点为最近公共祖先)
            if(left != null && right != null){
                return root;
            }else if(left != null && right == null){//若当前节点只有一个子树不为空,则返回该子树
                return left;
            }else if(left == null && right != null){
                return right;
            }
            //否则返回空
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    JZ68-II 二叉树的最近公共祖先

    • 迭代,对于二叉搜索树(中序遍历为递增序列,左小右大),从上往下遍历,只要遍历时发现某个节点值在目标节点值之间,当前节点即为最近公共祖先
      • 时间复杂度O(n),空间复杂度O(1)(递归为O(n))
    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{
                    return root;
                }
            }
            return null;    //没找到
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    js获取Element元素的常用方法
    uniapp开发app——nvue
    Qt提高-线程池QThreadPool 详解
    设计模式学习笔记 - 开源实战四(下):总结Spring中用到的11种设计模式
    flink本地IDEA测试checkpoint
    Windows线程 信号量 CreateSemaphore创建信号量、RelaseSemaphore设置信号量
    微信原生小程序直传上传阿里云OSS完整代码
    Leetcode 220. Contains Duplicate III (Sliding window + set)
    基于Springboot+Vue的网上蛋糕销售系统(含源码数据库)
    07-Go语言中map数据结构用法
  • 原文地址:https://blog.csdn.net/Kiwi_fruit/article/details/125751660