-
Notifications
You must be signed in to change notification settings - Fork 1
/
atom.xml
479 lines (282 loc) · 425 KB
/
atom.xml
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>健程之道</title>
<link href="/atom.xml" rel="self"/>
<link href="https://www.death00.top/"/>
<updated>2021-02-10T04:17:07.627Z</updated>
<id>https://www.death00.top/</id>
<author>
<name>健健</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>MySQL 的字符集与排序规则</title>
<link href="https://www.death00.top/2021/02/10/MySQL%20%E7%9A%84%E5%AD%97%E7%AC%A6%E9%9B%86%E4%B8%8E%E6%8E%92%E5%BA%8F%E8%A7%84%E5%88%99/"/>
<id>https://www.death00.top/2021/02/10/MySQL 的字符集与排序规则/</id>
<published>2021-02-10T01:00:00.000Z</published>
<updated>2021-02-10T04:17:07.627Z</updated>
<content type="html"><![CDATA[<p>今天突然被同事问到,MySql 里的 uft8 与 utf8mb4 究竟有什么区别,当时我也是一脸问号,因此特地去了解了一下。<br><a id="more"></a></p><h2 id="字符集"><a href="#字符集" class="headerlink" title="字符集"></a>字符集</h2><p>uft8 与 utf8mb4 其实指的是 MySQL 中的<code>字符集</code>,那到底什么是<code>字符集</code>呢?</p><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><p>很多人常常会把<code>字符</code>、<code>字符集</code>、<code>字符编码</code>的概念混为一谈,今天我们仔细来看看。</p><p>何为字符?</p><blockquote><p>字符(Charcter)是文字与符号的总称,包括文字、图形符号、数学符号等。26个英文字母属于字符,每个汉字也属于一个字符。</p></blockquote><p>那么什么叫字符集?</p><blockquote><p>字符集是一组抽象的字符(Charcter)组合的集合。举一个例子,所有的汉字就算一个“字符集合”, 所有的英语字母也算一个“字符集合”。 注意,我这里说它们是字符集合,而且还有双引号。是因为字符集并不简单的是字符的集合, 准确概述来说,字符集是一套符号和编码的规则。 字符集需要以某种字符编码方式来表示、存储字符。我们知道,计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有0和1两种状态。而如果用不同的0和1组合表示不同的字符就是编码。</p></blockquote><p>那什么叫字符编码呢?</p><blockquote><p>字符最终是以二进制形式存储在磁盘的,这也是为什么要有字符编码的原因,因为计算机最终都要以二进制形式存储,那么编码规则就是用什么样的二进制来代表这个字符。例如,我们所熟知的ASCII码表中,01000011这个二进制对应的十进制是67,它代表的就是英语字母C。准确概述来说,字符编码方式是用一个或多个字节的二进制形式表示字符集中的一个字符。每种字符集都有自己特有的编码方式,因此同一个字符,在不同字符集的编码方式下,可能会产生不同的二进制形式。</p></blockquote><p>既然我们经知道了 utf8 与 utf8mb4 都是一种字符集,那两种到底有什么区别呢?</p><h3 id="utf8"><a href="#utf8" class="headerlink" title="utf8"></a>utf8</h3><p>MySQL 在创立时使用的字符集就是 utf8。首先它能存储下大部分的中文汉字,对于我们正常使用肯定是绰绰有余的。</p><p>它由<code>三个字节</code>组成,能组成的最大 Unicode 字符是<code>0xffff</code>,也就是 Unicode 中的基本多文种平面(BMP)。</p><p>也就是说,任何不在基本多文本平面的 Unicode 字符,都无法使用 MySQL 的 utf8 字符集存储。</p><h3 id="utf8mb4"><a href="#utf8mb4" class="headerlink" title="utf8mb4"></a>utf8mb4</h3><p>MySQL 在5.5.3之后增加了这个utf8mb4的编码,mb4就是most bytes 4的意思,专门用来兼容<code>四字节</code>的 Unicode。</p><p>新增的一个字节,可以让它支持包括 Emoji 表情(Emoji 是一种特殊的 Unicode 编码,常见于 ios 和 android 手机上),和很多不常用的汉字,以及任何新增的 Unicode 字符等等。</p><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><p>我觉得,为了获取更好的兼容性,应该总是使用 utf8mb4 而非 utf8。</p><p>对于 CHAR 类型数据,utf8mb4 会多消耗一些空间,根据 MySQL 官方建议,可以使用 VARCHAR 替代 CHAR。</p><h2 id="排序规则"><a href="#排序规则" class="headerlink" title="排序规则"></a>排序规则</h2><p>创建库的时候,我们经常会使用语句:<br><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">DATABASE</span> dbname <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span> utf8 <span class="keyword">COLLATE</span> utf8_general_ci;</span><br></pre></td></tr></table></figure></p><p>既然我们知道了<code>CHARSET</code>是代表字符集,那么<code>COLLATE</code>又代表什么呢?它代表着排序规则。</p><h3 id="概念-1"><a href="#概念-1" class="headerlink" title="概念"></a>概念</h3><blockquote><p>MySQl的排序规则(collation),一般指对字符集中字符串之间的比较、排序制定的规则, MySLQ排序规则特征:</p></blockquote><blockquote><blockquote><p>o 两个不同的字符集不能有相同的校对规则;</p><p>o 每个字符集有一个默认校对规则;</p><p>o 存在校对规则命名约定:以其相关的字符集名开始,中间包括一个语言名,并且以_ci(大小写不敏感)、_cs(大小写敏感)或_bin(二元)结束。</p></blockquote></blockquote><p>其实对于排序规则的细节问题,我们关注较少,反而对排序规则中是否涉及大小写敏感关注较多。</p><p>例如,系统使用 utf8 字符集,若使用 utf8_bin 排序规则,执行 SQL 查询时区分大小写。使用 utf8_general_ci 不区分大小写(默认的 utf8 字符集对应的校对规则是 utf8_general_ci)。</p><h3 id="utf8-unicode-ci-与-utf8-general-ci-的区别"><a href="#utf8-unicode-ci-与-utf8-general-ci-的区别" class="headerlink" title="utf8_unicode_ci 与 utf8_general_ci 的区别"></a>utf8_unicode_ci 与 utf8_general_ci 的区别</h3><p>当前,utf8_unicode_ci 校对规则仅部分支持 Unicode 校对规则算法。一些字符还是不能支持。并且,不能完全支持组合的记号。这主要影响越南和俄罗斯的一些少数民族语言,如:Udmurt 、Tatar、Bashkir和Mari。</p><p>utf8_unicode_ci 的最主要的特色是支持扩展,即当把一个字母看作与其它字母组合相等时。例如,在德语和一些其它语言中‘ß’等于‘ss’。</p><p>utf8_general_ci 是一个遗留的校对规则,不支持扩展。它仅能够在字符之间进行逐个比较。这意味着 utf8_general_ci 校对规则进行的比较速度很快,但是与使用 utf8_unicode_ci 的校对规则相比,比较正确性较差)。</p><h3 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h3><p>对于一种语言仅当使用 utf8_unicode_ci 排序做的不好时,才执行与具体语言相关的utf8字符集 校对规则。例如,对于德语和法语,utf8_unicode_ci 工作的很好,因此不再需要为这两种语言创建特殊的 utf8 校对规则。</p><p>utf8_general_ci 也适用与德语和法语,除了‘ß’等于‘s’,而不是‘ss’之外。如果你的应用能够接受这些,那么应该使用 utf8_general_ci,因为它速度快。否则,使用 utf8_unicode_ci,因为它比较准确。 </p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>想不到 DB 创建语句中的<code>CHARSET</code> 与 <code>COLLATE</code> 都有这么大的学问,码农的学习之路真的是一刻都不能停止。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。<br><br><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a><br><br>公众号:健程之道<br><br><img src="https://static01.imgkr.com/temp/dcfad764025e4ea9ab8d73ef0934efdd.png" alt=""><br><br><img src="https://static01.imgkr.com/temp/003e8c4d5f5544b0bd70dfe7aa63bf60.png" alt=""></p>]]></content>
<summary type="html">
<p>今天突然被同事问到,MySql 里的 uft8 与 utf8mb4 究竟有什么区别,当时我也是一脸问号,因此特地去了解了一下。<br>
</summary>
<category term="MySQL" scheme="https://www.death00.top/tags/MySQL/"/>
</entry>
<entry>
<title>Class.forName 造成的线程阻塞</title>
<link href="https://www.death00.top/2020/12/15/Class.forName%20%E9%80%A0%E6%88%90%E7%9A%84%E7%BA%BF%E7%A8%8B%E9%98%BB%E5%A1%9E/"/>
<id>https://www.death00.top/2020/12/15/Class.forName 造成的线程阻塞/</id>
<published>2020-12-15T01:00:00.000Z</published>
<updated>2020-12-27T06:04:51.579Z</updated>
<content type="html"><![CDATA[<p>今天在查看服务器时,发现机器上稳定的会有 3 ~ 4 个线程处于阻塞状态,感觉应该是有问题的,仔细排查了一下,最终发现和 Class.forName 有关。<br><a id="more"></a></p><h2 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h2><p>某一天突然收到了公司的系统提醒,说是我们的服务中,长时间都有好几个处于<code>BLOCKED</code>状态的线程。</p><p>因为我们的访问量还是不小的,因此写了一段代码模拟了一下,大致类似于:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.beans.BeanInfo;</span><br><span class="line"><span class="keyword">import</span> java.beans.Introspector;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.CountDownLatch;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.LinkedBlockingQueue;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ThreadFactory;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ThreadPoolExecutor;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.atomic.AtomicInteger;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> ThreadPoolExecutor executor = <span class="keyword">new</span> ThreadPoolExecutor(<span class="number">10</span>, <span class="number">10</span>, <span class="number">0</span>, TimeUnit.SECONDS,</span><br><span class="line"> <span class="keyword">new</span> LinkedBlockingQueue<>(), <span class="keyword">new</span> ThreadFactory() {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> AtomicInteger count = <span class="keyword">new</span> AtomicInteger(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Thread <span class="title">newThread</span><span class="params">(Runnable r)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Thread(r, <span class="string">"testThread-"</span> + count.incrementAndGet());</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> totalCount = <span class="number">1000</span>;</span><br><span class="line"> CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(totalCount);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < totalCount; i++) {</span><br><span class="line"> <span class="keyword">int</span> current = i;</span><br><span class="line"> executor.execute(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < totalCount; j++) {</span><br><span class="line"> BeanInfo destBean = Introspector.getBeanInfo(Test.class, java.lang.Object.class);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> System.out.println(current + <span class="string">"\tfail"</span>);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> latch.countDown();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> latch.await();</span><br><span class="line"> System.out.println(<span class="string">"finish"</span>);</span><br><span class="line"> executor.shutdown();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Integer age;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Integer <span class="title">getAge</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> age;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setAge</span><span class="params">(Integer age)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.age = age;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>当时登上了服务器,首先查询出我们的 java 进程号:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ps -ef | grep java</span><br></pre></td></tr></table></figure></p><p>假设结果是<code>26385</code>,这时再借助<code>jstack</code>命令打印出各个线程的状态:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jstack 26385 > 26385.txt</span><br></pre></td></tr></table></figure></p><p>然后分析了<code>26385.txt</code>,发现了原因:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">"testThread-7" #14 prio=5 os_prio=0 tid=0x00007f72a810e800 nid=0x6706 waiting for monitor entry [0x00007f729837c000]</span><br><span class="line"> java.lang.Thread.State: BLOCKED (on object monitor)</span><br><span class="line">at java.lang.ClassLoader.loadClass(ClassLoader.java:404)</span><br><span class="line">- waiting to lock <0x00000000f7c773b8> (a java.lang.Object)</span><br><span class="line">at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)</span><br><span class="line">at java.lang.ClassLoader.loadClass(ClassLoader.java:357)</span><br><span class="line">at java.lang.Class.forName0(Native Method)</span><br><span class="line">at java.lang.Class.forName(Class.java:348)</span><br><span class="line">at com.sun.beans.finder.ClassFinder.findClass(ClassFinder.java:103)</span><br><span class="line">at java.beans.Introspector.findCustomizerClass(Introspector.java:1301)</span><br><span class="line">at java.beans.Introspector.getTargetBeanDescriptor(Introspector.java:1295)</span><br><span class="line">at java.beans.Introspector.getBeanInfo(Introspector.java:425)</span><br><span class="line">at java.beans.Introspector.getBeanInfo(Introspector.java:262)</span><br><span class="line">at java.beans.Introspector.getBeanInfo(Introspector.java:224)</span><br><span class="line">at Test.lambda$main$0(Test.java:35)</span><br><span class="line">at Test$$Lambda$1/303563356.run(Unknown Source)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)</span><br><span class="line">at java.lang.Thread.run(Thread.java:748)</span><br></pre></td></tr></table></figure></p><p>上面很清晰地显示了,我们写的<code>Introspector.getBeanInfo</code>代码,最终会调用<code>Class</code>类中的<code>forName0</code>方法:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">/** Called after security check for system loader access checks have been made. */</span><br><span class="line">private static native Class<?> forName0(String name, boolean initialize,</span><br><span class="line"> ClassLoader loader,</span><br><span class="line"> Class<?> caller)</span><br><span class="line"> throws ClassNotFoundException;</span><br></pre></td></tr></table></figure></p><p>从上面的<code>stack</code>中分析可以得知,这个方法内部应该是有锁的,因此会阻塞其他线程。</p><h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p>既然它是有锁的,为了不让它在运行时每次都执行,最简单的方法就是在初始化时,就将需要处理的类全部处理好,这样在应用运行期间,完全不会再去反射。</p><h2 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h2><p>有些人可能会好奇,<code>forName0</code>中究竟是如何使用到了锁,这里就把源码展示给大家。</p><p>下文的调用链路是:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">forName0 -> JVM_FindClassFromCaller -> find_class_from_class_loader -> resolve_or_fail -> resolve_or_null -> resolve_instance_class_or_null -> load_instance_class</span><br></pre></td></tr></table></figure></p><p><code>forName0</code>源码实现位于<code>src/java.base/share/native/libjava/Class.c</code><br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 动态装载类型入口</span></span><br><span class="line">JNIEXPORT jclass JNICALL</span><br><span class="line">Java_java_lang_Class_forName0(JNIEnv *env, jclass <span class="keyword">this</span>, jstring classname,</span><br><span class="line"> jboolean initialize, jobject loader, jclass caller)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">char</span> *clname;</span><br><span class="line"> jclass cls = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">char</span> buf[<span class="number">128</span>];</span><br><span class="line"> jsize len;</span><br><span class="line"> jsize unicode_len;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (classname == <span class="literal">NULL</span>) {</span><br><span class="line"> JNU_ThrowNullPointerException(env, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 把类全限定名里的'.'翻译成'/'</span></span><br><span class="line"> <span class="keyword">if</span> (VerifyFixClassname(clname) == JNI_TRUE) {</span><br><span class="line"> <span class="comment">/* slashes present in clname, use name b4 translation for exception */</span></span><br><span class="line"> (*env)->GetStringUTFRegion(env, classname, <span class="number">0</span>, unicode_len, clname);</span><br><span class="line"> JNU_ThrowClassNotFoundException(env, clname);</span><br><span class="line"> <span class="keyword">goto</span> done;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 验证类全限定名名合法性(是否以'/'分隔)</span></span><br><span class="line"> <span class="keyword">if</span> (!VerifyClassname(clname, JNI_TRUE)) { <span class="comment">/* expects slashed name */</span></span><br><span class="line"> JNU_ThrowClassNotFoundException(env, clname);</span><br><span class="line"> <span class="keyword">goto</span> done;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从指定的加载器查找该类</span></span><br><span class="line"> cls = JVM_FindClassFromCaller(env, clname, initialize, loader, caller);</span><br><span class="line"></span><br><span class="line"> done:</span><br><span class="line"> <span class="keyword">if</span> (clname != buf) {</span><br><span class="line"> <span class="built_in">free</span>(clname);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cls;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>FindClassFromCaller</code>位于<code>src/hotspot/share/prims/jvm.cpp</code><br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 从指定的加载器查找该类</span></span><br><span class="line"><span class="comment">// Find a class with this name in this loader, using the caller's protection domain.</span></span><br><span class="line">JVM_ENTRY(jclass, JVM_FindClassFromCaller(JNIEnv* env, <span class="keyword">const</span> <span class="keyword">char</span>* name,</span><br><span class="line"> jboolean init, jobject loader,</span><br><span class="line"> jclass caller))</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 把当前类加入符号表(一个哈希表实现)</span></span><br><span class="line"> TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取加载器和调用类</span></span><br><span class="line"> oop loader_oop = JNIHandles::resolve(loader);</span><br><span class="line"> oop from_class = JNIHandles::resolve(caller);</span><br><span class="line"> oop protection_domain = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (from_class != <span class="literal">NULL</span> && loader_oop != <span class="literal">NULL</span>) {</span><br><span class="line"> protection_domain = java_lang_Class::as_Klass(from_class)->protection_domain();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 查找该类</span></span><br><span class="line"> jclass result = find_class_from_class_loader(env, h_name, init, h_loader,</span><br><span class="line"> h_prot, <span class="literal">false</span>, THREAD);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 返回结果</span></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">JVM_END</span><br></pre></td></tr></table></figure></p><p><code>find_class_from_class_loader</code>位于<code>/src/hotspot/share/prims/jvm.cpp</code><br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Shared JNI/JVM entry points //////////////////////////////////////////////////////////////</span></span><br><span class="line"><span class="comment">// 从指定的classloader中查找类</span></span><br><span class="line"><span class="function">jclass <span class="title">find_class_from_class_loader</span><span class="params">(JNIEnv* env, Symbol* name, jboolean init,</span></span></span><br><span class="line"><span class="function"><span class="params"> Handle loader, Handle protection_domain,</span></span></span><br><span class="line"><span class="function"><span class="params"> jboolean throwError, TRAPS)</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">//==========================================</span></span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="comment">// 根据指定的类名和加载器返回一个Klass对象,必要情况下需要加载该类。</span></span><br><span class="line"> <span class="comment">// 如果未找到该类则抛出NoClassDefFoundError或ClassNotFoundException</span></span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="comment">//=========================================</span></span><br><span class="line"> Klass* klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != <span class="number">0</span>, CHECK_NULL);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Check if we should initialize the class</span></span><br><span class="line"> <span class="keyword">if</span> (init && klass->is_instance_klass()) {</span><br><span class="line"> klass->initialize(CHECK_NULL);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (jclass) JNIHandles::make_local(env, klass->java_mirror());</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>resolve_or_fail</code>位于<code>src/hotspot/share/classfile/systemDictionary.cpp</code><br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Forwards to resolve_or_null</span></span><br><span class="line"></span><br><span class="line">Klass* SystemDictionary::resolve_or_fail(Symbol* class_name, Handle class_loader, Handle protection_domain, <span class="keyword">bool</span> throw_error, TRAPS) {</span><br><span class="line"> Klass* klass = resolve_or_null(class_name, class_loader, protection_domain, THREAD);</span><br><span class="line"> <span class="keyword">if</span> (HAS_PENDING_EXCEPTION || klass == <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="comment">// can return a null klass</span></span><br><span class="line"> klass = handle_resolution_exception(class_name, throw_error, klass, THREAD);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> klass;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>resolve_or_null</code>也位于<code>src/hotspot/share/classfile/systemDictionary.cpp</code><br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Forwards to resolve_instance_class_or_null</span></span><br><span class="line"></span><br><span class="line">Klass* SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) {</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (FieldType::is_array(class_name)) {</span><br><span class="line"> <span class="keyword">return</span> resolve_array_class_or_null(class_name, class_loader, protection_domain, THREAD);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (FieldType::is_obj(class_name)) {</span><br><span class="line"> ResourceMark rm(THREAD);</span><br><span class="line"> <span class="comment">// Ignore wrapping L and ;.</span></span><br><span class="line"> TempNewSymbol name = SymbolTable::new_symbol(class_name->as_C_string() + <span class="number">1</span>,</span><br><span class="line"> class_name->utf8_length() - <span class="number">2</span>, CHECK_NULL);</span><br><span class="line"> <span class="keyword">return</span> resolve_instance_class_or_null(name, class_loader, protection_domain, THREAD);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 解析实例类</span></span><br><span class="line"> <span class="keyword">return</span> resolve_instance_class_or_null(class_name, class_loader, protection_domain, THREAD);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>resolve_instance_class_or_null</code>也位于<code>src/hotspot/share/classfile/systemDictionary.cpp</code><br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line">Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,</span><br><span class="line"> Handle class_loader,</span><br><span class="line"> Handle protection_domain,</span><br><span class="line"> TRAPS) {</span><br><span class="line"> Handle lockObject = compute_loader_lock_object(class_loader, THREAD);</span><br><span class="line"> check_loader_lock_contention(lockObject, THREAD);</span><br><span class="line"> <span class="comment">// 获取对象锁</span></span><br><span class="line"> <span class="function">ObjectLocker <span class="title">ol</span><span class="params">(lockObject, THREAD, DoObjectLock)</span></span>;</span><br><span class="line"></span><br><span class="line"> {</span><br><span class="line"> <span class="function">MutexLocker <span class="title">mu</span><span class="params">(SystemDictionary_lock, THREAD)</span></span>;</span><br><span class="line"> <span class="comment">// 查找类</span></span><br><span class="line"> InstanceKlass* check = find_class(d_index, d_hash, name, dictionary);</span><br><span class="line"> <span class="keyword">if</span> (check != <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="comment">// Klass is already loaded, so just return it</span></span><br><span class="line"> class_has_been_loaded = <span class="literal">true</span>;</span><br><span class="line"> k = check;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 查找该类是否在placeholder table中</span></span><br><span class="line"> placeholder = placeholders()->get_entry(p_index, p_hash, name, loader_data);</span><br><span class="line"> <span class="keyword">if</span> (placeholder && placeholder->super_load_in_progress()) {</span><br><span class="line"> super_load_in_progress = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">if</span> (placeholder->havesupername() == <span class="literal">true</span>) {</span><br><span class="line"> superclassname = placeholder->supername();</span><br><span class="line"> havesupername = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果该类在placeholder table中,则说明类加载进行中</span></span><br><span class="line"> <span class="keyword">if</span> (super_load_in_progress && havesupername==<span class="literal">true</span>) {</span><br><span class="line"> k = handle_parallel_super_load(name,</span><br><span class="line"> superclassname,</span><br><span class="line"> class_loader,</span><br><span class="line"> protection_domain,</span><br><span class="line"> lockObject, THREAD);</span><br><span class="line"> <span class="keyword">if</span> (HAS_PENDING_EXCEPTION) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (k != <span class="literal">NULL</span>) {</span><br><span class="line"> class_has_been_loaded = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">bool</span> throw_circularity_error = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">if</span> (!class_has_been_loaded) {</span><br><span class="line"> <span class="keyword">bool</span> load_instance_added = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!class_has_been_loaded) {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// =====================================</span></span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="comment">// 执行实例加载动作</span></span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="comment">// =====================================</span></span><br><span class="line"> k = load_instance_class(name, class_loader, THREAD);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!HAS_PENDING_EXCEPTION && k != <span class="literal">NULL</span> &&</span><br><span class="line"> k->class_loader() != class_loader()) {</span><br><span class="line"></span><br><span class="line"> check_constraints(d_index, d_hash, k, class_loader, <span class="literal">false</span>, THREAD);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Need to check for a PENDING_EXCEPTION again; check_constraints</span></span><br><span class="line"> <span class="comment">// can throw and doesn't use the CHECK macro.</span></span><br><span class="line"> <span class="keyword">if</span> (!HAS_PENDING_EXCEPTION) {</span><br><span class="line"> { <span class="comment">// Grabbing the Compile_lock prevents systemDictionary updates</span></span><br><span class="line"> <span class="comment">// during compilations.</span></span><br><span class="line"> <span class="function">MutexLocker <span class="title">mu</span><span class="params">(Compile_lock, THREAD)</span></span>;</span><br><span class="line"> update_dictionary(d_index, d_hash, p_index, p_hash,</span><br><span class="line"> k, class_loader, THREAD);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 通知JVMTI类加载事件</span></span><br><span class="line"> <span class="keyword">if</span> (JvmtiExport::should_post_class_load()) {</span><br><span class="line"> Thread *thread = THREAD;</span><br><span class="line"> assert(thread->is_Java_thread(), <span class="string">"thread->is_Java_thread()"</span>);</span><br><span class="line"> JvmtiExport::post_class_load((JavaThread *) thread, k);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="comment">// load_instance_class</span></span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> k;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>load_instance_class</code>也位于<code>src/hotspot/share/classfile/systemDictionary.cpp</code><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br></pre></td><td class="code"><pre><span class="line">// ===================================================================================</span><br><span class="line">//</span><br><span class="line">// 加载实例class,这里有两种方式:</span><br><span class="line">// ===================================================================================</span><br><span class="line">//</span><br><span class="line">// 1、如果classloader为null则说明是加载系统类,使用bootstrap loader</span><br><span class="line">// 调用方式:直接调用ClassLoader::load_class()加载该类</span><br><span class="line">//</span><br><span class="line">// 2、如果classloader不为null则说明是非系统类,使用ext/app/自定义 classloader</span><br><span class="line">// 调用方式:通过JavaCalls::call_virtual()调用Java方法ClassLoader.loadClass()加载该类</span><br><span class="line">//</span><br><span class="line">// ===================================================================================</span><br><span class="line">InstanceKlass* SystemDictionary::load_instance_class(Symbol* class_name, Handle class_loader, TRAPS) {</span><br><span class="line"></span><br><span class="line"> // 使用bootstrap加载器加载</span><br><span class="line"> if (class_loader.is_null()) {</span><br><span class="line"></span><br><span class="line"> // 根据全限定名获取包名</span><br><span class="line"> // Find the package in the boot loader's package entry table.</span><br><span class="line"> TempNewSymbol pkg_name = InstanceKlass::package_from_name(class_name, CHECK_NULL);</span><br><span class="line"> if (pkg_name != NULL) {</span><br><span class="line"> pkg_entry = loader_data->packages()->lookup_only(pkg_name);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> InstanceKlass* k = NULL;</span><br><span class="line"></span><br><span class="line"> if (k == NULL) {</span><br><span class="line"> // Use VM class loader</span><br><span class="line"> PerfTraceTime vmtimer(ClassLoader::perf_sys_classload_time());</span><br><span class="line"> // =================================================================</span><br><span class="line"> //</span><br><span class="line"> // 使用bootstrap loader加载该类</span><br><span class="line"> //</span><br><span class="line"> // =================================================================</span><br><span class="line"> k = ClassLoader::load_class(class_name, search_only_bootloader_append, CHECK_NULL);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> return k;</span><br><span class="line"> } else {</span><br><span class="line"> // =======================================================================================</span><br><span class="line"> //</span><br><span class="line"> // 使用用户指定的加载器加载该类,调用class_loader的loadClass操作方法,</span><br><span class="line"> // 最终返回一个标准的InstanceKlass,流程如下</span><br><span class="line"> //</span><br><span class="line"> // +-----------+ loadClass() +---------------+ get_jobject() +-------------+</span><br><span class="line"> // | className | -------------> | JavaValue | ---------------> | oop |</span><br><span class="line"> // +-----------+ +---------------+ +-------------+</span><br><span class="line"> // |</span><br><span class="line"> // | as_Klass()</span><br><span class="line"> // v</span><br><span class="line"> // +---------------+ cast() +-------------+</span><br><span class="line"> // | InstanceKlass | <--------------- | Klass |</span><br><span class="line"> // +---------------+ +-------------+</span><br><span class="line"> //</span><br><span class="line"> // ======================================================================================= </span><br><span class="line"> ResourceMark rm(THREAD);</span><br><span class="line"></span><br><span class="line"> assert(THREAD->is_Java_thread(), "must be a JavaThread");</span><br><span class="line"> JavaThread* jt = (JavaThread*) THREAD;</span><br><span class="line"></span><br><span class="line"> PerfClassTraceTime vmtimer(ClassLoader::perf_app_classload_time(),</span><br><span class="line"> ClassLoader::perf_app_classload_selftime(),</span><br><span class="line"> ClassLoader::perf_app_classload_count(),</span><br><span class="line"> jt->get_thread_stat()->perf_recursion_counts_addr(),</span><br><span class="line"> jt->get_thread_stat()->perf_timers_addr(),</span><br><span class="line"> PerfClassTraceTime::CLASS_LOAD);</span><br><span class="line"></span><br><span class="line"> Handle s = java_lang_String::create_from_symbol(class_name, CHECK_NULL);</span><br><span class="line"> // Translate to external class name format, i.e., convert '/' chars to '.'</span><br><span class="line"> Handle string = java_lang_String::externalize_classname(s, CHECK_NULL);</span><br><span class="line"></span><br><span class="line"> JavaValue result(T_OBJECT);</span><br><span class="line"></span><br><span class="line"> InstanceKlass* spec_klass = SystemDictionary::ClassLoader_klass();</span><br><span class="line"></span><br><span class="line"> // Added MustCallLoadClassInternal in case we discover in the field</span><br><span class="line"> // a customer that counts on this call</span><br><span class="line"> if (MustCallLoadClassInternal && has_loadClassInternal()) {</span><br><span class="line"> JavaCalls::call_special(&result,</span><br><span class="line"> class_loader,</span><br><span class="line"> spec_klass,</span><br><span class="line"> vmSymbols::loadClassInternal_name(),</span><br><span class="line"> vmSymbols::string_class_signature(),</span><br><span class="line"> string,</span><br><span class="line"> CHECK_NULL);</span><br><span class="line"> } else {</span><br><span class="line"> // ===============================================================</span><br><span class="line"> //</span><br><span class="line"> // 调用ClassLoader.loadClass()方法加载该类,而最终会调用ClassLoader的native方法defineClass1()</span><br><span class="line"> // 其实现位于ClassLoader.c # Java_java_lang_ClassLoader_defineClass1()</span><br><span class="line"> //</span><br><span class="line"> // ===============================================================</span><br><span class="line"> JavaCalls::call_virtual(&result,</span><br><span class="line"> class_loader,</span><br><span class="line"> spec_klass,</span><br><span class="line"> vmSymbols::loadClass_name(),</span><br><span class="line"> vmSymbols::string_class_signature(),</span><br><span class="line"> string,</span><br><span class="line"> CHECK_NULL);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> assert(result.get_type() == T_OBJECT, "just checking");</span><br><span class="line"> // 获取oop对象</span><br><span class="line"> oop obj = (oop) result.get_jobject();</span><br><span class="line"></span><br><span class="line"> // 如果不是基本类,则转换成对应的InstanceKlass</span><br><span class="line"> if ((obj != NULL) && !(java_lang_Class::is_primitive(obj))) {</span><br><span class="line"> InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(obj));</span><br><span class="line"> </span><br><span class="line"> if (class_name == k->name()) {</span><br><span class="line"> // 返回最终InstanceKlass</span><br><span class="line"> return k;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> // Class is not found or has the wrong name, return NULL</span><br><span class="line"> return NULL;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>以上便是相关的所有底层源码。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>一个小小的<code>Class.forName</code>方法,也会引出不少问题,如果仔细研究,在排查的过程,相信你一定会有所收获。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。<br><br><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a><br><br>公众号:健程之道<br><br><img src="https://static01.imgkr.com/temp/dcfad764025e4ea9ab8d73ef0934efdd.png" alt=""><br><br><img src="https://static01.imgkr.com/temp/003e8c4d5f5544b0bd70dfe7aa63bf60.png" alt=""></p>]]></content>
<summary type="html">
<p>今天在查看服务器时,发现机器上稳定的会有 3 ~ 4 个线程处于阻塞状态,感觉应该是有问题的,仔细排查了一下,最终发现和 Class.forName 有关。<br>
</summary>
<category term="源码" scheme="https://www.death00.top/tags/%E6%BA%90%E7%A0%81/"/>
<category term="线程" scheme="https://www.death00.top/tags/%E7%BA%BF%E7%A8%8B/"/>
</entry>
<entry>
<title>SqlServer 关于 datetime 的更新引发的思考</title>
<link href="https://www.death00.top/2020/12/15/SqlServer%20%E5%85%B3%E4%BA%8E%20datetime%20%E7%9A%84%E6%9B%B4%E6%96%B0%E5%BC%95%E5%8F%91%E7%9A%84%E6%80%9D%E8%80%83/"/>
<id>https://www.death00.top/2020/12/15/SqlServer 关于 datetime 的更新引发的思考/</id>
<published>2020-12-15T01:00:00.000Z</published>
<updated>2020-12-27T06:03:16.019Z</updated>
<content type="html"><![CDATA[<p>今天在测试更新 SqlServer 表的 datetime 字段时,突然发现并没有更新成功,同时也没有报错,感觉十分诧异,因此仔细排查了一下,终于发现是和字段本身的精度有关。<br><a id="more"></a></p><h2 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h2><p>假设我们现在有一张 SqlServer 表 basic_info,其表结构为:</p><table><thead><tr><th>字段名</th><th>类型</th></tr></thead><tbody><tr><td>name</td><td>varchar</td></tr><tr><td>open_time</td><td>datetime</td></tr></tbody></table><p>现在其中有一条数据:</p><table><thead><tr><th>name</th><th>open_time</th></tr></thead><tbody><tr><td>Jack</td><td>2014-05-25 11:11:01.260</td></tr></tbody></table><p>现在我想把这条数据的 open_time 字段修改为 <code>2014-05-25 11:11:01.262</code>,执行语句:<br><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">update</span> basic_info <span class="keyword">set</span> open_time = <span class="string">'2014-05-25 11:11:01.262'</span> <span class="keyword">where</span> <span class="keyword">name</span> = <span class="string">'Jack'</span>;</span><br></pre></td></tr></table></figure></p><p>结果是并没有更新为我想要的值:</p><table><thead><tr><th>name</th><th>open_time</th></tr></thead><tbody><tr><td>Jack</td><td>2014-05-25 11:11:01.263</td></tr></tbody></table><p>这个 <code>263</code> 是怎么出现的呢?</p><h2 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h2><p>这个问题我排查了很久,最终在关于 <a href="https://docs.microsoft.com/en-us/sql/t-sql/data-types/datetime-transact-sql?view=sql-server-ver15" target="_blank" rel="noopener">datetime 字段定义</a> 的网站上,在关于字段描述的表中,有这么一行:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Accuracy Rounded to increments of .000, .003, or .007 seconds</span><br></pre></td></tr></table></figure></p><p>意思就是说, datetime 这个字段的 <code>精度</code> ,四舍五入后会变为 .000, .003, or .007 秒。</p><p>这样的话,也就能说这个字段的时间毫秒数的个位数,只会是 <code>0、3、7</code>,那么也就能解释上面的现象,为什么我想更新成 <code>2</code>,最终变成了 <code>3</code>。</p><h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p>当你对于精度要求不高的话,比如只需要精确到秒级别,那么这个字段的使用完全是没有问题的。</p><p>但如果你一定需要精确到毫秒呢?那么可以使用 <code>datetime2</code> 类型,它的精度可以精确到 <code>100 纳秒</code>。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>对于服务端开发人员,数据库几乎是一个无法避免的中间件,虽然我们并非专业的 DBA,但多了解一些总是好事。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://static01.imgkr.com/temp/dcfad764025e4ea9ab8d73ef0934efdd.png" alt=""></p><p><img src="https://static01.imgkr.com/temp/003e8c4d5f5544b0bd70dfe7aa63bf60.png" alt=""></p>]]></content>
<summary type="html">
<p>今天在测试更新 SqlServer 表的 datetime 字段时,突然发现并没有更新成功,同时也没有报错,感觉十分诧异,因此仔细排查了一下,终于发现是和字段本身的精度有关。<br>
</summary>
<category term="力扣" scheme="https://www.death00.top/categories/%E5%8A%9B%E6%89%A3/"/>
<category term="SqlServer" scheme="https://www.death00.top/tags/SqlServer/"/>
<category term="datetime" scheme="https://www.death00.top/tags/datetime/"/>
</entry>
<entry>
<title>力扣1514——概率最大的路径</title>
<link href="https://www.death00.top/2020/08/26/%E5%8A%9B%E6%89%A31514%E2%80%94%E2%80%94%E6%A6%82%E7%8E%87%E6%9C%80%E5%A4%A7%E7%9A%84%E8%B7%AF%E5%BE%84/"/>
<id>https://www.death00.top/2020/08/26/力扣1514——概率最大的路径/</id>
<published>2020-08-26T01:00:00.000Z</published>
<updated>2020-08-26T12:51:12.591Z</updated>
<content type="html"><![CDATA[<p>本题主要和图的遍历求解最短路径相关,可以用 Dijkstra 或者 Bellman-Ford 算法进行解决。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>给你一个由 n 个节点(下标从 0 开始)组成的无向加权图,该图由一个描述边的列表组成,其中 edges[i] = [a, b] 表示连接节点 a 和 b 的一条无向边,且该边遍历成功的概率为 succProb[i] 。</p><p>指定两个节点分别作为起点 start 和终点 end ,请你找出从起点到终点成功概率最大的路径,并返回其成功概率。</p><p>如果不存在从 start 到 end 的路径,请 返回 0 。只要答案与标准答案的误差不超过 1e-5 ,就会被视作正确答案。</p><p>示例 1:</p><p><img src="https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/07/12/1558_ex1.png" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">输入:n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.2], start = 0, end = 2</span><br><span class="line">输出:0.25000</span><br><span class="line">解释:从起点到终点有两条路径,其中一条的成功概率为 0.2 ,而另一条为 0.5 * 0.5 = 0.25</span><br></pre></td></tr></table></figure><p>示例 2:</p><p><img src="https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/07/12/1558_ex2.png" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.3], start = 0, end = 2</span><br><span class="line">输出:0.30000</span><br></pre></td></tr></table></figure><p>示例 3:</p><p><img src="https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/07/12/1558_ex3.png" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">输入:n = 3, edges = [[0,1]], succProb = [0.5], start = 0, end = 2</span><br><span class="line">输出:0.00000</span><br><span class="line">解释:节点 0 和 节点 2 之间不存在路径</span><br></pre></td></tr></table></figure><p>提示:</p><ul><li>2 <= n <= 10^4</li><li>0 <= start, end < n</li><li>start != end</li><li>0 <= a, b < n</li><li>a != b</li><li>0 <= succProb.length == edges.length <= 2*10^4</li><li>0 <= succProb[i] <= 1</li><li>每两个节点之间最多有一条边</li></ul><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="首次尝试"><a href="#首次尝试" class="headerlink" title="首次尝试"></a>首次尝试</h3><p>原本,我想利用树的深度优先搜索遍历,加上一定程度的剪枝(就是排除已经遍历过的节点),完成这道题目,代码如下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * key为起始点,value为所有相连的点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> Map<Integer, Set<Integer>> map;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * key为"点A_点B"(A < B),value为对应的概率</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> Map<String, Double> probMap;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">double</span> maxProb = -<span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> end;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">double</span> <span class="title">maxProbability</span><span class="params">(<span class="keyword">int</span> n, <span class="keyword">int</span>[][] edges, <span class="keyword">double</span>[] succProb, <span class="keyword">int</span> start, <span class="keyword">int</span> end)</span> </span>{</span><br><span class="line"> map = <span class="keyword">new</span> HashMap<>(n * <span class="number">4</span> / <span class="number">3</span> + <span class="number">1</span>);</span><br><span class="line"> probMap = <span class="keyword">new</span> HashMap<>(succProb.length * <span class="number">4</span> / <span class="number">3</span> + <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">this</span>.end = end;</span><br><span class="line"> <span class="comment">// 构造每个点的相连关系</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < edges.length; i++) {</span><br><span class="line"> <span class="keyword">int</span>[] edge = edges[i];</span><br><span class="line"> Set<Integer> set = map.computeIfAbsent(edge[<span class="number">0</span>], k -> <span class="keyword">new</span> HashSet<>());</span><br><span class="line"> set.add(edge[<span class="number">1</span>]);</span><br><span class="line"> set = map.computeIfAbsent(edge[<span class="number">1</span>], k -> <span class="keyword">new</span> HashSet<>());</span><br><span class="line"> set.add(edge[<span class="number">0</span>]);</span><br><span class="line"></span><br><span class="line"> String key = edge[<span class="number">0</span>] < edge[<span class="number">1</span>] ? (edge[<span class="number">0</span>] + <span class="string">"_"</span> + edge[<span class="number">1</span>]) : (edge[<span class="number">1</span>] + <span class="string">"_"</span> + edge[<span class="number">0</span>]);</span><br><span class="line"> probMap.put(key, succProb[i]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">boolean</span>[] visited = <span class="keyword">new</span> <span class="keyword">boolean</span>[n];</span><br><span class="line"> dp(start, <span class="number">1</span>, visited);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> maxProb == -<span class="number">1</span> ? <span class="number">0</span> : maxProb;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">dp</span><span class="params">(<span class="keyword">int</span> index, <span class="keyword">double</span> prob, <span class="keyword">boolean</span>[] visited)</span> </span>{</span><br><span class="line"> <span class="comment">// 已到终点</span></span><br><span class="line"> <span class="keyword">if</span> (index == end) {</span><br><span class="line"> maxProb = prob > maxProb ? prob : maxProb;</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取当前点可以到达的所有点</span></span><br><span class="line"> Set<Integer> set = map.get(index);</span><br><span class="line"> <span class="comment">// 如果当前点到达不了其余点</span></span><br><span class="line"> <span class="keyword">if</span> (set == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 标记当前点已访问</span></span><br><span class="line"> visited[index] = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 遍历相邻的点</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> next : set) {</span><br><span class="line"> <span class="keyword">if</span> (visited[next]) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> String key = index < next ? (index + <span class="string">"_"</span> + next) : (next + <span class="string">"_"</span> + index); </span><br><span class="line"> <span class="comment">// 访问下一个点</span></span><br><span class="line"> dp(next, prob * probMap.get(key), visited);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 退出,将该点标记为未访问</span></span><br><span class="line"> visited[index] = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>但很可惜,超时了。我想了一下,应该是因为没有借用之前已经计算出来的结果,因此比较浪费时间。</p><p>其时间复杂度取决于边的数量,假设边的数量是 m ,则时间复杂度为<code>O(m^2)</code>。</p><p>而边 m 与点 n 的关系,m 最小是 0(也就是点之间没有线),最大是 <code>(n - 1) * n / 2</code>,每个点之间都有连线。</p><p>因此可以预见,这样的算法效率确实很差。</p><h3 id="Dijkstra-算法"><a href="#Dijkstra-算法" class="headerlink" title="Dijkstra 算法"></a>Dijkstra 算法</h3><h4 id="定义概览"><a href="#定义概览" class="headerlink" title="定义概览"></a>定义概览</h4><p>Dijkstra (迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。</p><p><code>注意该算法要求图中不存在负权边。</code></p><h4 id="算法思想"><a href="#算法思想" class="headerlink" title="算法思想"></a>算法思想</h4><p>设 G=(V,E) 是一个带权有向图,把图中顶点集合 V 分成两组:</p><p>第一组为已求出最短路径的顶点集合(用 S 表示,初始时 S 中只有一个源点,以后每求得一条最短路径 , 就将加入到集合 S 中,直到全部顶点都加入到 S 中,算法就结束了)。</p><p>第二组为其余未确定最短路径的顶点集合(用 U 表示),按最短路径长度的递增次序依次把第二组的顶点加入 S 中。</p><p>在加入的过程中,总保持从源点 v 到 S 中各顶点的最短路径长度不大于从源点 v 到 U 中任何顶点的最短路径长度。</p><p>此外,每个顶点对应一个距离,S 中的顶点的距离就是从 v 到此顶点的最短路径长度。U 中的顶点的距离,是从 v 到此顶点只包括 S 中的顶点为中间顶点的当前最短路径长度。</p><h4 id="算法步骤"><a href="#算法步骤" class="headerlink" title="算法步骤"></a>算法步骤</h4><ol><li>初始时,S 只包含源点,即 S ={v},v 的距离为0。U 包含除 v 外的其他顶点,即: U ={其余顶点},若 v 与 U 中顶点 u 有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。</li><li>从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。</li><li>以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。</li><li>重复步骤b和c直到所有顶点都包含在S中。</li></ol><p>执行动画过程如下图</p><p><img src="https://pic002.cnblogs.com/images/2012/426620/2012073019540660.gif" alt=""></p><h4 id="本题解法"><a href="#本题解法" class="headerlink" title="本题解法"></a>本题解法</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">double</span> <span class="title">maxProbability</span><span class="params">(<span class="keyword">int</span> n, <span class="keyword">int</span>[][] edges, <span class="keyword">double</span>[] succProb, <span class="keyword">int</span> start, <span class="keyword">int</span> end)</span> </span>{</span><br><span class="line"> <span class="comment">// records[i]代表点i相邻的所有点,以及其概率</span></span><br><span class="line"> List<List<Record>> allRecords = <span class="keyword">new</span> ArrayList<>(n + <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n + <span class="number">1</span>; i++) {</span><br><span class="line"> allRecords.add(<span class="keyword">new</span> LinkedList<>());</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 构造每个点的相连关系</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < edges.length; i++) {</span><br><span class="line"> <span class="keyword">int</span>[] edge = edges[i];</span><br><span class="line"> List<Record> records = allRecords.get(edge[<span class="number">0</span>]);</span><br><span class="line"> records.add(<span class="keyword">new</span> Record(edge[<span class="number">1</span>], succProb[i]));</span><br><span class="line"> </span><br><span class="line"> records = allRecords.get(edge[<span class="number">1</span>]);</span><br><span class="line"> records.add(<span class="keyword">new</span> Record(edge[<span class="number">0</span>], succProb[i]));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 利用广度优先搜索,进行遍历</span></span><br><span class="line"> <span class="comment">// 借用优先队列,保证优先遍历当前概率高的</span></span><br><span class="line"> PriorityQueue<Record> queue = <span class="keyword">new</span> PriorityQueue<>();</span><br><span class="line"> <span class="comment">// 记录从start到每一个点的概率</span></span><br><span class="line"> <span class="keyword">double</span>[] result = <span class="keyword">new</span> <span class="keyword">double</span>[n];</span><br><span class="line"> <span class="comment">// 从start开始遍历</span></span><br><span class="line"> queue.offer(<span class="keyword">new</span> Record(start, <span class="number">1</span>));</span><br><span class="line"> result[start] = <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 开始</span></span><br><span class="line"> <span class="keyword">while</span> (!queue.isEmpty()) {</span><br><span class="line"> <span class="comment">// 当前节点</span></span><br><span class="line"> Record record = queue.poll();</span><br><span class="line"> <span class="keyword">int</span> node = record.node;</span><br><span class="line"> <span class="keyword">double</span> prob = record.prob;</span><br><span class="line"> <span class="comment">// 获取当前点所能达到的其他节点</span></span><br><span class="line"> List<Record> otherNodes = allRecords.get(node);</span><br><span class="line"> <span class="comment">// 遍历其余节点</span></span><br><span class="line"> <span class="keyword">for</span> (Record next : otherNodes) {</span><br><span class="line"> <span class="keyword">int</span> nextNode = next.node;</span><br><span class="line"> <span class="keyword">double</span> nextProb = prob * next.prob;</span><br><span class="line"> <span class="comment">// 如果当前计算出的概率,小于等于之前计算的概率</span></span><br><span class="line"> <span class="keyword">if</span> (nextProb <= result[nextNode]) {</span><br><span class="line"> <span class="comment">// 那么就没有必要继续算了,直接用之前的即可</span></span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 更新概率</span></span><br><span class="line"> result[nextNode] = nextProb;</span><br><span class="line"> <span class="comment">// 如果已到结尾或者当前的概率已经比到end的小</span></span><br><span class="line"> <span class="keyword">if</span> (nextNode == end || nextProb < result[end]) {</span><br><span class="line"> <span class="comment">// 那么也没有必要继续了</span></span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 添加节点</span></span><br><span class="line"> queue.offer(<span class="keyword">new</span> Record(nextNode, nextProb));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result[end];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Record</span> <span class="keyword">implements</span> <span class="title">Comparable</span><<span class="title">Record</span>> </span>{</span><br><span class="line"> <span class="keyword">int</span> node;</span><br><span class="line"> <span class="keyword">double</span> prob;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Record</span><span class="params">(<span class="keyword">int</span> node, <span class="keyword">double</span> prob)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.node = node;</span><br><span class="line"> <span class="keyword">this</span>.prob = prob;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">compareTo</span><span class="params">(Record other)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (other == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.prob == other.prob) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.node - other.node;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.prob - other.prob > <span class="number">0</span> ? -<span class="number">1</span> : <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>提交OK,执行用时超过了<code>69%</code>的 java 提交记录,看来还有值得优化的地方。</p><p>假设边的数量为 m ,点的数量为 n ,则时间复杂度为<code>O(n + m + nlogn)</code>。</p><h3 id="Bellman-Ford-算法"><a href="#Bellman-Ford-算法" class="headerlink" title="Bellman-Ford 算法"></a>Bellman-Ford 算法</h3><p>之前有说到 Dijkstra 算法要求不能有<code>负权边</code>,而这个 Bellman-Ford 算法是支持的。</p><h4 id="算法步骤-1"><a href="#算法步骤-1" class="headerlink" title="算法步骤"></a>算法步骤</h4><ol><li>创建源顶点 v 到图中所有顶点的距离的集合 distSet,为图中的所有顶点指定一个距离值,初始均为 Infinite,源顶点距离为 0;</li><li>计算最短路径,执行 V - 1 次遍历;对于图中的每条边:如果起点 u 的距离 d 加上边的权值 w 小于终点 v 的距离 d,则更新终点 v 的距离值 d;</li><li>检测图中是否有负权边形成了环,遍历图中的所有边,计算 u 至 v 的距离,如果对于 v 存在更小的距离,则说明存在环;</li></ol><p>例如,下面的有向图 G 中包含 5 个顶点和 8 条边。假设源点 为 A。初始化 distSet 所有距离为 INFI,源点 A 为 0。</p><p><img src="https://images0.cnblogs.com/blog/175043/201501/291147026756939.png" alt=""></p><p>由于图中有 5 个顶点,按照步骤 1 需要遍历 4 次,第一次遍历的结果如下。</p><p><img src="https://images0.cnblogs.com/blog/175043/201501/291150096287121.png" alt=""></p><p>第二次遍历的结果如下。</p><p><img src="https://images0.cnblogs.com/blog/175043/201501/291151019728645.png" alt=""></p><p>以此类推可以得出完全遍历的结果。</p><h4 id="本题解法-1"><a href="#本题解法-1" class="headerlink" title="本题解法"></a>本题解法</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">double</span> <span class="title">maxProbability</span><span class="params">(<span class="keyword">int</span> n, <span class="keyword">int</span>[][] edges, <span class="keyword">double</span>[] succProb, <span class="keyword">int</span> start, <span class="keyword">int</span> end)</span> </span>{</span><br><span class="line"> <span class="comment">// 记录结果</span></span><br><span class="line"> <span class="keyword">double</span>[] result = <span class="keyword">new</span> <span class="keyword">double</span>[n];</span><br><span class="line"> <span class="comment">// 起点</span></span><br><span class="line"> result[start] = <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 从start点出发,先更新直接与start点相连的点的概率,然后逐步更新,直到不需要更新为止</span></span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> <span class="comment">// 是否有过变动</span></span><br><span class="line"> <span class="keyword">boolean</span> changed = <span class="keyword">false</span>;</span><br><span class="line"> <span class="comment">// 遍历所有边</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < edges.length; j++) {</span><br><span class="line"> <span class="keyword">int</span>[] edge = edges[j];</span><br><span class="line"> <span class="comment">// 如果从当前点edge[0]出发,到edge[1]的概率,大于之前记录的结果</span></span><br><span class="line"> <span class="keyword">if</span> (result[edge[<span class="number">0</span>]] * succProb[j] > result[edge[<span class="number">1</span>]]) {</span><br><span class="line"> <span class="comment">// 则更新</span></span><br><span class="line"> result[edge[<span class="number">1</span>]] = result[edges[j][<span class="number">0</span>]] * succProb[j];</span><br><span class="line"> changed = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 因为是无向图,所以再反向遍历</span></span><br><span class="line"> <span class="keyword">if</span> (result[edge[<span class="number">1</span>]] * succProb[j] > result[edge[<span class="number">0</span>]]) {</span><br><span class="line"> result[edge[<span class="number">0</span>]] = result[edge[<span class="number">1</span>]] * succProb[j];</span><br><span class="line"> changed = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 一遍未修改则表示图已遍历完成</span></span><br><span class="line"> <span class="keyword">if</span> (!changed) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result[end];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>提交OK,执行用时超过了<code>95%</code>的 java 提交记录。</p><p>其时间假设边的数量为 m ,点的数量为 n ,则时间复杂度为<code>O(mn)</code>。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。本题主要和图的遍历求解最短路径相关,可以用 Dijkstra 或者 Bellman-Ford 算法进行解决。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://static01.imgkr.com/temp/dcfad764025e4ea9ab8d73ef0934efdd.png" alt=""></p><p><img src="https://static01.imgkr.com/temp/003e8c4d5f5544b0bd70dfe7aa63bf60.png" alt=""></p>]]></content>
<summary type="html">
<p>本题主要和图的遍历求解最短路径相关,可以用 Dijkstra 或者 Bellman-Ford 算法进行解决。<br>
</summary>
<category term="力扣" scheme="https://www.death00.top/categories/%E5%8A%9B%E6%89%A3/"/>
<category term="图" scheme="https://www.death00.top/tags/%E5%9B%BE/"/>
<category term="最短路径" scheme="https://www.death00.top/tags/%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84/"/>
<category term="Dijkstra" scheme="https://www.death00.top/tags/Dijkstra/"/>
<category term="Bellman-Ford" scheme="https://www.death00.top/tags/Bellman-Ford/"/>
</entry>
<entry>
<title>力扣 1519——子树中标签相同的节点数</title>
<link href="https://www.death00.top/2020/08/08/%E5%8A%9B%E6%89%A3%201519%E2%80%94%E2%80%94%E5%AD%90%E6%A0%91%E4%B8%AD%E6%A0%87%E7%AD%BE%E7%9B%B8%E5%90%8C%E7%9A%84%E8%8A%82%E7%82%B9%E6%95%B0/"/>
<id>https://www.death00.top/2020/08/08/力扣 1519——子树中标签相同的节点数/</id>
<published>2020-08-08T01:00:00.000Z</published>
<updated>2020-08-26T12:49:55.531Z</updated>
<content type="html"><![CDATA[<p>本题主要在于对树这种数据结构的考察,以及深度优先遍历的使用,优化时可以采取空间换时间的策略。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>给你一棵树(即,一个连通的无环无向图),这棵树由编号从 0 到 n - 1 的 n 个节点组成,且恰好有 n - 1 条 edges 。树的根节点为节点 0 ,树上的每一个节点都有一个标签,也就是字符串 labels 中的一个小写字符(编号为 i 的 节点的标签就是 labels[i] )</p><p>边数组 edges 以 edges[i] = [ai, bi] 的形式给出,该格式表示节点 ai 和 bi 之间存在一条边。</p><p>返回一个大小为 n 的数组,其中 ans[i] 表示第 i 个节点的子树中与节点 i 标签相同的节点数。</p><p>树 T 中的子树是由 T 中的某个节点及其所有后代节点组成的树。</p><p>示例 1:</p><p><img src="https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/07/19/q3e1.jpg" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">输入:n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], labels = "abaedcd"</span><br><span class="line">输出:[2,1,1,1,1,1,1]</span><br><span class="line">解释:节点 0 的标签为 'a' ,以 'a' 为根节点的子树中,节点 2 的标签也是 'a' ,因此答案为 2 。注意树中的每个节点都是这棵子树的一部分。</span><br><span class="line">节点 1 的标签为 'b' ,节点 1 的子树包含节点 1、4 和 5,但是节点 4、5 的标签与节点 1 不同,故而答案为 1(即,该节点本身)。</span><br></pre></td></tr></table></figure><p>示例 2:</p><p><img src="https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/07/19/q3e2.jpg" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">输入:n = 4, edges = [[0,1],[1,2],[0,3]], labels = "bbbb"</span><br><span class="line">输出:[4,2,1,1]</span><br><span class="line">解释:节点 2 的子树中只有节点 2 ,所以答案为 1 。</span><br><span class="line">节点 3 的子树中只有节点 3 ,所以答案为 1 。</span><br><span class="line">节点 1 的子树中包含节点 1 和 2 ,标签都是 'b' ,因此答案为 2 。</span><br><span class="line">节点 0 的子树中包含节点 0、1、2 和 3,标签都是 'b',因此答案为 4 。</span><br></pre></td></tr></table></figure><p>示例 3 :</p><p><img src="https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/07/19/q3e3.jpg" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:n = 5, edges = [[0,1],[0,2],[1,3],[0,4]], labels = "aabab"</span><br><span class="line">输出:[3,2,1,1,1]</span><br></pre></td></tr></table></figure><p>示例 4:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:n = 6, edges = [[0,1],[0,2],[1,3],[3,4],[4,5]], labels = "cbabaa"</span><br><span class="line">输出:[1,2,1,1,2,1]</span><br></pre></td></tr></table></figure><p>示例 5:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:n = 7, edges = [[0,1],[1,2],[2,3],[3,4],[4,5],[5,6]], labels = "aaabaaa"</span><br><span class="line">输出:[6,5,4,1,3,2,1]</span><br></pre></td></tr></table></figure></p><p>提示:</p><ul><li>1 <= n <= 10^5</li><li>edges.length == n - 1</li><li>edges[i].length == 2</li><li>0 <= ai, bi < n</li><li>ai != bi</li><li>labels.length == n</li><li>labels 仅由小写英文字母组成</li></ul><p>原题 url:<a href="https://leetcode-cn.com/problems/number-of-nodes-in-the-sub-tree-with-the-same-label" target="_blank" rel="noopener">https://leetcode-cn.com/problems/number-of-nodes-in-the-sub-tree-with-the-same-label</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="首次尝试"><a href="#首次尝试" class="headerlink" title="首次尝试"></a>首次尝试</h3><p>这道题是要让我们计算:在子树中,和当前节点字符相同的节点个数。</p><p>那么我们就必然需要构建树中各个节点的关系,那么就需要记录父子节点的关系,因为是普通的树,一个节点的子节点可能有多个,因此我用<code>LinkedList<Integer>[] tree</code>这样一个数组进行存储,其中<code>tree[i]</code>代表节点 i 的所有子节点。</p><p>至于求相同节点的个数,我想着可以从根节点 0 开始逐个遍历,先获取其第一层子节点,再根据第一层子节点逐个获取,可以采用广度优先遍历的形式。</p><p>让我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span>[] countSubTrees(<span class="keyword">int</span> n, <span class="keyword">int</span>[][] edges, String labels) {</span><br><span class="line"> <span class="comment">// 构造树</span></span><br><span class="line"> LinkedList<Integer>[] tree = <span class="keyword">new</span> LinkedList[n];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span>[] edge : edges) {</span><br><span class="line"> <span class="comment">// edge[0]的子节点</span></span><br><span class="line"> LinkedList<Integer> child = tree[edge[<span class="number">0</span>]];</span><br><span class="line"> <span class="keyword">if</span> (child == <span class="keyword">null</span>) {</span><br><span class="line"> child = <span class="keyword">new</span> LinkedList<>();</span><br><span class="line"> tree[edge[<span class="number">0</span>]] = child;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 增加子节点</span></span><br><span class="line"> child.add(edge[<span class="number">1</span>]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 结果</span></span><br><span class="line"> <span class="keyword">int</span>[] result = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line"> <span class="comment">// 遍历并计算</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> <span class="comment">// 需要遍历的字符</span></span><br><span class="line"> <span class="keyword">char</span> cur = labels.charAt(i);</span><br><span class="line"> <span class="comment">// 该节点的子树中与该字符相同的节点数</span></span><br><span class="line"> <span class="keyword">int</span> curCount = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 广度优先遍历</span></span><br><span class="line"> LinkedList<Integer> searchList = <span class="keyword">new</span> LinkedList<>();</span><br><span class="line"> searchList.add(i);</span><br><span class="line"> <span class="keyword">while</span>(!searchList.isEmpty()) {</span><br><span class="line"> <span class="keyword">int</span> index = searchList.removeFirst();</span><br><span class="line"> <span class="keyword">if</span> (cur == labels.charAt(index)) {</span><br><span class="line"> curCount++;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 找出该节点的子树</span></span><br><span class="line"> <span class="keyword">if</span> (tree[index] == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> searchList.addAll(tree[index]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> result[i] = curCount;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交之后,发现有<code>错误</code>。错误的情况是:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">输入:</span><br><span class="line">4</span><br><span class="line">[[0,2],[0,3],[1,2]]</span><br><span class="line">"aeed"</span><br><span class="line"></span><br><span class="line">输出:</span><br><span class="line">[1,2,1,1]</span><br><span class="line"></span><br><span class="line">预期:</span><br><span class="line">[1,1,2,1]</span><br></pre></td></tr></table></figure></p><p>根据这样输入,我构造出的树是:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1 0</span><br><span class="line"> \ / \</span><br><span class="line"> 2 3</span><br></pre></td></tr></table></figure></p><p>但根据预期结果反推出来的树是:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> 0</span><br><span class="line"> / \</span><br><span class="line"> 2 3</span><br><span class="line"> /</span><br><span class="line">1</span><br></pre></td></tr></table></figure></p><p>那么输入中最后给出的<code>[1,2]</code>就不是从<code>父节点</code>指向<code>子节点</code>,也就是输入中给出的边关联的节点顺序,是任意的。</p><p>那我们的树究竟该如何构造呢?</p><h3 id="双向记录构造树"><a href="#双向记录构造树" class="headerlink" title="双向记录构造树"></a>双向记录构造树</h3><p>既然我们在构造树的时候,无法直接得出父子关系,那么就将对应两个节点同时记录另一个节点。</p><p>根据题目中给出的条件:<code>树的根节点为节点 0</code>。这样我们在遍历的时候,就从 0 开始,只要 0 关联的节点,一定是 0 的子节点。将这些节点进行标记,这样再递归访问接下来的节点时,如果是标记过的,则说明是父节点,这样就可以明确父子节点关系了。</p><p>至于遍历的时候,因为这次我们是不知道父子节点关系的,所以无法直接采用广度优先遍历,换成<code>深度优先遍历</code>。</p><p>让我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 总节点数</span></span><br><span class="line"> <span class="keyword">int</span> n;</span><br><span class="line"> <span class="comment">// 树</span></span><br><span class="line"> Map<Integer, LinkedList<Integer>> tree;</span><br><span class="line"> <span class="comment">// 字符串</span></span><br><span class="line"> String labels;</span><br><span class="line"> <span class="comment">// 最终结果</span></span><br><span class="line"> <span class="keyword">int</span>[] result;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span>[] countSubTrees(<span class="keyword">int</span> n, <span class="keyword">int</span>[][] edges, String labels) {</span><br><span class="line"> <span class="keyword">this</span>.n = n;</span><br><span class="line"> <span class="keyword">this</span>.labels = labels;</span><br><span class="line"> result = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line"></span><br><span class="line"> LinkedList<Integer> list;</span><br><span class="line"> <span class="comment">// 双向构造树的关系</span></span><br><span class="line"> tree = <span class="keyword">new</span> HashMap<>(n / <span class="number">4</span> * <span class="number">3</span> + <span class="number">1</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span>[] edge : edges) {</span><br><span class="line"> <span class="comment">// 添加映射关系</span></span><br><span class="line"> list = tree.computeIfAbsent(edge[<span class="number">0</span>], k -> <span class="keyword">new</span> LinkedList<>());</span><br><span class="line"> list.add(edge[<span class="number">1</span>]);</span><br><span class="line"> list = tree.computeIfAbsent(edge[<span class="number">1</span>], k -> <span class="keyword">new</span> LinkedList<>());</span><br><span class="line"> list.add(edge[<span class="number">0</span>]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 深度优先搜索</span></span><br><span class="line"> dfs(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span>[] dfs(<span class="keyword">int</span> index) {</span><br><span class="line"> <span class="comment">// 当前子树中,所有字符的个数</span></span><br><span class="line"> <span class="keyword">int</span>[] charArray = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">26</span>];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 开始计算,标志该节点已经计算过</span></span><br><span class="line"> result[index] = <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 获得其关联的节点</span></span><br><span class="line"> List<Integer> nodes = tree.get(index);</span><br><span class="line"> <span class="comment">// 遍历</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> node : nodes) {</span><br><span class="line"> <span class="comment">// 如果该节点已经访问过</span></span><br><span class="line"> <span class="keyword">if</span> (result[node] > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 递归遍历子节点</span></span><br><span class="line"> <span class="keyword">int</span>[] array = dfs(node);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">26</span>; i++) {</span><br><span class="line"> charArray[i] += array[i];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 将当前节点的值计算一下</span></span><br><span class="line"> charArray[labels.charAt(index) - <span class="string">'a'</span>] += <span class="number">1</span>;</span><br><span class="line"> result[index] = charArray[labels.charAt(index) - <span class="string">'a'</span>];</span><br><span class="line"> <span class="keyword">return</span> charArray;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK,执行用时<code>136ms</code>,超过<code>36.71%</code>,内存消耗<code>104.5MB</code>,超过<code>91.38%</code>。</p><p>时间复杂度上,应该是要研究<code>dfs</code>方法中的两个<code>for</code>循环,外层肯定是每个节点都遍历一遍,内层还需要遍历<code>26</code>个英文字母,也就是<code>O(n)</code>。</p><p>空间复杂度上,最大的应该就是存储节点映射关系的<code>tree</code>了,里面实际上就是 2n 个节点(因为每条边对应的两个节点都会互相存一次对方),因此也就是<code>O(n)</code>。</p><p>虽然过了,但执行速度很慢,可以进一步优化。</p><h3 id="用空间换时间"><a href="#用空间换时间" class="headerlink" title="用空间换时间"></a>用空间换时间</h3><p>针对我上面的解法,其中<code>tree</code>我是用的<code>Map</code>,虽然其<code>get</code>方法理论上是<code>O(n)</code>,但毕竟涉及 hash,可以优化成数组。</p><p>至于每次取节点对应的字符所用的<code>charAt</code>方法,具体其实是:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">char</span> <span class="title">charAt</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> ((index < <span class="number">0</span>) || (index >= value.length)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> StringIndexOutOfBoundsException(index);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> value[index];</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>每次都会检查一次 index,其实这完全是可以省略的,因此可以提前构造好每个位置对应的值,也用一个数组存储。</p><p>让我们看看新的代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 总节点数</span></span><br><span class="line"> <span class="keyword">int</span> n;</span><br><span class="line"> <span class="comment">// 树</span></span><br><span class="line"> LinkedList<Integer>[] tree;</span><br><span class="line"> <span class="comment">// 每个节点的值(用数字表示)</span></span><br><span class="line"> <span class="keyword">int</span>[] nodeValueArray;</span><br><span class="line"> <span class="comment">// 最终结果</span></span><br><span class="line"> <span class="keyword">int</span>[] result;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span>[] countSubTrees(<span class="keyword">int</span> n, <span class="keyword">int</span>[][] edges, String labels) {</span><br><span class="line"> <span class="keyword">this</span>.n = n;</span><br><span class="line"> nodeValueArray = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line"> result = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 双向构造树的关系</span></span><br><span class="line"> tree = <span class="keyword">new</span> LinkedList[n];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> tree[i] = <span class="keyword">new</span> LinkedList<>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span>[] edge : edges) {</span><br><span class="line"> <span class="comment">// 添加映射关系</span></span><br><span class="line"> tree[edge[<span class="number">0</span>]].add(edge[<span class="number">1</span>]);</span><br><span class="line"> tree[edge[<span class="number">1</span>]].add(edge[<span class="number">0</span>]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 生成节点的值</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> nodeValueArray[i] = labels.charAt(i) - <span class="string">'a'</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 深度优先搜索</span></span><br><span class="line"> dfs(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span>[] dfs(<span class="keyword">int</span> index) {</span><br><span class="line"> <span class="comment">// 当前子树中,所有字符的个数</span></span><br><span class="line"> <span class="keyword">int</span>[] charArray = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">26</span>];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 开始计算,标志该节点已经计算过</span></span><br><span class="line"> result[index] = <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 获得其关联的节点</span></span><br><span class="line"> List<Integer> nodes = tree[index];</span><br><span class="line"> <span class="comment">// 遍历</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> node : nodes) {</span><br><span class="line"> <span class="comment">// 如果该节点已经访问过</span></span><br><span class="line"> <span class="keyword">if</span> (result[node] > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 递归遍历子节点</span></span><br><span class="line"> <span class="keyword">int</span>[] array = dfs(node);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">26</span>; i++) {</span><br><span class="line"> charArray[i] += array[i];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 将当前节点的值计算一下</span></span><br><span class="line"> charArray[nodeValueArray[index]] += <span class="number">1</span>;</span><br><span class="line"> result[index] = charArray[nodeValueArray[index]];</span><br><span class="line"> <span class="keyword">return</span> charArray;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交之后,执行用时是<code>96ms</code>,内存消耗是<code>402.2MB</code>。看来优化的效果并不明显。</p><h3 id="研究一下目前最优解法"><a href="#研究一下目前最优解法" class="headerlink" title="研究一下目前最优解法"></a>研究一下目前最优解法</h3><p>这个解法真的是巧妙,执行用时<code>20ms</code>,超过了<code>100%</code>,内存消耗<code>76.3MB</code>,超过了<code>100%</code>。</p><p>我在代码中增加了注释,方便大家理解。但这样的写法,研究一下是能够看懂,但让我想估计是永远不可能想出来,可以让大家也一起学习和借鉴:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Next</span> </span>{</span><br><span class="line"></span><br><span class="line"> Next next;</span><br><span class="line"> Node node;</span><br><span class="line"></span><br><span class="line"> Next(Next next, Node node) {</span><br><span class="line"> <span class="keyword">this</span>.next = next;</span><br><span class="line"> <span class="keyword">this</span>.node = node;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Node</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 当前节点的index</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> index;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 当前节点对应的字符值(减去'a')</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">int</span> ci;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 所有关联的节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> Next children;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 该节点的父节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> Node parent;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 子树中和该节点含有相同字符的节点总个数</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">int</span> result;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 是否还在队列中,可以理解为是否已访问过</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">boolean</span> inQueue;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Node</span><span class="params">(<span class="keyword">int</span> index, <span class="keyword">int</span> ci)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.index = index;</span><br><span class="line"> <span class="keyword">this</span>.ci = ci;</span><br><span class="line"> <span class="keyword">this</span>.result = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 从后往前,找到当前节点没有访问过的第一个子节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function">Node <span class="title">popChild</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (; ; ) {</span><br><span class="line"> <span class="comment">// 当前节点的所有关联节点</span></span><br><span class="line"> Next n = <span class="keyword">this</span>.children;</span><br><span class="line"> <span class="comment">// 如果没有,说明子节点都遍历完了</span></span><br><span class="line"> <span class="keyword">if</span> (n == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 从后往前移除关联节点</span></span><br><span class="line"> <span class="keyword">this</span>.children = n.next;</span><br><span class="line"> <span class="comment">// 返回第一个没有访问过的节点</span></span><br><span class="line"> <span class="keyword">if</span> (!n.node.inQueue) {</span><br><span class="line"> <span class="keyword">return</span> n.node;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 访问了该节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function">Node <span class="title">enqueue</span><span class="params">(Node[] cnodes)</span> </span>{</span><br><span class="line"> <span class="comment">// 该节点标记为访问过</span></span><br><span class="line"> <span class="keyword">this</span>.inQueue = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 记录该节点的父节点</span></span><br><span class="line"> <span class="keyword">this</span>.parent = cnodes[ci];</span><br><span class="line"> <span class="comment">// 那么现在该字符值对应的最高节点,就是当前节点。</span></span><br><span class="line"> <span class="comment">// 这样如果之后也遇到相同字符的子节点,就可以为子节点赋值其父节点,也就是上面一行是有效的</span></span><br><span class="line"> cnodes[ci] = <span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 退出该节点</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">dequeue</span><span class="params">(Node[] cnodes, <span class="keyword">int</span>[] res)</span> </span>{</span><br><span class="line"> <span class="comment">// 之后会访问该节点的兄弟节点,因此父节点需要重新设置</span></span><br><span class="line"> cnodes[ci] = <span class="keyword">this</span>.parent;</span><br><span class="line"> <span class="comment">// 设置当前节点的值</span></span><br><span class="line"> res[index] = <span class="keyword">this</span>.result;</span><br><span class="line"> <span class="comment">// 父节点也可以进行累加</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.parent != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">this</span>.parent.result += <span class="keyword">this</span>.result;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">link</span><span class="params">(Node x)</span> </span>{</span><br><span class="line"> <span class="comment">// this节点和x节点,互相绑定</span></span><br><span class="line"> <span class="keyword">this</span>.children = <span class="keyword">new</span> Next(<span class="keyword">this</span>.children, x);</span><br><span class="line"> x.children = <span class="keyword">new</span> Next(x.children, <span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span>[] countSubTrees(<span class="keyword">int</span> n, <span class="keyword">int</span>[][] edges, String labels) {</span><br><span class="line"> <span class="comment">// 构造树</span></span><br><span class="line"> Node[] nodes = <span class="keyword">new</span> Node[n];</span><br><span class="line"> <span class="comment">// 每个节点对应的字符</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> nodes[i] = <span class="keyword">new</span> Node(i, labels.charAt(i) - <span class="string">'a'</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 通过边的关系,将节点互相绑定</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span>[] es : edges) {</span><br><span class="line"> nodes[es[<span class="number">0</span>]].link(nodes[es[<span class="number">1</span>]]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 最终的结果</span></span><br><span class="line"> <span class="keyword">int</span>[] res = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line"> <span class="comment">// 当前访问的节点下标</span></span><br><span class="line"> <span class="keyword">int</span> sz = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 26个小写英文字母对应的节点数组</span></span><br><span class="line"> Node[] cnodes = <span class="keyword">new</span> Node[<span class="number">26</span>];</span><br><span class="line"> <span class="comment">// 下面三行可以合并成这一行:</span></span><br><span class="line"> <span class="comment">// Node node = nodes[sz++] = nodes[0].enqueue(cnodes);</span></span><br><span class="line"> nodes[sz] = nodes[<span class="number">0</span>].enqueue(cnodes);</span><br><span class="line"> <span class="comment">// 当前访问的节点</span></span><br><span class="line"> Node node = nodes[sz];</span><br><span class="line"> <span class="comment">// 因为当前节点已经访问过,自然下标需要+1</span></span><br><span class="line"> sz++;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (; ; ) {</span><br><span class="line"> <span class="comment">// 从后往前,找到当前节点没有访问过的第一个子节点</span></span><br><span class="line"> Node child = node.popChild();</span><br><span class="line"> <span class="comment">// 如果已经全部访问过了</span></span><br><span class="line"> <span class="keyword">if</span> (child == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 开始计算</span></span><br><span class="line"> node.dequeue(cnodes, res);</span><br><span class="line"> <span class="keyword">if</span> (--sz == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 回溯到父节点</span></span><br><span class="line"> node = nodes[sz - <span class="number">1</span>];</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 保证了相邻节点一定是父子节点</span></span><br><span class="line"> node = nodes[sz++] = child.enqueue(cnodes);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。本题主要在于对树这种数据结构的考察,以及深度优先遍历的使用,优化时可以采取空间换时间的策略。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://static01.imgkr.com/temp/dcfad764025e4ea9ab8d73ef0934efdd.png" alt=""></p><p><img src="https://static01.imgkr.com/temp/003e8c4d5f5544b0bd70dfe7aa63bf60.png" alt=""></p>]]></content>
<summary type="html">
<p>本题主要在于对树这种数据结构的考察,以及深度优先遍历的使用,优化时可以采取空间换时间的策略。<br>
</summary>
<category term="力扣" scheme="https://www.death00.top/categories/%E5%8A%9B%E6%89%A3/"/>
<category term="树" scheme="https://www.death00.top/tags/%E6%A0%91/"/>
<category term="深度优先遍历" scheme="https://www.death00.top/tags/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86/"/>
</entry>
<entry>
<title>剑指offer 43——1~n整数中1出现的次数</title>
<link href="https://www.death00.top/2020/06/09/%E5%89%91%E6%8C%87offer%2043%E2%80%94%E2%80%941%EF%BD%9En%E6%95%B4%E6%95%B0%E4%B8%AD1%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0/"/>
<id>https://www.death00.top/2020/06/09/剑指offer 43——1~n整数中1出现的次数/</id>
<published>2020-06-09T01:00:00.000Z</published>
<updated>2020-08-26T12:48:16.286Z</updated>
<content type="html"><![CDATA[<p>本题主要在于找规律,从一个例子开始,总结出其中的规律。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。</p><p>例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。</p><p>示例 1:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:n = 12</span><br><span class="line">输出:5</span><br></pre></td></tr></table></figure></p><p>示例 2:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:n = 13</span><br><span class="line">输出:6</span><br></pre></td></tr></table></figure></p><p>限制:</p><ul><li>1 <= n < 2^31</li></ul><p>原题url:<a href="https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/" target="_blank" rel="noopener">https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="暴力法"><a href="#暴力法" class="headerlink" title="暴力法"></a>暴力法</h3><p>一开始我的想法是计算出从 1 到 n 中,每一个数中包含的1个数,累加在一起,求得结果:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">countDigitOne</span><span class="params">(<span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (n < <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 存储从1到上一个数的总结果,初始是从1到1的总结果</span></span><br><span class="line"> <span class="keyword">int</span> before = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> temp, count;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">2</span>; i <= n; i++) {</span><br><span class="line"> temp = i;</span><br><span class="line"> count = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (temp != <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 计算当前数字的个位数是否等于1</span></span><br><span class="line"> <span class="keyword">if</span> (temp % <span class="number">10</span> == <span class="number">1</span>) {</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"> temp /= <span class="number">10</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> before += count;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> before;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提示我<code>超出时间限制</code>。</p><p>接下来我想到的是,当给我们一个数 x 时,如果我知道 (x / 10) 对应的 1 的个数,那么再加上最高位 1 的个数,就得出了当前数字对应的 1 的个数。</p><p>用一个数组,记录每一个数字对应的1的个数。由此可以写出代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">countDigitOne</span><span class="params">(<span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (n < <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 存储数字原本含有的1的数量</span></span><br><span class="line"> <span class="keyword">int</span>[] countOne = <span class="keyword">new</span> <span class="keyword">int</span>[n + <span class="number">1</span>];</span><br><span class="line"> <span class="comment">// 存储从1到上一个数的总结果,初始是从1到1的总结果</span></span><br><span class="line"> <span class="keyword">int</span> before = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> temp, count;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i <= n; i++) {</span><br><span class="line"> countOne[i] = (i % <span class="number">10</span> == <span class="number">1</span> ? <span class="number">1</span> : <span class="number">0</span>) + countOne[i / <span class="number">10</span>];</span><br><span class="line"> before += countOne[i];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> before;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提示我<code>超出内存限制</code>。</p><p>看来暴力求解不可取,让我们换一种思路。</p><h3 id="找规律"><a href="#找规律" class="headerlink" title="找规律"></a>找规律</h3><p>由上面的暴力法,我们可以得知:</p><ol><li>这道题肯定不能从 1 开始慢慢算出每一个数所对应的 1 的个数,因为这样会超时。</li><li>也不可以借用 n 个额外空间,因为这样会超出内存限制。</li></ol><p>那么正确的思路就应该是,给你一个数 n ,你直接计算出从 1 到 n 的数中,所有 1 的总个数。</p><p>假设给你一个数 3210,你会如何求解呢?</p><p>我们可以把这个数进行拆分:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">3210 = 3000 + 200 + 10 + 0</span><br><span class="line"> = 3 * 1000 + 2 * 100 + 1 * 10 + 0 * 1</span><br></pre></td></tr></table></figure></p><p>似乎没有看出什么规律,那么我们再试着求出每一位,对应的 1 的个数。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">从千位 3 开始,针对 1000 ~ 1999 这 1000 个数字,千位上都是 1,因此这里有 1000 个。</span><br><span class="line">百位 2,针对 100 ~ 199 这 100 个数字,百位上都是 1。一共出现了 3 组,分别是 100 ~ 199、1100 ~ 1199、2100 ~ 2199、3100 ~ 3199,一共 (4 * 100 = 400) 个。</span><br><span class="line">十位 1,针对 10 ~ 19 这 10 个数字,十位上都是 1。一共出现了 32 组,但是再加上 3210 本身十位上也是 1,因此一共有 ( 32 * 10 + 1 = 321) 个。</span><br><span class="line">个位 0,针对 1、11、21、...、101、111、121、...、1001、1011、1021、...、3201,每 10 个数都会出现 1 个,因此一共有 (321 * 1 = 321)个。</span><br><span class="line">一共 2042 个。</span><br></pre></td></tr></table></figure></p><p>我用力扣本身的测试用例进行了校验,结果是一致的。</p><p>我们总结一下上面的过程:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">我们将数字 n,按照位,从低到高进行遍历。</span><br><span class="line">我们假设当前位的数字为 cur,高位为 high,低位为 low,位数为 digit。比如 3210 这个数,针对百位而言,cur 是 2,high 是 3,low 是 10,digit 是 100。</span><br><span class="line">针对 cur,有 3 种情况:</span><br><span class="line">1、大于 1,那么此位 1 的个数就是:(high + 1) * digit</span><br><span class="line">2、等于 1,那么此位 1 的个数就是:high * digit + low + 1</span><br><span class="line">3、小于 1(也就是等于 0 ),那么此位 1 的个数就是:high * digit</span><br></pre></td></tr></table></figure></p><p>接下来我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">countDigitOne</span><span class="params">(<span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (n < <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 高位</span></span><br><span class="line"> <span class="keyword">int</span> temp = n;</span><br><span class="line"> <span class="comment">// 低位</span></span><br><span class="line"> <span class="keyword">int</span> low = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 1出现的总次数</span></span><br><span class="line"> <span class="keyword">int</span> total = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 当前位数,比如个位时为1,十位时为10,百位时为100</span></span><br><span class="line"> <span class="keyword">int</span> pow = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">while</span> (temp != <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 当前位上的数字</span></span><br><span class="line"> <span class="keyword">int</span> num = temp % <span class="number">10</span>;</span><br><span class="line"> <span class="comment">// 剩下的高位</span></span><br><span class="line"> temp = temp / <span class="number">10</span>;</span><br><span class="line"> <span class="comment">// 如果当前位上的数字是0</span></span><br><span class="line"> <span class="keyword">if</span> (num == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 只加上高位对应</span></span><br><span class="line"> total += temp * pow;</span><br><span class="line"> } </span><br><span class="line"> <span class="comment">// 如果当前位上的数字是1</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (num == <span class="number">1</span>) {</span><br><span class="line"> <span class="comment">// 加上高位对应的数字,和低位的所有,再加1(它本身)。</span></span><br><span class="line"> <span class="comment">// 比如10,虽然低位是0,但本身还有1</span></span><br><span class="line"> total += temp * pow + low + <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果当前位上的数字大于1</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 那么高位+1</span></span><br><span class="line"> total += (temp + <span class="number">1</span>) * pow;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 低位</span></span><br><span class="line"> low += num * pow;</span><br><span class="line"> <span class="comment">// 进入下一位</span></span><br><span class="line"> pow = pow * <span class="number">10</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> total;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。</p><p>我们来分析一下复杂度:</p><ul><li>时间复杂度 <code>O(log N)</code> : 循环内的计算操作使用 O(1) 时间,循环次数为数字 n 的位数,即 log 以10为底 n,因此总时间为 <code>O(log N)</code>。</li><li>空间复杂度 <code>O(1)</code> : 只有几个变量使用常数大小的额外空间。</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。本题主要在于找规律,从一个例子开始,总结出其中的规律。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>本题主要在于找规律,从一个例子开始,总结出其中的规律。<br>
</summary>
<category term="剑指offer" scheme="https://www.death00.top/categories/%E5%89%91%E6%8C%87offer/"/>
<category term="找规律" scheme="https://www.death00.top/tags/%E6%89%BE%E8%A7%84%E5%BE%8B/"/>
</entry>
<entry>
<title>剑指offer 38——字符串的排列</title>
<link href="https://www.death00.top/2020/06/07/%E5%89%91%E6%8C%87offer%2038%E2%80%94%E2%80%94%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97/"/>
<id>https://www.death00.top/2020/06/07/剑指offer 38——字符串的排列/</id>
<published>2020-06-07T01:00:00.000Z</published>
<updated>2020-06-07T10:54:23.280Z</updated>
<content type="html"><![CDATA[<p>本题主要在于对回溯的理解,优化时可以结合 java 特性,以及排列的一些知识。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>输入一个字符串,打印出该字符串中字符的所有排列。</p><p>你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。</p><p>示例:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:s = "abc"</span><br><span class="line">输出:["abc","acb","bac","bca","cab","cba"]</span><br></pre></td></tr></table></figure></p><p>限制:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">1 <= s 的长度 <= 8</span><br></pre></td></tr></table></figure></p><p>原题url:<a href="https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof" target="_blank" rel="noopener">https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="回溯"><a href="#回溯" class="headerlink" title="回溯"></a>回溯</h3><p>回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。</p><p>大家在解决经典的<code>八皇后</code>问题时,大多都会采用<code>回溯</code>进行解决。</p><p>本问题其实就是求所有字符的排列组合,针对这种问题,也可以利用<code>回溯</code>进行解决,但要求不能重复,因此需要进行<code>剪枝</code>。</p><p>比如字符串 <code>abc</code> ,如果让我们求所有排列,肯定是:</p><ol><li>先固定第 1 位,从 a 、b 、 c 中选一个,比如 a。</li><li>在以 a 为第 1 位的前提下,固定第 2 位,从 b 、 c 中选一个,比如 b。</li><li>此时第 3 位也没有可以选择的余地了,只剩下 c,这一步就走完了。</li><li>退回第 2 步,依旧在第 2 位,这次选择 c 。</li><li>此时第 3 位也没有可以选择的余地了,只剩下 b,这一步也走完了。</li><li>退回第 1 步。</li></ol><p>从上面,你可以总结出,正常的回溯,就是先走一条路,当结束后,退回上一步继续走,反复执行,直至退无可退,结束流程。</p><p>我们可以发现,最终是没有可以选择的余地,这在程序里可以理解为,运行到下一位时,不能使用之前使用过的数据,因此会涉及到字符交换。</p><p>但因为会进行回溯,所以数字可以在回溯后再换回去,从而不影响下一次的回溯。</p><p>那什么叫<code>剪枝</code>呢?就是要排除一些情况,针对本题,就是要排除重复的情况。</p><p>也就是在同一位置,不能出现两次相同的字符,因为第 2 次出现时,之前肯定已经针对这种情况,所有路线都已经走过了。</p><p>因此可以联想到使用<code>集合</code>,存储当前位置出现过的字符,如果重复,就可以直接跳过。</p><p>接下来我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">char</span>[] array;</span><br><span class="line"> List<String> result = <span class="keyword">new</span> LinkedList<>();</span><br><span class="line"> <span class="keyword">public</span> String[] permutation(String s) {</span><br><span class="line"> array = s.toCharArray();</span><br><span class="line"> <span class="comment">// 回溯</span></span><br><span class="line"> backtrack(<span class="number">0</span>);</span><br><span class="line"> <span class="comment">// 赋值给数组</span></span><br><span class="line"> String[] resultArray = <span class="keyword">new</span> String[result.size()];</span><br><span class="line"> <span class="keyword">int</span> index = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (String str : result) {</span><br><span class="line"> resultArray[index] = str;</span><br><span class="line"> index++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> resultArray;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">backtrack</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="comment">// 如果是最后一个位置,就可以添加进result中</span></span><br><span class="line"> <span class="keyword">if</span> (index == array.length - <span class="number">1</span>) {</span><br><span class="line"> StringBuilder sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">char</span> temp : array) {</span><br><span class="line"> sb.append(temp);</span><br><span class="line"> }</span><br><span class="line"> result.add(sb.toString());</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Set<Character> set = <span class="keyword">new</span> HashSet<>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = index; i < array.length; i++) {</span><br><span class="line"> <span class="comment">// 保证不会重复</span></span><br><span class="line"> <span class="keyword">if</span> (set.contains(array[i])) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> set.add(array[i]);</span><br><span class="line"> <span class="comment">// 交换两者的位置</span></span><br><span class="line"> swap(index, i);</span><br><span class="line"> <span class="comment">// 固定下一个位置,继续寻找</span></span><br><span class="line"> backtrack(index + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 还原两者的位置</span></span><br><span class="line"> swap(i, index);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span> index, <span class="keyword">int</span> newIndex)</span> </span>{</span><br><span class="line"> <span class="keyword">char</span> temp = array[index];</span><br><span class="line"> array[index] = array[newIndex];</span><br><span class="line"> array[newIndex] = temp;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。</p><p>分析一下复杂度:</p><ul><li>时间复杂度 <code>O(N!)</code> : 这个比较好理解,长度为 N 的字符串,需要计算的次数是: <code>N * (N - 1) * (N - 2) * ... * 2 * 1</code>,结果也就是 N! 。</li><li>空间复杂度 <code>O(N^2)</code> : 需要借助的额外空间,也就是那个保证不会重复所使用到的<code>set</code>,它所存储的总量,最差情况下,长度为 N 的字符串中,所有字符各不相同,也就需要 <code>N + (N - 1) + (N - 2) * ... * 2 * 1</code>,结果也就是 N^2。</li></ul><h3 id="java-优化"><a href="#java-优化" class="headerlink" title="java 优化"></a>java 优化</h3><p>针对上面代码中出现的 <code>char[]</code> 转 <code>String</code>,可以使用<code>String.valueOf(char[])</code>方法进行优化,因为该方法,最终会使用<code>System.arrayCopy</code>方法,该方法属于<code>native</code>方法,更加高效。</p><p>至于最终,将 list 转 array 的过程,可以用<code>list.toArray(String[])</code>做写法上的简化,性能上倒并没有什么提升。</p><p>优化后的代码为:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">char</span>[] array;</span><br><span class="line"> List<String> result = <span class="keyword">new</span> LinkedList<>();</span><br><span class="line"> <span class="keyword">public</span> String[] permutation(String s) {</span><br><span class="line"> array = s.toCharArray();</span><br><span class="line"> <span class="comment">// 回溯</span></span><br><span class="line"> backtrack(<span class="number">0</span>);</span><br><span class="line"> <span class="comment">// 赋值给数组</span></span><br><span class="line"> <span class="keyword">return</span> result.toArray(<span class="keyword">new</span> String[result.size()]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">backtrack</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="comment">// 如果是最后一个位置,就可以添加进result中</span></span><br><span class="line"> <span class="keyword">if</span> (index == array.length - <span class="number">1</span>) {</span><br><span class="line"> result.add(String.valueOf(array));</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Set<Character> set = <span class="keyword">new</span> HashSet<>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = index; i < array.length; i++) {</span><br><span class="line"> <span class="comment">// 保证不会重复</span></span><br><span class="line"> <span class="keyword">if</span> (set.contains(array[i])) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> set.add(array[i]);</span><br><span class="line"> <span class="comment">// 交换两者的位置</span></span><br><span class="line"> swap(index, i);</span><br><span class="line"> <span class="comment">// 固定下一个位置,继续寻找</span></span><br><span class="line"> backtrack(index + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 还原两者的位置</span></span><br><span class="line"> swap(i, index);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span> index, <span class="keyword">int</span> newIndex)</span> </span>{</span><br><span class="line"> <span class="keyword">char</span> temp = array[index];</span><br><span class="line"> array[index] = array[newIndex];</span><br><span class="line"> array[newIndex] = temp;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="继续优化"><a href="#继续优化" class="headerlink" title="继续优化"></a>继续优化</h3><p>其实到了,如果想进一步优化的话,可以针对 list 转 array 这里。</p><p>因为我们使用的是 LinkedList,内部存储的 String 对象在物理上是不连续的,在最后遍历时会相对比较耗时。</p><p>如果我们一开始就可以求出所有该字符串所能获得的所有不重复字符串的总个数的话,就可以提前构造一个 array,不需要在最后又遍历一次 list 了。</p><p>那么如何求出有重复字符的所有排列呢?假设是字符串<code>aabbc</code>,其求法为:</p><ol><li>假设先排 a ,一共 5 个位置,选 2 个位置,<code>C(5, 2) = (5 * 4) / (2 * 1) = 10</code>。</li><li>再排 b ,剩下 3 个位置里,选 2 个位置,<code>C(3, 2) = (3 * 2) / (2 * 1) = 3</code>。</li><li>最后排 c ,剩下 1 个位置里,选 1 个位置,<code>C(1, 1) = 1</code>。</li><li>综上,一共有<code>10 * 3 * 1 = 30</code>种排列。</li></ol><p>接下来看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">char</span>[] array;</span><br><span class="line"> String[] result;</span><br><span class="line"> <span class="keyword">int</span> resultIndex = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">public</span> String[] permutation(String s) {</span><br><span class="line"> array = s.toCharArray();</span><br><span class="line"> <span class="comment">// 求出一共有多少种可能</span></span><br><span class="line"> <span class="keyword">int</span> totalCount = calculate();</span><br><span class="line"> result = <span class="keyword">new</span> String[totalCount];</span><br><span class="line"> <span class="comment">// 回溯</span></span><br><span class="line"> backtrack(<span class="number">0</span>);</span><br><span class="line"> <span class="comment">// 赋值给数组</span></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">calculate</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 各字符出现的次数,默认只会出现26个英文字母</span></span><br><span class="line"> <span class="keyword">int</span>[] countArray = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">26</span>];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">char</span> temp : array) {</span><br><span class="line"> countArray[temp - <span class="string">'a'</span>] += <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 统计总次数</span></span><br><span class="line"> <span class="keyword">int</span> length = array.length;</span><br><span class="line"> <span class="keyword">int</span> totalCount = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> count : countArray) {</span><br><span class="line"> <span class="keyword">if</span> (count == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 求排列</span></span><br><span class="line"> totalCount *= cc(length, count);</span><br><span class="line"> length -= count;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> totalCount;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">cc</span><span class="params">(<span class="keyword">int</span> total, <span class="keyword">int</span> count)</span> </span>{</span><br><span class="line"> <span class="comment">// 如果count超过total的一半,则换成 (total - count),因为在排列中,C(5, 4) = C(5, 1)</span></span><br><span class="line"> <span class="keyword">if</span> (count > total / <span class="number">2</span>) {</span><br><span class="line"> count = total - count;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 分别求分子、分母</span></span><br><span class="line"> <span class="keyword">int</span> result = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> result1 = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < count; i++) {</span><br><span class="line"> result *= (total - i);</span><br><span class="line"> result1 *= (count - i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result / result1;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">backtrack</span><span class="params">(<span class="keyword">int</span> index)</span> </span>{</span><br><span class="line"> <span class="comment">// 如果是最后一个位置,就可以添加进result中</span></span><br><span class="line"> <span class="keyword">if</span> (index == array.length - <span class="number">1</span>) {</span><br><span class="line"> result[resultIndex++] = String.valueOf(array);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 默认只会出现26个英文字母</span></span><br><span class="line"> <span class="keyword">boolean</span>[] exists = <span class="keyword">new</span> <span class="keyword">boolean</span>[<span class="number">26</span>];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = index; i < array.length; i++) {</span><br><span class="line"> <span class="comment">// 保证不会重复</span></span><br><span class="line"> <span class="keyword">if</span> (exists[array[i] - <span class="string">'a'</span>]) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> exists[array[i] - <span class="string">'a'</span>] = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 交换两者的位置</span></span><br><span class="line"> swap(index, i);</span><br><span class="line"> <span class="comment">// 固定下一个位置,继续寻找</span></span><br><span class="line"> backtrack(index + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 还原两者的位置</span></span><br><span class="line"> swap(i, index);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span> index, <span class="keyword">int</span> newIndex)</span> </span>{</span><br><span class="line"> <span class="keyword">char</span> temp = array[index];</span><br><span class="line"> array[index] = array[newIndex];</span><br><span class="line"> array[newIndex] = temp;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK,其执行时间最短,因此认为优化是有效的。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。本题主要在于对回溯的理解,优化时可以结合 java 特性,以及排列的一些知识。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>本题主要在于对回溯的理解,优化时可以结合 java 特性,以及排列的一些知识。<br>
</summary>
<category term="剑指offer" scheme="https://www.death00.top/categories/%E5%89%91%E6%8C%87offer/"/>
<category term="排列组合" scheme="https://www.death00.top/tags/%E6%8E%92%E5%88%97%E7%BB%84%E5%90%88/"/>
<category term="回溯" scheme="https://www.death00.top/tags/%E5%9B%9E%E6%BA%AF/"/>
</entry>
<entry>
<title>剑指offer 33——二叉搜索树的后序遍历序列</title>
<link href="https://www.death00.top/2020/05/24/%E5%89%91%E6%8C%87offer%2033%E2%80%94%E2%80%94%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97/"/>
<id>https://www.death00.top/2020/05/24/剑指offer 33——二叉搜索树的后序遍历序列/</id>
<published>2020-05-24T01:00:00.000Z</published>
<updated>2020-05-24T09:42:39.769Z</updated>
<content type="html"><![CDATA[<p>本题主要在于考察对二叉搜索树和后序遍历的理解。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 <code>true</code>,否则返回 <code>false</code>。假设输入的数组的任意两个数字都互不相同。</p><p>参考以下这颗二叉搜索树:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> 5</span><br><span class="line"> / \</span><br><span class="line"> 2 6</span><br><span class="line"> / \</span><br><span class="line">1 3</span><br></pre></td></tr></table></figure></p><p>示例 1:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入: [1,6,3,2,5]</span><br><span class="line">输出: false</span><br></pre></td></tr></table></figure></p><p>示例 2:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入: [1,3,2,6,5]</span><br><span class="line">输出: true</span><br></pre></td></tr></table></figure></p><p>提示:</p><ul><li>数组长度 <= 1000</li></ul><p>原题url:<a href="https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/" target="_blank" rel="noopener">https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><p>首先介绍一些基本概念,方便后续做题。</p><ol><li>后序遍历:[ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。</li><li>二叉搜索树: 左子树中所有节点的值 < 根节点的值;右子树中所有节点的值 > 根节点的值;其左、右子树也分别为二叉搜索树。</li></ol><h3 id="递归分治"><a href="#递归分治" class="headerlink" title="递归分治"></a>递归分治</h3><p>既然本题只提供了后序遍历,那么我们就要在此基础之上下功夫了。</p><p>根据上面的提供的说明,后序遍历是,先左右子树再根节点,那么根是容易判断的,肯定在整个序列的最后。</p><p>而二叉搜索树节点值满足,左子树 < 根 < 右子树,因此我们就可以以根为基础,从后向前遍历,。</p><p>一开始的节点肯定都大于根,因为都在右子树上,一旦出现小于根的节点,说明就进入了左子树,那么之后所有的节点都应该小于根。然后再分别遍历左右子树,直至到叶子节点为止(即无左右子树的节点)。</p><p>按照上面的方法,就需要我们将后序遍历分成左右子树,不断递归遍历检查。</p><p>接下来看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">verifyPostorder</span><span class="params">(<span class="keyword">int</span>[] postorder)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> checkTree(postorder, <span class="number">0</span>, postorder.length - <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">checkTree</span><span class="params">(<span class="keyword">int</span>[] postorder, <span class="keyword">int</span> start, <span class="keyword">int</span> end)</span> </span>{</span><br><span class="line"> <span class="comment">// 如果start >= end,说明已经寻找结束</span></span><br><span class="line"> <span class="keyword">if</span> (start >= end) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 找到根</span></span><br><span class="line"> <span class="keyword">int</span> root = postorder[end];</span><br><span class="line"> <span class="comment">// 左子树开始的下标</span></span><br><span class="line"> <span class="keyword">int</span> leftStart = start;</span><br><span class="line"> <span class="comment">// 左子树结束的下标</span></span><br><span class="line"> <span class="keyword">int</span> leftEnd = leftStart;</span><br><span class="line"> <span class="comment">// 找到第一个大于根节点的值</span></span><br><span class="line"> <span class="keyword">while</span> (leftEnd < end && postorder[leftEnd] < root) {</span><br><span class="line"> leftEnd++;</span><br><span class="line"> }</span><br><span class="line"> leftEnd--;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 右子树开始的下标</span></span><br><span class="line"> <span class="keyword">int</span> rightStart = leftEnd + <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 右子树结束的下标</span></span><br><span class="line"> <span class="keyword">int</span> rightEnd = end - <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 检查右子树是否都大于根节点</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = rightStart; i < end; i++) {</span><br><span class="line"> <span class="keyword">if</span> (postorder[i] > root) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 继续检查左右子树</span></span><br><span class="line"> <span class="keyword">return</span> checkTree(postorder, leftStart, leftEnd) &&</span><br><span class="line"> checkTree(postorder, rightStart, rightEnd);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。</p><p>分析一下复杂度:</p><ul><li>时间复杂度 <code>O(N^2)</code> : 每次调用 checkTree 方法减去一个根节点,因此递归占用 O(N) ;最差情况下(即当树退化为链表),每轮递归都需遍历树所有节点,占用 O(N ^ 2) 。</li><li>空间复杂度 <code>O(N)</code> : 最差情况下(即当树退化为链表),递归深度将达到 N 。</li></ul><h3 id="递增栈"><a href="#递增栈" class="headerlink" title="递增栈"></a>递增栈</h3><p>既然上面分析出时间复杂度为 <code>O(N^2)</code> ,那么是否可以找到一种更高效的方法,只遍历一次序列,就可以解决问题呢?因为这样可以在时间复杂度上进行很大的优化。</p><p>这就需要再进一步结合搜索二叉树和后序遍历的特性了。(这个方法我是在网上看到的,感觉属于一种比较偏门的优化,一般很难像出这种方法)</p><p>在我们从后向前遍历序列时,大致是经历了<code>根、右子树、左子树</code>,而<code>左子树 < 根 < 右子树</code>,那么一开始应该是单调递增的,我们可以将这些节点依次入栈。</p><p>当不满足<code>单调递增</code>调试时,一般是碰到了右子树中某一个左子树节点,或者真正的左子树,这时候可以将栈顶元素出栈,直到碰到比当前节点小的元素,那么将最后的栈顶元素设为<code>根节点</code>。</p><p>此时继续遍历,应该保证所有节点都小于根节点,因为此时已经进入左子树序列了。否则说明该序列不满足搜索二叉树的后序遍历。</p><p>重复以上步骤,如果遍历结束,说明满足搜索二叉树的后序遍历。</p><p>这么说可能比较难懂,直接上代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">verifyPostorder</span><span class="params">(<span class="keyword">int</span>[] postorder)</span> </span>{</span><br><span class="line"> <span class="comment">// 单调递增栈</span></span><br><span class="line"> Stack<Integer> stack = <span class="keyword">new</span> Stack<>();</span><br><span class="line"> <span class="keyword">int</span> root = Integer.MAX_VALUE;</span><br><span class="line"> <span class="comment">// 倒序遍历</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = postorder.length - <span class="number">1</span>; i >= <span class="number">0</span>; i--) {</span><br><span class="line"> <span class="keyword">if</span> (postorder[i] > root) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果当前栈不为空,且当前遍历的节点小于栈顶节点</span></span><br><span class="line"> <span class="keyword">while</span> (!stack.isEmpty() && </span><br><span class="line"> postorder[i] < stack.peek()) {</span><br><span class="line"> <span class="comment">// 栈顶节点压出,且更新根节点</span></span><br><span class="line"> root = stack.pop();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 当前节点入栈</span></span><br><span class="line"> stack.push(postorder[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。</p><p>分析一下复杂度:</p><ul><li>时间复杂度 <code>O(N)</code> : 遍历 postorder 所有节点,各节点均入栈 / 出栈一次,使用 O(N) 时间。</li><li>空间复杂度 <code>O(N)</code> : 最差情况下(即当树退化为链表),单调递增栈 stack 存储所有节点。</li></ul><p>神奇的是,力扣给出的执行结果显示:递归分治方法消耗的时间更短。这点大家也可以研究研究是为什么。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。本题主要在于考察对二叉搜索树和后序遍历的理解,递归分治是容易想出来的方法,但是后面那种单调递增栈确实很难想到,可以作为一种特殊思路进行理解。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>本题主要在于考察对二叉搜索树和后序遍历的理解。<br>
</summary>
<category term="剑指offer" scheme="https://www.death00.top/categories/%E5%89%91%E6%8C%87offer/"/>
<category term="二叉树" scheme="https://www.death00.top/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/"/>
<category term="后序遍历" scheme="https://www.death00.top/tags/%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/"/>
<category term="栈" scheme="https://www.death00.top/tags/%E6%A0%88/"/>
</entry>
<entry>
<title>Java 线程池讲解——针对 IO 密集型任务</title>
<link href="https://www.death00.top/2020/05/20/Java%20%E7%BA%BF%E7%A8%8B%E6%B1%A0%E8%AE%B2%E8%A7%A3%E2%80%94%E2%80%94%E9%92%88%E5%AF%B9%20IO%20%E5%AF%86%E9%9B%86%E5%9E%8B%E4%BB%BB%E5%8A%A1/"/>
<id>https://www.death00.top/2020/05/20/Java 线程池讲解——针对 IO 密集型任务/</id>
<published>2020-05-20T01:00:00.000Z</published>
<updated>2020-05-20T14:00:55.819Z</updated>
<content type="html"><![CDATA[<p>针对 IO 密集型的任务,我们可以针对原本的线程池做一些改造,从而可以提高任务的处理效率。<br><a id="more"></a></p><h2 id="基本"><a href="#基本" class="headerlink" title="基本"></a>基本</h2><p>在<code>阿里巴巴泰山版java开发手册</code>中有这么一条:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,</span><br><span class="line">这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。</span><br></pre></td></tr></table></figure></p><p>那么如果要使用 ThreadPoolExecutor ,那就先来看看构造方法中的所有入参:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">corePoolSize : 核心线程数,当线程池中的线程数量为 corePoolSize 时,即使这些线程处于空闲状态,也不会销毁(除非设置 allowCoreThreadTimeOut)。</span><br><span class="line">maximumPoolSize : 最大线程数,线程池中允许的线程数量的最大值。</span><br><span class="line">keepAliveTime : 线程空闲时间,当线程池中的线程数大于 corePoolSize 时,多余的空闲线程将在销毁之前等待新任务的最长时间。</span><br><span class="line">workQueue : 任务队列</span><br><span class="line">unit : 线程空闲时间的单位。</span><br><span class="line">threadFactory : 线程工厂,线程池创建线程时使用的工厂。</span><br><span class="line">handler : 拒绝策略,因达到线程边界和任务队列满时,针对新任务的处理方法。</span><br></pre></td></tr></table></figure></p><p>这么说可能有些难以理解,你可以结合下图进行参考:<br><img src="https://upload-images.jianshu.io/upload_images/5401975-4493820fd31f3127.png?imageMogr2/auto-orient/strip|imageView2/2/w/833/format/webp" alt=""></p><p>那么由此我们可以知道,当大量任务被放入线程池之后,先是被核心线程执行,多余的会被放进队列里,当队列满了之后才会创建额外的线程进行处理,再多就会采取拒绝策略。</p><p>但这样真的能满足我们的所有需求吗?</p><h2 id="任务的分类"><a href="#任务的分类" class="headerlink" title="任务的分类"></a>任务的分类</h2><p>正常来说,我们可以把需要处理的任务按照消耗资源的不同,分为两种:<code>CPU 密集型</code>和<code>IO 密集型</code>。</p><h2 id="CPU-密集型"><a href="#CPU-密集型" class="headerlink" title="CPU 密集型"></a>CPU 密集型</h2><p>既然名字里带有<code>CPU</code>了,说明其消耗的主要资源就是 CPU 了。</p><p>具体是指那种包含大量运算、在持有的 CPU 分配的时间片上一直在执行任务、几乎不需要依赖或等待其他任何东西。</p><p>这样的任务,在我的理解中,处理起来其实没有多少优化空间,因为处理时几乎没有等待时间,所以一直占有 CPU 进行执行,才是最好的方式。</p><p>唯一能想到优化的地方,就是当单个线程累计较多任务时,其他线程能进行分担,类似<code>fork/join框架</code>的概念。</p><p>设置线程数时,针对单台机器,最好就是有几个 CPU ,就创建几个线程,然后每个线程都在执行这种任务,永不停歇。</p><h2 id="IO-密集型"><a href="#IO-密集型" class="headerlink" title="IO 密集型"></a>IO 密集型</h2><p>和上面一样,既然名字里带有<code>IO</code>了,说明其消耗的主要资源就是 IO 了。</p><p>我们所接触到的 IO ,大致可以分成两种:<code>磁盘 IO</code>和<code>网络 IO</code>。</p><p>磁盘 IO ,大多都是一些针对磁盘的读写操作,最常见的就是文件的读写,假如你的数据库、 Redis 也是在本地的话,那么这个也属于磁盘 IO。</p><p>网络 IO ,这个应该是大家更加熟悉的,我们会遇到各种网络请求,比如 http 请求、远程数据库读写、远程 Redis 读写等等。</p><p>IO 操作的特点就是需要等待,我们请求一些数据,由对方将数据写入<code>缓冲区</code>,在这段时间中,需要读取数据的线程根本无事可做,因此可以把 CPU 时间片让出去,直到<code>缓冲区</code>写满。</p><p>既然这样,IO 密集型任务其实就有很大的优化空间了(毕竟存在等待),那现有的线程池可以很好的满足我们的需求吗?</p><h3 id="线程池的优化"><a href="#线程池的优化" class="headerlink" title="线程池的优化"></a>线程池的优化</h3><p>还记得上面说的, ThreadPoolExecutor 针对多余任务的处理,是先放到等待队列中,当队列塞满后,再创建额外的线程进行处理。</p><p>假设我们的任务基本都是 IO 密集型,我们希望程序可以有更高的吞吐量,可以在更短的时间内处理更多的任务,那么上面的 ThreadPoolExecutor 明显是不满足我们的需求,那该如何解决呢?</p><p>也许再来看看 ThreadPoolExecutor 的 execute 方法,会让我们有一些思路:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(Runnable command)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (command == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line"> <span class="keyword">int</span> c = ctl.get();</span><br><span class="line"> <span class="comment">// 如果当前活跃线程数,小于核心线程数</span></span><br><span class="line"> <span class="keyword">if</span> (workerCountOf(c) < corePoolSize) {</span><br><span class="line"> <span class="comment">// 则优先创建线程</span></span><br><span class="line"> <span class="keyword">if</span> (addWorker(command, <span class="keyword">true</span>))</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> c = ctl.get();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果任务可以成功放入队列中</span></span><br><span class="line"> <span class="keyword">if</span> (isRunning(c) && workQueue.offer(command)) {</span><br><span class="line"> <span class="keyword">int</span> recheck = ctl.get();</span><br><span class="line"> <span class="keyword">if</span> (! isRunning(recheck) && remove(command))</span><br><span class="line"> reject(command);</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (workerCountOf(recheck) == <span class="number">0</span>)</span><br><span class="line"> addWorker(<span class="keyword">null</span>, <span class="keyword">false</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果不可以成功放入队列,则创建线程</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (!addWorker(command, <span class="keyword">false</span>))</span><br><span class="line"> <span class="comment">// 如果无法继续创建线程,则拒绝任务</span></span><br><span class="line"> reject(command);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>针对放入队列的操作,如果队列放入失败,线程池就会选择去创建线程了。因此,我们或许可以尝试自定义线程池,针对 offer 操作,做一些自定义处理。</p><p>也就是将任务放入队列时,先检查线程池的线程数是否小于最大线程数,如果是,则拒绝放入队列,否则,再尝试放入队列中。</p><p>如果你有看过 dubbo 或者 tomcat 的线程池,你会发现他们就有这样的实现方法。</p><p>比如 dubbo 中的 TaskQueue,我们来看看它的 offer 方法:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">offer</span><span class="params">(Runnable runnable)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (executor == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RejectedExecutionException(<span class="string">"The task queue does not have executor!"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> currentPoolThreadSize = executor.getPoolSize();</span><br><span class="line"> <span class="comment">// 如果有空闲等待的线程,则将任务放入队列中,让线程去处理任务</span></span><br><span class="line"> <span class="keyword">if</span> (executor.getSubmittedTaskCount() < currentPoolThreadSize) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.offer(runnable);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果当前线程数小于最大线程数,则返回 false ,让线程池去创建新的线程</span></span><br><span class="line"> <span class="keyword">if</span> (currentPoolThreadSize < executor.getMaximumPoolSize()) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 否则,就将任务放入队列中</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.offer(runnable);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这样就可以让线程池优先新建线程了。需要注意的时,此时的队列因为需要根据线程池中的线程数决定是否放入任务成功,所以需要持有<code>executor</code>对象,这点不要忘记奥。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过本篇文章,主要是让大家重新了解了一下 ThreadPoolExecutor ,并针对高吞吐场景下如何进行局部优化。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>针对 IO 密集型的任务,我们可以针对原本的线程池做一些改造,从而可以提高任务的处理效率。<br>
</summary>
<category term="Java" scheme="https://www.death00.top/tags/Java/"/>
<category term="线程池" scheme="https://www.death00.top/tags/%E7%BA%BF%E7%A8%8B%E6%B1%A0/"/>
</entry>
<entry>
<title>剑指offer 31——栈的压入、弹出序列</title>
<link href="https://www.death00.top/2020/05/18/%E5%89%91%E6%8C%87offer%2031%E2%80%94%E2%80%94%E6%A0%88%E7%9A%84%E5%8E%8B%E5%85%A5%E3%80%81%E5%BC%B9%E5%87%BA%E5%BA%8F%E5%88%97/"/>
<id>https://www.death00.top/2020/05/18/剑指offer 31——栈的压入、弹出序列/</id>
<published>2020-05-18T01:00:00.000Z</published>
<updated>2020-05-19T13:43:41.048Z</updated>
<content type="html"><![CDATA[<p>本题主要在于考察栈的特性,优化时可以考虑自己实现一个栈。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。</p><p>示例 1:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]</span><br><span class="line">输出:true</span><br><span class="line">解释:我们可以按以下顺序执行:</span><br><span class="line">push(1), push(2), push(3), push(4), pop() -> 4,</span><br><span class="line">push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1</span><br></pre></td></tr></table></figure></p><p>示例 2:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]</span><br><span class="line">输出:false</span><br><span class="line">解释:1 不能在 2 之前弹出。</span><br></pre></td></tr></table></figure></p><p>提示:</p><ul><li>0 <= pushed.length == popped.length <= 1000</li><li>0 <= pushed[i], popped[i] < 1000</li><li>pushed 是 popped 的排列。</li></ul><p>原题url:<a href="https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/" target="_blank" rel="noopener">https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="借用现成的Stack类"><a href="#借用现成的Stack类" class="headerlink" title="借用现成的Stack类"></a>借用现成的Stack类</h3><p>既然本题是要判断进栈、出栈序列是否匹配,那么我们可以直接用现成的<code>Stack</code>类进行模拟。</p><p>栈的特性是<code>先入后出</code>,因此入栈的数字,想进行出栈:</p><ol><li>进栈之后立刻出栈</li><li>进栈之后,等待后面进栈的数字全部出栈后,再进行出栈</li></ol><p>那模拟的时候,就可以遍历进栈序列,先让当前数字进栈,然后开始让栈中数字出栈,如果满足出栈序列,则可以继续出栈,直到不能出栈。</p><p>让我们来看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">validateStackSequences</span><span class="params">(<span class="keyword">int</span>[] pushed, <span class="keyword">int</span>[] popped)</span> </span>{</span><br><span class="line"> <span class="comment">// 模拟栈</span></span><br><span class="line"> Stack<Integer> stack = <span class="keyword">new</span> Stack<>();</span><br><span class="line"> <span class="comment">// 出栈下标</span></span><br><span class="line"> <span class="keyword">int</span> popIndex = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 遍历入栈序列</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < pushed.length; i++) {</span><br><span class="line"> <span class="comment">// 先让当前数字入栈</span></span><br><span class="line"> stack.push(pushed[i]);</span><br><span class="line"> <span class="comment">// 遍历栈</span></span><br><span class="line"> <span class="keyword">while</span> (!stack.isEmpty() && stack.peek() == popped[popIndex]) {</span><br><span class="line"> popIndex++;</span><br><span class="line"> stack.pop();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> popIndex == pushed.length;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。</p><p>复杂度分析:<br>时间复杂度 O(N): 其中 N 为列表 pushed 的长度;每个元素最多入栈与出栈一次,即最多共 2N 次出入栈操作。忽略系数后,得出 O(N)。<br>空间复杂度 O(N): 辅助栈 stack 最多同时存储 N 个元素。</p><h3 id="自己实现一个简单的栈结构"><a href="#自己实现一个简单的栈结构" class="headerlink" title="自己实现一个简单的栈结构"></a>自己实现一个简单的栈结构</h3><p>上面我们使用了 Java 中现成的类 Stack,但因为我们这里的场景十分简单,而 Stack 会考虑到扩容、并发修改的情况,所以相应会对性能有一定的影响。因此我们完全可以利用数组设计一个符合本题的、简单的栈结构。</p><p>其实代码和上面类似,但因为是直接使用数组实现的,因此在大数量下,性能上可以快很多。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">validateStackSequences</span><span class="params">(<span class="keyword">int</span>[] pushed, <span class="keyword">int</span>[] popped)</span> </span>{</span><br><span class="line"> <span class="comment">// 利用数组实现一个栈</span></span><br><span class="line"> <span class="keyword">int</span> stack[] = <span class="keyword">new</span> <span class="keyword">int</span>[pushed.length];</span><br><span class="line"> <span class="comment">// 栈中的元素个数</span></span><br><span class="line"> <span class="keyword">int</span> stackSize = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 出栈序列被遍历到的下标</span></span><br><span class="line"> <span class="keyword">int</span> poppedIndex = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 遍历入栈序列</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < pushed.length; i++) {</span><br><span class="line"> stack[stackSize] = pushed[i];</span><br><span class="line"> stackSize++;</span><br><span class="line"> <span class="comment">// 栈内有数据,并且栈顶元素等于当前出栈序列中的数字</span></span><br><span class="line"> <span class="keyword">while</span> (stackSize != <span class="number">0</span> && stack[stackSize - <span class="number">1</span>] == popped[poppedIndex]) {</span><br><span class="line"> <span class="comment">// 出栈</span></span><br><span class="line"> stackSize--;</span><br><span class="line"> <span class="comment">// 继续比较下一个</span></span><br><span class="line"> poppedIndex++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> poppedIndex == popped.length;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>提交OK,复杂度和上面完全相同。</p><p>当然,这道题的难度还有可以提高的地方,原题中有<code>假设压入栈的所有数字均不相等</code>,如果允许数字重复的话,你可以想到要怎么解决吗?</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。本题主要在于考察栈的特性,优化时可以考虑自己实现一个栈。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>本题主要在于考察栈的特性,优化时可以考虑自己实现一个栈。<br>
</summary>
<category term="剑指offer" scheme="https://www.death00.top/categories/%E5%89%91%E6%8C%87offer/"/>
<category term="栈" scheme="https://www.death00.top/tags/%E6%A0%88/"/>
</entry>
<entry>
<title>剑指offer 16——数值的整数次方</title>
<link href="https://www.death00.top/2020/05/13/%E5%89%91%E6%8C%87offer%2016%E2%80%94%E2%80%94%E6%95%B0%E5%80%BC%E7%9A%84%E6%95%B4%E6%95%B0%E6%AC%A1%E6%96%B9/"/>
<id>https://www.death00.top/2020/05/13/剑指offer 16——数值的整数次方/</id>
<published>2020-05-13T01:00:00.000Z</published>
<updated>2020-05-14T13:55:30.006Z</updated>
<content type="html"><![CDATA[<p>这道题可以利用二进制,就可以快速解决了。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。</p><p>示例 1:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入: 2.00000, 10</span><br><span class="line">输出: 1024.00000</span><br></pre></td></tr></table></figure></p><p>示例 2:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入: 2.10000, 3</span><br><span class="line">输出: 9.26100</span><br></pre></td></tr></table></figure></p><p>示例 3:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">输入: 2.00000, -2</span><br><span class="line">输出: 0.25000</span><br><span class="line">解释: 2-2 = 1/2^2 = 1/4 = 0.25</span><br></pre></td></tr></table></figure></p><p>说明:</p><ul><li>-100.0 < x < 100.0</li><li>n 是 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1] 。</li></ul><p>原题url:<a href="https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/" target="_blank" rel="noopener">https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><p>这道题,如果你是用正常计算的话,提交之后会发现报超时,因此,肯定需要寻找捷径的。</p><p>因为不能使用库函数,而且上面普通方法也是会超时的,那么问题的关键就是在如何快速计算。</p><p>而如果想快的,最好的办法就是可以利用曾经计算的结果,避免重复计算。</p><p>我一开始的想法是,比如计算 2^6 ,从数学上来说,等同于计算 4^3。但如果要用这种逻辑的话,就必须要求传入参数 n 是 2^w(其中 w 是正整数),否则计算逻辑会比较复杂。因此放弃该方案。</p><h3 id="二进制"><a href="#二进制" class="headerlink" title="二进制"></a>二进制</h3><p>重点依旧是放在<code>利用曾经计算的结果,避免重复计算</code>上,那么理想情况也就是计算 x^n 后,之后希望直接计算 x^2n,而<code>x^2n = x^n * x^n = x^(n + n)</code>。</p><p>从上面的讨论可以看出,计算幂,可以转换成将指数进行合理的加法拆分。所谓<code>合理</code>,就是后一个是前一个的 2 倍,这样的话,就自然联想到要对指数从<code>十进制</code>转为<code>二进制</code>。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">7 = (111) = 1 * 2^2 + 1 * 2^1 + 1 * 2^0</span><br><span class="line">9 = (1001) = 1 * 2^3 + 0 * 2^2 + 0 * 2^1 + 1 * 2^0</span><br></pre></td></tr></table></figure><p>当然,上面是从大到小累加,实际计算时肯定是从小到大进行累加的。</p><p>说到二进制,肯定少不了位运算,那么计算每一位二进制上的值,有什么快速的方法呢?</p><p>有的,利用<code>n & 1</code>,求出最低位的值(0或者1),然后<code>n >> 1</code>,右移,相当于移除最低位,不停循环,也就能计算出二进制上每一位的值了。</p><p>接下来看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">double</span> <span class="title">myPow</span><span class="params">(<span class="keyword">double</span> x, <span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (x == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 此处用long,是防止n是Integer.MIN_VALUE时,取反后直接就超过了Integer.MAX_VALUE</span></span><br><span class="line"> <span class="keyword">long</span> b = n;</span><br><span class="line"> <span class="keyword">double</span> res = <span class="number">1.0</span>;</span><br><span class="line"> <span class="keyword">if</span>(b < <span class="number">0</span>) {</span><br><span class="line"> x = <span class="number">1</span> / x;</span><br><span class="line"> b = -b;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(b > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> ((b & <span class="number">1</span>) == <span class="number">1</span>) {</span><br><span class="line"> res *= x;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 底数扩大</span></span><br><span class="line"> x *= x;</span><br><span class="line"> <span class="comment">// 指数右移</span></span><br><span class="line"> b >>= <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题利用二进制,就可以快速解决了。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>这道题可以利用二进制,就可以快速解决了。<br>
</summary>
<category term="剑指offer" scheme="https://www.death00.top/categories/%E5%89%91%E6%8C%87offer/"/>
<category term="二进制" scheme="https://www.death00.top/tags/%E4%BA%8C%E8%BF%9B%E5%88%B6/"/>
</entry>
<entry>
<title>剑指offer 14——剪绳子</title>
<link href="https://www.death00.top/2020/05/10/%E5%89%91%E6%8C%87offer%2014%E2%80%94%E2%80%94%E5%89%AA%E7%BB%B3%E5%AD%90/"/>
<id>https://www.death00.top/2020/05/10/剑指offer 14——剪绳子/</id>
<published>2020-05-10T01:00:00.000Z</published>
<updated>2020-05-10T02:46:32.577Z</updated>
<content type="html"><![CDATA[<p>这道题的一般解法是动态规划,优化时可以尝试找规律。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 <code>k[0],k[1]...k[m]</code> 。请问 <code>k[0]*k[1]*...*k[m]</code> 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。</p><p>示例 1:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">输入: 2</span><br><span class="line">输出: 1</span><br><span class="line">解释: 2 = 1 + 1, 1 × 1 = 1</span><br></pre></td></tr></table></figure></p><p>示例 2:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">输入: 10</span><br><span class="line">输出: 36</span><br><span class="line">解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36</span><br></pre></td></tr></table></figure></p><p>提示:</p><ul><li>2 <= n <= 58</li></ul><p>原题url:<a href="https://leetcode-cn.com/problems/jian-sheng-zi-lcof" target="_blank" rel="noopener">https://leetcode-cn.com/problems/jian-sheng-zi-lcof</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="动态规划-DP"><a href="#动态规划-DP" class="headerlink" title="动态规划 DP"></a>动态规划 DP</h3><p>我们想想,本题要求是计算 n 被分成 m 份后,相乘最大的结果,这比较明显可以看出是需要求一定要求下的最优解,那么如果能求出局部最优解的话,也能求出方便求出最终最优解。讲白了,就是一个个试,但需要保证需要将所有情况都计算过,且不要重复计算。</p><p>那这就要利用动态规划的思想了,从初始情况开始,一步步递推。假设绳子长度为 x ,其最大乘积为 f(x),则有:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">f(2) = 1; (1 * 1)</span><br><span class="line">f(3) = 2; (1 * 2)</span><br><span class="line">f(4) = 4; (2 * 2)</span><br><span class="line">f(5) = 6; (3 * 2)</span><br><span class="line">f(6) = 9; (3 * 3)</span><br><span class="line">f(7) = 12; (3 * 2 * 2 = 3 * f(4))</span><br><span class="line">f(8) = 18; (3 * 3 * 2 = f(6) * 2 = 3 * f(5))</span><br></pre></td></tr></table></figure></p><p>自己先试着写出初始的情况,然后从中找出规律:</p><ol><li>长度1、2、3,并没有继续分隔的必要,其作为整体,直接参与计算应该就是最大的数字了。</li><li>长度4,分隔成2、2是比较合理的。</li><li>当长度越长,被分隔成的数量越多时,其实可以想象成将其中多段合并成1段,最后都是可以当做分隔成2段来计算的。</li></ol><p>因此,根据上面总结出来的规律,我们应该是需要从小开始计算,并将中间结果保留,因此可以用一个数组进行存储。</p><p>我们来看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">cuttingRope</span><span class="params">(<span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (n == <span class="number">2</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 记录计算结果,第2位代表长度为2的绳子,其最大乘积</span></span><br><span class="line"> <span class="keyword">int</span>[] result = <span class="keyword">new</span> <span class="keyword">int</span>[n + <span class="number">1</span>];</span><br><span class="line"> result[<span class="number">1</span>] = <span class="number">1</span>;</span><br><span class="line"> result[<span class="number">2</span>] = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">3</span>; i <= n; i++) {</span><br><span class="line"> <span class="comment">// 默认初始值就是剪成两段:1 和 i-1,所以最大乘积是 i-1</span></span><br><span class="line"> result[i] = i - <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">1</span>; j <= i / <span class="number">2</span>; j++) {</span><br><span class="line"> <span class="keyword">int</span> x = Math.max(result[i - j], i - j);</span><br><span class="line"> <span class="keyword">int</span> y = Math.max(result[j], j);</span><br><span class="line"> result[i] = Math.max(x * y, result[i]);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result[n];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。</p><h3 id="数学推导的极致优化"><a href="#数学推导的极致优化" class="headerlink" title="数学推导的极致优化"></a>数学推导的极致优化</h3><p>这个解法,我也是看了别人的解析才知道的,通过代码提交发现结论确实是正确的,但其中的推导过程我也没有看懂,看看原文:</p><p><img src="https://imgkr.cn-bj.ufileos.com/f299c9b1-62ef-40d2-8536-3dc7d5fc4d92.png" alt=""><br><img src="https://imgkr.cn-bj.ufileos.com/166cc4c8-3b1b-444d-bd6c-bb9a9e053599.png" alt=""></p><p>接下来我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">cuttingRope</span><span class="params">(<span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (n == <span class="number">2</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (n == <span class="number">3</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 可以被3分成几段</span></span><br><span class="line"> <span class="keyword">int</span> count = n / <span class="number">3</span>;</span><br><span class="line"> <span class="comment">// 剩余的数字</span></span><br><span class="line"> <span class="keyword">int</span> remain = n % <span class="number">3</span>;</span><br><span class="line"> <span class="comment">// 如果没有剩余的</span></span><br><span class="line"> <span class="keyword">if</span> (remain == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 直接计算当前的值</span></span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">int</span>) Math.pow(<span class="number">3</span>, count);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果剩1,则和原本的一个3,重新拆分成2和2,因为2 * 2 > 3 * 1</span></span><br><span class="line"> <span class="keyword">if</span> (remain == <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">int</span>) Math.pow(<span class="number">3</span>, count - <span class="number">1</span>) * <span class="number">2</span> * <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果剩2,则正常乘</span></span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">int</span>) Math.pow(<span class="number">3</span>, count) * <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题的一般解法是动态规划,优化时可以尝试找规律,数学推导出其中当然是最快的,但这需要一定的功底。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>这道题的一般解法是动态规划,优化时可以尝试找规律。<br>
</summary>
<category term="剑指offer" scheme="https://www.death00.top/categories/%E5%89%91%E6%8C%87offer/"/>
<category term="动态规划" scheme="https://www.death00.top/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
</entry>
<entry>
<title>剑指offer 13——机器人的运动范围</title>
<link href="https://www.death00.top/2020/05/08/%E5%89%91%E6%8C%87offer%2013%E2%80%94%E2%80%94%E6%9C%BA%E5%99%A8%E4%BA%BA%E7%9A%84%E8%BF%90%E5%8A%A8%E8%8C%83%E5%9B%B4/"/>
<id>https://www.death00.top/2020/05/08/剑指offer 13——机器人的运动范围/</id>
<published>2020-05-08T01:00:00.000Z</published>
<updated>2020-05-10T02:45:54.102Z</updated>
<content type="html"><![CDATA[<p>这道题本质还是搜索,因此可以使用深度优先搜索和广度优先搜索进行解决。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>地上有一个m行n列的方格,从坐标 <code>[0,0]</code> 到坐标 <code>[m-1,n-1]</code> 。一个机器人从坐标 <code>[0, 0]</code> 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">示例 1:</span><br><span class="line"></span><br><span class="line">输入:m = 2, n = 3, k = 1</span><br><span class="line">输出:3</span><br><span class="line">示例 2:</span><br><span class="line"></span><br><span class="line">输入:m = 3, n = 1, k = 0</span><br><span class="line">输出:1</span><br></pre></td></tr></table></figure><p>提示:</p><ul><li>1 <= n,m <= 100</li><li>0 <= k <= 20</li></ul><p>原题url:<a href="https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/" target="_blank" rel="noopener">https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="深度优先搜索"><a href="#深度优先搜索" class="headerlink" title="深度优先搜索"></a>深度优先搜索</h3><p>从一个点出发,遍历完所有点,为了保证不会重复遍历,因此我们可以借助一个二维矩阵记录已经遍历过的点。而用深度优先搜索遍历的话,一般都是使用<code>递归</code>的。</p><p>需要注意的是,虽然机器人可以上下左右移动,但因为是从<code>[0, 0]</code>开始的,所以可以想象成根节点往子节点或兄弟节点的遍历方式,深度优先搜索就是先遍历子节点,子节点遍历完成后,在遍历兄弟节点。</p><p>终止条件应该有:</p><ol><li>坐标越界,也就是 <code>x >= m</code> 或者 <code>y >= n</code> 。</li><li>该点已经访问过,既然访问过,自然不用重新计算。</li><li>坐标数字之和大于 <code>k</code> </li></ol><p>求数字各数位之和,最简单的方法应该就是 <code>摸除% + 整除/</code> 就可以了。</p><p>我们来看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">movingCount</span><span class="params">(<span class="keyword">int</span> m, <span class="keyword">int</span> n, <span class="keyword">int</span> k)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> result = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> max = m > n ? m : n;</span><br><span class="line"> <span class="comment">// key为数字,value为该数字各位之和</span></span><br><span class="line"> Map<Integer, Integer> numMap = <span class="keyword">new</span> HashMap<>(max * <span class="number">4</span> / <span class="number">3</span> + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 记录已经访问过的节点</span></span><br><span class="line"> <span class="keyword">boolean</span>[][] visited = <span class="keyword">new</span> <span class="keyword">boolean</span>[m][n];</span><br><span class="line"> <span class="comment">// 从(0, 0)开始移动</span></span><br><span class="line"> <span class="keyword">return</span> move(<span class="number">0</span>, <span class="number">0</span>, m, n, k, numMap, visited);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">move</span><span class="params">(<span class="keyword">int</span> x, <span class="keyword">int</span> y, <span class="keyword">int</span> m, <span class="keyword">int</span> n, <span class="keyword">int</span> k, Map<Integer, Integer> numMap, <span class="keyword">boolean</span>[][] visited)</span> </span>{</span><br><span class="line"> <span class="comment">// 是否越界</span></span><br><span class="line"> <span class="keyword">if</span> (x >= m || y >= n) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果该节点已经访问过</span></span><br><span class="line"> <span class="keyword">if</span> (visited[x][y] == <span class="keyword">true</span>) {</span><br><span class="line"> <span class="comment">// 说明该方格所代表的次数已经被计算过,因此返回0</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 标记该节点已经访问过</span></span><br><span class="line"> visited[x][y] = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 计算</span></span><br><span class="line"> <span class="keyword">int</span> xSum = getNumSum(x, numMap);</span><br><span class="line"> <span class="keyword">int</span> ySum = getNumSum(y, numMap);</span><br><span class="line"> <span class="keyword">if</span> (xSum + ySum > k) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 尝试向下、向右</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span> + move(x + <span class="number">1</span>, y, m, n, k, numMap, visited) + move(x, y + <span class="number">1</span>, m, n, k, numMap, visited);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getNumSum</span><span class="params">(<span class="keyword">int</span> num, Map<Integer, Integer> numMap)</span> </span>{</span><br><span class="line"> Integer sum = numMap.get(num);</span><br><span class="line"> <span class="keyword">if</span> (sum != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> key = num;</span><br><span class="line"> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (num != <span class="number">0</span>) {</span><br><span class="line"> sum += num % <span class="number">10</span>;</span><br><span class="line"> num = num / <span class="number">10</span>;</span><br><span class="line"> }</span><br><span class="line"> numMap.put(key, sum);</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。我们来看看这种方法的复杂度:</p><ul><li>时间复杂度 <code>O(MN)</code> : 最差情况下,机器人遍历矩阵所有单元格,此时时间复杂度为 O(MN)。</li><li>空间复杂度 <code>O(MN)</code> : visited 矩阵的大小就是 mn,因此使用了 O(MN) 的额外空间。</li></ul><h3 id="广度优先搜索"><a href="#广度优先搜索" class="headerlink" title="广度优先搜索"></a>广度优先搜索</h3><p>广度优先搜索,也就是从根节点出发,先遍历兄弟节点,再遍历子节点。一般我们都需要借助一个队列存储已经即将要遍历的节点,因为队列的特性是先进先出,因此当父节点遍历完成后,会依序遍历所有该父节点的所有子节点(这些节点都是兄弟),再遍历下一层的子节点。</p><p>(PS:现在想想,如果用栈存储已经遍历过的节点,也是可以的,只是访问节点的方式并没有什么规律可言。)</p><p>针对该机器人的运动,也是从 <code>[0, 0]</code> 出发,向下向右移动,层层递进。</p><p>接下来我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">movingCount</span><span class="params">(<span class="keyword">int</span> m, <span class="keyword">int</span> n, <span class="keyword">int</span> k)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> result = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> max = m > n ? m : n;</span><br><span class="line"> <span class="comment">// key为数字,value为该数字各位之和</span></span><br><span class="line"> Map<Integer, Integer> numMap = <span class="keyword">new</span> HashMap<>(max * <span class="number">4</span> / <span class="number">3</span> + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 记录已经访问过的节点</span></span><br><span class="line"> <span class="keyword">boolean</span>[][] visited = <span class="keyword">new</span> <span class="keyword">boolean</span>[m][n];</span><br><span class="line"> <span class="comment">// 记录还未访问结束的点</span></span><br><span class="line"> Queue<Coordinate> queue = <span class="keyword">new</span> LinkedList<>();</span><br><span class="line"> <span class="comment">// 从(0, 0)开始移动</span></span><br><span class="line"> queue.offer(<span class="keyword">new</span> Coordinate(<span class="number">0</span>, <span class="number">0</span>));</span><br><span class="line"> <span class="keyword">while</span> (!queue.isEmpty()) {</span><br><span class="line"> <span class="comment">// 获取队首元素</span></span><br><span class="line"> Coordinate coordinate = queue.poll();</span><br><span class="line"> <span class="comment">// 判断当前坐标是否有效</span></span><br><span class="line"> <span class="keyword">if</span> (coordinate.x >= m || coordinate.y >= n) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 判断当前左边是否已经访问过</span></span><br><span class="line"> <span class="keyword">if</span> (visited[coordinate.x][coordinate.y]) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 标记当前坐标已经访问过</span></span><br><span class="line"> visited[coordinate.x][coordinate.y] = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 判断当前坐标是否有效</span></span><br><span class="line"> <span class="keyword">int</span> xSum = getNumSum(coordinate.x, numMap);</span><br><span class="line"> <span class="keyword">int</span> ySum = getNumSum(coordinate.y, numMap);</span><br><span class="line"> <span class="keyword">if</span> (xSum + ySum > k) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果有效</span></span><br><span class="line"> result++;</span><br><span class="line"> <span class="comment">// 将下边一格节点放入队列中</span></span><br><span class="line"> queue.add(<span class="keyword">new</span> Coordinate(coordinate.x + <span class="number">1</span>, coordinate.y));</span><br><span class="line"> <span class="comment">// 将右边一格节点放入队列中</span></span><br><span class="line"> queue.add(<span class="keyword">new</span> Coordinate(coordinate.x, coordinate.y + <span class="number">1</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getNumSum</span><span class="params">(<span class="keyword">int</span> num, Map<Integer, Integer> numMap)</span> </span>{</span><br><span class="line"> Integer sum = numMap.get(num);</span><br><span class="line"> <span class="keyword">if</span> (sum != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> key = num;</span><br><span class="line"> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (num != <span class="number">0</span>) {</span><br><span class="line"> sum += num % <span class="number">10</span>;</span><br><span class="line"> num = num / <span class="number">10</span>;</span><br><span class="line"> }</span><br><span class="line"> numMap.put(key, sum);</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Coordinate</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> x;</span><br><span class="line"> <span class="keyword">int</span> y;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Coordinate</span><span class="params">(<span class="keyword">int</span> x, <span class="keyword">int</span> y)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.x = x;</span><br><span class="line"> <span class="keyword">this</span>.y = y;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。我们来看看这种方法的复杂度:</p><ul><li>时间复杂度 <code>O(MN)</code> : 最差情况下,机器人遍历矩阵所有单元格,此时时间复杂度为 O(MN)。</li><li>空间复杂度 <code>O(MN)</code> : visited 矩阵的大小就是 mn,因此使用了 O(MN) 的额外空间。</li></ul><p>既然上下两种方法时间复杂度相同,但比较奇怪的在于,我在力扣上提交时,上面一种方法所花费的时间是 <code>1ms</code> ,但这种方法所花费的时间是 <code>7ms</code> 。既然复杂度的计算是忽略了系数、低阶、常数,但我认为上下两种方法即使不忽略,应该也是一样的。 <code>如果你有新的看法,欢迎指教。</code></p><h3 id="求坐标之和"><a href="#求坐标之和" class="headerlink" title="求坐标之和"></a>求坐标之和</h3><p>求坐标之和的方法,我写的是比较简单的一种,但如果你好好想想,就可以发现有更简单的方法。</p><p>正常情况下,随着数字逐渐变大,数字各位之和应该也是逐渐上升的,但唯一的特殊情况就是 <code>进位</code> ,比如 19 变到 20 ,各数位之和从 10 变为 2,其实细心点就可以发现,十位数虽然加1,但个位数减9,因此总体减8。所以可以总结出:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">假设现在的数字为x,其各位数之和为Sum(x),那么下一个Sum(x + 1)为:</span><br><span class="line">Sum(x + 1) = ((x + 1) % 10 == 0) ? (Sum(x) - 8) : (Sum(x) + 1)</span><br></pre></td></tr></table></figure><p>那么上面的代码还有可以优化的地方,这个就留给大家去完成了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题本质还是搜索,因此可以使用深度优先搜索和广度优先搜索进行解决。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>这道题本质还是搜索,因此可以使用深度优先搜索和广度优先搜索进行解决。<br>
</summary>
<category term="剑指offer" scheme="https://www.death00.top/categories/%E5%89%91%E6%8C%87offer/"/>
<category term="深度优先搜索" scheme="https://www.death00.top/tags/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2/"/>
<category term="广度优先搜索" scheme="https://www.death00.top/tags/%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2/"/>
</entry>
<entry>
<title>力扣289——生命游戏</title>
<link href="https://www.death00.top/2020/05/06/%E5%8A%9B%E6%89%A3289%E2%80%94%E2%80%94%E7%94%9F%E5%91%BD%E6%B8%B8%E6%88%8F/"/>
<id>https://www.death00.top/2020/05/06/力扣289——生命游戏/</id>
<published>2020-05-06T13:00:00.000Z</published>
<updated>2020-05-06T13:39:54.669Z</updated>
<content type="html"><![CDATA[<p>这道题还是比较简单的,只要针对数组进行正常遍历即可。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>根据 百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。</p><p>给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态:1 即为活细胞(live),或 0 即为死细胞(dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:</p><ol><li>如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;</li><li>如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;</li><li>如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;</li><li>如果死细胞周围正好有三个活细胞,则该位置死细胞复活;</li></ol><p>根据当前状态,写一个函数来计算面板上所有细胞的下一个(一次更新后的)状态。下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。</p><p>示例:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">输入: </span><br><span class="line">[</span><br><span class="line"> [0,1,0],</span><br><span class="line"> [0,0,1],</span><br><span class="line"> [1,1,1],</span><br><span class="line"> [0,0,0]</span><br><span class="line">]</span><br><span class="line">输出:</span><br><span class="line">[</span><br><span class="line"> [0,0,0],</span><br><span class="line"> [1,0,1],</span><br><span class="line"> [0,1,1],</span><br><span class="line"> [0,1,0]</span><br><span class="line">]</span><br></pre></td></tr></table></figure></p><p>进阶:</p><ul><li>你可以使用原地算法解决本题吗?请注意,面板上所有格子需要同时被更新:你不能先更新某些格子,然后使用它们的更新后的值再更新其他格子。</li><li>本题中,我们使用二维数组来表示面板。原则上,面板是无限的,但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题?</li></ul><p>原题url:<a href="https://leetcode-cn.com/problems/game-of-life/" target="_blank" rel="noopener">https://leetcode-cn.com/problems/game-of-life/</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="复制-遍历"><a href="#复制-遍历" class="headerlink" title="复制 + 遍历"></a>复制 + 遍历</h3><p>因为<code>细胞的出生和死亡是同时发生的</code>,所以我们在更新时,只能根据上一个状态进行判定。</p><p>因此,正常思路应该就是复制一个一模一样的数组,然后遍历这个复制的数组,进行条件判断,修改原数组。</p><p>我们来看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">gameOfLife</span><span class="params">(<span class="keyword">int</span>[][] board)</span> </span>{</span><br><span class="line"> <span class="comment">// 先复制一份</span></span><br><span class="line"> <span class="keyword">int</span>[][] copyBoard = <span class="keyword">new</span> <span class="keyword">int</span>[board.length][];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < board.length; i++) {</span><br><span class="line"> copyBoard[i] = <span class="keyword">new</span> <span class="keyword">int</span>[board[i].length];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < board[i].length; j++) {</span><br><span class="line"> copyBoard[i][j] = board[i][j];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 遍历copyBoard,更新board</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < copyBoard.length; i++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < copyBoard[i].length; j++) {</span><br><span class="line"> <span class="comment">// 求出当前细胞周围8个位置的活细胞个数</span></span><br><span class="line"> <span class="keyword">int</span> aliveCount = -copyBoard[i][j];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> row = -<span class="number">1</span>; row <= <span class="number">1</span> && i + row <= copyBoard.length - <span class="number">1</span>; row++) {</span><br><span class="line"> <span class="keyword">if</span> (i + row < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> col = -<span class="number">1</span>; col <= <span class="number">1</span> && j + col <= copyBoard[i].length - <span class="number">1</span>; col++) {</span><br><span class="line"> <span class="keyword">if</span> (j + col < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> aliveCount += copyBoard[i + row][j + col];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 规则1、3</span></span><br><span class="line"> <span class="keyword">if</span> (aliveCount < <span class="number">2</span> || aliveCount > <span class="number">3</span>) {</span><br><span class="line"> <span class="comment">// 该位置细胞死亡</span></span><br><span class="line"> board[i][j] = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 规则2、4</span></span><br><span class="line"> <span class="keyword">if</span> ((copyBoard[i][j] == <span class="number">1</span> && aliveCount >= <span class="number">2</span> && aliveCount <= <span class="number">3</span>) || (copyBoard[i][j] == <span class="number">0</span> && aliveCount == <span class="number">3</span>)) {</span><br><span class="line"> <span class="comment">// 该位置细胞存活</span></span><br><span class="line"> board[i][j] = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK。</p><h3 id="原地算法"><a href="#原地算法" class="headerlink" title="原地算法"></a>原地算法</h3><p>因为题目中给出了:<code>用原地算法解决本题</code>,所以我们是否可以仅使用原数组进行解决呢?</p><p>肯定是可以的,我们只需要将所有可能的情况都考虑好即可。</p><p>原本只有0(死亡)、1(存活)两种状态,现在因为涉及到当前时间和下一次时间,因此我们增加两种状态:</p><ul><li>-1:代表这个细胞过去是活的现在死了</li><li>2:代表这个细胞过去是死的现在活了</li></ul><p>所以我们在进行遍历的时候,需要增加这两种状态的判断,并直接修改原始数组。看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">gameOfLife</span><span class="params">(<span class="keyword">int</span>[][] board)</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span>[] neighbors = {<span class="number">0</span>, <span class="number">1</span>, -<span class="number">1</span>};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> rows = board.length;</span><br><span class="line"> <span class="keyword">int</span> cols = board[<span class="number">0</span>].length;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 遍历面板每一个格子里的细胞</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> row = <span class="number">0</span>; row < rows; row++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> col = <span class="number">0</span>; col < cols; col++) {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 对于每一个细胞统计其八个相邻位置里的活细胞数量</span></span><br><span class="line"> <span class="keyword">int</span> liveNeighbors = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">3</span>; i++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < <span class="number">3</span>; j++) {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!(neighbors[i] == <span class="number">0</span> && neighbors[j] == <span class="number">0</span>)) {</span><br><span class="line"> <span class="comment">// 相邻位置的坐标</span></span><br><span class="line"> <span class="keyword">int</span> r = (row + neighbors[i]);</span><br><span class="line"> <span class="keyword">int</span> c = (col + neighbors[j]);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 查看相邻的细胞是否是活细胞</span></span><br><span class="line"> <span class="keyword">if</span> ((r < rows && r >= <span class="number">0</span>) && (c < cols && c >= <span class="number">0</span>) && (Math.abs(board[r][c]) == <span class="number">1</span>)) {</span><br><span class="line"> liveNeighbors += <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 规则 1 或规则 3 </span></span><br><span class="line"> <span class="keyword">if</span> ((board[row][col] == <span class="number">1</span>) && (liveNeighbors < <span class="number">2</span> || liveNeighbors > <span class="number">3</span>)) {</span><br><span class="line"> <span class="comment">// -1 代表这个细胞过去是活的现在死了</span></span><br><span class="line"> board[row][col] = -<span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 规则 4</span></span><br><span class="line"> <span class="keyword">if</span> (board[row][col] == <span class="number">0</span> && liveNeighbors == <span class="number">3</span>) {</span><br><span class="line"> <span class="comment">// 2 代表这个细胞过去是死的现在活了</span></span><br><span class="line"> board[row][col] = <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 遍历 board 得到一次更新后的状态</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> row = <span class="number">0</span>; row < rows; row++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> col = <span class="number">0</span>; col < cols; col++) {</span><br><span class="line"> <span class="keyword">if</span> (board[row][col] > <span class="number">0</span>) {</span><br><span class="line"> board[row][col] = <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> board[row][col] = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK,相较于上面的方法,这种解法的空间消耗更少。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题主要是针对数组的处理,外加一些状态的定义即可。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>这道题还是比较简单的,只要针对数组进行正常遍历即可。<br>
</summary>
<category term="力扣" scheme="https://www.death00.top/categories/%E5%8A%9B%E6%89%A3/"/>
<category term="找规律" scheme="https://www.death00.top/tags/%E6%89%BE%E8%A7%84%E5%BE%8B/"/>
<category term="数组" scheme="https://www.death00.top/tags/%E6%95%B0%E7%BB%84/"/>
</entry>
<entry>
<title>马拉车算法</title>
<link href="https://www.death00.top/2020/03/06/%E9%A9%AC%E6%8B%89%E8%BD%A6%E7%AE%97%E6%B3%95/"/>
<id>https://www.death00.top/2020/03/06/马拉车算法/</id>
<published>2020-03-06T02:00:00.000Z</published>
<updated>2020-03-07T06:13:59.757Z</updated>
<content type="html"><![CDATA[<p>针对最长回文子串相关的问题,马拉车算法应该是比较通用的解法,今天我们就来具体看看这个算法。<br><a id="more"></a></p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><blockquote><p>马拉车算法(Manacher‘s Algorithm)是用来查找一个字符串的最长回文子串的线性方法,由一个叫 Manacher 的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性。</p></blockquote><p>这个算法最厉害的地方是在于能够在<code>线性时间</code>内解决问题。一般我们解决最长回文子串,不可避免都要进行回溯之类的操作,那么时间复杂度一定是大于线性的。</p><p>而马拉车算法的主要思路是维护一个跟原字符串 str 长度一样的数组 lens,lens[i] 表示以 str[i] 为中点的回串其中一边的长度。</p><p>在这里,有的人把中点算进去,有的人记录两边的长度,其实都是一样的。我在这里是只记录一边的长度,不包括中点。比如<code>CDCDE</code>:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">str: [C, D, C, D, E]</span><br><span class="line">lens: [0, 1, 1, 0, 0]</span><br></pre></td></tr></table></figure></p><p>那么 lens 里最大的自然就对应最长回串的中点了。所以这个算法的核心就是如何快速计算 lens。</p><h2 id="具体思路"><a href="#具体思路" class="headerlink" title="具体思路"></a>具体思路</h2><h3 id="预处理"><a href="#预处理" class="headerlink" title="预处理"></a>预处理</h3><p>回文有奇偶长度两种情况,通过补充间隔符可以将这两种情况化简为奇数长度。</p><p>比如:</p><blockquote><p><code>ABA</code>补充为<code>^#A#B#A#$</code>,中点还是 B。<br><code>ABBA</code>补充为<code>^#A#B#B#A#$</code>,中点为 #,最后可以去掉。</p></blockquote><p>针对间隔符,首先要确保在字符串中不会出现,这里我是确保字符串中不会出现<code>^、#、$</code>。</p><p>原字符串中每一个字符都会被<code>#</code>包围,这样就确保现在的字符串长度一定是奇数。</p><p>至于在开头增加<code>^</code>,在结尾增加<code>$</code>,这样是为了确保从任意一个位置开始检查回文时,一定会遇到不一样的时候,从而退出循环。而且也方便我们计算原字符的下标,直接除以2即可。</p><p>写法是:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">String str = <span class="string">"cbcbccde"</span>;</span><br><span class="line"><span class="keyword">char</span>[] T = <span class="keyword">new</span> <span class="keyword">char</span>[str.length() * <span class="number">2</span> + <span class="number">3</span>];</span><br><span class="line">T[<span class="number">0</span>] = <span class="string">'^'</span>;</span><br><span class="line">T[<span class="number">1</span>] = <span class="string">'#'</span>;</span><br><span class="line">T[T.length - <span class="number">1</span>] = <span class="string">'$'</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < str.length(); i++) {</span><br><span class="line"> <span class="keyword">char</span> charStr = str.charAt(i);</span><br><span class="line"> T[<span class="number">2</span> * i + <span class="number">2</span>] = charStr;</span><br><span class="line"> T[<span class="number">2</span> * i + <span class="number">3</span>] = <span class="string">'#'</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3 id="计算长度数组"><a href="#计算长度数组" class="headerlink" title="计算长度数组"></a>计算长度数组</h3><p>这就是算法的关键了,它充分利用了回文串的对称性。</p><p>我们用 C 表示回文串的中心,用 R 表示回文串的右边半径。所以 R = C + P[ i ] 。C 和 R 所对应的回文串是当前循环中 R 最靠右的回文串。用 i_mirror 表示当前需要求的第 i 个字符关于 C 对应的下标。</p><p>让我们考虑求 P [ i ] 的时候:</p><p><img src="https://pic1.zhimg.com/80/v2-11f96d39d9648b7c146e49cdceb0854c_720w.jpg" alt=""></p><p>我们可以利用回文串 C 的对称性。i 关于 C 的对称点是 i_mirror ,P [ i_mirror ] = 3,所以 P [ i ] 也等于 3 。</p><p>但需要考虑特殊情况:</p><h4 id="P-i-mirror-i-gt-R"><a href="#P-i-mirror-i-gt-R" class="headerlink" title="P [ i_mirror ] + i >= R"></a>P [ i_mirror ] + i >= R</h4><p>如下图:</p><p><img src="https://pic2.zhimg.com/80/v2-70833cbd20c51a98257b5bf3a8c53985_720w.jpg" alt=""></p><p>当我们要求 P [ i ] 的时候,P [ mirror ] = 7,而此时 P [ i ] 并不等于 7 ,为什么呢,因为我们从 i 开始往后数 7 个,等于 22 ,已经超过了最右的 R ,此时不能利用对称性了,但我们一定可以扩展到 R 的,所以 P [ i ] 至少等于 R - i = 20 - 15 = 5,会不会更大呢,我们只需要比较 T [ R+1 ] 和 T [ R+1 ]关于 i 的对称点就行了,就像中心扩展法一样一个个扩展。</p><h4 id="i-mirror-P-i-mirror-0"><a href="#i-mirror-P-i-mirror-0" class="headerlink" title="i_mirror - P [ i_mirror ] == 0"></a>i_mirror - P [ i_mirror ] == 0</h4><p>如下图:</p><p><img src="https://pic4.zhimg.com/80/v2-8eb77a3735fb23e67a51e320a47e636b_720w.jpg" alt=""></p><p>此时P [ i_mirror ] = 1,但是 P [ i ] 赋值成 1 是不正确的,出现这种情况的原因是 P [ i_mirror ] 在扩展的时候首先是 “#” == “#” ,之后遇到了 “^”和另一个字符比较,也就是到了边界,才终止循环的。而 P [ i ] 并没有遇到边界,所以我们可以继续通过中心扩展法一步一步向两边扩展就行了。</p><h4 id="C-和-R-的更新"><a href="#C-和-R-的更新" class="headerlink" title="C 和 R 的更新"></a>C 和 R 的更新</h4><p>既然知道如何计算长度数组了,那最关键的 C 和 R 到底什么时候需要更新呢?</p><p><code>i + P [ i ] > R</code>时,也就是当求出的 P [ i ] 的右边界大于当前的 R 时,我们就需要更新 C 和 R 为当前的回文串了。因为我们必须保证 i 在 R 里面,所以一旦有更右边的 R 就要更新 R。</p><h3 id="最终写法"><a href="#最终写法" class="headerlink" title="最终写法"></a>最终写法</h3><p>假设我们要写一个方法,传入参数是原字符串<code>s</code>,返回值是各个字符对应的最长回文子串长度数组,那么具体方法就是:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">int</span>[] calSubstrings(String s) {</span><br><span class="line"> <span class="keyword">if</span> (s.length() == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 存放新的内容</span></span><br><span class="line"> <span class="keyword">char</span>[] content = <span class="keyword">new</span> <span class="keyword">char</span>[s.length() * <span class="number">2</span> + <span class="number">3</span>];</span><br><span class="line"> <span class="comment">// 开头用^</span></span><br><span class="line"> content[<span class="number">0</span>] = <span class="string">'^'</span>;</span><br><span class="line"> <span class="comment">// s中的每一个字符要用#包围</span></span><br><span class="line"> content[<span class="number">1</span>] = <span class="string">'#'</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < s.length(); i++) {</span><br><span class="line"> content[i * <span class="number">2</span> + <span class="number">2</span>] = s.charAt(i);</span><br><span class="line"> content[i * <span class="number">2</span> + <span class="number">3</span>] = <span class="string">'#'</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 结尾用$</span></span><br><span class="line"> content[content.length - <span class="number">1</span>] = <span class="string">'$'</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 当前的回文串中心下标</span></span><br><span class="line"> <span class="keyword">int</span> center = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 当前的回文串右边界</span></span><br><span class="line"> <span class="keyword">int</span> right = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 存储以每一个位置为中心,所能获得的最长回文子串的长度</span></span><br><span class="line"> <span class="keyword">int</span>[] maxLength = <span class="keyword">new</span> <span class="keyword">int</span>[content.length];</span><br><span class="line"> <span class="comment">// 首尾两个字符没有必要计算</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> index = <span class="number">1</span>; index < content.length - <span class="number">1</span>; index++) {</span><br><span class="line"> <span class="comment">// 如果当前求解的位置,在右边界以内</span></span><br><span class="line"> <span class="keyword">if</span> (index < right) {</span><br><span class="line"> <span class="comment">// 则其最长回文子串的长度,至少为:</span></span><br><span class="line"> maxLength[index] = Math.min(</span><br><span class="line"> <span class="comment">// 对称center的位置上的</span></span><br><span class="line"> maxLength[center * <span class="number">2</span> - index],</span><br><span class="line"> <span class="comment">// 或者当前位置到右边界的距离</span></span><br><span class="line"> right - index</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 正常求解,向左右扩展</span></span><br><span class="line"> <span class="keyword">while</span> (content[index + (maxLength[index] + <span class="number">1</span>)] ==</span><br><span class="line"> content[index - (maxLength[index] + <span class="number">1</span>)]) {</span><br><span class="line"> maxLength[index]++;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果当前index对应的右边界,比现有的right大</span></span><br><span class="line"> <span class="keyword">if</span> (index + maxLength[index] > right) {</span><br><span class="line"> <span class="comment">// 更新右边界和中心</span></span><br><span class="line"> right = index + maxLength[index];</span><br><span class="line"> center = index;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 最终的结果</span></span><br><span class="line"> <span class="keyword">int</span>[] result = <span class="keyword">new</span> <span class="keyword">int</span>[s.length()];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < s.length(); i++) {</span><br><span class="line"> result[i] = maxLength[i * <span class="number">2</span> + <span class="number">2</span>];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是我关于马拉车算法的理解,用来解决最长回文子串的问题,简直就是一把利器。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>针对最长回文子串相关的问题,马拉车算法应该是比较通用的解法,今天我们就来具体看看这个算法。<br>
</summary>
<category term="马拉车算法" scheme="https://www.death00.top/tags/%E9%A9%AC%E6%8B%89%E8%BD%A6%E7%AE%97%E6%B3%95/"/>
<category term="最长回文子串" scheme="https://www.death00.top/tags/%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2/"/>
</entry>
<entry>
<title>设计模式——单例模式</title>
<link href="https://www.death00.top/2020/03/05/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E2%80%94%E2%80%94%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/"/>
<id>https://www.death00.top/2020/03/05/设计模式——单例模式/</id>
<published>2020-03-05T02:00:00.000Z</published>
<updated>2020-03-05T14:18:42.549Z</updated>
<content type="html"><![CDATA[<p>关于单例模式,这是面试时最容易遇到的问题。当时以为很简单的内容,深挖一下,也可以关联出类加载、序列化等知识。<br><a id="more"></a></p><h2 id="饿汉式"><a href="#饿汉式" class="headerlink" title="饿汉式"></a>饿汉式</h2><p>我们先来看看基本的饿汉式写法:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Hungry</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Hungry instance = <span class="keyword">new</span> Hungry();</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">Hungry</span><span class="params">()</span> </span>{}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Hungry <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>优点:写法简答,不需要考虑多线程等问题。</p><p>缺点:如果该实例从未被用到的话,相当于资源浪费。</p><h3 id="static-代码块"><a href="#static-代码块" class="headerlink" title="static 代码块"></a>static 代码块</h3><p>我们也可以用 static 代码块的方式,实现饿汉式:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Hungry</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Hungry instance;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> {</span><br><span class="line"> instance = <span class="keyword">new</span> Hungry();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">Hungry</span><span class="params">()</span> </span>{}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Hungry <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这就是利用了 static 代码块的功能:<code>它是随着类的加载而执行,只执行一次,并优先于主函数。</code></p><h2 id="懒汉式"><a href="#懒汉式" class="headerlink" title="懒汉式"></a>懒汉式</h2><p>我们先来看看基本的懒汉式写法:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Lazy</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> Lazy instance;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">Lazy</span><span class="params">()</span></span>{}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Lazy <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (instance == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">synchronized</span> (Lazy.class) {</span><br><span class="line"> <span class="keyword">if</span> (instance == <span class="keyword">null</span>) {</span><br><span class="line"> instance = <span class="keyword">new</span> Lazy();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里就涉及到了很多知识点,让我们一一讲解。</p><h3 id="volatile"><a href="#volatile" class="headerlink" title="volatile"></a>volatile</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">这里使用 volatile,主要是为了禁止指令重排序。</span><br><span class="line"></span><br><span class="line">主要就是针对 instance = new Lazy(); 这1行命令,在 JVM 中至少对应3条指令:</span><br><span class="line">1. 给 instance 分配内存空间。</span><br><span class="line">2. 调用 Lazy 的构造方法等来初始化 instance。</span><br><span class="line">3. 将 instance 对象指向分配的内存空间(执行完这一步,instance 就不是 null 了)。</span><br><span class="line"></span><br><span class="line">这里需要注意,JVM 会对指令进行优化排序,就是第 2 步与第 3 步的顺序是不一定的,可能是 1-2-3 ,也可能是 1-3-2 。</span><br><span class="line"></span><br><span class="line">如果是后者,可能1个线程执行完 1-3 之后,另一个线程进入了</span><br></pre></td></tr></table></figure><p>以上这一段想必就是大家平常看到的解释了,原本我对此也是深信不疑的,但是因为本地一直无法复现,因此让我产生了怀疑。</p><p>查阅资料后,可能是和以下两点有关。</p><h4 id="Intel-64-IA-32架构下的内存访问重排序"><a href="#Intel-64-IA-32架构下的内存访问重排序" class="headerlink" title="Intel 64/IA-32架构下的内存访问重排序"></a>Intel 64/IA-32架构下的内存访问重排序</h4><p>指令重排发生在处理器平台,对于Java来说是看不到的,因为Jvm基于线程栈,所有的读写都对应了 store 操作,而Intel 64/IA-32架构下处理器不需要LoadLoad、LoadStore、StoreStore屏障,因此不会发生需要这三种屏障的重排序。所以,store 操作之间是不会重排序的。</p><h4 id="JMM"><a href="#JMM" class="headerlink" title="JMM"></a>JMM</h4><p>JMM 抽象地将内存分为主内存和本地内存,各个线程有各自的本地内存。</p><p>如果2个线程在执行<code>Lazy.getInstance()</code>方法,<code>instance</code>作为 static 修改的变量,处于主内存中,两个线程会各自复制<code>instance</code>到本地内存中,当线程1执行<code>instance = new Lazy();</code>方法,除非全部结束,否则不会将本地内存中的<code>instance</code>写回主内存中。</p><p>以上也可能是我想错了,但欢迎大家一起探讨。</p><h3 id="double-check"><a href="#double-check" class="headerlink" title="double-check"></a>double-check</h3><p>为什么要有双重检查呢?<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">第二个 if 判定:是为了保证当有两个线程同时通过了第一个 if 判定,一个线程获取到锁,生成了 Lazy 的一个实例,然后第二个线程获取到锁,如果没有第二个 if 判断,那么此时会再次生成生成 Lazy 的一个实例。</span><br><span class="line">第一个 if 判定:是为了保证多线程同时执行,如果没有第一个 if 判断,所有线程都会串行执行,效率低下。</span><br></pre></td></tr></table></figure></p><h3 id="静态内部类"><a href="#静态内部类" class="headerlink" title="静态内部类"></a>静态内部类</h3><p>也可以利用静态内部类来实现:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Lazy</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">Lazy</span><span class="params">()</span> </span>{}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">InnerLazy</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Lazy INSTANCE = <span class="keyword">new</span> Lazy();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Lazy <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> InnerLazy.INSTANCE;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>为什么这样能实现懒加载呢?</p><p>因为只有当调用<code>InnerLazy.INSTANCE</code>时,才会对 InnnerLazy 类进行初始化,然后才会调用 Lazy 的构造方法,这也是由<code>类加载机制</code>保证的:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">遇到 new 、getstatic、putstatic 或者 invokestatic 这 4 条字节码指令时,如果没有对类进行初始化,则需要先触发其初始化。</span><br><span class="line">这4个指令对应的 Java 场景是:使用 new 新建一个 Java 对象,访问或者设置一个类的静态字段,访问一个类的静态方法的时候。</span><br></pre></td></tr></table></figure></p><h3 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h3><p>以上方法的优缺点:</p><p>优点:使用的时候才会进行初始化,拥有更好的资源优化。</p><p>缺点:</p><ol><li>除去最后一种<code>静态内部类</code>之外,写法都比较繁琐。</li><li>如果使用反射或者反序列化,依旧可以强制生成新的实例。</li></ol><p>针对第2点,我们可以举例子来说明一下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Lazy</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> String name;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">Lazy</span><span class="params">()</span> </span>{</span><br><span class="line"> name = String.valueOf(System.currentTimeMillis());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">InnerLazy</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Lazy INSTANCE = <span class="keyword">new</span> Lazy();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Lazy <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> InnerLazy.INSTANCE;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">print</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Lazy print : "</span> + name);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IllegalAccessException, InstantiationException, IOException, ClassNotFoundException </span>{</span><br><span class="line"> Lazy instance1 = Lazy.getInstance();</span><br><span class="line"> instance1.print();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 反射</span></span><br><span class="line"> Lazy instance3 = Lazy.class.newInstance();</span><br><span class="line"> instance3.print();</span><br><span class="line"> System.out.println(instance1 == instance3);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 反序列化</span></span><br><span class="line"> ObjectOutputStream oos = <span class="keyword">new</span> ObjectOutputStream(<span class="keyword">new</span> FileOutputStream(<span class="string">"file"</span>));</span><br><span class="line"> oos.writeObject(instance1);</span><br><span class="line"> oos.close();</span><br><span class="line"> ObjectInputStream ois = <span class="keyword">new</span> ObjectInputStream(<span class="keyword">new</span> FileInputStream(<span class="string">"file"</span>));</span><br><span class="line"> Lazy instance2 = (Lazy) ois.readObject();</span><br><span class="line"> instance2.print();</span><br><span class="line"> System.out.println(instance1 == instance2);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>输出结果为:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Lazy print : 1583410057762</span><br><span class="line">Lazy print : 1583410057768</span><br><span class="line">false</span><br><span class="line">Lazy print : 1583410057762</span><br><span class="line">false</span><br></pre></td></tr></table></figure></p><p>说明反射和反序列化,都会破坏以上写法的单例特征。那该如何解决呢?</p><ol><li>针对反射,解决起来比较简单,可以在构造方法中判断一下 InnerLazy.INSTANCE ,如果不为 null ,则抛出异常。</li><li>针对反序列化,可以实现接口 Serializable ,重写 readResolve 方法,返回单例对象 InnerLazy.INSTANCE。</li></ol><p>看看修改后的代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> singleton;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Lazy</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> String name;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">Lazy</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (InnerLazy.INSTANCE != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"can not be invoked"</span>);</span><br><span class="line"> }</span><br><span class="line"> name = String.valueOf(System.currentTimeMillis());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">InnerLazy</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Lazy INSTANCE = <span class="keyword">new</span> Lazy();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Lazy <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> InnerLazy.INSTANCE;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">print</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Lazy print : "</span> + name);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> Object <span class="title">readResolve</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> InnerLazy.INSTANCE;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IllegalAccessException, InstantiationException, IOException, ClassNotFoundException </span>{</span><br><span class="line"> Lazy instance1 = Lazy.getInstance();</span><br><span class="line"> instance1.print();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 反序列化</span></span><br><span class="line"> ObjectOutputStream oos = <span class="keyword">new</span> ObjectOutputStream(<span class="keyword">new</span> FileOutputStream(<span class="string">"file"</span>));</span><br><span class="line"> oos.writeObject(instance1);</span><br><span class="line"> oos.close();</span><br><span class="line"> ObjectInputStream ois = <span class="keyword">new</span> ObjectInputStream(<span class="keyword">new</span> FileInputStream(<span class="string">"file"</span>));</span><br><span class="line"> Lazy instance2 = (Lazy) ois.readObject();</span><br><span class="line"> instance2.print();</span><br><span class="line"> System.out.println(instance1 == instance2);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 反射</span></span><br><span class="line"> Lazy instance3 = Lazy.class.newInstance();</span><br><span class="line"> instance3.print();</span><br><span class="line"> System.out.println(instance1 == instance3);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>运行结果为:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Lazy print : 1583409803987</span><br><span class="line">Lazy print : 1583409803987</span><br><span class="line">true</span><br><span class="line">Exception in thread "main" java.lang.RuntimeException: can not be invoked</span><br><span class="line"> at singleton.Lazy.<init>(Lazy.java:11)</span><br><span class="line"> at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)</span><br><span class="line"> at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)</span><br><span class="line"> at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)</span><br><span class="line"> at java.lang.reflect.Constructor.newInstance(Constructor.java:423)</span><br><span class="line"> at java.lang.Class.newInstance(Class.java:442)</span><br><span class="line"> at singleton.Lazy.main(Lazy.java:46)</span><br></pre></td></tr></table></figure></p><h2 id="枚举类"><a href="#枚举类" class="headerlink" title="枚举类"></a>枚举类</h2><p>针对上面的缺点,我们也可以用 enum 解决。来看看写法:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> singleton;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.databind.ObjectMapper;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> Singleton {</span><br><span class="line"></span><br><span class="line"> INSTANCE;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">Singleton</span><span class="params">()</span> </span>{</span><br><span class="line"> name = String.valueOf(System.currentTimeMillis());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">print</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"Lazy print : "</span> + name);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IllegalAccessException, InstantiationException, IOException </span>{</span><br><span class="line"> Singleton instance1 = Singleton.INSTANCE;</span><br><span class="line"> instance1.print();</span><br><span class="line"> <span class="comment">// 反序列化</span></span><br><span class="line"> ObjectMapper objectMapper = <span class="keyword">new</span> ObjectMapper();</span><br><span class="line"> String content = objectMapper.writeValueAsString(instance1);</span><br><span class="line"> Singleton instance3 = objectMapper.readValue(content, Singleton.class);</span><br><span class="line"> System.out.println(instance1 == instance3);</span><br><span class="line"> instance3.print();</span><br><span class="line"> <span class="comment">// 反射</span></span><br><span class="line"> Singleton instance2 = Singleton.class.newInstance();</span><br><span class="line"> System.out.println(instance1 == instance2);</span><br><span class="line"> instance2.print();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>运行结果为:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Lazy print : 1583409004276</span><br><span class="line">true</span><br><span class="line">Lazy print : 1583409004276</span><br><span class="line">Exception in thread "main" java.lang.InstantiationException: singleton.Singleton</span><br><span class="line"> at java.lang.Class.newInstance(Class.java:427)</span><br><span class="line"> at singleton.Singleton.main(Singleton.java:31)</span><br><span class="line">Caused by: java.lang.NoSuchMethodException: singleton.Singleton.<init>()</span><br><span class="line"> at java.lang.Class.getConstructor0(Class.java:3082)</span><br><span class="line"> at java.lang.Class.newInstance(Class.java:412)</span><br><span class="line"> ... 1 more</span><br></pre></td></tr></table></figure></p><p>首先,枚举是不能被反射生成实例的,这也就解决了<code>反射破坏单例</code>的问题。</p><p>其次,在序列化枚举类型时,只会存储枚举类的引用和枚举常量的名称。随后的反序列化的过程中,这些信息被用来在运行时环境中查找存在的枚举类型对象,这也就解决了<code>序列化破坏单例</code>的问题。</p><p>但需要注意:<code>这种方法属于饿汉模式</code>,所以有浪费资源的隐患,但如果你的单例对象并不占用资源,没有状态变量,那么这种方式就很适合你。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是我关于单例模式的一些理解,简单的问题,也可以关联出并发、类加载、序列化等重要知识。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>关于单例模式,这是面试时最容易遇到的问题。当时以为很简单的内容,深挖一下,也可以关联出类加载、序列化等知识。<br>
</summary>
<category term="设计模式" scheme="https://www.death00.top/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<category term="单例模式" scheme="https://www.death00.top/tags/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/"/>
</entry>
<entry>
<title>力扣621——任务调度器</title>
<link href="https://www.death00.top/2020/02/21/%E5%8A%9B%E6%89%A3621%E2%80%94%E2%80%94%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%99%A8/"/>
<id>https://www.death00.top/2020/02/21/力扣621——任务调度器/</id>
<published>2020-02-21T02:00:00.000Z</published>
<updated>2020-02-22T03:39:38.670Z</updated>
<content type="html"><![CDATA[<p>这道题主要是找规律,优化的时候可以采用贪心算法的思想。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>给定一个用字符数组表示的 CPU 需要执行的任务列表。其中包含使用大写的 A - Z 字母表示的26 种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。CPU 在任何一个单位时间内都可以执行一个任务,或者在待命状态。</p><p>然而,两个<code>相同种类</code>的任务之间必须有长度为 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。</p><p>你需要计算完成所有任务所需要的<code>最短时间</code>。</p><p>示例 1:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">输入: tasks = ["A","A","A","B","B","B"], n = 2</span><br><span class="line">输出: 8</span><br><span class="line">执行顺序: A -> B -> (待命) -> A -> B -> (待命) -> A -> B.</span><br></pre></td></tr></table></figure></p><p>注:</p><ol><li>任务的总个数为 [1, 10000]。</li><li>n 的取值范围为 [0, 100]。</li></ol><p>原题url:<a href="https://leetcode-cn.com/problems/task-scheduler/" target="_blank" rel="noopener">https://leetcode-cn.com/problems/task-scheduler/</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="找规律"><a href="#找规律" class="headerlink" title="找规律"></a>找规律</h3><p>这道题的思路,正向推导的话,其实就是优先排出现次数多的任务,根据间隔 n ,填充任务,直到所有任务的次数最终都减为0。</p><p>因此,我们可以用数组存储任务的总次数(因为用大写英文字母表示任务,那就代表最多只能有26种任务),排序之后,按照间隔 n ,从大到小取任务,取完后,再对数组排序,重复上述取任务的过程,直到数组的最大值为0。</p><p>接下来我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">leastInterval</span><span class="params">(<span class="keyword">char</span>[] tasks, <span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="comment">// 将task放入数组中</span></span><br><span class="line"> <span class="keyword">int</span>[] countArray = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">26</span>];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">char</span> task: tasks) {</span><br><span class="line"> countArray[task - <span class="string">'A'</span>]++;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 从小到大,进行排序</span></span><br><span class="line"> Arrays.sort(countArray);</span><br><span class="line"> <span class="comment">// 最终耗时</span></span><br><span class="line"> <span class="keyword">int</span> time = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 从大到小开始遍历</span></span><br><span class="line"> <span class="keyword">while</span> (countArray[<span class="number">25</span>] > <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 每次遍历前n个数</span></span><br><span class="line"> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (i <= n) {</span><br><span class="line"> <span class="comment">// 说明所有任务已经执行完成</span></span><br><span class="line"> <span class="keyword">if</span> (countArray[<span class="number">25</span>] == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 遍历</span></span><br><span class="line"> <span class="keyword">if</span> (i < <span class="number">26</span> && countArray[<span class="number">25</span> - i] > <span class="number">0</span>) {</span><br><span class="line"> countArray[<span class="number">25</span> - i]--;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 耗时+1</span></span><br><span class="line"> time++;</span><br><span class="line"> <span class="comment">// 更换任务</span></span><br><span class="line"> i++;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 从小到大排序</span></span><br><span class="line"> Arrays.sort(countArray);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> time;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK,但执行时间上确实不太好,只打败了<code>47.62%</code>的 java 执行时间,其时间复杂度为<code>O(time)</code>, time 代表最终的执行时间。</p><h3 id="贪心算法"><a href="#贪心算法" class="headerlink" title="贪心算法"></a>贪心算法</h3><p>我们再来想想这道题,影响最终执行时间的,有两个因素,一个是任务中出现的最大次数,另一个就是间隔 n 了。</p><p>如果我们站在最多任务的角度,来看这个问题,假设其最大次数为 maxCount,那么该任务所需的最短执行时间为<code>(maxCount - 1) * (n + 1) + 1</code>,如果还有其他 i 个和 maxCount 相同次数的任务,那么需要在最终的结果上再加上 i。</p><p>那么上面求出来的就是正确答案了吗?</p><p>并不是,因为上面的最短时间,是当剩余时间片能够塞满任务数小于 maxCount 的所有任务。假设 n 很小,那么剩余任务肯定需要在任务数等于 maxCount 的那些任务执行完之后,还要继续执行。</p><p>但因为最大任务已经可以满足在间隔时间内执行完,那么出现次数小于 maxCount 的任务,肯定可以连续执行完成的,也就是不需要空闲等待时间。那么此时的最短执行时间也就是总任务数了。</p><p>接下来我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">leastInterval</span><span class="params">(<span class="keyword">char</span>[] tasks, <span class="keyword">int</span> n)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (tasks.length == <span class="number">0</span> || n == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> tasks.length;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将task放入数组中</span></span><br><span class="line"> <span class="keyword">int</span>[] countArray = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">26</span>];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">char</span> task : tasks) {</span><br><span class="line"> countArray[task - <span class="string">'A'</span>]++;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 从小到大,进行排序</span></span><br><span class="line"> Arrays.sort(countArray);</span><br><span class="line"> <span class="comment">// 获取最大次数</span></span><br><span class="line"> <span class="keyword">int</span> maxCount = countArray[<span class="number">25</span>];</span><br><span class="line"> <span class="comment">// 如果其他次数都比maxCount小的话,求出针对maxCount的最短时间</span></span><br><span class="line"> <span class="keyword">int</span> result = (maxCount - <span class="number">1</span>) * (n + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 遍历countArray</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">25</span>; i >= <span class="number">0</span>; i--) {</span><br><span class="line"> <span class="comment">// 如果有和maxCount相同的,则执行时间+1</span></span><br><span class="line"> <span class="keyword">if</span> (countArray[i] == maxCount) {</span><br><span class="line"> result++;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 否则,直接结束</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 如果总任务数比理论上的最短时间长,说明任务很多,但可以把每个桶填满,因此最短时间也就是总任务数</span></span><br><span class="line"> <span class="keyword">return</span> Math.max(result, tasks.length);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK ,在所有 Java 提交中击败了<code>100.00%</code>的用户,确实快了很多。其时间复杂度为<code>O(M)</code>,M 代表总任务数。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题主要是找规律,优化的时候可以采用贪心算法的思想。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>这道题主要是找规律,优化的时候可以采用贪心算法的思想。<br>
</summary>
<category term="力扣" scheme="https://www.death00.top/categories/%E5%8A%9B%E6%89%A3/"/>
<category term="找规律" scheme="https://www.death00.top/tags/%E6%89%BE%E8%A7%84%E5%BE%8B/"/>
<category term="贪心算法" scheme="https://www.death00.top/tags/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95/"/>
</entry>
<entry>
<title>力扣739——每日温度</title>
<link href="https://www.death00.top/2020/02/21/%E5%8A%9B%E6%89%A3739%E2%80%94%E2%80%94%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6/"/>
<id>https://www.death00.top/2020/02/21/力扣739——每日温度/</id>
<published>2020-02-21T02:00:00.000Z</published>
<updated>2020-03-01T07:06:17.958Z</updated>
<content type="html"><![CDATA[<p>这道题主要是找规律,优化的时候可以利用数据结构的特性(数组和栈)。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>根据每日<code>气温</code>列表,请重新生成一个列表,对应位置的输入是你需要再等待多久,温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。</p><p>例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。</p><p>提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。</p><p>原题url:<a href="https://leetcode-cn.com/problems/daily-temperatures/" target="_blank" rel="noopener">https://leetcode-cn.com/problems/daily-temperatures/</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="优先队列"><a href="#优先队列" class="headerlink" title="优先队列"></a>优先队列</h3><p>如果正向思考的话,就是从前向后遍历,将温度存储在一个优先级队列中(小顶堆),队列中的结构需要记录温度和天数。</p><p>遍历时需要找到队列中最小的值是否大于当前温度,如果大于,则更新结果;如果小于,则将当前记录放入队列中。</p><p>接下来看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span>[] dailyTemperatures(<span class="keyword">int</span>[] T) {</span><br><span class="line"> <span class="comment">// 以温度为排序依据的小顶堆,温度越低越靠前</span></span><br><span class="line"> PriorityQueue<Node> queue = <span class="keyword">new</span> PriorityQueue<>(Comparator.comparingInt(o -> o.temperature));</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> index = <span class="number">0</span>; index < T.length; index++) {</span><br><span class="line"> Node node = <span class="keyword">new</span> Node();</span><br><span class="line"> node.temperature = T[index];</span><br><span class="line"> node.index = index;</span><br><span class="line"> <span class="comment">// 放入队列中</span></span><br><span class="line"> queue.add(node);</span><br><span class="line"> <span class="comment">// 取队列中最小的元素</span></span><br><span class="line"> Node newNode = queue.peek();</span><br><span class="line"> <span class="comment">// 如果队列中最低温度就是当前温度</span></span><br><span class="line"> <span class="keyword">if</span> (newNode.temperature == node.temperature) {</span><br><span class="line"> <span class="comment">// 说明queue中没有比当前温度低的天</span></span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 最低温度比当前低,满足情况</span></span><br><span class="line"> <span class="keyword">while</span> (newNode.temperature < node.temperature) {</span><br><span class="line"> <span class="comment">// 更新T,并且移除</span></span><br><span class="line"> T[newNode.index] = node.index - newNode.index;</span><br><span class="line"> queue.remove();</span><br><span class="line"> newNode = queue.peek();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 队列中剩余的节点,都对应0</span></span><br><span class="line"> Iterator<Node> iterator = queue.iterator();</span><br><span class="line"> <span class="keyword">while</span> (iterator.hasNext()) {</span><br><span class="line"> Node node = iterator.next();</span><br><span class="line"> T[node.index] = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> T;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">Node</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> temperature;</span><br><span class="line"> <span class="keyword">int</span> index;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK,时间复杂度为<code>O(n)</code>,但小顶堆的更新也是需要时间的,因此还是有可以优化的地方。</p><h3 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h3><p>因为题目中给出了:<code>每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数</code>,所以我们可以用一个长度为100的数组存储气温对应的天数。</p><p>这样我们就需要从后向前遍历了,将气温对应的天数记录在数组中,这样每向前遍历一个,就遍历一次这个长度为<code>100</code>的数组,如果有比当前温度高的,则更新结果,否则就记为0。</p><p>虽然每次都要遍历一次长度为<code>100</code>的数组,但因为数组查询的时间复杂度为<code>O(1)</code>,所以速度是很快的。接下来我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span>[] dailyTemperatures(<span class="keyword">int</span>[] T) {</span><br><span class="line"> <span class="comment">// 最终结果</span></span><br><span class="line"> <span class="keyword">int</span>[] result = <span class="keyword">new</span> <span class="keyword">int</span>[T.length];</span><br><span class="line"> <span class="comment">// 因为温度不超过100度,所以温度对应的天数存储在这个数组中</span></span><br><span class="line"> <span class="keyword">int</span>[] next = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">101</span>];</span><br><span class="line"> Arrays.fill(next, Integer.MAX_VALUE);</span><br><span class="line"> <span class="comment">// 从后向前遍历</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = T.length - <span class="number">1</span>; i >= <span class="number">0</span>; --i) {</span><br><span class="line"> <span class="comment">// 比当前温度更高的下标</span></span><br><span class="line"> <span class="keyword">int</span> warmerIndex = Integer.MAX_VALUE;</span><br><span class="line"> <span class="comment">// 从next数组中查找比当前温度高的天数</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> t = T[i] + <span class="number">1</span>; t <= <span class="number">100</span>; ++t) {</span><br><span class="line"> <span class="comment">// 找出天数最小的一个</span></span><br><span class="line"> <span class="keyword">if</span> (next[t] < warmerIndex) {</span><br><span class="line"> warmerIndex = next[t];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果有找到,则更新result</span></span><br><span class="line"> <span class="keyword">if</span> (warmerIndex < Integer.MAX_VALUE) {</span><br><span class="line"> result[i] = warmerIndex - i;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 在next数组中记录当前的温度</span></span><br><span class="line"> next[T[i]] = i;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK,这里其实就够了,但有没有其他更方便的数据结构呢?</p><h3 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h3><p>我们主要想知道的,就是最近的比当前温度高的天数,这样的需求,感觉栈应该是可以满足的,因为先进后出。</p><p>我们还是从后向前遍历,在栈中存储气温的天数,当前遍历到的温度,如果比栈顶的存储的天数对应的温度高,那么栈中存储的是没有意义的;如果比它低,那么就可以更新结果了。</p><p>接下来看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span>[] dailyTemperatures(<span class="keyword">int</span>[] T) {</span><br><span class="line"> <span class="comment">// 用栈存储温度的下标</span></span><br><span class="line"> Stack<Integer> stack = <span class="keyword">new</span> Stack<>();</span><br><span class="line"> <span class="comment">// 最终的结果</span></span><br><span class="line"> <span class="keyword">int</span>[] result = <span class="keyword">new</span> <span class="keyword">int</span>[T.length];</span><br><span class="line"> <span class="comment">// 从后向前遍历</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = T.length - <span class="number">1</span>; i >= <span class="number">0</span>; i--) {</span><br><span class="line"> <span class="comment">// 如果当前温度大于栈顶的温度</span></span><br><span class="line"> <span class="keyword">while</span> (!stack.isEmpty() && T[i] >= T[stack.peek()]) {</span><br><span class="line"> <span class="comment">// 因为当前温度比栈顶存储的温度高,</span></span><br><span class="line"> <span class="comment">// 栈顶元素也没有存储的意义,所以出栈</span></span><br><span class="line"> stack.pop();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果栈为空,则结果为0</span></span><br><span class="line"> <span class="comment">// 如果栈不为空,则用当前栈顶存储的温度</span></span><br><span class="line"> result[i] = stack.isEmpty() ? <span class="number">0</span> : (stack.peek() - i);</span><br><span class="line"> <span class="comment">// 让当前的温度入栈</span></span><br><span class="line"> stack.push(i);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK,时间复杂度和上面的方法相同,但空间复杂度理论上是会比上面小的。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题主要是找规律,优化的时候可以利用数据结构的特性(数组和栈)。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>这道题主要是找规律,优化的时候可以利用数据结构的特性(数组和栈)。<br>
</summary>
<category term="力扣" scheme="https://www.death00.top/categories/%E5%8A%9B%E6%89%A3/"/>
<category term="找规律" scheme="https://www.death00.top/tags/%E6%89%BE%E8%A7%84%E5%BE%8B/"/>
</entry>
<entry>
<title>力扣560——和为K的子数组</title>
<link href="https://www.death00.top/2020/02/19/%E5%8A%9B%E6%89%A3560%E2%80%94%E2%80%94%E5%92%8C%E4%B8%BAK%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84/"/>
<id>https://www.death00.top/2020/02/19/力扣560——和为K的子数组/</id>
<published>2020-02-19T02:00:00.000Z</published>
<updated>2020-02-22T03:38:42.003Z</updated>
<content type="html"><![CDATA[<p>这道题主要是找规律,优化的时候可以利用哈希表和数组的特性。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。</p><p>示例 1 :<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">输入:nums = [1,1,1], k = 2</span><br><span class="line">输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。</span><br></pre></td></tr></table></figure></p><p>说明 :</p><ol><li>数组的长度为 [1, 20,000]。</li><li>数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。</li></ol><p>原题url:<a href="https://leetcode-cn.com/problems/subarray-sum-equals-k/" target="_blank" rel="noopener">https://leetcode-cn.com/problems/subarray-sum-equals-k/</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><p>一开始的想法肯定就是利用暴力解法了,三层 for 循环的那种,第1层和第2层选择起点和终点,第3层则是计算起点到终点的总和。这样的想法最简单,但很可惜,直接超时了,让我们稍微优化一下。</p><h3 id="子数组之和"><a href="#子数组之和" class="headerlink" title="子数组之和"></a>子数组之和</h3><p>因为题目要求子数组下标连续,那么我们想想,如果要求<code>sum(i, j)</code>(也就是从下标 i 到下标 j 的子数组之和),其实可以转化成<code>sum(0, i - 1) - sum(0, j)</code>。这样的话,就可以把上面的三层for循环优化成两层。</p><p>接下来我们直接看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">subarraySum</span><span class="params">(<span class="keyword">int</span>[] nums, <span class="keyword">int</span> k)</span> </span>{</span><br><span class="line"> <span class="comment">// sum(i, j) = sum(0, j) - sum(0, i - 1)</span></span><br><span class="line"> <span class="keyword">int</span>[] sumArray = <span class="keyword">new</span> <span class="keyword">int</span>[nums.length];</span><br><span class="line"> sumArray[<span class="number">0</span>] = nums[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i < nums.length; i++) {</span><br><span class="line"> sumArray[i] = sumArray[i - <span class="number">1</span>] + nums[i];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> result = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = sumArray.length - <span class="number">1</span>; i >= <span class="number">0</span>; i--) {</span><br><span class="line"> <span class="comment">// 前i个数之和</span></span><br><span class="line"> <span class="keyword">if</span> (sumArray[i] == k) {</span><br><span class="line"> result++;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 查询从(j + 1)到i的和</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = i - <span class="number">1</span>; j >= <span class="number">0</span> && j != i; j--) {</span><br><span class="line"> sum = sumArray[i] - sumArray[j];</span><br><span class="line"> <span class="keyword">if</span> (sum == k) {</span><br><span class="line"> result++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK,但时间复杂度是<code>O(n^2)</code>,优化一下。</p><h3 id="用哈希表优化"><a href="#用哈希表优化" class="headerlink" title="用哈希表优化"></a>用哈希表优化</h3><p>我们想想,上面使用使用第二层for循环,主要是为了查出 sumArray 中是否还存在等于<code>sumArray[i] - k</code>的数,这明显是一个映射关系,因此我们用一个 map 去记录中间的求和结果。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">subarraySum</span><span class="params">(<span class="keyword">int</span>[] nums, <span class="keyword">int</span> k)</span> </span>{</span><br><span class="line"> <span class="comment">// sum(i, j) = sum(0, j) - sum(0, i - 1)</span></span><br><span class="line"> <span class="comment">// 用map存储,key为sum,value为第i个数</span></span><br><span class="line"> Map<Integer, List<Integer>> map = <span class="keyword">new</span> HashMap<>(nums.length * <span class="number">4</span> / <span class="number">3</span> + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 数组求和</span></span><br><span class="line"> <span class="keyword">int</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 记录一共有哪些和</span></span><br><span class="line"> Set<Integer> sumSet = <span class="keyword">new</span> HashSet<>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < nums.length; i++) {</span><br><span class="line"> sum += nums[i];</span><br><span class="line"> sumSet.add(sum);</span><br><span class="line"> <span class="comment">// 查看当前是否已经记录</span></span><br><span class="line"> List<Integer> indexList = map.get(sum);</span><br><span class="line"> <span class="keyword">if</span> (indexList == <span class="keyword">null</span>) {</span><br><span class="line"> indexList = <span class="keyword">new</span> LinkedList<>();</span><br><span class="line"> }</span><br><span class="line"> indexList.add(i);</span><br><span class="line"> map.put(sum, indexList);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> result = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (Integer subSum : sumSet) {</span><br><span class="line"> List<Integer> list = map.get(subSum);</span><br><span class="line"> <span class="comment">// 如果list为null,说明是被移除了,说明之前已经计算过</span></span><br><span class="line"> <span class="keyword">if</span> (list == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (subSum == k) {</span><br><span class="line"> result += map.get(subSum).size();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 剩余的和</span></span><br><span class="line"> <span class="keyword">int</span> remainSum = subSum - k;</span><br><span class="line"> List<Integer> remainList = map.get(remainSum);</span><br><span class="line"> <span class="comment">// 如果为null,说明不存在</span></span><br><span class="line"> <span class="keyword">if</span> (remainList == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果不为null,说明存在,则可以进行配对</span></span><br><span class="line"> <span class="comment">// 让list和remainList进行配对</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> index1 : list) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> index2 : remainList) {</span><br><span class="line"> <span class="keyword">if</span> (index1 <= index2) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> result++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>提交OK,虽然较之前的方法时间效率上有所提高,但并没有本质区别。特别是最后的双重 for 循环,因为下标只有大的减小的才有意义,这样也就给自己额外增加了运算。</p><p>那么反思一下,是否真的有必要提前算好子数组的和?如果一边遍历一边求和,并将求和的结果存入map中,那么 map 中存在的,一定是下标小于自己的求和结果。针对这点,我们可以再做一步优化:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">subarraySum</span><span class="params">(<span class="keyword">int</span>[] nums, <span class="keyword">int</span> k)</span> </span>{</span><br><span class="line"> <span class="comment">// 最终结果的数量</span></span><br><span class="line"> <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 求和</span></span><br><span class="line"> <span class="keyword">int</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// key为中间求出了哪些和,value为当前和有几种情况</span></span><br><span class="line"> HashMap<Integer, Integer> map = <span class="keyword">new</span> HashMap<>();</span><br><span class="line"> <span class="comment">// 和为0有1中情况,就是一个数都不选</span></span><br><span class="line"> map.put(<span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 遍历数组</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < nums.length; i++) {</span><br><span class="line"> <span class="comment">// 求和</span></span><br><span class="line"> sum += nums[i];</span><br><span class="line"> <span class="comment">// map中是否有记录剩余的值</span></span><br><span class="line"> <span class="keyword">if</span> (map.containsKey(sum - k)) {</span><br><span class="line"> <span class="comment">// 累加,此处可以直接添加,是因为求和是从前往后进行的,现在能找到的,说明一定是之前的</span></span><br><span class="line"> count += map.get(sum - k);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 记录当前的值</span></span><br><span class="line"> map.put(sum, map.getOrDefault(sum, <span class="number">0</span>) + <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> count;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK,这样时间复杂度就变为了<code>O(n)</code>。</p><h3 id="数据结构的优化"><a href="#数据结构的优化" class="headerlink" title="数据结构的优化"></a>数据结构的优化</h3><p>现在,我们用哈希表来存储中间结果。虽然哈希表的查找效率理论上可以达到<code>O(1)</code>,但考虑到哈希冲突,最坏情况下还是有可能达到<code>O(n)</code>。真正能够保证达到<code>O(1)</code>的数据结构,是数组(用空间换取时间)。</p><p>那这个用来存储的一维数组究竟长度该设置为多少呢?自然就是找出数组中子数组之和的最大值和最小值,两者求差,结果就是最终的数组长度。利用这个数组去存储子数组求和的结果,这样就能保证在查找时的效率了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">subarraySum</span><span class="params">(<span class="keyword">int</span>[] nums, <span class="keyword">int</span> k)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// sum的最小值和最大值,因为可以一个数值都不选,因此0作为初始值</span></span><br><span class="line"> <span class="keyword">int</span> min = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> max = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 求和</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> n : nums) {</span><br><span class="line"> sum += n;</span><br><span class="line"> min = Math.min(min, sum);</span><br><span class="line"> max = Math.max(max, sum);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// sums[i]代表从下标为0到下标为i的子数组之和</span></span><br><span class="line"> <span class="comment">// 用一个数组存储,相比于map,取值更快,用空间换取时间</span></span><br><span class="line"> <span class="keyword">int</span>[] sums = <span class="keyword">new</span> <span class="keyword">int</span>[max - min + <span class="number">1</span>];</span><br><span class="line"> <span class="comment">// 最终结果</span></span><br><span class="line"> <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line"> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 遍历数组</span></span><br><span class="line"> <span class="comment">// 需要重新求和,因为没有类似set这样的结构存储了结果</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> n : nums) {</span><br><span class="line"> <span class="comment">// 求和</span></span><br><span class="line"> sum += n;</span><br><span class="line"> <span class="comment">// 新的目标值</span></span><br><span class="line"> <span class="keyword">int</span> target = sum - min - k;</span><br><span class="line"> <span class="comment">// 是否有超过范围</span></span><br><span class="line"> <span class="keyword">if</span> (target >= <span class="number">0</span> && target < sums.length) {</span><br><span class="line"> count += sums[target];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> sums[sum - min]++;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 再加上本身就是k的子数组的数量</span></span><br><span class="line"> <span class="keyword">if</span> (k - min >= <span class="number">0</span> && k - min < sums.length) {</span><br><span class="line"> count += sums[k - min];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> count;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>提交OK,执行时间上更快了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题主要是找规律,优化的时候可以利用哈希表和数组的特性。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>这道题主要是找规律,优化的时候可以利用哈希表和数组的特性。<br>
</summary>
<category term="力扣" scheme="https://www.death00.top/categories/%E5%8A%9B%E6%89%A3/"/>
<category term="找规律" scheme="https://www.death00.top/tags/%E6%89%BE%E8%A7%84%E5%BE%8B/"/>
</entry>
<entry>
<title>力扣494——目标和</title>
<link href="https://www.death00.top/2020/02/18/%E5%8A%9B%E6%89%A3494%E2%80%94%E2%80%94%E7%9B%AE%E6%A0%87%E5%92%8C/"/>
<id>https://www.death00.top/2020/02/18/力扣494——目标和/</id>
<published>2020-02-18T02:00:00.000Z</published>
<updated>2020-02-22T03:37:45.466Z</updated>
<content type="html"><![CDATA[<p>这道题主要是利用动态规划进行求解,优化的时候可以找规律,转化成正常的背包问题。<br><a id="more"></a></p><h2 id="原题"><a href="#原题" class="headerlink" title="原题"></a>原题</h2><p>给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。</p><p>返回可以使最终数组和为目标数 S 的所有添加符号的方法数。</p><p>示例 1:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">输入: nums: [1, 1, 1, 1, 1], S: 3</span><br><span class="line">输出: 5</span><br><span class="line">解释: </span><br><span class="line"></span><br><span class="line">-1+1+1+1+1 = 3</span><br><span class="line">+1-1+1+1+1 = 3</span><br><span class="line">+1+1-1+1+1 = 3</span><br><span class="line">+1+1+1-1+1 = 3</span><br><span class="line">+1+1+1+1-1 = 3</span><br><span class="line"></span><br><span class="line">一共有5种方法让最终目标和为3。</span><br></pre></td></tr></table></figure></p><p>注意:</p><ol><li>数组非空,且长度不会超过20。</li><li>初始的数组的和不会超过1000。</li><li>保证返回的最终结果能被32位整数存下。</li></ol><p>原题url:<a href="https://leetcode-cn.com/problems/target-sum/" target="_blank" rel="noopener">https://leetcode-cn.com/problems/target-sum/</a></p><h2 id="解题"><a href="#解题" class="headerlink" title="解题"></a>解题</h2><h3 id="简单递归"><a href="#简单递归" class="headerlink" title="简单递归"></a>简单递归</h3><p>最简单的方法就是计算出所有的可能性,如果最终结果等于目标值,则说明该情况可以。直接看一下代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> result = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">findTargetSumWays</span><span class="params">(<span class="keyword">int</span>[] nums, <span class="keyword">int</span> S)</span> </span>{</span><br><span class="line"> <span class="comment">// 递归</span></span><br><span class="line"> findTargetSumWays(nums, S, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> <span class="comment">// 返回最终结果</span></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// index : 当前计算到数组中的位置</span></span><br><span class="line"> <span class="comment">// sum : 当前的总和</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">findTargetSumWays</span><span class="params">(<span class="keyword">int</span>[] nums, <span class="keyword">int</span> S, <span class="keyword">int</span> index, <span class="keyword">int</span> sum)</span> </span>{</span><br><span class="line"> <span class="comment">// 遍历是否结束</span></span><br><span class="line"> <span class="keyword">if</span> (index == nums.length) {</span><br><span class="line"> <span class="comment">// 如果总和等于S,则最终结果+1</span></span><br><span class="line"> <span class="keyword">if</span> (sum == S) {</span><br><span class="line"> result++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 针对当前的数值,有加和减两种情况</span></span><br><span class="line"> findTargetSumWays(nums, S, index + <span class="number">1</span>, sum + nums[index]);</span><br><span class="line"> findTargetSumWays(nums, S, index + <span class="number">1</span>, sum - nums[index]);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>方法很简单,但是时间复杂度太高,<code>O(2^n)</code>,执行用时在 java 中也只打败了<code>31.65%</code>,看来确实不够好。</p><h3 id="简单的动态规划"><a href="#简单的动态规划" class="headerlink" title="简单的动态规划"></a>简单的动态规划</h3><p>这其实类似于<code>背包问题</code>,有容量要求(部分数字之和等于目标值)。首先我们来想一下状态转移方程:</p><p>我们用二维数组<code>dp[i][j]</code>表示用数组中的前<code>i</code>个元素,组成和为<code>j</code>的方案数。考虑第<code>i</code>个数<code>nums[i]</code>,它可以被添加 + 或 -,因此状态转移方程如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]]</span><br></pre></td></tr></table></figure></p><p>也可以写成递推的形式:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">dp[i][j + nums[i]] += dp[i - 1][j]</span><br><span class="line">dp[i][j - nums[i]] += dp[i - 1][j]</span><br></pre></td></tr></table></figure></p><p>因为题目中提到所有数的和不超过 1000,那么 j 的最小值可以达到 -1000。在 java 中,是不允许数组的下标为负数的,因此我们需要给<code>dp[i][j]</code>的第二维预先增加 1000,那么新的递推关系如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">dp[i][j + nums[i] + 1000] += dp[i - 1][j + 1000]</span><br><span class="line">dp[i][j - nums[i] + 1000] += dp[i - 1][j + 1000]</span><br></pre></td></tr></table></figure></p><p>接下来我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">findTargetSumWays</span><span class="params">(<span class="keyword">int</span>[] nums, <span class="keyword">int</span> S)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (S > <span class="number">1000</span> || S < -<span class="number">1000</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 求和的最大值</span></span><br><span class="line"> <span class="keyword">int</span> max = <span class="number">1000</span>;</span><br><span class="line"> <span class="comment">// 初始的数组的和不会超过1000,因此最大为1000,最小为-1000</span></span><br><span class="line"> <span class="keyword">int</span>[][] dp = <span class="keyword">new</span> <span class="keyword">int</span>[nums.length][max * <span class="number">2</span> + <span class="number">1</span>];</span><br><span class="line"> <span class="comment">// 针对nums[0],先求出第一步</span></span><br><span class="line"> dp[<span class="number">0</span>][nums[<span class="number">0</span>] + max] = <span class="number">1</span>;</span><br><span class="line"> dp[<span class="number">0</span>][-nums[<span class="number">0</span>] + max] += <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 遍历数组</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i < nums.length; i++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> sum = -max; sum <= max; sum++) {</span><br><span class="line"> <span class="comment">// 如果上一步有求出和为sum,那么可以继续计算下一步</span></span><br><span class="line"> <span class="keyword">if</span> (dp[i - <span class="number">1</span>][sum + max] > <span class="number">0</span>) {</span><br><span class="line"> dp[i][nums[i] + sum + max] += dp[i - <span class="number">1</span>][sum + max];</span><br><span class="line"> dp[i][-nums[i] + sum + max] += dp[i - <span class="number">1</span>][sum + max];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> dp[nums.length - <span class="number">1</span>][S + max];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>提交OK,时间复杂度为<code>O(N ∗ max)</code>,max 代表数组中所有数字之和的最大值,执行用时在 java 中打败了<code>58.91%</code>,看来还有很大的提升空间。</p><h3 id="动态规划-找规律"><a href="#动态规划-找规律" class="headerlink" title="动态规划 + 找规律"></a>动态规划 + 找规律</h3><p>我们想一下,之所以上面的方法会涉及到 max,因为所谓的<code>求和</code>,并不只是加法,也可以用减法。这和我们一般理解的<code>背包问题</code>还是有所不同的,那么我们是否可以将本题转换成真正意义上的<code>背包问题</code>呢?</p><p>首先,我们可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,那么可以推导出一下关系:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">1、target = sum(P) - sum(N)</span><br><span class="line">2、sum(nums) = sum(P) + sum(N)</span><br><span class="line">由1可以得出:sum(P) = target + sum(N)</span><br><span class="line">由2可以得出:sum(N) = sum(nums) - sum(P)</span><br><span class="line">综合以上,可以得出:</span><br><span class="line">sum(P) = (target + sum(nums)) / 2</span><br></pre></td></tr></table></figure></p><p>因此只要找到一个子集,令它们都取正号,并且和等于 (target + sum(nums))/2,就证明存在解,这就属于正常的<code>0-1背包问题</code>的范畴了。需要注意<code>target + sum(nums)</code>必须为偶数,否则 sum(P) 就是小数了,这和题目要求的所有数都是<code>非负整数</code>不符。</p><p>接下来我们看看代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Solution</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">findTargetSumWays</span><span class="params">(<span class="keyword">int</span>[] nums, <span class="keyword">int</span> S)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (S > <span class="number">1000</span> || S < -<span class="number">1000</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 求出数组的和</span></span><br><span class="line"> <span class="keyword">int</span> sumNums = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> num : nums) {</span><br><span class="line"> sumNums += num;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 新的目标和(sumNums + S) / 2</span></span><br><span class="line"> <span class="keyword">int</span> newTarget = sumNums + S;</span><br><span class="line"> <span class="comment">// 如果是奇数,那么无法被2整除,不符合规则</span></span><br><span class="line"> <span class="keyword">if</span> ((newTarget & <span class="number">1</span>) == <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> newTarget = newTarget / <span class="number">2</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 正常的背包问题,在nums中找几个数,满足和为newTarget</span></span><br><span class="line"> <span class="comment">// dp[i]表示从数组nums找出和为i的组合情况</span></span><br><span class="line"> <span class="keyword">int</span>[] dp = <span class="keyword">new</span> <span class="keyword">int</span>[newTarget + <span class="number">1</span>];</span><br><span class="line"> dp[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 遍历数组</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> num : nums) {</span><br><span class="line"> <span class="comment">// 更新dp</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> sum = newTarget; sum >= num; sum--) {</span><br><span class="line"> dp[sum] += dp[sum - num];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> dp[newTarget];</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><pre><code>提交OK,时间复杂度是`O(n * newTarget)`,其中,` newTarget = (target + sum(nums))/2`,和前面方法中的`max`相比,`sum(nums) <= max`,如果`target > max`,也会直接返回0,因此这个方法的时间复杂度更优。## 总结</code></pre><p>以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题主要是利用<code>动态规划</code>,优化时可以<code>找规律</code>,转化成正常的<code>背包问题</code>。</p><p>有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。</p><p><a href="https://death00.github.io/" target="_blank" rel="noopener">https://death00.github.io/</a></p><p>公众号:健程之道</p><p><img src="https://imgkr.cn-bj.ufileos.com/01be7a29-45a6-4739-ae17-807c175741db.jfif" alt=""></p><p><img src="https://imgkr.cn-bj.ufileos.com/2984c6cb-07db-460e-aa3a-4dde4df2f4cc.jfif" alt=""></p>]]></content>
<summary type="html">
<p>这道题主要是利用动态规划进行求解,优化的时候可以找规律,转化成正常的背包问题。<br>
</summary>
<category term="力扣" scheme="https://www.death00.top/categories/%E5%8A%9B%E6%89%A3/"/>
<category term="动态规划" scheme="https://www.death00.top/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
</entry>
</feed>