第一周刷题

① 104.二叉树的最大深度

https://leetcode-cn.com/problems/maximum-depth-of-binary-tree

拿到这道题目,首先想的是要根据层序来遍历,但地总说了得要递归,就试着用了深度遍历的方法。

深度遍历的思路有两种

思路一:从上到下计算(前序遍历)

  • 从根节点开始,每下降一层,就将深度+1
  • 用全局变量来记录下最大深度
  • 每当达到叶子节点时就与全局变量进行比较和更新。
class Solution {
    int res = 0;     // 全局变量,存放结果
    public int maxDepth(TreeNode root) {
        dfs(root, 0);        // 深度遍历,一开始的深度为 0
        return this.res;     // 返回结果
    }

    public void dfs(TreeNode root, int length){
        if (root == null){      // 如果到达叶子节点,就更新结果
            res = Math.max(this.res, length);
            return;
        }
        // 由于当前节点有值,故深度要+1
        dfs(root.left, length+1);    // 查找左节点
        dfs(root.right, length+1);   // 查找右节点
    }
}

思路二:从下到上(后序遍历)

  • 若我们知道两个叶子节点的长度,那么我们就能通过两个叶子的最大深度再+1,得到根节点的最大深度。
  • 而先知道两个子节点,再知道根节点恰好符合后序遍历的特点。
class Solution {
    public int maxDepth(TreeNode root) {
        return dfs(root);		// 返回当前节点的最大深度
    }

    public int dfs(TreeNode root){
        if (root == null){    // 如果是空节点,则深度为 0
            return 0;
        }
        int leftLength = dfs(root.left);  // 遍历左节点得到左节点的最大深度
        int rightLength = dfs(root.right);   // 遍历右节点得到右节点的最大深度
        return Math.max(leftLength, rightLength) + 1;  // 返回两个节点的最大深度,再加根节点的深度(+1)
    }
}

思路三:层序遍历

  • 使用队列来存放节点
  • 一开始先知道当前层数的节点个数,然后根据个数出队,并对其孩子节点入队
  • 每经过一层,res + 1
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        Deque<TreeNode> deque = new LinkedList<>(); // 队列
        deque.offer(root);                          // 第一层
        int res = 0;
        while (!deque.isEmpty()){
            res += 1;                             // 层数
            int length = deque.size();            // 找到当前层的节点数
            for (int i=0; i < length; i++){     // 清空当前层,并把孩子节点入队
                TreeNode node = deque.pollFirst(); 
                if (node.left != null)
                    deque.offer(node.left);
                if (node.right != null)
                    deque.offer(node.right);
            }
        }
        return res;
    }
}



② 62. 不同路径

https://leetcode-cn.com/problems/unique-paths/

思路一:递归回溯 + 剪枝

这道题是经典的 dp 问题,一开始想到的也是 dp。但要求用递归来做,那么就想了一下。

  • 一个最重要的步骤就是: dfs(i, j) = dfs(i+1, j) + dfs(i, j+1)
  • 递归的出口就是当到达边界时,直接返回1。
    • 如果是下边界,那么就只有一条路径(横着走到终点)
    • 如果是右边界,那么就只有一条路径(竖着走到达终点)
  • 还有一个重要的问题:需要剪枝。

所以这个问题也就是按照 回溯法 + 剪枝 的思路来弄。
剪枝的思路:

  • 当这个点已经知道到终点总共有几条路径,就直接给出路径数就行。
  • 要实现这个方法,就通过一个 map 来记录下点的路径数。
  • 每个点的唯一化可以通过 i*n + j 来表示,故 map 可以为 HashMap<Integer, Integer>
class Solution {
    HashMap<Integer, Integer> map;          // 记录下点到终点的路径数
    int m, n;
    public int uniquePaths(int m, int n) {
        map = new HashMap<>();
        this.m = m;
        this.n = n;
        return dfs(0, 0);
    }

    public int dfs(int i, int j){
        if (i == m-1 || j == n-1)     // 如果到达边界
            return 1;
        int x = i*n + j;              // 计算点的唯一标识
        if (map.get(x) != null){      // 是否以算过这个点
            return map.get(x);
        }
        map.put(x, dfs(i+1, j) + dfs(i, j+1));   // 将计算的点放到 map 中
        return map.get(x);
    }
}

完成这个题还是胆战心惊的。因为在测试数据时,输入 m=100, n=100 直接报了错如下:
在这里插入图片描述
再缩小范围到 m=60, n=60,还是报错。

继续缩小,m=30, n=30,还在报错!!!

到了 m=15, n=15,才计算出来。这让我非常的胆怯。

去看了看题解,发现有些做得没有这方法好还过了,手抖的点了提交,就通过了!!!

原来提供的范围是骗人的!!

思路二:动态规划

思路:

  • 如果已知从原点走到这个点,共有几个路径,那么递推过去就能知道从原点到终点共有几个路径。
  • 状态转移方程为:dp[i][j] = dp[i-1][j] + dp[i][j-1]
  • 初始化 dp,
    • 第一行肯定都为 1,因为只能往下或往右走。
    • 第一列肯定都为 1,原因如上。
  • 在按状态转移方程就能得到。
class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];
        for (int i=0; i < m; i++){     // 初始化 dp
            dp[i][0] = 1;
        }
        for(int i=0; i < n; i++){
            dp[0][i] = 1;
        }
        for(int i=1; i < m; i++){
            for(int j=1; j < n; j++){
                dp[i][j] = dp[i-1][j] + dp[i][j-1];  // 根据状态转移方程得到
            }  
        }
        return dp[m-1][n-1];
    }
}

思路三:状态压缩

从上面的状态转移方程中可看到,我们只需要两行的结果。故我们可以只创建两个一位数组,分别对应前一行和后一行,从而将二维数组转为两个一位数组。

再继续压缩,从状态转移方程中可看到,只用到了两个单元就能得到结果,并且这两个单元还是交叉的,故可以用一维数组来直接表示。

class Solution {
    public int uniquePaths(int m, int n) {
        int[] dp = new int[n];
        for(int i=0; i<n; i++){   // 初始化 dp
            dp[i] = 1;
        }

        for(int i=1; i < m; i++){       // 遍历多少行
            for(int j=1; j < n; j++){     
                dp[j] = dp[j-1] + dp[j];   // dp[j-1] 是当前列,dp[j] 是前一列,结果是当前列
            }
        }
        return dp[n-1];
    }
}



③ 剑指 Offer 16. 数值的整数次方

https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/

这道题是典型的快速幂算法,但这道题坑比较多,除了要区别 n 是正数还是负数,还要考虑 n 取反溢出的问题。

思路:假设 求 7 ^ 11

  • 若将 n 用二进制表示,则 11 = 1011
  • 则 7 ^ 11 可以看成 7^1 * 7^2 * 7^8
  • 故我们可以每次判断最后一位数字是否是 1
    • 是的话结果就必须乘 x
    • 若不是的话,就不处理
    • 不管是不是,x 每一轮都要翻倍。

解决快速幂后,就解决正负数的问题。

  • 一开始没有考虑到 2^31 次方的问题(取相反数后也是 2^31 次方)
  • 故需要用一个 long 类型来接收 n,然后再去取相反数。
  • 判断 n
    • 正数,不用做操作
    • 0,直接返回 0
    • 负数,转为正数并将 x 倒过来。

思路一:递归版本

class Solution {
    public double myPow(double x, int n) {
        if (x == 0)         // 若是 0,直接返回
            return 0;
        double res = 1.0;   // 结果
        long b = n;         // 用 long 来存储 n
        if (n < 0){         // 考虑负数情况
            x = 1/x;
            b = -b;
        }
        return quitPow(x, b);
    }

    public double quitPow(double x, long n){
        if (n == 0)
            return 1;
        if ((n & 1) == 1)
            return x * quitPow(x*x, n>>1);
        return quitPow(x*x, n >> 1);
    }
}

思路二:非递归版本

class Solution {
    public double myPow(double x, int n) {
        if (x == 0)         // 若是 0,直接返回
            return 0;
        double res = 1.0;   // 结果
        long b = n;         // 用 long 来存储 n
        if (n < 0){         // 考虑负数情况
            x = 1/x;
            b = -b;
        }
        while (b > 0){        // 快速幂
            if ((b & 1) == 1)
                res *= x;
            x *= x;
            b >>= 1;
        }
        return res;
    }
}



④ 4. 寻找两个正序数组的中位数

https://leetcode-cn.com/problems/median-of-two-sorted-arrays/

看到题目的时候,就想到了算法书上有一道很类似的题目,然后一想,这是 hard 难度的题吗?

点进去一看,原来是我天真了,这两个数组不是等长的。然后大概的感觉是用分治法,但如何都想不出怎么写。

去看了题解,才发现精髓就是要找到 k 项之前的数,并去掉。从而慢慢的缩减,直到 k = 1。

思路:

  • 假设前 k 项是由 数组1数组2 平分掉的,所以数组1k/2数组2k-k/2(k被数组1取后剩余的)。
  • 但取 k/2 的前提是当前数组的长度不能小于 k/2,否则就只能是数组的长度项,数组2 的取项也要随之改变。
  • 去判断 nums1[k/2]nums2[k-k/2],小的一项就证明其前面的也都在 k 项的前面。
  • 于是我们就可以去缩小范围了,k 的范围也缩小。
  • 直到 k == 1 或者 数组1数组2 没有长度了,此时可以得到相应结果。
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int len = nums1.length + nums2.length;  // 总数组长度
        if ((len & 1) == 1){          // 奇数个,可以直接得出结果
            return find(nums1, 0, nums2, 0, len/2+1);  // len/2+1 表示要得到第 len/2 + 1 个 
        }
        // 偶数个,则要得到两个中位数取平均
        return (find(nums1, 0, nums2, 0, len/2) + find(nums1, 0, nums2, 0, len/2+1)) / 2;
    }

    public double find(int[] nums1, int i, int[] nums2, int j, int k){
        if (nums1.length - i > nums2.length - j)    // 先规定好现在的 nums1 长度小于 nums2 长度,否则调换下位置(可以减少讨论条件)
            return find(nums2, j, nums1, i, k);

        if (i == nums1.length)        // 如果 nums1 没法再取元素,就只能都在 nums2 中取
            return nums2[j+k-1];
        if (k == 1)                   // 如果 k 只剩一个,那么可以找到最小的那一个作为第 k 个
            return Math.min(nums1[i], nums2[j]);

        int ki = Math.min(nums1.length, i+k/2);    // 数组1 取 k/2 个,但前提是数组长度足够
        int kj = j+k-k/2;           // 数组2 取剩下的个数

        if (nums1[ki-1] > nums2[kj-1])      // 由于 ki 表示的是第几个(从1开始),而数组是从 0 开始
            return find(nums1, i, nums2, kj, k-(kj-j));   // 缩小范围,前面符合的都去掉
        else
            return find(nums1, ki, nums2, j, k-(ki-i));   // 缩小范围,前面符合的都去掉
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慢慢编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值