知识点集合
1. 异或
a^b =c , b = c^a;
a^a = 0, aba = 0^b=b;
prev[3+1] = abc^d, prev[2] = a^b, 那么prev[3+1]^prev[2] = c^d, 即为第2个到第3个数的异或结果; (前缀和)
2. 同余定理
a %b = c%b, 那么(a-b)%b = 0; 已知前缀和为a,他对b的区域结果是mod, 那么在之前的前缀和结果中寻找前缀和c, c对b求余结果也是mod,由此可知index_c 到index_a-1之间的数的和能够整除b;
(a+b) %c = (a%c + b%c) %c;
(a-b) %c = (a%c - b%c) %c;
(a*b) %c = (a%c * b%c) %c;
(1)先加起来 等于 边加边模
(2)先乘起来 等于 边乘边模
不用看上面的公式,直接抽象的理解其含义:我乘的时候在哪模都行,只要我边乘边模就行。也就是我1000×1000×1000×1000可以改成
(1000 % 5)×(1000 % 5)×(1000 % 5)×(1000 % 5),也可以改成(1000 % 5)×1000×(1000 % 5)×(1000 % 5),也可以改成1000×1000×1000 ×(1000 % 5),也可以改成…
只要我乘的过程中用模来减小我的数了就行。
这一切的抽象基于最后还要模一次
应用如下:
int mod = 0;
for(int i=0; i<nums.length; i++){
sum+=nums[i]; //计算过程容易溢出
}
mod = sum%p;
for(int i=0; i<nums.length; i++){
mod = (mod+nums[i])%p; //不会溢出,且取余结果不变
//等价于先循环mod = mod+ nums[i]%p; 循环结束后,在外面再计算mod = mod%p;就是上面说的最后再模一次;
}
3.二分法的边界问题
(32条消息) 二分查找算法细节与查找左右侧边界_许诗宇的博客-CSDN博客_寻找左侧边界的二分搜索
第一个,最基本的二分查找算法:
因为我们初始化 right = nums.length - 1, 所以决定了我们的「搜索区间」是 [left, right], 所以决定了 while (left <= right),同时也决定了 left = mid+1 和 right = mid-1,因为我们只需找到一个 target 的索引即可, 所以当 nums[mid] == target 时可以立即返回;
第二个,寻找左侧边界的二分查找:value的左侧边界 如果value不在数组里,那么返回的是比value大的第一个数(在value右边)的下标;
因为我们初始化 right = nums.length,所以决定了我们的「搜索区间」是 [left, right),所以决定了 while (left < right),同时也决定了 left = mid+1 和 right = mid因为我们需找到 target 的最左侧索引,所以当 nums[mid] == target 时不要立即返回,而要收紧右侧边界以锁定左侧边界
public static int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length; // 注意
while (left < right) { // 注意
int mid = (left + right) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid; // 注意
}
}
return left;
}
第三个,寻找右侧边界的二分查找:value的右侧边界,如果value不在数组里,那么返回的是比value小的第一个数(在value左)的下标;
因为我们初始化 right = nums.length,所以决定了我们的「搜索区间」是 [left, right),所以决定了 while (left < right),同时也决定了 left = mid+1 和 right = mid,因为我们需找到 target 的最右侧索引,所以当 nums[mid] == target 时不要立即返回,而要收紧左侧边界以锁定右侧边界,又因为收紧左侧边界时必须 left = mid + 1,所以最后无论返回 left 还是 right,必须减一
public static int right_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
left = mid + 1; // 注意
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left - 1; // 注意
}
找>=target的第一个数的下标;
public int search(int[] nums,int target){
int l=0,r=nums.length;
while(l<r){
int mid=(r+l)>>1;
if(nums[mid]>=target)
r=mid;
else
l=mid+1;
}
return l;
}
-
前缀和
具体做法为,我们使用一个与字符串 ss 等长的哈希数组 h[]h[],以及次方数组 p[]p[]。 由字符串预处理得到这样的哈希数组和次方数组复杂度为 O(n)O(n)。当我们需要计算子串 s[i...j]s[i...j] 的哈希值,只需要利用前缀和思想 h[j] - h[i - 1] * p[j - i + 1]h[j]−h[i−1]∗p[j−i+1] 即可在 O(1)O(1) 时间内得出哈希值(与子串长度无关)。 class Solution { int N = (int)1e5+10, P = 131313; int[] h = new int[N], p = new int[N]; public List<String> findRepeatedDnaSequences(String s) { int n = s.length(); List<String> ans = new ArrayList<>(); p[0] = 1; for (int i = 1; i <= n; i++) { h[i] = h[i - 1] * P + s.charAt(i - 1); p[i] = p[i - 1] * P; } Map<Integer, Integer> map = new HashMap<>(); for (int i = 1; i + 10 - 1 <= n; i++) { int j = i + 10 - 1; int hash = h[j] - h[i - 1] * p[j - i + 1]; int cnt = map.getOrDefault(hash, 0); if (cnt == 1) ans.add(s.substring(i - 1, i + 10 - 1)); map.put(hash, cnt + 1); } return ans; } } 作者:AC_OIer 链接:https://leetcode-cn.com/problems/repeated-dna-sequences/solution/gong-shui-san-xie-yi-ti-shuang-jie-hua-d-30pg/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
4. 并查集
class UnionFind{
int[] feather;
int[] rank;
public UnionFind(int n){
feather = new int[n];
rank = new int[n];
for(int i=0; i<n; i++){
feather[i] = i;
rank[i] = 1;
}
}
public void union(int a, int b){
int fa = find(a);
int fb = find(b);
if(rank[fa]<rank[fb]){
feather[fa] = fb;
}else if(rank[fa] > rank[fb]){
feather[fb] = fa;
}else{
feather[fa] = fb;
rank[fb] +=1;
}
}
public int find(int x){
if(feather[x] == x){
return feather[x];
}
return find(feather[x]);
}
5. 图
1. bfs
public void bfs(int[][] G){
int n = G.length;
LinkedList<Integer> queue = new LinkedList<>();
boolean[] used = new boolean[n];
int start = 0;
queue.add(start);
while(queue.size() != 0){
int size = queue.size();
for(int i=0; i<size; i++){
int cur = queue.poll();
System.out.println(cur);
//遍历寻找当前节点能够到达的所有节点;
for(int i=0; i<n; i++){
if(!used[i] && G[cur][i] != Integer.MAX_VALUE){//cur能够到达i;且之前没有走过的路;
queue.add(i);
}
}
}
}
}
2.dfs
3.拓扑排序
现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。
例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/course-schedule-ii
public int[] findOrder(int numCourses, int[][] prerequisites) {
List<List<Integer>> edges = new ArrayList<>();
int[] in = new int[numCourses];
for(int i=0; i<numCourses; i++){
edges.add(new ArrayList<>());
}
for(int[] subject:prerequisites){
edges.get(subject[1]).add(subject[0]);//b-->a b出去的边
in[subject[0]]++;//;b--->a, 指向a的边有多少条;
}
Queue<Integer> queue = new LinkedList<>();
for(int i=0; i<numCourses; i++){
if(in[i] == 0){
queue.add(i);
}
}
int[] res = new int[numCourses];
int j=0;
while(!queue.isEmpty()){
int node = queue.poll();
res[j++] = node;
List<Integer> list = edges.get(node);
for(int i=0; i<list.size(); i++){
in[list.get(i)]--;
if(in[list.get(i)] ==0){
queue.add(list.get(i));
}
}
}
return j==numCourses? res:new int[0];
}
最小生成树:
图的所有的点连起来, 不构成回路,且权值累加和最小。
也就是说,从任何点出发都可以得到最小生成树!!!
4.Kruskal 算法
将连通网中所有的边按照权值大小做升序排序,从权值最小的边开始选择,只要此边不和已选择的边一起构成环路(有并查集判断两个点的根不能相同),就可以选择它组成最小生成树。对于 N 个顶点的连通网,挑选出 N-1 条符合条件的边,这些边组成的生成树就是最小生成树。
5.Prim
最小生成树的过程,采用了贪心算法的思想。对于包含 N 个顶点的连通网,普里姆算法每次从与已经生成的树 相连的边里面找出一个权值最小的边,这样的操作重复 N-1 次,由 N-1 条权值最小的边组成的生成树就是最小生成树。
package com.liuzhen.chapter8;
import java.util.ArrayList;
public class Prim {
/*
* 参数G:给定的图,其顶点分别为0~G.length-1,相应权值为具体元素的值
* 函数功能:返回构造生成的最小生成树,以二维数组形式表示,其中元素为0表示最小生成树的边
*/
public void getMinTree(int[][] G,int start) {
int n = G.length;
ArrayList<Integer> path = new ArrayList<>();
path.add(start);
int[] dis = new int[n];
dis = G[start];
while(path.size() < n){
//寻找下一个距离集团最小的点;
int next = 0;
int Min = Integer.MAX_VALUE;
for(int i=0; i<n; i++){
if(!path.contains(i) && dis[i] < min){
min = dis[i];
next = i;
}
}
path.add(next);
//更新剩下的点到集团的最小距离;
for(int i=0; i<n; i++){
if(!path.contains(i) && dis[i] > G[next][i]){
dis[i] = G[next][i];
}
}
}
for(Integer p: path){
System.out.println(p);
}
}
public static void main(String[] args) {
Prim test = new Prim();
int[][] G = {{-1,3,-1,-1,6,5},
{3,-1,1,-1,-1,4},
{-1,1,-1,6,-1,4},
{-1,-1,6,-1,8,5},
{6,-1,-1,8,-1,2},
{5,4,4,5,2,-1}};
test.getMinTree(G, 3);
}
}
最短路径
6. Dijkstra
找到某个点到图上其他点的最小距离。权值不能是负的。
public class Dijkstra{
public void text(int[][] edges, int n, int start){
//edges=[[2,1,1],[2,3,1],[3,4,1]],n为节点个数
int INF = Integer.MAX_VALUE;
int[][] weight = new int[n][n];
//领结矩阵初始化;
for(int i=0; i<n; i++){
for(int j=i; j<n; j++){
weight[i][j] = i==j? 0:INF;
weight[j][i] = i==j? 0:INF;
}
}
//根据输入构建领结矩阵
for(int[] edge:edges){
int from = edge[0], to = edge[1], w = edge[2];
weight[from][to] = w;
weight[to][from] = w;
}
int[] dis;
digstra(weight, n, start);
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = Math.max(ans, dis[i]);
System.out.println(dis[i]);
}
}
public void digstra(int[][] weight, int n, int start){
dis = new int[n];
boolean[] used = new boolean[n];
used[start] = true;
dis = weight[start];
dis[start] = 0;
for(int j=1; j<n; j++){
int next=-1;
for(int i=0; i<n; i++){
if(!used[i] && (next==-1 || dis[next] > dis[i])){
next = i;//找到下一轮最近的点;
}
used[next] = true;//跳到下一步上;
//更新距离
for(int i=0; i<n; i++ ){//更新从start到i的距离
if(!used[i] && dis[i] > dis[next]+weight[next][i]){
dis[i] = dis[next]+weight[next][i];
}
}
}
}
}
7. Bellman ford
最多中转K次, src 到其他点的最小距离。
支持权值为负, 某个点到其他点的最小距离;
Bellman-ford 算法比dijkstra算法更具普遍性,因为它对边没有要求,可以处理负权边与负权回路。
缺点是时间复杂度过高,高达O(VE), V为顶点数,E为边数。
其主要思想:对所有的边进行n-1轮松弛操作,因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1边。
换句话说,第1轮在对所有的边进行松弛后,得到的是源点最多经过一条边到达其他顶点的最短距离;
第2轮在对所有的边进行松弛后,得到的是源点最多经过两条边到达其他顶点的最短距离;
第3轮在对所有的边进行松弛后,得到的是源点最多经过一条边到达其他顶点的最短距离…
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
int[][] weight = new int[n][n];
int INF = 100000;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
weight[i][j] = i==j? 0:INF;
//weight[j][i] = i==j? 0:INF;
}
}
for(int[] flight: flights){
weight[flight[0]][flight[1]] = flight[2];
}
int[] dis = weight[src];
//Arrays.fill(dis, INF);
dis[src] = 0;
for(int limit = 1; limit<=k; limit++){
int[] clone = dis.clone();
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
dis[j] = Math.min(dis[j], clone[i] + weight[i][j]);
}
}
}
return dis[dst] == INF? -1:dis[dst];
}
8. Floyd
Floyd 属于多源最短路径算法,能够求出任意2个顶点之间的最短路径,支持负权边
int[][] dis = new int[n][n]; // n是有n个点;
for(int k=0; k<n; k++){
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
dis[i][j] = Math.min(dis[i][j], Math.min(grid[i][j], dp[i][k] + grid[k][j]));
// 从i到j 有两种路, 1, i直接去j; 2, i经过k, 再从k到j;
// 但是注意此处还是要和原来的dp[i][j]比较一下,说不定别的k'更近!!!
}
}
}
6. 字典树
class Tire{
Tire[] nextNodes;
Boolean isEND;
Ppublic Tire(){
nextNodes = new Tire[26];
isEnd = false;
}
public void insert(String word){
Tire root = this;
if(word.length == 0 || root == null) return;
for(int i=0; i<word.length(); i++){
int index = word.charAt(i) -'a';
if(root.nextNodes[index] == null){
root.nextNodes[index] = new Tire();
}
root = root.nextNodes[index];
}
root.isEnd = true;
}
public Tire seachPre(String pre){
Tire root = this;
if(word.length == 0 || root == null) return null;
for(int i=0; i<word.length(); i++){
int index = word.charAt(i) -'a';
if(root.nextNodes[index] == null){
return null;
}
root = root.nextNodes[index];
}
return root;
}
}
7. 排序
1. 归并排序
//归并排序
public void guibingSort(int[] nums,int low, int high, int[] temp){
if(low == high){
return;
}
int mid = low + (high-low) /2;
guibingSort( nums, low, mid,temp); //左边归并
guibingSort(nums, mid+1, high,temp);//右边归并
int l = low, r = mid+1;
int i=0;
while(l<=mid && r <=high){
if(nums[l] < nums[r]){
temp[i++] = nums[l++];
}else{
temp[i++] = nums[r++];
}
}
while(l<=mid ){
temp[i++] = nums[l++];
}
while(r<=high ){
temp[i++] = nums[r++];
}
for(int t=0; t<i; t++){
nums[low+t] = temp[t];
}
}
2. 快速排序
3. 堆排序
4. 插入排序
5. 选择排序
树
1.前中后序遍历二叉树
中序遍历
2.dfs (深度遍历)
题目1 出现次数最多的子树元素和
class Solution {
//ArrayList<Integer> res = new ArrayList<>();
HashMap<Integer, Integer> res = new HashMap<>();
public int[] findFrequentTreeSum(TreeNode root) {
if(root == null) return null;
dfs(root);
System.out.println(res.toString());
List<Integer> ans = new ArrayList<>();
int Biggest = 0;
for(Integer key:res.keySet()){
int showTimes = res.get(key);
if(showTimes == Biggest){
ans.add(key);
}else if(showTimes > Biggest){
ans =new ArrayList<>();
ans.add(key);
Biggest = showTimes;
}
}
int[] returnRes = new int[ans.size()];
for(int i=0; i<ans.size(); i++){
returnRes[i] = ans.get(i);
}
return returnRes;
}
public int dfs(TreeNode root){
if(root == null){
return 0;
}
if(root.left == null && root.right == null){
res.put(root.val, res.getOrDefault(root.val,0)+1);
return root.val;
}
int left = dfs(root.left );
int right = dfs(root.right);
int temSum = root.val+left+right;
res.put(temSum, res.getOrDefault(temSum,0)+1);
return temSum;
}
}
题目2 654. 最大二叉树
题目3 687. 最长同值路径
题目4.剑指 Offer 26. 树的子结构
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A == null || B == null) return false;
if(A.val == B.val){
if(isSame(A,B)) return true;
}
return isSubStructure(A.left, B) || isSubStructure(A.right, B);
}
public boolean isSame(TreeNode A, TreeNode B){
if(B == null) return true;
if(A == null || A.val != B.val) return false;
return isSame(A.left,B.left) && isSame(A.right, B.right);
}
//抄袭题解的;
class Solution {
int Long=0;
public int longestUnivaluePath(TreeNode root) {
dfs(root);
return Long;
}
public int dfs(TreeNode root){
if(root == null) return 0;
int left = dfs(root.left);
int right = dfs(root.right);
int arrowLeft = 0, arrowRight = 0;
if(root.left != null && root.left.val == root.val){
arrowLeft+=left+1;
}
if(root.right != null && root.right.val == root.val){
arrowRight+=right+1;
}
Long = Math.max(Long, arrowLeft+arrowRight);
return Math.max(arrowLeft,arrowRight);
}
}
题目5. 剑指 Offer 34. 二叉树中和为某一值的路径
做了几次了,逻辑一直有点乱;
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
dfs(root, target,0,new ArrayList<>());
return res;
}
public void dfs(TreeNode root, int target, int prevSum,List<Integer> path){
if(root == null ){
return;
}
int sum = prevSum + root.val;
path.add(root.val);
if(root.left == null && root.right == null && sum == target){
res.add(new ArrayList<>(path));
}
dfs(root.left, target, sum, path);
dfs(root.right, target, sum, path);
path.remove(path.size()-1);
}
题目6. 剑指 Offer II 047. 二叉树剪枝
我感觉我只会一种垃圾dfs…
//模仿高赞题解做的。。。。。。
public TreeNode pruneTree(TreeNode root) {
if(root == null) return root;
root.left = pruneTree(root.left);
root.right = pruneTree(root.right);
if(root.left == null && root.right == null && root.val ==0) root = null;
return root;
}
题目7 剑指 Offer 36. 二叉搜索树与双向链表
算法流程:
dfs(cur): 递归法中序遍历;
终止条件: 当节点 cur 为空,代表越过叶节点,直接返回;
递归左子树,即 dfs(cur.left) ;
构建链表:
当 pre 为空时: 代表正在访问链表头节点,记为 head ;
当 pre 不为空时: 修改双向节点引用,即 pre.right = cur , cur.left = pre ;
保存 cur : 更新 pre = cur ,即节点 cur 是后继节点的 pre ;
递归右子树,即 dfs(cur.right) ;
treeToDoublyList(root):
特例处理: 若节点 root 为空,则直接返回;
初始化: 空节点 pre ;
转化为双向链表: 调用 dfs(root) ;
构建循环链表: 中序遍历完成后,head 指向头节点, pre 指向尾节点,因此修改 head 和 pre 的双向节点引用即可;
返回值: 返回链表的头节点 head 即可;
class Solution {
Node head, pre;
public Node treeToDoublyList(Node root) {
if(root == null) return root;
dfs(root);
pre.right = head;
head.left = pre;
return head;
}
public void dfs(Node cur){
if(cur == null) return;
dfs(cur.left);
if(pre != null){
pre.right = cur;
} else{
head = cur;
}
cur.left = pre;
pre =cur;
dfs(cur.right);
}
}
题目8 1026. 节点与其祖先之间的最大差值
class Solution {
public int maxAncestorDiff(TreeNode root) {
int[] res = dfs(root);
return res[2];
}
public int[] dfs(TreeNode root){
if(root == null) return new int[3];
int val = root.val;
int min = val;
int max = val;
int ans = 0;
if(root.left !=null){
int[] res = dfs(root.left);
min = Math.min(min,res[0]);
max = Math.max(max, res[1]);
ans = Math.max(res[2], Math.max(Math.abs(val-res[0]), Math.abs(val-res[1])));
}
if(root.right !=null){
int[] res = dfs(root.right);
min = Math.min(min,res[0]);
max = Math.max(max, res[1]);
ans = Math.max(Math.max(ans,res[2]), Math.max(Math.abs(val-res[0]), Math.abs(val-res[1])));
}
return new int[]{min, max,ans};
}
}
3.bfs (层序遍历)
1. 623. 在二叉树中增加一行
https://leetcode-cn.com/problems/add-one-row-to-tree/
给定一个二叉树的根 root 和两个整数 val 和 depth ,在给定的深度 depth 处添加一个值为 val 的节点行。
注意,根节点 root 位于深度 1 。
加法规则如下:
给定整数 depth,对于深度为 depth - 1 的每个非空树节点 cur ,创建两个值为 val 的树节点作为 cur 的左子树根和右子树根。
cur 原来的左子树应该是新的左子树根的左子树。
cur 原来的右子树应该是新的右子树根的右子树。
如果 depth == 1 意味着 depth - 1 根本没有深度,那么创建一个树节点,值 val 作为整个原始树的新根,而原始树就是新根的左子树。
public TreeNode addOneRow(TreeNode root, int val, int depth) {
if(depth == 1){
TreeNode node = new TreeNode(val);
node.left = root;
return node;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int d=1;
while(d<depth-1){
int size = queue.size();
for(int i=0; i<size; i++){
TreeNode node = queue.poll();
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
d++;
}
while(!queue.isEmpty()){
TreeNode node = queue.poll();
TreeNode temp = node.left;
TreeNode addLeftNode = new TreeNode(val);
node.left = addLeftNode;
addLeftNode.left = temp;
temp = node.right;
TreeNode addRightNode = new TreeNode(val);
node.right = addRightNode;
addRightNode.right = temp;
}
return root;
}
2.在每个树行中找最大值
https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/solution/
题目1
4.重建二叉树
题目1. 前序和中序遍历重构二叉树
(32条消息) 根据前序遍历与中序遍历构造二叉树_Nan_Feng726的博客-CSDN博客_根据前序遍历和中序遍历构建二叉树
二叉搜索树的中序遍历是一个递增序列,对于给定的前序遍历进行sort就得到了一个递增序列即该二叉树的中序遍历, 因此转化为前序和中序遍历构建二叉树,构建的二叉树也是二叉搜索树;
题目2. 中序和后序遍历重构二叉树
(32条消息) 根据中序遍历和后序遍历构造二叉树_Nan_Feng726的博客-CSDN博客_根据后序遍历和中序遍历构造二叉树
题目3. 前序和后序遍历重构二叉树
(32条消息) 根据先序遍历和后序遍历构建二叉树_外婆家的大灰狼-CSDN博客_怎么根据前序和后序确定树
public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
if(preorder.length == 0) return null;
int rootVal = preorder[0];
TreeNode root = new TreeNode(rootVal);
if(preorder.length == 1) return root;
int L = 0;
int LeftRootVal = preorder[1];
for(int i=0; i<postorder.length; i++){
if(postorder[i] == LeftRootVal){
L = i;
break;
}
}
root.left = constructFromPrePost(Arrays.copyOfRange(preorder,1,L+2),
Arrays.copyOfRange(postorder, 0,L+1));
root.right = constructFromPrePost(Arrays.copyOfRange(preorder,L+2,preorder.length),
Arrays.copyOfRange(postorder, L+1,postorder.length-1));
return root;
}
题目4 109. 有序链表转换二叉搜索树
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
public TreeNode sortedListToBST(ListNode head) {
return constru(head,null);
}
public TreeNode constru( ListNode head, ListNode tail){
if(head == tail) return null;
ListNode slow = head, fast = head;
while(fast !=tail && fast.next != tail){
slow = slow.next;
fast = fast.next.next;
}//寻找中间节点;
TreeNode root = new TreeNode(slow.val);
root.left = constru(head, slow);
root.right = constru(slow.next, tail);
return root;
}
}
5.先序,中序,后序两两组合重构二叉树
先序,中序重构后序
6.搜索二叉树
7. 特殊的数结构
题目1. 222. 完全二叉树的节点个数
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
再来回顾一下满二叉的节点个数怎么计算,如果满二叉树的层数为h,则总节点数为:2^h - 1.
那么我们来对 root 节点的左右子树进行高度统计,分别记为 left 和 right,有以下两种结果:
left == right。这说明,左子树一定是满二叉树,因为节点已经填充到右子树了,左子树必定已经填满了。所以左子树的节点总数我们可以直接得到,是 2^left - 1,加上当前这个 root 节点,则正好是 2^left。再对右子树进行递归统计。
left != right。说明此时最后一层不满,但倒数第二层已经满了,可以直接得到右子树的节点个数。同理,右子树节点 +root 节点,总数为 2^right。再对左子树进行递归查找。
public int countNodes(TreeNode root) {
if(root == null){
return 0;
}
int left = countLevel(root.left);
int right = countLevel(root.right);
if(left == right){//左子树填满
return countNodes(root.right) + (1<<left);
}else{//左子树不满
return countNodes(root.left) + (1<<right);
}
}
private int countLevel(TreeNode root){
int level = 0;
while(root != null){
level++;
root = root.left;
}
return level;
}
作者:xiao-ping-zi-5
链接:https://leetcode-cn.com/problems/count-complete-tree-nodes/solution/chang-gui-jie-fa-he-ji-bai-100de-javajie-fa-by-xia/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
位运算
HJ15 求int型正整数在内存中存储时1的个数
求int型正整数在内存中存储时1的个数_牛客题霸_牛客网 (nowcoder.com)
输入一个 int 型的正整数,计算出该 int 型数据在内存中存储时 1 的个数。
数据范围:保证在 32 位整型数字范围内
输入描述:
输入一个整数(int类型)
输出描述:
这个数转换成2进制后,输出1的个数
import java.util.*;
public class Main{
public static void main(String[] str){
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
int res = 0;
while(num>0){
if((num &1) == 1){
res++;
}
num = num >>1;
}
System.out.println(res);
}
}