写作进阶
主要面向二维问题,不超时而已
一 模型
- 有关系就会有复杂度,没有关联性就没有复杂度
- 问题模型复杂度数学模型可能2的N次方升维到M的N次方
二 组合,匹配
- 分类讨论,只看最佳答案的方面的东西,过程数量为平方,最终只有N个极值
- 极端情况,极端考虑,一个维度一个极值,N个维度N个极值只考虑最值
三 实现
- 想到背到并不是实现,只能遵守更高级的规则计算,才可能恰到好处实现
- 分类讨论
A 条件顺序规律,从后往前写
B 答案变量的逻辑是最重要的
C 过程层次清晰
四 测试
A 找规律,必须要验证,然后特别注意层次,反复验证各个层次的规律和特殊性,尤其是0
五 习惯
A 速度,步骤,想到就多写写,写着写着不就没什么问题了
问题 8
审题
- 题目名:山。“先低调不减,后单调不增“
- 20(底数看成2了),看到不够,一般也是不够存储
- 对于每一名学生
- 一个排列。也就是说区间内,只要大小值和区间对应即可连续。
信息存储
- 取余压缩存储
- 取模压缩存储
变量关系
- 维护成绩的前缀和:n - cnt[i]不是多于非当前分数人数
- 无关紧要的忽略
循环
1.循环:反复执行一段相同或相似的代码
2.循环三要素:
1)循环变量的初始化
2)循环的条件(以循环变量为基础)
3)循环变量的改变(向着循环的结束变)
循环变量:在整个循环过程中所反复改变的那个数
3.循环结构:
1)while:先判断后执行,有可能一次都不执行
2)do...while:先执行后判断,至少执行一次
要素1与要素3相同时,首选do...while
3)for:应用率高,固定次数循环
4.break:跳出循环
continue:跳过循环体中剩余语句而进入下一次循环
写作基础 11
给写懵了,不知道写了个什么,写到哪了,该写啥了
50行里面可能只有10行有效,其他全部都是标点符号,变量,基本语法关键字而已
那么一个程序可能只有10行,又是什么样的呢?
似乎一般都是一个主循环,然后分类讨论各处理1-2行就可以了
可能也只有2种情况,YES or NO 这样二进制可能才更加简单便捷高效
- 思想50% 模板50% 语法50% 计算技巧50%
- 条件顺序规律,从后往前写
前面的条件往往是后面结果的判断的前提条件
- 变量的变化,各层次类别具有一致性,需要逻辑清晰
尤其是++、--,先后,位置,含义,都得很清晰;
尤其是层次,越基础层可以越忽略,比如说初始化存储和处理后数据的存储结构;
- 输入
A 一长串:数组、队列
B 多个子串,且成双:Pair (键值对)
数组临存,需要循环处理后再顺便必要动态存储在PriorityQueue
C 多个字串,且为边的关系:Pair (键值对)
分别采用泛型链表存储两次,依次遍历,其中ArrayList<ArrayList<Pair>>,第一层为位数,第二层为泛型结构
- 最优解
A 动态规划全部都得分类讨论递推,可能会很复杂,比如说图,不仅仅只是数量,还需要边的关系。
B 贪心比较轻量,只需要单个点最佳,不一定有递推必要,比如说,人数与最大值,可能不需要人直接的关系。
- 答案变量是最重要的
几乎所有的变量、语句、操作都是围绕着进行,多想想规律、变化、相关公式、方法
逻辑A PriorityQueue<Pair>格外注意Pair泛型接口的优先级及含义,比如说存储最佳解,存储方式可能是循环遍历自动排序求解
- 嵌套循环
综上所述,程序不过就是循环的变式。层次就是各种维度的映射关系而已。
A 图广度循环
第一类问题:从节点A出发,有前往节点B的路径吗?
第二类问题:从节点A出发,前往节点B的哪条路径最短?
通过Dijkstra算法遍历时
先遍历第一层节点,显然其中的初始值只会遍历到一定程度
然后进入下一层节点,其中的条件判断标志是是否存在这样的一条连续小于路径
如果不是连续小于,说明还有可能更小,那么就进入该层次下一节点
其中的层次规律只是根据顺序,不断遍历更新数值,最终实现全局遍历判断找出结果
A 图深度循环
通过不断递归调用自己,往前一步,直到各个维度的各个层次最深处才返回,从而依次遍历完各个维度的各个层次,其中次序只是顺序
a[step]=i;//将i号扑克牌放到第step个盒子中
book[i]=1;//此时i号扑克牌已经被使用
dfs(step+1);
/*注意这里是自己调用自己,表示此时走到了第step+1个盒子面前*/
book[i]=0;
/*book[i]=0表示dfs调用结束了,换句话说就是扑克牌已经全部放完了
需要按照顺序将扑克牌收回,重新放,也就是前面所说的
C 循环起点:注意初始值哦。一般都是0。尤其是最高层初始值。
方案 3
问题:判断一个数是否是类斐波那契数(重点)
一、描述1:对于一个有n位的十进制数N=d1d2d3…dn
- 将给定数字转为分解成单个数字:toList 方法
二、描述2:如果这个数N会出现在对应的类斐波那契数列S中,那么N就是一个类斐波那契循环数
(二)判断是否为循环数:isFab 方法
三、寻找最大循环数
方案:跟踪每个副节点已经同步到主节点队列的元素数量,并找出所有副节点中同步到的最少元素数量,这个数量即为所有副节点都已经同步的元素数量。
(1)初始化mainQueueSize,followerSync
(2)处理操作add element:sync follower_id:query:
输出 minSync
难看出,在不考虑数量影响的前提下,两人寝的利用方式更加丰富,且四人寝的利用方式均可由两人寝替代。因此,对于「2个两人寝」和「1个四人寝」的两种入座方式,我们应尽可能优先使用「1个四人寝」。
其余情况同理。我们只需按照以上的思路进行分析,即可确认贪心方案,完成求解。
【模板】 4
只用计算任意两个边的最短路径即可。如果最短路径小于等于传送门最多使用次数,那么这个星球就可以到达。
用一个 dist 数组保存源点到其余各个节点的距离,dist[i] 表示源点到节点 i 的距离
public static long[] dijkstra(List<List<Pair>> adj, int start, int n) {
long[] dist = new long[n];
Arrays.fill(dist, Long.MAX_VALUE);
PriorityQueue<Pair> pq = new
PriorityQueue<>(Comparator.comparingLong(p -> p.weight));
dist[start] = 0;
pq.add(new Pair(start, 0));
while (!pq.isEmpty()) {
Pair current = pq.poll();
int u = current.node;
long d = current.weight;
if (d > dist[u]) continue;
for (Pair edge : adj.get(u)) {
int v = edge.node;
long weight = edge.weight;
if (dist[u] + weight < dist[v]) {
dist[v] = dist[u] + weight;
pq.add(new Pair(v, dist[v]));
}
}
}
void dfs(int step){ //此时在第step盒子面前,需要往里面放第i张扑克牌
int i;
if(step==n+1){ //这里说明前面的n个盒子已经放好了,这是dfs结束的标志
for(i=1;i<=n;i++)
printf("%d",a[i]);
printf("\n");
return ;
/*
注意这个 return 它的作用不是返回主函数,而是返回上一级的dfs函数
例:如果此时是 dfs(5),遇到这个 return 就会回到上一级的 dfs函数
也就是dfs(4),但此时dfs(4)的大部分语句已经执行了,只需要接着执行 book[i]=0
然后继续进入for循环进入下一次的 dfs函数,直到结束。
*/
}
for(int i=1;i<=n;i++){
if(book[i]==0){ //说明i号扑克牌还在手里,需要放入step号盒子
a[step]=i;//将i号扑克牌放到第step个盒子中
book[i]=1;//此时i号扑克牌已经被使用
dfs(step+1);
/*注意这里是自己调用自己,表示此时走到了第step+1个盒子面前*/
book[i]=0;
/*book[i]=0表示dfs调用结束了,换句话说就是扑克牌已经全部放完了
需要按照顺序将扑克牌收回,重新放,也就是前面所说的
*/
}
}
return;//这里表示这一级别的dfs函数已经结束了,返回上一级 dfs函数
}
int main(){
scanf("%d",&n);
dfs(1); //dfs函数的开始
return 0;
}
for (int[] rect : a) {
int l = rect[0], w = rect[1], c = rect[2];
for (int i = 0; i < 3; i++) {
if (i != c) {
ans += tr[i].rangeSum(w, M);
ans %= mod;
}
}
tr[c].add(w, 1);
}
int rangeSum(int l, int r) {
return sum(r) - sum(l);
}
int sum(int x) {
int ans = 0;
for (; x > 0; x -= x & (-x)) {
ans += tree[x];
}
return ans;
}
void add(int x, int val) {
for (; x <= n; x += x & (-x)) {
tree[x] += val;
}
}
long mid = l + r >> 1 ;//除2缩小范围
if(check(mid) >= k) r = mid ;//是否范围内,在就进入下一步缩小范围
else l = mid + 1 ;
//不在范围内扩大范围至下一层在范围内的1.5倍
//如果一直在就一直缩小
//如果一次不在,就再扩大1次,再判断,再查询直到二分查找到合适范围
语法 14
import java.util.*;
import java.io.*;
import java.util.Scanner;
public class JavaSanner {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.print("请输入:");
int d=sc.nextInt();
System.out.print("输入的数据为:"+d);
while (scanner.hasNext()) {
int a = scanner.nextInt();
int b = scanner.nextInt();
int c = a + b;
System.out.println(c);}
sc.close();
}
}
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
long n = Long.parseLong(br.readLine());
StringTokenizer st = new StringTokenizer(br.readLine());
long:202420242024
double mi = Double.MAX_VALUE;
static声明的方法为静态方法,不需要对象,就可以调用(类名.方法名)
int XXX[];
int[] XXX = new int[X];
int[] XXX = new int[]{X,X,X};
Arrays.fill(index, -1);
Collections.reverse(list);
a.sort((o1, o2) -> {
if (o1[0] != o2[0]) {
return Integer.compare(o1[0], o2[0]);
} else {
return Integer.compare(o1[1], o2[1]);
}
});
static boolean isFab(int a){
ArrayList<Integer> list=new ArrayList<>(toList(a));
int len=list.size();2//获得a的位数
while(true){
int sum=0;
//注意下标不要越界!!!
for(int i=list.size()-1;i>list.size()-1-len;i--){//递推类斐波那契数
sum+=list.get(i);2
}
if(sum==a)return true;
if(sum>a)return false;
list.add(sum);
}
static List<Integer> toList(int a){
List<Integer> list=new ArrayList<>();2
while(a>0){
int t=a%10;//得到个位数
list.add(t);2
a/=10;
}
Collections.reverse(list);//逆置
return list;
}
Math.pow(1 - p, k);
String[] tokens = line.split(" ");
"query".equals(s)
poll()方法,它将检索队列的head元素,然后删除队列的head元素。
Comparator.comparing 接受一个函数,该函数从给定类型中提取一个可比较的排序键,并返回一个通过该排序键进行比较的比较器
- x += x & (-x)
公式 3
while (cur != 0)
{ sum += cur % base;
cur /= base; }
除,取余,可以很好遍历每位数,从而循环累加计算,条件判断
需要计算出两个矩形的面积,然后找出重叠部分的面积并从总面积中减去
long overlapX = Math.max(0, Math.min(x2, x4) - Math.max(x1, x3));
long overlapY = Math.max(0, Math.min(y2, y4) - Math.max(y1, y3));
dp[i][0] = Math.min(dp[i - 1][0] + x[i] - x[i - 1], dp[i - 1][1] + b[i - 1] / 1.3);
dp[i][1] = Math.min(dp[i][0] + a[i] / 0.7,
dp[i - 1][1] + ((b[i - 1] > a[i]) ? (b[i - 1] - a[i]) / 1.3 : (a[i] - b[i - 1]) / 0.7));
技巧 3
因此只需要分解一步,转换成前40个数的后9位阶乘之和即可,完美减少计算量(2)
题意就是给出一个整数数组a,让我们将其分为两组,要求每组的和均为偶数。(求和符合)
问题转化为求从n 个数中选出若干个数使其和为偶数的方案数,可以使用动态规划解决
我们按数组的总和来分类讨论
形式:X或Y倍数的正整数:第奇数项是20的倍数,第偶数项时24的倍数
合计 38
能力
- 代码掌握能力(代码实现上较难)
- 抽象能力(思维方面比较简单)
特殊情况
算法往往就是特殊情况的把握的方法而已。
说清楚这个方法逻辑,了解透彻特殊情况,稍加代码逻辑整合就可以了
一般都是优化时间存储,尤其是不会超时,甚至进一步优化物理及需求吧
方式主要是预测计算,或者纯预测
作者 | 毕玄 来源|阿里巴巴云原生公众号
- 如果环境不具备,就给自己一个挑战的命题,例如要学高并发的通信,可以尝试自己写一个和其他的做对比,做性能等的 pk,这个通常提升还是会很大的,要学 GC,可以尝试给自己几个题目,来控制 GC 的行为等,如果环境具备的话,确实会更加有利。
- 多和优秀的程序员一起,我自己从多隆、撒迦身上学习到了很多很多,从很多优秀的开源代码,像 Netty,OpenJDK 里面也学习到了很多很多,所以多参与一些优秀的开源项目也是一个很好的提升方法,看优秀的书(例如并发里的那本 Java 并发编程实战,JVM 里的 Oracle JRockit: The Definitive Guide,深入理解 Java 虚拟机等),也同样是一种向优秀程序员学习的好方法。
- 多多尝试解决问题/故障,这绝对是提升代码综合能力非常好的一个方法,自己工作里机会少的话,网上有大把,像 stackoverflow 之类的,都是很好的练习场。
对于程序员而言,我始终认为代码是展现能力的关键,一个优秀程序员写的代码,和一个普通程序员写的代码是很容易看出差别的,代码作为程序员的硬实力和名片的展示,怎么提升写代码的能力始终是一个关键的话题
查清原因后,决定基于当时的 Mina 重写整个 HSF 的通信,重写的这两个月时间对我自己写代码的能力有很大的提升,无论是对网络 IO 方面处理的深入学习,还是在高并发系统上的深入学习,现在想想学习的方式也就是翻各类网络 IO 的科普资料,然后是读 Mina 的源码、Java 网络 IO 的源码,并发这块的学习主要还是靠那本经典的《Java 并发编程实战》,以及读 Java J.U.C 里的代码,这段时间的学习相比以往翻 Think in Java 之类的最大区别是,学习后付诸实践,随着 HSF 这个新的重写的版本的上线,基本算是逐渐真正掌握了这些部分的代码能力。
一开始看到各种故障的时候,压根就不知道怎么下手,处理故障会需要的通常不仅仅是写代码的能力,还需要对一个系统全貌要有一定的掌握,例如前几年一篇特别火的文章,当点击搜索背后发生了什么这样的文章,其实就是要对一个系统的处理流程特别的熟悉,这在处理故障的时候是非常重要的,在有了故障大概在哪个环节后,很重要的就是对这个环节代码运行机制的细节掌控了,这个时候通常来说各种工具非常重要,可以有效的帮助你知道具体发生了什么,例如像系统层面的 top -H 之类的, java 层面的 btrace 等等,都可以让你根据运行情况去定位问题的点。
这段时间我觉得我的提升就是靠大量的练手,故障确实有点多,一开始就靠看人怎么处理,主要是从多隆这里学,然后是尝试自己解决一些故障,解决的越来越多后慢慢熟练度就上去了,除了解决故障能力的提升外,因为看了很多由于代码层面造成的故障,对自己在写代码时如何更好的保证鲁棒性,来避免故障,是非常有帮助的,例如,我看过很多滥用线程池造成创建了大量线程,最终导致线程创建不出来的 case,就会明白自己在用线程池的场景里一定要非常清楚的控制最大的数量,包括堆积的策略等,又例如我看过 N 多的因为自增长容量的数据结构导致的 OOM 的 case,就会明白在写代码的时候不能认为一定不会发生数据结构增长到超级大,所以不做任何保护的 case,这个时间我明白到的就是写一段能运转,实现需求的代码不难,但要写一段在各种情况下都能长期稳定运行的代码是真心不容易,这我觉得是一个职业的写商业系统的程序员和只是写程序玩玩的最大差别。
本来以为之前在写 HSF 的那几年应该算是对通信框架这块的代码相关的能力掌握的不错了,在和多隆一起重写的这段过程中,才发现差距还是很大的,多隆教会了很多细节的问题,基于 NIO 的通信框架的核心是用非常少的 IO 线程来处理 IO 事件(太多也没用,因为有些部分就只能串行),所以怎么高效的使用好这几个 IO 线程是非常关键的,要尽量减少这几个 IO 线程处理一些不相关的动作,另外一点就是尽量减少 IO 线程和业务处理线程的切换,例如后来常见的批量把一个流里的多个请求一次性丢给业务处理线程。
这段经历对自己更加深入的掌握在代码逻辑整体的细节层面是非常有帮助的,这对于写要求很高的系统是非常重要的,毕竟对于一个超大规模的系统而言,1% 的提升还是可观的。
很幸运,碰到了一个同样爱好又比我强很多的同学,就是撒迦,圈内通常叫 R 大,我和撒迦好几个周末约着在公司一起看 JVM 代码,有撒迦的指点,我终于是入门了,知道大概怎么去看了,而且两个人一起看代码,互相分享和探讨,效率是非常高的。
有了这段经历,再加上继续处理着一些故障,基本上逐渐对 JVM 的代码实现有了更多的理解,在后来做故障分享、问题解决什么的时候终于能更好的做到知其然知所以然,同样,这对处理故障的能力,写代码的能力也是非常有帮助的,例如会更加明白以前认为的所谓的面向 GC 友好的代码是几个意思,也会有了更深的感受是其实 Java 的代码呢,通常不会写的太烂,因为 JVM 在运行期会做很多的尽可能的优化,拉到一个平均线,但要写的很好,难度是非常大的,因为需要懂 JVM,懂 JVM 下面的 OS。
我也是在2017年和毕玄开始接触,第一次我们在讨论CPU利用率的问题。国内企业数据中心CPU利用率只有10%多一些,而当时Google的已经达到40%左右,所以毕玄就开始研究了离线、离在线混部技术。果然在2017年双11起就开始正式上线了这套技术方案,结果给公司节省了几十亿服务器采购费用
核心是不够透明。(这才P10,那P14得多少钱。不过这种纯研究等终究占不了太高比例)