简略地描述思路,以供后来观之
本笔记只是备忘录,并不是详细解决方案,请使用ctrl+F查询
Algorithm
1. 打印字符串全排列
递归后需要马上还原(因为所有的),因为递归是一颗深度优先的树(也就是栈),是先从叶子开始改变的
2. 二叉搜索树转为双向链
中序遍历二叉搜索树正好是递增的,只需要把结点左右指针连接中序中前后的结点。(二叉搜索树没有相同值的点)
3. 递归求排列
求排列时若用递归法,递归叶子回溯到父亲时需要还原改变(叶子最先改变,也就是说是从字符串数组最后两位开始的),不然就不是同一串字符串出发的(画出排列树推吧)
3. 复制复杂链表
每个结点复制一个接在该结点后面,a->a'->b->b'->c->c',同时复制原结点所指向的任意结点,最后奇偶结点分离(跳着分离)
4. 最小的k个数
partition函数(快排)最后得到的index就是他在序列中的位置,左边的就是前index个数。(期间可以用二分法逐渐缩小区间,由于是用一个固定位置的数作pivot所以最后得到的index是针对整个数组而言的)
5. 集合和最大堆基于红黑树
6. 最大子数组和
如果累加和为负则重新从新元素开始
7. 从1到n数里包含1的个数
O(log10N) , 十位上,每轮出现10次是指[10,19]这10个数 解析
8. 逆序对
归并排序的同时进行计数
9. 数组排成最小的数
可以用sprintf将数字写入字符串
10. 二叉树最低公共祖先
(若是二叉搜索树)比一个结点大,比另一个结点小;(若有指向父节点的指针)寻找两个链表的公共结点;(什么都不是)用栈递归到叶子再逐个往上退栈。
11. 二叉树路径和等于某个数
传递vector的引用(用vector作栈方便顺序输出),递归遍历,进入结点时压入该结点,退出时弹出(恢复成父节点进入的情景)
12. 不能继承的类
虚拟继承virtual 父类,把父类构造函数设为私有,并且父类含有一个友元函数,友元函数的类型是由模板类给出的,所以虚拟继承的时候需要把子类的类型当成参数传入;(只能在堆上)私有构造与析构,用静态getInstance函数返回一个新的父类对象。
13. C++按类中声明的元素顺序初始化成员
14. 求方程正整数解个数
15. 多路平衡归并排序
外排序,m个归并项(子表个数)k路归并的归并趟数s=logk(m),败者树
16. n阶乘末尾0的个数
(n除以5,即求5的质因数个数) 存在0必然是52的结果,所以必然含有5!(5432*1),所以只要求多少个5的倍数即可
17. mongodb为什么没有自增id
因为并发情况下同时插入,分布式存储的情况下无法协调分配连续数字(协调需要消耗网络资源),并且内部也不是按id顺序存的,因为文档长度可变,不知道什么时候文档变长要放到后面去
18. 数n的约数个数
(约数:能把别人整除的叫约数)自然数的约数的个数是有限的,质数的约数是1和本身;合数一定有3个以上的约数。由于约数个数定理知道:约数的个数等于:所有质因数的指数加上1后的乘积; 若一个数分解质因数后为(a^m)*(b^n),其中a,b均为质因数;m,n均为相应质因数的指数. 则约数个数为(m+1)(n+1).(因为a^0,a^1 ... a^m,都是它的约数共m+1个)
例如: (1)12=2^2 * 3,质因数有2和3,其指数分别为2和1,那么12的约数有(2+1) *(1+1)=6(个); (2)60=22 * 3 * 5,质因数2, 3, 5的指数分别为2, 1, 1,那么60的约数有(2+1) * (1+1) * (1+1)=12(个)
19. 一个数所有约数之和
一个数所有约数之和 等于先把每个质因数从0次幂一直加到其最高次幂,再把每个相应质因数幂的和相乘. 若一个数分解为(a^m)*(b^n),则这个数所有约数的和为: (a^0+a^1+a^2+a^3+…+a^m)(b^0+b^1+b^2+b^3+…+b^n).
例如: (1)12=223,则12所有约数的和为:(2^0+2^1+2^2)(3^0+3^1)=74=28; (2)60=2235=(2^0+2^1+2^2)(3^0+3^1)(5^0+5^1)=74*6=168.
20. Trie 树
21. 需要同时移动多少步A和B才同时指向一个节点
长度为100的循环链表,指针A和指针B都指向了链表中的同一个节点,A以步长为1向前移动,B以步长为3向前移动,一共需要同时移动多少步A和B才能再次指向同一个节点?
- 假定经过n步A、B再次相遇。则A经过的结点为n,B经过的结点为3n;此刻B必然比A多经过了整数倍的链表长度(圈的长度),假定经过了i倍的链表长度,则有3n-n=100i,即2n=100i;满足该等式的最小整数位i=2,n=100。即A经过了100个结点,B经过了300个结点,二者再次相遇。
- 也可以认为跑圈100m,A速度1m/s B速度3m/s,B比A多跑一圈耗时100/(3-1)=50s
22. 模拟加减乘
注意 ^按位异或 、& 按位与 、| 按位或
加法运算
将一个整数用二进制表示,其加法运算就是:相异(^)时,本位为1,进位为0;同为1时本位为0,进位为1;同为0时,本位进位均为0。
故不计进位的和为sum = a^b,进位就是arr = a&b,(与sum相加时先左移一位,因为这是进位)。完成加法直到进位为0.
int Add(int a, int b){
return b ? Add(a^b, (a&b)<<1) : a;
}
求相反数
//求a的相反数:将各位取反加一
int negative(int a){ //get -a
return Add(~a, 1);
}
减法运算
a-b = a+(-b) 根据补码的特性,各位取反加1即可(注意得到的是相反数,不是该数的补码,因为符号位改变了) (上面用二进制实现的加减法可以直接应用于负数)
int Minus(int a, int b)
{
return Add(a, negative(b));
}
乘法运算
原理上还是通过加法计算。将b个a相加 参考二进制数转十进制数,每位有一个2次幂的权重,当该位为1时,加上 2^x 倍 a 所以权重 a 不断 a<<=1
//正数乘法
int Multi(int a, int b){
int ans = 0;
while(b){
if(b&1)
ans = Add(ans, a);
a = a << 1;
b = b >> 1;
}
return ans;
}
//正数除法
int Divide(int a, int b)
{
int coun = 0;
while(a >= b)
{
a = Minus(a, b);
coun = Add(coun, 1);
}
return coun;
}
除法运算
除法运算是乘法的逆。看a最多能减去多少个b,
//判断是否是负数,0,正数
int isneg(int a)
{
return a & 0x8000;
}
int iszero(int a)
{
return !(a & 0xFFFF);
}
int ispos(int a)
{
return (a&0xFFFF) && !(a&0x8000);
}
//处理负数的乘法和除法
int My_Multi(int a, int b)
{
if(iszero(a) || iszero(b))
return 0;
if(isneg(a))
{
if(isneg(b))
return Multi(negative(a), negative(b));
else
return negative(Multi(negative(a), b));
}else if(isneg(b))
return negative(Multi(a, negative(b)));
else
return Multi(a, b);
}
int My_Divide(int a, int b)
{
if(iszero(b))
{
cout << "Error!" << endl;
exit(1);
}
if(iszero(a))
return 0;
if(isneg(a))
{
if(isneg(b))
return Divide(negative(a), negative(b));
else
return negative(Divide(negative(a), b));
}else if(isneg(b))
return negative(Divide(a, negative(b)));
else
return Divide(a, b);
}
计算统中数值一律用补码来表示,因为补码可以使符号位和数值位统一处理,同时可以使减法按照加法来处理。 原码 -> 补码: 数值位取反加1
数值位不包括符号位补码 -> 原码: 对该补码的数值位继续 取反加1 补码的绝对值:(称为真值)正数的真值就是本身,负数的真值是各位(包括符号位)取反加1(即变成原码并把符号位取反) b -> -b : 各位(包括符号位)取反加1
旋转字符:字符串前面的部分排在后面
"abcd"的旋转串是"abcd","bcda" ... "dabc" 首先目标串与原串长度要相等
- 可以设字符
s+s,只要是s+s的子串都是旋转字符 比如:"abcd"+"abcd",即"abcdabcd"中寻找子串,时间复杂度是根据查找子串的时间来定的,所以如果是采用KMP则是O(N)
字符串提取出现的正负整数总和
注意可以负负得正
设置符号位positive,cur当前字符,num每一位不断往高位挪的积累数(字符串转化为数字),res数字总和
- 当cur为数字时,
num = num * 10 + positive? cur:-cur; - 当cur不为数字的时候,说明此时刚刚结束数字的转化,或者是开始部分没有数字,无论哪种都应该加上刚刚转化的数字num,并把num置零重新开始计数
此时需要注意当cur为负号时,前一位为负则positive=true,否则为负;如果不是负号,则重置positive为正
字符串转换成int范围内数字
字符串可以是只出现一个0,即 0
字符串不能出现 - 、 -0 、 -02 、 A12
如果没有出现违法,只要检查是否每个字符都是数字就可以,
- 接下来进入判断,由于int范围是
-2147483648到2147483647先把所有的数字都转为负数(转为负数可以不用再写一遍正数的,最后如果是整数再负负得正回去) - 判断是否溢出,因为
-214748364 * 10 + (-8)刚好是最大整数 先判断是否( res < = -214748364 )|| (res == -214748364 && cur <= -8 )同时也要注意如果是正整数,则转换后不能大于 2147483647
字符串replace a with b
for(for...){
if(a[i++]==b[j]){
j++ ;
if(j==源串长){ // 不用源串长-1,前面++过了
标记a串 [i-源串长,i] 为0或者某个特定符表示需要删掉
//(为了节省时间我们可以不删掉,只是不打印就可以)
}
}
else j=0;
}
当a[i]=0而a[i-1]!=0时表示可以开始将前面积累的字符串打印了
res = res + cur + to注意最后是如果末尾全是0,没有到不是0的过度,我们需要再手动把这部分转换加上res += cur
计算连续出现字符的个数
aabbbcc -> a_2_b_3_c2
这种连续计数,在字符变化后结束上一轮计数的都是一种做法,注意末尾部分没有转换需要手动加上
- 本题从第二个字符开始判断每个字符和前一个字符如果不一样,进入转换,记录数字个数,重置计数个数为1(因为当转换出现,和前个不一样,前个计数为1,例如ab,从b开始,a计数为1)
如果是 a_2_b_3_c2 -> aabbbcc,求第5个字符是什么,此处是b 由于有"_",可以因此分别数字与字符,保存当前字符cur,记录数字,当数字大于需要求的位置则返回
判断是否每个字符只出现一次
可以用Map 但是如果只花费O(1)空间,只能使用堆排序,将数组排序对比前后是否相同
浮点数高精度幂
替换空为%20
先扫描一遍记录总长度为len+2*blank,再从后往前复制
一般挪动数组元素的,都需要从右往左复制
reverse字符串
reverse整个串
while(start<end){ swap(c[start],c[end]); start++; end--; }
reverse前后部分顺序
abcde->deabc 先翻转abc,再翻转de,再翻转整个串
变成回文串需要的最少字符
str长为N, 的动态规划表 dp[i][j]表示str[i..j]这段字符最少需要多个字符使str[i..j]变成回文串
dp[i][j]=
未完待续
判断最长左右括号
()() 与 (())合法,返回最长的合法出现的括号位置
- 先检查
(和)的数量是否相等 - 设dp[i]表示str[0..i]位置上,以str[i]结尾的最长有效括号长度
- str[i]==
(,有效括号长度子串以)结尾,故dp[i]=0; - str[i]==
),dp[i-1]代表str[i-1]结尾的最长有效括号长度,上一处没有被匹配过的位置i-1-dp[i-1]上如果是(,可以和str[i]凑成一对,此时dp[i]=dp[i-1]+2,同时dp[i-1]前面即str[i-2]之前的dp[i-2]也应该算上
- str[i]==
- 最后求解
Max(dp[0..N-1])就是最终结果
计算字符串 4*((3*(-4)+-4*3))的结果
每次碰到(就进入递归进入下一层,)就结束递归,并传递参数 (char [], 下一个位置位置 )数组返回(计算的结果, 结束的位置)
Recur(char [] , int i){
while(i < length && ch[i] !=')'){
if( ch[i] 是数字 ){
pre = pre * 10 + ch[i] - '0';
i++;
}else if (ch[i] 是操作符 ){
队列a入队pre
队列a入队ch[i]
pre = 0;
i++;
}else(ch[i] == '('){
array = 递归本函数(ch,i+1)
pre = array[0];
i = array[1]+1;
}
}
}
整数N的二进制全排中0左边必有1的个数
按顺序拼接字符串使总结果字典序最小
- 先按照拼接的方式将数组排列,即 return (a+b)>(b+a)
- 将排序后数组从头到尾组合起来
贪心算法,有详细需证明它的贪心策略是正确
新类型字符a、Aa、AA 中第k个字符是哪种类型的
从k-1个字符开始往左对大写字母计数,碰到小写字母停止
- 根据奇偶判断
k和k+1是AA,还是Aa,还是k+1是单独的a
回文段最少切割数
dp[i]是子串str[i..len-1]需要切几次才能使str[i..len-1]全切成回文串,dp[0]是最后结果
从右往左,i初始为len-1
- 如果 str[i..j]是回文串,
dp[i]=dp[j+1]+1,只需从str[j+1..len-1]寻找最经济的切割法 - j 从 [i..len-1] 遍历,
dp[i]=Min{dp[j+1]+1},i<=j<len且str[i..j]为回文串 - 快速判断是否是回文串的方法是回文子串长度的相同方法,设
p[i][j]为i..j间是否是回文串的判定 以下情况p[i][j]是回文串:-
str[i..j]长度为1
-
str[i..j]长度为2,且2个字符相等
-
子串str[i+1..j-1]是回文串(即p[i][j]=true),且str[i]==str[j],最外面的两个字符串相等。
-
i 是从右往左,j是从i往右,所以
i..j间长度是展开的便于计算p[i][j]
-
移动0
二叉树的中序遍历
爬楼梯、括号生成
删除排序数组中的重复项
盛水最多的容器
跳表
trim树 - 搜索自动补全
TSP问题
三重dp
拓扑 + ?
单调栈
括号生成
大根堆
PriorityQueue默认是小跟堆
背包问题为什么不能换顺序?组合和排序有什么不同? 卡特兰数是什么 ⭐
快排优化
-
保卫王国 - 牛客
归并排序
剑指 Offer 51. 数组中的逆序对和 [315. 计算右侧小于当前元素的个数]都是归并排序的相同方法,区别在于左指针还是右指针入队时判断
- 当递归到规模足够小时,利用插入排序(归并排序只是需要两个有序数组,小于7的时候使用插入排序可以更节省栈空间)
- 归并前判断一下是否还有必要归并(如果左边的最大的比右边的最小的还小(或者等于),那就不用归并了,已经有序了)
- 只在排序前开辟一次空间(如文中所述,
System.copyOfRange每次会分配空间,System.arraycopy只是每次在已经开辟的空间上复制 )
方法一 递归归并
public int reversePairs(int[] arr) {
int[] temp = new int[arr.length];
return sort(arr, 0, arr.length - 1, temp);
}
// 变成 [left, mid] [mid + 1, right]的闭区间
public int sort(int[] arr, int left, int right, int[] temp) {
if (left >= right) return 0;
// mid + 1的时候left可能会比right大1
int mid = (right - left) / 2 + left; // 防止溢出
int leftReverse = sort(arr, left, mid, temp);
int rightReverse = sort(arr, mid + 1, right, temp);
// 对已经排序后,两个有序数组来说,此处发现已经有序,不用继续
if (arr[mid] <= arr[mid + 1]) return leftReverse + rightReverse;
return mergeS(arr, left, mid, right, temp) + leftReverse + rightReverse;
}
private int mergeS(int[] arr, int l, int m, int r, int[] temp) {
int total = r - l + 1;
// 拷贝一遍当前排序状态的[l, r]当作辅助数组
// 一个用于指针遍历,一个用于存放结果
// 这里是修改了原数组存放结果,拷贝的辅助数组来遍历指针
System.arraycopy(arr, l, temp, l, total);
int reverseNum = 0;
int p = l;
int q = m + 1;
for (int i = l; i <= r; i++) {
if (p > m) { // 左区间指针已经走完
arr[i] = temp[q++];
// 已经没有
} else if (q > r) { // 右区间指针已经走完
arr[i] = temp[p++];
} else if (temp[p] <= temp[q]) { // 辅助数组大于等于时,归并排序才是稳定排序(指相同数字依然按照原数组顺序排序)
arr[i] = temp[p++]; // 左区间更小
} else { // 右区间更小
arr[i] = temp[q++];
// 右指针前进时,左区间尚存的数都是该数都逆序数(两数组各自有序,左区间尚存的数肯定比右指针之前一个指向的数大)
reverseNum += m - p + 1;
}
}
return reverseNum;
}
354. 俄罗斯套娃信封问题
长宽都需要大于另一个信封时才能被套娃
- 按长升序,宽降序
长相同的信封不能套娃,所以长相同时需要按宽降序,忽略相同的长:使用LIS寻找不连续的上升序列的时候可以自动忽略相同的长
- 使用LIS对宽进行LIS,寻找不连续的上升序列
最长连续序列
并查集,用v和v+1并组,每一个数字a的parent就是并查集里该组对应的最大数字b,b - a + 1 就是的距离取max
public String[] largestNumberList(int[] nums) {
// Get input integers as strings.
String[] asStrs = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
asStrs[i] = String.valueOf(nums[i]);
}
// 把问题丢给string...
Arrays.sort(asStrs, (a, b) -> (b + a).compareTo(a + b)); // 降序
// 特别地,所有数都为0的情况下,返回0(因为前导0是要去掉的)
if (asStrs[0].equals("0")) {
return "0";
}
return asStr;
}
46. 全排列
总个数是卡特兰数 方法一,回溯,不重复元素下标,然后对应两个位置交换。这种不保证字典序,保证字典序需要预先对list排序
public void recursion(List<Integer> a, int k) {
if (index == nums.length) {
res.add(new ArrayList<>(a));
return;
}
for (int i = 0; i < total; i++) {
for (int j = i; j < total; j++) {
swap(a, i, j); // 注意重要的点在这里
recursion(a, k + 1);
swap(a, j, i);
}
}
}
方法二,保证字典序,for循环每个位置选a里的数组,找出所有组合,然后用used去重
public List<List<Integer>> permute(int[] nums) {
allComposeWithNoMap(0, nums, new ArrayList<>());
return resAll;
}
private void allComposeNext(int index, int[] nums, List<Integer> res, Set<Integer> used) {
if (index == nums.length) {
resAll.add(new ArrayList<>(res));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used.contains(nums[i])) continue;
res.add(nums[i]);
used.add(nums[i]);
allComposeNext(index + 1, nums, res, used);
res.remove(res.size() - 1);
used.remove(nums[i]);
}
}
329. 矩阵中的最长递增路径
- 从某个位置出发的最大步数是固定的。
- 由于只用记步数,所以dfs四个方向最大的步数可以记忆化记下来
- 经过自己需要++1
- memo记忆化数组不为空,表示搜索过以这个点出发
也可以用BFS+拓扑排序 设(i,j)比相邻点大,出度为0,这就是边界条件 计算全图出度,从所有出度为0的点开始BFS 每遍历一个点,就拓扑排序去掉一个出度,若出度减为0,则入队BFS 结果为BFS层序遍历的次数
螺旋矩阵
可以构造visited[]数组记录去过的地方,然后用dir[]数组记录要走的方向,这个会比较通用一点的解法。
简单解法是构造(l, t, r, b)方框,每走完一边就缩小范围,以下两个方法都是这样做的
方法一
// ---------
// | |
// | |
// --------|
private static List<Integer> calcBounds(int[][] matrix) {
List<Integer> res = new ArrayList<>();
int row = matrix.length;
int col = matrix[0].length;
int l = 0, t = 0, b = row - 1, r = col - 1;
while (true) {
for (int i = l; i <= r; i++) res.add(matrix[t][i]);
if (++t > b) break;
for (int i = t; i <= b; i++) res.add(matrix[i][r]);
if (--r < l) break;
for (int i = r; i >= l; i--) res.add(matrix[b][i]);
if (--b < t) break;
// 此处t已经下挪过一了
for (int i = b; i >= t; i--) res.add(matrix[i][l]);
if (++l > r) break;
}
return res;
}
}
方法二
// ---------
// | |
// | |
// |-------|
private static List<Integer> calcBounds1(int[][] matrix) {
List<Integer> order = new ArrayList<Integer>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return order;
}
int rows = matrix.length, columns = matrix[0].length;
int l = 0, r = columns - 1, t = 0, b = rows - 1;
while (l <= r && t <= b) {
for (int column = l; column <= r; column++) {
order.add(matrix[t][column]);
}
for (int row = t + 1; row <= b; row++) {
order.add(matrix[row][r]);
}
if (l < r && t < b) {
for (int column = r - 1; column > l; column--) {
order.add(matrix[b][column]);
}
for (int row = b; row > t; row--) {
order.add(matrix[row][l]);
}
}
// 统一缩小
l++;
r--;
t++;
b--;
}
return order;
}
约瑟夫环问题
矩阵最长递增路径
同一个单元格对应的最长递增路径的长度是固定不变的。
所以记忆化搜索从memo[i][j]开始的最长递增路径
无损压缩算法:deflate、Huffman Coding
寻找两个有序数组的中位数
要求算法的时间复杂度为 O(log(m + n)) 即找出第(m+n)/2个元素 设k=(m+n)/2,则需要在两个数组中分别寻找第k/2个元素,原因如下:
题目中要求的时间复杂度为 O(log(m + n)),很容易想到的方法就是二分,现在有两个数组,要对那个数组进行二分合适?由于找的是中位数,那么这个数字的两边的元素个数是相等的,所以只需要确定一个数组中的两边元素,两一个数组的对应的补上去就可以了,为了提高效率,要选择最短的数组做二分查找 — 原文
为什么Java中int型数据取值范围是[-2^{31}, 2^{31}-1]
为什么Java中int型数据取值范围是[-2^{31}, 2^{31}-1]
判断一个有符号int型整数是不是溢出:
x > INT_MAX / 10 || x == INT_MAX && x * 10 + 7 就会溢出 x < INT_MIN / 10 || x == INT_MIN. && x * 10 + 8 就会溢出
反转数字
push一位数字x:int res = res * 10 + x Pop一位数字:res /= 10 需要警惕是否溢出
Lru最近访问
Lru在Java里就是LinkedHashMap有序字典,每次get/put后把该元素挪动到双向链表尾。双向链表是因为方便删除,是所有数据的。
2、3、5硬币面值(无限个数),构成总数为k的方法数
和上楼梯很像,区别在于上楼梯只是1、2两步
上楼梯的状态方程:
dp[0] = dp[1] = 1, dp[i] = dp[i-1] + dp[i-2] (i > 2)。设置dp[0] = 1只是为了后续好加; 这一题就是dp[1] = 1, dp[i] = dp[i-2] + dp[i-3] + dp[i-5],意义是为了总数为i的金额是由i-2的情况和i-3和i-5的情况达成的
300. 最长上升子序列 LIS
方法一 动态规划
设dp[i]保存[0, i]的最长上升子序列 (可以不连续)
中找到的一个比当前数i小的数j,如果第j个数字小于第i个数字,i就可以跟在j后面 dp[i] = Math.max(dp[i], dp[j] + 1)(0 <= j < i)
public int lengthOfLIS(int[] nums) {
int total = nums.length;
int[] dp = new int[total];
// Arrays.fill(dp, 1);
dp[0] = 1; //注意都要初始化为1,因为自己就是一个长度为1的序列
int max = 1;
for (int i = 1; i < total; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(max, dp[i]);
}
return max;
}
方法二 贪心 + 二分
要使上升子序列尽可能的长,得让序列上升得尽可能慢,在上升子序列最后加上的那个数尽可能的小。 基于上面的贪心思路,维护一个数组 d[i] ,录目前最长上升子序列的长度,遍历数组看是否能二分插入其中获得最小值 最后数组的长度就是LIS
public int lengthOfLIS(int[] nums) {
int total = nums.length;
int len = 0; // len = 下次要插入的地方
int[] a = new int[total + 1]; // minElements 保存所有最小的上升数字,如果更小的数字可以插入到这个数组里,则替换掉
for (int i = 0; i < total; i++) {
// find where to insert in minElements 寻找在比它更小的数字后面插入
int l = 0, r = len - 1;
while (l <= r) {
int mid = (r + l) >> 1;
if (a[mid] < nums[i]) {
l = mid + 1; // 插入到这个数后
} else {
r = mid - 1;
}
}
// every element is larger than m[i], clear the array and set to m[0] 没找到比它更小的数字
a[l] = nums[i];
if (l == len) { // 比所有位置都大,需要在后面新加一个
len++;
}
}
return len;
}
击鼓传花
dp
第 i 个同学,可以收到第(i-1)个同学或者第(i+1)个同学的花。
dp[i][j]=dp[(i-1)][(j-1+n)%n]+dp[(i-1)][(j+1+n)%n];
字符压缩算法(字符串解码)
(括号匹配) 用栈
反转链表II
public static class SolutionReverseLinkedListNodeII {
//思路:head表示需要反转的头节点,pre表示需要反转头节点的前驱节点
//我们需要反转n-m次,我们将head的next节点移动到需要反转链表部分的首部,需要反转链表部分剩余节点依旧保持相对顺序即可
//比如1->2->3->4->5,m=1,n=5
//第一次反转:1(head) 2(next) 3 4 5 反转为 2 1 3 4 5
//第二次反转:2 1(head) 3(next) 4 5 反转为 3 2 1 4 5
//第三次发转:3 2 1(head) 4(next) 5 反转为 4 3 2 1 5
//第四次反转:4 3 2 1(head) 5(next) 反转为 5 4 3 2 1
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode prev = dummy;
while (m > 1) {
prev = prev.next;
m--;
n--;
}
head = prev.next;
// 1 2 3 4 5
while (n > 1) {
ListNode next = head.next; // 3 (head指向的元素一直不变)
head.next = head.next.next; // 1-> 4
next.next = prev.next; // 3 -> 2 (prev.next是上一次的next,永远是反转部分的第一个,因为prev的位置不变)
prev.next = next; // 0 -> 2
n--;
}
return dummy.next;
}
}
73. 矩阵置零
问题在于如何提醒是0 方法一:扫一遍有0的地方,额外开两个数组记下i和j。再遍历一次将出现的地方置0 方法二:扫一遍有0的地方标记为一个特殊值,再次遍历一次将特殊值出现的地方置0 方法三:由于可能不知道特殊值设置为什么,所以把出现0的地方对应的第一行第一列标记为0。第一行第一列需要一开始就判断有没有0。 最后根据第一行第一列的是否有0来置0。