文章目录
刷题遇到的小知识点,在这里做个笔记
多看,多练
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. 二叉树操作的小总结
- 当做根、左子树、右子树三部分,左子树、右子树具体是什么样的不管
- 找出终止条件,就是什么时候return,return什么
- 只考虑当前这一步要完成什么功能
4. MySQL分组内取前几名的问题
(185题)一个不错的思路:比如说取每个部门中工资前三高的员工(limit用不了),自连接,条件是工资比我高的员工,然后判断工资比我高的员工(注意相同工资的去重)的个数是不是小于3,是的话说明我就是工资前三高 的员工
5. SQL中的小问题
- CASE END 模仿多分枝选择判断,CASE END 可以用在更新语句SET后边,如
SET 字段=CASE ... END
CASE
WHEN 字段与某个值比较 THEN '解释数据1'
WHEN 字段与某个值比较可以使用聚合函数 THEN '解释数据2'
ELSE '解释数据3'
END
- CASE END模仿switch
CASE 字段名
WHEN '值1' THEN '解释数据1'
WHEN '值2' THEN '解释数据2'
ELSE '解释数据3'
END
- 掌握union的用法
- 在where后面不能用函数,可以用算式,如
id % 2 = 0
,而在其它地方要用聚合函数,如MOD(id, 2) = 0
- 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)初理解
- 交换律:
a ^ b ^ c <=> a ^ c ^ b
- 任何数于0异或为任何数:
0 ^ n => n
- 相同的数异或为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。
常见的题型:
-
n个节点构成的二叉搜索树,有多少种可能(93题)?简单思路:左子树有0个节点,则右子树有n-1个节点,左子树有1个节点,则右子数有n-2个节点……以此类推,可以dp来做
-
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个字符。依次类推。 -
进出栈问题:一个栈(无穷大)的进栈序列为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
- 用map的方法
javafx.util.Pair对象
:指一对
键值对,只能存放一对,可以用于方法返回两个参数的情况,与map中的Entry类似,方法也相同(getKey(),getValue()),不过比map更轻量
持续更新中……