算法学习 (门徒计划)4-3 专项面试题解析 学习笔记
- 前言
- LeetCode 1367. 二叉树中的列表
- LeetCode 958. 二叉树的完全性检验
- LeetCode 剑指 Offer 36. 二叉搜索树与双向链表
- LeetCode 464. 我能赢吗
- LeetCode 172. 阶乘后的零
- LeetCode 384. 打乱数组
- LeetCode 437. 路径总和 III
- LeetCode 395. 至少有 K 个重复字符的最长子串
- LeetCode 190. 颠倒二进制位
- LeetCode 8. 字符串转换整数 (atoi)
- LeetCode 380. O(1) 时间插入、删除和获取随机元素
- LeetCode 402. 移掉 K 位数字
- LeetCode 316. 去除重复字母
- LeetCode 1499. 满足不等式的最大值
- LeetCode 321. 拼接最大数
- 结语
前言
(7.29继续学习-第六章第3节: 2021.07.29 专项面试题解析)
4-3 开课吧第十二讲专项面试题解析
以下一共12题(可能也没有)
9.47~11.28; 1.5H
12.30~13.38;1H
14.40~ 17.15;2.5H
20.00~01.00;5H
10.40~11.27;0.5H
总计(10.5H加上课时的4.5H)15H,超过了3倍耗时,需要反思。
LeetCode 1367. 二叉树中的列表
链接:https://leetcode-cn.com/problems/linked-list-in-binary-tree
给你一棵以 root 为根的二叉树和一个 head 为第一个节点的链表。
如果在二叉树中,存在一条一直向下的路径,且每个点的数值恰好一一对应以 head 为首的链表中每个节点的值,那么请你返回 True ,否则返回 False 。
一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径。
解题思路
本题显然是跟着二叉树进行向下递归,期望找到从某一个节点后能够有连续的递归内容跟链表相同。
(这题很简单)
示例代码
(1ms、38.6MB)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSubPath(ListNode head, TreeNode root) {
if(head==null)return true;
if(root == null) return false;
if(head.val == root.val&&isMySubPath(head,root)){
return true;
}
return isSubPath(head,root.left)||isSubPath(head,root.right);
}
public boolean isMySubPath(ListNode head, TreeNode root) {
if(head==null)return true;
if(root == null) return false;
if(head.val == root.val){
return isMySubPath(head.next,root.left)||isMySubPath(head.next,root.right);
}
return false;
}
}
LeetCode 958. 二叉树的完全性检验
链接:https://leetcode-cn.com/problems/check-completeness-of-a-binary-tree
给定一个二叉树,确定它是否是一个完全二叉树。
百度百科中对完全二叉树的定义如下:
若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。(注:第 h 层可能包含 1~ 2h 个节点。)
解题思路
方案1
准备广搜,每次提取出当前节点后进行判断,如果存在左枝空缺或者右枝空缺则进行标记,当出现左枝空缺但是有右枝时立即返回false,当出现右枝空缺时,如果后续出队列的节点依然有子树则反回false,否则全部出队列后认为true。(入队方式为,出队后,将左右子节点入队)
但是这样性能不好。
(课上的思路如下)
方案二
知道完全二叉树的总节点数后,就可以计算出左子树应该有多少个节点,右子树有多少个节点。
另外完全二叉树的子树也是完全二叉树,因此进行向下递归判断左右子树是否都是完全二叉树。
而完全二叉树从节点数考虑特征,最后一层前(假设当前深度为m)的节点总数为(2m-1个),最后一层中属于左子树的节点不会超过m个。
由此得出左右子树应该持有的节点数为(其中m是最后一层前的深度,l表示是最后一层中左子树持有的节点数,r是最后一层中右子树持有的节点数):
- 左:m-1+l
- 右:m-1+r
(这题可以用广搜解,但是本次用课上思路)
示例代码
(0ms,37.2MB)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isCompleteTree(TreeNode root) {
if(root==null) return true;
int n = getTreeCnts(root);
int m=1,cnt = 1;
while(cnt + 2*m<n){
m*=2;
cnt +=m;
}
//System.out.println("n:"+n+"m:"+m);
return judge(root ,n,m);
}
private boolean judge(TreeNode root,int n,int m){
if(root ==null) return n==0;
if(n==0) return false;
if(n == 1 ) {
return root.left ==null &&root.right ==null;
}
int k= Math.max(2*m -1,0);
int l = Math.min(m,n-k),r = n-k-l;
return judge(root.left,(k-1)/2+l,m/2)&&judge(root.right,(k-1)/2+r,m/2);
}
private int getTreeCnts(TreeNode root){
if(root ==null) return 0;
return 1+getTreeCnts(root.left)+getTreeCnts(root.right);
}
}
LeetCode 剑指 Offer 36. 二叉搜索树与双向链表
链接:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
解题思路
本题是一个对于二叉树进行中序遍历,最终将遍历生成序列串联成双向链表,并且收尾相连。
(最终对外的链表头结点为原始二叉树最左侧的节点、也就是中序遍历后的第一个节点)
并且利用原本的二叉树的两个指针域用作双向链表指向前一位和后一位的作用。
由此本题的核心思路就是中序遍历,并且在遍历的过程中将节点串联拼接形成结果
(我的解题思路为)
先设计一套中序遍历的代码,并且准备一个数据结构存储遍历的节点,最终全部中序遍历完成后根据这个数据结构从头遍历一次将每个节点的指针域改为指向前后节点
(课上的思路为)
在中序遍历的过程中就进行拼接
(显然比我的思路性能好)
示例代码
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val,Node _left,Node _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
public Node treeToDoublyList(Node root) {
if(root == null) return null;
pre = null;//尾节点
head = null;//头节点
in_order(root);
head.left = pre;
pre.right = head;
return head;
}
private Node head,pre;
private void in_order(Node root){
if(root == null) return ;
//左遍历
in_order(root.left);
//以下当原始准备串联的链表尾部还未确定时,定义链表的头部
if(pre ==null)
head = root;
else
pre.right = root;
//接上左侧的遍历结果,接上左枝干的尾部
root.left = pre;
//左遍历的结果和根节点合并,尾部指针表示为当前队列末尾
pre = root;
//右遍历
in_order(root.right);
}
}
LeetCode 464. 我能赢吗
链接:https://leetcode-cn.com/problems/can-i-win
在 “100 game” 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到或超过 100 的玩家,即为胜者。
如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?
例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。
给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?
你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。
示例:
输入:
maxChoosableInteger = 10
desiredTotal = 11
输出:
false
解释:
无论第一个玩家选择哪个整数,他都会失败。
第一个玩家可以选择从 1 到 10 的整数。
如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。
第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利.
同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。
解题思路
本题在于如何判断先手的人能否赢,由于假想两位玩家游戏时都表现最佳,因此就是判断对手是否会输。
对于这种取数的解法,如果跟枚举相关,那么就通常要使用问题求解树来进行计算。因此准备深搜的思想并且用记忆化进行历史使用情况的排除。
(另外可以在计算前可以排除一些不可能达到的情况)
本题的核心在于如何进行记忆化进行问题求解树重叠情况的排除,而需要记忆的内容仅为本次使用的数(因为其他值比如剩余值是随着使用的数变化的,而上限值也是固定的)
(代码略)
LeetCode 172. 阶乘后的零
链接:https://leetcode-cn.com/problems/factorial-trailing-zeroes
给定一个整数 n,返回 n! 结果尾数中零的数量。
示例 1:
输入: 3
输出: 0
解释: 3! = 6, 尾数中没有零。
示例 2:
输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.
解题思路
每有一个零就表示在相乘的过程中生成了一个含有零的因数,因此能统计出多少个结果含有0的因数相乘最终结果中就有多少个0
而能生成零的数,基本就是2*5,而通常能找的5因数的情况下至少能找到2个2的因数,因此本题就是找上限到n数中一共能拆解出多少个5作为因数
(获取因数时对于5的阶乘,例如25,一次能拆解出2个因数)
示例代码
class Solution {
public int trailingZeroes(int n) {
int cnt =0,m=25;
cnt += n/5;
while(m<=n){
cnt += n/m;
m*=5;
}
return cnt;
}