leetcode 单词接龙
leetcode 164 最大间距
leetcode 220
别人总结的二分法
山脉数组中查找目标
快乐数(类似链表找循环)
注意与滑动窗口的区别
leetcode 560
leetcode 974
涉及到嵌套 和顺序有关的时候 (也可考虑递归)
leetcode 394
leetcode 201 数字范围按位与 (本质找公共前缀)
n&(n-1)把最后一个1 变0。
n&(-n) n&(~n+1) 保留最后一个1 剑指offer 数组中数字出现的次数
leetcode 204计数质数 有点类似的(丑数系列)
偶数一定不是质数,
奇数有的也不是。
所以代码中只看奇数,没有考虑偶数
class Solution {
public int countPrimes(int n) {
boolean[] temp = new boolean[n];
int res = 0;
if(n>2){
res++;
}
for(int i =3;i<n;i=i+2){
if(!temp[i]){
for(int j = 3;j*i<n;j=j+2){
temp[i*j] = true;
}
res++;
}
}
return res;
}
}
leetcode 416
leetcode 494
01背包 完全背包 总结!
看看符不符合滑动窗口,不符合考虑dp 以nums【i】结尾的。。。 子序列 子数组啥的
和最大子数组 乘积最大子数组 最短子数组 各种子数组问题 通常:1.滑动窗口 2.前缀和
leetcode 220
存放下标
leetcode 239 (优先队列 都是为了O(1)取得最大值)
边界是一 或者全部是一
leetcode 292 入门问题
几个基础博弈:
问题:有一堆石子n个 , 每次可以取1~m个石子 , 没有石子可取的那方输 , 问第一个取的人的输赢
解决方法:有一堆 n 个物品,两人轮流从堆中取物品,每次取 x 个 ( 1 ≤ x ≤ m)最多取m个。
最后取光者获胜。如果有 n = m + 1, 一次至多取 m 个,所以无论先取者,取了多少个,一定还剩下 x 个( 1 ≤ x ≤ m)。
所以,后取者必胜。因此我们发现了取胜的秘诀:如果我们把 n 表示为 n = (m + 1) * r + s 。(0 ≤ s < m , r ≥ 0)。
先取者拿走 s 个, 后取者拿走 k 个 (1 ≤ k ≤ m),那么先取者 再 拿走 m + 1 - k 个。结果还剩下 ( m + 1 ) * ( r - 1 ) 个。
我们只要始终给对手留下 m + 1 的倍数,那么先取者肯定必胜。 现在 我们可以知道,如果 s = 0,那么后取者必胜。 否则 先取者 必胜。
变形(最后取得输):
留一块给对面
(n-1)%(m+1);
有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
(n1,n2) 格局
解法: p=(sprt(5)+1)/2; 1.618
差值*p==最小值 则 后手赢
有k堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
有一堆石子,两个人轮流从其中取走一定的石子,取走最后所有石子的人为赢家,不过得遵循如下规则:
1.第一次取不能取完,至少取1颗.
2.从第二次开始,每个人取的石子数至少为1,至多为对手刚取的石子数的两倍。
解决方法:当n为Fibonacci数时,先手必败。否则先手必胜
证明:根据“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。如n=83 = 55+21+5+2,假如先手取2颗,那么后手无法取5颗或更多,而5是一个Fibonacci数,那么一定是先手取走这5颗石子中的最后一颗,同理,接下去先手取走接下来的后21颗中的最后一颗,再取走后55颗中的最后一颗,那么先手赢。
循环 (while(n%x==0){
n = n/x;
}
判断是不是1
)
换底公式 log10(n)/log10(x) 是不是整数
位运算(2,4)
leetcode 5
leetcode 28
左神 算法书
这种题一般有这条件: 1 ≤ a[i] ≤ n ( n = 数组大小 )
leetcode 41
缺失的第一个正数
leetcode 448
找到所有数组中消失的数字
leetcode442
数组中重复的数字
找重复数字: add(nums[i]);
找缺失数字:add(i+1);
leetcode 43
两数相除
普通的递减太慢了,要加倍减。结合递归
注意边界问题!
转化为负数(处理边界问题)
方法一
public int divide(int dividend, int divisor) {
int ans = -1;
int sign = 1;
if (dividend > 0) {
sign = opposite(sign);
dividend = opposite(dividend);
}
if (divisor > 0) {
sign = opposite(sign);
divisor = opposite(divisor);
}
int origin_dividend = dividend;
int origin_divisor = divisor;
if (dividend > divisor) {
return 0;
}
dividend -= divisor;
while (divisor >= dividend) {
ans = ans + ans;
divisor += divisor;
dividend -= divisor;
}
//此时我们传进的是两个负数,正常情况下,它就返回正数,但我们是在用负数累加,所以要取相反数
int a = ans + opposite(divide(origin_dividend - divisor, origin_divisor));
if(a == Integer.MIN_VALUE){
if( sign > 0){
return Integer.MAX_VALUE;
}else{
return Integer.MIN_VALUE;
}
}else{
if(sign > 0){
return opposite(a);
}else{
return a;
}
}
}
public int opposite(int x) {
return ~x + 1;
}
}
方法二
(直接从最大开始)
class Solution {
public int divide(int dividend, int divisor) {
if(dividend==0){
return 0;
}
if(dividend == Integer.MIN_VALUE && divisor == -1){
return Integer.MAX_VALUE;
}
boolean flag = (dividend^divisor)<0;
long t = Math.abs((long) dividend);
long d= Math.abs((long) divisor);
int result = 0;
for(int i =31;i>=0;i--){
if((t>>i) >= d){
result+=1<<i;
t-=d<<i;
}
}return flag?-result : result;
}
}
10转26(不是一般的26)
class Solution {
public String convertToTitle(int n) {
StringBuilder ans = new StringBuilder();
while(n!=0){
n--;
ans.append((char)(n%26+'A'));
n /=26;
}
return ans.reverse().toString();
}
}
26 转 10
class Solution {
public int titleToNumber(String s) {
int ans = 0;
int j =0;
for(int i = s.length()-1;i>=0;i--){
ans += (int)(s.charAt(i)-(int)('A')+1)*Math.pow(26,j);
j++;
}
return ans;
}
}
小数的转换
迭代地 X乘h,直到小数部分为0时停止乘h,将 每次得到的整数部分 正序排列 即为答案 (有时小数部分永远不为0)
动态规划
class Solution {
public int integerBreak(int n) {
int[] dp = new int[n+1];
for(int i =2;i<=n;i++){
for(int j =1 ;j<i;j++){
//继续细分或者不分
dp[i] = Math.max(dp[i],Math.max(dp[i-j]*j,(i-j)*j));
}
}
return dp[n];
}
}
数学规律
① 当所有拆分出的数字相等时,乘积最大。
“算术几何均值不等式” ,等号当且仅当 n_1 = n_2 = … = n_an
时成立。
② 最优拆分数字为 3。
算出来的。
取最大值时, 乘积达到最大值。
最优: 3。把数字 nn 可能拆为多个因子 3 ,余数可能为 0,1,2 三种情况。
次优: 2 。若余数为 2 ;则保留,不再拆为 1+1 。
最差: 1 。若余数为 1;则应把一份 3 + 1替换为 2 + 2,因为2×2>3×1。
if(n<=3){
return n-1;
}
int a = n/3;
int b = n%3;
if(b==0){
return (int)Math.pow(3,a);
}else if(b==2){
return ((int)Math.pow(3,a))*2;
}else{
return ((int)Math.pow(3,a-1))*4;
}
位运算+动态规划 涨姿势了
class Solution {
public int[] countBits(int num) {
int[] dp = new int[num+1];
dp[0] = 0;
for(int i = 1;i<dp.length;i++){
//这一句
dp[i] = dp[i&(i-1)]+1;
}
return dp;
}
}
leetcode96 不同的搜索二叉树
卡特兰数,及应用
「拓扑排序」是专门应用于有向图的算法;
这道题用 BFS 和 DFS 都可以完成,只需要掌握 BFS 的写法就可以了,BFS 的写法很经典;
BFS 的写法就叫「拓扑排序」,这里还用到了贪心算法的思想,贪的点是:当前让入度为 0 的那些结点入队;
「拓扑排序」的结果不唯一;
删除结点的操作,通过「入度数组」体现,这个技巧要掌握;
「拓扑排序」的一个附加效果是:能够顺带检测有向图中是否存在环,这个知识点非常重要,如果在面试的过程中遇到这个问题,要把这一点说出来。
主要两部分:
1.入度数组。
2.邻接表。
leetcode 207 课程表
检测是否是DAG(有向无环图)
leetcode 210课程表Ⅱ
和前两个不一样,没有用到拓扑排序。用的 是优先队列+贪心(先用优先队列控制一个变量)
leetcode 630 课程表Ⅲ
leetcode208 实现Trie(前缀树)
重点两个
struct TrieNode {
bool isEnd; //该结点是否是一个串的结束
TrieNode* next[26]; //字母映射表
};
leetcode211 添加与搜索单词
前缀树+通符串匹配
leetcode 212单词搜索Ⅱ
将回溯与前缀树结合起来 ,效率更高,不用每个去遍历一遍
数组中的第K个最大元素
1.使用 java自带api
2.自己实现堆(heapInsert和heapify)
3.基于快速排序的选择算法
还有数据流中的中位数 ,用到了大顶堆和小顶堆
leetcode 220 存在重复元素
有两个变化量,想办法控制住一个。类比之前单词搜索Ⅱ(用优先队列)。
借助了treeset 的ceillng()方法。
leetcode214 最短回文串(类似的题还有在末尾加字符)
kmp和马拉车算法都可以用,这题本质是求包括开头的最长回文串。
kmp 主要借助了next 数组,将两个字符串叠加,找最大的next.
马拉车算法原理:看出没出最大右边界,出了的话中间扩散,没出的话借用对称的信息。
1.中缀表达式 转化为后缀表达式,后缀表达式 容易求解。
中缀表达式转后缀表达式
中缀表达式a + b*c + (d * e + f) * g,其转换成后缀表达式则为a b c * + d e * f + g * +。
转换过程需要用到栈,具体过程如下:
1)如果遇到操作数,我们就直接将其输出。
2)如果遇到操作符,则我们将其放入到栈中,遇到左括号时我们也将其放入栈中。
3)如果遇到一个右括号,则将栈元素弹出,将弹出的操作符输出直到遇到左括号为止。注意,左括号只弹出并不输出。
4)如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,从栈中弹出元素直到遇到发现更低优先级的元素(或者栈为空)为止。弹出完这些元素后,才将遇到的操作符压入到栈中。有一点需要注意,只有在遇到" ) “的情况下我们才弹出” ( “,其他情况我们都不会弹出” ( "。
5)如果我们读到了输入的末尾,则将栈中所有元素依次弹出。
2.双栈
使用两个栈,stack0 用于存储操作数,stack1 用于存储操作符
从左往右扫描,遇到操作数入栈 stack0
遇到操作符时,如果当前优先级低于或等于栈顶操作符优先级,则从 stack0 弹出两个元素,从 stack1 弹出一个操作符,进行计算,将结果并压入stack0,继续与栈顶操作符的比较优先级。
如果遇到操作符高于栈顶操作符优先级,则直接入栈 stack1
遇到左括号,直接入栈 stack1。
遇到右括号,则从 stack0 弹出两个元素,从 stack1 弹出一个操作符进行计算,并将结果加入到 stack0 中,重复这步直到遇到左括号
出现次数超过一半的数
出现次数超过n/3的数
摩尔投票法 两个步骤 : 抵消阶段 和 投票阶段