55.输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true 。示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false 。限制:
0 <= 树的结点个数 <= 10000
注意:本题与主站 110 题相同:https://leetcode-cn.com/problems/balanced-binary-tree/
class Solution {
public boolean isBalanced(TreeNode root) {
int height=dfs(root);
if(height==-1){
return false;
}else{
return true;
}
}
//浅写一个dfs
public int dfs(TreeNode root){
if(root==null){
return 0;
}
int left=dfs(root.left);
int right=dfs(root.right);
if(left==-1||right==-1||Math.abs(left-right)>1){
return -1;
}else{
return Math.max(left,right)+1;
}
}
}
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
限制:0 <= 节点个数 <= 10000
鬼畜想法有一个莫里斯:把俩都转成一条,类似于链表问题。
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
//递归呢?任何一个为null都是false,判断是不是当前结点的子,或者左右节点的子
return (A!=null)&&(B!=null)&&(recur(A,B)||isSubStructure(A.right,B)||isSubStructure(A.left,B));
}
public boolean recur(TreeNode A,TreeNode B){
if(B==null){
return true;
}
if(A==null||A.val!=B.val){
return false;
}
return recur(A.right,B.right)&&recur(A.left,B.left);
}
}
33.输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5
/ \
2 6
/ \
1 3
示例 1:输入: [1,6,3,2,5]
输出: false
示例 2:输入: [1,3,2,6,5]
输出: true
提示:
数组长度 <= 1000
注意题目是二叉搜索树,即左子树中所有节点的值都小于根节点的值,右子树中所有节点的值都大于根节点的值,其中,左右子树也是二叉搜索树。
1、递归分治法
每次都划分根左右,并判断是不是二叉搜索树。
class Solution {
public boolean verifyPostorder(int[] postorder) {
int length=postorder.length;
return recur(postorder,0,length-1);
}
public boolean recur(int[] postorder,int left,int right){
if(left>=right){
return true;
}
int root=postorder[right];
int index=left;
while(postorder[index]<root){
index++;
}
//前面全是左子树满足
int temp=index;
while(temp<right){
if(postorder[temp]<root){
//右子树只要有比根节点小的都不是二叉搜索树
return false;
}
temp++;
}
return recur(postorder,left,index-1)&&recur(postorder,index,right-1);
}
}
2、单调栈
class Solution {
public boolean verifyPostorder(int[] postorder) {
//浅写一个这种类型题目的思路
//以本题为例,倒叙来看,如果是升序,则后者是前者的右子,否则,后者是前者某个爹的左子,所以爹要存在栈里
//爹要更新,最后弹出的就是爹,如果当前的值大于前一个弹出的爹,出大问题,因为弹出意味着已经在处理爹的左儿子
Deque<Integer> dq=new ArrayDeque<>();
int length=postorder.length;
//初始化一下爹
int root=Integer.MAX_VALUE;
for(int i=length-1;i>=0;i--){
if(postorder[i]>root){
return false;
}
while(!dq.isEmpty()&&postorder[i]<dq.peekLast()){
root=dq.pollLast();
}
dq.addLast(postorder[i]);
}
return true;
}
}
34.给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
示例 2:输入:root = [1,2,3], targetSum = 5
输出:[]
示例 3:输入:root = [1,2], targetSum = 0
输出:[]
提示:
树中节点总数在范围 [0, 5000] 内
-1000 <= Node.val <= 1000
-1000 <= targetSum <= 1000
注意:本题与主站 113 题相同:https://leetcode-cn.com/problems/path-sum-ii/
递归回溯搞一哈,没搞出来,太多遗漏知识需要补
class Solution {
List<List<Integer>> res=new LinkedList<>();
Deque<Integer> temp=new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
//递归回溯肯定可以搞一哈
recur(root,target);
return res;
}
public void recur(TreeNode root,int target){
if(root==null){
return;
}
temp.offerLast(root.val);
target-=root.val;
if(root.left==null&&root.right==null&&target==0){
//这里只能采取这种初始化的方式无形中进行一个转换,这里是不能改动temp的
res.add(new LinkedList<>(temp));
}
recur(root.left,target);
recur(root.right,target);
//删掉最后一个元素
temp.pollLast();
}
}
不用递归应该可以bfs一下
这个哈希表记录你爹也太他妈的绝了把。。
class Solution {
List<List<Integer>> res=new LinkedList<>();
//记录你爹
Map<TreeNode,TreeNode> hashMap=new HashMap<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
if(root==null){
return res;
}
Queue<TreeNode> queueNode=new LinkedList<>();
Queue<Integer> queueSum=new LinkedList<>();
queueNode.offer(root);
queueSum.offer(0);
while(!queueNode.isEmpty()){
TreeNode curr=queueNode.poll();
int sum=queueSum.poll()+curr.val;
if(curr.left==null&&curr.right==null){
if(sum==target){
getPath(curr);
}
}else{
if(curr.left!=null){
queueNode.offer(curr.left);
queueSum.offer(sum);
//存爹
hashMap.put(curr.left,curr);
}
if(curr.right!=null){
queueNode.offer(curr.right);
queueSum.offer(sum);
hashMap.put(curr.right,curr);
}
}
}
return res;
}
//找你祖宗然后写道族谱里
public void getPath(TreeNode root){
List<Integer> temp=new LinkedList<>();
while(root!=null){
temp.add(root.val);
root=hashMap.get(root);
}
Collections.reverse(temp);
res.add(new LinkedList<>(temp));
}
}
68.给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉搜索树中。
注意:本题与主站 235 题相同:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
TreeNode curr=root;
while(true){
if(curr.val>p.val&&curr.val>q.val){
curr=curr.left;
}else if(curr.val<p.val&&curr.val<q.val){
curr=curr.right;
}else{
break;
}
}
return curr;
}
}
63.假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
限制:
0 <= 数组长度 <= 10^5
注意:本题与主站 121 题相同:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
class Solution {
public int maxProfit(int[] prices) {
//min存储目前为止最低的买入价
//max存储目前为止最高的利润
int length=prices.length;
if(length==0){
return 0;
}
int min=prices[0];
int max=0;
for(int i=0;i<length;i++){
min=Math.min(prices[i],min);
max=Math.max(max,prices[i]-min);
}
return max;
}
}
59.给定一个数组
nums
和滑动窗口的大小k
,请找出所有滑动窗口里的最大值。示例:
输入: nums =[1,3,-1,-3,5,3,6,7]
, 和 k = 3 输出:[3,3,5,5,6,7] 解释:
滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
注意:本题与主站 239 题相同:力扣
这题目做过,有一个机智的做法
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//两次遍历,以k为一个切割点
int n=nums.length;
if(n==0){
return new int[0];
}
int[] t1=new int[n];
int[] t2=new int[n];
int[] res=new int[n-k+1];
for(int i=0;i<n;i++){
if(i%k==0){
t1[i]=nums[i];
}else{
t1[i]=Math.max(t1[i-1],nums[i]);
}
}
for(int i=n-1;i>=0;i--){
if(i==n-1||(i+1)%k==0){
t2[i]=nums[i];
}else{
t2[i]=Math.max(t2[i+1],nums[i]);
}
}
for(int i=0;i<=n-k;i++){
res[i]=Math.max(t1[i+k-1],t2[i]);
}
return res;
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//整点阳间做法--优先队列
//1.创建优先队列,重写comparator比较器里的compare方法
PriorityQueue<int[]> pq=new PriorityQueue<>(new Comparator<int[]>(){
public int compare(int[] pair1,int[] pair2){
//前面放值,后面是索引
return pair1[0]!=pair2[0]?pair2[0]-pair1[0]:pair2[1]-pair1[1];
}
}
);
//2.初始化优先队列
for(int i=0;i<k;i++){
pq.offer(new int[]{nums[i],i});
}
//3.处理滑动窗口
int length=nums.length;
if(length==0){
return new int[0];
}
int[] res=new int[length-k+1];
res[0]=pq.peek()[0];
for(int i=1;i<length-k+1;i++){
pq.offer(new int[]{nums[i+k-1],i+k-1});
//如何抛弃?不在范围内的索引所对应的数组被抛弃
while(pq.peek()[1]<i){
pq.poll();
}
res[i]=pq.peek()[0];
}
return res;
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
PriorityQueue<int[]> pq=new PriorityQueue<>(new Comparator<int[]>(){
public int compare(int[] num1,int[] num2){
return num1[0]!=num2[0]?num2[0]-num1[0]:num2[1]-num1[1];
}
});
int length=nums.length;
if(length==0){
return new int[0];
}
for(int i=0;i<k;i++){
pq.offer(new int[]{nums[i],i});
}
int[] res=new int[length-k+1];
res[0]=pq.peek()[0];
for(int i=1;i<length-k+1;i++){
pq.offer(new int[]{nums[i+k-1],i+k-1});
while(pq.peek()[1]<i){
pq.poll();
}
res[i]=pq.peek()[0];
}
return res;
}
}
单调队列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> dq=new LinkedList<>();
int length=nums.length;
if(length==0){
return new int[0];
}
int[] res=new int[length-k+1];
for(int i=0;i<length;i++){
while(!dq.isEmpty()&&nums[i]>nums[dq.peekLast()]){
dq.pollLast();
}
dq.addLast(i);
if(dq.peek()<=i-k){
dq.poll();
}
if(i>=k-1){
res[i-k+1]=nums[dq.peek()];
}
}
return res;
}
}
add和offer方法是默认加在最后面,其他都默认前面,需要注意!!!
51.在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
1、归并排序
class Solution {
int[] temp;
int[] nums;
public int reversePairs(int[] nums) {
//浅写一个归并排序的思路
//因为两个已经各自排好序的数组的拼接逆序数比较容易获得,
//即:如果右边要插到左边前面,哪怕是最后一个数,都构成逆序数,逆序数为左边数组的大小减去比右边小的数的下标+1
//所以可以通过一边归并一边获取逆序数
//需要注意的是,需要实时修改nums数组使左右数组各自是有序的,所以需要一个中介数组存储原数组的值
this.nums=nums;
int length=nums.length;
temp=new int[length];
return mergeSort(0,length-1);
}
public int mergeSort(int left,int right){
//写出结束条件
if(left>=right){
return 0;
}
//否则分
int m=(left+right)/2;
int res=mergeSort(left,m)+mergeSort(m+1,right);
//存储当前的nums到临时数组
for(int i=left;i<=right;i++){
temp[i]=nums[i];
}
int j=left;
int k=m+1;
for(int i=left;i<=right;i++){
//实时修改nums数组
if(j==m+1){
//左边走完了,右边直接构成新的nums数组
nums[i]=temp[k++];
}else if(k==right+1||temp[j]<=temp[k]){//防止数组越界
nums[i]=temp[j++];
}else{
res+=(m-j+1);
nums[i]=temp[k++];
}
}
return res;
}
}
2.树状数组
Binary Indexed Tree,二叉索引树,设计初衷是解决数据压缩里的累积频率的计算问题,现多用于高效计算数列的前缀和,区间和。
低位运算:非负整数再二进制表示下最后面的1及后面所有的0构成的数的数值。
计算构成n的所有二次幂数 :一个一个减去lowbit(n)
树状数组
树状数组基于此思想,用途是维护序列的前缀和。
看不懂啊,坏了啊。。。
class Solution {
public int reversePairs(int[] nums) {
int n = nums.length;
int[] tmp = new int[n];
System.arraycopy(nums, 0, tmp, 0, n);
// 离散化
Arrays.sort(tmp);
for (int i = 0; i < n; ++i) {
nums[i] = Arrays.binarySearch(tmp, nums[i]) + 1;
}
// 树状数组统计逆序对
BIT bit = new BIT(n);
int ans = 0;
for (int i = n - 1; i >= 0; --i) {
ans += bit.query(nums[i] - 1);
bit.update(nums[i]);
}
return ans;
}
}
class BIT {
private int[] tree;
private int n;
public BIT(int n) {
this.n = n;
this.tree = new int[n + 1];
}
public static int lowbit(int x) {
return x & (-x);
}
public int query(int x) {
int ret = 0;
while (x != 0) {
ret += tree[x];
x -= lowbit(x);
}
return ret;
}
public void update(int x) {
while (x <= n) {
++tree[x];
x += lowbit(x);
}
}
}
59.请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
限制:
1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5
通过次数127,621提交次数267,。
class MaxQueue {
//底层用双向队列模拟
Deque<Integer> dq;
//队列存储最大值
Deque<Integer> max;
public MaxQueue() {
dq=new LinkedList<>();
max=new LinkedList<>();
}
public int max_value() {
if(max.isEmpty()){
return -1;
}else{
return max.peekFirst();
}
}
public void push_back(int value) {
while(!max.isEmpty()&&value>max.peekLast()){
max.pollLast();
}
max.addLast(value);
dq.addLast(value);
}
public int pop_front() {
if(dq.isEmpty()){
return -1;
}else{
//为什么必须写成poll再比较,否则的话就意味着弹出两次!!
int ans=dq.pollFirst();
if(ans==max.peekFirst()){
max.pollFirst();
}
return ans;
}
}
}
58.输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
说明:
无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
注意:本题与主站 151 题相同:https://leetcode-cn.com/problems/reverse-words-in-a-string/注意:此题对比原题有改动
class Solution {
public String reverseWords(String s) {
StringBuilder sb = trimSpaces(s);
// 翻转字符串
reverse(sb, 0, sb.length() - 1);
// 翻转每个单词
reverseEachWord(sb);
return sb.toString();
}
public StringBuilder trimSpaces(String s) {
int left = 0, right = s.length() - 1;
// 去掉字符串开头的空白字符
while (left <= right && s.charAt(left) == ' ') {
++left;
}
// 去掉字符串末尾的空白字符
while (left <= right && s.charAt(right) == ' ') {
--right;
}
// 将字符串间多余的空白字符去除
StringBuilder sb = new StringBuilder();
while (left <= right) {
char c = s.charAt(left);
if (c != ' ') {
sb.append(c);
} else if (sb.charAt(sb.length() - 1) != ' ') {
sb.append(c);
}
++left;
}
return sb;
}
public void reverse(StringBuilder sb, int left, int right) {
while (left < right) {
char tmp = sb.charAt(left);
sb.setCharAt(left++, sb.charAt(right));
sb.setCharAt(right--, tmp);
}
}
public void reverseEachWord(StringBuilder sb) {
int n = sb.length();
int start = 0, end = 0;
while (start < n) {
// 循环至单词的末尾
while (end < n && sb.charAt(end) != ' ') {
++end;
}
// 翻转单词
reverse(sb, start, end - 1);
// 更新start,去找下一个单词
start = end + 1;
++end;
}
}
}