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) {
    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++;//后移
    }
    

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 = ";}

其它

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

持续更新中……

发布了45 篇原创文章 · 获赞 46 · 访问量 1809

猜你喜欢

转载自blog.csdn.net/zyx1260168395/article/details/103533005
今日推荐