剑指Offer(二)

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

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

示例:

现有矩阵 matrix 如下:

[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。

给定 target = 20,返回 false。

思路(参考题解)

image-20211230203127749

从左下角开始遍历,i表示行,j表示列。i从最大行开始遍历(matrix.length - 1),如果目标值<当前值,说明该行大于目标值,因为是行递增的;j从第0列开始遍历,如果目标值>当前值,说明该行后面可能有满足条件的值,列方向往后移。找到满足条件的值返回true

public class findNumberIn2DArray {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        //(从左下角开始)i:行 j:列
        int i = matrix.length - 1, j = 0;
        while (i >= 0 && j < matrix[0].length) {
            //如果目标值<当前值,说明该行大于目标值,因为是行递增的
            if (target < matrix[i][j]) {
                i --;
                //如果目标值>当前值,说明该行后面可能有满足条件的值,列方向往后移
            } else if (target > matrix[i][j]) {
                j ++;
            } else {
                //找到目标值,返回true
                return true;
            }
        }
        return false;
    }
}
2、剑指 Offer 11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为1。

示例 1:

输入:[3,4,5,1,2]
输出:1

思路:

暴力解法:遍历查找最小值

public int minArray(int[] numbers) {
        //暴力解法
        int res = numbers[0];
        for (int i = 1; i < numbers.length; i ++) {
            if (numbers[i] < res) {
                res = numbers[i];
            }
        }
        return res;
    }

二分法

最小值元素一定在右排序数组中,因此通过二分法找到右排序数组,其中首位就是最小值。本题使用j与mid的值进行比较,从而缩小区间,不能用i,因为当mid>i时,无法判断mid在哪个排序数组中 ,如果与j比较,j肯定在右排序数组中。

    public int minArray2(int[] numbers) {
        //二分法
        //旋转数组 最小值肯定在右排序数组
        int i = 0, j = numbers.length - 1;
        while (i < j) {
            int mid = (i + j) / 2;
            //mid在左排序数组中
            if (numbers[mid] > numbers[j]) {
                i = mid + 1;
                //mid在右排序数组中
            } else if(numbers[mid] < numbers[j]) {
                j = mid;
            } else {
                j --;
            }
        }
        return numbers[i];
    }
}
3、剑指 Offer 50. 第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

示例 1:

输入:s = “abaccdeff”
输出:‘b’

思路:哈希表

先将字符串转为字符数组,第一次遍历,如果该字符是第一次出现,添加键值对dic(c,true),如果不是第一次出现,则添加键值对dic(c,false);第二次遍历,查找第一个值为true的字符,找到返回字符。如果没有,返回空格。

public class firstUniqChar {
    public char firstUniqChar2(String s) {
        //参考题解 哈希表
        //dic(c,true)
        HashMap<Character, Boolean> dic = new HashMap<Character, Boolean>();
        char[] sc = s.toCharArray();//字符串转为字符数组
        //第一次遍历,将所有字符添加到map
        for (char c : sc) {
            //如果map中不包含字符c,第一次出现,添加键值对dic(c,true)
            if (! dic.containsKey(c)) {
                dic.put(c, true);
            } else {
                //如果包含了字符c,说明重复出现,添加键值对dic(c,false)
                dic.put(c, false);
            }
        }
        //第二次遍历,返回map中的第一个值
        for (char c : sc) {
            //dic.get(c)为true,说明只出现了1次,返回
            if (dic.get(c)) {
                return c;
            }
        }
        return ' ';
    }
}
4、面试题32 - I. 从上到下打印二叉树

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

例如: 给定二叉树: [3,9,20,null,null,15,7],

​ 3
/
9 20
​ /
15 7
返回:[3,9,20,15,7]

思路:二叉树层次遍历,利用队列

先遍历根节点,如果根节点为空,返回空数组;如果根节点不为空,将根节点入队,根节点的值放入数组列表list中(动态数组可以自动扩充,最终放入数组中用于返回),依次判断当前节点的左右节点,不为空则放入队列中,进行下一次的遍历。

//二叉树节点
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) {
        val = x;
    }
}
//层次遍历
public class levelOrder {
    public int[] levelOrder(TreeNode root) {
        //如果是空树,返回空数组
        if (root == null) {
            return new int[0];
        }

        ArrayList<Integer> list = new ArrayList<>();//动态数组列表:用于存放返回值
        Queue<TreeNode> queue = new LinkedList<>();//队列:用于存放二叉树节点
        queue.add(root);//根节点不为空,先入队
        //队列不为空
        while (! queue.isEmpty()) {
            TreeNode node = queue.poll();//移除并返回队头节点
            list.add(node.val);//将队头节点的值放入list
            //判断当前节点的左右节点是否为空,如果不为空,放入节点队列
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
        int[] res = new int[list.size()];//返回数组
        //遍历动态数组列表,将其值存入返回数组
        for (int i = 0; i < list.size(); i ++) {
            res[i] = list.get(i);
        }
        return res;
    }
}
5、剑指 Offer 32 - II. 从上到下打印二叉树 II

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

例如: 给定二叉树: [3,9,20,null,null,15,7],

​ 3
/
9 20
​ /
15 7
返回其层次遍历结果:

[
[3],
[9,20],
[15,7]
]

思路:与上一题基本相似,不同点就是每层遍历需要单独打印

每一轮遍历的队列中存放的就是该层的节点,需要增加一个for循环遍历,判断该层遍历是否完成,一层一层的处理

使用的是双层List的形式返回:List<List<Integer>>,内层List<Integer>表示每层的数,外层List表示多少层

public class levelOrder2 {
    public List<List<Integer>> levelOrder(TreeNode root) {
        //List<List<Integer>>:内层List<Integer>表示每层的数,外层List表示多少层
        List<List<Integer>> res = new ArrayList<>();//存放返回数组列表
        Queue<TreeNode> queue = new LinkedList<>();//队列:用于存放二叉树节点
        if (root != null) {
            queue.add(root);//根节点不为空,先入队
        }
        //队列不为空
        while (! queue.isEmpty()) {
            List<Integer> temp = new ArrayList<>();//存放当层的数
            //当前queue中就是该当前层的节点数,一层一层处理
            for (int i = queue.size(); i > 0; i --) {
                TreeNode node = queue.poll();//移除并返回队头节点
                temp.add(node.val);//将当层节点的值放入temp
                //判断当前节点的左右节点是否为空,如果不为空,放入节点队列,此处为下一层
                if (node.left != null) {
                    queue.add(node.left);
                }
                if (node.right != null) {
                    queue.add(node.right);
                }
            }
            //当层节点值全部放入temp,处理完一层后,将整个temp放入res
            res.add(temp);
        }
        return res;
    }
}
6、剑指 Offer 32 - III. 从上到下打印二叉树 III

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

例如:给定二叉树: [3,9,20,null,null,15,7],

​ 3
/
9 20
​ /
15 7
返回其层次遍历结果:

[
[3],
[20,9],
[15,7]
]

思路:跟上题类似,不同点是:对每层输出是进行判断,奇数层直接打印,偶数层逆序之后再打印

public class levelOrder3 {
    public List<List<Integer>> levelOrder(TreeNode root) {
        //List<List<Integer>>:内层List<Integer>表示每层的数,外层List表示多少层
        List<List<Integer>> res = new ArrayList<>();//存放返回数组列表
        Queue<TreeNode> queue = new LinkedList<>();//队列:用于存放二叉树节点
        if (root != null) {
            queue.add(root);//根节点不为空,先入队
        }
        //队列不为空
        int j = 0;//记录当前层是奇数还是偶数,奇数层从左往右打印,偶数层从右往左打印,即逆序
        while (! queue.isEmpty()) {
            j ++;
            List<Integer> temp = new ArrayList<>();//存放当层的数
            //当前queue中就是该当前层的节点数,一层一层处理
            for (int i = queue.size(); i > 0; i --) {
                TreeNode node = queue.poll();//移除并返回队头节点
                temp.add(node.val);//将当层节点的值放入temp
                //判断当前节点的左右节点是否为空,如果不为空,放入节点队列,此处为下一层
                if (node.left != null) {
                    queue.add(node.left);
                }
                if (node.right != null) {
                    queue.add(node.right);
                }
            }
            //当层节点值全部放入temp,处理完一层后,将整个temp放入res
            //如果是奇数层,直接放入res,如果是偶数层,逆序之后再放入res
            if (j % 2 == 0) {
                Collections.reverse(temp);
            }
            res.add(temp);
        }
        return res;
    }
}
7、剑指 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

public class isSubStructure2 {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        //1.当A B任一个为空,返回false
        //2.recur(A, B):以A为根节点的子树包含B
        //3.isSubStructure(A.left, B):B是A的左子树结构;isSubStructure(A.right, B):B是A的右子树结构
        return (A != null && B != null && (recur(A, B) ||
                isSubStructure(A.left, B) || isSubStructure(A.right, B)));
    }

    public boolean recur(TreeNode A, TreeNode B) {
        //3个终止条件
        //1.B为空,说明树B已匹配完成,返回true
        if (B == null) {
            return true;
        }
        //2.A为空,说明已经越过A,匹配失败,返回false
        //3.A B的值不同,匹配失败,返回false
        if (A == null || A.val != B.val) {
            return false;
        }
        //继续匹配子节点
        return recur(A.left, B.left) && recur(A.right, B.right);
    }
}
//第二种写法
public class isSubStructure {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        //A B有一个为空,则不能是子结构
        if (A == null || B == null) {
            return false;
        }
        //如果A B根节点的值相等,则继续比较后续节点(A的左右节点、B的左右节点),使用方法helper比较后续节点
        if (A.val == B.val && (helper(A.left, B.left) && helper(A.right, B.right))) {
            return true;
        }
        //如果A B的根节点不同,则将A的左右子节点分别再与B进行比较
        return isSubStructure(A.left, B) || isSubStructure(A.right, B);
    }

    //判断后续节点
    public boolean helper(TreeNode root1, TreeNode root2) {
        //B的子节点为空,如果之前节点是其子结构,则返回true
        if (root2 == null) {
            return true;
        }
        //B的子节点不为空,A的子节点为空,表明B不是A的子结构,返回false
        if (root1 == null) {
            return false;
        }
        //A B子节点都不为空,且其值相等,继续比较后续节点(左右子节点)
        if (root1.val == root2.val) {
            return helper(root1.left, root2.left) && helper(root1.right, root2.right);
        } else {
            //A B子节点的值不相等,表明B不是A的子结构,返回false
            return false;
        }
    }
}
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 则可构成顺子
    }
}
8、剑指 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]

思路:

遍历二叉树,交换每个节点的左右节点

public class mirrorTree {
    public TreeNode mirrorTree(TreeNode root) {
        //当前根节点为空,表明已经越过叶子结点,返回null
        if (root == null) {
            return null;
        }
        TreeNode tmp = root.left;//保存根节点的左节点
        root.left = mirrorTree(root.right);//对右子树镜像,赋值给左节点
        root.right = mirrorTree(tmp);//对左子树镜像,赋值给右节点
        return root;
    }
}
9、剑指 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

思路:

从上至下遍历,判断根节点的左右节点是否是对称的

public class isSymmetric {
    public boolean isSymmetric(TreeNode root) {
        //空树对称
        if (root == null) {
            return true;
        }else {
            return recur(root.left, root.right);//判断根节点的左右子树是否对称
        }
    }

    public boolean recur(TreeNode leftNode, TreeNode rightNode) {
        //左右子树都为空,对称
        if (leftNode == null && rightNode == null) {
            return true;
        }
        //左右子树只有一个为空,或者都不为空但其值不同,不对称
        if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
            return false;
        }
        //继续判断左右子树的左右子树 注意:左右节点的对应关系
        return recur(leftNode.left, rightNode.right) && recur(rightNode.left, leftNode.right);
    }
}
10、剑指 Offer 10- I. 斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:1

class Solution {
    public int fib(int n) {
        if(n == 0) return 0;
        int fn0 = 0;
        int fn1 = 1;
        int temp;
        for(int i = 2; i <= n; i++){
            temp = fn0 + fn1;
            fn0 = fn1;
            fn1 = temp% 1000000007; // 每次运算都取模,避免越界
        }
        return fn1;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值