在算法题中会用到一些很基础,但业务开发中不常用的工具(类/方法)和技巧。时间长不接触可能就会忘记,这里简单总结一下。
s.charAt(i)//大量字符串相关的算法都会用到*
也可以先转成字符数组,再遍历
char[] arr = s.toCharArray();//这个并不常用,因为多了一道工序,还占用了一个数组的空间。
String[] arr = s.split(“,”);//业务中更常用的是这个,但算法中不常用
//注意substring里没有大写字母
s.substring(start, end+1)
//数字字符直接转成int【常用的技巧*】
int a = ‘8’ - ‘0’
//字符转字符串
‘c’ + “”
char c = ‘3’;
if(Character.isDigit©) {
}
当然,如果忘记了。用(c>=‘0’ && c<=‘9’) 也是可以的
尽量用StringBuilder, 而不要用StringBuffer。因为算法题基本都是单线程的,即便看起来很复杂的深度、广度优先遍历。所以尽量用“线程不安全”的简单类。
//判断长度
sb.length()>0
//设置长度
sb.setLength(xx)
int[][] a = new int[外数组数(行)][内数组数(列)];
int[][] a = {{},{},{}};//初始化一个2行3列的数组
String[][] arr = {{"aa", "bb", "dd"}, {"cc", "ee"}, {"ff", "gg", "ww", "ww", "ddd"}};
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j]);
}
System.out.println();
}
尽量不要用for(int t : arr)
,因为效率比较差。
Arrays.fill(a, 1);//把数组a全填充为1【常用方法*】
Arrays.equals(grid2[j], grid[i])
Arrays.sort(arr)
Arrays.stream(arr).sum()
建议:自己遍历相加
大概率用递归。简单好理解。
记住要合理利用二叉搜索树的特性:左子节点比根小,右子节点比根大
符号:^
特性:相同为0,相异为1
true^1 -----> false
flase^1 ------> true
1^1 -----> 0
0^1 -----> 1
可用于遍历过程中,实现一个来回切换的变量。
if ((x==0) ^ (y==0)) {
//出现了一个0,一个非0
}
虽然在日常业务开发中,Map是继List之后,最常用的集合类。但是在算法中:
能不用map就尽量不要用map。因为map本身过于复杂,会导致占空间 也大大拖慢速度(仅限于算法题目中)。
但有时候 可以参考map的hash思想,如果元素的范围不大,可以把元素的ASCII码作为下标。
map.getOrDefault(key, 0)
一般使用这个双端队列(开销小一点),注意名称是que,而不是Queue
Queue q = new ArrayDeque();
除了应用在比较好理解的滑动窗口上面(一前一后组成窗口)
还有一个很重要的就是让快慢指针的速度 满足一定的函数关系,比如2倍关系。那么当快指针到头的时候,慢指针刚好能到达一个依赖总长度的位置(比如中间)
但要注意边界点:想要在跳出循环时,慢指针刚好在指定位置,那么快指针的起步位置一般要比慢指针多一位。
int left = 0; int right = n;
int mid = 0;
//这个pos的初始化很关键,pos就是最终要取的值。
//可能遍历完也没给pos赋值,那么最终就是pos的初始值
int pos = right;
while(left <= right) {
mid = left+(right-left>>1);//这个位运算提升效率明显
if (potions[mid] > min) {
pos = mid;//至少要符合要求的位置
right = mid - 1;
} else {
left = mid + 1;
}
}
mid的计算
迭代思想,有点类似于数学里的归纳法(为了求出第n项,那么需要知道第n-1项。。。。)
听起来像是递归干的事。核心思想确实都是迭代。只不过动态规划一般使用非递归方式循环遍历。
关键是每一步都可以利用之前存起来的数据进行计算。最好的情况是:第i项刚好依赖第i-1项的数据。那么此时连额外的存储空间都不需要了。
你可能听到,动态规划的核心是:状态转移方程。
这个方程的意思就是你找到的计算第i项的通用公式。并且这个公式依赖前面计算好的数据。可以让整个计算过程,迭代成一条链,从第1项一直连接到第n项,得出最终结果。(不可迭代成一条链的公式就不属于状态转移方程)
当写出状态转移方程,构建出一张“逐行扫描”的表。通过一行一行的填表数据,得到最终的答案。那么此时就可以考虑优化的问题了。优化的思想也很简单:
m x n
的表格 优化成2 x n
的表格。1 x n
。如果滑动窗口前后端不太好获取,那么转成前缀和(前i项 - 前(i-k)项)或许好计算一些。
判断条件上有方法直接用到了迭代变量,那么一定要先对迭代变量i
进行判断
while(--i>=0 && Character.isDigit(s.charAt(i))) {
sb.insert(0, s.charAt(i));
}
栈在peek或者pop()前一定要先判断
if(stack.empty()) {
}
栈中如果存的是string,别忘了是用equals(经常往栈里存字符,有时候存字符串时就忘了)
while(!stack.peek().equals("]")) {
sb.append(stack.pop());
}
一旦用递归,就不能共用stringBuilder了,因为每一层都可能处理了一半,就调用下一层了。如果两层都在用stringBuilder就会冲突(如果算法本身就是想让多层,一点点修改stringBuilder,是可以的)。
如果比较一串节点和另外一串节点 是否一样,不要用字符串拼接。因为 1,21 和 12,1拼接出来是一样的。可以用数组。然后用Arrays.equals(arr1, arr2)
这个数据结构在处理一些特殊算法时,特别好用巧妙。典型的比如判断括号匹配问题。因为它一边处理,一边甩包袱,而且能实现正进反出的效果。
递归的本质也是栈。用的是方法调用栈。
前缀树,Trie树,也叫字典树
结构:自己存自己的数组类。然后加一个isEnd,表示单词结尾
实际生产局限性:虽然算法很巧妙,空间被最大化的利用。但实际生产中 如果要加中文,那么这个数组则会太大,过于浪费空间。还是老老实实存一个字符的更合适。
这个算法只能说是针对条件做的特殊优化。
算法题的条件一般都很简单。在这样的完美条件下,为了追求性能产生了很多被极致优化的算法。在实际生产中可以参考一些思想,但能应用的其实并不多。