剑指 Offer 57

Posted lxy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剑指 Offer 57相关的知识,希望对你有一定的参考价值。

题目链接:剑指 Offer 57 - II. 和为s的连续正数序列

方法一:同向双指针

解题思路

使用两个双指针维护一个窗口,设窗口中元素的和为\\(curSum\\)。当\\(curSum > target\\)时,左指针右移一位;当\\(curSum < target\\)时,右指针右移一位;当\\(curSum == target\\)时,记录窗口内为一个答案,然后左右指针共同右移一位。
注意:由于\\(窗口大小 >= 2\\),所以当\\(r > target / 2 + 1\\)时,窗口最小的值 \\(r + (r - 1) > target\\),此时无意义,因此只取 \\(r <= target / 2 + 1\\)即可。

代码

class Solution 
public:
    vector<vector<int>> findContinuousSequence(int target) 
        vector<vector<int>> ans;
        int l = 1, r = 2, curSum = 3;
        // r 最多只能取target / 2 + 1,因为必须是两个连续数字,
        // 当r > target / 2 + 1时,r + r - 1 > target 没有意义,所以r取不到后面的值。
        while (r <= target / 2 + 1)  
            while (curSum < target) 
                r ++ ;
                curSum += r;
            
            while (curSum > target) 
                curSum -= l;
                l ++ ;
            
            if (curSum == target) 
                vector<int> oneAns;
                for (int i = l; i <= r; i ++ ) oneAns.push_back(i);
                ans.push_back(oneAns);
                curSum -= l;
                l ++ ;
                r ++ ;
                curSum += r;
            
        
        return ans;
    
;

复杂度分析

时间复杂度:\\(O(target)\\)
空间复杂度:\\(O(1)\\)

剑指 Offer 26. 树的子结构 / 剑指 Offer 27. 二叉树的镜像 / 剑指 Offer 28. 对称的二叉树 / 剑指 Offer 29. 顺时针打印矩阵

剑指 Offer 26. 树的子结构

题目描述

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

例如:
给定的树 A:

     3
    / \\
   4   5
  / \\
 1   2
给定的树 B:

   4 
  /
 1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

看到就想到的思路就是递归的判断,如果根节点相同,那么就往左右节点判断,然后我就写出了下面的代码

class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        //递归的判断,如果根节点相同,就判断左边右边结点是否也相同
        if(B == null)
            return true;
        if(A == null)
            return false;
        if(A.val != B.val){
            return isSubStructure(A.left, B) || isSubStructure(A.right, B);
        }else{
            return isSubStructure(A.left, B.left) && isSubStructure(A.right, B.right);
        }
    }
}

然后不出意外,挂了,倒在了B为空的示例上,因为题目中给出B为空,是false。但是在递归的过程中,B为空必须得返回tru,因此我把这部分重写了一个方法,又得到了下面的代码

class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        //递归的判断,如果根节点相同,就判断左边右边结点是否也相同
        if(B == null)
            return false;
        if(A == null)
            return false;
        return subtree(A, B);
    }

    public boolean subtree(TreeNode A, TreeNode B){
        if(B == null)
            return true;
        if(A == null)
            return false;
        if(A.val != B.val){
            return subtree(A.left, B) || subtree(A.right, B);
        }else{
            return subtree(A.left, B.left) && subtree(A.right, B.right);
        }
    }
}

然后又挂了,倒在了[4,2,3,4,5,6,7,8,9] [4,8,9]这个示例上,这是因为啥呢,这是因为我是在左子树上找B的左半部分,右子树上找B的右半部分,显然不对,然后又改

class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        //递归的判断,如果根节点相同,就判断左边右边结点是否也相同
        if(B == null)
            return false;
        if(A == null)
            return false;
        return subtree(A, B);
    }

    public boolean subtree(TreeNode A, TreeNode B){
        if(B == null)
            return true;
        if(A == null)
            return false;
        if(A.val != B.val){
            return subtree(A.left, B) || subtree(A.right, B);
        }else{
            return (subtree(A.left, B.left) && subtree(A.right, B.right)) || subtree(A.left, B) || subtree(A.right, B);
        }
    }
}

还是倒在了最后一个示例上[1,0,1,-4,-3] [1,-4],这次倒下其实是预料之中,因为按照我写的代码,如果根节点相同的话,我是在左右子树找剩下的部分,而这个部分不和根节点相邻我的代码也会返回true,所以继续改,这次是改成遍历所有的节点,然后判断了,这次过了
最后想想,树的题目本身比较难,这个题其实用了先序遍历+判断子树的方法,子树是一个连续的结构,不能拆开,继续加油吧

class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        //递归的判断,如果根节点相同,就判断左边右边结点是否也相同
        if(B == null)
            return false;
        if(A == null)
            return false;
        //需要遍历每一个节点
        return subtree(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
    }

    public boolean subtree(TreeNode A, TreeNode B){
        if(B == null)
            return true;
        if(A == null)
            return false;
        if(A.val != B.val){
            return false;
        }else{
            return subtree(A.left, B.left) && subtree(A.right, B.right);
        }
    }
}

剑指 Offer 27. 二叉树的镜像

题目描述

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

例如输入:

     4
   /   \\
  2     7
 / \\   / \\
1   3 6   9
镜像输出:

     4
   /   \\
  7     2
 / \\   / \\
9   6 3   1


示例 1:

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

观察可知,镜像就是将左右子树交换,层层交换就可以得到结果,然后写了这个代码

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        //一层一层翻转
        if(root == null)
            return null;
        TreeNode leftTree = root.left;
        root.left = root.right;
        root.right = leftTree;
        mirrorTree(root.left);
        mirrorTree(root.right);
        return root;
    }
}

过了,但是这里有个问题,就是这个方法是有返回值的,但是这里面调用方法的时候,并没有返回值,为什么还行的通,然后就去查了一下,有返回值的方法,可以选择用不用,没有返回值的方法,不能用

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        //先将底层翻转,自底向上
        if(root == null)
            return null;
        TreeNode leftTree = mirrorTree(root.right);
        TreeNode rightTree = mirrorTree(root.left);
        root.left = leftTree;
        root.right = rightTree;
        return root;
    }
}

剑指 Offer 28. 对称的二叉树

题目描述

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

    1
   / \\
  2   2
 / \\ / \\
3  4 4  3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

    1
   / \\
  2   2
   \\   \\
   3    3

 

示例 1:

输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:

输入:root = [1,2,2,null,3,null,3]
输出:false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

因为是对称的,所以从上到下依次判断左右子树是否相同,即left和right是否相同

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root == null)
            return true;
        return isSame(root.left, root.right);
    }

    public boolean isSame(TreeNode A, TreeNode B){
    	//如果都为空,对称
        if(A == null && B == null)
            return true;
        //单个为空,不对称
        if(A == null || B == null)
            return false;
        //数值不同,不对称
        if(A.val != B.val)
            return false;
        //数值相同,看左右
        return isSame(A.left, B.right) && isSame(A.right, B.left);
    }
}

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

题目描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。


示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

模拟这个过程,上右下左这样循环遍历,注意区间的开闭,如果行列较小值为奇数需要另外处理

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        //模拟这个过程,循环处理就行了,循环n / 2次,注意循环时候区间的开闭
        int m = matrix.length;
        if(m == 0)
            return new int[]{};
        int n = matrix[0].length;

        int l = m < n ? m : n;
        //如果是奇数,需要在后面加一行
        boolean flag = false; 
        if(l % 2 == 1)
            flag = true;
        l = l / 2;
        int[] res = new int[m * n];

        int index = 0;
        for(int k = 0; k < l; k++){
            //所有区间都是左闭右开,上面的行
            for(int i = k; i < n - k - 1; i++){
                res[index++] = matrix[k][i];
            }
            //右边的列
            for(int i = k; i < m - k - 1; i++){
                res[index++] = matrix[i][n - k - 1];
            }
            //下面的行
            for(int i = n - k - 1; i > k; i--){
                res[index++] = matrix[m - k - 1][i];
            }
            //左边的列
            for(int i = m - k - 1; i > k; i--){
                res[index++] = matrix[i][k];
            }
        }
        //此时,最多只剩下一行或者一列了
        if(flag){
            for(int i = l; i <= m - l - 1; i++){
                for(int j = l; j <= n - l - 1; j++){
                    res[index++] = matrix[i][j];
                }
            }
        }
        return res;
    }
}

这里复制一个优雅的转圈遍历代码,几个月之前的,找不到是谁的了,不好意思,再学习一下

class Solution {
    //转圈遍历的优雅代码
    public List<Integer> spiralOrder(int[][] matrix) {
        LinkedList<Integer> result = new LinkedList<>();
        if(matrix==null||matrix.length==0) return result;
        int left = 0;   //左边
        int right = matrix[0].length - 1;   //右边
        int top = 0;    //上边
        int bottom = matrix.length - 1; //下边
        //总共的元素个数
        int numEle = matrix.length * matrix[0].length;
        while (numEle >= 1) {
            //最上面一行,从left到right
            for (int i = left; i <= right && numEle >= 1; i++) {
                result.add(matrix[top][i]);
                numEle--;
            }
            //然后top++
            top++;
            //最右边一列,从top到bottom
            for (int i = top; i <= bottom && numEle >= 1; i++) {
                result.add(matrix[i][right]);
                numEle--;
            }
            right--;
            //最下面一行,从right 到 left;注意到right已经减1了
            for (int i = right; i >= left && numEle >= 1; i--) {
                result.add(matrix[bottom][i]);
                numEle--;
            }
            bottom--;
            //最左面一列,bottom到top,注意到bottom也减1了
            for (int i = bottom; i >= top && numEle >= 1; i--) {
                result.add(matrix[i][left]);
                numEle--;
            }
            left++;
        }
        return result;
    }
}

还有官解中的这个直接模拟的方法,就是朝着一个方向走,如果到头了就换另一个方向,另外还需要一个矩阵来记录每个元素是否已经被访问了。感觉更不好想,而且实现起来也比较难。

以上是关于剑指 Offer 57的主要内容,如果未能解决你的问题,请参考以下文章

乱序版 ● 剑指offer每日算法题打卡题解—— 双指针(题号21,57,58)

剑指Offer 57

乱序版 ● 剑指offer每日算法题打卡题解——数学 (题号14,57)

0510-II173942555757-II64

剑指offer 面试57题

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