-------------------------- 模板 --------------------------
动态规划
总结
递归+额外空间保存中间状态(减少重复计算)
步骤
拿leetcode题目为例: https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/solution/nge-tou-zi-de-dian-shu-dong-tai-gui-hua-ji-qi-yo-3/
使用动态规划解决问题一般分为三步:
- 表示状态
- 找出状态转移方程
- 边界处理
下面我们一步一步分析,相信你一定会有所收获!
表示状态
分析问题的状态时,不要分析整体,只分析最后一个阶段即可!因为动态规划问题都是划分为多个阶段的,各个阶段的状态表示都是一样,而我们的最终答案在就是在最后一个阶段。
对于这道题,最后一个阶段是什么呢?
通过题目我们知道一共投掷 nn 枚骰子,那最后一个阶段很显然就是:当投掷完 nn 枚骰子后,各个点数出现的次数。
注意,这里的点数指的是前 nn 枚骰子的点数和,而不是第 nn 枚骰子的点数,下文同理。
找出了最后一个阶段,那状态表示就简单了。
- 首先用数组的第一维来表示阶段,也就是投掷完了几枚骰子。
- 然后用第二维来表示投掷完这些骰子后,可能出现的点数。
- 数组的值就表示,该阶段各个点数出现的次数。
所以状态表示就是这样的:dp[i][j]dp[i][j] ,表示投掷完 ii 枚骰子后,点数 jj 的出现次数。
找出状态转移方程
找状态转移方程也就是找各个阶段之间的转化关系,同样我们还是只需分析最后一个阶段,分析它的状态是如何得到的。
最后一个阶段也就是投掷完 nn 枚骰子后的这个阶段,我们用 dp[n][j]dp[n][j] 来表示最后一个阶段点数 jj 出现的次数。
单单看第 nn 枚骰子,它的点数可能为 1 , 2, 3, ... , 61,2,3,...,6 ,因此投掷完 nn 枚骰子后点数 jj 出现的次数,可以由投掷完 n-1n−1 枚骰子后,对应点数 j-1, j-2, j-3, ... , j-6j−1,j−2,j−3,...,j−6 出现的次数之和转化过来。
for (第n枚骰子的点数 i = 1; i <= 6; i ++) {
dp[n][j] += dp[n-1][j - i]
}
边界处理
这里的边界处理很简单,只要我们把可以直接知道的状态初始化就好了。
我们可以直接知道的状态是啥,就是第一阶段的状态:投掷完 11 枚骰子后,它的可能点数分别为 1, 2, 3, ... , 61,2,3,...,6 ,并且每个点数出现的次数都是 11 .
for (int i = 1; i <= 6; i ++) {
dp[1][i] = 1;
}
位运算
- 与(&)、非(~)、或(|)、异或(^)
- 位运算的优先级小于==, 因此在该位运算语句中 if ( (nums[i] & mask) == 0),要加入括号。
- 详细解析:位运算 - 位运算的常见用法/题目
把整数最右边一个1变成0 : 把该整数减去1,再和原来的整数做与(&)运算
把一个整数减去1,再和原来的整数做与运算,会把该整数最右边一个1变成0。
那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。
eg:
原整数为 1110...
1110 & (1110 - 1) = 1110 & 1101 = 1100
1100 & (1100 - 1) = 1100 & 1011 = 1000
1000 & (1000 - 1) = 1000 & 0111 = 0000
判断整数第i位是否是1:num & (1<
假设整数为: 0110 0000
如果我们想判断第4位是否为1, 那就把1右移4为 0000 1000, 和原数据0110 0000做与运算,结果为0.代表第4为不是1.
如果我们想判断第6位是否为1, 那就把1右移6为 0010 0000, 和原数据0110 0000做与运算,结果不为0.代表第6为是1.
把0的第i位变成1:0 ^ (1<
- 0与任何数字异或还是该数字本身
两个相同的数字做异或(^), 等于0
- 0与任何数字异或还是该数字本身
- 两个相同的数字做异或,等于0
海量数据
- 不能把所有的数据加载进内存中
- 需要维护一个固定大小的容器,每进来一个元素进行比较和保留
例题:
面试题40. 最小的k个数
题目描述的暗示
暗示你用HashMap
“假设数组中没有重复的数字”/ “保证只有一个答案”
暗示你用int[26],int[52],int[256]
当题干提示“只包含大写or小写字母”,那么使用int[26];
如果大小写均有,使用int[52];
如果是任意字符,则是int[256]
暗示你用数组解题
“在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。”
正序会覆盖,试试倒序
有时题目会要求在O(k) (或者比正常思路小)的空间复杂度里操作,可能需要重复利用空间。
如果从0往后使用,极有可能会overlap。这时可以倒序处理。
119. Pascal's Triangle II
class Solution { public ListgetRow(int rowIndex) { List result = new ArrayList<>(); int[] array = new int[rowIndex+1]; if(rowIndex < 0){ return result; } for(int i=0; i<=rowIndex; i++){ for(int j = i; j >= 0; j--){ if(j == 0 || j == i){ array[j] = 1; continue; } array[j] = array[j] + array[j-1]; } } for(int q = 0; q < array.length; q++){ result.add(array[q]); } return result; } }
二叉树
当递归函数需要记录两种信息
例题:判断一个树是否为平衡二叉树 https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/
又要记录树的高度int,又要记录树是否是平衡的boolean,可以用-1来代替boolean(false),>0的int代替boolean(true)
二叉搜索树
模板
把一个二叉搜索树“中序遍历”,会是一个递增的序列。例如下图,中序遍历后是1,5,6,7,8
注意
不能单纯的只递归左节点<中间节点 && 右节点>中间节点,不然下面的case无法通过。(4虽然小于7, 但是因为4在6的右边,也应该大于6才行)
Sliding Window 滑动窗口
模板
整体思路:左右两个指针。右指针在for循环里面控制,左指针在for内部的while循环控制。当窗口内部的某条件不符合题目要求时,会在while循环里移动左指针。
public int slidingWindowTemplate(String[] a, ...) { // 输入参数有效性判断 if (...) { ... } // 申请一个散列,用于记录窗口中具体元素的个数情况 // 这里用数组的形式呈现,也可以考虑其他数据结构 int[] hash = new int[...]; // 预处理(可省), 一般情况是改变 hash ... // l 表示左指针 // count 记录当前的条件,具体根据题目要求来定义 // result 用来存放结果 int l = 0, count = ..., result = ...; //*important* for循环只控制右指针,左指针通过内部的while语句控制 for (int r = 0; r < A.length; ++r) { // 更新新元素在散列中的数量 hash[A[r]]--; // 根据窗口的变更结果来改变条件值 if (hash[A[r]] == ...) { count++; } // 如果当前条件不满足,移动左指针直至条件满足为止 while (count > K || ...) { ... if (...) { count--; } hash[A[l]]++; l++; } // 更新结果 results = ... } return results; }
注意事项
1- 有时可以存储item本身,也可以存储index下标
2- 使用HashSet或者HashMap,让查询的时间复杂度降到O(1)
3-当题干提示只包含大写or小写字母,那么使用int[26]; 如果大小写均有,使用int[52];如果是任意字符,则是int[128]
4-当for循环的index变量, 在for循环外依旧要使用。注意该index已经超过了for循环设置的limit,例如:
当跑出for循环外,i的值变成了11,而不是10。如果想使用for的最后一个index值(10),需要将i (11)减去1后再使用。
int i = 0; for(; i <= 10; i++){ //do something... } System.out.println("i value = " + i); //i = 11,not 10
5-for循环的执行顺序
第一次进入for循环:1--> 2
其余次进入for循环:3 --> 2
因此,该循环最后一次i会变成11,从而造成>10,跳出循环。
-------------------------- 易错点 --------------------------
快速排序
相等的情况怎么处理,特别容易出错
1-在普通情况下,在while循环当中,两个while循环都要包含==情况
2-在需要自定义“大小排序”函数时,不要声明boolean返回值的判断函数,一定要是int返回型得函数 (0代表相等,>0代表n1>n2, <0代表n1
大数
1-当题目要求输出max/min时,中途会有max/min操作。此时切记不要中途做mod操作,因为原本A>B,但是如果因为A超过了规定的模值,提前mod的话,会导致A
public int cuttingRope(int n) {
BigInteger[] cache = new BigInteger[n+1];
cache[0]= new BigInteger("1");
cache[1]= new BigInteger("1");
BigInteger e= new BigInteger("1000000007");
for(int i =2; i <= n; i++){
BigInteger temp_max = new BigInteger("0");
BigInteger max = new BigInteger("0");
for(int j = 1; j <= i-1; j++){
//如果在这里提前mod...会在下一步max()出错
BigInteger t1 = (BigInteger.valueOf(j).multiply(cache[i-j]));
BigInteger t2 = (BigInteger.valueOf(j).multiply(BigInteger.valueOf(i-j)));
//这里牵扯到max比较,因为必须全都保存原始数据
temp_max = t1.max(t2);
max = max.max(temp_max);
}
//这里也不能mod,要全都保存原始数据
cache[i] = max;
}
//最最后返回时,再取mod
return cache[n].mod(e).intValue();
}
-------------------------- 常用类 --------------------------
Arrays
import java.util.Arrays;
快排排序 Arrays.sort(int[]/long[]/short[]/char[]/byte[]/float[]/double[])
int[] test = new int[]{0,3,4,2,1};
Arrays.sort(test); // test change to {0,1,2,3,4}
指定坐标区间快速排序 Arrays.sort(int[] a, int fromIndex, int toIndex)
[fromIndex, toIndex)
类型转换
List <> int[][]
Listlist = new ArrayList<>(); int[][] result = list.toArray(new int[list.size][]);
注意:此方法不适合一维数组, 因为如果一维数组调用“泛型”那个api,返回的应该是Integer[],而不是int[].
// from java.util.List interface Object[] toArray(); // 非泛型T[] toArray(T[] a); //泛型
int > char
因为是高精度>低精度的去转换,可能出现丢失精度的情况,需要强制转换。
- char c = 'a' + 10;
- char c = (char) ('a' + 10); //correct
char, char[], int, long, float, double, boolean > String
String.valueOf();
String <> char[]
char[] = String.toCharArray();
String = new String(char[]);
String = String.valueOf(char[]);
int <> BigInteger
int = BigInteger.intValue();
BigInteger = BigInteger.valueOf();
>>>THIS IS WRONG: BigInteger = new BigInteger(int);
能直接用new方法构造BigInteger的, 有 new BigInteger(String / long), new BigInteger(int[] / byte[])
int[][]
初始化
只定义有多少行即可,每一行的具体细节可以之后再赋值
//只初始化有多少行即可
int size = 5; int[][] result_arr = new int[size][]; //再单独处理每一行的细节 for(int i = 0; i < size; i++){ result_arr[i] = listToArray(result.get(i)); //具体逻辑 }
String
直接比较数字大小
可以直接调用String.compareTo()函数
int result = "123".compareTo("987") // result < 0 int result = "987".compareTo("123") // result > 0 int result = "123".compareTo("123") // result = 0
截取substring
注意函数名字的大小写,
- 是String.substring(int i, int j) // [i,j)
- 不是String.subString()
split()
以下的“$”代表一个空格“ ”
"hello$world".split(" ") --> {“hello”, "world"}
"hello$$world".split(" ") --> {“hello”, "", "world"} // Not {“hello”, "$", "world"}
"hello$$$world".split(" ") --> {“hello”, "", "", "world"} // Not {“hello”, "$", "$", "world"}
trim()
以下的“$”代表一个空格“ ”
"$$$hello$$$$world$$$$$$".trim() --> "hello$$$$world"
LinkedList
通用!!! 可以用来替代ArrayList, Stack, Queue
public class LinkedListextends AbstractSequentialList implements List , Deque , Cloneable, java.io.Serializable
代替ArrayList
LinkedList
LinkedList
LinkedList
代替Stack
LinkedList本质上是一个Queue,只不过因其实现了Deque,所以可以双向操作。
Stack的push() : LinkedList
Stack的pop() : LinkedList
Stack的peek() : LinkedList
代替Queue
Queue的add() : LinkedList
Queue的poll() : LinkedList
Queue的peek() : LinkedList
深度拷贝 Deep Clone
list2 is the deep clone of list1.
LinkedList
LinkedList
/** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public LinkedList(Collection extends E> c) { this(); addAll(c); }
ArrayList
不能用[]下标访问
ArrayList
>>>THIS IS WRONG: Integer i = list[0];
>>>THIS IS RIGHT: Integer i = list.get(0);
初始化后,不能直接调用set(int index, E element)
ArrayList
list.set(int index, E element); // Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
因为ArrayList初始化后的size是0,紧接着做任何的set都会报错。必须先使用add()操作扩容size才可。详细请查阅ArrayList
HashSet
HashSet的遍历
HashSetset = new HashSet<>(); Iterator iter = set.iterator(); while(iter.hasNext()){ System.out.println(iter.next()); }
HashSet的add()
如果所加的元素,之前不存在,会返回true。如果已经存在,则返回false。
PriorityQueue 优先队列
https://www.cnblogs.com/yongh/p/9945539.html
最大堆 & 最小堆 初始化
PriorityQueue(优先队列),一个基于优先级堆的无界优先级队列。实际上是一个堆(不指定Comparator时默认为最小堆),通过传入自定义的Comparator函数可以实现大顶堆。
PriorityQueueminHeap = new PriorityQueue (); //小顶堆,默认容量为11 PriorityQueue maxHeap = new PriorityQueue (11,new Comparator<Integer>(){ //大顶堆,容量11 @Override public int compare(Integer i1,Integer i2){ return i2-i1; } });
常用方法
PriorityQueue的常用方法有:poll(),offer(Object),size(),peek()等。
- 插入方法 offer()、poll()、add() 时间复杂度为O(log(n)) ;
- 删除方法 remove(Object) 时间复杂度为O(n)
- 包含方法 contains(Object) 时间复杂度为O(n);
- 检索方法(peek、element 和 size)时间复杂度为O(1)。
初始化capacity和size不一样
PriorityQueuequeue = new PriorityQueue<>(6, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2-o1; } }); //capacity = 6 int size = queue.size(); //size = 0