【Leetcode & Java】#112 / 168 / 169 / 171 / 172
112. 路径总和【200707每日一题】
方法一:递归
因为是二叉树 + 函数返回值是 boolean 型,所以自然就想试试递归,由此先确定了函数的return语句:
return hasPathSum(root.left, sum) || hasPathSum(root.right, sum);
也就是说,只要当前节点的左子树或右子树中存在一条路径满足条件,则原题成立(返回true)。
需要满足的条件是:
- 当前节点为叶子节点 --> 其左右子节点均为空;
- 从根节点到当前节点的路径和等于目标值 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。去查了一下这两种有什么关联和区别:
168. Excel 表列名称
方法:进制转换
还是题刷的少,最开始懵懵懂懂地想出了大致的思路,但想着想着又总会把自己绕进去。后来看了题解才明白,这个题本质上其实是 十进制与二十六进制的转换 。题解中推荐了这篇文章【理解进制转换的原理】,看完确实有助于整理进制转换的思路。
根据题意,要将一个十进制数转换成二十六进制,于是首先考虑一般情况下,十进制数与二十六进制数的数学关系是:
很显然,如果我们想要获得二十六进制各个数位上的数(a / b / c /...),就需要对十进制数做循环的【求模 - 取商】操作,直到 n 除尽:
- 求模:a = n % 26;
- 取商:n /= 26;
根据数学等式,显然求 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] 出现的次数>。
思路很直接很好想,对数组遍历:
- 若此时哈希表中不含有 nums[i] ,将 <nums[i], 1> 加入表中
- 若此时哈希表中包含 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% 以上,那就从池子里随便挑一个,挑完就遍历一次(统计出现次数,检查是否中奖),要是中了就非常开心,没中就再来一次,反正我应该不至于脸黑到挑的全是没有奖的吧……
真就不信非酋脸黑呗……
// 累了,没写,以后补
方法四:投票
感谢官方题解教我数学 😃 相比于玄学,我还是对数学更感兴趣。从开始刷题到现在,第一次看到这种方法,很有意思。简单来讲,就是设置两个变量:
- candidate:用于存储候选众数
- count:统计当前候补的票面情况
原理很妙,因为真正的众数必是天选之人数量最多,也就是投票数最多的那个。在遍历数组时:
- 如果 count == 0,设当前元素为 candidate
- 计票:
若 nums[i] == candidate,那么当前候补胜算多了一分 --> count++;
反之,nums[i] != candidate,这一票不是投给当前候补的,可惜了胜算变小了 --> count--; - 继续开票,看下一张...
就这样你一票我一票,投着投着我们的天选之人众数就会上位。可能中间也会一时失意(失去 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;
}
}

浙公网安备 33010602011771号