ARTS 第51周

ARTS (第51周)

细节很重要。稍不注意就会出现错误。

Algorithm 算法

文件队列

自己写的个文件队列DEMO 用nio和nio的内存映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.Arrays;

/**
* 文件队列
* 不支持并发 并发会出现各种内存映射错误
* @author la-huan
*
*/
public class FileQueue {

public static void main(String[] args) throws Exception {
String base = "阿萨德执行称其为地方";
FileQueue queue = new FileQueue("test");
long min = queue.getQueueSize();
long size = min + 5;
for (long i = min; i < size; i++) {
byte[] b = (base + i).getBytes();
queue.write(b);
}
//queue.rewind();
size = queue.getLeftNum();
// size = queue.getQueueSize();
//size =0;
for (int i = 0; i < size; i++) {
// byte[] bytes = queue.randomGet(i);
byte[] bytes = queue.seqGet();
System.out.println(Arrays.toString(bytes));
if (bytes != null)
System.out.println(new String(bytes));
System.out.println("------");
}
}


// 每页大小 每次映射文件的大小 不能大于INT_MAX 不能小于16 并且必须是16的倍数
private static final int PAGE_SIZE = 16;// 8 * 1024 * 1024;
// LONG的大小 即每个地址的大小
private static final int LONG_SIZE = Long.BYTES;// 8
// 队列路径
private String fileDir = ProcessGlobelConfig.DEFAULT_FILE_PATH;// 默认磁盘路径
// 队列名,即文件名,暂时不考虑分文件保存。
private String queueName = fileDir + "\\default";
// 采用byte数组保存主数据
private String fileName = queueName + "_dat.bin";
// 每两个long为一组保存
// 保存每个数据对象的起始位置和结束位置
private String indexName = queueName + "_idx.bin";
// 第0个保存队列大小 第1个保存写索引指针 第2个long保存写指针 第3个long保存读指针
private String pointName = queueName + "_pt.bin";
//////////////////////
// 文件随机访问对象
//////////////////////
/**
* 读文件
*/
private RandomAccessFile readFile;
/**
* 写文件
*/
private RandomAccessFile writeFile;
/**
* 写索引文件
*/
private RandomAccessFile writeIndexFile;
/**
* 读索引文件
*/
private RandomAccessFile readIndexFile;
/**
* 指针文件
*/
private RandomAccessFile pointFile;
//////////////////////
// 通道
//////////////////////
/**
* 读文件通道
*/
private FileChannel readChannel;
/**
* 写文件通道
*/
private FileChannel writeChannel;

/**
* 写索引文件通道
*/
private FileChannel writeIndexChannel;
/**
* 读索引文件通道
*/
private FileChannel readIndexChannel;
/**
* 指针文件通道
*/
private FileChannel pointChannel;
//////////////////////
// 内存映射文件
//////////////////////
/**
* 内存映射文件 读文件
*/
private MappedByteBuffer readBuff;
/**
* 内存映射文件 写文件
*/
private MappedByteBuffer writeBuff;
/**
* 内存映射文件 写索引
*/
private MappedByteBuffer writeIndexBuff;
/**
* 内存映射文件 读索引
*/
private MappedByteBuffer readIndexBuff;
/**
* 内存映射文件 指针
*/
private MappedByteBuffer pointBuff;

/**
* 队列大小
*/
private long queueSize = 0;
/**
* 读指针
*/
private long readIdx = 0;
/**
* 写指针
*/
private long writeIdx = 0;
/**
* 索引指针
*/
private long indexIdx = 0;

/**
* 磁盘路径 队列名
*
* @param fileDir
* @param queueName
*/
public FileQueue(String fileDir, String queueName) {
this.fileDir = fileDir;
init(queueName);
}

/**
* 队列名,磁盘路径使用默认值
*
* @param fileDir
* @param queueName
*/
public FileQueue(String queueName) {
init(queueName);
}

/**
* 初始化
*
* @param queueName
*/
private void init(String queueName) {
this.queueName = queueName;
queueName = fileDir + "\\" + queueName;
// 采用byte数组保存数据
fileName = queueName + "_dat.bin";
// 每两个long为一组保存
// 保存每个数据对象的起始位置和结束位置
indexName = queueName + "_idx.bin";
// 第0个保存队列大小 第1个保存写索引指针 第2个long保存写指针 第3个long保存读指针
pointName = queueName + "_pt.bin";
try {
// 初始化文件
initFile();
// 初始化指针
initPoint();
System.out.println("Queue init success:"+queueName);
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 读指针重置 可以重新顺序读取
*
* @throws Exception
*/
public void rewind() throws Exception {
setReadIdx(0);
readBuff = readChannel.map(MapMode.READ_WRITE, 0, PAGE_SIZE);
readIndexBuff = readIndexChannel.map(MapMode.READ_WRITE, 0, PAGE_SIZE);
}

/**
* 顺序读取 自动记录读指针
*
* @return
* @throws Exception
*/
public byte[] seqGet() throws Exception {
if (readIdx == queueSize) {
return null;// 读取到末尾了
}
// 先计算索引偏移量
long offset = readIdx * 2 * LONG_SIZE;
// 计算偏移量
long s1 = offset % PAGE_SIZE;
readIndexBuff.position((int) s1);
// 起始位置和结束位置
long s = readIndexBuff.getLong();
long e = readIndexBuff.getLong();
byte[] b = seqRead(s, e);
// 判断写是否需要进行翻页
//计算新的位置
offset = offset + 2 * LONG_SIZE;
s1 = offset % PAGE_SIZE;
if (s1==0) {
// 计算页码
long page = offset / PAGE_SIZE;
readIndexBuff = writeIndexChannel.map(MapMode.READ_WRITE, page * PAGE_SIZE, PAGE_SIZE);
}
setReadIdx(readIdx + 1);
return b;
}

/**
* 顺序读
*
* @param s
* @param e
* @return
* @throws Exception
*/
private byte[] seqRead(long s, long e) throws Exception {
int size = (int) (e - s);
int readPos = 0;// 数组偏移量
byte[] r = new byte[size];
// 当前映射偏移量
long pos = s % PAGE_SIZE;
// 当前映射剩余空间
long left = PAGE_SIZE - pos;
while (readPos < r.length) {
long length = left > size ? size : left;
readBuff.position((int) pos);
readBuff.get(r, readPos, (int) length);
//
readPos += length;
size -= length;
left -= length;
// 判断是否需要跳到下一个映射
if (left == 0) {
long page = (s + readPos) / PAGE_SIZE;
readBuff = readChannel.map(MapMode.READ_WRITE, page * PAGE_SIZE, PAGE_SIZE);
pos = 0;
left = PAGE_SIZE;
}

}

return r;
}

/**
* 随机读指定位置的数据 每次都新建个独立的文件映射
*
* @param idx
* @return
* @throws Exception
*/
public byte[] randomGet(long idx) throws Exception {
if (idx >= queueSize) {
throw new RuntimeException("随机访问错误:队列大小:" + queueSize + ",索引位置:" + idx);
}
// 先计算索引偏移量
long offset = idx * 2 * LONG_SIZE;
// 计算偏移量
long s1 = offset % PAGE_SIZE;
// 计算页码
long page = offset / PAGE_SIZE;
MappedByteBuffer indexBuff = readIndexChannel.map(MapMode.READ_WRITE, page * PAGE_SIZE, PAGE_SIZE);
indexBuff.position((int) s1);
// 起始位置和结束位置
long s = indexBuff.getLong();
long e = indexBuff.getLong();

return randomRead(s, e);
}

/**
* 随机读取
*
* @param s
* @param e
* @return
* @throws Exception
*/
private byte[] randomRead(long s, long e) throws Exception {
int size = (int) (e - s);
int readPos = 0;// 数组偏移量
byte[] r = new byte[size];
// long start =s;
long page = s / PAGE_SIZE;
MappedByteBuffer randomReadBuff = readChannel.map(MapMode.READ_WRITE, page * PAGE_SIZE, PAGE_SIZE);
// 当前映射偏移量
long pos = s % PAGE_SIZE;
// 当前映射剩余空间
long left = PAGE_SIZE - pos;
while (readPos < r.length) {
long length = left > size ? size : left;
randomReadBuff.position((int) pos);
randomReadBuff.get(r, readPos, (int) length);
//
readPos += length;
size -= length;
left -= length;
// 判断是否需要跳到下一个映射位置
if (left == 0 && r.length > readPos) {
randomReadBuff = readChannel.map(MapMode.READ_WRITE, ++page * PAGE_SIZE, PAGE_SIZE);
pos = 0;
left = PAGE_SIZE;
}

}
return r;
}

/**
* 写入
* 会返回队列大小
* @param b
* @throws Exception
*/
public long write(byte[] b) throws Exception {
// 起始位置和大小
long start = writeIdx;
long size = b.length;
// 文件偏移量
long s1 = start % PAGE_SIZE;
// 当前页剩余多少
long left = PAGE_SIZE - s1;
// byte数组偏移量
long pos = 0;
// 写入数据文件
// 需要跨页写入的时候 循环调用
while (size > 0) {
// 获取当前页写入的起始位置
// 计算当前页剩余多少
// 最大能放的长度
long length = left > size ? size : left;
writeBuff.position((int) s1);
writeBuff.put(b, (int) pos, (int) length);
// 指针重设
pos += length;
start += length;
size -= length;
// 本次完成 判断是否进行跨页
s1 = start % PAGE_SIZE;// 偏移量
if (s1 == 0) {
// 本页已满的时候, 这里必须保证传入的byte数组 size大于0 否则这里的判断是错误的
// 计算旧页码
long page = start / PAGE_SIZE;
// 调整页码
writeBuff = writeChannel.map(MapMode.READ_WRITE, page * PAGE_SIZE, PAGE_SIZE);
// s1归零
// s1 = 0;
}
left = PAGE_SIZE - s1;
}
// 写入索引文件
writeIndex(writeIdx, writeIdx + b.length);
// 更新指针文件
setWriteIdx(writeIdx + b.length);
// 队列大小增加
setQueueSize(queueSize + 1);
return queueSize;
}

/**
* 写入索引 起始位置和结束位置
*
* @param s
* @param e
* @throws Exception
*/
private void writeIndex(long s, long e) throws Exception {
long s1 = indexIdx % PAGE_SIZE;
writeIndexBuff.position((int) s1);
writeIndexBuff.putLong(s);
writeIndexBuff.putLong(e);
// 设置索引指针
setIndexIdx(indexIdx + LONG_SIZE * 2);
s1 = indexIdx % PAGE_SIZE;
if (s1 == 0) {
// 需要切换新的页了
long page = indexIdx / PAGE_SIZE;
// 调整页码
writeIndexBuff = writeIndexChannel.map(MapMode.READ_WRITE, page * PAGE_SIZE, PAGE_SIZE);
}

}

/**
* 初始化文件
*
* @throws Exception
*/
private void initFile() throws Exception {
// 文件 这里会自动创建文件
readFile = new RandomAccessFile(fileName, "rw");
writeFile = new RandomAccessFile(fileName, "rw");
writeIndexFile = new RandomAccessFile(indexName, "rw");
readIndexFile = new RandomAccessFile(indexName, "rw");
pointFile = new RandomAccessFile(pointName, "rw");
// 通道
readChannel = readFile.getChannel();
writeChannel = writeFile.getChannel();
writeIndexChannel = writeIndexFile.getChannel();
readIndexChannel = readIndexFile.getChannel();
pointChannel = pointFile.getChannel();

}

/**
* 初始化指针
*
* @throws Exception
*/
private void initPoint() throws Exception {
// 指针文件映射
pointBuff = pointChannel.map(FileChannel.MapMode.READ_WRITE, 0, Long.BYTES * 4);
// 第0个保存队列大小 第1个保存写索引指针 第2个long保存写指针 第3个long保存读指针
pointBuff.rewind();
queueSize = pointBuff.getLong();
indexIdx = pointBuff.getLong();
writeIdx = pointBuff.getLong();
readIdx = pointBuff.getLong();
// 内存映射文件 这里会自动扩大文件
// FileChannel.MapMode.READ_WRITE
// 初始化读写指针
initWritePoint();
initReadPoint();
}

/**
* 初始化写指针
*
* @throws Exception
*/
private void initWritePoint() throws Exception {
// 顺序写文件映射
long pageWrite = writeIdx / PAGE_SIZE;// 页码
writeBuff = writeChannel.map(MapMode.READ_WRITE, pageWrite * PAGE_SIZE, PAGE_SIZE);

// 写索引文件映射
long pageWriteIdx = indexIdx / PAGE_SIZE;// 页码
writeIndexBuff = writeIndexChannel.map(MapMode.READ_WRITE, pageWriteIdx * PAGE_SIZE, PAGE_SIZE);

}

/**
* 初始化读指针
*
* @throws Exception
*/
private void initReadPoint() throws Exception {
if (readIdx == 0) {
// 未读取的时候 直接从头开始
readIndexBuff = readIndexChannel.map(MapMode.READ_WRITE, 0, PAGE_SIZE);
readBuff = readChannel.map(MapMode.READ_WRITE, 0, PAGE_SIZE);
} else {

// 获取读索引文件 前一个映射
long readOffset = (readIdx - 1) * 2 * LONG_SIZE;
long pageReadIdx = readOffset / PAGE_SIZE;// 页码
readIndexBuff = readIndexChannel.map(MapMode.READ_WRITE, pageReadIdx * PAGE_SIZE, PAGE_SIZE);

// 顺序读文件映射
// 根据前一个读索引获取读指针应该在的位置
long s1 = readOffset % PAGE_SIZE;
readIndexBuff.position((int) s1);
// 起始位置
readIndexBuff.getLong();
long e = readIndexBuff.getLong();
long pageRead = e / PAGE_SIZE;// 页码
readBuff = readChannel.map(MapMode.READ_WRITE, pageRead * PAGE_SIZE, PAGE_SIZE);
if (2 * LONG_SIZE + s1 == PAGE_SIZE) {
// 因为读索引读取的是前一个的 如果当前索引页读取到底了 就需要切换到下一页
readIndexBuff = readIndexChannel.map(MapMode.READ_WRITE, ++pageReadIdx * PAGE_SIZE, PAGE_SIZE);
}
}
}

/**
* 清空队列<br>
* 这里只是重设指针,没有真正的删除文件
*/
public void clear() {
setQueueSize(0);
setIndexIdx(0);
setReadIdx(0);
setWriteIdx(0);
}

/**
* 设置队列大小
*
* @param v
*/
private void setQueueSize(long v) {
// 第0个保存队列大小 第1个保存写索引指针 第2个long保存写指针 第3个long保存读指针
pointBuff.position(0);
pointBuff.putLong(v);
this.queueSize = v;
}

/**
* 设置写索引指针索引位置
*
* @param v
*/
private void setIndexIdx(long v) {
// 第0个保存队列大小 第1个保存写索引指针 第2个long保存写指针 第3个long保存读指针
pointBuff.position(LONG_SIZE);
pointBuff.putLong(v);
this.indexIdx = v;

}

/**
* 设置顺序写指针索引位置
*
* @param v
*/
private void setWriteIdx(long v) {
// 第0个保存队列大小 第1个保存写索引指针 第2个long保存写指针 第3个long保存读指针
pointBuff.position(LONG_SIZE * 2);
pointBuff.putLong(v);
this.writeIdx = v;
}

/**
* 设置顺序读指针索引位置
*
* @param v
*/
private void setReadIdx(long v) {
// 第0个保存队列大小 第1个保存写索引指针 第2个long保存写指针 第3个long保存读指针
pointBuff.position(LONG_SIZE * 3);
pointBuff.putLong(v);
this.readIdx = v;
}

/**
* 获取未消费的数据数量
*
* @return
*/
public long getLeftNum() {
return queueSize - readIdx;
}

/**
* 队列总大小 包含已消费的
*
* @return
*/
public long getQueueSize() {
return queueSize;
}

/**
* 获取顺序读指针的位置
*
* @return
*/
public long getReadIdx() {
return readIdx;
}

/**
* 获取顺序写指针的位置
*
* @return
*/
public long getWriteIdx() {
return writeIdx;
}

/**
* 获取索引指针的位置
*
* @return
*/
public long getIndexIdx() {
return indexIdx;
}

}

加锁可并发版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152


import java.util.concurrent.locks.ReentrantLock;

/**
* 同步文件队列
* 在com.lahuan.common.queue.FileQueue的基础上加上可重入锁
* @author la-huan
*
*/
public class ConcurrentFileQueue {

/**
* 调用的还是文件队列
*/
private FileQueue queue = null;
/**
* 写锁 读写是分开的 可以分别设置锁
*/
private ReentrantLock writeLock = new ReentrantLock();
/**
* 读锁 读写是分开的 可以分别设置锁
*/
private ReentrantLock readLock = new ReentrantLock();

/**
* 磁盘路径 队列名
*
* @param fileDir
* @param queueName
*/
public ConcurrentFileQueue(String fileDir, String queueName) {
queue = new FileQueue(fileDir, queueName);
}

/**
* 队列名,磁盘路径使用默认值
*
* @param fileDir
* @param queueName
*/
public ConcurrentFileQueue(String queueName) {
queue = new FileQueue(queueName);
}

/**
* 读指针重置 可以重新顺序读取
*
* @throws Exception
*/
public void rewind() throws Exception {
readLock.lock();
queue.rewind();
readLock.unlock();
}

/**
* 顺序读取 自动记录读指针
*
* @return
* @throws Exception
*/
public byte[] seqGet() throws Exception {
readLock.lock();
byte[] bs = queue.seqGet();
readLock.unlock();
return bs;
}

/**
* 随机读指定位置的数据 每次都新建文件映射
* 随机读不加锁
* @param idx
* @return
* @throws Exception
*/
public byte[] randomGet(long idx) throws Exception {
return queue.randomGet(idx);
}


/**
* 写入
*
* @param b
* @return
* @throws Exception
*/
public long write(byte[] b) throws Exception {
writeLock.lock();
long size =queue.write(b);
writeLock.unlock();
return size;
}
/**
* 清空队列<br>
* 这里只是重设指针,没有真正的删除文件
*/
public void clear() {
writeLock.lock();
readLock.lock();
queue.clear();
writeLock.unlock();
readLock.unlock();
}

/**
* 获取未消费的数据数量
*
* @return
*/
public long getLeftNum() {
return queue.getLeftNum();
}

/**
* 队列总大小 包含已消费的
*
* @return
*/
public long getQueueSize() {
return queue.getQueueSize();
}

/**
* 获取顺序读指针的位置
*
* @return
*/
public long getReadIdx() {
return queue.getReadIdx();
}

/**
* 获取顺序写指针的位置
*
* @return
*/
public long getWriteIdx() {
return queue.getWriteIdx();
}

/**
* 获取索引指针的位置
*
* @return
*/
public long getIndexIdx() {
return queue.getIndexIdx();
}

}

第一个只出现一次的字符

1
2
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写)
https://www.nowcoder.com/practice/1c82e8cf713b4bbeb2a5b31cf5b0417c?tpId=13&tqId=11187&tPage=2&rp=2&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 本质来说 是利用hash的思想
public int FirstNotRepeatingChar(String str) {
if (str == null || str.length() == 0) {
return -1;
}
// System.out.println('z'-'A');//57
int[] c = new int[58];// 算上本身 最小应该为58 也试过57 会越界
for (int i = 0; i < str.length(); i++) {
c[str.charAt(i) - 'A'] += 1;
}
for (int i = 0; i < str.length(); i++) {
if (c[str.charAt(i) - 'A'] == 1) {
return i;
}
}
return -1;
}

数组中的逆序对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
输入描述:
题目保证输入的数组中没有的相同的数字

数据范围:

对于%50的数据,size<=10^4

对于%75的数据,size<=10^5

对于%100的数据,size<=2*10^5

示例1
输入
1,2,3,4,5,6,7,0
输出
7

https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188&tPage=2&rp=2&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

解法 归并排序

每排序一次 逆序度就增加对应的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

public int InversePairs(int[] array) {
if (array == null || array.length == 0) {
return 0;
}
InversePairs_Help(array, array.length);
return InversePairs_num;
}

int InversePairs_num = 0;

public int InversePairs_Help(int[] a, int n) {
InversePairs_num = 0;
mergeSortCounting(a, 0, n - 1);
return InversePairs_num;
}

private void mergeSortCounting(int[] a, int p, int r) {
if (p >= r)
return;
int q = (p + r) / 2;
mergeSortCounting(a, p, q);
mergeSortCounting(a, q + 1, r);
merge(a, p, q, r);
}

private void merge(int[] a, int p, int q, int r) {
int i = p, j = q + 1, k = 0;
int[] tmp = new int[r - p + 1];

while (i <= q && j <= r) {
if (a[i] <= a[j]) {
tmp[k++] = a[i++];
} else {
// num ++;
InversePairs_num += (q - i + 1); // 统计 p-q 之间,比 a[j] 大的元素个数
InversePairs_num%=1000000007;//题目特别要求
tmp[k++] = a[j++];
}
}
while (i <= q) { // 处理剩下的
tmp[k++] = a[i++];
}
while (j <= r) { // 处理剩下的
tmp[k++] = a[j++];
}
for (i = 0; i <= r - p; ++i) { // 从 tmp 拷贝回 a
a[p + i] = tmp[i];
}
}

Review 英文文章

https://spring.io/guides/gs/gradle/

spring也支持gradle构建

Tip 技巧

SOA粗暴理解:把系统按照实际业务,拆分成刚刚好大小的、合适的、独立部署的模块,每个模块之间相互独立。

比如现我有一个数据库,一个JavaWeb(或者PHP等)的网站客户端,一个安卓app客户端,一个IOS客户端。

现在我要从这个数据库中获取注册用户列表,如果不用SOA的设计思想,那么就会这样:JavaWeb里面写一个查询方法从数据库里面查数据然后在网页显示,安卓app里面写一个查询方法查询后在app上显示,IOS同样如此。这里就会出现查询方法重叠了,这样的坏处很明显了,三个地方都有相同的业务代码,要改三个地方都要改,而且要改的一模一样。当然问题不止这一个。

于是乎出现了这样的设计思想,比如用Java(或者是其他语言皆可)单独创建一个工程部署在一台服务器上,并且写一个方法(或称函数)执行上述查询操作,然后使其他人可以通过某种途径(可以是http链接,或者是基于socket的RPC调用)访问这个方法得到返回数据,返回的数据类型是通用的json或者xml数据,就是说把这个操作封装到一个工程中去,然后暴露访问的方式,形成“服务”。比如这里就是注册用户服务,而关于注册用户的所有相关增删改查操作这个服务都会提供方法。

来自 https://www.zhihu.com/question/42061683?sort=created

用户 光太狼

Share 分享

https://zhuanlan.zhihu.com/p/50295490 把13亿中国人拉到一个微信群会发生什么?腾讯给出回答

http://www.360doc.com/content/16/1229/07/36536207_618569902.shtml 博弈论的几个经典例子

https://www.zhihu.com/question/42061683?sort=created 如何通俗易懂地解释什么是SOA?