文章目录

  • 1. 需要判断输入的两个参数的大小/长度
  • 2. 数学分式的化简
  • 3. 二叉树操作的小总结
  • 4. MySQL分组内取前几名的问题
  • 5. SQL中的小问题
  • 6. 对哈希表的初步理解
    • (1)初步理解
    • (2)二次遇上再理解
  • 7. 字符串大小写转换(使用位运算)
  • 8. 异或运算
    • (1)初理解
    • (2)再理解
  • 9. 取两端的值
  • 10. bfs与dfs的应用
    • (1)都可以用
    • (2)bfs(队列)
    • (3)dfs(栈)
  • 11. i++ , ++i ,i+1
  • 12. *二分查找
    • (1)Arrays.binarySearch()使用
    • (2)*手动实现
  • 13. 使用质数统计
  • 14. BFS总结
  • 15. 洗牌算法
  • 16. DP动态规化
  • 17. 卡特兰数(Catalan)
  • 18. 并查集
  • 19. 返回代码本身
  • 20. 减治、分治与变治
  • 21. 位运算操作小技巧
  • 21. 快慢指针
  • 22. 结果对`1e9+7(1000000007)`取模
  • 23. 约瑟夫环问题
  • 24. 求最大公约数
  • 25. 字典树
  • 26. 二维数组中的bfs
  • 27. 单调栈
  • 28. 方阵翻转替代旋转
  • 29. 数状数组
  • 30. Morris 中序遍历
  • 其它

刷题遇到的小知识点,在这里做个笔记,主要是记录想法、理解、一些牛逼的操作、没见过的算法、数据结构。

看到一篇博客,感触良多,适合正在努力学习算法的你:算法这一站是新的起点

多看,多练

1. 需要判断输入的两个参数的大小/长度

67题,需要根据输入的字符串a、b中长度较短的进行循环,所以我的思路是,首先要判断两个字符串的长度,再进行调换……

public String addBinary(String a, String b) {int len1 = a.length();int len2 = b.length();if(len1 < len2) return addBinary(b, a); //这里是重点,码住!!/*后面是其它操作,省略……*/
}

适用的地方:在方法开始的位置,递归的时候前面没有做太多的操作,不然得不偿失

2. 数学分式的化简

(题号:LCP2)
a + 1 / b 必定是最简分数,所以不用求GCD(最大公约数,还有一个词叫LCM是最小公倍数)了。 (前提:a是整数,b是一个最简分数) 因为b是最简分数,所以 1 / b肯定也是一个最简分数,加上一个整数仍然是最简分数:(ab + 1)/ b = a …… 1

对于求最大公约数的方法,有辗转相除法和更相减损法
辗转相除法:两个数相除取余,然后用被除数与余数相除求余,直到余数为零,此时的被除数即为最大公约数

3. 二叉树操作的小总结

  1. 当做根、左子树、右子树三部分,左子树、右子树具体是什么样的不管
  2. 找出终止条件,就是什么时候return,return什么
  3. 只考虑当前这一步要完成什么功能

4. MySQL分组内取前几名的问题

(185题)一个不错的思路:比如说取每个部门中工资前三高的员工(limit用不了),自连接,条件是工资比我高的员工,然后判断工资比我高的员工(注意相同工资的去重)的个数是不是小于3,是的话说明我就是工资前三高 的员工

5. SQL中的小问题

  1. CASE END 模仿多分枝选择判断,CASE END 可以用在更新语句SET后边,如SET 字段=CASE ... END
CASEWHEN 字段与某个值比较    THEN  '解释数据1'WHEN 字段与某个值比较可以使用聚合函数    THEN  '解释数据2'ELSE '解释数据3'
END
  1. CASE END模仿switch
CASE   字段名WHEN  '值1'    THEN '解释数据1'WHEN  '值2'    THEN  '解释数据2'ELSE  '解释数据3'
END
  1. 掌握union的用法
  2. 在where后面不能用函数,可以用算式,如id % 2 = 0,而在其它地方要用聚合函数,如MOD(id, 2) = 0
  3. IF函数的使用:IF(判断条件, "true条件为true时", "条件为false时")

6. 对哈希表的初步理解

(1)初步理解

哈希表是最典型的时间换空间,对于一个数组,可以使用哈希表,将数组中的内容当做索引(key),数组下标当做值,当要检索数组中是否存在某个值时,速度比数组快,一个简单的应用:在一个元素不重复的数组中找两个数的和是0(可以是任何数,为了方便假定是0),可以先把数组元素存入到散列表中,在遍历数组时,在散列表中查找是否存在(0-arr[i])的元素。

(2)二次遇上再理解

(第442题:题目大致的意思,一个数组中有些元素出现两次而其他元素出现一次,找到所有出现两次的元素,限定条件:1 ≤ a[i] ≤ n (n为数组长度))

我没有注意到这个限定条件,直接就是遍历一遍数组,把每个元素存入到散列表中,如果表中已经存在该值了,就把这个值返回,这个思路的时间复杂度O(n),空间复杂度也是O(n)。当打开评论的时候,真的是另一个新天地,大佬们的代码在时间复杂度没变的同时居然没有使用额外的空间!!

大佬们的思路:因为限定条件的存在,所以可以将数组中的元素不重复的散列在输入数组的范围中,然后再通过正负来标记一个数是否出现过。

7. 字符串大小写转换(使用位运算)

大写变小写、小写变大写:字符 ^= 32
大写变小写、小写变小写:字符 |= 32
小写变大写、大写变大写:字符 &= -33

8. 异或运算

(1)初理解

  1. 交换律:a ^ b ^ c <=> a ^ c ^ b
  2. 任何数于0异或为任何数: 0 ^ n => n
  3. 相同的数异或为0:n ^ n => 0
    第136题,一个数组中只有一个数出现一次,其余的数均出现了两次,就可以对整个数组元素求异或,最后得到的结果就是那个只出现了一次的数

(2)再理解

第260题,136题的升级版,一数数组中只出现一次的数变为了两个。这时候的思路:先整体异或一遍,求出两个只出现一次的数 x,y 的异或结果xor,根据 xor 二进制位上为 1 的位(比如说最后一个为 1 的位),将数组分成两部分,x,y 刚好被分别分到了数组的两个部分中,然后对两部分的数组分别进行异或运算,就可以求出两个数

  • 补充:x ^ y != 0,故二进制中存在某个位为 1
  • 如果 xor 的某个二进制位为 1,则说明x,y二进制位的该位一定不同

其它位运算:

  • 389. 找不同

9. 取两端的值

一段字符串,左往中间走加1计数,从中间往右走减1计数,取最左边的数(计数为1)和最右边的数(计数为0),这时候两个数不统一(一个为0,一个为1),这时可以用下面的方法:一个先加1或者减1再判断,另一个先判断再加1或者减1

//这里两次判断均使用的是数字0,
for (int i = 0; i < inputs.length; i++) {char currentChar = inputs[i];if (currentChar == '(') {//注意这里是先判断,再加1if (count > 0) {sb.append(currentChar);}count++;} else {//这里是先减1,再判断count--;if (count > 0) {sb.append(currentChar);}}
}

10. bfs与dfs的应用

(1)都可以用

遍历图和二叉数

(2)bfs(队列)

计算二叉数和图的深度均可,计算二叉数的深度目前为止个人觉得使用dfs又顺手一点

(3)dfs(栈)

  • 计算二叉数的深度:dfs可以将上一层的深度加1,不需要额外的存储空间,而bfs计算深度就要在每个节点中加一块记录当前节点深度的属性,dfs仅能计算二叉数的深度,若要计算图的深度,只能使用bfs
    判断路径是否可达,计算一块区域的面积,

注意:有的和路径相关的,求最佳啥的,是使用 dp 来做的,要具体分析

11. i++ , ++i ,i+1

使用时要考虑原来的值到底变不变,先加1 还是后加1,

i+1不会改变原来的值。
i++和++i都会改变原来的值,单独使用时,都可以。但是和其它函数一起使用时,就要注意了,

12. *二分查找

(1)Arrays.binarySearch()使用

  • 搜索值是数组元素,从0开始计数,得到搜索值的索引值;

  • 搜索值不是数组元素,且在数组范围内,从1开始计数,得“ - 插入点索引值”;

  • 搜索值不是数组元素,且大于数组内元素,索引值为 – (length + 1);

  • 搜索值不是数组元素,且小于数组内元素,索引值为 – 1。

总之:如果搜不到,则插入点为 - ( 索引值 + 1 )

//LIS代码片段
for (int num : nums) {int i = Arrays.binarySearch(dp, 0, len, num);if (i < 0) {i = -(i + 1);}dp[i] = num;if (i == len) {len++;}
}

(2)*手动实现

  • lo和hi表示的区间为左闭右开
  • 当区间为偶数时,mid会落在较大的那个数上
  • 搜索值是数组元素,则 lo(左指针) 和 hi(右指针)均指向搜索值的下标(从0开始);
  • 搜索值 x 在数组中不存在,则 lo(左指针) 和 hi(右指针)均指向数组中小于 x 的最大的数的下一个下标(即大于x的最小数的下标)
int arr = {......};int lo = 0, hi = arr.length;
while(lo < hi) {//这里也可以写成 /2,因为只要是 2 的方幂,Java 的编译器都会转成位运算去计算//mid = low + (high - low) >> 1 这样写更稳妥一些,写成下面的形式可能出现int溢出int mid = (hi + lo) >>> 1;if(arr[mid] < x)lo = mid + 1;elsehi = mid;
}
return arr[lo];

13. 使用质数统计

893题,对于一个没有顺序、小写字母、长度较短的字符串,都可以用这种方式处理,来统计每个字母出现次数是否相同

  • 每个字母对应一个质数,出现一次该字母就在原数字(起始为1)上乘以该质数,最后对比数字就可以知道字符串是否相同
int[] primes = new int[]{2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101};
Set<String> set = new HashSet<>();
for(String a : A){long odd = 1;long even = 1;char[] cs = a.toCharArray();for(int i=0;i<cs.length;i++){if(i % 2 == 0){even *= primes[cs[i]-'a'];}else{odd *= primes[cs[i]-'a'];}}set.add(odd+"_"+even);
}

14. BFS总结

(1)使用Set集合来标记遍历过的节点,遍历过的加到Set中(在入队时加入),使用contains方法确定是否遍历过
(2)带层数的bfs:

//先创建一个队列,并将队列的起始节点加入到队列中
Queue<Integer> queue = new LinkedList<>();
queue.offer(id);
//使用Set集合来去除掉已经遍历过的节点
Set<Integer> visited = new HashSet<>();
visited.add(id);//len为层数
int len = 0;//bfs,如果要选取某一层的元素,则在这里加个条件:len < 层数
while (!queue.isEmpty()) {//这个size为当前层的元素个数int size = queue.size();//将当前层的握有元素出队,并将下一层的所有元素入队for (int i = 0; i < size; i++) {Integer a = queue.poll();for (int j = 0; j < friends[idd].length; j++) {if (!visited.contains(friends[idd][j])) {//如果该元素之前没有被遍历过的话就加入到队列和Set集合中queue.add(friends[idd][j]);visited.add(friends[idd][j]);}}}//层数加1len++;
}

15. 洗牌算法

  • Knuth 洗牌算法的伪代码:

    • 基本思想:i 从后向前,每次随机一个 [0…i] 之间的下标,然后将 arr[i] 和这个随机的下标元素,也就是 arr[Math.random() * (i+1) ] 交换位置。
    • 证明:对于原排列最后一个数字:很显然他在第n个位置的概率是1/n,在倒数第二个位置概率是[(n-1)/n] * [1/(n-1)] = 1/n,在倒数第k个位置的概率是[(n-1)/n] * [(n-2)/(n-1)] *…* [(n-k+1)/(n-k+2)] *[1/(n-k+1)] = 1/n。对于原排列的其他数字也可以同上求得他们在每个位置的概率都是1/n。
    • 时间复杂度为O(n),空间复杂度为O(1),缺点必须知道数组长度n
    for(int i = n - 1; i >= 0 ; i -- )swap(arr[i], arr[Math.random() * (i + 1)])
    
  • Inside-Out Algorithm

    • 基本思想:从前向后扫描数据,把位置i的数据随机插入到前 i+1个(从0个到第i个)位置中(假设为k),然后把数组中位置k的数字和位置i的数字交换。
    • 证明:原数组的第 i 个元素在新数组的前 i 个位置的概率都是:(1/i) * [i/(i+1)] * [(i+1)/(i+2)] *…* [(n-1)/n] = 1/n,(即第i次刚好随机放到了该位置,在后面的n-i 次选择中该数字不被选中)
    • 时间复杂度为O(n),空间复杂度为O(n) ,可以不知道数组的长度
    int i = 0;
    int[] array = {......};while(i < n){int tmp = Math.random() * (i + 1);//随机生成一个从0到i(包含i)的数swap(array[i], array[tmp]);//交换下标为i的数和下标为tmp的数i++;//后移
    }
    
  • Java.util.Collections类下有一个静态的shuffle()方法,可以对List集合进行洗牌,如下:

    • static void shuffle(List<?> list):使用默认随机源对列表进行置换,所有置换发生的可能性都是大致相等的。

    • static void shuffle(List<?> list, Random rand):使用指定的随机源对指定列表进行置换,所有置换发生的可能性都是大致相等的,假定随机源是公平的。

    注意:如果给定一个整型数组,用Arrays.asList()方法将其转化为一个集合类,有两种途径:

    • List<Integer> list=ArrayList(Arrays.asList(ia)),用shuffle()打乱不会改变底层数组的顺序。

    • List<Integer> list=Arrays.aslist(ia),然后用shuffle()打乱会改变底层数组的顺序。

Knuth 洗牌算法:https://www.jianshu.com/p/4be78c20095e
三种洗牌算法shuffle:https://blog.csdn.net/qq_26399665/article/details/79831490

16. DP动态规化

先列出dp方程,再根据dp方程来写程序,

基本上DP的题,能列出dp方程,程序也就写出来了,还有就是,要能想到这道题是用dp来做

一些经典的动态规化题,必刷:

  • 70. 爬楼梯
  • 121. 买卖股票的最佳时机
  • 198. 打家劫舍
  • 300. 最长上升子序列(LIS)
  • 1143. 最长公共子序列(LCS)
  • 72. 编辑距离

17. 卡特兰数(Catalan)

Catalan数的定义:令h(0)=1,Catalan数满足递归式:h(n)= h(0)*h(n-1) + h(1)*h(n-2) + ... + h(n-1)h(0) (其中n>=0)。该递推关系的解为:h(n) = C(2n-2,n-1)/n,n=1,2,3,...(其中C(2n-2,n-1)表示2n-2个中取n-1个的组合数)。

卡特兰数的前几位分别是:规定h(0)=1,而h(1)=1,h(2)=2,h(3)=5,h(4)=14,h(5)=42,h(6)=132,h(7)=429,h(8)=1430,h(9)=4862,h(10)=16796,h(11)=58786,h(12)=208012,h(13)=742900,h(14)=2674440,h(15)=9694845。

常见的题型:

  1. n个节点构成的二叉搜索树,有多少种可能(93题)?简单思路:左子树有0个节点,则右子树有n-1个节点,左子树有1个节点,则右子数有n-2个节点……以此类推,可以dp来做

  2. n对括号有多少种合法匹配方式?考虑n对括号,相当于有2n个符号,n个左括号、n个右括号。动态规化的思想:可以设问题的解为dp(2n)。第0个符号肯定为左括号,与之匹配的右括号必须为第2i+1字符。因为如果是第2i个字符,那么第0个字符与第2i个字符间包含奇数个字符,而奇数个字符是无法构成匹配的。通过简单分析,可以得出如下的递推式 f(2i) = f(0)*f(2i-2) + f(2)*f(2i - 4) + ... + f(2i - 4)*f(2) + f(2i-2)*f(0)。简单解释一下,f(0) * f(2n-2)表示第0个字符与第1个字符匹配为一个括号,以这个括号为准,剩下的括号被分成了这对括号里的0个字符,和这对括号外的2n-2个字符,然后对这两部分求解。f(2)*f(2n-4)表示第0个字符与第3个字符匹配,同时剩余字符分成两个部分,一部分为2个字符,另一部分为2n-4个字符。依次类推。

  3. 进出栈问题:一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列?和括号匹配问题相同,进栈看成左括号,出栈看成是右括号。

参考了这个,写的不错:Catalan数相关的算法问题

18. 并查集

上周的周赛(2020.1.12),第三题(1319题)就是用并查集写的,可惜我太菜了,没有写出来,所以特地来学习一下

先看了别人的博客,对并查集有了一定的了解:

虽然是C语言写的,但是写的非常有趣,适合入门:超有爱的并查集~

  • 并查集就是一个数组,用数组表示多个树,数组的下标 i 代表的是树中节点的编号,数组中的元素 arr[i] 为节点 i 的父节点,这样连起来就是一个树。树的根节点的父节点为它自己,即:i == arr[i]
  • 一个数组中有几个i == arr[i],就是有几个树(连通分量)

常用方法模版:

public int makeConnected(int n, int[][] connections) {if (n - 1 > connections.length) {return -1;}int[] arr = new int[n];//初始化并查集for (int i = 0; i < arr.length; i++) {arr[i] = i;}//合并for (int[] connection : connections) {union(connection[0], connection[1]);}//计算连通分量的个数int count = 0;for (int i = 0; i < n; i++) {if (parent[i] == i) {count++;}}
}//查找树的根,同时进行路径压缩
private int findRoot(int[] arr, int node) {return arr[node] == node ? node : (arr[node] = findRoot(arr, arr[node]));
}//合并两个树
private void union(int[] arr, int node1, int node2) {int root1 = findRoot(node1);int root2 = findRoot(node2);if (root1 != root2) {arr[root1] = root2;}
}

19. 返回代码本身

一道读题就得读半天的题:你需要返回一个字符串,这个字符串就是你提交的代码本身

关键点:

  • 由于换行需要返回的字符串中也要"\n",所以代码就写一行了
  • char c = 34;34表示的字符为双引号:""",涉及字符串的双引号, 记得用ASCII码输出代替,不要用",你会陷入无限套娃的烦恼
class Solution { public String q() { char c = 34; return s+c+s+c+';'+'}'; } static String s = "class Solution { public String q() { char c = 34; return s+c+s+c+';'+'}'; } static String s = ";}

20. 减治、分治与变治

减治、分治与变治

21. 位运算操作小技巧

n&(n-1):将n的二进制表示中的最低位1改为0

21. 快慢指针

  1. 选取链表中倒数第k个节点:快慢指针,先让快指针走k步,然后两个指针同步走,当快指针走到头时,慢指针就是链表倒数第k个节点。
  2. Floyd 判圈算法,又称龟兔赛跑算法:用来检测一个链表是否有环。如果链表上存在环,那么在某个环上以不同速度前进的2个指针必定会在某个时刻相遇。
    1. 环起点的判断:当2个指针相遇时,将其中一个指针移到链表头部,另一个指针还是在他们相遇的地方,然后都以步长为1向后移动,当他们再次相遇时,即为环的起点。
    2. 环长度的计算

22. 结果对1e9+7(1000000007)取模

大数阶乘,大数的排列组合等,一般都要求将输出结果对1000000007取模,主要有以下原因:

  1. 1000000007是一个质数
  2. int32位的最大值为2147483647,所以对于int32位来说1000000007足够大
  3. int64位的最大值为2^63-1,对于1000000007来说它的平方不会在int64中溢出,因为(a∗b)%c=((a%c)∗(b%c))%c,所以相乘时两边都对1000000007取模,再保存在int64里面不会溢出 。

23. 约瑟夫环问题

约瑟夫问题是个著名的问题:N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。

递推公式f(N, M) = (f(N−1, M) + M) % N

  • f(N,M)表示,N个人报数,每报到M时杀掉那个人,最终胜利者的编号
  • f(N−1,M)表示,N-1个人报数,每报到M时杀掉那个人,最终胜利者的编号

原理这篇博客讲的挺不错的:约瑟夫环——公式法(递推公式)

关键点:

  • 问题1:假设我们已经知道11个人时,胜利者的下标位置为6。那下一轮10个人时,胜利者的下标位置为多少?

    • 答:其实吧,第一轮删掉编号为3的人后,之后的人都往前面移动了3位,胜利这也往前移动了3位,所以他的下标位置由6变成3。
  • 问题2:假设我们已经知道10个人时,胜利者的下标位置为3。那下一轮11个人时,胜利者的下标位置为多少?
    • 答:这可以看错是上一个问题的逆过程,大家都往后移动3位,所以f(11, 3) = f(10, 3) + 3。不过有可能数组会越界,所以最后模上当前人数的个数,f(11, 3) =(f(10, 3) + 3)% 11

24. 求最大公约数

  1. 求两个数的最大公约数:

    // 辗转相除法
    private int gcd (int a, int b) {return b == 0? a: gcd(b, a % b);
    }
    
  2. 求多个数的最大公约数:(gcd的结合律)

    gcd(a, b, c) = gcd(gcd(a, b), c)
    

25. 字典树

字典树又名前缀树,Trie树,是一种存储大量字符串的树形数据结构,相比于HashMap存储,在存储单词(和语种无关,任意语言都可以)的场景上,节省了大量的内存空间。

下图演示了一个保存了8个单词的字典树的结构,8个单词分别是:“A”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”。

怎么理解这颗树呢?你从根节点走到叶子节点,尝试走一下所有的路径。你会发现,每条从根节点到叶子节点的路径都构成了单词(有的不需要走到叶子节点也是单词,比如 “i” 和 “in”)。trie树里的每个节点只需要保存当前的字符就可以了(当然你也可以额外记录别的信息,比如记录一下如果以当前节点结束是否构成单词)。

  • trid树的练习:208. 实现 Trie (前缀树)

  • trie树的使用:

  1. 搜索引擎中,输入一个字,会出现以该字为前缀的相关搜索
  2. 区块链:trie树的进阶版,Merkle Patricia Tree,他能够高效、安全地验证大型数据结构中的数据
  3. IP路由,倒排索引
  4. 分词

26. 二维数组中的bfs

经常会遇到在二维数组中使用bfs时,要遍历四个方向或者八个方向,这时可以使用下面的方法避免写4个判断或者4个循环

//定义偏移数组
int[] dx = {0, 0, 1, -1}; //八个方向:{0, 0, 1, -1, 1, 1, -1, -1}
int[] dy = {1, -1, 0, 0}; //八个方向:{1, -1, 0, 0, 1, -1, -1, 1}//在偏移数组中循环4(偏移数组的长度)次
for (int i = 0; i < 4; i++) {//获取偏移之后的数组坐标int newX = x + dx[i];int newY = y + dy[i];//越界检查与条件判断,m和n分别表示二维数组的大小为m*nif (newX < 0 || newX >= m || newY < 0 || newY >= n) {continue;}/*执行其它操作*/
}

27. 单调栈

单调栈就是比普通的栈多一个性质,即维护一个栈内元素单调递增或者递减。比如当前某个单调递减的栈的元素从栈底到栈顶分别是:[10, 9, 8, 3, 2],如果要入栈元素5,需要先把 23 从栈中pop出去,满足单调递减为止,即变成[10, 9, 8],然后再入栈5,就是[10, 9, 8, 5]

相应的题:

  • 42. 接雨水
  • 84. 柱状图中最大的矩形

28. 方阵翻转替代旋转

先沿对角线翻转,再沿水平线或者垂直线翻转,可以实现方阵顺时针或逆时针旋转90度。

不同的对角线和水平/垂直线搭配,旋转的方向不同:

  • 左上-右下 + 水平:逆时针90度
  • 左上-右下 + 垂直:顺时针90度
  • 右上-左下 + 水平:顺时针90度
  • 右上-左下 + 垂直:逆时针90度

29. 数状数组

树状数组(Fenwick Tree)是用数组来模拟树形结构,可以解决大部分基于区间上的更新以及求和问题。树状数组中修改和查询的复杂度都是O(logN)

数状数组的功能主要有下面两个:

  1. 单点更新 update(i, v): 把序列 i 位置的数加上一个值 v
  2. 区间查询 query(i): 查询序列 [1][i] 区间的和,即 i 位置的前缀和
public class FenwickTree {private int[] tree;private int len;public FenwickTree(int n) {this.len = n;tree = new int[n + 1];}/*** 单点更新:将 index 这个位置 + delta** @param i* @param delta*/public void update(int i, int delta) {// 从下到上,最多到 size,可以等于 sizewhile (i <= this.len) {tree[i] += delta;i += lowbit(i);}}// 区间查询:查询小于等于 tree[index] 的元素个数// 查询的语义是「前缀和」public int query(int i) {// 从右到左查询int sum = 0;while (i > 0) {sum += tree[i];i -= lowbit(i);}return sum;}//求 x 的二进制中从最低位到高位连续零的长度(称为lowbit)public int lowbit(int x) {return x & (-x);}
}

用到的地方:

  1. 求数组中的逆序对(也可以使用归并排序来实现)

参考:

  • 树状数组详解
  • 力扣题解:树状数组的详细分析(含实例)

30. Morris 中序遍历

无论是二叉树的中序遍历还是用 stack 模拟递归,都需要 O(n) 的空间复杂度。

Morris 遍历是一种 O(1) 空间复杂度 的遍历方法,其本质是 线索二叉树(Threaded Binary Tree),利用二叉树中 n+1 个指向 NULL 的指针。

先明确一些基础概念:

  1. 中序遍历:先遍历节点的左子树,然后遍历当前节点,最后遍历当前节点的右子树。对于二叉搜索树,其中序遍历是递增的。
  2. 前驱节点与后继节点:对一棵二叉树进行中序遍历,遍历后的顺序,当前节点的前一个节点为该节点的前驱节点,后一个节点为后继节点。

Morris 中序遍历步骤:

  1. 如果当前节点没有左子树,则遍历这个点,然后跳转到当前节点的右子树。
  2. 如果当前节点有左子树,那么先找到它的前驱节点。它的前驱节点一定在左子树上,我们可以在左子树上一直向右行走,找到当前点的前驱节点。
    1. 如果前驱节点没有右子树,就将前驱节点的 right 指针指向当前节点。这一步是为了在遍历完前驱节点后能找到前驱节点的后继,也就是当前节点。
    2. 如果前驱节点的右子树为当前节点,说明前驱节点已经被遍历过并被修改了 right 指针,这个时候我们重新将前驱的右孩子设置为空,遍历当前的点,然后跳转到当前节点的右子树。
// cur 为当前节点, pre 为前驱节点
TreeNode cur = root, pre = null;
while (cur != null) {if (cur.left == null) {//当前节点没有左子树的情况System.out.println(cur.val);cur = cur.right;continue;}//当前节点有左子树,先找到他的前驱节点pre = cur.left;while (pre.right != null && pre.right != cur) {pre = pre.right;}if (pre.right == null) {//前驱节点没有被改过,说明左子树没有被遍历,遍历左子树pre.right = cur;cur = cur.left;} else {//前驱节点被改过,说明左子树遍历过了,接下来就是遍历当前节点和右子树pre.right = null;System.out.println(cur.val);cur = cur.right;}
}

二叉树节点类:

public class TreeNode {int val;TreeNode left;TreeNode right;TreeNode(int x) {val = x;}
}

其它

  • map.getOrDefault(key, defaultValue):如果没有key,则取出defaultValue,否则取出key对应的value值
  • 根据map的value排序:
    • 用map的方法map.entrySet()生成Set集合,遍历集合将键值对存放到优先队列PriorityQueue中,并指定Comparator
  • javafx.util.Pair对象:指一对键值对,只能存放一对,可以用于方法返回两个参数的情况,与map中的Entry类似,方法也相同(getKey(),getValue()),不过比map更轻量。使用时导包:javafx.util.Pair
  • 清空StringBuilder:stringBuilder.setLength(0);

持续更新中……

LeetCode刷题遇到的小知识点总结相关推荐

  1. C#LeetCode刷题之#453-最小移动次数使数组元素相等(Minimum Moves to Equal Array Elements)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3877 访问. 给定一个长度为 n 的非空整数数组,找到让数组所有 ...

  2. C#LeetCode刷题之#155-最小栈(Min Stack)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4020 访问. 设计一个支持 push,pop,top 操作,并能 ...

  3. C#LeetCode刷题-设计

    设计篇 # 题名 刷题 通过率 难度 146 LRU缓存机制 33.1% 困难 155 最小栈 C#LeetCode刷题之#155-最小栈(Min Stack) 44.9% 简单 173 二叉搜索树迭 ...

  4. C#LeetCode刷题-栈

    栈篇 # 题名 刷题 通过率 难度 20 有效的括号 C#LeetCode刷题之#20-有效的括号(Valid Parentheses) 33.0% 简单 42 接雨水 35.6% 困难 71 简化路 ...

  5. C#LeetCode刷题-数学

    数学篇 # 题名 刷题 通过率 难度 2 两数相加 29.0% 中等 7 反转整数 C#LeetCode刷题之#7-反转整数(Reverse Integer) 28.6% 简单 8 字符串转整数 (a ...

  6. 小何同学的leetcode刷题笔记 基础篇(01)整数反转

    小何同学的leetcode刷题笔记 基础篇(01)整数反转[07] *** [01]数学取余法*** 对数字进行数位操作时,常见的方法便是用取余的方法提取出各位数字,再进行操作 操作(1):对10取余 ...

  7. 个人LeetCode刷题记录(带题目链接及解答)持续更新

    Leetcode 刷题 注:~[完成]代表还有一些方法没看,最后再看 一.一些需要重刷的典型题: 1.快速排序,归并排序,堆排序(递归的思想) 2.链表中的回文链表,其中的快慢指针,多看,多练 3.链 ...

  8. Leetcode刷题第1题:两数之和(基于Java语言)

    ** Leetcode刷题第1题:两数之和(基于Java语言) ** 题目: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标 ...

  9. 卷进大厂系列之LeetCode刷题笔记:二分查找(简单)

    LeetCode刷题笔记:二分查找(简单) 学算法,刷力扣,加油卷,进大厂! 题目描述 涉及算法 题目解答 学算法,刷力扣,加油卷,进大厂! 题目描述 力扣题目链接 给定一个 n 个元素有序的(升序) ...

最新文章

  1. 通往安全自动驾驶汽车的艰难道路
  2. Developer Express 之 XtraReport如何显示设计窗体,打开已设计过的报表
  3. 静默安装oracle
  4. Some notes for CLFS2017
  5. JQuery果然是神器,这里顺便测试一下我发现的那个漏洞!
  6. ABAP--关于ABAP流程处理的一些命令的说明(stop,exit,return,check,reject)
  7. java-设计模式(结构型)-【代理模式】
  8. C均值聚类算法及其实现
  9. linux错误—2.man:command not found
  10. 4.ES 相关插件安装
  11. BeyondCompare3密钥过期如何处理。
  12. Latex英文论文模板汇总(elsevier、arXiv、IEEE Access)
  13. rgba 和 IE 的 filter数值转换
  14. 浅谈Hibernate的flush机制
  15. 英语 前缀 (整理中)
  16. kettle 完成处理后的字母含义,(I)nput, (O)utput, (R)ead, (W)ritten, (U)pdated, (E)rror
  17. 09|自研or借力(下):集成Gin替换已有核心
  18. 点分治+CDQ分治+整体二分全纪录
  19. VMware 虚拟网卡防火墙问题
  20. hive一个字段包含另一个字段

热门文章

  1. macOS更新10.14.6 更新到macOS Monterey
  2. Chrome浏览器通过EasyPlayer播放多路flv视频流后浏览器崩溃是什么原因?
  3. 将C语言的字符串转为OC的字符串
  4. 计算机模拟人脑,人造突触问世,计算机模拟人脑不是梦
  5. 使用Ballerina构建API网关
  6. A Game of Thrones(21)
  7. 卡券、直充订单列表(post 表单提交)接口
  8. Python4班平均成绩统计_郑州十一中2020届高考成绩简析(含新疆内高班)
  9. Codeforces 513F1 513F2 Scaygerboss 网络流
  10. 爬虫(三):爬取西刺高匿代理