Merge pull request #4 from CyC2018/master

merge
This commit is contained in:
林锴
2018-08-15 22:49:51 +08:00
committed by GitHub
13 changed files with 902 additions and 734 deletions

View File

@ -179,12 +179,6 @@
欢迎在 Issue 中提交对本仓库的改进建议~
### Authorization
虽然没有加开源协议,但是允许非商业性使用。
转载使用请注明出处,谢谢!
### Typesetting
笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。
@ -201,6 +195,12 @@
笔者将自己实现文档转换功能提取出来,方便大家在需要将本地 Markdown 上传到 Github或者制作项目 README 文档时生成目录时使用:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。
### License
在对本作品进行演绎时,请署名并以相同方式共享。
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a>
### Statement
本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.)

View File

@ -733,7 +733,6 @@ java.util.concurrentJ.U.C大大提高了并发性能AQS 被认为是 J.
```java
public class CountdownLatchExample {
public static void main(String[] args) throws InterruptedException {
final int totalThread = 10;
CountDownLatch countDownLatch = new CountDownLatch(totalThread);
@ -759,15 +758,30 @@ run..run..run..run..run..run..run..run..run..run..end
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
和 CountdownLatch 相似,都是通过维护计数器来实现的。但是它的计数器是递增的,每次执行 await() 方法之后计数器会 1直到计数器的值和设置的值相等,等待的所有线程才继续执行。和 CountdownLatch 的另一个区别是CyclicBarrier 的计数器可以循环使用,所以它才叫做循环屏障。
和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会 1并进行等待,直到计数器为 0所有调用 awati() 方法而在等待的线程才继续执行。
下图应该从下往上看才正确
CyclicBarrier 和 CountdownLatch 的一个区别是CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环屏障
CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值barrierAction 在所有线程都到达屏障的时候会执行一次。
```java
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
```
<div align="center"> <img src="../pics//CyclicBarrier.png" width=""/> </div><br>
```java
public class CyclicBarrierExample {
public static void main(String[] args) throws InterruptedException {
public static void main(String[] args) {
final int totalThread = 10;
CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);
ExecutorService executorService = Executors.newCachedThreadPool();
@ -776,9 +790,7 @@ public class CyclicBarrierExample {
System.out.print("before..");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.print("after..");
@ -1198,8 +1210,6 @@ volatile 关键字通过添加内存屏障的方式来禁止指令重排,即
上面提到了可以用 volatile 和 synchronized 来保证有序性。除此之外JVM 还规定了先行发生原则,让一个操作无需控制就能先于另一个操作完成。
主要有以下这些原则:
### 1. 单一线程原则
> Single Thread rule
@ -1270,14 +1280,16 @@ Thread 对象的结束先行发生于 join() 方法返回。
### 1. 不可变
不可变Immutable的对象一定是线程安全的无论是对象的方法实现还是方法的调用者,都不需要再采取任何的线程安全保障措施只要一个不可变的对象被正确地构建出来,那其外部的可见状态永远也不会改变,永远也不会看到它在多个线程之中处于不一致的状态。
不可变Immutable的对象一定是线程安全的不需要再采取任何的线程安全保障措施只要一个不可变的对象被正确地构建出来,永远也不会看到它在多个线程之中处于不一致的状态。
多线程环境下,应当尽量使对象成为不可变,来满足线程安全。
不可变的类型:
- final 关键字修饰的基本数据类型
- final 关键字修饰的基本数据类型
- String
- 枚举类型
- Number 部分子类,如 Long 和 Double 等数值包装类型BigInteger 和 BigDecimal 等大数据类型。但同为 Number 的子类型的原子类 AtomicInteger 和 AtomicLong 则并非不可变的。
- Number 部分子类,如 Long 和 Double 等数值包装类型BigInteger 和 BigDecimal 等大数据类型。但同为 Number 的原子类 AtomicInteger 和 AtomicLong 则可变的。
对于集合类型,可以使用 Collections.unmodifiableXXX() 方法来获取一个不可变的集合。
@ -1305,21 +1317,19 @@ public V put(K key, V value) {
}
```
多线程环境下,应当尽量使对象成为不可变,来满足线程安全。
### 2. 绝对线程安全
不管运行时环境如何,调用者都不需要任何额外的同步措施。
### 3. 相对线程安全
相对线程安全需要保证对这个对象单独的操作是线程安全的,在调用的时候不需要做额外的保障措施但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。
相对线程安全需要保证对这个对象单独的操作是线程安全的,在调用的时候不需要做额外的保障措施但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。
在 Java 语言中,大部分的线程安全类都属于这种类型,例如 Vector、HashTable、Collections 的 synchronizedCollection() 方法包装的集合等。
对于下面的代码,如果删除元素的线程删除了一个元素,而获取元素的线程试图访问一个已经被删除的元素,那么就会抛出 ArrayIndexOutOfBoundsException。
对于下面的代码,如果删除元素的线程删除了 Vector 的一个元素,而获取元素的线程试图访问一个已经被删除的元素,那么就会抛出 ArrayIndexOutOfBoundsException。
```java
```Java
public class VectorUnsafeExample {
private static Vector<Integer> vector = new Vector<>();
@ -1389,15 +1399,17 @@ synchronized 和 ReentrantLock。
### 2. 非阻塞同步
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。
互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。
互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。
互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。
随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步操作称为非阻塞同步。
**CAS**
乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成
随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步
硬件支持的原子性操作最典型的是比较并交换Compare-and-SwapCAS。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时只有当 V 的值等于 A才将 V 的值更新为 B。
乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是比较并交换Compare-and-SwapCAS。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时只有当 V 的值等于 A才将 V 的值更新为 B。
**AtomicInteger**
J.U.C 包里面的整数原子类 AtomicInteger其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 类的 CAS 操作。
@ -1419,7 +1431,7 @@ public final int incrementAndGet() {
}
```
以下代码是 getAndAddInt() 源码var1 指示对象内存地址var2 指示该字段相对对象内存地址的偏移var4 指示操作需要加的数值,这里为 1。通过 getIntVolatile(var1, var2) 得到旧的预期值,通过调用 compareAndSwapInt() 来进行 CAS 比较,如果该字段内存地址中的值 ==var5那么就更新内存地址为 var1+var2 的变量为 var5+var4。
以下代码是 getAndAddInt() 源码var1 指示对象内存地址var2 指示该字段相对对象内存地址的偏移var4 指示操作需要加的数值,这里为 1。通过 getIntVolatile(var1, var2) 得到旧的预期值,通过调用 compareAndSwapInt() 来进行 CAS 比较,如果该字段内存地址中的值等于 var5那么就更新内存地址为 var1+var2 的变量为 var5+var4。
可以看到 getAndAddInt() 在一个循环中进行,发生冲突的做法是不断的进行重试。
@ -1434,23 +1446,19 @@ public final int getAndAddInt(Object var1, long var2, int var4) {
}
```
ABA :如果一个变量初次读取的时候是 A 值,它的值被改成了 B后来又被改回为 A那 CAS 操作就会误认为它从来没有被改变过。
**ABA**
如果一个变量初次读取的时候是 A 值,它的值被改成了 B后来又被改回为 A那 CAS 操作就会误认为它从来没有被改变过。
J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。
### 3. 无同步方案
要保证线程安全,并不是一定就要进行同步,两者没有因果关系。同步只是保证共享数据争用时的正确性的手段,如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性,因此会有一些代码天生就是线程安全的
要保证线程安全,并不是一定就要进行同步如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。
**(一)可重入代码Reentrant Code**
**(一)栈封闭**
这种代码也叫做纯代码Pure Code可以在代码执行的任何时刻中断它转而去执行另外一段代码包括递归调用它本身而在控制权返回后原来的程序不会出现任何错误
可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。
**(二)栈封闭**
多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在栈中,属于线程私有的。
多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的
```java
import java.util.concurrent.ExecutorService;
@ -1482,11 +1490,11 @@ public static void main(String[] args) {
100
```
**线程本地存储Thread Local Storage**
**线程本地存储Thread Local Storage**
如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。
符合这种特点的应用并不少见,大部分使用消费队列的架构模式(如“生产者-消费者”模式)都会将产品的消费过程尽量在一个线程中消费完其中最重要的一个应用实例就是经典 Web 交互模型中的“一个请求对应一个服务器线程”Thread-per-Request的处理方式这种处理方式的广泛应用使得很多 Web 服务端应用都可以使用线程本地存储来解决线程安全问题。
符合这种特点的应用并不少见,大部分使用消费队列的架构模式(如“生产者-消费者”模式)都会将产品的消费过程尽量在一个线程中消费完其中最重要的一个应用实例就是经典 Web 交互模型中的“一个请求对应一个服务器线程”Thread-per-Request的处理方式这种处理方式的广泛应用使得很多 Web 服务端应用都可以使用线程本地存储来解决线程安全问题。
可以使用 java.lang.ThreadLocal 类来实现线程本地存储功能。
@ -1584,17 +1592,25 @@ public T get() {
}
```
ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因为根本不存在多线程竞争。在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。
ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因为根本不存在多线程竞争。
在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。
**可重入代码Reentrant Code**
这种代码也叫做纯代码Pure Code可以在代码执行的任何时刻中断它转而去执行另外一段代码包括递归调用它本身而在控制权返回后原来的程序不会出现任何错误。
可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。
# 十二、锁优化
这里的锁优化主要是指虚拟机对 synchronized 的优化。
这里的锁优化主要是指 JVM 对 synchronized 的优化。
## 自旋锁
互斥同步进入阻塞状态的开销都很大,应该尽量避免。在许多应用中,共享数据的锁定状态只会持续很短的一段时间。自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态。
互斥同步进入阻塞状态的开销都很大,应该尽量避免。在许多应用中,共享数据的锁定状态只会持续很短的一段时间。自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态。
锁虽然能避免进入阻塞状态从而减少开销,但是它需要进行忙循环操作占用 CPU 时间,它只适用于共享数据的锁定状态很短的场景。
锁虽然能避免进入阻塞状态从而减少开销,但是它需要进行忙循环操作占用 CPU 时间,它只适用于共享数据的锁定状态很短的场景。
在 JDK 1.6 中引入了自适应的自旋锁。自适应意味着自旋的次数不再固定了,而是由前一次在同一个锁上的自旋次数及锁的拥有者的状态来决定。
@ -1624,7 +1640,7 @@ public static String concatString(String s1, String s2, String s3) {
}
```
每个 append() 方法中都有一个同步块。虚拟机观察变量 sb很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说sb 的所有引用永远不会逃逸到 concatString() 方法之外,其他线程无法访问到它,因此可以进行消除。
每个 append() 方法中都有一个同步块。虚拟机观察变量 sb很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说sb 的所有引用永远不会逃逸到 concatString() 方法之外,其他线程无法访问到它,因此可以进行消除。
## 锁粗化
@ -1636,9 +1652,9 @@ public static String concatString(String s1, String s2, String s3) {
JDK 1.6 引入了偏向锁和轻量级锁从而让锁拥有了四个状态无锁状态unlocked、偏向锁状态biasble、轻量级锁状态lightweight locked和重量级锁状态inflated
以下是 HotSpot 虚拟机对象头的内存布局,这些数据被称为 mark word。其中 tag bits 对应了五个状态,这些状态在右侧的 state 表格中给出,应该注意的是 state 表格不是存储在对象头中的。除了 marked for gc 状态,其它四个状态已经在前面介绍过了。
以下是 HotSpot 虚拟机对象头的内存布局,这些数据被称为 Mark Word。其中 tag bits 对应了五个状态,这些状态在右侧的 state 表格中给出。除了 marked for gc 状态,其它四个状态已经在前面介绍过了。
<div align="center"> <img src="../pics//bb6a49be-00f2-4f27-a0ce-4ed764bc605c.png" width="600"/> </div><br>
<div align="center"> <img src="../pics//bb6a49be-00f2-4f27-a0ce-4ed764bc605c.png" width="500"/> </div><br>
下图左侧是一个线程的虚拟机栈,其中有一部分称为 Lock Record 的区域,这是在轻量级锁运行过程创建的,用于存放锁对象的 Mark Word。而右侧就是一个锁对象包含了 Mark Word 和其它信息。
@ -1646,9 +1662,9 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
轻量级锁是相对于传统的重量级锁而言,它使用 CAS 操作来避免重量级锁使用互斥量的开销。对于绝大部分的锁,在整个同步周期内都是不存在竞争的,因此也就不需要都使用互斥量进行同步,可以先采用 CAS 操作进行同步,如果 CAS 失败了再改用互斥量进行同步。
当尝试获取一个锁对象时,如果锁对象标记为 0 01说明锁对象的锁未锁定unlocked状态。此时虚拟机在当前线程栈中创建 Lock Record然后使用 CAS 操作将对象的 Mark Word 更新为 Lock Record 指针。如果 CAS 操作成功了,那么线程就获取了该对象上的锁,并且对象的 Mark Word 的锁标记变为 00表示该对象处于轻量级锁状态。
当尝试获取一个锁对象时,如果锁对象标记为 0 01说明锁对象的锁未锁定unlocked状态。此时虚拟机在当前线程的虚拟机栈中创建 Lock Record然后使用 CAS 操作将对象的 Mark Word 更新为 Lock Record 指针。如果 CAS 操作成功了,那么线程就获取了该对象上的锁,并且对象的 Mark Word 的锁标记变为 00表示该对象处于轻量级锁状态。
<div align="center"> <img src="../pics//baaa681f-7c52-4198-a5ae-303b9386cf47.png" width="500"/> </div><br>
<div align="center"> <img src="../pics//baaa681f-7c52-4198-a5ae-303b9386cf47.png" width="400"/> </div><br>
如果 CAS 操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的虚拟机栈,如果是的话说明当前线程已经拥有了这个锁对象,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁。
@ -1666,9 +1682,9 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
- 给线程起个有意义的名字,这样可以方便找 Bug。
- 缩小同步范围,例如对于 synchronized应该尽量使用同步块而不是同步方法。
- 缩小同步范围,从而减少锁争用。例如对于 synchronized应该尽量使用同步块而不是同步方法。
- 多用同步少用 wait() 和 notify()。首先CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
- 多用同步工具少用 wait() 和 notify()。首先CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
- 多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。

View File

@ -45,7 +45,7 @@
可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小:
```java
java -Xss=512M HackTheJava
java -Xss512M HackTheJava
```
该区域可能抛出以下异常:
@ -81,7 +81,7 @@ java -Xss=512M HackTheJava
可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
```java
java -Xms=1M -Xmx=2M HackTheJava
java -Xms1M -Xmx2M HackTheJava
```
## 方法区

View File

@ -129,7 +129,7 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
### 1. 数据结构
B Tree 指的是 Balance Tree也就是平衡树。平衡树一颗查找树,并且所有叶子节点位于同一层。
B Tree 指的是 Balance Tree也就是平衡树。平衡树一颗查找树,并且所有叶子节点位于同一层。
B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。

View File

@ -10,8 +10,8 @@
* [9. 用两个栈实现队列](#9-用两个栈实现队列)
* [10.1 斐波那契数列](#101-斐波那契数列)
* [10.2 跳台阶](#102-跳台阶)
* [10.3 变态跳台阶](#103-变态跳台阶)
* [10.4 矩形覆盖](#104-矩形覆盖)
* [10.3 矩形覆盖](#103-矩形覆盖)
* [10.4 变态跳台阶](#104-变态跳台阶)
* [11. 旋转数组的最小数字](#11-旋转数组的最小数字)
* [12. 矩阵中的路径](#12-矩阵中的路径)
* [13. 机器人的运动范围](#13-机器人的运动范围)
@ -58,8 +58,8 @@
* [50. 第一个只出现一次的字符位置](#50-第一个只出现一次的字符位置)
* [51. 数组中的逆序对](#51-数组中的逆序对)
* [52. 两个链表的第一个公共结点](#52-两个链表的第一个公共结点)
* [53 数字在排序数组中出现的次数](#53-数字在排序数组中出现的次数)
* [54. 二叉搜索树的第 K 个结点](#54-二叉搜索树的第-k-个结点)
* [53. 数字在排序数组中出现的次数](#53-数字在排序数组中出现的次数)
* [54. 二叉查找树的第 K 个结点](#54-二叉查找树的第-k-个结点)
* [55.1 二叉树的深度](#551-二叉树的深度)
* [55.2 平衡二叉树](#552-平衡二叉树)
* [56. 数组中只出现一次的数字](#56-数组中只出现一次的数字)
@ -112,11 +112,11 @@ Output:
要求复杂度为 O(N) + O(1),也就是时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。牛客网讨论区这一题的首票答案使用 nums[i] + length 来将元素标记,这么做会有加法溢出问题。
这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素到第 i 个位置上。
这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上。
以 (2, 3, 1, 0, 2, 5) 为例:
```text-html-basic
```text
position-0 : (2,3,1,0,2,5) // 2 <-> 1
(1,3,2,0,2,5) // 1 <-> 3
(3,1,2,0,2,5) // 3 <-> 0
@ -146,7 +146,9 @@ public boolean duplicate(int[] nums, int length, int[] duplication) {
}
private void swap(int[] nums, int i, int j) {
int t = nums[i]; nums[i] = nums[j]; nums[j] = t;
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
```
@ -176,12 +178,12 @@ Given target = 20, return false.
从右上角开始查找。矩阵中的一个数,它左边的数都比它小,下边的数都比它大。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间。
复杂度O(M + N) + O(1)
当前元素的查找区间为左下角的所有元素,例如元素 12 的查找区间如下:
<div align="center"> <img src="../pics//f94389e9-55b1-4f49-9d37-00ed05900ae0.png" width="250"/> </div><br>
复杂度O(M + N) + O(1)
```java
public boolean Find(int target, int[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0)
@ -193,7 +195,7 @@ public boolean Find(int target, int[][] matrix) {
return true;
else if (target > matrix[r][c])
r++;
else
else
c--;
}
return false;
@ -206,26 +208,33 @@ public boolean Find(int target, int[][] matrix) {
## 题目描述
请实现一个函数,将一个字符串中的空格替换成“%20”。例如当字符串为 We Are Happy. 则经过替换之后的字符串为 We%20Are%20Happy。
将一个字符串中的空格替换成 "%20"。
```text
Input:
"We Are Happy"
Output:
"We%20Are%20Happy"
```
## 解题思路
在字符串尾部填充任意字符,使得字符串的长度等于字符串替换之后的长度。因为一个空格要替换成三个字符(%20因此当遍历到一个空格时需要在尾部填充两个任意字符。
在字符串尾部填充任意字符,使得字符串的长度等于替换之后的长度。因为一个空格要替换成三个字符(%20因此当遍历到一个空格时需要在尾部填充两个任意字符。
令 P1 指向字符串原来的末尾位置P2 指向字符串现在的末尾位置。P1 和 P2从后向前遍历当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。
从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。
复杂度O(N) + O(1)
```java
public String replaceSpace(StringBuffer str) {
int oldLen = str.length();
for (int i = 0; i < oldLen; i++)
int P1 = str.length() - 1;
for (int i = 0; i < str.length(); i++)
if (str.charAt(i) == ' ')
str.append(" ");
int P1 = oldLen - 1, P2 = str.length() - 1;
int P2 = str.length() - 1;
while (P1 >= 0 && P2 > P1) {
char c = str.charAt(P1--);
if (c == ' ') {
@ -345,23 +354,23 @@ inorder = [9,3,15,20,7]
前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为树的左子树中序遍历结果,右部分为树的右子树中序遍历的结果。
```java
// 缓存中序遍历数组每个值对应的索引
private Map<Integer, Integer> inOrderNumsIndexs = new HashMap<>();
// 缓存中序遍历数组每个值对应的索引
private Map<Integer, Integer> indexForInOrders = new HashMap<>();
public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
for (int i = 0; i < in.length; i++)
inOrderNumsIndexs.put(in[i], i);
return reConstructBinaryTree(pre, 0, pre.length - 1, 0, in.length - 1);
indexForInOrders.put(in[i], i);
return reConstructBinaryTree(pre, 0, pre.length - 1, 0);
}
private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL, int inR) {
private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL) {
if (preL > preR)
return null;
TreeNode root = new TreeNode(pre[preL]);
int inIndex = inOrderNumsIndexs.get(root.val);
int inIndex = indexForInOrders.get(root.val);
int leftTreeSize = inIndex - inL;
root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeSize, inL, inL + leftTreeSize - 1);
root.right = reConstructBinaryTree(pre, preL + leftTreeSize + 1, preR, inL + leftTreeSize + 1, inR);
root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeSize, inL);
root.right = reConstructBinaryTree(pre, preL + leftTreeSize + 1, preR, inL + leftTreeSize + 1);
return root;
}
```
@ -456,7 +465,7 @@ public int pop() throws Exception {
## 题目描述
求菲波那契数列的第 n 项n <= 39。
求斐波那契数列的第 n 项n <= 39。
<div align="center"><img src="https://latex.codecogs.com/gif.latex?f(n)=\left\{\begin{array}{rcl}0&&{n=0}\\1&&{n=1}\\f(n-1)+f(n-2)&&{n>1}\end{array}\right."/></div> <br>
@ -526,23 +535,6 @@ public class Solution {
## 解题思路
复杂度O(N) + O(N)
```java
public int JumpFloor(int n) {
if (n == 1)
return 1;
int[] dp = new int[n];
dp[0] = 1;
dp[1] = 2;
for (int i = 2; i < n; i++)
dp[i] = dp[i - 1] + dp[i - 2];
return dp[n - 1];
}
```
复杂度O(N) + O(1)
```java
public int JumpFloor(int n) {
if (n <= 2)
@ -558,7 +550,32 @@ public int JumpFloor(int n) {
}
```
# 10.3 变态跳台阶
# 10.3 矩形覆盖
[NowCoder](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
我们可以用 2\*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2\*1 的小矩形无重叠地覆盖一个 2\*n 的大矩形,总共有多少种方法?
## 解题思路
```java
public int RectCover(int n) {
if (n <= 2)
return n;
int pre2 = 1, pre1 = 2;
int result = 0;
for (int i = 3; i <= n; i++) {
result = pre2 + pre1;
pre2 = pre1;
pre1 = result;
}
return result;
}
```
# 10.4 变态跳台阶
[NowCoder](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@ -579,47 +596,6 @@ public int JumpFloorII(int target) {
}
```
# 10.4 矩形覆盖
[NowCoder](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
我们可以用 2\*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2\*1 的小矩形无重叠地覆盖一个 2\*n 的大矩形,总共有多少种方法?
## 解题思路
复杂度O(N) + O(N)
```java
public int RectCover(int n) {
if (n <= 2)
return n;
int[] dp = new int[n];
dp[0] = 1;
dp[1] = 2;
for (int i = 2; i < n; i++)
dp[i] = dp[i - 1] + dp[i - 2];
return dp[n - 1];
}
```
复杂度O(N) + O(1)
```java
public int RectCover(int n) {
if (n <= 2)
return n;
int pre2 = 1, pre1 = 2;
int result = 0;
for (int i = 3; i <= n; i++) {
result = pre2 + pre1;
pre2 = pre1;
pre1 = result;
}
return result;
}
```
# 11. 旋转数组的最小数字
@ -629,18 +605,34 @@ public int RectCover(int n) {
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1。NOTE给出的所有元素都大于 0若数组大小为 0请返回 0。
例如数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1。
## 解题思路
在一个有序数组中查找一个元素可以用二分查找,二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度都为 O(logN)。
本题可以修改二分查找算法进行求解:
- 当 nums[m] <= nums[h] 的情况下,说明解在 [l, m] 之间,此时令 h = m
- 否则解在 [m + 1, h] 之间,令 l = m + 1。
因为 h 的赋值表达式为 h = m因此循环体的循环条件应该为 l < h详细解释请见 [Leetcode 题解](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3.md) 二分查找部分。
```java
public int minNumberInRotateArray(int[] nums) {
if (nums.length == 0)
return 0;
int l = 0, h = nums.length - 1;
while (l < h) {
int m = l + (h - l) / 2;
if (nums[m] <= nums[h])
h = m;
else
l = m + 1;
}
return nums[l];
}
```
但是如果出现 nums[l] == nums[m] == nums[h],那么此时无法确定解在哪个区间,需要切换到顺序查找。
复杂度O(logN) + O(1)
如果数组元素允许重复的话,那么就会出现一个特殊的情况:nums[l] == nums[m] == nums[h],那么此时无法确定解在哪个区间,需要切换到顺序查找。例如对于数组 {1,1,1,0,1}l、m 和 h 指向的数都为 1此时无法知道最小数字 0 在哪个区间。
```java
public int minNumberInRotateArray(int[] nums) {
@ -728,7 +720,9 @@ private char[][] buildMatrix(char[] array) {
## 题目描述
地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。例如,当 k 为 18 时机器人能够进入方格35, 37因为 3+5+3+7=18。但是它不能进入方格35, 38因为 3+5+3+8=19。请问该机器人能够达到多少个格子
地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。
例如,当 k 为 18 时,机器人能够进入方格 (35,37),因为 3+5+3+7=18。但是它不能进入方格 (35,37),因为 3+5+3+8=19。请问该机器人能够达到多少个格子
## 解题思路
@ -797,7 +791,7 @@ return 36 (10 = 3 + 3 + 4)
### 贪心
尽可能多剪长度为 3 的绳子,并且不允许有长度为 1 的绳子出现如果出现了,就从已经切好长度为 3 的绳子中拿出一段与长度为 1 的绳子重新组合,把它们切成两段长度为 2 的绳子。
尽可能多剪长度为 3 的绳子,并且不允许有长度为 1 的绳子出现如果出现了,就从已经切好长度为 3 的绳子中拿出一段与长度为 1 的绳子重新组合,把它们切成两段长度为 2 的绳子。
证明:当 n >= 5 时3(n - 3) - 2(n - 2) = n - 5 >= 0。因此把长度大于 5 的绳子切成两段,令其中一段长度为 3 可以使得两段的乘积最大。
@ -838,19 +832,9 @@ public int integerBreak(int n) {
输入一个整数,输出该数二进制表示中 1 的个数。
### Integer.bitCount()
```java
public int NumberOf1(int n) {
return Integer.bitCount(n);
}
```
### n&(n-1)
O(M) 时间复杂度解法,其中 M 表示 1 的个数
该位运算是去除 n 的位级表示中最低的那一位。
该位运算去除 n 的位级表示中最低的那一位
```
n : 10110100
@ -858,6 +842,9 @@ n-1 : 10110011
n&(n-1) : 10110000
```
时间复杂度O(M),其中 M 表示 1 的个数。
```java
public int NumberOf1(int n) {
int cnt = 0;
@ -869,13 +856,22 @@ public int NumberOf1(int n) {
}
```
### Integer.bitCount()
```java
public int NumberOf1(int n) {
return Integer.bitCount(n);
}
```
# 16. 数值的整数次方
[NowCoder](https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&tqId=11165&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 题目描述
给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent求 base 的 exponent 次方。
给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent求 base 的 exponent 次方。
## 解题思路
@ -883,7 +879,7 @@ public int NumberOf1(int n) {
<div align="center"><img src="https://latex.codecogs.com/gif.latex?x^n=\left\{\begin{array}{rcl}(x*x)^{n/2}&&{n\%2=0}\\x*(x*x)^{n/2}&&{n\%2=1}\end{array}\right."/></div> <br>
因为 (x\*x)<sup>n/2</sup> 可以通过递归求解,并且每递归一次,n 都减小一半,因此整个算法的时间复杂度为 O(logN)。
因为 (x\*x)<sup>n/2</sup> 可以通过递归求解,并且每递归 n 都减小一半,因此整个算法的时间复杂度为 O(logN)。
```java
public double Power(double base, int exponent) {
@ -1019,6 +1015,7 @@ public ListNode deleteDuplication(ListNode pHead) {
```java
public boolean match(char[] str, char[] pattern) {
int m = str.length, n = pattern.length;
boolean[][] dp = new boolean[m + 1][n + 1];
@ -1049,9 +1046,24 @@ public boolean match(char[] str, char[] pattern) {
## 题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
```html
true
"+100"
"5e2"
"-123"
"3.1416"
"-1E-16"
false
"12e"
"1a3.14"
"1.2.3"
"+-5"
"12e+4.3"
```
例如,字符串 "+100","5e2","-123","3.1416" 和 "-1E-16" 都表示数值。但是 "12e","1a3.14","1.2.3","+-5" 和 "12e+4.3" 都不是。
## 解题思路
@ -1115,8 +1127,7 @@ public void reOrderArray(int[] nums) {
<div align="center"> <img src="../pics//ea2304ce-268b-4238-9486-4d8f8aea8ca4.png" width="500"/> </div><br>
```java
public ListNode FindKthToTail(ListNode head, int k)
{
public ListNode FindKthToTail(ListNode head, int k) {
if (head == null)
return null;
ListNode P1 = head;
@ -1139,9 +1150,7 @@ public ListNode FindKthToTail(ListNode head, int k)
## 题目描述
一个链表中包含环,请找出该链表的环的入口结点。
要求不能使用额外的空间。
一个链表中包含环,请找出该链表的环的入口结点。要求不能使用额外的空间。
## 解题思路
@ -1152,8 +1161,7 @@ public ListNode FindKthToTail(ListNode head, int k)
<div align="center"> <img src="../pics//2858f8ad-aedb-45a5-a706-e98c96d690fa.jpg" width="600"/> </div><br>
```java
public ListNode EntryNodeOfLoop(ListNode pHead)
{
public ListNode EntryNodeOfLoop(ListNode pHead) {
if (pHead == null || pHead.next == null)
return null;
ListNode slow = pHead, fast = pHead;
@ -1295,8 +1303,6 @@ private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) {
## 解题思路
### 递归
```java
public void Mirror(TreeNode root) {
if (root == null)
@ -1313,29 +1319,6 @@ private void swap(TreeNode root) {
}
```
### 迭代
```java
public void Mirror(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
if (node == null)
continue;
swap(node);
stack.push(node.left);
stack.push(node.right);
}
}
private void swap(TreeNode node) {
TreeNode t = node.left;
node.left = node.right;
node.right = t;
}
```
# 28 对称的二叉树
[NowCder](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@ -1436,7 +1419,9 @@ public int min() {
## 题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。
例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。
## 解题思路
@ -1829,7 +1814,7 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) {
多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(N)。
使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素相等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0说明前 i 个元素没有 majority或者有 majority但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中majority 的数目依然多于 (n - i) / 2因此继续查找就能找出 majority。
使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素相等时,令 cnt++,否则令 cnt--。如果前面查找了 i 个元素,且 cnt == 0说明前 i 个元素没有 majority或者有 majority但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中majority 的数目依然多于 (n - i) / 2因此继续查找就能找出 majority。
```java
public int MoreThanHalfNum_Solution(int[] nums) {
@ -2259,7 +2244,7 @@ public int GetUglyNumber_Solution(int N) {
## 题目描述
在一个字符串 (1 <= 字符串长度 <= 10000全部由字母组成) 中找到第一个只出现一次的字符并返回它的位置
在一个字符串 中找到第一个只出现一次的字符并返回它的位置
## 解题思路
@ -2277,7 +2262,7 @@ public int FirstNotRepeatingChar(String str) {
}
```
以上实现的空间复杂度还不是最优的考虑到只需要找到只出现一次的字符,那么我们只需要统计的次数信息只有 0,1,更大,使用两个比特位就能存储这些信息
以上实现的空间复杂度还不是最优的考虑到只需要找到只出现一次的字符那么需要统计的次数信息只有 0,1,更大使用两个比特位就能存储这些信息
```java
public int FirstNotRepeatingChar2(String str) {
@ -2304,13 +2289,13 @@ public int FirstNotRepeatingChar2(String str) {
## 题目描述
在数组中的两个数字如果前面一个数字大于后面的数字则这两个数字组成一个逆序对输入一个数组求出这个数组中的逆序对的总数 P。
在数组中的两个数字如果前面一个数字大于后面的数字则这两个数字组成一个逆序对输入一个数组求出这个数组中的逆序对的总数
## 解题思路
```java
private long cnt = 0;
private int[] tmp; // 在这里创建辅助数组,而不是在 merge() 递归函数中创建
private int[] tmp; // 在这里声明辅助数组,而不是在 merge() 递归函数中声明
public int InversePairs(int[] nums) {
tmp = new int[nums.length];
@ -2359,7 +2344,7 @@ private void merge(int[] nums, int l, int m, int h) {
设 A 的长度为 a + cB 的长度为 b + c其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。
当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部重新开始访问链表 B同样地当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部重新开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
当访问链表 A 的指针访问到链表尾部时,令它从链表 B 的头部重新开始访问链表 B同样地当访问链表 B 的指针访问到链表尾部时,令它从链表 A 的头部重新开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
```java
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
@ -2372,7 +2357,7 @@ public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
}
```
# 53 数字在排序数组中出现的次数
# 53. 数字在排序数组中出现的次数
[NowCoder](https://www.nowcoder.com/practice/70610bf967994b22bb1c26f9ae901fa2?tpId=13&tqId=11190&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@ -2380,8 +2365,9 @@ public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
```html
Input:
1, 2, 3, 3, 3, 3, 4, 6
3
nums = 1, 2, 3, 3, 3, 3, 4, 6
K = 3
Output:
4
```
@ -2408,13 +2394,13 @@ private int binarySearch(int[] nums, int K) {
}
```
# 54. 二叉搜索树的第 K 个结点
# 54. 二叉查找树的第 K 个结点
[NowCoder](https://www.nowcoder.com/practice/ef068f602dde4d28aab2b210e859150a?tpId=13&tqId=11215&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
## 解题思路
利用二叉搜索数中序遍历有序的特点。
利用二叉查找树中序遍历有序的特点。
```java
private TreeNode ret;
@ -2520,7 +2506,7 @@ public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) {
## 题目描述
输入一个递增排序的数组和一个数字 S在数组中查找两个数使得他们的和正好是 S如果有多对数字的和等于 S输出两个数的乘积最小的。
输入一个递增排序的数组和一个数字 S在数组中查找两个数使得他们的和正好是 S如果有多对数字的和等于 S输出两个数的乘积最小的。
## 解题思路
@ -2596,9 +2582,13 @@ public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
## 题目描述
输入:"I am a student."
```html
Input:
"I am a student."
输出:"student. a am I"
Output:
"student. a am I"
```
## 解题思路
@ -2640,7 +2630,14 @@ private void swap(char[] c, int i, int j) {
## 题目描述
对于一个给定的字符序列 S请你把其循环左移 K 位后的序列输出。例如,字符序列 S=”abcXYZdef”, 要求输出循环左移 3 位后的结果即“XYZdefabc”。
```html
Input:
S="abcXYZdef"
K=3
Output:
"XYZdefabc"
```
## 解题思路
@ -2675,7 +2672,9 @@ private void swap(char[] chars, int i, int j) {
## 题目描述
给定一个数组和滑动窗口的大小找出所有滑动窗口里数值的最大值。例如,如果输入数组 {2, 3, 4, 2, 6, 2, 5, 1} 及滑动窗口的大小 3那么一共存在 6 个滑动窗口,他们的最大值分别为 {4, 4, 6, 6, 6, 5}。
给定一个数组和滑动窗口的大小找出所有滑动窗口里数值的最大值
例如如果输入数组 {2, 3, 4, 2, 6, 2, 5, 1} 及滑动窗口的大小 3那么一共存在 6 个滑动窗口他们的最大值分别为 {4, 4, 6, 6, 6, 5}。
## 解题思路
@ -2774,7 +2773,7 @@ public List<Map.Entry<Integer, Double>> dicesSum(int n) {
## 题目描述
五张牌,其中大小鬼为癞子,牌面大小为 0。判断是否能组成顺子。
五张牌,其中大小鬼为癞子,牌面大小为 0。判断这五张牌是否能组成顺子。
## 解题思路
@ -2803,7 +2802,7 @@ public boolean isContinuous(int[] nums) {
## 题目描述
让小朋友们围成一个大圈。然后,随机指定一个数 m让编号为 0 的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续 0...m-1 报数 .... 这样下去 .... 直到剩下最后一个小朋友,可以不用表演。
让小朋友们围成一个大圈。然后,随机指定一个数 m让编号为 0 的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续 0...m-1 报数 .... 这样下去 .... 直到剩下最后一个小朋友,可以不用表演。
## 解题思路
@ -2813,7 +2812,7 @@ public boolean isContinuous(int[] nums) {
public int LastRemaining_Solution(int n, int m) {
if (n == 0) /* 特殊输入的处理 */
return -1;
if (n == 1) /* 返回条件 */
if (n == 1) /* 递归返回条件 */
return 0;
return (LastRemaining_Solution(n - 1, m) + m) % n;
}
@ -2851,7 +2850,7 @@ public int maxProfit(int[] prices) {
## 题目描述
求 1+2+3+...+n要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句A?B:C
要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句 A ? B : C
## 解题思路
@ -2859,7 +2858,7 @@ public int maxProfit(int[] prices) {
条件与 && 具有短路原则,即在第一个条件语句为 false 的情况下不会去执行第二个条件语句。利用这一特性,将递归的返回条件取非然后作为 && 的第一个条件语句,递归的主体转换为第二个条件语句,那么当递归的返回条件为 true 的情况下就不会执行递归的主体部分,递归返回。
以下实现中,递归返回条件为 n <= 0取非后就是 n > 0递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。
本题的递归返回条件为 n <= 0取非后就是 n > 0递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。
```java
public int Sum_Solution(int n) {
@ -2875,7 +2874,7 @@ public int Sum_Solution(int n) {
## 题目描述
写一个函数,求两个整数之和,要求在函数体内不得使用 +、-、\*、/ 四则运算符号。
写一个函数,求两个整数之和,要求不得使用 +、-、\*、/ 四则运算符号。
## 解题思路
@ -2895,7 +2894,7 @@ public int Add(int a, int b) {
## 题目描述
给定一个数组 A[0, 1,..., n-1], 请构建一个数组 B[0, 1,..., n-1], 其中 B 中的元素 B[i]=A[0]\*A[1]\*...\*A[i-1]\*A[i+1]\*...\*A[n-1]。不能使用除法
给定一个数组 A[0, 1,..., n-1]请构建一个数组 B[0, 1,..., n-1]其中 B 中的元素 B[i]=A[0]\*A[1]\*...\*A[i-1]\*A[i+1]\*...\*A[n-1]。要求不能使用除法
## 解题思路
@ -2917,7 +2916,7 @@ public int[] multiply(int[] A) {
## 题目描述
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为 0 或者字符串不是一个合法的数值则返回 0。
将一个字符串转换成一个整数字符串不是一个合法的数值则返回 0要求不能使用字符串转换整数的库函数
```html
Iuput:
@ -2979,7 +2978,7 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
[Leetcode : 236. Lowest Common Ancestor of a Binary Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/)
在左右子树中查找是否存在 p 或者 q如果 p 和 q 分别在两个子树中,那么就说明根节点就是 LCA
在左右子树中查找是否存在 p 或者 q如果 p 和 q 分别在两个子树中,那么就说明根节点就是最低公共祖先
```java
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB