【Leetcode & Java】#112 / 168 / 169 / 171 / 172


112. 路径总和【200707每日一题】

方法一:递归

因为是二叉树 + 函数返回值是 boolean 型,所以自然就想试试递归,由此先确定了函数的return语句:

return hasPathSum(root.left, sum) || hasPathSum(root.right, sum); 

也就是说,只要当前节点的左子树或右子树中存在一条路径满足条件,则原题成立(返回true)。

需要满足的条件是:

  1. 当前节点为叶子节点 --> 其左右子节点均为空;
  2. 从根节点到当前节点的路径和等于目标值 sum。

考虑递归的终点,也就是 return 语句中的两个条件都为 false 的情况:此时 root 已到达当前路径的“端点”,也就是叶子节点 --> 其左右子节点均为空

考虑如何求路径和:由于在遍历节点时都会向下传递 sum(路径和的目标值),可以在遍历过程中直接在 sum 中除去当前节点的值,相当于每次向下传递的 sum 都已经减去了到达其父节点的路径和。这样根据前面的分析,若当前节点为叶子节点,且当前节点的值等于 sum(即符合目标值),则返回 true。

//方法1:递归
class Solution {
      public boolean hasPathSum(TreeNode root, int sum) {
            if(root == null){
                  return false;
            }
            sum -= root.val;
            if(root.left == null && root.right == null && sum == 0){
                  return true;
            }
            return hasPathSum(root.left, sum) || hasPathSum(root.right, sum);
      }
}

方法二:BFS广度优先

其实看到二叉树首先应该想到的就是DFS / BFS,但递归思考起来比较顺所以总是我的第一顺位……(大概还是刷题刷的不够多,还没形成机械记忆吧)其实BFS方法的解法还是比较固定,只需要在模版上根据题目要求修修补补就行。

//广度优先遍历模版
        Queue<TreeNode> nodes = new ArrayDeque<>();
        if(root == null){
            return false;
        }
        nodes.offer(root);
  
        while(!nodes.isEmpty()){
            int n = nodes.size();
            while(n > 0){
                n--;
                TreeNode temp = nodes.poll();
                if(temp.left != null){
                    nodes.offer(temp.left);
                }
                if(temp.right != null){
                    nodes.offer(temp.right);
                }
            }
        }

做题的时候不爱费脑子想了,就直接按照官方题解里的思路做了(添加一个辅助队列同步求累加和)。以后重刷的时候应该思考一下怎么优化空间复杂度。

//方法二:BFS
        Queue<TreeNode> nodes = new ArrayDeque<>();
            //第一个队列存树节点,第二个队列同步存累加和
        Queue<Integer> findSum = new ArrayDeque<>();

        if(root == null){
            return false;
        }
        nodes.offer(root);
        findSum.offer(root.val);

        while(!nodes.isEmpty()){
            int n = nodes.size();
            while(n > 0){
                n--;
                TreeNode temp = nodes.poll();
                Integer tempSum = findSum.poll();
                if(temp.left == null && temp.right == null && tempSum == sum){
                    return true;
                }
                if(temp.left != null){
                    nodes.offer(temp.left);
                    findSum.offer(tempSum + temp.left.val);
                }
                if(temp.right != null){
                    nodes.offer(temp.right);
                    findSum.offer(tempSum + temp.right.val);
                }
            }
        }
        return false;

这里还有一个问题是,官方题解里将两个队列定义为 LinkedList 方法(或者叫类?原谅我这个Java小白的无知...),而我不知道从哪里取过经习惯把队列一律定义为 ArrayDeque。去查了一下这两种有什么关联和区别:

  1. ArrayDeque 类的操作方法
  2. ArrayDeque 的关联与分析

168. Excel 表列名称

方法:进制转换

还是题刷的少,最开始懵懵懂懂地想出了大致的思路,但想着想着又总会把自己绕进去。后来看了题解才明白,这个题本质上其实是 十进制与二十六进制的转换 。题解中推荐了这篇文章【理解进制转换的原理】,看完确实有助于整理进制转换的思路。

根据题意,要将一个十进制数转换成二十六进制,于是首先考虑一般情况下,十进制数与二十六进制数的数学关系是:

\[当二十六进制数为 ...cba 时,十进制数 n = a * 26 ^ 0 + b * 26 ^ 1 + c * 26 ^ 2 + ... \]

很显然,如果我们想要获得二十六进制各个数位上的数(a / b / c /...),就需要对十进制数做循环的【求模 - 取商】操作,直到 n 除尽:

  1. 求模:a = n % 26;
  2. 取商:n /= 26;

\[\frac{n}{26} = b * 26 ^ 0 + c * 26 ^ 1 + ... \]

根据数学等式,显然求 b 的方法与前面求 a 是相同的。

这里还有一个小问题,一般使用二十六进制时,A~ Z 对应的应该是 0 ~ 25,但本题里对应的实际是 1 ~ 26,所以在每次求模之前都应该对 n 减 1。

public String convertToTitle(int n) {
        if(n == 0){
            return null;
        }
        
        String str = new String();

        while(n > 0){
            n--;
            str = map.get(n % 26) + str;
                  // 这里犯傻了,其实可以直接用 char 型数据直接做运算
                  // 但不知道为什么脑海里第一个想到了哈希表,而且敲了26次 map.put(); 的我实在不忍心删 :-(
            n = n / 26;
        }

        return str;
    }
}

169. 多数元素

方法一:哈希表

大概是哈希表虐我千百遍吧,这千百次的擦肩回眸让我对它念念不忘情根深种,所以第一个想到的方法就是哈希表。最开始的想直接用哈希集合来做,但发现这样不方便计数,所以使用哈希映射来存储数据对 <Key: nums[i], Value: n[i] 出现的次数>。

思路很直接很好想,对数组遍历:

  1. 若此时哈希表中不含有 nums[i] ,将 <nums[i], 1> 加入表中
  2. 若此时哈希表中包含 nums[i],判断是否已超半数,超过则返回 nums[i],否则将 nums[i] 已出现的次数 + 1.

代码如下:

// 方法一:哈希表
Map<Integer, Integer> hashmap = new HashMap<>();
        int n = nums.length;
        int count = 0;
        for(int i = 0; i < n; i++){
            if(!hashmap.containsKey(nums[i])){
                hashmap.put(nums[i], 1);
            }else{
                count = hashmap.get(nums[i]);
                if(count + 1 > n / 2){
                    return nums[i];
                }
                hashmap.put(nums[i], count + 1);
                  // 哈希表会自动删除原本 Key = nums[i] 对应的数据对,使表中只保存唯一且最新的数据对
            }

        }
        return nums[n - 1];

方法二:排序

感谢官方题解教我做人 😃 官方管它叫排序,但我觉得应该叫数学解法(学好/ 学活数学真的很重要)。其实原理很简单,因为要找的数组元素超过半数,所以在对数组作排序操作后,数组里 n / 2 的位置上必然是这个众数。简单理解的话,在排序后,所有重复的元素必然会在数组中连续排列,由于这个众数的数量超过 n / 2 ,即便它是数组中的最大值 / 最小值(也就是从数组的一端开始连续排列),数组中 n / 2 处也始终都会包含在这个众数的连续排列中。

// 累了,没写,以后补


方法三:随机化

感谢官方题解教我玄学 😦 简单用抽奖来理解的话就是,反正中奖率在 50% 以上,那就从池子里随便挑一个,挑完就遍历一次(统计出现次数,检查是否中奖),要是中了就非常开心,没中就再来一次,反正我应该不至于脸黑到挑的全是没有奖的吧……

真就不信非酋脸黑呗……

// 累了,没写,以后补


方法四:投票

感谢官方题解教我数学 😃 相比于玄学,我还是对数学更感兴趣。从开始刷题到现在,第一次看到这种方法,很有意思。简单来讲,就是设置两个变量:

  1. candidate:用于存储候选众数
  2. count:统计当前候补的票面情况

原理很妙,因为真正的众数必是天选之人数量最多,也就是投票数最多的那个。在遍历数组时:

  1. 如果 count == 0,设当前元素为 candidate
  2. 计票:
    若 nums[i] == candidate,那么当前候补胜算多了一分 --> count++;
    反之,nums[i] != candidate,这一票不是投给当前候补的,可惜了胜算变小了 --> count--;
  3. 继续开票,看下一张...

就这样你一票我一票,投着投着我们的天选之人众数就会上位。可能中间也会一时失意(失去 candidate 的位置),但架不住它票数多群众都投它呀。本来众数的票数就多,如果前面开票的结果是它的票数少,那后面继续开票的时候它就会开出更多的票啊,所以最后C位出道的还会是它 😃

瞬间刷题变得有意思了 ^ ^

// 累了,没写,以后补


171. Excel 表列序号

方法:进制转换

光是看标题就知道是 【168. Excel 表列名称】的逆过程,还是进制转换,思路现成的直接写代码就行。

class Solution {
    public int titleToNumber(String s) {
    
        int ans = 0;
        int temp = 1;
        int n = s.length();
        while(n > 0){
            ans += temp * (s.charAt(n - 1) - 'A' + 1);
            temp *= 26;
            n--;
        }
        return ans;
    }
}

172. 阶乘后的零

方法:纯数学,求阶乘里因子 5 的个数

我爱数学 😃 官方题解里看到最后才是这个方法,题解里前面那些方法话太多了代码也太长不想看,所以拿到题目也不能无脑套模版直接上手写代码,关键还是要先分析找出突破口。

题目里要求数尾数有多少个零,其实就相当于是数作阶乘运算时能乘出多少个 10,因为阶乘式里 2 的个数肯定要比 5 多,所以求有多少个 10 就可以转换成求有多少个 5。

想知道有多少个因子 5 就要去找 5 的倍数,通常 5 的倍数只能分解出一个 5,但还要考虑 25 / 125 /... 的倍数,因为它们不只能分解出一个 5 。因此,需要让 n /= 5,直到不能再整除为止。

class Solution {
    public int trailingZeroes(int n) {
        int count = 0;
        while(n > 0){
            n /= 5;
            count += n;
        }
        return count;
    }
}
posted @ 2020-07-08 13:53  Z/z  阅读(175)  评论(0)    收藏  举报