LeetCode刷题遇到的小知识点总结

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

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

多看,多练

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
CASE
		WHEN 字段与某个值比较    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二进制位的该位一定不同

其它位运算:

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 == '(') {
    	//注意这里是先判断,再加1
        if (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;
    else
        hi = 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]);
            }
        }
    }
    //层数加1
    len++;
}

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来做

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

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树里的每个节点只需要保存当前的字符就可以了(当然你也可以额外记录别的信息,比如记录一下如果以当前节点结束是否构成单词)。

  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*n
    if (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]

相应的题:

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,可以等于 size
        while (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);

持续更新中……

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值