diff --git a/.gitignore b/.gitignore index 314f02b1..61f36d59 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.txt \ No newline at end of file +*.txt +.idea/ diff --git a/README.md b/README.md index 4c2d1c28..580b86a6 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,10 @@ | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | | :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:| | 算法[:pencil2:](#算法-pencil2) | 操作系统[:computer:](#操作系统-computer)|网络[:cloud:](#网络-cloud) | 面向对象[:couple:](#面向对象-couple) |数据库[:floppy_disk:](#数据库-floppy_disk)| Java [:coffee:](#java-coffee)| 分布式[:sweat_drops:](#分布式-sweat_drops)| 工具[:hammer:](#工具-hammer)| 编码实践[:speak_no_evil:](#编码实践-speak_no_evil)| 后记[:memo:](#后记-memo) | + +本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.)
-:loudspeaker: 本仓库不参与商业行为,不向读者收取任何费用。 - -:loudspeaker: This repository is not engaging in business activities, and does not charge readers any fee. -

- ## 算法 :pencil2: @@ -147,9 +144,9 @@ Google 开源项目的代码风格规范。 **关于贡献** -因为大部分内容是笔者一个字一个字打上去的,所有难免会有一些笔误。如果发现,可以直接在相应的文档上编辑修改。 +因为大部分内容是笔者一个字一个字打上去的,所以难免会有一些笔误。如果发现,可以直接在相应的文档上编辑修改。 -笔者能力有限,很多内容还不够完善。如果您希望和笔者一起完善这个仓库,可以在发表一个 Issue,表明您想要添加的内容,笔者会及时查看。 +笔者能力有限,很多内容还不够完善。如果您希望和笔者一起完善这个仓库,可以发表一个 Issue,表明您想要添加的内容,笔者会及时查看。 因为不打算将这个仓库做成一个大而全的面试宝典,只希望添加一些比较通用的基础知识,或者是与 Java 和分布式相关的内容,但是不添加 Java Web 相关的内容。 @@ -163,7 +160,7 @@ Google 开源项目的代码风格规范。 **关于排版** -笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/#%E4%B8%8D%E8%A6%81%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%9C%B0%E9%81%93%E7%9A%84%E7%BC%A9%E5%86%99) 进行排版,以保证内容的可读性。这里提供了笔者实现的中英混排文档在线排版工具:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting),目前实现了加空格的功能,之后打算实现对英文专有名词提示首字母大写的功能。 +笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。这里提供了笔者实现的中英混排文档在线排版工具:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting),目前实现了加空格的功能,之后打算实现对英文专有名词提示首字母大写的功能。 不使用 `![]()` 这种方式来引用图片是为了能够控制图片以合适的大小显示。而且 GFM 不支持 `
![]()
` 让图片居中显示,只能使用 `
` ,所以只能使用 img 标签来引用图片。 diff --git a/notes/HTTP.md b/notes/HTTP.md index 97f361c9..1c70e89d 100644 --- a/notes/HTTP.md +++ b/notes/HTTP.md @@ -63,15 +63,15 @@ ## Web 基础 -- HTTP(HyperText Transfer Protocol,超文本传输协议)。 -- WWW(World Wide Web)的三种技术:HTML、HTTP、URL。 +- HTTP(HyperText Transfer Protocol,超文本传输协议) +- WWW(World Wide Web)的三种技术:HTML、HTTP、URL - RFC(Request for Comments,征求修正意见书),互联网的设计文档。 ## URL - URI(Uniform Resource Indentifier,统一资源标识符) - URL(Uniform Resource Locator,统一资源定位符) -- URN(Uniform Resource Name,统一资源名称),例如 urn:isbn:0-486-27557-4 。 +- URN(Uniform Resource Name,统一资源名称),例如 urn:isbn:0-486-27557-4。 URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。 diff --git a/notes/Java 基础.md b/notes/Java 基础.md index ed94785c..34bd383e 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -269,7 +269,7 @@ set.add(e2); System.out.println(set.size()); // 2 ``` -理想的散列函数应当具有均匀性,即不相等的实例应当均匀分不到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。 +理想的散列函数应当具有均匀性,即不相等的实例应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。 一个数与 31 相乘可以转换成移位和减法:31\*x == (x<<5)-x。 @@ -506,9 +506,9 @@ protected 用于修饰成员,表示在继承体系中成员对于子类可见 设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。 -如果子类的方法覆盖了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里式替换原则。 +如果子类的方法覆盖了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。 -字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。可以使用共有的 getter 和 setter 方法来替换共有字段。 +字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。可以使用公有的 getter 和 setter 方法来替换公有字段。 ```java public class AccessExample { @@ -634,14 +634,14 @@ System.out.println(InterfaceExample.x); - 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。 - 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。 -- 接口的字段只能是 static 和 final 类型的,而抽象类的字段可以有多种访问权限。 +- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。 - 接口的方法只能是 public 的,而抽象类的方法可以由多种访问权限。 **4. 使用选择** 使用抽象类: -- 需要在几个相关的类中共享代码; +- 需要在几个相关的类中共享代码。 - 需要能控制继承来的方法和域的访问权限,而不是都为 public。 - 需要继承非静态(non-static)和非常量(non-final)字段。 @@ -771,7 +771,7 @@ String s5 = "bbb"; System.out.println(s4 == s5); // true ``` -Java 虚拟机将堆划分成新生代、老年代和永久代(PermGen Space)。在 Java 7 之前,字符串常量池被放在永久代中,而在 Java 7,它被放在堆的其它位置。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 +在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被放在堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 > [What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning)
[深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html) diff --git a/notes/Java 容器.md b/notes/Java 容器.md index 9893ee27..6791e2ed 100644 --- a/notes/Java 容器.md +++ b/notes/Java 容器.md @@ -212,7 +212,7 @@ private void writeObject(java.io.ObjectOutputStream s) ### 4. 和 LinkedList 的区别 -- ArrayList 基于动态数组实现,LinkedList 基于双向循环链表实现; +- ArrayList 基于动态数组实现,LinkedList 基于双向链表实现; - ArrayList 支持随机访问,LinkedList 不支持; - LinkedList 在任意位置添加删除元素更快。 diff --git a/notes/Java 并发.md b/notes/Java 并发.md index edc9dc0f..61c9fdb8 100644 --- a/notes/Java 并发.md +++ b/notes/Java 并发.md @@ -43,6 +43,7 @@ * [内存模型三大特性](#内存模型三大特性) * [先行发生原则](#先行发生原则) * [十一、线程安全](#十一线程安全) + * [线程安全定义](#线程安全定义) * [线程安全分类](#线程安全分类) * [线程安全的实现方法](#线程安全的实现方法) * [十二、锁优化](#十二锁优化) @@ -86,7 +87,7 @@ ## 限期等待(Timed Waiting) -无需等待其它线程显示地唤醒,在一定时间之后会被系统自动唤醒。 +无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。 调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。 @@ -188,7 +189,7 @@ public static void main(String[] args) { ## Executor -Executor 管理多个异步任务的执行,而无需程序员显示地管理线程的生命周期。 +Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。 主要有三种 Executor: @@ -715,10 +716,10 @@ java.util.concurrent(J.U.C)大大提高了并发性能,AQS 被认为是 J. public class CountdownLatchExample { public static void main(String[] args) throws InterruptedException { - final int totalTread = 10; - CountDownLatch countDownLatch = new CountDownLatch(totalTread); + final int totalThread = 10; + CountDownLatch countDownLatch = new CountDownLatch(totalThread); ExecutorService executorService = Executors.newCachedThreadPool(); - for (int i = 0; i < totalTread; i++) { + for (int i = 0; i < totalThread; i++) { executorService.execute(() -> { System.out.print("run.."); countDownLatch.countDown(); @@ -747,12 +748,11 @@ run..run..run..run..run..run..run..run..run..run..end ```java public class CyclicBarrierExample { - public static void main(String[] args) throws InterruptedException { - final int totalTread = 10; - CyclicBarrier cyclicBarrier = new CyclicBarrier(totalTread); + final int totalThread = 10; + CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread); ExecutorService executorService = Executors.newCachedThreadPool(); - for (int i = 0; i < totalTread; i++) { + for (int i = 0; i < totalThread; i++) { executorService.execute(() -> { System.out.print("before.."); try { @@ -1235,6 +1235,10 @@ join() 方法返回先行发生于 Thread 对象的结束。 # 十一、线程安全 +## 线程安全定义 + +一个类在可以被多个线程安全调用时就是线程安全的。 + ## 线程安全分类 线程安全不是一个非真即假的命题,可以将共享数据按照安全程度的强弱顺序分成以下五类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。 @@ -1507,7 +1511,7 @@ public class ThreadLocalExample1 {

-每个 Thread 都有一个 TreadLocal.ThreadLocalMap 对象,Thread 类中就定义了 ThreadLocal.ThreadLocalMap 成员。 +每个 Thread 都有一个 ThreadLocal.ThreadLocalMap 对象,Thread 类中就定义了 ThreadLocal.ThreadLocalMap 成员。 ```java /* ThreadLocal values pertaining to this thread. This map is maintained @@ -1595,7 +1599,7 @@ public static String concatString(String s1, String s2, String s3) { ## 轻量级锁 -轻量级锁是 JDK 1.6 之中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就称为“重量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重要级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。 +轻量级锁是 JDK 1.6 之中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就称为“重量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。 要理解轻量级锁,以及后面会讲到的偏向锁的原理和运作过程,必须从 HotSpot 虚拟机的对象(对象头部分)的内存布局开始介绍。HotSpot 虚拟机的对象头(Object Header)分为两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄(Generational GC Age)等,这部分数据是长度在 32 位和 64 位的虚拟机中分别为 32 bit 和 64 bit,官方称它为“Mark Word”,它是实现轻量级锁和偏向锁的关键。另外一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。 diff --git a/notes/Java 虚拟机.md b/notes/Java 虚拟机.md index 5173f2d5..4b72b865 100644 --- a/notes/Java 虚拟机.md +++ b/notes/Java 虚拟机.md @@ -123,7 +123,7 @@ objB.instance = objA; ### 2. 可达性 -通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是都是可用的,不可达的对象可被回收。 +通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是可用的,不可达的对象可被回收。

@@ -142,7 +142,7 @@ Java 对引用的概念进行了扩充,引入四种强度不同的引用类型 **(一)强引用** -只要强引用存在,垃圾回收器永远不会回收掉被引用的对象。 +只要强引用存在,垃圾回收器永远不会回收被引用的对象。 使用 new 一个新对象的方式来创建强引用。 diff --git a/notes/Leetcode 题解.md b/notes/Leetcode 题解.md index 73f19098..e96fa6f9 100644 --- a/notes/Leetcode 题解.md +++ b/notes/Leetcode 题解.md @@ -7,6 +7,7 @@ * [快速选择](#快速选择) * [堆排序](#堆排序) * [桶排序](#桶排序) + * [荷兰国旗问题](#荷兰国旗问题) * [搜索](#搜索) * [BFS](#bfs) * [DFS](#dfs) @@ -44,6 +45,9 @@ * [BST](#bst) * [Trie](#trie) * [图](#图) + * [二分图](#二分图) + * [拓扑排序](#拓扑排序) + * [并查集](#并查集) * [位运算](#位运算) * [参考资料](#参考资料) @@ -53,30 +57,87 @@ ## 二分查找 +**正常实现** + ```java -public int search(int key, int[] array) { - int l = 0, h = array.length - 1; +public int binarySearch(int[] nums, int key) { + int l = 0, h = nums.length - 1; while (l <= h) { - int mid = l + (h - l) / 2; - if (key == array[mid]) return mid; - if (key < array[mid]) h = mid - 1; - else l = mid + 1; + int m = l + (h - l) / 2; + if (nums[m] == key) + return m; + else if (nums[m] > key) + h = m - 1; + else + l = m + 1; } return -1; } ``` -实现时需要注意以下细节: +**时间复杂度** -- 在计算 mid 时不能使用 mid = (l + h) / 2 这种方式,因为 l + h 可能会导致加法溢出,应该使用 mid = l + (h - l) / 2。 +O(logN) -- 对 h 的赋值和循环条件有关,当循环条件为 l <= h 时,h = mid - 1;当循环条件为 l < h 时,h = mid。解释如下:在循环条件为 l <= h 时,如果 h = mid,会出现循环无法退出的情况,例如 l = 1,h = 1,此时 mid 也等于 1,如果此时继续执行 h = mid,那么就会无限循环;在循环条件为 l < h,如果 h = mid - 1,会错误跳过查找的数,例如对于数组 [1,2,3],要查找 1,最开始 l = 0,h = 2,mid = 1,判断 key < arr[mid] 执行 h = mid - 1 = 0,此时循环退出,直接把查找的数跳过了。 +**计算 mid** -- l 的赋值一般都为 l = mid + 1。 +有两种计算 mid 的方式: + +- mid = (l + h) / 2 +- mid = l + (h - l) / 2 + +l + h 可能出现加法溢出,最好使用第二种方式。 + +**返回值** + +循环退出时如果仍然没有查找到 key,那么表示查找失败。可以有两种返回值: + +- -1:以一个错误码指示没有查找到 key +- l:将 key 插入到 nums 中的正确位置 + +**变种** + +二分查找可以有很多变种,变种实现要多注意边界值的判断。例如在一个有重复元素的数组中查找 key 的最左位置的实现如下: + +```java +public int binarySearch(int[] nums, int key) { + int l = 0, h = nums.length - 1; + while (l < h) { + int m = l + (h - l) / 2; + if (nums[m] >= key) + h = m; + else + l = m + 1; + } + return l; +} +``` + +该实现和正常实现有以下不同: + +- 循环条件为 l < h +- h 的赋值表达式为 h = m +- 最后返回 l 而不是 -1 + +在 nums[m] >= key 的情况下,可以推导出最左 key 位于 [l, m] 区间中,这是一个闭区间。h 的赋值表达式为 h = m,因为 m 位置也可能是解。 + +在 h 的赋值表达式为 h = mid 的情况下,如果循环条件为 l <= h,那么会出现循环无法退出的情况,因此循环条件只能是 l < h。 + +```text +nums = {0, 1}, key = 0 +l m h +0 1 2 nums[m] >= key +0 0 1 nums[m] >= key +0 0 0 nums[m] >= key +0 0 0 nums[m] >= key +... +``` + +当循环体退出时,不表示没有查找到 key,因此最后返回的结果不应该为 -1。为了验证有没有查找到,需要在调用端判断一下返回位置上的值和 key 是否相等。 **求开方** -[Leetcode : 69. Sqrt(x) (Easy)](https://leetcode.com/problems/sqrtx/description/) +[69. Sqrt(x) (Easy)](https://leetcode.com/problems/sqrtx/description/) ```html Input: 4 @@ -87,105 +148,192 @@ Output: 2 Explanation: The square root of 8 is 2.82842..., and since we want to return an integer, the decimal part will be truncated. ``` -一个数 x 的开方 sqrt 一定在 0 \~ x 之间,并且满足 sqrt == x / sqrt 。可以利用二分查找在 0 \~ x 之间查找 sqrt。 +一个数 x 的开方 sqrt 一定在 0 \~ x 之间,并且满足 sqrt == x / sqrt。可以利用二分查找在 0 \~ x 之间查找 sqrt。 + +对于 x = 8,它的开方是 2.82842...,最后应该返回 2 而不是 3。在循环条件为 l <= h 并且循环退出时,h 总是比 l 小 1,也就是说 h = 2,l = 3,因此最后的返回值应该为 h 而不是 l。 ```java public int mySqrt(int x) { - if(x <= 1) return x; + if (x <= 1) + return x; int l = 1, h = x; - while(l <= h){ + while (l <= h) { int mid = l + (h - l) / 2; int sqrt = x / mid; - if(sqrt == mid) return mid; - else if(sqrt < mid) h = mid - 1; - else l = mid + 1; + if (sqrt == mid) + return mid; + else if (sqrt < mid) + h = mid - 1; + else + l = mid + 1; } return h; } ``` -**摆硬币** +**大于给定元素的最小元素** -[Leetcode : 441. Arranging Coins (Easy)](https://leetcode.com/problems/arranging-coins/description/) +[744. Find Smallest Letter Greater Than Target (Easy)](https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/) ```html -n = 8 -The coins can form the following rows: -¤ -¤ ¤ -¤ ¤ ¤ -¤ ¤ -Because the 4th row is incomplete, we return 3. +Input: +letters = ["c", "f", "j"] +target = "d" +Output: "f" + +Input: +letters = ["c", "f", "j"] +target = "k" +Output: "c" ``` -题目描述:第 i 行摆 i 个,统计能够摆的行数。 +题目描述:给定一个有序的字符数组 letters 和一个字符 target,要求找出 letters 中大于 target 的最小字符。letters 字符数组是循环数组。 -返回 h 而不是 l,因为摆的硬币最后一行不能算进去。 +应该注意最后返回的是 l 位置的字符。 ```java -public int arrangeCoins(int n) { - int l = 0, h = n; - while(l <= h){ +public char nextGreatestLetter(char[] letters, char target) { + int n = letters.length; + int l = 0, h = n - 1; + while (l <= h) { int m = l + (h - l) / 2; - long x = m * (m + 1L) / 2; - if(x == n) return m; - else if(x < n) l = m + 1; - else h = m - 1; + if (letters[m] <= target) + l = m + 1; + else + h = m - 1; } - return h; -} -``` - -可以不用二分查找,更直观的解法如下: - -```java -public int arrangeCoins(int n) { - int level = 1; - while (n > 0) { - n -= level; - level++; - } - return n == 0 ? level - 1 : level - 2; + return l < n ? letters[l] : letters[0]; } ``` **有序数组的 Single Element** -[Leetcode : 540. Single Element in a Sorted Array (Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/description/) +[540. Single Element in a Sorted Array (Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/description/) ```html Input: [1,1,2,3,3,4,4,8,8] Output: 2 ``` -题目描述:一个有序数组只有一个数不出现两次,找出这个数。 +题目描述:一个有序数组只有一个数不出现两次,找出这个数。要求以 O(logN) 时间复杂度进行求解。 + +令 key 为 Single Element 在数组中的位置。如果 m 为偶数,并且 m + 1 < key,那么 nums[m] == nums[m + 1];m + 1 >= key,那么 nums[m] != nums[m + 1]。 + +从上面的规律可以知道,如果 nums[m] == nums[m + 1],那么 key 所在的数组位置为 [m + 2, h],此时令 l = m + 2;如果 nums[m] != nums[m + 1],那么 key 所在的数组位置为 [l, m],此时令 h = m。 + +因为 h 的赋值表达式为 h = m,那么循环条件也就只能使用 l < h 这种形式。 ```java public int singleNonDuplicate(int[] nums) { int l = 0, h = nums.length - 1; - while(l < h) { + while (l < h) { int m = l + (h - l) / 2; - if(m % 2 == 1) m--; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数 - if(nums[m] == nums[m + 1]) l = m + 2; - else h = m; + if (m % 2 == 1) + m--; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数 + if (nums[m] == nums[m + 1]) + l = m + 2; + else + h = m; } return nums[l]; } ``` +**第一个错误的版本** + +[278. First Bad Version (Easy)](https://leetcode.com/problems/first-bad-version/description/) + +题目描述:给定一个元素 n 代表有 [1, 2, ..., n] 版本,可以调用 isBadVersion(int x) 知道某个版本是否错误,要求找到第一个错误的版本。 + +如果第 m 个版本出错,则表示第一个错误的版本在 [l, m] 之间,令 h = m;否则第一个错误的版本在 [m + 1, h] 之间,令 l = m + 1。 + +因为 h 的赋值表达式为 h = m,因此循环条件为 l < h。 + +```java +public int firstBadVersion(int n) { + int l = 1, h = n; + while (l < h) { + int mid = l + (h - l) / 2; + if (isBadVersion(mid)) + h = mid; + else + l = mid + 1; + } + return l; +} +``` + +**旋转数组的最小数字** + +[153. Find Minimum in Rotated Sorted Array (Medium)](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/) + +```html +Input: [3,4,5,1,2], +Output: 1 +``` + +```java +public int findMin(int[] nums) { + 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]; +} +``` + +**查找区间** + +[34. Search for a Range (Medium)](https://leetcode.com/problems/search-for-a-range/description/) + +```html +Input: nums = [5,7,7,8,8,10], target = 8 +Output: [3,4] + +Input: nums = [5,7,7,8,8,10], target = 6 +Output: [-1,-1] +``` + +```java +public int[] searchRange(int[] nums, int target) { + int first = binarySearch(nums, target); + int last = binarySearch(nums, target + 1) - 1; + if (first == nums.length || nums[first] != target) + return new int[]{-1, -1}; + else + return new int[]{first, Math.max(first, last)}; +} + +private int binarySearch(int[] nums, int target) { + int l = 0, h = nums.length; // 注意 h 的初始值 + while (l < h) { + int m = l + (h - l) / 2; + if (nums[m] >= target) + h = m; + else + l = m + 1; + } + return l; +} +``` + ## 贪心思想 贪心思想保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。 **分配饼干** -[Leetcode : 455. Assign Cookies (Easy)](https://leetcode.com/problems/assign-cookies/description/) +[455. Assign Cookies (Easy)](https://leetcode.com/problems/assign-cookies/description/) ```html Input: [1,2], [1,2,3] Output: 2 -Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2. +Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2. You have 3 cookies and their sizes are big enough to gratify all of the children, You need to output 2. ``` @@ -194,60 +342,25 @@ You need to output 2. 因为最小的孩子最容易得到满足,因此先满足最小孩子。给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。 -证明:假设在某次选择中,贪心策略选择给第 i 个孩子分配第 m 个饼干,并且第 i 个孩子满足度最小,第 m 个饼干为可以满足第 i 个孩子的最小饼干,利用贪心策略最终可以满足 k 个孩子。假设最优策略在这次选择中给 i 个孩子分配第 n 个饼干,并且这个饼干大于第 m 个饼干。我们发现使用第 m 个饼干去替代第 n 个饼干完全不影响后续的结果,因此不存在比贪心策略更优的策略,即贪心策略就是最优策略。 +证明:假设在某次选择中,贪心策略选择给第 i 个孩子分配第 m 个饼干,并且第 i 个孩子满足度最小,第 m 个饼干为可以满足第 i 个孩子的最小饼干。假设最优策略在这次选择中给 i 个孩子分配第 n 个饼干,并且这个饼干大于第 m 个饼干。我们发现使用第 m 个饼干去替代第 n 个饼干完全不影响后续的结果,因此不存在比贪心策略更优的策略,即贪心策略就是最优策略。 ```java public int findContentChildren(int[] g, int[] s) { Arrays.sort(g); Arrays.sort(s); - int i = 0, j = 0; - while(i < g.length && j < s.length){ - if(g[i] <= s[j]) i++; - j++; + int gIndex = 0, sIndex = 0; + while (gIndex < g.length && sIndex < s.length) { + if (g[gIndex] <= s[sIndex]) + gIndex++; + sIndex++; } - return i; + return gIndex; } ``` -**投飞镖刺破气球** - -[Leetcode : 452. Minimum Number of Arrows to Burst Balloons (Medium)](https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/description/) - -``` -Input: -[[10,16], [2,8], [1,6], [7,12]] - -Output: -2 -``` - -题目描述:气球在一个水平数轴上摆放,可以重叠,飞镖垂直射向坐标轴,使得路径上的气球都会刺破。求解最小的投飞镖次数使所有气球都被刺破。 - -从左往右投飞镖,并且在每次投飞镖时满足以下条件: - -1. 左边已经没有气球了; -2. 本次投飞镖能够刺破最多的气球。 - -```java -public int findMinArrowShots(int[][] points) { - if(points.length == 0) return 0; - Arrays.sort(points,(a,b) -> (a[1] - b[1])); - int curPos = points[0][1]; - int ret = 1; - for (int i = 1; i < points.length; i++) { - if(points[i][0] <= curPos) { - continue; - } - curPos = points[i][1]; - ret++; - } - return ret; - } -``` - **股票的最大收益** -[Leetcode : 122. Best Time to Buy and Sell Stock II (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/) +[122. Best Time to Buy and Sell Stock II (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/) 题目描述:一次交易包含买入和卖出,多个交易之间不能交叉进行。 @@ -256,32 +369,35 @@ public int findMinArrowShots(int[][] points) { ```java public int maxProfit(int[] prices) { int profit = 0; - for(int i = 1; i < prices.length; i++){ - if(prices[i] > prices[i-1]) profit += (prices[i] - prices[i-1]); - } + for (int i = 1; i < prices.length; i++) + if (prices[i] > prices[i - 1]) + profit += (prices[i] - prices[i - 1]); + return profit; } ``` **种植花朵** -[Leetcode : 605. Can Place Flowers (Easy)](https://leetcode.com/problems/can-place-flowers/description/) +[605. Can Place Flowers (Easy)](https://leetcode.com/problems/can-place-flowers/description/) ```html Input: flowerbed = [1,0,0,0,1], n = 1 Output: True ``` -题目描述:花朵之间至少需要一个单位的间隔。 +题目描述:花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。 ```java public boolean canPlaceFlowers(int[] flowerbed, int n) { + int len = flowerbed.length; int cnt = 0; - for(int i = 0; i < flowerbed.length; i++){ - if(flowerbed[i] == 1) continue; + for (int i = 0; i < len; i++) { + if (flowerbed[i] == 1) + continue; int pre = i == 0 ? 0 : flowerbed[i - 1]; - int next = i == flowerbed.length - 1 ? 0 : flowerbed[i + 1]; - if(pre == 0 && next == 0) { + int next = i == len - 1 ? 0 : flowerbed[i + 1]; + if (pre == 0 && next == 0) { cnt++; flowerbed[i] = 1; } @@ -292,7 +408,7 @@ public boolean canPlaceFlowers(int[] flowerbed, int n) { **修改一个数成为非递减数组** -[Leetcode : 665. Non-decreasing Array (Easy)](https://leetcode.com/problems/non-decreasing-array/description/) +[665. Non-decreasing Array (Easy)](https://leetcode.com/problems/non-decreasing-array/description/) ```html Input: [4,2,3] @@ -302,17 +418,19 @@ Explanation: You could modify the first 4 to 1 to get a non-decreasing array. 题目描述:判断一个数组能不能只修改一个数就成为非递减数组。 -在出现 nums[i] < nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且 **不影响后续的操作** 。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,那么就有可能比 nums[i + 1] 大,从而影响了后续操作。还有一个比较特别的情况就是 nums[i] < nums[i - 2],只修改 nums[i - 1] = nums[i] 不能令数组成为非递减,只能通过修改 nums[i] = nums[i - 1] 才行。 +在出现 nums[i] < nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且 **不影响后续的操作** 。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,就有可能比 nums[i + 1] 大,从而影响了后续操作。还有一个比较特别的情况就是 nums[i] < nums[i - 2],只修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组,只能修改 nums[i] = nums[i - 1]。 ```java public boolean checkPossibility(int[] nums) { int cnt = 0; - for(int i = 1; i < nums.length; i++){ - if(nums[i] < nums[i - 1]){ - cnt++; - if(i - 2 >= 0 && nums[i - 2] > nums[i]) nums[i] = nums[i-1]; - else nums[i - 1] = nums[i]; - } + for (int i = 1; i < nums.length && cnt < 2; i++) { + if (nums[i] >= nums[i - 1]) + continue; + cnt++; + if (i - 2 >= 0 && nums[i - 2] > nums[i]) + nums[i] = nums[i - 1]; + else + nums[i - 1] = nums[i]; } return cnt <= 1; } @@ -320,7 +438,7 @@ public boolean checkPossibility(int[] nums) { **判断是否为子串** -[Leetcode : 392. Is Subsequence (Medium)](https://leetcode.com/problems/is-subsequence/description/) +[392. Is Subsequence (Medium)](https://leetcode.com/problems/is-subsequence/description/) ```html s = "abc", t = "ahbgdc" @@ -329,18 +447,99 @@ Return true. ```java public boolean isSubsequence(String s, String t) { - int index = 0; + int index = -1; for (char c : s.toCharArray()) { - index = t.indexOf(c, index); - if (index == -1) return false; + index = t.indexOf(c, index + 1); + if (index == -1) + return false; } return true; } ``` +**不重叠的区间个数** + +[435. Non-overlapping Intervals (Medium)](https://leetcode.com/problems/non-overlapping-intervals/description/) + +```html +Input: [ [1,2], [1,2], [1,2] ] + +Output: 2 + +Explanation: You need to remove two [1,2] to make the rest of intervals non-overlapping. +``` + +```html +Input: [ [1,2], [2,3] ] + +Output: 0 + +Explanation: You don't need to remove any of the intervals since they're already non-overlapping. +``` + +题目描述:计算让一组区间不重叠所需要移除的区间个数。 + +直接计算最多能组成的不重叠区间个数即可。 + +在每次选择中,区间的结尾最为重要,选择的区间结尾越小,留给后面的区间的空间越大,那么后面能够选择的区间个数也就越大。 + +按区间的结尾进行排序,每次选择结尾最小,并且和前一个区间不重叠的区间。 + +```java +public int eraseOverlapIntervals(Interval[] intervals) { + if (intervals.length == 0) + return 0; + Arrays.sort(intervals, Comparator.comparingInt(o -> o.end)); + int cnt = 1; + int end = intervals[0].end; + for (int i = 1; i < intervals.length; i++) { + if (intervals[i].start < end) + continue; + end = intervals[i].end; + cnt++; + } + return intervals.length - cnt; +} +``` + +**投飞镖刺破气球** + +[452. Minimum Number of Arrows to Burst Balloons (Medium)](https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/description/) + +``` +Input: +[[10,16], [2,8], [1,6], [7,12]] + +Output: +2 +``` + +题目描述:气球在一个水平数轴上摆放,可以重叠,飞镖垂直投向坐标轴,使得路径上的气球都会刺破。求解最小的投飞镖次数使所有气球都被刺破。 + +也是计算不重叠的区间个数,不过和 Non-overlapping Intervals 的区别在于,[1, 2] 和 [2, 3] 在本题中算是重叠区间。 + +```java +public int findMinArrowShots(int[][] points) { + if (points.length == 0) + return 0; + + Arrays.sort(points, Comparator.comparingInt(o -> o[1])); + + int cnt = 1, end = points[0][1]; + for (int i = 1; i < points.length; i++) { + if (points[i][0] <= end) // [1,2] 和 [2,3] 算重叠 + continue; + cnt++; + end = points[i][1]; + } + return cnt; +} +``` + + **分隔字符串使同种字符出现在一起** -[Leetcode : 763. Partition Labels (Medium)](https://leetcode.com/problems/partition-labels/description/) +[763. Partition Labels (Medium)](https://leetcode.com/problems/partition-labels/description/) ```html Input: S = "ababcbacadefegdehijhklij" @@ -352,30 +551,30 @@ A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits ``` ```java -public List partitionLabels(String S) { - List ret = new ArrayList<>(); - int[] lastIndexs = new int[26]; - for (int i = 0; i < S.length(); i++) { - lastIndexs[S.charAt(i) - 'a'] = i; - } - int firstIndex = 0; - while (firstIndex < S.length()) { - int lastIndex = firstIndex; - for (int i = firstIndex; i < S.length() && i <= lastIndex; i++) { - int index = lastIndexs[S.charAt(i) - 'a']; - if (index == i) continue; - if (index > lastIndex) lastIndex = index; - } - ret.add(lastIndex - firstIndex + 1); - firstIndex = lastIndex + 1; - } - return ret; -} + public List partitionLabels(String S) { + int[] lastIndexs = new int[26]; + for (int i = 0; i < S.length(); i++) + lastIndexs[S.charAt(i) - 'a'] = i; + + List ret = new ArrayList<>(); + int firstIndex = 0; + while (firstIndex < S.length()) { + int lastIndex = firstIndex; + for (int i = firstIndex; i < S.length() && i <= lastIndex; i++) { + int index = lastIndexs[S.charAt(i) - 'a']; + if (index > lastIndex) + lastIndex = index; + } + ret.add(lastIndex - firstIndex + 1); + firstIndex = lastIndex + 1; + } + return ret; + } ``` **根据身高和序号重组队列** -[Leetcode : 406. Queue Reconstruction by Height(Medium)](https://leetcode.com/problems/queue-reconstruction-by-height/description/) +[406. Queue Reconstruction by Height(Medium)](https://leetcode.com/problems/queue-reconstruction-by-height/description/) ```html Input: @@ -393,25 +592,17 @@ Output: ```java public int[][] reconstructQueue(int[][] people) { - if (people == null || people.length == 0 || people[0].length == 0) return new int[0][0]; - Arrays.sort(people, (a, b) -> { - if (a[0] == b[0]) return a[1] - b[1]; - return b[0] - a[0]; - }); - int N = people.length; - List tmp = new ArrayList<>(); - for (int i = 0; i < N; i++) { - int index = people[i][1]; - int[] p = new int[]{people[i][0], people[i][1]}; - tmp.add(index, p); - } + if (people == null || people.length == 0 || people[0].length == 0) + return new int[0][0]; - int[][] ret = new int[N][2]; - for (int i = 0; i < N; i++) { - ret[i][0] = tmp.get(i)[0]; - ret[i][1] = tmp.get(i)[1]; - } - return ret; + Arrays.sort(people, (a, b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0])); + + List queue = new ArrayList<>(); + + for (int[] p : people) + queue.add(p[1], p); + + return queue.toArray(new int[queue.size()][]); } ``` @@ -428,9 +619,9 @@ Input: numbers={2, 7, 11, 15}, target=9 Output: index1=1, index2=2 ``` -题目描述:从一个已经排序的数组中找出两个数,使它们的和为 0。 +题目描述:在有序数组中找出两个数,使它们的和为 target。 -使用双指针,一个指针指向元素较小的值,一个指针指向元素较大的值。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。 +使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。 如果两个指针指向元素的和 sum == target,那么得到要求的结果;如果 sum > target,移动较大的元素,使 sum 变小一些;如果 sum < target,移动较小的元素,使 sum 变大一些。 @@ -439,54 +630,20 @@ public int[] twoSum(int[] numbers, int target) { int i = 0, j = numbers.length - 1; while (i < j) { int sum = numbers[i] + numbers[j]; - if (sum == target) return new int[]{i + 1, j + 1}; - else if (sum < target) i++; - else j--; + if (sum == target) + return new int[]{i + 1, j + 1}; + else if (sum < target) + i++; + else + j--; } return null; } ``` -**反转字符串中的元音字符** - -[Leetcode : 345. Reverse Vowels of a String (Easy)](https://leetcode.com/problems/reverse-vowels-of-a-string/description/) - -```html -Given s = "leetcode", return "leotcede". -``` - -使用双指针,指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。 - -```java -private HashSet vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U')); - -public String reverseVowels(String s) { - if (s.length() == 0) return s; - int i = 0, j = s.length() - 1; - char[] result = new char[s.length()]; - while (i <= j) { - char ci = s.charAt(i); - char cj = s.charAt(j); - if (!vowels.contains(ci)) { - result[i] = ci; - i++; - } else if (!vowels.contains(cj)) { - result[j] = cj; - j--; - } else { - result[i] = cj; - result[j] = ci; - i++; - j--; - } - } - return new String(result); -} -``` - **两数平方和** -[Leetcode : 633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/) +[633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/) ```html Input: 5 @@ -501,17 +658,52 @@ public boolean judgeSquareSum(int c) { int i = 0, j = (int) Math.sqrt(c); while (i <= j) { int powSum = i * i + j * j; - if (powSum == c) return true; - if (powSum > c) j--; - else i++; + if (powSum == c) + return true; + if (powSum > c) + j--; + else + i++; } return false; } ``` +**反转字符串中的元音字符** + +[345. Reverse Vowels of a String (Easy)](https://leetcode.com/problems/reverse-vowels-of-a-string/description/) + +```html +Given s = "leetcode", return "leotcede". +``` + +使用双指针,指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。 + +```java +private HashSet vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U')); + +public String reverseVowels(String s) { + int i = 0, j = s.length() - 1; + char[] result = new char[s.length()]; + while (i <= j) { + char ci = s.charAt(i); + char cj = s.charAt(j); + if (!vowels.contains(ci)) + result[i++] = ci; + else if (!vowels.contains(cj)) + result[j--] = cj; + else { + result[i++] = cj; + result[j--] = ci; + } + } + return new String(result); +} +``` + **回文字符串** -[Leetcode : 680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/) +[680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/) ```html Input: "abca" @@ -523,40 +715,51 @@ Explanation: You could delete the character 'c'. ```java public boolean validPalindrome(String s) { - int i = 0, j = s.length() - 1; - while (i < j) { - if (s.charAt(i) != s.charAt(j)) { + int i = -1, j = s.length(); + while (++i < --j) + if (s.charAt(i) != s.charAt(j)) return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j); - } - i++; - j--; - } + return true; } -private boolean isPalindrome(String s, int l, int r) { - while (l < r) { - if (s.charAt(l++) != s.charAt(r--)) return false; - } +private boolean isPalindrome(String s, int i, int j) { + while (i < j) + if (s.charAt(i++) != s.charAt(j--)) + return false; + return true; } ``` **归并两个有序数组** -[Leetcode : 88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/) +[88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/) + +```html +Input: +nums1 = [1,2,3,0,0,0], m = 3 +nums2 = [2,5,6], n = 3 + +Output: [1,2,2,3,5,6] +``` 题目描述:把归并结果存到第一个数组上。 ```java public void merge(int[] nums1, int m, int[] nums2, int n) { - int i = m - 1, j = n - 1; // 需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值 + // 需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值 + int i = m - 1, j = n - 1; int index = m + n - 1; while (i >= 0 || j >= 0) { - if (i < 0) nums1[index] = nums2[j--]; - else if (j < 0) nums1[index] = nums1[i--]; - else if (nums1[i] > nums2[j]) nums1[index] = nums1[i--]; - else nums1[index] = nums2[j--]; + if (i < 0) + nums1[index] = nums2[j--]; + else if (j < 0) + nums1[index] = nums1[i--]; + else if (nums1[i] > nums2[j]) + nums1[index] = nums1[i--]; + else + nums1[index] = nums2[j--]; index--; } } @@ -564,18 +767,19 @@ public void merge(int[] nums1, int m, int[] nums2, int n) { **判断链表是否存在环** -[Leetcode : 141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/) +[141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/) 使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。 ```java public boolean hasCycle(ListNode head) { - if (head == null) return false; + if (head == null) + return false; ListNode l1 = head, l2 = head.next; - while (l1 != null && l2 != null) { - if (l1 == l2) return true; + while (l1 != null && l2 != null && l2.next != null) { + if (l1 == l2) + return true; l1 = l1.next; - if (l2.next == null) break; l2 = l2.next.next; } return false; @@ -584,7 +788,7 @@ public boolean hasCycle(ListNode head) { **最长子序列** -[Leetcode : 524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/) +[524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/) ``` Input: @@ -598,18 +802,25 @@ Output: ```java public String findLongestWord(String s, List d) { - String ret = ""; - for (String str : d) { - for (int i = 0, j = 0; i < s.length() && j < str.length(); i++) { - if (s.charAt(i) == str.charAt(j)) j++; - if (j == str.length()) { - if (ret.length() < str.length() || (ret.length() == str.length() && ret.compareTo(str) > 0)) { - ret = str; - } - } - } + String longestWord = ""; + for (String target : d) { + int l1 = longestWord.length(), l2 = target.length(); + if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)) + continue; + if (isValid(s, target)) + longestWord = target; } - return ret; + return longestWord; +} + +private boolean isValid(String s, String target) { + int i = 0, j = 0; + while (i < s.length() && j < target.length()) { + if (s.charAt(i) == target.charAt(j)) + j++; + i++; + } + return j == target.length(); } ``` @@ -623,11 +834,11 @@ public String findLongestWord(String s, List d) { ### 堆排序 -堆排序用于求解 **TopK Elements** 问题,通过维护一个大小为 K 的堆,堆中的元素就是 TopK Elements。当然它也可以用于求解 Kth Element 问题,因为最后出堆的那个元素就是 Kth Element。快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。 +堆排序用于求解 **TopK Elements** 问题,通过维护一个大小为 K 的堆,堆中的元素就是 TopK Elements。当然它也可以用于求解 Kth Element 问题,堆顶元素就是 Kth Element。快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。 **Kth Element** -[Leetocde : 215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/) +[215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/) **排序** :时间复杂度 O(NlogN),空间复杂度 O(1) @@ -642,12 +853,11 @@ public int findKthLargest(int[] nums, int k) { ```java public int findKthLargest(int[] nums, int k) { - PriorityQueue pq = new PriorityQueue<>(); + PriorityQueue pq = new PriorityQueue<>(); // 小顶堆 for (int val : nums) { pq.add(val); - if (pq.size() > k) { + if (pq.size() > k) // 维护堆的大小为 K pq.poll(); - } } return pq.peek(); } @@ -661,9 +871,12 @@ public int findKthLargest(int[] nums, int k) { int l = 0, h = nums.length - 1; while (l < h) { int j = partition(nums, l, h); - if (j == k) break; - if (j < k) l = j + 1; - else h = j - 1; + if (j == k) + break; + else if (j < k) + l = j + 1; + else + h = j - 1; } return nums[k]; } @@ -671,9 +884,10 @@ public int findKthLargest(int[] nums, int k) { private int partition(int[] a, int l, int h) { int i = l, j = h + 1; while (true) { - while (i < h && less(a[++i], a[l])) ; - while (j > l && less(a[l], a[--j])) ; - if (i >= j) break; + while (a[++i] < a[l] && i < h) ; + while (a[--j] > a[l] && j > l) ; + if (i >= j) + break; swap(a, i, j); } swap(a, l, j); @@ -681,13 +895,9 @@ private int partition(int[] a, int l, int h) { } private void swap(int[] a, int i, int j) { - int tmp = a[i]; + int t = a[i]; a[i] = a[j]; - a[j] = tmp; -} - -private boolean less(int v, int w) { - return v < w; + a[j] = t; } ``` @@ -695,7 +905,7 @@ private boolean less(int v, int w) { **出现频率最多的 k 个数** -[Leetcode : 347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/) +[347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/) ```html Given [1,1,1,2,2,3] and k = 2, return [1,2]. @@ -705,32 +915,30 @@ Given [1,1,1,2,2,3] and k = 2, return [1,2]. ```java public List topKFrequent(int[] nums, int k) { - List ret = new ArrayList<>(); Map frequencyMap = new HashMap<>(); - for (int num : nums) { + for (int num : nums) frequencyMap.put(num, frequencyMap.getOrDefault(num, 0) + 1); - } + List[] bucket = new List[nums.length + 1]; for (int key : frequencyMap.keySet()) { int frequency = frequencyMap.get(key); - if (bucket[frequency] == null) { + if (bucket[frequency] == null) bucket[frequency] = new ArrayList<>(); - } bucket[frequency].add(key); } - for (int i = bucket.length - 1; i >= 0 && ret.size() < k; i--) { - if (bucket[i] != null) { - ret.addAll(bucket[i]); - } - } - return ret; + List topK = new ArrayList<>(); + for (int i = bucket.length - 1; i >= 0 && topK.size() < k; i--) + if (bucket[i] != null) + topK.addAll(bucket[i]); + + return topK; } ``` **按照字符出现次数对字符串排序** -[Leetcode : 451. Sort Characters By Frequency (Medium)](https://leetcode.com/problems/sort-characters-by-frequency/description/) +[451. Sort Characters By Frequency (Medium)](https://leetcode.com/problems/sort-characters-by-frequency/description/) ```html Input: @@ -746,33 +954,68 @@ So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid ans ```java public String frequencySort(String s) { - Map map = new HashMap<>(); - for (char c : s.toCharArray()) { - map.put(c, map.getOrDefault(c, 0) + 1); - } + Map frequencyMap = new HashMap<>(); + for (char c : s.toCharArray()) + frequencyMap.put(c, frequencyMap.getOrDefault(c, 0) + 1); + List[] frequencyBucket = new List[s.length() + 1]; - for(char c : map.keySet()){ - int f = map.get(c); - if (frequencyBucket[f] == null) { + for (char c : frequencyMap.keySet()) { + int f = frequencyMap.get(c); + if (frequencyBucket[f] == null) frequencyBucket[f] = new ArrayList<>(); - } frequencyBucket[f].add(c); } StringBuilder str = new StringBuilder(); for (int i = frequencyBucket.length - 1; i >= 0; i--) { - if (frequencyBucket[i] == null) { + if (frequencyBucket[i] == null) continue; - } - for (char c : frequencyBucket[i]) { - for (int j = 0; j < i; j++) { + for (char c : frequencyBucket[i]) + for (int j = 0; j < i; j++) str.append(c); - } - } } return str.toString(); } ``` +### 荷兰国旗问题 + +荷兰国旗包含三种颜色:红、白、蓝。有这三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。 + +它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。 + +

+ +**按颜色进行排序** + +[75. Sort Colors (Medium)](https://leetcode.com/problems/sort-colors/description/) + +```html +Input: [2,0,2,1,1,0] +Output: [0,0,1,1,2,2] +``` + +题目描述:只有 0/1/2 三种颜色。 + +```java +public void sortColors(int[] nums) { + int zero = -1, one = 0, two = nums.length; + while (one < two) { + if (nums[one] == 0) + swap(nums, ++zero, one++); + else if (nums[one] == 2) + swap(nums, --two, one); + else + ++one; + } +} + +private void swap(int[] nums, int i, int j) { + int t = nums[i]; + nums[i] = nums[j]; + nums[j] = t; +} +``` + ## 搜索 深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。 @@ -781,7 +1024,7 @@ public String frequencySort(String s) {

-广度优先搜索的搜索过程有点像一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个长度。需要注意的是,遍历过的节点不能再次被遍历。 +广度优先搜索的搜索过程有点像一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。 第一层: @@ -799,11 +1042,11 @@ public String frequencySort(String s) { - 4 -> {} - 3 -> {} -可以看到,每一轮遍历的节点都与根节点路径长度相同。设 di 表示第 i 个节点与根节点的路径长度,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di<=dj。利用这个结论,可以求解最短路径等 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径,如果继续遍历,之后再遍历到目的节点,所经过的路径就不是最短路径。 +可以看到,每一轮遍历的节点都与根节点距离相同。设 di 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di<=dj。利用这个结论,可以求解最短路径等 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径。 在程序实现 BFS 时需要考虑以下问题: -- 队列:用来存储每一轮遍历的节点; +- 队列:用来存储每一轮遍历得到的节点; - 标记:对于遍历过的节点,应该将它标记,防止重复遍历。 **计算在网格中从原点到特定点的最短路径长度** @@ -815,35 +1058,178 @@ public String frequencySort(String s) { [1,0,1,1]] ``` -1 表示可以经过某个位置。 +题目描述:1 表示可以经过某个位置,求解从 (0, 0) 位置到 (tr, tc) 位置的最短路径长度。 ```java public int minPathLength(int[][] grids, int tr, int tc) { - int[][] next = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; - int m = grids.length, n = grids[0].length; - Queue queue = new LinkedList<>(); - queue.add(new Position(0, 0, 1)); + final int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; + final int m = grids.length, n = grids[0].length; + Queue> queue = new LinkedList<>(); + queue.add(new Pair(0, 0)); + int pathLength = 0; while (!queue.isEmpty()) { - Position pos = queue.poll(); - for (int i = 0; i < 4; i++) { - Position nextPos = new Position(pos.r + next[i][0], pos.c + next[i][1], pos.length + 1); - if (nextPos.r < 0 || nextPos.r >= m || nextPos.c < 0 || nextPos.c >= n) continue; - if (grids[nextPos.r][nextPos.c] != 1) continue; - grids[nextPos.r][nextPos.c] = 0; - if (nextPos.r == tr && nextPos.c == tc) return nextPos.length; - queue.add(nextPos); + int size = queue.size(); + pathLength++; + while (size-- > 0) { + Pair cur = queue.poll(); + for (int[] d : direction) { + Pair next = new Pair(cur.getKey() + d[0], cur.getValue() + d[1]); + if (next.getKey() < 0 || next.getValue() >= m || next.getKey() < 0 || next.getValue() >= n) + continue; + grids[next.getKey()][next.getValue()] = 0; // 标记 + if (next.getKey() == tr && next.getValue() == tc) + return pathLength; + queue.add(next); + } } } return -1; } +``` -private class Position { - int r, c, length; - public Position(int r, int c, int length) { - this.r = r; - this.c = c; - this.length = length; +**组成整数的最小平方数数量** + +[279. Perfect Squares (Medium)](https://leetcode.com/problems/perfect-squares/description/) + +```html +For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9. +``` + +可以将每个整数看成图中的一个节点,如果两个整数只差为一个平方数,那么这两个整数所在的节点就有一条边。 + +要求解最小的平方数数量,就是求解从节点 n 到节点 0 的最短路径。 + +本题也可以用动态规划求解,在之后动态规划部分中会再次出现。 + +```java +public int numSquares(int n) { + List squares = generateSquares(n); + Queue queue = new LinkedList<>(); + boolean[] marked = new boolean[n + 1]; + queue.add(n); + marked[n] = true; + int num = 0; + while (!queue.isEmpty()) { + int size = queue.size(); + num++; + while (size-- > 0) { + int cur = queue.poll(); + for (int s : squares) { + int next = cur - s; + if (next < 0) + break; + if (next == 0) + return num; + if (marked[next]) + continue; + marked[next] = true; + queue.add(cur - s); + } + } } + return n; +} + +private List generateSquares(int n) { + List squares = new ArrayList<>(); + int square = 1; + int diff = 3; + while (square <= n) { + squares.add(square); + square += diff; + diff += 2; + } + return squares; +} +``` + +**最短单词路径** + +[127. Word Ladder (Medium)](https://leetcode.com/problems/word-ladder/description/) + +```html +Input: +beginWord = "hit", +endWord = "cog", +wordList = ["hot","dot","dog","lot","log","cog"] + +Output: 5 + +Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog", +return its length 5. +``` + +```html +Input: +beginWord = "hit" +endWord = "cog" +wordList = ["hot","dot","dog","lot","log"] + +Output: 0 + +Explanation: The endWord "cog" is not in wordList, therefore no possible transformation. +``` + +题目描述:要找出一条从 beginWord 到 endWord 的最短路径,每次移动规定为改变一个字符,并且改变之后的字符串必须在 wordList 中。 + +```java +public int ladderLength(String beginWord, String endWord, List wordList) { + wordList.add(beginWord); + int N = wordList.size(); + int start = N - 1; + int end = 0; + while (end < N && !wordList.get(end).equals(endWord)) + end++; + if (end == N) + return 0; + List[] graphic = buildGraphic(wordList); + return getShortestPath(graphic, start, end); +} + +private List[] buildGraphic(List wordList) { + int N = wordList.size(); + List[] graphic = new List[N]; + for (int i = 0; i < N; i++) { + graphic[i] = new ArrayList<>(); + for (int j = 0; j < N; j++) { + if (isConnect(wordList.get(i), wordList.get(j))) + graphic[i].add(j); + } + } + return graphic; +} + +private boolean isConnect(String s1, String s2) { + int diffCnt = 0; + for (int i = 0; i < s1.length() && diffCnt <= 1; i++) { + if (s1.charAt(i) != s2.charAt(i)) + diffCnt++; + } + return diffCnt == 1; +} + +private int getShortestPath(List[] graphic, int start, int end) { + Queue queue = new LinkedList<>(); + boolean[] marked = new boolean[graphic.length]; + queue.add(start); + marked[start] = true; + int path = 1; + while (!queue.isEmpty()) { + int size = queue.size(); + path++; + while (size-- > 0) { + int cur = queue.poll(); + for (int next : graphic[cur]) { + if (next == end) + return path; + if (marked[next]) + continue; + marked[next] = true; + queue.add(next); + } + } + } + return 0; } ``` @@ -851,7 +1237,9 @@ private class Position {

-广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列先存储起来以备下一层遍历的时候再遍历;而深度优先搜索在得到到一个新节点时立马对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。 +广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。 + +而深度优先搜索在得到一个新节点时立马对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。 从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 **可达性** 问题。 @@ -862,7 +1250,7 @@ private class Position { **查找最大的连通面积** -[Leetcode : 695. Max Area of Island (Easy)](https://leetcode.com/problems/max-area-of-island/description/) +[695. Max Area of Island (Easy)](https://leetcode.com/problems/max-area-of-island/description/) ```html [[0,0,1,0,0,0,0,1,0,0,0,0,0], @@ -876,30 +1264,87 @@ private class Position { ``` ```java +private int m, n; +private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; + public int maxAreaOfIsland(int[][] grid) { - int max = 0; - for (int i = 0; i < grid.length; i++) { - for (int j = 0; j < grid[i].length; j++) { - if (grid[i][j] == 1) { - max = Math.max(max, dfs(grid, i, j)); - } - } - } - return max; + if (grid == null || grid.length == 0) + return 0; + + m = grid.length; + n = grid[0].length; + + int maxArea = 0; + for (int i = 0; i < m; i++) + for (int j = 0; j < n; j++) + maxArea = Math.max(maxArea, dfs(grid, i, j)); + + return maxArea; } -private int dfs(int[][] grid, int i, int j) { - if (i < 0 || i >= grid.length || j < 0 || j >= grid[i].length || grid[i][j] == 0) { +private int dfs(int[][] grid, int r, int c) { + if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == 0) return 0; - } - grid[i][j] = 0; - return dfs(grid, i + 1, j) + dfs(grid, i - 1, j) + dfs(grid, i, j + 1) + dfs(grid, i, j - 1) + 1; + + grid[r][c] = 0; + int area = 1; + for (int[] d : direction) + area += dfs(grid, r + d[0], c + d[1]); + + return area; } ``` -**图的连通分量** +**矩阵中的连通分量数目** -[Leetcode : 547. Friend Circles (Medium)](https://leetcode.com/problems/friend-circles/description/) +[200. Number of Islands (Medium)](https://leetcode.com/problems/number-of-islands/description/) + +```html +Input: +11000 +11000 +00100 +00011 + +Output: 3 +``` + +可以将矩阵表示看成一张有向图。 + +```java +private int m, n; +private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; + +public int numIslands(char[][] grid) { + if (grid == null || grid.length == 0) + return 0; + + m = grid.length; + n = grid[0].length; + int islandsNum = 0; + for (int i = 0; i < m; i++) + for (int j = 0; j < n; j++) + if (grid[i][j] != '0') { + dfs(grid, i, j); + islandsNum++; + } + + return islandsNum; +} + +private void dfs(char[][] grid, int i, int j) { + if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') + return; + + grid[i][j] = '0'; + for (int[] d : direction) + dfs(grid, i + d[0], j + d[1]); +} +``` + +**好友关系的连通分量数目** + +[547. Friend Circles (Medium)](https://leetcode.com/problems/friend-circles/description/) ```html Input: @@ -911,145 +1356,35 @@ Explanation:The 0th and 1st students are direct friends, so they are in a friend The 2nd student himself is in a friend circle. So return 2. ``` +好友关系可以看成是一个无向图,例如第 0 个人与第 1 个人是好友,那么 M[0][1] 和 M[1][0] 的值都为 1。 + ```java +private int n; + public int findCircleNum(int[][] M) { - int n = M.length; - int ret = 0; + n = M.length; + int circleNum = 0; boolean[] hasVisited = new boolean[n]; - for (int i = 0; i < n; i++) { + for (int i = 0; i < n; i++) if (!hasVisited[i]) { dfs(M, i, hasVisited); - ret++; + circleNum++; } - } - return ret; + return circleNum; } private void dfs(int[][] M, int i, boolean[] hasVisited) { hasVisited[i] = true; - for (int k = 0; k < M.length; k++) { - if (M[i][k] == 1 && !hasVisited[k]) { + for (int k = 0; k < n; k++) + if (M[i][k] == 1 && !hasVisited[k]) dfs(M, k, hasVisited); - } - } -} -``` - -**矩阵中的连通区域数量** - -[Leetcode : 200. Number of Islands (Medium)](https://leetcode.com/problems/number-of-islands/description/) - -```html -11110 -11010 -11000 -00000 -Answer: 1 -``` - -```java -private int m, n; -private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; - -public int numIslands(char[][] grid) { - if (grid == null || grid.length == 0) return 0; - m = grid.length; - n = grid[0].length; - int ret = 0; - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - if (grid[i][j] == '1') { - dfs(grid, i, j); - ret++; - } - } - } - return ret; -} - -private void dfs(char[][] grid, int i, int j) { - if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') return; - grid[i][j] = '0'; - for (int k = 0; k < direction.length; k++) { - dfs(grid, i + direction[k][0], j + direction[k][1]); - } -} -``` - -**输出二叉树中所有从根到叶子的路径** - -[Leetcode : 257. Binary Tree Paths (Easy)](https://leetcode.com/problems/binary-tree-paths/description/) - -```html - 1 - / \ -2 3 -\ - 5 -``` -```html -["1->2->5", "1->3"] -``` - -```java -public List binaryTreePaths(TreeNode root) { - List ret = new ArrayList(); - if(root == null) return ret; - dfs(root, "", ret); - return ret; -} - -private void dfs(TreeNode root, String prefix, List ret){ - if(root == null) return; - if(root.left == null && root.right == null){ - ret.add(prefix + root.val); - return; - } - prefix += (root.val + "->"); - dfs(root.left, prefix, ret); - dfs(root.right, prefix, ret); -} -``` - -**IP 地址划分** - -[Leetcode : 93. Restore IP Addresses(Medium)](https://leetcode.com/problems/restore-ip-addresses/description/) - -```html -Given "25525511135", -return ["255.255.11.135", "255.255.111.35"]. -``` - -```java -private List ret; - -public List restoreIpAddresses(String s) { - ret = new ArrayList<>(); - doRestore(0, "", s); - return ret; -} - -private void doRestore(int k, String path, String s) { - if (k == 4 || s.length() == 0) { - if (k == 4 && s.length() == 0) { - ret.add(path); - } - return; - } - for (int i = 0; i < s.length() && i <= 2; i++) { - if (i != 0 && s.charAt(0) == '0') break; - String part = s.substring(0, i + 1); - if (Integer.valueOf(part) <= 255) { - doRestore(k + 1, path.length() != 0 ? path + "." + part : part, s.substring(i + 1)); - } - } } ``` **填充封闭区域** -[Leetcode : 130. Surrounded Regions (Medium)](https://leetcode.com/problems/surrounded-regions/description/) +[130. Surrounded Regions (Medium)](https://leetcode.com/problems/surrounded-regions/description/) ```html For example, @@ -1065,7 +1400,7 @@ X X X X X O X X ``` -题目描述:使得被 'X' 的 'O' 转换为 'X'。 +题目描述:使得被 'X' 包围的 'O' 转换为 'X'。 先填充最外侧,剩下的就是里侧了。 @@ -1074,9 +1409,12 @@ private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; private int m, n; public void solve(char[][] board) { - if (board == null || board.length == 0) return; + if (board == null || board.length == 0) + return; + m = board.length; n = board[0].length; + for (int i = 0; i < m; i++) { dfs(board, i, 0); dfs(board, i, n - 1); @@ -1085,26 +1423,29 @@ public void solve(char[][] board) { dfs(board, 0, i); dfs(board, m - 1, i); } - for (int i = 0; i < m; i++) { + + for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) { - if (board[i][j] == 'T') board[i][j] = 'O'; - else if (board[i][j] == 'O') board[i][j] = 'X'; + if (board[i][j] == 'T') + board[i][j] = 'O'; + else if (board[i][j] == 'O') + board[i][j] = 'X'; } - } } private void dfs(char[][] board, int r, int c) { - if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != 'O') return; + if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != 'O') + return; + board[r][c] = 'T'; - for (int i = 0; i < direction.length; i++) { - dfs(board, r + direction[i][0], c + direction[i][1]); - } + for (int[] d : direction) + dfs(board, r + d[0], c + d[1]); } ``` -**从两个方向都能到达的区域** +**能到达的太平洋和大西洋的区域** -[Leetcode : 417. Pacific Atlantic Water Flow (Medium)](https://leetcode.com/problems/pacific-atlantic-water-flow/description/) +[417. Pacific Atlantic Water Flow (Medium)](https://leetcode.com/problems/pacific-atlantic-water-flow/description/) ```html Given the following 5x5 matrix: @@ -1130,12 +1471,15 @@ private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; public List pacificAtlantic(int[][] matrix) { List ret = new ArrayList<>(); - if (matrix == null || matrix.length == 0) return ret; - this.m = matrix.length; - this.n = matrix[0].length; + if (matrix == null || matrix.length == 0) + return ret; + + m = matrix.length; + n = matrix[0].length; this.matrix = matrix; boolean[][] canReachP = new boolean[m][n]; boolean[][] canReachA = new boolean[m][n]; + for (int i = 0; i < m; i++) { dfs(i, 0, canReachP); dfs(i, n - 1, canReachA); @@ -1144,24 +1488,25 @@ public List pacificAtlantic(int[][] matrix) { dfs(0, i, canReachP); dfs(m - 1, i, canReachA); } - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - if (canReachP[i][j] && canReachA[i][j]) { + + for (int i = 0; i < m; i++) + for (int j = 0; j < n; j++) + if (canReachP[i][j] && canReachA[i][j]) ret.add(new int[]{i, j}); - } - } - } + return ret; } private void dfs(int r, int c, boolean[][] canReach) { - if(canReach[r][c]) return; + if (canReach[r][c]) + return; + canReach[r][c] = true; - for (int i = 0; i < direction.length; i++) { - int nextR = direction[i][0] + r; - int nextC = direction[i][1] + c; - if (nextR < 0 || nextR >= m || nextC < 0 || nextC >= n - || matrix[r][c] > matrix[nextR][nextC]) continue; + for (int[] d : direction) { + int nextR = d[0] + r; + int nextC = d[1] + c; + if (nextR < 0 || nextR >= m || nextC < 0 || nextC >= n || matrix[r][c] > matrix[nextR][nextC]) + continue; dfs(nextR, nextC, canReach); } } @@ -1169,13 +1514,19 @@ private void dfs(int r, int c, boolean[][] canReach) { ### Backtracking -回溯属于 DF,它不是用在遍历图的节点上,而是用于求解 **排列组合** 问题,例如有 { 'a','b','c' } 三个字符,求解所有由这三个字符排列得到的字符串。 +Backtracking(回溯)属于 DFS。 -在程序实现时,回溯需要注意对元素进行标记的问题。使用递归实现的回溯,在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;但是在递归返回时,需要将该元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。 +- 普通 DFS 主要用在 **可达性问题** ,这种问题只需要执行到特点的位置然后返回即可。 +- 而 Backtracking 主要用于求解 **排列组合** 问题,例如有 { 'a','b','c' } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。 + +因为 Backtracking 不是立即就返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题: + +- 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素; +- 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。 **数字键盘组合** -[Leetcode : 17. Letter Combinations of a Phone Number (Medium)](https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/) +[17. Letter Combinations of a Phone Number (Medium)](https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/)

@@ -1189,7 +1540,8 @@ private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", public List letterCombinations(String digits) { List ret = new ArrayList<>(); - if (digits == null || digits.length() == 0) return ret; + if (digits == null || digits.length() == 0) + return ret; combination(new StringBuilder(), digits, ret); return ret; } @@ -1201,16 +1553,54 @@ private void combination(StringBuilder prefix, String digits, List ret) } String letters = KEYS[digits.charAt(prefix.length()) - '0']; for (char c : letters.toCharArray()) { - prefix.append(c); + prefix.append(c); // 添加 combination(prefix, digits, ret); prefix.deleteCharAt(prefix.length() - 1); // 删除 } } ``` +**IP 地址划分** + +[93. Restore IP Addresses(Medium)](https://leetcode.com/problems/restore-ip-addresses/description/) + +```html +Given "25525511135", +return ["255.255.11.135", "255.255.111.35"]. +``` + +```java +public List restoreIpAddresses(String s) { + List addresses = new ArrayList<>(); + StringBuilder path = new StringBuilder(); + doRestore(0, path, s, addresses); + return addresses; +} + +private void doRestore(int k, StringBuilder path, String s, List addresses) { + if (k == 4 || s.length() == 0) { + if (k == 4 && s.length() == 0) + addresses.add(path.toString()); + return; + } + for (int i = 0; i < s.length() && i <= 2; i++) { + if (i != 0 && s.charAt(0) == '0') + break; + String part = s.substring(0, i + 1); + if (Integer.valueOf(part) <= 255) { + if (path.length() != 0) + part = "." + part; + path.append(part); + doRestore(k + 1, path, s.substring(i + 1), addresses); + path.delete(path.length() - part.length(), path.length()); + } + } +} +``` + **在矩阵中寻找字符串** -[Leetcode : 79. Word Search (Medium)](https://leetcode.com/problems/word-search/description/) +[79. Word Search (Medium)](https://leetcode.com/problems/word-search/description/) ```html For example, @@ -1226,48 +1616,102 @@ word = "ABCB", -> returns false. ``` ```java -private static int[][] shift = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; -private static boolean[][] visited; +private static int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; private int m; private int n; public boolean exist(char[][] board, String word) { - if (word == null || word.length() == 0) return true; - if (board == null || board.length == 0 || board[0].length == 0) return false; + if (word == null || word.length() == 0) + return true; + if (board == null || board.length == 0 || board[0].length == 0) + return false; + m = board.length; n = board[0].length; - visited = new boolean[m][n]; - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - if (dfs(board, word, 0, i, j)) return true; - } - } + boolean[][] visited = new boolean[m][n]; + + for (int i = 0; i < m; i++) + for (int j = 0; j < n; j++) + if (backtracking(board, visited, word, 0, i, j)) return true; + return false; } -private boolean dfs(char[][] board, String word, int start, int r, int c) { - if (start == word.length()) { +private boolean backtracking(char[][] board, boolean[][] visited, String word, int start, int r, int c) { + if (start == word.length()) return true; - } - if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(start) || visited[r][c]) { + if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(start) || visited[r][c]) return false; - } + visited[r][c] = true; - for (int i = 0; i < shift.length; i++) { - int nextR = r + shift[i][0]; - int nextC = c + shift[i][1]; - if (dfs(board, word, start + 1, nextR, nextC)) { + + for (int[] d : direction) + if (backtracking(board, visited, word, start + 1, r + d[0], c + d[1])) return true; - } - } + visited[r][c] = false; + return false; } ``` +**输出二叉树中所有从根到叶子的路径** + +[257. Binary Tree Paths (Easy)](https://leetcode.com/problems/binary-tree-paths/description/) + +```html + 1 + / \ +2 3 + \ + 5 +``` + +```html +["1->2->5", "1->3"] +``` + +```java +public List binaryTreePaths(TreeNode root) { + List paths = new ArrayList(); + if (root == null) + return paths; + List values = new ArrayList<>(); + backtracking(root, values, paths); + return paths; +} + +private void backtracking(TreeNode node, List values, List paths) { + if (node == null) + return; + values.add(node.val); + if (isLeaf(node)) + paths.add(buildPath(values)); + else { + backtracking(node.left, values, paths); + backtracking(node.right, values, paths); + } + values.remove(values.size() - 1); +} + +private boolean isLeaf(TreeNode node) { + return node.left == null && node.right == null; +} + +private String buildPath(List values) { + StringBuilder str = new StringBuilder(); + for (int i = 0; i < values.size(); i++) { + str.append(values.get(i)); + if (i != values.size() - 1) + str.append("->"); + } + return str.toString(); +} +``` + **排列** -[Leetcode : 46. Permutations (Medium)](https://leetcode.com/problems/permutations/description/) +[46. Permutations (Medium)](https://leetcode.com/problems/permutations/description/) ```html [1,2,3] have the following permutations: @@ -1290,14 +1734,14 @@ public List> permute(int[] nums) { return ret; } -private void backtracking(List permuteList, boolean[] visited, int[] nums, List> ret){ - if(permuteList.size() == nums.length){ - ret.add(new ArrayList(permuteList)); +private void backtracking(List permuteList, boolean[] visited, int[] nums, List> ret) { + if (permuteList.size() == nums.length) { + ret.add(new ArrayList(permuteList)); // 重新构造一个 List return; } - - for(int i = 0; i < visited.length; i++){ - if(visited[i]) continue; + for (int i = 0; i < visited.length; i++) { + if (visited[i]) + continue; visited[i] = true; permuteList.add(nums[i]); backtracking(permuteList, visited, nums, ret); @@ -1309,14 +1753,14 @@ private void backtracking(List permuteList, boolean[] visited, int[] nu **含有相同元素求排列** -[Leetcode : 47. Permutations II (Medium)](https://leetcode.com/problems/permutations-ii/description/) +[47. Permutations II (Medium)](https://leetcode.com/problems/permutations-ii/description/) ```html [1,1,2] have the following unique permutations: [[1,1,2], [1,2,1], [2,1,1]] ``` -题目描述:数组元素可能含有相同的元素,进行排列时就有可能出现 重复的排列,要求重复的排列只返回一个。 +题目描述:数组元素可能含有相同的元素,进行排列时就有可能出现重复的排列,要求重复的排列只返回一个。 在实现上,和 Permutations 不同的是要先排序,然后在添加一个元素时,判断这个元素是否等于前一个元素,如果等于,并且前一个元素还未访问,那么就跳过这个元素。 @@ -1332,13 +1776,15 @@ public List> permuteUnique(int[] nums) { private void backtracking(List permuteList, boolean[] visited, int[] nums, List> ret) { if (permuteList.size() == nums.length) { - ret.add(new ArrayList(permuteList)); // 重新构造一个 List + ret.add(new ArrayList(permuteList)); return; } for (int i = 0; i < visited.length; i++) { - if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) continue; // 防止重复 - if (visited[i]) continue; + if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) + continue; // 防止重复 + if (visited[i]) + continue; visited[i] = true; permuteList.add(nums[i]); backtracking(permuteList, visited, nums, ret); @@ -1350,7 +1796,7 @@ private void backtracking(List permuteList, boolean[] visited, int[] nu **组合** -[Leetcode : 77. Combinations (Medium)](https://leetcode.com/problems/combinations/description/) +[77. Combinations (Medium)](https://leetcode.com/problems/combinations/description/) ```html If n = 4 and k = 2, a solution is: @@ -1372,23 +1818,23 @@ public List> combine(int n, int k) { return ret; } -private void backtracking(int start, int n, int k, List combineList, List> ret){ - if(k == 0){ +private void backtracking(int start, int n, int k, List combineList, List> ret) { + if (k == 0) { ret.add(new ArrayList(combineList)); return; } - for(int i = start; i <= n - k + 1; i++) { // 剪枝 - combineList.add(i); // 把 i 标记为已访问 + for (int i = start; i <= n - k + 1; i++) { // 剪枝 + combineList.add(i); backtracking(i + 1, n, k - 1, combineList, ret); - combineList.remove(combineList.size() - 1); // 把 i 标记为未访问 + combineList.remove(combineList.size() - 1); } } ``` **组合求和** -[Leetcode : 39. Combination Sum (Medium)](https://leetcode.com/problems/combination-sum/description/) +[39. Combination Sum (Medium)](https://leetcode.com/problems/combination-sum/description/) ```html given candidate set [2, 3, 6, 7] and target 7, @@ -1397,32 +1843,30 @@ A solution set is: ``` ```java - private List> ret; +public List> combinationSum(int[] candidates, int target) { + List> ret = new ArrayList<>(); + doCombination(candidates, target, 0, new ArrayList<>(), ret); + return ret; +} - public List> combinationSum(int[] candidates, int target) { - ret = new ArrayList<>(); - doCombination(candidates, target, 0, new ArrayList<>()); - return ret; - } - - private void doCombination(int[] candidates, int target, int start, List list) { - if (target == 0) { - ret.add(new ArrayList<>(list)); - return; - } - for (int i = start; i < candidates.length; i++) { - if (candidates[i] <= target) { - list.add(candidates[i]); - doCombination(candidates, target - candidates[i], i, list); - list.remove(list.size() - 1); - } - } - } +private void doCombination(int[] candidates, int target, int start, List list, List> ret) { + if (target == 0) { + ret.add(new ArrayList<>(list)); + return; + } + for (int i = start; i < candidates.length; i++) { + if (candidates[i] <= target) { + list.add(candidates[i]); + doCombination(candidates, target - candidates[i], i, list, ret); + list.remove(list.size() - 1); + } + } +} ``` **含有相同元素的求组合求和** -[Leetcode : 40. Combination Sum II (Medium)](https://leetcode.com/problems/combination-sum-ii/description/) +[40. Combination Sum II (Medium)](https://leetcode.com/problems/combination-sum-ii/description/) ```html For example, given candidate set [10, 1, 2, 7, 6, 1, 5] and target 8, @@ -1436,26 +1880,25 @@ A solution set is: ``` ```java -private List> ret; - public List> combinationSum2(int[] candidates, int target) { - ret = new ArrayList<>(); + List> ret = new ArrayList<>(); Arrays.sort(candidates); - doCombination(candidates, target, 0, new ArrayList<>(), new boolean[candidates.length]); + doCombination(candidates, target, 0, new ArrayList<>(), new boolean[candidates.length], ret); return ret; } -private void doCombination(int[] candidates, int target, int start, List list, boolean[] visited) { +private void doCombination(int[] candidates, int target, int start, List list, boolean[] visited, List> ret) { if (target == 0) { ret.add(new ArrayList<>(list)); return; } for (int i = start; i < candidates.length; i++) { - if (i != 0 && candidates[i] == candidates[i - 1] && !visited[i - 1]) continue; + if (i != 0 && candidates[i] == candidates[i - 1] && !visited[i - 1]) + continue; if (candidates[i] <= target) { list.add(candidates[i]); visited[i] = true; - doCombination(candidates, target - candidates[i], i + 1, list, visited); + doCombination(candidates, target - candidates[i], i + 1, list, visited, ret); visited[i] = false; list.remove(list.size() - 1); } @@ -1465,7 +1908,7 @@ private void doCombination(int[] candidates, int target, int start, List> combinationSum3(int k, int n) { List> ret = new ArrayList<>(); List path = new ArrayList<>(); - for (int i = 1; i <= 9; i++) { - path.add(i); - backtracking(k - 1, n - i, path, i, ret); - path.remove(0); - } + backtracking(k, n, path, 1, ret); return ret; } @@ -1494,10 +1933,11 @@ private void backtracking(int k, int n, List path, int start, List(path)); return; } - if (k == 0 || n == 0) return; - for (int i = start + 1; i <= 9; i++) { // 只能访问下一个元素,防止遍历的结果重复 + if (k == 0 || n == 0) + return; + for (int i = start; i <= 9; i++) { path.add(i); - backtracking(k - 1, n - i, path, i, ret); + backtracking(k - 1, n - i, path, i + 1, ret); path.remove(path.size() - 1); } } @@ -1505,7 +1945,7 @@ private void backtracking(int k, int n, List path, int start, List subsetList; public List> subsets(int[] nums) { ret = new ArrayList<>(); subsetList = new ArrayList<>(); - for (int i = 0; i <= nums.length; i++) { // 不同的子集大小 + for (int i = 0; i <= nums.length; i++) // 不同的子集大小 backtracking(0, i, nums); - } return ret; } @@ -1527,7 +1966,6 @@ private void backtracking(int startIdx, int size, int[] nums) { ret.add(new ArrayList(subsetList)); return; } - for (int i = startIdx; i < nums.length; i++) { subsetList.add(nums[i]); backtracking(i + 1, size, nums); @@ -1538,7 +1976,7 @@ private void backtracking(int startIdx, int size, int[] nums) { **含有相同元素求子集** -[Leetcode : 90. Subsets II (Medium)](https://leetcode.com/problems/subsets-ii/description/) +[90. Subsets II (Medium)](https://leetcode.com/problems/subsets-ii/description/) ```html For example, @@ -1564,9 +2002,10 @@ public List> subsetsWithDup(int[] nums) { subsetList = new ArrayList<>(); visited = new boolean[nums.length]; Arrays.sort(nums); - for (int i = 0; i <= nums.length; i++) { + + for (int i = 0; i <= nums.length; i++) backtracking(0, i, nums); - } + return ret; } @@ -1575,9 +2014,9 @@ private void backtracking(int startIdx, int size, int[] nums) { ret.add(new ArrayList(subsetList)); return; } - for (int i = startIdx; i < nums.length; i++) { - if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) continue; + if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) + continue; subsetList.add(nums[i]); visited[i] = true; backtracking(i + 1, size, nums); @@ -1589,7 +2028,7 @@ private void backtracking(int startIdx, int size, int[] nums) { **分割字符串使得每个部分都是回文数** -[Leetcode : 131. Palindrome Partitioning (Medium)](https://leetcode.com/problems/palindrome-partitioning/description/) +[131. Palindrome Partitioning (Medium)](https://leetcode.com/problems/palindrome-partitioning/description/) ```html For example, given s = "aab", @@ -1606,11 +2045,11 @@ private List> ret; public List> partition(String s) { ret = new ArrayList<>(); - doPartion(new ArrayList<>(), s); + doPartition(new ArrayList<>(), s); return ret; } -private void doPartion(List list, String s) { +private void doPartition(List list, String s) { if (s.length() == 0) { ret.add(new ArrayList<>(list)); return; @@ -1618,23 +2057,23 @@ private void doPartion(List list, String s) { for (int i = 0; i < s.length(); i++) { if (isPalindrome(s, 0, i)) { list.add(s.substring(0, i + 1)); - doPartion(list, s.substring(i + 1)); + doPartition(list, s.substring(i + 1)); list.remove(list.size() - 1); } } } private boolean isPalindrome(String s, int begin, int end) { - while (begin < end) { - if (s.charAt(begin++) != s.charAt(end--)) return false; - } + while (begin < end) + if (s.charAt(begin++) != s.charAt(end--)) + return false; return true; } ``` **数独** -[Leetcode : 37. Sudoku Solver (Hard)](https://leetcode.com/problems/sudoku-solver/description/) +[37. Sudoku Solver (Hard)](https://leetcode.com/problems/sudoku-solver/description/)

@@ -1646,20 +2085,19 @@ private char[][] board; public void solveSudoku(char[][] board) { this.board = board; - for (int i = 0; i < 9; i++) { + for (int i = 0; i < 9; i++) for (int j = 0; j < 9; j++) { - if (board[i][j] == '.') continue; + if (board[i][j] == '.') + continue; int num = board[i][j] - '0'; rowsUsed[i][num] = true; colsUsed[j][num] = true; cubesUsed[cubeNum(i, j)][num] = true; } - } - for (int i = 0; i < 9; i++) { - for (int j = 0; j < 9; j++) { + + for (int i = 0; i < 9; i++) + for (int j = 0; j < 9; j++) backtracking(i, j); - } - } } private boolean backtracking(int row, int col) { @@ -1667,14 +2105,17 @@ private boolean backtracking(int row, int col) { row = col == 8 ? row + 1 : row; col = col == 8 ? 0 : col + 1; } - if (row == 9) { + + if (row == 9) return true; - } + for (int num = 1; num <= 9; num++) { - if (rowsUsed[row][num] || colsUsed[col][num] || cubesUsed[cubeNum(row, col)][num]) continue; + if (rowsUsed[row][num] || colsUsed[col][num] || cubesUsed[cubeNum(row, col)][num]) + continue; rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubeNum(row, col)][num] = true; board[row][col] = (char) (num + '0'); - if (backtracking(row, col)) return true; + if (backtracking(row, col)) + return true; board[row][col] = '.'; rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubeNum(row, col)][num] = false; } @@ -1690,19 +2131,19 @@ private int cubeNum(int i, int j) { **N 皇后** -[Leetcode : 51. N-Queens (Hard)](https://leetcode.com/problems/n-queens/description/) +[51. N-Queens (Hard)](https://leetcode.com/problems/n-queens/description/)

-题目描述:在 n\*n 的矩阵中摆放 n 个皇后,并且每个皇后不能在同一行,同一列,同一对角线上,要求解所有的 n 皇后解。 +题目描述:在 n\*n 的矩阵中摆放 n 个皇后,并且每个皇后不能在同一行,同一列,同一对角线上,求所有的 n 皇后的解。 一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要用三个标记数组来确定某一列是否合法,这三个标记数组分别为:列标记数组、45 度对角线标记数组和 135 度对角线标记数组。 -45 度对角线标记数组的维度为 2\*n - 1,通过下图可以明确 (r,c) 的位置所在的数组下标为 r + c。 +45 度对角线标记数组的维度为 2 \* n - 1,通过下图可以明确 (r, c) 的位置所在的数组下标为 r + c。

-135 度对角线标记数组的维度也是 2\*n - 1,(r,c) 的位置所在的数组下标为 n - 1 - (r - c)。 +135 度对角线标记数组的维度也是 2 \* n - 1,(r, c) 的位置所在的数组下标为 n - 1 - (r - c)。

@@ -1717,21 +2158,21 @@ private int n; public List> solveNQueens(int n) { ret = new ArrayList<>(); nQueens = new char[n][n]; - Arrays.fill(nQueens, '.'); + for(int i = 0; i < n; i++) + Arrays.fill(nQueens[i], '.'); colUsed = new boolean[n]; diagonals45Used = new boolean[2 * n - 1]; diagonals135Used = new boolean[2 * n - 1]; this.n = n; - backstracking(0); + backtracking(0); return ret; } -private void backstracking(int row) { +private void backtracking(int row) { if (row == n) { List list = new ArrayList<>(); - for (char[] chars : nQueens) { + for (char[] chars : nQueens) list.add(new String(chars)); - } ret.add(list); return; } @@ -1739,12 +2180,11 @@ private void backstracking(int row) { for (int col = 0; col < n; col++) { int diagonals45Idx = row + col; int diagonals135Idx = n - 1 - (row - col); - if (colUsed[col] || diagonals45Used[diagonals45Idx] || diagonals135Used[diagonals135Idx]) { + if (colUsed[col] || diagonals45Used[diagonals45Idx] || diagonals135Used[diagonals135Idx]) continue; - } nQueens[row][col] = 'Q'; colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = true; - backstracking(row + 1); + backtracking(row + 1); colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = false; nQueens[row][col] = '.'; } @@ -1755,7 +2195,7 @@ private void backstracking(int row) { **给表达式加括号** -[Leetcode : 241. Different Ways to Add Parentheses (Medium)](https://leetcode.com/problems/different-ways-to-add-parentheses/description/) +[241. Different Ways to Add Parentheses (Medium)](https://leetcode.com/problems/different-ways-to-add-parentheses/description/) ```html Input: "2-1-1". @@ -1769,7 +2209,7 @@ Output : [0, 2] ```java public List diffWaysToCompute(String input) { int n = input.length(); - List ret = new ArrayList<>(); + List ways = new ArrayList<>(); for (int i = 0; i < n; i++) { char c = input.charAt(i); if (c == '+' || c == '-' || c == '*') { @@ -1778,16 +2218,24 @@ public List diffWaysToCompute(String input) { for (int l : left) { for (int r : right) { switch (c) { - case '+': ret.add(l + r); break; - case '-': ret.add(l - r); break; - case '*': ret.add(l * r); break; + case '+': + ways.add(l + r); + break; + case '-': + ways.add(l - r); + break; + case '*': + ways.add(l * r); + break; } } } } } - if (ret.size() == 0) ret.add(Integer.valueOf(input)); - return ret; + if (ways.size() == 0) { + ways.add(Integer.valueOf(input)); + } + return ways; } ``` @@ -1799,7 +2247,7 @@ public List diffWaysToCompute(String input) { **爬楼梯** -[Leetcode : 70. Climbing Stairs (Easy)](https://leetcode.com/problems/climbing-stairs/description/) +[70. Climbing Stairs (Easy)](https://leetcode.com/problems/climbing-stairs/description/) 题目描述:有 N 阶楼梯,每次可以上一阶或者两阶,求有多少种上楼梯的方法。 @@ -1813,10 +2261,9 @@ dp[N] 即为所求。 ```java public int climbStairs(int n) { - if(n == 1) return 1; - if(n == 2) return 2; - int pre1 = 2, pre2 = 1; - for(int i = 2; i < n; i++){ + if (n <= 2) return n; + int pre2 = 1, pre1 = 2; + for (int i = 2; i < n; i++) { int cur = pre1 + pre2; pre2 = pre1; pre1 = cur; @@ -1837,7 +2284,7 @@ public int climbStairs(int n) { **强盗抢劫** -[Leetcode : 198. House Robber (Easy)](https://leetcode.com/problems/house-robber/description/) +[198. House Robber (Easy)](https://leetcode.com/problems/house-robber/description/) 题目描述:抢劫一排住户,但是不能抢邻近的住户,求最大抢劫量。 @@ -1885,7 +2332,7 @@ public int rob(int[] nums) { **强盗在环形街区抢劫** -[Leetcode : 213. House Robber II (Medium)](https://leetcode.com/problems/house-robber-ii/description/) +[213. House Robber II (Medium)](https://leetcode.com/problems/house-robber-ii/description/) ```java private int[] dp; @@ -1940,32 +2387,34 @@ dp[N] 即为所求。

-对于一个长度为 N 的序列,最长子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,即 max{ dp[i] | 1 <= i <= N} 即为所求。 +对于一个长度为 N 的序列,最长递增子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,即 max{ dp[i] | 1 <= i <= N} 即为所求。 **最长递增子序列** -[Leetcode : 300. Longest Increasing Subsequence (Medium)](https://leetcode.com/problems/longest-increasing-subsequence/description/) +[300. Longest Increasing Subsequence (Medium)](https://leetcode.com/problems/longest-increasing-subsequence/description/) ```java public int lengthOfLIS(int[] nums) { int n = nums.length; int[] dp = new int[n]; - for(int i = 0; i < n; i++){ + for (int i = 0; i < n; i++) { int max = 1; - for(int j = 0; j < i; j++){ - if(nums[i] > nums[j]) max = Math.max(max, dp[j] + 1); + for (int j = 0; j < i; j++) { + if (nums[i] > nums[j]) { + max = Math.max(max, dp[j] + 1); + } } dp[i] = max; } int ret = 0; - for(int i = 0; i < n; i++){ + for (int i = 0; i < n; i++) { ret = Math.max(ret, dp[i]); } return ret; } ``` -以上解法的时间复杂度为 O(n2) ,可以使用二分查找使得时间复杂度降低为 O(nlogn)。定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素,例如对于数组 [4,5,6,3],有 +以上解法的时间复杂度为 O(N2) ,可以使用二分查找将时间复杂度降低为 O(NlogN)。定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。如果有多个长度相等的最长递增子序列,那么 tails[i] 就取最小值。例如对于数组 [4,5,6,3],有 ```html len = 1 : [4], [5], [6], [3] => tails[0] = 3 @@ -1973,6 +2422,7 @@ len = 2 : [4, 5], [5, 6] => tails[1] = 5 len = 3 : [4, 5, 6] => tails[2] = 6 ``` + 对于一个元素 x, - 如果它大于 tails 数组所有的值,那么把它添加到 tails 后面,表示最长递增子序列长度加 1; @@ -2006,7 +2456,7 @@ private int binarySearch(int[] nums, int first, int last, int key) { **一组整数对能够构成的最长链** -[Leetcode : 646. Maximum Length of Pair Chain (Medium)](https://leetcode.com/problems/maximum-length-of-pair-chain/description/) +[646. Maximum Length of Pair Chain (Medium)](https://leetcode.com/problems/maximum-length-of-pair-chain/description/) ```html Input: [[1,2], [2,3], [3,4]] @@ -2043,7 +2493,7 @@ public int findLongestChain(int[][] pairs) { **最长摆动子序列** -[Leetcode : 376. Wiggle Subsequence (Medium)](https://leetcode.com/problems/wiggle-subsequence/description/) +[376. Wiggle Subsequence (Medium)](https://leetcode.com/problems/wiggle-subsequence/description/) ```html Input: [1,7,4,9,2,5] @@ -2094,7 +2544,7 @@ public int wiggleMaxLength(int[] nums) { - 针对的是两个序列,求它们的最长公共子序列。 - 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j 。 -- 由于 2 ,在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 SN 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。 +- 在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 SN 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。 ```java public int lengthOfLCS(int[] nums1, int[] nums2) { @@ -2114,7 +2564,7 @@ public int lengthOfLCS(int[] nums1, int[] nums2) { 有一个容量为 N 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v。 -定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示体积不超过 j 的情况下,前 i 件物品能达到的最大价值。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论: +定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示前 i 件物品体积不超过 j 的情况下能达到的最大价值。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论: - 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。 - 第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。 @@ -2187,7 +2637,7 @@ public int knapsack(int W, int N, int[] weights, int[] values) { **划分数组为和相等的两部分** -[Leetcode : 416. Partition Equal Subset Sum (Medium)](https://leetcode.com/problems/partition-equal-subset-sum/description/) +[416. Partition Equal Subset Sum (Medium)](https://leetcode.com/problems/partition-equal-subset-sum/description/) ```html Input: [1, 5, 11, 5] @@ -2220,7 +2670,7 @@ Explanation: The array can be partitioned as [1, 5, 5] and [11]. **字符串按单词列表分割** -[Leetcode : 139. Word Break (Medium)](https://leetcode.com/problems/word-break/description/) +[139. Word Break (Medium)](https://leetcode.com/problems/word-break/description/) ```html s = "leetcode", @@ -2251,7 +2701,7 @@ public boolean wordBreak(String s, List wordDict) { **改变一组数的正负号使得它们的和为一给定数** -[Leetcode : 494. Target Sum (Medium)](https://leetcode.com/problems/target-sum/description/) +[494. Target Sum (Medium)](https://leetcode.com/problems/target-sum/description/) ```html Input: nums is [1, 1, 1, 1, 1], S is 3. @@ -2306,16 +2756,14 @@ public int findTargetSumWays(int[] nums, int S) { } private int findTargetSumWays(int[] nums, int start, int S) { - if (start == nums.length) { - return S == 0 ? 1 : 0; - } + if (start == nums.length) return S == 0 ? 1 : 0; return findTargetSumWays(nums, start + 1, S + nums[start]) + findTargetSumWays(nums, start + 1, S - nums[start]); } ``` **01 字符构成最多的字符串** -[Leetcode : 474. Ones and Zeroes (Medium)](https://leetcode.com/problems/ones-and-zeroes/description/) +[474. Ones and Zeroes (Medium)](https://leetcode.com/problems/ones-and-zeroes/description/) ```html Input: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3 @@ -2348,7 +2796,7 @@ public int findMaxForm(String[] strs, int m, int n) { **找零钱** -[Leetcode : 322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/) +[322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/) ```html Example 1: @@ -2362,7 +2810,7 @@ return -1. 题目描述:给一些面额的硬币,要求用这些硬币来组成给定面额的钱数,并且使得硬币数量最少。硬币可以重复使用。 -这是一个完全背包问题。 +这是一个完全背包问题,完全背包问题和 0-1 背包问题在实现上的区别在于,0-1 背包遍历物品的循环在外侧,而完全背包问题遍历物品的循环在内侧,在内侧体现出物品可以使用多次。 ```java public int coinChange(int[] coins, int amount) { @@ -2371,7 +2819,7 @@ public int coinChange(int[] coins, int amount) { Arrays.fill(dp, amount + 1); dp[0] = 0; for (int i = 1; i <= amount; i++) { - for (int c : coins) { + for (int c : coins) { // 硬币可以使用多次 if (c <= i) { dp[i] = Math.min(dp[i], dp[i - c] + 1); } @@ -2383,7 +2831,7 @@ public int coinChange(int[] coins, int amount) { **组合总和** -[Leetcode : 377. Combination Sum IV (Medium)](https://leetcode.com/problems/combination-sum-iv/description/) +[377. Combination Sum IV (Medium)](https://leetcode.com/problems/combination-sum-iv/description/) ```html nums = [1, 2, 3] @@ -2423,7 +2871,7 @@ public int combinationSum4(int[] nums, int target) { **只能进行 k 次的股票交易** -[Leetcode : 188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/) +[188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/) ```html dp[i, j] = max(dp[i, j-1], prices[j] - prices[jj] + dp[i-1, jj]) { jj in range of [0, j-1] } @@ -2455,7 +2903,7 @@ public int maxProfit(int k, int[] prices) { **只能进行两次的股票交易** -[Leetcode : 123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/) +[123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/) ```java public int maxProfit(int[] prices) { @@ -2475,7 +2923,7 @@ public int maxProfit(int[] prices) { **数组区间和** -[Leetcode : 303. Range Sum Query - Immutable (Easy)](https://leetcode.com/problems/range-sum-query-immutable/description/) +[303. Range Sum Query - Immutable (Easy)](https://leetcode.com/problems/range-sum-query-immutable/description/) ```html Given nums = [-2, 0, 3, -5, 2, -1] @@ -2506,7 +2954,7 @@ class NumArray { **子数组最大的和** -[Leetcode : 53. Maximum Subarray (Easy)](https://leetcode.com/problems/maximum-subarray/description/) +[53. Maximum Subarray (Easy)](https://leetcode.com/problems/maximum-subarray/description/) ```html For example, given the array [-2,1,-3,4,-1,2,1,-5,4], @@ -2528,7 +2976,7 @@ public int maxSubArray(int[] nums) { **数组中等差递增子区间的个数** -[Leetcode : 413. Arithmetic Slices (Medium)](https://leetcode.com/problems/arithmetic-slices/description/) +[413. Arithmetic Slices (Medium)](https://leetcode.com/problems/arithmetic-slices/description/) ```html A = [1, 2, 3, 4] @@ -2559,7 +3007,7 @@ public int numberOfArithmeticSlices(int[] A) { **删除两个字符串的字符使它们相等** -[Leetcode : 583. Delete Operation for Two Strings (Medium)](https://leetcode.com/problems/delete-operation-for-two-strings/description/) +[583. Delete Operation for Two Strings (Medium)](https://leetcode.com/problems/delete-operation-for-two-strings/description/) ```html Input: "sea", "eat" @@ -2584,9 +3032,30 @@ public int minDistance(String word1, String word2) { } ``` -**修改一个字符串称为另一个字符串** +**修改一个字符串成为另一个字符串** -[Leetcode : 72. Edit Distance (Hard)](https://leetcode.com/problems/edit-distance/description/) +[72. Edit Distance (Hard)](https://leetcode.com/problems/edit-distance/description/) + +```html +Example 1: + +Input: word1 = "horse", word2 = "ros" +Output: 3 +Explanation: +horse -> rorse (replace 'h' with 'r') +rorse -> rose (remove 'r') +rose -> ros (remove 'e') +Example 2: + +Input: word1 = "intention", word2 = "execution" +Output: 5 +Explanation: +intention -> inention (remove 't') +inention -> enention (replace 'i' with 'e') +enention -> exention (replace 'n' with 'x') +exention -> exection (replace 'n' with 'c') +exection -> execution (insert 'u') +``` ```java public int minDistance(String word1, String word2) { @@ -2618,7 +3087,7 @@ public int minDistance(String word1, String word2) { **分割整数的最大乘积** -[Leetcode : 343. Integer Break (Medim)](https://leetcode.com/problems/integer-break/description/) +[343. Integer Break (Medim)](https://leetcode.com/problems/integer-break/description/) 题目描述:For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4). @@ -2637,7 +3106,7 @@ public int integerBreak(int n) { **按平方数来分割整数** -[Leetcode : 279. Perfect Squares(Medium)](https://leetcode.com/problems/perfect-squares/description/) +[279. Perfect Squares(Medium)](https://leetcode.com/problems/perfect-squares/description/) 题目描述:For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9. @@ -2646,12 +3115,12 @@ public int numSquares(int n) { List squareList = generateSquareList(n); int[] dp = new int[n + 1]; for (int i = 1; i <= n; i++) { - int max = Integer.MAX_VALUE; + int min = Integer.MAX_VALUE; for (int square : squareList) { if (square > i) break; - max = Math.min(max, dp[i - square] + 1); + min = Math.min(min, dp[i - square] + 1); } - dp[i] = max; + dp[i] = min; } return dp[n]; } @@ -2671,7 +3140,7 @@ private List generateSquareList(int n) { **分割整数构成字母字符串** -[Leetcode : 91. Decode Ways (Medium)](https://leetcode.com/problems/decode-ways/description/) +[91. Decode Ways (Medium)](https://leetcode.com/problems/decode-ways/description/) 题目描述:Given encoded message "12", it could be decoded as "AB" (1 2) or "L" (12). @@ -2697,7 +3166,7 @@ public int numDecodings(String s) { **矩阵的总路径数** -[Leetcode : 62. Unique Paths (Medium)](https://leetcode.com/problems/unique-paths/description/) +[62. Unique Paths (Medium)](https://leetcode.com/problems/unique-paths/description/) 题目描述:统计从矩阵左上角到右下角的路径总数,每次只能向右或者向下移动。 @@ -2732,7 +3201,7 @@ public int uniquePaths(int m, int n) { **矩阵的最小路径和** -[Leetcode : 64. Minimum Path Sum (Medium)](https://leetcode.com/problems/minimum-path-sum/description/) +[64. Minimum Path Sum (Medium)](https://leetcode.com/problems/minimum-path-sum/description/) ```html [[1,3,1], @@ -2763,11 +3232,11 @@ public int minPathSum(int[][] grid) { **需要冷却期的股票交易** -[Leetcode : 309. Best Time to Buy and Sell Stock with Cooldown(Medium)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/) +[309. Best Time to Buy and Sell Stock with Cooldown(Medium)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/) 题目描述:交易之后需要有一天的冷却时间。 -

+

```java public int maxProfit(int[] prices) { @@ -2791,7 +3260,7 @@ public int maxProfit(int[] prices) { **需要交易费用的股票交易** -[Leetcode : 714. Best Time to Buy and Sell Stock with Transaction Fee (Medium)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/) +[714. Best Time to Buy and Sell Stock with Transaction Fee (Medium)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/) ```html Input: prices = [1, 3, 2, 8, 4, 9], fee = 2 @@ -2806,7 +3275,7 @@ The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8. 题目描述:每交易一次,都要支付一定的费用。 -

+

```java public int maxProfit(int[] prices, int fee) { @@ -2829,11 +3298,11 @@ public int maxProfit(int[] prices, int fee) { **买入和售出股票最大的收益** -[Leetcode : 121. Best Time to Buy and Sell Stock (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/) +[121. Best Time to Buy and Sell Stock (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/) 只进行一次交易。 -只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看这个价格是否是当前的最大价格。 +只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看当前收益是不是最大收益。 ```java public int maxProfit(int[] prices) { @@ -2851,7 +3320,7 @@ public int maxProfit(int[] prices) { **复制粘贴字符** -[Leetcode : 650. 2 Keys Keyboard (Medium)](https://leetcode.com/problems/2-keys-keyboard/description/) +[650. 2 Keys Keyboard (Medium)](https://leetcode.com/problems/2-keys-keyboard/description/) 题目描述:最开始只有一个字符 A,问需要多少次操作能够得到 n 个字符 A,每次操作可以复制当前所有的字符,或者粘贴。 @@ -2902,17 +3371,20 @@ public int minSteps(int n) { **整除** 令 x = 2m0 \* 3m1 \* 5m2 \* 7m3 \* 11m4 \* … + 令 y = 2n0 \* 3n1 \* 5n2 \* 7n3 \* 11n4 \* … 如果 x 整除 y(y mod x == 0),则对于所有 i,mi <= ni。 -x 和 y 的 **最大公约数** 为:gcd(x,y) = 2min(m0,n0) \* 3min(m1,n1) \* 5min(m2,n2) \* ... +**最大公约数最小公倍数** -x 和 y 的 **最小公倍数** 为:lcm(x,y) = 2max(m0,n0) \* 3max(m1,n1) \* 5max(m2,n2) \* ... +x 和 y 的最大公约数为:gcd(x,y) = 2min(m0,n0) \* 3min(m1,n1) \* 5min(m2,n2) \* ... + +x 和 y 的最小公倍数为:lcm(x,y) = 2max(m0,n0) \* 3max(m1,n1) \* 5max(m2,n2) \* ... **生成素数序列** -[Leetcode : 204. Count Primes (Easy)](https://leetcode.com/problems/count-primes/description/) +[204. Count Primes (Easy)](https://leetcode.com/problems/count-primes/description/) 埃拉托斯特尼筛法在每次找到一个素数时,将能被素数整除的数排除掉。 @@ -2920,11 +3392,13 @@ x 和 y 的 **最小公倍数** 为:lcm(x,y) = 2max(m0,n0) \* 3< public int countPrimes(int n) { boolean[] notPrimes = new boolean[n + 1]; int cnt = 0; - for(int i = 2; i < n; i++){ - if(notPrimes[i]) continue; + for (int i = 2; i < n; i++){ + if (notPrimes[i]) { + continue; + } cnt++; // 从 i * i 开始,因为如果 k < i,那么 k * i 在之前就已经被去除过了 - for(long j = (long) i * i; j < n; j += i){ + for (long j = (long) i * i; j < n; j += i){ notPrimes[(int) j] = true; } } @@ -2963,7 +3437,7 @@ int lcm(int a, int b){ **7 进制** -[Leetcode : 504. Base 7 (Easy)](https://leetcode.com/problems/base-7/description/) +[504. Base 7 (Easy)](https://leetcode.com/problems/base-7/description/) ```java public String convertToBase7(int num) { @@ -2998,7 +3472,7 @@ public String convertToBase7(int num) { **16 进制** -[Leetcode : 405. Convert a Number to Hexadecimal (Easy)](https://leetcode.com/problems/convert-a-number-to-hexadecimal/description/) +[405. Convert a Number to Hexadecimal (Easy)](https://leetcode.com/problems/convert-a-number-to-hexadecimal/description/) 负数要用它的补码形式。 @@ -3019,9 +3493,9 @@ Output: ```java public String toHex(int num) { char[] map = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; - if(num == 0) return "0"; + if (num == 0) return "0"; StringBuilder sb = new StringBuilder(); - while(num != 0){ + while (num != 0) { sb.append(map[num & 0b1111]); num >>>= 4; // 无符号右移,左边填 0 } @@ -3029,11 +3503,35 @@ public String toHex(int num) { } ``` +**26 进制** + +[168. Excel Sheet Column Title (Easy)](https://leetcode.com/problems/excel-sheet-column-title/description/) + +```html +1 -> A +2 -> B +3 -> C +... +26 -> Z +27 -> AA +28 -> AB +``` + +因为是从 1 开始计算的,而不是从 0 开始,因此需要对 n 执行 -1 操作。 + +```java +public String convertToTitle(int n) { + if (n == 0) return ""; + n--; + return convertToTitle(n / 26) + (char) (n % 26 + 'A'); +} +``` + ### 阶乘 **统计阶乘尾部有多少个 0** -[Leetcode : 172. Factorial Trailing Zeroes (Easy)](https://leetcode.com/problems/factorial-trailing-zeroes/description/) +[172. Factorial Trailing Zeroes (Easy)](https://leetcode.com/problems/factorial-trailing-zeroes/description/) 尾部的 0 由 2 * 5 得来,2 的数量明显多于 5 的数量,因此只要统计有多少个 5 即可。 @@ -3051,7 +3549,7 @@ public int trailingZeroes(int n) { **二进制加法** -[Leetcode : 67. Add Binary (Easy)](https://leetcode.com/problems/add-binary/description/) +[67. Add Binary (Easy)](https://leetcode.com/problems/add-binary/description/) ```html a = "11" @@ -3075,9 +3573,9 @@ public String addBinary(String a, String b) { **字符串加法** -[Leetcode : 415. Add Strings (Easy)](https://leetcode.com/problems/add-strings/description/) +[415. Add Strings (Easy)](https://leetcode.com/problems/add-strings/description/) -字符串的值为非负整数 +字符串的值为非负整数。 ```java public String addStrings(String num1, String num2) { @@ -3097,7 +3595,7 @@ public String addStrings(String num1, String num2) { **改变数组元素使所有的数组元素都相等** -[Leetcode : 462. Minimum Moves to Equal Array Elements II (Medium)](https://leetcode.com/problems/minimum-moves-to-equal-array-elements-ii/description/) +[462. Minimum Moves to Equal Array Elements II (Medium)](https://leetcode.com/problems/minimum-moves-to-equal-array-elements-ii/description/) ```html Input: @@ -3127,14 +3625,14 @@ Only two moves are needed (remember each move increments or decrements one eleme ```java public int minMoves2(int[] nums) { Arrays.sort(nums); - int ret = 0; + int move = 0; int l = 0, h = nums.length - 1; - while(l <= h) { - ret += nums[h] - nums[l]; + while (l <= h) { + move += nums[h] - nums[l]; l++; h--; } - return ret; + return move; } ``` @@ -3144,31 +3642,41 @@ public int minMoves2(int[] nums) { ```java public int minMoves2(int[] nums) { - int ret = 0; - int n = nums.length; - int median = quickSelect(nums, 0, n - 1, n / 2 + 1); - for(int num : nums) ret += Math.abs(num - median); - return ret; + int move = 0; + int median = findKthSmallest(nums, nums.length / 2); + for (int num : nums) { + move += Math.abs(num - median); + } + return move; } -private int quickSelect(int[] nums, int start, int end, int k) { - int l = start, r = end, privot = nums[(l + r) / 2]; - while(l <= r) { - while(nums[l] < privot) l++; - while(nums[r] > privot) r--; - if(l >= r) break; - swap(nums, l, r); - l++; r--; +private int findKthSmallest(int[] nums, int k) { + int l = 0, h = nums.length - 1; + while (l < h) { + int j = partition(nums, l, h); + if (j == k) break; + if (j < k) l = j + 1; + else h = j - 1; } - int left = l - start + 1; - if(left > k) return quickSelect(nums, start, l - 1, k); - if(left == k && l == r) return nums[l]; - int right = r - start + 1; - return quickSelect(nums, r + 1, end, k - right); + return nums[k]; +} + +private int partition(int[] nums, int l, int h) { + int i = l, j = h + 1; + while (true) { + while (nums[++i] < nums[l] && i < h) ; + while (nums[--j] > nums[l] && j > l) ; + if (i >= j) break; + swap(nums, i, j); + } + swap(nums, l, j); + return j; } private void swap(int[] nums, int i, int j) { - int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; } ``` @@ -3176,9 +3684,9 @@ private void swap(int[] nums, int i, int j) { **数组中出现次数多于 n / 2 的元素** -[Leetcode : 169. Majority Element (Easy)](https://leetcode.com/problems/majority-element/description/) +[169. Majority Element (Easy)](https://leetcode.com/problems/majority-element/description/) -先对数组排序,最中间那个数出现次数一定多于 n / 2 +先对数组排序,最中间那个数出现次数一定多于 n / 2。 ```java public int majorityElement(int[] nums) { @@ -3187,18 +3695,14 @@ public int majorityElement(int[] nums) { } ``` -可以利用 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。 +可以利用 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。 ```java public int majorityElement(int[] nums) { - int cnt = 0, majority = 0; - for(int i = 0; i < nums.length; i++){ - if(cnt == 0) { - majority = nums[i]; - cnt++; - } - else if(majority == nums[i]) cnt++; - else cnt--; + int cnt = 0, majority = nums[0]; + for (int num : nums) { + majority = (cnt == 0) ? num : majority; + cnt = (majority == num) ? cnt + 1 : cnt - 1; } return majority; } @@ -3208,7 +3712,7 @@ public int majorityElement(int[] nums) { **平方数** -[Leetcode : 367. Valid Perfect Square (Easy)](https://leetcode.com/problems/valid-perfect-square/description/) +[367. Valid Perfect Square (Easy)](https://leetcode.com/problems/valid-perfect-square/description/) ```html Input: 16 @@ -3216,6 +3720,7 @@ Returns: True ``` 平方序列:1,4,9,16,.. + 间隔:3,5,7,... 间隔为等差数列,使用这个特性可以得到从 1 开始的平方序列。 @@ -3233,7 +3738,7 @@ public boolean isPerfectSquare(int num) { **3 的 n 次方** -[Leetcode : 326. Power of Three (Easy)](https://leetcode.com/problems/power-of-three/description/) +[326. Power of Three (Easy)](https://leetcode.com/problems/power-of-three/description/) ```java public boolean isPowerOfThree(int n) { @@ -3241,9 +3746,40 @@ public boolean isPowerOfThree(int n) { } ``` +**乘积数组** + +[238. Product of Array Except Self (Medium)](https://leetcode.com/problems/product-of-array-except-self/description/) + +```html +For example, given [1,2,3,4], return [24,12,8,6]. +``` + +题目描述:给定一个数组,创建一个新数组,新数组的每个元素为原始数组中除了该位置上的元素之外所有元素的乘积。 + +题目要求:时间复杂度为 O(N),并且不能使用除法。 + +```java +public int[] productExceptSelf(int[] nums) { + int n = nums.length; + int[] products = new int[n]; + Arrays.fill(products, 1); + int left = 1; + for (int i = 1; i < n; i++) { + left *= nums[i - 1]; + products[i] *= left; + } + int right = 1; + for (int i = n - 2; i >= 0; i--) { + right *= nums[i + 1]; + products[i] *= right; + } + return products; +} +``` + **找出数组中的乘积最大的三个数** -[Leetcode : 628. Maximum Product of Three Numbers (Easy)](https://leetcode.com/problems/maximum-product-of-three-numbers/description/) +[628. Maximum Product of Three Numbers (Easy)](https://leetcode.com/problems/maximum-product-of-three-numbers/description/) ```html Input: [1,2,3,4] @@ -3276,44 +3812,13 @@ public int maximumProduct(int[] nums) { } ``` -**乘积数组** - -[Leetcode : 238. Product of Array Except Self (Medium)](https://leetcode.com/problems/product-of-array-except-self/description/) - -```html -For example, given [1,2,3,4], return [24,12,8,6]. -``` - -题目描述:给定一个数组,创建一个新数组,新数组的每个元素为原始数组中除了该位置上的元素之外所有元素的乘积。 - -题目要求:时间复杂度为 O(n),并且不能使用除法。 - -```java -public int[] productExceptSelf(int[] nums) { - int n = nums.length; - int[] ret = new int[n]; - ret[0] = 1; - int left = 1; - for (int i = 1; i < n; i++) { - ret[i] = left * nums[i - 1]; - left *= nums[i - 1]; - } - int right = 1; - for (int i = n - 1; i >= 0; i--) { - ret[i] *= right; - right *= nums[i]; - } - return ret; -} -``` - # 数据结构相关 ## 栈和队列 **用栈实现队列** -[Leetcode : 232. Implement Queue using Stacks (Easy)](https://leetcode.com/problems/implement-queue-using-stacks/description/) +[232. Implement Queue using Stacks (Easy)](https://leetcode.com/problems/implement-queue-using-stacks/description/) 一个栈实现: @@ -3350,6 +3855,7 @@ class MyQueue { ```java class MyQueue { + private Stack in = new Stack(); private Stack out = new Stack(); @@ -3383,7 +3889,7 @@ class MyQueue { **用队列实现栈** -[Leetcode : 225. Implement Stack using Queues (Easy)](https://leetcode.com/problems/implement-stack-using-queues/description/) +[225. Implement Stack using Queues (Easy)](https://leetcode.com/problems/implement-stack-using-queues/description/) ```java class MyStack { @@ -3418,7 +3924,7 @@ class MyStack { **最小值栈** -[Leetcode : 155. Min Stack (Easy)](https://leetcode.com/problems/min-stack/description/) +[155. Min Stack (Easy)](https://leetcode.com/problems/min-stack/description/) 用两个栈实现,一个存储数据,一个存储最小值。 @@ -3444,7 +3950,7 @@ class MinStack { public void pop() { dataStack.pop(); minStack.pop(); - min = minStack.isEmpty() ? min = Integer.MAX_VALUE : minStack.peek(); + min = minStack.isEmpty() ? Integer.MAX_VALUE : minStack.peek(); } public int top() { @@ -3461,7 +3967,7 @@ class MinStack { **用栈实现括号匹配** -[Leetcode : 20. Valid Parentheses (Easy)](https://leetcode.com/problems/valid-parentheses/description/) +[20. Valid Parentheses (Easy)](https://leetcode.com/problems/valid-parentheses/description/) ```html "()[]{}" @@ -3494,7 +4000,7 @@ Input: [73, 74, 75, 71, 69, 72, 76, 73] Output: [1, 1, 4, 2, 1, 1, 0, 0] ``` -[Leetcode : 739. Daily Temperatures (Medium)](https://leetcode.com/problems/daily-temperatures/description/) +[739. Daily Temperatures (Medium)](https://leetcode.com/problems/daily-temperatures/description/) 在遍历数组时用 Stack 把数组中的数存起来,如果当前遍历的数比栈顶元素来的大,说明栈顶元素的下一个比它大的数就是当前元素。 @@ -3503,8 +4009,8 @@ public int[] dailyTemperatures(int[] temperatures) { int n = temperatures.length; int[] ret = new int[n]; Stack stack = new Stack<>(); - for(int i = 0; i < n; i++) { - while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) { + for (int i = 0; i < n; i++) { + while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) { int idx = stack.pop(); ret[idx] = i - idx; } @@ -3516,7 +4022,7 @@ public int[] dailyTemperatures(int[] temperatures) { **在另一个数组中比当前元素大的下一个元素** -[Leetcode : 496. Next Greater Element I (Easy)](https://leetcode.com/problems/next-greater-element-i/description/) +[496. Next Greater Element I (Easy)](https://leetcode.com/problems/next-greater-element-i/description/) ```html Input: nums1 = [4,1,2], nums2 = [1,3,4,2]. @@ -3527,15 +4033,15 @@ Output: [-1,3,-1] public int[] nextGreaterElement(int[] nums1, int[] nums2) { Map map = new HashMap<>(); Stack stack = new Stack<>(); - for(int num : nums2){ - while(!stack.isEmpty() && num > stack.peek()){ + for (int num : nums2) { + while (!stack.isEmpty() && num > stack.peek()) { map.put(stack.pop(), num); } stack.add(num); } int[] ret = new int[nums1.length]; - for(int i = 0; i < nums1.length; i++){ - if(map.containsKey(nums1[i])) ret[i] = map.get(nums1[i]); + for (int i = 0; i < nums1.length; i++) { + if (map.containsKey(nums1[i])) ret[i] = map.get(nums1[i]); else ret[i] = -1; } return ret; @@ -3544,7 +4050,7 @@ public int[] nextGreaterElement(int[] nums1, int[] nums2) { **循环数组中比当前元素大的下一个元素** -[Leetcode : 503. Next Greater Element II (Medium)](https://leetcode.com/problems/next-greater-element-ii/description/) +[503. Next Greater Element II (Medium)](https://leetcode.com/problems/next-greater-element-ii/description/) ```java public int[] nextGreaterElements(int[] nums) { @@ -3571,23 +4077,23 @@ Java 中的 **HashSet** 用于存储一个集合,并以 O(1) 的时间复杂 Java 中的 **HashMap** 主要用于映射关系,从而把两个元素联系起来。 -在对一个内容进行压缩或者其它转换时,利用 HashMap 可以把原始内容和转换后的内容联系起来。例如在一个简化 url 的系统中([Leetcdoe : 535. Encode and Decode TinyURL (Medium)](https://leetcode.com/problems/encode-and-decode-tinyurl/description/)),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源。 +在对一个内容进行压缩或者其它转换时,利用 HashMap 可以把原始内容和转换后的内容联系起来。例如在一个简化 url 的系统中[Leetcdoe : 535. Encode and Decode TinyURL (Medium)](https://leetcode.com/problems/encode-and-decode-tinyurl/description/),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源。 HashMap 也可以用来对元素进行计数统计,此时键为元素,值为计数。和 HashSet 类似,如果元素有穷并且范围不大,可以用整型数组来进行统计。 **数组中的两个数和为给定值** -[Leetcode : 1. Two Sum (Easy)](https://leetcode.com/problems/two-sum/description/) +[1. Two Sum (Easy)](https://leetcode.com/problems/two-sum/description/) 可以先对数组进行排序,然后使用双指针方法或者二分查找方法。这样做的时间复杂度为 O(NlogN),空间复杂度为 O(1)。 -用 HashMap 存储数组元素和索引的映射,在访问到 nums[i] 时,判断 HashMap 中是否存在 target - nums[i] ,如果存在说明 target - nums[i] 所在的索引和 i 就是要找的两个数。该方法的时间复杂度为 O(N),空间复杂度为 O(N),使用空间来换取时间。 +用 HashMap 存储数组元素和索引的映射,在访问到 nums[i] 时,判断 HashMap 中是否存在 target - nums[i],如果存在说明 target - nums[i] 所在的索引和 i 就是要找的两个数。该方法的时间复杂度为 O(N),空间复杂度为 O(N),使用空间来换取时间。 ```java public int[] twoSum(int[] nums, int target) { HashMap map = new HashMap<>(); - for(int i = 0; i < nums.length; i++){ - if(map.containsKey(target - nums[i])) return new int[]{map.get(target - nums[i]), i}; + for (int i = 0; i < nums.length; i++) { + if (map.containsKey(target - nums[i])) return new int[] { map.get(target - nums[i]), i }; else map.put(nums[i], i); } return null; @@ -3596,19 +4102,21 @@ public int[] twoSum(int[] nums, int target) { **判断数组是否含有相同元素** -[Leetcode : 217. Contains Duplicate (Easy)](https://leetcode.com/problems/contains-duplicate/description/) +[217. Contains Duplicate (Easy)](https://leetcode.com/problems/contains-duplicate/description/) ```java public boolean containsDuplicate(int[] nums) { Set set = new HashSet<>(); - for (int num : nums) set.add(num); + for (int num : nums) { + set.add(num); + } return set.size() < nums.length; } ``` **最长和谐序列** -[Leetcode : 594. Longest Harmonious Subsequence (Easy)](https://leetcode.com/problems/longest-harmonious-subsequence/description/) +[594. Longest Harmonious Subsequence (Easy)](https://leetcode.com/problems/longest-harmonious-subsequence/description/) ```html Input: [1,3,2,2,5,2,3,7] @@ -3636,13 +4144,15 @@ public int findLHS(int[] nums) { **最长连续序列** -[Leetcode : 128. Longest Consecutive Sequence (Medium)](https://leetcode.com/problems/longest-consecutive-sequence/description/) +[128. Longest Consecutive Sequence (Hard)](https://leetcode.com/problems/longest-consecutive-sequence/description/) ```html Given [100, 4, 200, 1, 3, 2], The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4. ``` +题目要求:以 O(N) 的时间复杂度求解。 + ```java public int longestConsecutive(int[] nums) { Map numCnts = new HashMap<>(); @@ -3677,55 +4187,28 @@ private int count(Map numCnts, int num) { **两个字符串包含的字符是否完全相同** -[Leetcode : 242. Valid Anagram (Easy)](https://leetcode.com/problems/valid-anagram/description/) +[242. Valid Anagram (Easy)](https://leetcode.com/problems/valid-anagram/description/) ```html s = "anagram", t = "nagaram", return true. s = "rat", t = "car", return false. ``` -字符串只包含小写字符,总共有 26 个小写字符。可以用 Hash Table 来映射字符与出现次数,因为键值范围很小,因此可以使用长度为 26 的整型数组对字符串出现的字符进行统计,比较两个字符串出现的字符数量是否相同。 +字符串只包含小写字符,总共有 26 个小写字符。可以用 Hash Table 来映射字符与出现次数,因为键值范围很小,因此可以使用长度为 26 的整型数组对字符串出现的字符进行统计,然后比较两个字符串出现的字符数量是否相同。 ```java public boolean isAnagram(String s, String t) { int[] cnts = new int[26]; - for(int i = 0; i < s.length(); i++) cnts[s.charAt(i) - 'a'] ++; - for(int i = 0; i < t.length(); i++) cnts[t.charAt(i) - 'a'] --; - for(int i = 0; i < 26; i++) if(cnts[i] != 0) return false; - return true; -} -``` - -**字符串同构** - -[Leetcode : 205. Isomorphic Strings (Easy)](https://leetcode.com/problems/isomorphic-strings/description/) - -```html -Given "egg", "add", return true. -Given "foo", "bar", return false. -Given "paper", "title", return true. -``` - -记录一个字符上次出现的位置,如果两个字符串中某个字符上次出现的位置一样,那么就属于同构。 - -```java -public boolean isIsomorphic(String s, String t) { - int[] m1 = new int[256]; - int[] m2 = new int[256]; - for(int i = 0; i < s.length(); i++){ - if(m1[s.charAt(i)] != m2[t.charAt(i)]) { - return false; - } - m1[s.charAt(i)] = i + 1; - m2[t.charAt(i)] = i + 1; - } + for (char c : s.toCharArray()) cnts[c - 'a']++; + for (char c : t.toCharArray()) cnts[c - 'a']--; + for (int cnt : cnts) if (cnt != 0) return false; return true; } ``` **计算一组字符集合可以组成的回文字符串的最大长度** -[Leetcode : 409. Longest Palindrome (Easy)](https://leetcode.com/problems/longest-palindrome/description/) +[409. Longest Palindrome (Easy)](https://leetcode.com/problems/longest-palindrome/description/) ```html Input : "abccccdd" @@ -3733,34 +4216,60 @@ Output : 7 Explanation : One longest palindrome that can be built is "dccaccd", whose length is 7. ``` -使用长度为 128 的整型数组来统计每个字符出现的个数,每个字符有偶数个可以用来构成回文字符串。因为回文字符串最中间的那个字符可以单独出现,所以如果有单独的字符就把它放到最中间。 +使用长度为 256 的整型数组来统计每个字符出现的个数,每个字符有偶数个可以用来构成回文字符串。因为回文字符串最中间的那个字符可以单独出现,所以如果有单独的字符就把它放到最中间。 ```java public int longestPalindrome(String s) { - int[] cnts = new int[128]; // ascii 码总共 128 个 - for(char c : s.toCharArray()) cnts[c]++; + int[] cnts = new int[256]; + for (char c : s.toCharArray()) cnts[c]++; int ret = 0; - for(int cnt : cnts) ret += (cnt / 2) * 2; - if(ret < s.length()) ret++; // 这个条件下 s 中一定有单个未使用的字符存在,可以把这个字符放到回文的最中间 + for (int cnt : cnts) ret += (cnt / 2) * 2; + if (ret < s.length()) ret++; // 这个条件下 s 中一定有单个未使用的字符存在,可以把这个字符放到回文的最中间 return ret; } ``` +**字符串同构** + +[205. Isomorphic Strings (Easy)](https://leetcode.com/problems/isomorphic-strings/description/) + +```html +Given "egg", "add", return true. +Given "foo", "bar", return false. +Given "paper", "title", return true. +``` + +记录一个字符上次出现的位置,如果两个字符串中的字符上次出现的位置一样,那么就属于同构。 + +```java +public boolean isIsomorphic(String s, String t) { + int[] preIndexOfS = new int[256]; + int[] preIndexOfT = new int[256]; + for (int i = 0; i < s.length(); i++) { + char sc = s.charAt(i), tc = t.charAt(i); + if (preIndexOfS[sc] != preIndexOfT[tc]) return false; + preIndexOfS[sc] = i + 1; + preIndexOfT[tc] = i + 1; + } + return true; +} +``` + **判断一个整数是否是回文数** -[Leetcode : 9. Palindrome Number (Easy)](https://leetcode.com/problems/palindrome-number/description/) +[9. Palindrome Number (Easy)](https://leetcode.com/problems/palindrome-number/description/) -要求不能使用额外空间,也就不能将整数转换为字符串进行判断。 +题目要求:不能使用额外空间,也就不能将整数转换为字符串进行判断。 将整数分成左右两部分,右边那部分需要转置,然后判断这两部分是否相等。 ```java public boolean isPalindrome(int x) { - if(x == 0) return true; - if(x < 0) return false; - if(x % 10 == 0) return false; + if (x == 0) return true; + if (x < 0) return false; + if (x % 10 == 0) return false; int right = 0; - while(x > right){ + while (x > right) { right = right * 10 + x % 10; x /= 10; } @@ -3770,7 +4279,7 @@ public boolean isPalindrome(int x) { **回文子字符串** -[Leetcode : 647. Palindromic Substrings (Medium)](https://leetcode.com/problems/palindromic-substrings/description/) +[647. Palindromic Substrings (Medium)](https://leetcode.com/problems/palindromic-substrings/description/) ```html Input: "aaa" @@ -3783,7 +4292,7 @@ Explanation: Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa". ```java private int cnt = 0; public int countSubstrings(String s) { - for(int i = 0; i < s.length(); i++) { + for (int i = 0; i < s.length(); i++) { extendSubstrings(s, i, i); // 奇数长度 extendSubstrings(s, i, i + 1); // 偶数长度 } @@ -3791,7 +4300,7 @@ public int countSubstrings(String s) { } private void extendSubstrings(String s, int start, int end) { - while(start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)) { + while (start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)) { start--; end++; cnt++; @@ -3801,7 +4310,7 @@ private void extendSubstrings(String s, int start, int end) { **统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数** -[Leetcode : 696. Count Binary Substrings (Easy)](https://leetcode.com/problems/count-binary-substrings/description/) +[696. Count Binary Substrings (Easy)](https://leetcode.com/problems/count-binary-substrings/description/) ```html Input: "00110011" @@ -3812,14 +4321,13 @@ Explanation: There are 6 substrings that have equal number of consecutive 1's an ```java public int countBinarySubstrings(String s) { int preLen = 0, curLen = 1, ret = 0; - for(int i = 1; i < s.length(); i++){ - if(s.charAt(i) == s.charAt(i-1)) curLen++; - else{ + for (int i = 1; i < s.length(); i++) { + if (s.charAt(i) == s.charAt(i-1)) curLen++; + else { preLen = curLen; curLen = 1; } - - if(preLen >= curLen) ret++; + if (preLen >= curLen) ret++; } return ret; } @@ -3827,7 +4335,7 @@ public int countBinarySubstrings(String s) { **字符串循环移位包含** -[ 编程之美:3.1](#) +[编程之美:3.1](#) ```html s1 = AABCD, s2 = CDAA @@ -3840,7 +4348,7 @@ s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 **字符串循环移位** -[ 编程之美:2.17](#) +[编程之美:2.17](#) 将字符串向右循环移动 k 位。 @@ -3860,7 +4368,7 @@ s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 **把数组中的 0 移到末尾** -[Leetcode : 283. Move Zeroes (Easy)](https://leetcode.com/problems/move-zeroes/description/) +[283. Move Zeroes (Easy)](https://leetcode.com/problems/move-zeroes/description/) ```html For example, given nums = [0, 1, 0, 3, 12], after calling your function, nums should be [1, 3, 12, 0, 0]. @@ -3876,15 +4384,15 @@ public void moveZeroes(int[] nums) { **调整矩阵** -[Leetcode : 566. Reshape the Matrix (Easy)](https://leetcode.com/problems/reshape-the-matrix/description/) +[566. Reshape the Matrix (Easy)](https://leetcode.com/problems/reshape-the-matrix/description/) ```html -Input: -nums = +Input: +nums = [[1,2], [3,4]] r = 1, c = 4 -Output: +Output: [[1,2,3,4]] Explanation: The row-traversing of nums is [1,2,3,4]. The new reshaped matrix is a 1 * 4 matrix, fill it row by row by using the previous list. @@ -3908,193 +4416,22 @@ public int[][] matrixReshape(int[][] nums, int r, int c) { **找出数组中最长的连续 1** -[Leetcode : 485. Max Consecutive Ones (Easy)](https://leetcode.com/problems/max-consecutive-ones/description/) +[485. Max Consecutive Ones (Easy)](https://leetcode.com/problems/max-consecutive-ones/description/) ```java public int findMaxConsecutiveOnes(int[] nums) { - int max = 0; - int cur = 0; + int max = 0, cur = 0; for (int num : nums) { - if (num == 0) cur = 0; - else { - cur++; - max = Math.max(max, cur); - } + cur = num == 0 ? 0 : cur + 1; + max = Math.max(max, cur); } return max; } ``` -**数组相邻差值的个数** - -[Leetcode : 667. Beautiful Arrangement II (Medium)](https://leetcode.com/problems/beautiful-arrangement-ii/description/) - -```html -Input: n = 3, k = 2 -Output: [1, 3, 2] -Explanation: The [1, 3, 2] has three different positive integers ranging from 1 to 3, and the [2, 1] has exactly 2 distinct integers: 1 and 2. -``` - -题目描述:数组元素为 1\~n 的整数,要求构建数组,使得相邻元素的差值不相同的个数为 k。 - -让前 k+1 个元素构建出 k 个不相同的差值,序列为:1 k+1 2 k 3 k-1 ... k/2 k/2+1. - -```java -public int[] constructArray(int n, int k) { - int[] ret = new int[n]; - ret[0] = 1; - for (int i = 1, interval = k; i <= k; i++, interval--) { - ret[i] = i % 2 == 1 ? ret[i - 1] + interval : ret[i - 1] - interval; - } - for (int i = k + 1; i < n; i++) { - ret[i] = i + 1; - } - return ret; -} -``` - -**数组的度** - -[Leetcode : 697. Degree of an Array (Easy)](https://leetcode.com/problems/degree-of-an-array/description/) - -```html -Input: [1,2,2,3,1,4,2] -Output: 6 -``` - -题目描述:数组的度定义为元素出现的最高频率,例如上面的数组度为 3。要求找到一个最小的子数组,这个子数组的度和原数组一样。 - -```java -public int findShortestSubArray(int[] nums) { - Map numsCnt = new HashMap<>(); - Map numsLastIndex = new HashMap<>(); - Map numsFirstIndex = new HashMap<>(); - for (int i = 0; i < nums.length; i++) { - int num = nums[i]; - numsCnt.put(num, numsCnt.getOrDefault(num, 0) + 1); - numsLastIndex.put(num, i); - if (!numsFirstIndex.containsKey(num)) { - numsFirstIndex.put(num, i); - } - } - int maxCnt = 0; - for (int num : nums) { - maxCnt = Math.max(maxCnt, numsCnt.get(num)); - } - int ret = nums.length; - for (int i = 0; i < nums.length; i++) { - int num = nums[i]; - int cnt = numsCnt.get(num); - if (cnt != maxCnt) continue; - ret = Math.min(ret, numsLastIndex.get(num) - numsFirstIndex.get(num) + 1); - } - return ret; -} -``` - -**对角元素相等的矩阵** - -[Leetcode : 766. Toeplitz Matrix (Easy)](https://leetcode.com/problems/toeplitz-matrix/description/) - -```html -1234 -5123 -9512 - -In the above grid, the diagonals are "[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]", and in each diagonal all elements are the same, so the answer is True. -``` - -```java -public boolean isToeplitzMatrix(int[][] matrix) { - for (int i = 0; i < matrix[0].length; i++) { - if (!check(matrix, matrix[0][i], 0, i)) { - return false; - } - } - for (int i = 0; i < matrix.length; i++) { - if (!check(matrix, matrix[i][0], i, 0)) { - return false; - } - } - return true; -} - -private boolean check(int[][] matrix, int expectValue, int row, int col) { - if (row >= matrix.length || col >= matrix[0].length) { - return true; - } - if (matrix[row][col] != expectValue) { - return false; - } - return check(matrix, expectValue, row + 1, col + 1); -} -``` - -**嵌套数组** - -[Leetcode : 565. Array Nesting (Medium)](https://leetcode.com/problems/array-nesting/description/) - -```html -Input: A = [5,4,0,3,1,6,2] -Output: 4 -Explanation: -A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2. - -One of the longest S[K]: -S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0} -``` - -题目描述:S[i] 表示一个集合,集合的第一个元素是 A[i],第二个元素是 A[A[i]],如此嵌套下去。求最大的 S[i]。 - -```java -public int arrayNesting(int[] nums) { - int max = 0; - for (int i = 0; i < nums.length; i++) { - int cnt = 0; - for (int j = i; nums[j] != -1; ) { - cnt++; - int t = nums[j]; - nums[j] = -1; // 标记该位置已经被访问 - j = t; - - } - max = Math.max(max, cnt); - } - return max; -} -``` - -**分隔数组** - -[Leetcode : 769. Max Chunks To Make Sorted (Medium)](https://leetcode.com/problems/max-chunks-to-make-sorted/description/) - -```html -Input: arr = [1,0,2,3,4] -Output: 4 -Explanation: -We can split into two chunks, such as [1, 0], [2, 3, 4]. -However, splitting into [1, 0], [2], [3], [4] is the highest number of chunks possible. -``` - -题目描述:分隔数组,使得对每部分排序后数组就为有序。 - -```java -public int maxChunksToSorted(int[] arr) { - if (arr == null) return 0; - int ret = 0; - int right = arr[0]; - for (int i = 0; i < arr.length; i++) { - right = Math.max(right, arr[i]); - if (right == i) ret++; - } - return ret; -} -``` - - **一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出丢失的数和重复的数** -[Leetcode : 645. Set Mismatch (Easy)](https://leetcode.com/problems/set-mismatch/description/) +[645. Set Mismatch (Easy)](https://leetcode.com/problems/set-mismatch/description/) ```html Input: nums = [1,2,2,4] @@ -4106,16 +4443,9 @@ Input: nums = [1,2,2,4] Output: [2,3] ``` -最直接的方法是先对数组进行排序,这种方法时间复杂度为 O(nlogn)。本题可以以 O(n) 的时间复杂度、O(1) 空间复杂度来求解。 +最直接的方法是先对数组进行排序,这种方法时间复杂度为 O(NlogN)。本题可以以 O(N) 的时间复杂度、O(1) 空间复杂度来求解。 -主要思想是通过交换数组元素,使得数组上的元素在正确的位置上。 - -遍历数组,如果第 i 位上的元素不是 i + 1 ,那么就交换第 i 位 和 nums[i] - 1 位上的元素,使得 num[i] - 1 的元素为 nums[i] ,也就是该位的元素是正确的。交换操作需要循环进行,因为一次交换没办法使得第 i 位上的元素是正确的。但是要交换的两个元素可能就是重复元素,那么循环就可能永远进行下去,终止循环的方法是加上 nums[i] != nums[nums[i] - 1 条件。 - -类似题目: - -- [Leetcode :448. Find All Numbers Disappeared in an Array (Easy)](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/),寻找所有丢失的元素 -- [Leetcode : 442. Find All Duplicates in an Array (Medium)](https://leetcode.com/problems/find-all-duplicates-in-an-array/description/),寻找所有重复的元素。 +主要思想是通过交换数组元素,使得数组上的元素在正确的位置上。遍历数组,如果第 i 位上的元素不是 i + 1,那么就交换第 i 位和 nums[i] - 1 位上的元素,使得 num[i] - 1 位置上的元素为 nums[i],也就是该位置上的元素是正确的。 ```java public int[] findErrorNums(int[] nums) { @@ -4136,9 +4466,14 @@ private void swap(int[] nums, int i, int j) { } ``` +类似题目: + +- [448. Find All Numbers Disappeared in an Array (Easy)](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/),寻找所有丢失的元素 +- [442. Find All Duplicates in an Array (Medium)](https://leetcode.com/problems/find-all-duplicates-in-an-array/description/),寻找所有重复的元素。 + **找出数组中重复的数,数组值在 [1, n] 之间** -[Leetcode : 287. Find the Duplicate Number (Medium)](https://leetcode.com/problems/find-the-duplicate-number/description/) +[287. Find the Duplicate Number (Medium)](https://leetcode.com/problems/find-the-duplicate-number/description/) 要求不能修改数组,也不能使用额外的空间。 @@ -4180,7 +4515,7 @@ public int findDuplicate(int[] nums) { **有序矩阵查找** -[Leetocde : 240. Search a 2D Matrix II (Medium)](https://leetcode.com/problems/search-a-2d-matrix-ii/description/) +[240. Search a 2D Matrix II (Medium)](https://leetcode.com/problems/search-a-2d-matrix-ii/description/) ```html [ @@ -4206,7 +4541,7 @@ public boolean searchMatrix(int[][] matrix, int target) { **有序矩阵的 Kth Element** -[Leetcode : 378. Kth Smallest Element in a Sorted Matrix ((Medium))](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/) +[378. Kth Smallest Element in a Sorted Matrix ((Medium))](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/) ```html matrix = [ @@ -4270,11 +4605,179 @@ class Tuple implements Comparable { } ``` +**数组相邻差值的个数** + +[667. Beautiful Arrangement II (Medium)](https://leetcode.com/problems/beautiful-arrangement-ii/description/) + +```html +Input: n = 3, k = 2 +Output: [1, 3, 2] +Explanation: The [1, 3, 2] has three different positive integers ranging from 1 to 3, and the [2, 1] has exactly 2 distinct integers: 1 and 2. +``` + +题目描述:数组元素为 1\~n 的整数,要求构建数组,使得相邻元素的差值不相同的个数为 k。 + +让前 k+1 个元素构建出 k 个不相同的差值,序列为:1 k+1 2 k 3 k-1 ... k/2 k/2+1. + +```java +public int[] constructArray(int n, int k) { + int[] ret = new int[n]; + ret[0] = 1; + for (int i = 1, interval = k; i <= k; i++, interval--) { + ret[i] = i % 2 == 1 ? ret[i - 1] + interval : ret[i - 1] - interval; + } + for (int i = k + 1; i < n; i++) { + ret[i] = i + 1; + } + return ret; +} +``` + +**数组的度** + +[697. Degree of an Array (Easy)](https://leetcode.com/problems/degree-of-an-array/description/) + +```html +Input: [1,2,2,3,1,4,2] +Output: 6 +``` + +题目描述:数组的度定义为元素出现的最高频率,例如上面的数组度为 3。要求找到一个最小的子数组,这个子数组的度和原数组一样。 + +```java +public int findShortestSubArray(int[] nums) { + Map numsCnt = new HashMap<>(); + Map numsLastIndex = new HashMap<>(); + Map numsFirstIndex = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + int num = nums[i]; + numsCnt.put(num, numsCnt.getOrDefault(num, 0) + 1); + numsLastIndex.put(num, i); + if (!numsFirstIndex.containsKey(num)) { + numsFirstIndex.put(num, i); + } + } + int maxCnt = 0; + for (int num : nums) { + maxCnt = Math.max(maxCnt, numsCnt.get(num)); + } + int ret = nums.length; + for (int i = 0; i < nums.length; i++) { + int num = nums[i]; + int cnt = numsCnt.get(num); + if (cnt != maxCnt) continue; + ret = Math.min(ret, numsLastIndex.get(num) - numsFirstIndex.get(num) + 1); + } + return ret; +} +``` + +**对角元素相等的矩阵** + +[766. Toeplitz Matrix (Easy)](https://leetcode.com/problems/toeplitz-matrix/description/) + +```html +1234 +5123 +9512 + +In the above grid, the diagonals are "[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]", and in each diagonal all elements are the same, so the answer is True. +``` + +```java +public boolean isToeplitzMatrix(int[][] matrix) { + for (int i = 0; i < matrix[0].length; i++) { + if (!check(matrix, matrix[0][i], 0, i)) { + return false; + } + } + for (int i = 0; i < matrix.length; i++) { + if (!check(matrix, matrix[i][0], i, 0)) { + return false; + } + } + return true; +} + +private boolean check(int[][] matrix, int expectValue, int row, int col) { + if (row >= matrix.length || col >= matrix[0].length) { + return true; + } + if (matrix[row][col] != expectValue) { + return false; + } + return check(matrix, expectValue, row + 1, col + 1); +} +``` + +**嵌套数组** + +[565. Array Nesting (Medium)](https://leetcode.com/problems/array-nesting/description/) + +```html +Input: A = [5,4,0,3,1,6,2] +Output: 4 +Explanation: +A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2. + +One of the longest S[K]: +S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0} +``` + +题目描述:S[i] 表示一个集合,集合的第一个元素是 A[i],第二个元素是 A[A[i]],如此嵌套下去。求最大的 S[i]。 + +```java +public int arrayNesting(int[] nums) { + int max = 0; + for (int i = 0; i < nums.length; i++) { + int cnt = 0; + for (int j = i; nums[j] != -1; ) { + cnt++; + int t = nums[j]; + nums[j] = -1; // 标记该位置已经被访问 + j = t; + + } + max = Math.max(max, cnt); + } + return max; +} +``` + +**分隔数组** + +[769. Max Chunks To Make Sorted (Medium)](https://leetcode.com/problems/max-chunks-to-make-sorted/description/) + +```html +Input: arr = [1,0,2,3,4] +Output: 4 +Explanation: +We can split into two chunks, such as [1, 0], [2, 3, 4]. +However, splitting into [1, 0], [2], [3], [4] is the highest number of chunks possible. +``` + +题目描述:分隔数组,使得对每部分排序后数组就为有序。 + +```java +public int maxChunksToSorted(int[] arr) { + if (arr == null) return 0; + int ret = 0; + int right = arr[0]; + for (int i = 0; i < arr.length; i++) { + right = Math.max(right, arr[i]); + if (right == i) ret++; + } + return ret; +} +``` + ## 链表 +链表是空节点,或者有一个值和一个指向下一个链表的指针,因此很多链表问题可以用递归来处理。 + **找出两个链表的交点** -[Leetcode : 160. Intersection of Two Linked Lists (Easy)](https://leetcode.com/problems/intersection-of-two-linked-lists/description/) +[160. Intersection of Two Linked Lists (Easy)](https://leetcode.com/problems/intersection-of-two-linked-lists/description/) ```html A: a1 → a2 @@ -4284,7 +4787,7 @@ A: a1 → a2 B: b1 → b2 → b3 ``` -要求:时间复杂度为 O(n) 空间复杂度为 O(1) +要求:时间复杂度为 O(N) 空间复杂度为 O(1) 设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 @@ -4292,9 +4795,8 @@ B: b1 → b2 → b3 ```java public ListNode getIntersectionNode(ListNode headA, ListNode headB) { - if(headA == null || headB == null) return null; ListNode l1 = headA, l2 = headB; - while(l1 != l2){ + while (l1 != l2) { l1 = (l1 == null) ? headB : l1.next; l2 = (l2 == null) ? headA : l2.next; } @@ -4306,48 +4808,57 @@ public ListNode getIntersectionNode(ListNode headA, ListNode headB) { **链表反转** -[Leetcode : 206. Reverse Linked List (Easy)](https://leetcode.com/problems/reverse-linked-list/description/) +[206. Reverse Linked List (Easy)](https://leetcode.com/problems/reverse-linked-list/description/) -头插法能够按逆序构建链表。 +递归 ```java public ListNode reverseList(ListNode head) { - ListNode newHead = null; // 设为 null,作为新链表的结尾 - while(head != null){ - ListNode nextNode = head.next; - head.next = newHead; - newHead = head; - head = nextNode; - } + if (head == null || head.next == null) return head; + ListNode next = head.next; + ListNode newHead = reverseList(next); + next.next = head; + head.next = null; return newHead; } ``` +头插法 + +```java +public ListNode reverseList(ListNode head) { + ListNode newHead = new ListNode(-1); + while (head != null) { + ListNode next = head.next; + head.next = newHead.next; + newHead.next = head; + head = next; + } + return newHead.next; +} +``` + **归并两个有序的链表** -[Leetcode : 21. Merge Two Sorted Lists (Easy)](https://leetcode.com/problems/merge-two-sorted-lists/description/) - -链表和树一样,可以用递归方式来定义:链表是空节点,或者有一个值和一个指向下一个链表的指针。因此很多链表问题可以用递归来处理。 +[21. Merge Two Sorted Lists (Easy)](https://leetcode.com/problems/merge-two-sorted-lists/description/) ```java public ListNode mergeTwoLists(ListNode l1, ListNode l2) { - if(l1 == null) return l2; - if(l2 == null) return l1; - ListNode newHead = null; - if(l1.val < l2.val){ - newHead = l1; - newHead.next = mergeTwoLists(l1.next, l2); - } else{ - newHead = l2; - newHead.next = mergeTwoLists(l1, l2.next); + if (l1 == null) return l2; + if (l2 == null) return l1; + if (l1.val < l2.val) { + l1.next = mergeTwoLists(l1.next, l2); + return l1; + } else { + l2.next = mergeTwoLists(l1, l2.next); + return l2; } - return newHead; } ``` **从有序链表中删除重复节点** -[Leetcode : 83. Remove Duplicates from Sorted List (Easy)](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/) +[83. Remove Duplicates from Sorted List (Easy)](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/) ```html Given 1->1->2, return 1->2. @@ -4356,7 +4867,7 @@ Given 1->1->2->3->3, return 1->2->3. ```java public ListNode deleteDuplicates(ListNode head) { - if(head == null || head.next == null) return head; + if (head == null || head.next == null) return head; head.next = deleteDuplicates(head.next); return head.next != null && head.val == head.next.val ? head.next : head; } @@ -4364,7 +4875,7 @@ public ListNode deleteDuplicates(ListNode head) { **删除链表的倒数第 n 个节点** -[Leetcode : 19. Remove Nth Node From End of List (Medium)](https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/) +[19. Remove Nth Node From End of List (Medium)](https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/) ```html Given linked list: 1->2->3->4->5, and n = 2. @@ -4391,7 +4902,7 @@ public ListNode removeNthFromEnd(ListNode head, int n) { **交换链表中的相邻结点** -[Leetcode : 24. Swap Nodes in Pairs (Medium)](https://leetcode.com/problems/swap-nodes-in-pairs/description/) +[24. Swap Nodes in Pairs (Medium)](https://leetcode.com/problems/swap-nodes-in-pairs/description/) ```html Given 1->2->3->4, you should return the list as 2->1->4->3. @@ -4416,55 +4927,9 @@ public ListNode swapPairs(ListNode head) { } ``` -**根据有序链表构造平衡的 BST** - -[Leetcode : 109. Convert Sorted List to Binary Search Tree (Medium)](https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/description/) - -```html -Given the sorted linked list: [-10,-3,0,5,9], - -One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST: - - 0 - / \ - -3 9 - / / - -10 5 -``` - -```java -public TreeNode sortedListToBST(ListNode head) { - if (head == null) return null; - int size = size(head); - if (size == 1) return new TreeNode(head.val); - ListNode pre = head, mid = pre.next; - int step = 2; - while (step <= size / 2) { - pre = mid; - mid = mid.next; - step++; - } - pre.next = null; - TreeNode t = new TreeNode(mid.val); - t.left = sortedListToBST(head); - t.right = sortedListToBST(mid.next); - return t; -} - -private int size(ListNode node) { - int size = 0; - while (node != null) { - size++; - node = node.next; - } - return size; -} -``` - - **链表求和** -[Leetcode : 445. Add Two Numbers II (Medium)](https://leetcode.com/problems/add-two-numbers-ii/description/) +[445. Add Two Numbers II (Medium)](https://leetcode.com/problems/add-two-numbers-ii/description/) ```html Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) @@ -4501,49 +4966,9 @@ private Stack buildStack(ListNode l) { } ``` -**分隔链表** - -[Leetcode : 725. Split Linked List in Parts(Medium)](https://leetcode.com/problems/split-linked-list-in-parts/description/) - -```html -Input: -root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3 -Output: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] -Explanation: -The input has been split into consecutive parts with size difference at most 1, and earlier parts are a larger size than the later parts. -``` - -题目描述:把链表分隔成 k 部分,每部分的长度都应该尽可能相同,排在前面的长度应该大于等于后面的。 - -```java -public ListNode[] splitListToParts(ListNode root, int k) { - int N = 0; - ListNode cur = root; - while (cur != null) { - N++; - cur = cur.next; - } - int mod = N % k; - int size = N / k; - ListNode[] ret = new ListNode[k]; - cur = root; - for (int i = 0; cur != null && i < k; i++) { - ret[i] = cur; - int curSize = size + (mod-- > 0 ? 1 : 0); - for (int j = 0; j < curSize - 1; j++) { - cur = cur.next; - } - ListNode next = cur.next; - cur.next = null; - cur = next; - } - return ret; -} -``` - **回文链表** -[Leetcode : 234. Palindrome Linked List (Easy)](https://leetcode.com/problems/palindrome-linked-list/description/) +[234. Palindrome Linked List (Easy)](https://leetcode.com/problems/palindrome-linked-list/description/) 要求以 O(1) 的空间复杂度来求解。 @@ -4596,7 +5021,7 @@ private boolean isEqual(ListNode l1, ListNode l2) { **链表元素按奇偶聚集** -[Leetcode : 328. Odd Even Linked List (Medium)](https://leetcode.com/problems/odd-even-linked-list/description/) +[328. Odd Even Linked List (Medium)](https://leetcode.com/problems/odd-even-linked-list/description/) ```html Example: @@ -4621,6 +5046,46 @@ public ListNode oddEvenList(ListNode head) { } ``` +**分隔链表** + +[725. Split Linked List in Parts(Medium)](https://leetcode.com/problems/split-linked-list-in-parts/description/) + +```html +Input: +root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3 +Output: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] +Explanation: +The input has been split into consecutive parts with size difference at most 1, and earlier parts are a larger size than the later parts. +``` + +题目描述:把链表分隔成 k 部分,每部分的长度都应该尽可能相同,排在前面的长度应该大于等于后面的。 + +```java +public ListNode[] splitListToParts(ListNode root, int k) { + int N = 0; + ListNode cur = root; + while (cur != null) { + N++; + cur = cur.next; + } + int mod = N % k; + int size = N / k; + ListNode[] ret = new ListNode[k]; + cur = root; + for (int i = 0; cur != null && i < k; i++) { + ret[i] = cur; + int curSize = size + (mod-- > 0 ? 1 : 0); + for (int j = 0; j < curSize - 1; j++) { + cur = cur.next; + } + ListNode next = cur.next; + cur.next = null; + cur = next; + } + return ret; +} +``` + ## 树 ### 递归 @@ -4629,22 +5094,22 @@ public ListNode oddEvenList(ListNode head) { **树的高度** -[Leetcode : 104. Maximum Depth of Binary Tree (Easy)](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/) +[104. Maximum Depth of Binary Tree (Easy)](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/) ```java public int maxDepth(TreeNode root) { - if(root == null) return 0; + if (root == null) return 0; return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; } ``` **翻转树** -[Leetcode : 226. Invert Binary Tree (Easy)](https://leetcode.com/problems/invert-binary-tree/description/) +[226. Invert Binary Tree (Easy)](https://leetcode.com/problems/invert-binary-tree/description/) ```java public TreeNode invertTree(TreeNode root) { - if(root == null) return null; + if (root == null) return null; TreeNode left = root.left; // 后面的操作会改变 left 指针,因此先保存下来 root.left = invertTree(root.right); root.right = invertTree(left); @@ -4654,7 +5119,7 @@ public TreeNode invertTree(TreeNode root) { **归并两棵树** -[Leetcode : 617. Merge Two Binary Trees (Easy)](https://leetcode.com/problems/merge-two-binary-trees/description/) +[617. Merge Two Binary Trees (Easy)](https://leetcode.com/problems/merge-two-binary-trees/description/) ```html Input: @@ -4675,9 +5140,9 @@ Merged tree: ```java public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { - if(t1 == null && t2 == null) return null; - if(t1 == null) return t2; - if(t2 == null) return t1; + if (t1 == null && t2 == null) return null; + if (t1 == null) return t2; + if (t2 == null) return t1; TreeNode root = new TreeNode(t1.val + t2.val); root.left = mergeTrees(t1.left, t2.left); root.right = mergeTrees(t1.right, t2.right); @@ -4705,15 +5170,15 @@ return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22. ```java public boolean hasPathSum(TreeNode root, int sum) { - if(root == null) return false; - if(root.left == null && root.right == null && root.val == sum) return true; + if (root == null) return false; + if (root.left == null && root.right == null && root.val == sum) return true; return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val); } ``` **统计路径和等于一个数的路径数量** -[Leetcode : 437. Path Sum III (Easy)](https://leetcode.com/problems/path-sum-iii/description/) +[437. Path Sum III (Easy)](https://leetcode.com/problems/path-sum-iii/description/) ```html root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 @@ -4743,17 +5208,64 @@ public int pathSum(TreeNode root, int sum) { } private int pathSumStartWithRoot(TreeNode root, int sum){ - if(root == null) return 0; + if (root == null) return 0; int ret = 0; - if(root.val == sum) ret++; + if (root.val == sum) ret++; ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val); return ret; } ``` +**子树** + +[572. Subtree of Another Tree (Easy)](https://leetcode.com/problems/subtree-of-another-tree/description/) + +```html +Given tree s: + 3 + / \ + 4 5 + / \ + 1 2 +Given tree t: + 4 + / \ + 1 2 +Return true, because t has the same structure and node values with a subtree of s. + +Given tree s: + + 3 + / \ + 4 5 + / \ + 1 2 + / + 0 +Given tree t: + 4 + / \ + 1 2 +Return false. +``` + +```java +public boolean isSubtree(TreeNode s, TreeNode t) { + if (s == null) return false; + return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t); +} + +private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) { + if (t == null && s == null) return true; + if (t == null || s == null) return false; + if (t.val != s.val) return false; + return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right); +} +``` + **树的对称** -[Leetcode : 101. Symmetric Tree (Easy)](https://leetcode.com/problems/symmetric-tree/description/) +[101. Symmetric Tree (Easy)](https://leetcode.com/problems/symmetric-tree/description/) ```html 1 @@ -4765,21 +5277,21 @@ private int pathSumStartWithRoot(TreeNode root, int sum){ ```java public boolean isSymmetric(TreeNode root) { - if(root == null) return true; + if (root == null) return true; return isSymmetric(root.left, root.right); } private boolean isSymmetric(TreeNode t1, TreeNode t2){ - if(t1 == null && t2 == null) return true; - if(t1 == null || t2 == null) return false; - if(t1.val != t2.val) return false; + if (t1 == null && t2 == null) return true; + if (t1 == null || t2 == null) return false; + if (t1.val != t2.val) return false; return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left); } ``` **平衡树** -[Leetcode : 110. Balanced Binary Tree (Easy)](https://leetcode.com/problems/balanced-binary-tree/description/) +[110. Balanced Binary Tree (Easy)](https://leetcode.com/problems/balanced-binary-tree/description/) ```html 3 @@ -4810,23 +5322,23 @@ public int maxDepth(TreeNode root) { **最小路径** -[Leetcode : 111. Minimum Depth of Binary Tree (Easy)](https://leetcode.com/problems/minimum-depth-of-binary-tree/description/) +[111. Minimum Depth of Binary Tree (Easy)](https://leetcode.com/problems/minimum-depth-of-binary-tree/description/) 树的根节点到叶子节点的最小路径长度 ```java public int minDepth(TreeNode root) { - if(root == null) return 0; + if (root == null) return 0; int left = minDepth(root.left); int right = minDepth(root.right); - if(left == 0 || right == 0) return left + right + 1; + if (left == 0 || right == 0) return left + right + 1; return Math.min(left, right) + 1; } ``` **统计左叶子节点的和** -[Leetcode : 404. Sum of Left Leaves (Easy)](https://leetcode.com/problems/sum-of-left-leaves/description/) +[404. Sum of Left Leaves (Easy)](https://leetcode.com/problems/sum-of-left-leaves/description/) ```html 3 @@ -4840,23 +5352,23 @@ There are two left leaves in the binary tree, with values 9 and 15 respectively. ```java public int sumOfLeftLeaves(TreeNode root) { - if(root == null) return 0; - if(isLeaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right); + if (root == null) return 0; + if (isLeaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right); return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right); } private boolean isLeaf(TreeNode node){ - if(node == null) return false; + if (node == null) return false; return node.left == null && node.right == null; } ``` **修剪二叉查找树** -[Leetcode : 669. Trim a Binary Search Tree (Easy)](https://leetcode.com/problems/trim-a-binary-search-tree/description/) +[669. Trim a Binary Search Tree (Easy)](https://leetcode.com/problems/trim-a-binary-search-tree/description/) ```html -Input: +Input: 3 / \ 0 4 @@ -4868,10 +5380,10 @@ Input: L = 1 R = 3 -Output: +Output: 3 - / - 2 + / + 2 / 1 ``` @@ -4882,52 +5394,18 @@ Output: ```java public TreeNode trimBST(TreeNode root, int L, int R) { - if(root == null) return null; - if(root.val > R) return trimBST(root.left, L, R); - if(root.val < L) return trimBST(root.right, L, R); + if (root == null) return null; + if (root.val > R) return trimBST(root.left, L, R); + if (root.val < L) return trimBST(root.right, L, R); root.left = trimBST(root.left, L, R); root.right = trimBST(root.right, L, R); return root; } ``` -**子树** - -[Leetcode : 572. Subtree of Another Tree (Easy)](https://leetcode.com/problems/subtree-of-another-tree/description/) - -```html -Given tree s: - 3 - / \ - 4 5 - / \ - 1 2 -Given tree t: - 4 - / \ - 1 2 -Return true, because t has the same structure and node values with a subtree of s. -``` - -```java -public boolean isSubtree(TreeNode s, TreeNode t) { - if(s == null && t == null) return true; - if(s == null || t == null) return false; - if(s.val == t.val && isSame(s, t)) return true; - return isSubtree(s.left, t) || isSubtree(s.right, t); -} - -private boolean isSame(TreeNode s, TreeNode t){ - if(s == null && t == null) return true; - if(s == null || t == null) return false; - if(s.val != t.val) return false; - return isSame(s.left, t.left) && isSame(s.right, t.right); -} -``` - **从有序数组中构造二叉查找树** -[Leetcode : 108. Convert Sorted Array to Binary Search Tree (Easy)](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/) +[108. Convert Sorted Array to Binary Search Tree (Easy)](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/) ```java public TreeNode sortedArrayToBST(int[] nums) { @@ -4935,7 +5413,7 @@ public TreeNode sortedArrayToBST(int[] nums) { } private TreeNode toBST(int[] nums, int sIdx, int eIdx){ - if(sIdx > eIdx) return null; + if (sIdx > eIdx) return null; int mIdx = (sIdx + eIdx) / 2; TreeNode root = new TreeNode(nums[mIdx]); root.left = toBST(nums, sIdx, mIdx - 1); @@ -4946,7 +5424,7 @@ private TreeNode toBST(int[] nums, int sIdx, int eIdx){ **两节点的最长路径** -[Leetcode : 543. Diameter of Binary Tree (Easy)](https://leetcode.com/problems/diameter-of-binary-tree/description/) +[543. Diameter of Binary Tree (Easy)](https://leetcode.com/problems/diameter-of-binary-tree/description/) ```html Input: @@ -4954,7 +5432,7 @@ Input: / \ 2 3 / \ - 4 5 + 4 5 Return 3, which is the length of the path [4,2,1,3] or [5,2,1,3]. ``` @@ -4978,7 +5456,7 @@ private int depth(TreeNode root) { **找出二叉树中第二小的节点** -[Leetcode : 671. Second Minimum Node In a Binary Tree (Easy)](https://leetcode.com/problems/second-minimum-node-in-a-binary-tree/description/) +[671. Second Minimum Node In a Binary Tree (Easy)](https://leetcode.com/problems/second-minimum-node-in-a-binary-tree/description/) ```html Input: @@ -4995,21 +5473,21 @@ Output: 5 ```java public int findSecondMinimumValue(TreeNode root) { - if(root == null) return -1; - if(root.left == null && root.right == null) return -1; + if (root == null) return -1; + if (root.left == null && root.right == null) return -1; int leftVal = root.left.val; int rightVal = root.right.val; - if(leftVal == root.val) leftVal = findSecondMinimumValue(root.left); - if(rightVal == root.val) rightVal = findSecondMinimumValue(root.right); - if(leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal); - if(leftVal != -1) return leftVal; + if (leftVal == root.val) leftVal = findSecondMinimumValue(root.left); + if (rightVal == root.val) rightVal = findSecondMinimumValue(root.right); + if (leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal); + if (leftVal != -1) return leftVal; return rightVal; } ``` **二叉查找树的最近公共祖先** -[Leetcode : 235. Lowest Common Ancestor of a Binary Search Tree (Easy)](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/) +[235. Lowest Common Ancestor of a Binary Search Tree (Easy)](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/) ```html _______6______ @@ -5024,15 +5502,15 @@ For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another exa ```java public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { - if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q); - if(root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q); + if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q); + if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q); return root; } ``` **二叉树的最近公共祖先** -[Leetcode : 236. Lowest Common Ancestor of a Binary Tree (Medium) ](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) +[236. Lowest Common Ancestor of a Binary Tree (Medium) ](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) ```html _______3______ @@ -5056,27 +5534,28 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { **相同节点值的最大路径长度** -[Leetcode : 687. Longest Univalue Path (Easy)](https://leetcode.com/problems/longest-univalue-path/) +[687. Longest Univalue Path (Easy)](https://leetcode.com/problems/longest-univalue-path/) ```html 1 / \ 4 5 / \ \ - 4 4 5 + 4 4 5 Output : 2 ``` ```java private int path = 0; + public int longestUnivaluePath(TreeNode root) { dfs(root); return path; } private int dfs(TreeNode root){ - if(root == null) return 0; + if (root == null) return 0; int left = dfs(root.left); int right = dfs(root.right); int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0; @@ -5088,13 +5567,13 @@ private int dfs(TreeNode root){ **间隔遍历** -[Leetcode : 337. House Robber III (Medium)](https://leetcode.com/problems/house-robber-iii/description/) +[337. House Robber III (Medium)](https://leetcode.com/problems/house-robber-iii/description/) ```html 3 / \ 2 3 - \ \ + \ \ 3 1 Maximum amount of money the thief can rob = 3 + 3 + 1 = 7. ``` @@ -5125,17 +5604,17 @@ public int rob(TreeNode root) { ```java public List averageOfLevels(TreeNode root) { List ret = new ArrayList<>(); - if(root == null) return ret; + if (root == null) return ret; Queue queue = new LinkedList<>(); queue.add(root); - while(!queue.isEmpty()){ + while (!queue.isEmpty()){ int cnt = queue.size(); double sum = 0; - for(int i = 0; i < cnt; i++){ + for (int i = 0; i < cnt; i++){ TreeNode node = queue.poll(); sum += node.val; - if(node.left != null) queue.add(node.left); - if(node.right != null) queue.add(node.right); + if (node.left != null) queue.add(node.left); + if (node.right != null) queue.add(node.right); } ret.add(sum / cnt); } @@ -5145,7 +5624,7 @@ public List averageOfLevels(TreeNode root) { **得到左下角的节点** -[Leetcode : 513. Find Bottom Left Tree Value (Easy)](https://leetcode.com/problems/find-bottom-left-tree-value/description/) +[513. Find Bottom Left Tree Value (Easy)](https://leetcode.com/problems/find-bottom-left-tree-value/description/) ```html Input: @@ -5166,10 +5645,10 @@ Output: public int findBottomLeftValue(TreeNode root) { Queue queue = new LinkedList<>(); queue.add(root); - while(!queue.isEmpty()){ + while (!queue.isEmpty()){ root = queue.poll(); - if(root.right != null) queue.add(root.right); - if(root.left != null) queue.add(root.left); + if (root.right != null) queue.add(root.right); + if (root.left != null) queue.add(root.left); } return root.val; } @@ -5226,7 +5705,7 @@ void dfs(TreeNode root){ **非递归实现二叉树的前序遍历** -[Leetcode : 144. Binary Tree Preorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-preorder-traversal/description/) +[144. Binary Tree Preorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-preorder-traversal/description/) ```java public List preorderTraversal(TreeNode root) { @@ -5246,7 +5725,7 @@ public List preorderTraversal(TreeNode root) { **非递归实现二叉树的后序遍历** -[Leetcode : 145. Binary Tree Postorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-postorder-traversal/description/) +[145. Binary Tree Postorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-postorder-traversal/description/) 前序遍历为 root -> left -> right,后序遍历为 left -> right -> root,可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。 @@ -5269,7 +5748,7 @@ public List postorderTraversal(TreeNode root) { **非递归实现二叉树的中序遍历** -[Leetcode : 94. Binary Tree Inorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-inorder-traversal/description/) +[94. Binary Tree Inorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-inorder-traversal/description/) ```java public List inorderTraversal(TreeNode root) { @@ -5320,17 +5799,17 @@ public boolean findTarget(TreeNode root, int k) { List nums = new ArrayList<>(); inOrder(root, nums); int i = 0, j = nums.size() - 1; - while(i < j){ + while (i < j){ int sum = nums.get(i) + nums.get(j); - if(sum == k) return true; - if(sum < k) i++; + if (sum == k) return true; + if (sum < k) i++; else j--; } return false; } private void inOrder(TreeNode root, List nums){ - if(root == null) return; + if (root == null) return; inOrder(root.left, nums); nums.add(root.val); inOrder(root.right, nums); @@ -5339,7 +5818,7 @@ private void inOrder(TreeNode root, List nums){ **在 BST 中查找两个节点之差的最小绝对值** -[Leetcode : 530. Minimum Absolute Difference in BST (Easy)](https://leetcode.com/problems/minimum-absolute-difference-in-bst/description/) +[530. Minimum Absolute Difference in BST (Easy)](https://leetcode.com/problems/minimum-absolute-difference-in-bst/description/) ```html Input: @@ -5365,9 +5844,9 @@ public int getMinimumDifference(TreeNode root) { } private void inorder(TreeNode node){ - if(node == null) return; + if (node == null) return; inorder(node.left); - if(preVal != -1) minDiff = Math.min(minDiff, Math.abs(node.val - preVal)); + if (preVal != -1) minDiff = Math.min(minDiff, Math.abs(node.val - preVal)); preVal = node.val; inorder(node.right); } @@ -5375,7 +5854,7 @@ private void inorder(TreeNode node){ **把 BST 每个节点的值都加上比它大的节点的值** -[Leetcode : Convert BST to Greater Tree (Easy)](https://leetcode.com/problems/convert-bst-to-greater-tree/description/) +[Convert BST to Greater Tree (Easy)](https://leetcode.com/problems/convert-bst-to-greater-tree/description/) ```html Input: The root of a Binary Search Tree like this: @@ -5410,7 +5889,7 @@ private void traver(TreeNode root) { **寻找 BST 中出现次数最多的节点** -[Leetcode : 501. Find Mode in Binary Search Tree (Easy)](https://leetcode.com/problems/find-mode-in-binary-search-tree/description/) +[501. Find Mode in Binary Search Tree (Easy)](https://leetcode.com/problems/find-mode-in-binary-search-tree/description/) ```html 1 @@ -5459,20 +5938,20 @@ private void inOrder(TreeNode node) { **寻找 BST 的第 k 个元素** -[Leetcode : 230. Kth Smallest Element in a BST (Medium)](https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/) +[230. Kth Smallest Element in a BST (Medium)](https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/) 递归解法: ```java public int kthSmallest(TreeNode root, int k) { int leftCnt = count(root.left); - if(leftCnt == k - 1) return root.val; - if(leftCnt > k - 1) return kthSmallest(root.left, k); + if (leftCnt == k - 1) return root.val; + if (leftCnt > k - 1) return kthSmallest(root.left, k); return kthSmallest(root.right, k - leftCnt - 1); } private int count(TreeNode node) { - if(node == null) return 0; + if (node == null) return 0; return 1 + count(node.left) + count(node.right); } ``` @@ -5500,6 +5979,52 @@ private void inOrder(TreeNode node, int k) { } ``` +**根据有序链表构造平衡的 BST** + +[109. Convert Sorted List to Binary Search Tree (Medium)](https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/description/) + +```html +Given the sorted linked list: [-10,-3,0,5,9], + +One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST: + + 0 + / \ + -3 9 + / / + -10 5 +``` + +```java +public TreeNode sortedListToBST(ListNode head) { + if (head == null) return null; + int size = size(head); + if (size == 1) return new TreeNode(head.val); + ListNode pre = head, mid = pre.next; + int step = 2; + while (step <= size / 2) { + pre = mid; + mid = mid.next; + step++; + } + pre.next = null; + TreeNode t = new TreeNode(mid.val); + t.left = sortedListToBST(head); + t.right = sortedListToBST(mid.next); + return t; +} + +private int size(ListNode node) { + int size = 0; + while (node != null) { + size++; + node = node.next; + } + return size; +} +``` + + ### Trie

@@ -5508,11 +6033,16 @@ Trie,又称前缀树或字典树,用于判断字符串是否存在或者是 **实现一个 Trie** -[Leetcode : 208. Implement Trie (Prefix Tree) (Medium)](https://leetcode.com/problems/implement-trie-prefix-tree/description/) +[208. Implement Trie (Prefix Tree) (Medium)](https://leetcode.com/problems/implement-trie-prefix-tree/description/) ```java class Trie { + private class Node { + Node[] childs = new Node[26]; + boolean isLeaf; + } + private Node root = new Node(); public Trie() { @@ -5522,48 +6052,50 @@ class Trie { insert(word, root); } - private void insert(String word, Node node){ - int idx = word.charAt(0) - 'a'; - if(node.child[idx] == null){ - node.child[idx] = new Node(); + private void insert(String word, Node node) { + if (node == null) return; + if (word.length() == 0) { + node.isLeaf = true; + return; } - if(word.length() == 1) node.child[idx].isLeaf = true; - else insert(word.substring(1), node.child[idx]); + int index = indexForChar(word.charAt(0)); + if (node.childs[index] == null) { + node.childs[index] = new Node(); + } + insert(word.substring(1), node.childs[index]); } public boolean search(String word) { return search(word, root); } - private boolean search(String word, Node node){ - if(node == null) return false; - int idx = word.charAt(0) - 'a'; - if(node.child[idx] == null) return false; - if(word.length() == 1) return node.child[idx].isLeaf; - return search(word.substring(1), node.child[idx]); + private boolean search(String word, Node node) { + if (node == null) return false; + if (word.length() == 0) return node.isLeaf; + int index = indexForChar(word.charAt(0)); + return search(word.substring(1), node.childs[index]); } public boolean startsWith(String prefix) { return startWith(prefix, root); } - private boolean startWith(String prefix, Node node){ - if(node == null) return false; - if(prefix.length() == 0) return true; - int idx = prefix.charAt(0) - 'a'; - return startWith(prefix.substring(1), node.child[idx]); + private boolean startWith(String prefix, Node node) { + if (node == null) return false; + if (prefix.length() == 0) return true; + int index = indexForChar(prefix.charAt(0)); + return startWith(prefix.substring(1), node.childs[index]); } - private class Node{ - Node[] child = new Node[26]; - boolean isLeaf; + private int indexForChar(char c) { + return c - 'a'; } } ``` **实现一个 Trie,用来求前缀和** -[Leetcode : 677. Map Sum Pairs (Medium)](https://leetcode.com/problems/map-sum-pairs/description/) +[677. Map Sum Pairs (Medium)](https://leetcode.com/problems/map-sum-pairs/description/) ```html Input: insert("apple", 3), Output: Null @@ -5591,15 +6123,16 @@ class MapSum { } private void insert(String key, Node node, int val) { - int idx = key.charAt(0) - 'a'; - if (node.child[idx] == null) { - node.child[idx] = new Node(); + if (node == null) return; + if (key.length() == 0) { + node.value = val; + return; } - if (key.length() == 1) { - node.child[idx].value = val; - } else { - insert(key.substring(1), node.child[idx], val); + int index = indexForChar(key.charAt(0)); + if (node.child[index] == null) { + node.child[index] = new Node(); } + insert(key.substring(1), node.child[index], val); } public int sum(String prefix) { @@ -5607,28 +6140,203 @@ class MapSum { } private int sum(String prefix, Node node) { - if (node == null) { - return 0; + if (node == null) return 0; + if (prefix.length() != 0) { + int index = indexForChar(prefix.charAt(0)); + return sum(prefix.substring(1), node.child[index]); } int sum = node.value; - if (prefix.length() == 0) { - for (Node next : node.child) { - sum += sum(prefix, next); - } - } else { - int idx = prefix.charAt(0) - 'a'; - sum = sum(prefix.substring(1), node.child[idx]); + for (Node child : node.child) { + sum += sum(prefix, child); } return sum; } + + private int indexForChar(char c) { + return c - 'a'; + } } ``` ## 图 +### 二分图 + +如果可以用两种颜色对图中的节点进行着色,并且保证相邻的节点颜色不同,那么这个图就是二分图。 + +**判断是否为二分图** + +[785. Is Graph Bipartite? (Medium)](https://leetcode.com/problems/is-graph-bipartite/description/) + +```html +Input: [[1,3], [0,2], [1,3], [0,2]] +Output: true +Explanation: +The graph looks like this: +0----1 +| | +| | +3----2 +We can divide the vertices into two groups: {0, 2} and {1, 3}. +``` + +```html +Example 2: +Input: [[1,2,3], [0,2], [0,1,3], [0,2]] +Output: false +Explanation: +The graph looks like this: +0----1 +| \ | +| \ | +3----2 +We cannot find a way to divide the set of nodes into two independent subsets. +``` + +```java +public boolean isBipartite(int[][] graph) { + int[] colors = new int[graph.length]; + Arrays.fill(colors, -1); + + for (int i = 0; i < graph.length; i++) + if (colors[i] != -1 && !isBipartite(graph, i, 0, colors)) + return false; + + return true; +} + +private boolean isBipartite(int[][] graph, int cur, int color, int[] colors) { + if (colors[cur] != -1) + return colors[cur] == color; + + colors[cur] = color; + for (int next : graph[cur]) + if (!isBipartite(graph, next, 1 - color, colors)) + return false; + + return true; +} +``` + +### 拓扑排序 + +常用于在具有先序关系的任务规划中。 + +**课程安排的合法性** + +[207. Course Schedule (Medium)](https://leetcode.com/problems/course-schedule/description/) + +```html +2, [[1,0]] +return true +``` + +```html +2, [[1,0],[0,1]] +return false +``` + +题目描述:一个课程可能会先修课程,判断给定的先修课程规定是否合法。 + +本题不需要使用拓扑排序,只需要检测有向图是否存在环即可。 + +```java +public boolean canFinish(int numCourses, int[][] prerequisites) { + List[] graphic = new List[numCourses]; + for (int i = 0; i < numCourses; i++) + graphic[i] = new ArrayList<>(); + for (int[] pre : prerequisites) + graphic[pre[0]].add(pre[1]); + + boolean[] globalMarked = new boolean[numCourses]; + boolean[] localMarked = new boolean[numCourses]; + for (int i = 0; i < numCourses; i++) + if (!dfs(globalMarked, localMarked, graphic, i)) + return false; + + return true; +} + +private boolean dfs(boolean[] globalMarked, boolean[] localMarked, List[] graphic, int curNode) { + if (localMarked[curNode]) + return false; + if (globalMarked[curNode]) + return true; + + globalMarked[curNode] = true; + localMarked[curNode] = true; + + for (int nextNode : graphic[curNode]) + if (!dfs(globalMarked, localMarked, graphic, nextNode)) + return false; + + localMarked[curNode] = false; + + return true; +} +``` + +**课程安排的顺序** + +[210. Course Schedule II (Medium)](https://leetcode.com/problems/course-schedule-ii/description/) + +```html +4, [[1,0],[2,0],[3,1],[3,2]] +There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is[0,2,1,3]. +``` + +使用 DFS 来实现拓扑排序,使用一个栈存储后序遍历结果,这个栈元素的逆序结果就是拓扑排序结果。 + +证明:对于任何先序关系:v->w,后序遍历结果可以保证 w 先进入栈中,因此栈的逆序结果中 v 会在 w 之前。 + +```java +public int[] findOrder(int numCourses, int[][] prerequisites) { + List[] graphic = new List[numCourses]; + for (int i = 0; i < numCourses; i++) + graphic[i] = new ArrayList<>(); + for (int[] pre : prerequisites) + graphic[pre[0]].add(pre[1]); + + Stack topologyOrder = new Stack<>(); + boolean[] globalMarked = new boolean[numCourses]; + boolean[] localMarked = new boolean[numCourses]; + for (int i = 0; i < numCourses; i++) + if (!dfs(globalMarked, localMarked, graphic, i, topologyOrder)) + return new int[0]; + + int[] ret = new int[numCourses]; + for (int i = numCourses - 1; i >= 0; i--) + ret[i] = topologyOrder.pop(); + return ret; +} + +private boolean dfs(boolean[] globalMarked, boolean[] localMarked, List[] graphic, int curNode, Stack topologyOrder) { + if (localMarked[curNode]) + return false; + if (globalMarked[curNode]) + return true; + + globalMarked[curNode] = true; + localMarked[curNode] = true; + + for (int nextNode : graphic[curNode]) + if (!dfs(globalMarked, localMarked, graphic, nextNode, topologyOrder)) + return false; + + localMarked[curNode] = false; + topologyOrder.push(curNode); + + return true; +} +``` + +### 并查集 + +并查集可以动态地连通两个点,并且可以非常快速地判断两个点是否连通。 + **冗余连接** -[Leetcode : 684. Redundant Connection (Medium)](https://leetcode.com/problems/redundant-connection/description/) +[684. Redundant Connection (Medium)](https://leetcode.com/problems/redundant-connection/description/) ```html Input: [[1,2], [1,3], [2,3]] @@ -5698,19 +6406,21 @@ x ^ 1s = ~x x & 1s = x x | 1s = 1s x ^ x = 0 x & x = x x | x = x ``` -- 利用 x ^ 1s = \~x 的特点,可以将位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数; -- 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask :00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位; -- 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1 。 +- 利用 x ^ 1s = \~x 的特点,可以将位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。 +- 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask :00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。 +- 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。 -\>\> n 为算术右移,相当于除以 2n; -\>\>\> n 为无符号右移,左边会补上 0。 -<< n 为算术左移,相当于乘以 2n。 +位与运算技巧: -n&(n-1) 该位运算是去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110 **100** ,减去 1 得到 10110**011**,这两个数相与得到 10110**000**。 +- n&(n-1) 去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110 **100** ,减去 1 得到 10110**011**,这两个数相与得到 10110**000**。 +- n-n&(\~n+1) 去除 n 的位级表示中最高的那一位。 +- n&(-n) 得到 n 的位级表示中最低的那一位。-n 得到 n 的反码加 1,对于二进制表示 10110 **100** ,-n 得到 01001**100**,相与得到 00000**100** -n-n&(\~n+1) 运算是去除 n 的位级表示中最高的那一位。 +移位运算: -n&(-n) 该运算得到 n 的位级表示中最低的那一位。-n 得到 n 的反码加 1,对于二进制表示 10110 **100** ,-n 得到 01001**100**,相与得到 00000**100** +- \>\> n 为算术右移,相当于除以 2n; +- \>\>\> n 为无符号右移,左边会补上 0。 +- << n 为算术左移,相当于乘以 2n。 **2. mask 计算** @@ -5722,57 +6432,7 @@ n&(-n) 该运算得到 n 的位级表示中最低的那一位。-n 得到 n 的 要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 \~(1<<(i+1)-1)。 -**3. 位操作举例** - -① 获取第 i 位 - -num & 00010000 != 0 - -```java -(num & (1 << i)) != 0; -``` - -② 将第 i 位设置为 1 - -num | 00010000 - -```java -num | (1 << i); -``` - -③ 将第 i 位清除为 0 - -num & 11101111 - -```java -num & (~(1 << i)) -``` - -④ 将最高位到第 i 位清除为 0 - -num & 00001111 - -```java -num & ((1 << i) - 1); -``` - -⑤ 将第 0 位到第 i 位清除为 0 - -num & 11110000 - -```java -num & (~((1 << (i+1)) - 1)); -``` - -⑥ 将第 i 位设置为 0 或者 1 - -先将第 i 位清零,然后将 v 左移 i 位,执行“位或”运算。 - -```java -(num & (1 << i)) | (v << i); -``` - -**4. Java 中的位操作** +**3. Java 中的位操作** ```html static int Integer.bitCount(); // 统计 1 的数量 @@ -5782,7 +6442,20 @@ static String toBinaryString(int i); // 转换为二进制表示的字符串 **统计两个数的二进制表示有多少位不同** -[Leetcode : 461. Hamming Distance (Easy)](https://leetcode.com/problems/hamming-distance/) +[461. Hamming Distance (Easy)](https://leetcode.com/problems/hamming-distance/) + +```html +Input: x = 1, y = 4 + +Output: 2 + +Explanation: +1 (0 0 0 1) +4 (0 1 0 0) + ↑ ↑ + +The above arrows point to positions where the corresponding bits are different. +``` 对两个数进行异或操作,位级表示不同的那一位为 1,统计有多少个 1 即可。 @@ -5798,6 +6471,20 @@ public int hammingDistance(int x, int y) { } ``` +使用 z&(z-1) 去除 z 位级表示最低的那一位。 + +```java +public int hammingDistance(int x, int y) { + int z = x ^ y; + int cnt = 0; + while (z != 0) { + z &= (z - 1); + cnt++; + } + return cnt; +} +``` + 可以使用 Integer.bitcount() 来统计 1 个的个数。 ```java @@ -5806,9 +6493,28 @@ public int hammingDistance(int x, int y) { } ``` +**数组中唯一一个不重复的元素** + +[136. Single Number (Easy)](https://leetcode.com/problems/single-number/description/) + +```html +Input: [4,1,2,1,2] +Output: 4 +``` + +两个相同的数异或的结果为 0,对所有数进行异或操作,最后的结果就是单独出现的那个数。 + +```java +public int singleNumber(int[] nums) { + int ret = 0; + for (int n : nums) ret = ret ^ n; + return ret; +} +``` + **找出数组中缺失的那个数** -[Leetcode : 268. Missing Number (Easy)](https://leetcode.com/problems/missing-number/description/) +[268. Missing Number (Easy)](https://leetcode.com/problems/missing-number/description/) ```html Input: [3,0,1] @@ -5816,13 +6522,37 @@ Output: 2 ``` 题目描述:数组元素在 0-n 之间,但是有一个数是缺失的,要求找到这个缺失的数。 - ` ```java public int missingNumber(int[] nums) { int ret = 0; - for (int i = 0; i <= nums.length; i++) { - ret = i == nums.length ? ret ^ i : ret ^ i ^ nums[i]; + for (int i = 0; i < nums.length; i++) { + ret = ret ^ i ^ nums[i]; + } + return ret ^ nums.length; +} +``` + +**数组中不重复的两个元素** + +[260. Single Number III (Medium)](https://leetcode.com/problems/single-number-iii/description/) + +两个不相等的元素在位级表示上必定会有一位存在不同。 + +将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。 + +diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 + +```java +public int[] singleNumber(int[] nums) { + int diff = 0; + for (int num : nums) diff ^= num; + // 得到最右一位 + diff &= -diff; + int[] ret = new int[2]; + for (int num : nums) { + if ((num & diff) == 0) ret[0] ^= num; + else ret[1] ^= num; } return ret; } @@ -5830,7 +6560,7 @@ public int missingNumber(int[] nums) { **翻转一个数的比特位** -[Leetcode : 190. Reverse Bits (Easy)](https://leetcode.com/problems/reverse-bits/description/) +[190. Reverse Bits (Easy)](https://leetcode.com/problems/reverse-bits/description/) ```java public int reverseBits(int n) { @@ -5885,11 +6615,9 @@ b = a ^ b; a = a ^ b; ``` -令 c = a ^ b,那么 b ^ c = b ^ b ^ a = a,a ^ c = a ^ a ^ b = b。 - **判断一个数是不是 2 的 n 次方** -[Leetcode : 231. Power of Two (Easy)](https://leetcode.com/problems/power-of-two/description/) +[231. Power of Two (Easy)](https://leetcode.com/problems/power-of-two/description/) 二进制表示只有一个 1 存在。 @@ -5909,26 +6637,9 @@ public boolean isPowerOfTwo(int n) { **判断一个数是不是 4 的 n 次方** -[Leetcode : 342. Power of Four (Easy)](https://leetcode.com/problems/power-of-four/) +[342. Power of Four (Easy)](https://leetcode.com/problems/power-of-four/) -该数二进制表示有且只有一个奇数位为 1 ,其余的都为 0 ,例如 16 :10000。可以每次把 1 向左移动 2 位,就能构造出这种数字,然后比较构造出来的数与要判断的数是否相同。 - -```java -public boolean isPowerOfFour(int num) { - int i = 1; - while(i > 0){ - if(i == num) return true; - i = i << 2; - } - return false; -} -``` - -```java -public boolean isPowerOfFour(int num) { - return Integer.toString(num, 4).matches("10*"); -} -``` +这种数在二进制表示中有且只有一个奇数位为 1,例如 16(10000)。 ```java public boolean isPowerOfFour(int num) { @@ -5936,52 +6647,32 @@ public boolean isPowerOfFour(int num) { } ``` -**数组中唯一一个不重复的元素** - -[Leetcode : 136. Single Number (Easy)](https://leetcode.com/problems/single-number/description/) - -两个相同的数异或的结果为 0,对所有数进行异或操作,最后的结果就是单独出现的那个数。 - -类似的有:[Leetcode : 389. Find the Difference (Easy)](https://leetcode.com/problems/find-the-difference/description/),两个字符串仅有一个字符不相同,使用异或操作可以以 O(1) 的空间复杂度来求解,而不需要使用 HashSet。 +也可以使用正则表达式进行匹配。 ```java -public int singleNumber(int[] nums) { - int ret = 0; - for(int n : nums) ret = ret ^ n; - return ret; +public boolean isPowerOfFour(int num) { + return Integer.toString(num, 4).matches("10*"); } ``` -**数组中不重复的两个元素** - -[Leetcode : 260. Single Number III (Medium)](https://leetcode.com/problems/single-number-iii/description/) - -两个不相等的元素在位级表示上必定会有一位存在不同。 - -将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。 - -diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 - -```java -public int[] singleNumber(int[] nums) { - int diff = 0; - for(int num : nums) diff ^= num; - // 得到最右一位 - diff &= -diff; - int[] ret = new int[2]; - for(int num : nums) { - if((num & diff) == 0) ret[0] ^= num; - else ret[1] ^= num; - } - return ret; -} -``` **判断一个数的位级表示是否不会出现连续的 0 和 1** -[Leetcode : 693. Binary Number with Alternating Bits (Easy)](https://leetcode.com/problems/binary-number-with-alternating-bits/description/) +[693. Binary Number with Alternating Bits (Easy)](https://leetcode.com/problems/binary-number-with-alternating-bits/description/) -对于 10101 这种位级表示的数,把它向右移动 1 位得到 1010 ,这两个数每个位都不同,因此异或得到的结果为 11111。 +```html +Input: 10 +Output: True +Explanation: +The binary representation of 10 is: 1010. + +Input: 11 +Output: False +Explanation: +The binary representation of 11 is: 1011. +``` + +对于 1010 这种位级表示的数,把它向右移动 1 位得到 101,这两个数每个位都不同,因此异或得到的结果为 1111。 ```java public boolean hasAlternatingBits(int n) { @@ -5992,7 +6683,7 @@ public boolean hasAlternatingBits(int n) { **求一个数的补码** -[Leetcode : 476. Number Complement (Easy)](https://leetcode.com/problems/number-complement/description/) +[476. Number Complement (Easy)](https://leetcode.com/problems/number-complement/description/) ```html Input: 5 @@ -6000,15 +6691,15 @@ Output: 2 Explanation: The binary representation of 5 is 101 (no leading zero bits), and its complement is 010. So you need to output 2. ``` -不考虑二进制表示中的首 0 部分。 +题目描述:不考虑二进制表示中的首 0 部分。 对于 00000101,要求补码可以将它与 00000111 进行异或操作。那么问题就转换为求掩码 00000111。 ```java public int findComplement(int num) { - if(num == 0) return 1; + if (num == 0) return 1; int mask = 1 << 30; - while((num & mask) == 0) mask >>= 1; + while ((num & mask) == 0) mask >>= 1; mask = (mask << 1) - 1; return num ^ mask; } @@ -6018,7 +6709,7 @@ public int findComplement(int num) { ```java public int findComplement(int num) { - if(num == 0) return 1; + if (num == 0) return 1; int mask = Integer.highestOneBit(num); mask = (mask << 1) - 1; return num ^ mask; @@ -6047,9 +6738,11 @@ public int findComplement(int num) { **实现整数的加法** -[Leetcode : 371. Sum of Two Integers (Easy)](https://leetcode.com/problems/sum-of-two-integers/description/) +[371. Sum of Two Integers (Easy)](https://leetcode.com/problems/sum-of-two-integers/description/) -a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 +a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。 + +递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 ```java public int getSum(int a, int b) { @@ -6059,7 +6752,7 @@ public int getSum(int a, int b) { **字符串数组最大乘积** -[Leetcode : 318. Maximum Product of Word Lengths (Medium)](https://leetcode.com/problems/maximum-product-of-word-lengths/description/) +[318. Maximum Product of Word Lengths (Medium)](https://leetcode.com/problems/maximum-product-of-word-lengths/description/) ```html Given ["abcw", "baz", "foo", "bar", "xtfn", "abcdef"] @@ -6069,12 +6762,11 @@ The two words can be "abcw", "xtfn". 题目描述:字符串数组的字符串只含有小写字符。求解字符串数组中两个字符串长度的最大乘积,要求这两个字符串不能含有相同字符。 -解题思路:本题主要问题是判断两个字符串是否含相同字符,由于字符串只含有小写字符,总共 26 位,因此可以用一个 32 位的整数来存储每个字符是否出现过。 +本题主要问题是判断两个字符串是否含相同字符,由于字符串只含有小写字符,总共 26 位,因此可以用一个 32 位的整数来存储每个字符是否出现过。 ```java public int maxProduct(String[] words) { int n = words.length; - if (n == 0) return 0; int[] val = new int[n]; for (int i = 0; i < n; i++) { for (char c : words[i].toCharArray()) { @@ -6095,9 +6787,9 @@ public int maxProduct(String[] words) { **统计从 0 \~ n 每个数的二进制表示中 1 的个数** -[Leetcode : 338. Counting Bits (Medium)](https://leetcode.com/problems/counting-bits/description/) +[338. Counting Bits (Medium)](https://leetcode.com/problems/counting-bits/description/) -对于数字 6(110),它可以看成是数字 (10) 前面加上一个 1 ,因此 dp[i] = dp[i&(i-1)] + 1; +对于数字 6(110),它可以看成是 4(100) 再加一个 2(10),因此 dp[i] = dp[i&(i-1)] + 1; ```java public int[] countBits(int num) { diff --git a/notes/MySQL.md b/notes/MySQL.md index 56335138..4d38566f 100644 --- a/notes/MySQL.md +++ b/notes/MySQL.md @@ -36,11 +36,11 @@ InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支 内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够自动在内存中创建哈希索引以加速读操作的自适应哈希索引、能够加速插入操作的插入缓冲区等。 -通过一些机制和工具支持真正的热备份,其它存储引擎不支持热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 +通过一些机制和工具支持真正的热备份。其它存储引擎不支持热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 ## MyISAM -提供了大量的特性,包括全文索引、压缩表、空间数据索引等。应该注意的是,MySQL 5.6.4 添加了对 InnoDB 引擎的全文索引支持。 +MyISAM 提供了大量的特性,包括全文索引、压缩表、空间数据索引等。应该注意的是,MySQL 5.6.4 也添加了对 InnoDB 存储引擎的全文索引支持。 不支持事务。 @@ -140,7 +140,12 @@ B+Tree 索引是大多数 MySQL 存储引擎的默认索引类型。 InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。 -限制:哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响并不明显;无法用于分组与排序;只支持精确查找,无法用于部分查找和范围查找;如果哈希冲突很多,查找速度会变得很慢。 +限制: + +- 哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响并不明显; +- 无法用于分组与排序; +- 只支持精确查找,无法用于部分查找和范围查找; +- 如果哈希冲突很多,查找速度会变得很慢。 ### 3. 空间数据索引(R-Tree) @@ -162,7 +167,7 @@ MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而 - 帮助服务器避免进行排序和创建临时表(B+Tree 索引是有序的,可以用来做 ORDER BY 和 GROUP BY 操作); -- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相关的列值都存储在一起)。 +- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的列值都存储在一起)。 ## 索引优化 diff --git a/notes/Redis.md b/notes/Redis.md index d33d6f04..b92fc7ae 100644 --- a/notes/Redis.md +++ b/notes/Redis.md @@ -320,7 +320,7 @@ Redis Cluster。 ### 2. 时间事件 -又分为两类:定时事件是让一段程序在指定的时间之内执行一次;周期性时间是让一段程序每隔指定时间就执行一次。 +又分为两类:定时事件是让一段程序在指定的时间之内执行一次;周期性事件是让一段程序每隔指定时间就执行一次。 ## 事件的调度与执行 @@ -466,7 +466,7 @@ Redis 没有关系型数据库中的表这一概念来将同类型的数据存 # 参考资料 - Carlson J L. Redis in Action[J]. Media.johnwiley.com.au, 2013. -- 黄健宏. Redis 设计与实现 [M]. 机械工业出版社, 2014. +- [黄健宏. Redis 设计与实现 [M]. 机械工业出版社, 2014.](http://redisbook.com/index.html) - [REDIS IN ACTION](https://redislabs.com/ebook/foreword/) - [论述 Redis 和 Memcached 的差异](http://www.cnblogs.com/loveincode/p/7411911.html) - [Redis 3.0 中文版- 分片](http://wiki.jikexueyuan.com/project/redis-guide) diff --git a/notes/分布式问题分析.md b/notes/分布式问题分析.md index b9064a50..60ed87cd 100644 --- a/notes/分布式问题分析.md +++ b/notes/分布式问题分析.md @@ -95,7 +95,7 @@

-该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担多大的负载(下图的 Server 2)。 +该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担过大的负载(下图的 Server 2)。

@@ -107,7 +107,7 @@ ### 3. 最少连接(least Connections) -由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数多大,而另一台服务器的连接多小,造成负载不均衡。例如下图中,(1, 3, 5) 请求会被发送到服务器 1,但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1;(2, 4, 6) 请求被发送到服务器 2,只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担多大的负载。 +由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。例如下图中,(1, 3, 5) 请求会被发送到服务器 1,但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1;(2, 4, 6) 请求被发送到服务器 2,只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担过大的负载。

@@ -257,7 +257,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示 **(六)羊群效应** -在步骤二,一个节点未获得锁,需要监听监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。 +在步骤二,一个节点未获得锁,需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。 # 五、分布式 Session diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md index 4f6bd9ad..5e2b2553 100644 --- a/notes/剑指 offer 题解.md +++ b/notes/剑指 offer 题解.md @@ -82,15 +82,17 @@ # 2. 实现 Singleton -> [单例模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md) +[单例模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md) # 3. 数组中重复的数字 +[NowCoder](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 -在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为 7 的数组 {2, 3, 1, 0, 2, 5, 3},那么对应的输出是第一个重复的数字 2。 +在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为 7 的数组 {2, 3, 1, 0, 2, 5},那么对应的输出是第一个重复的数字 2。 -要求复杂度为 O(N) + O(1),时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。 +要求复杂度为 O(N) + O(1),也就是时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。牛客网讨论区这一题的首票答案使用 nums[i] + length 来将元素标记,这么做会有加法溢出问题。 ## 解题思路 @@ -111,19 +113,18 @@ position-4 : (0,1,2,3,2,5) // nums[i] == nums[nums[i]], exit 遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复。 -复杂度:O(N) + O(1) - ```java public boolean duplicate(int[] nums, int length, int[] duplication) { - if (nums == null || length <= 0) return false; + if (nums == null || length <= 0) + return false; for (int i = 0; i < length; i++) { - while (nums[i] != i && nums[i] != nums[nums[i]]) { + while (nums[i] != i) { + if (nums[i] == nums[nums[i]]) { + duplication[0] = nums[i]; + return true; + } swap(nums, i, nums[i]); } - if (nums[i] != i && nums[i] == nums[nums[i]]) { - duplication[0] = nums[i]; - return true; - } } return false; } @@ -135,6 +136,8 @@ private void swap(int[] nums, int i, int j) { # 4. 二维数组中的查找 +[NowCoder](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 @@ -157,17 +160,21 @@ Given target = 20, return false. 从右上角开始查找。因为矩阵中的一个数,它左边的数都比它小,下边的数都比它大。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间。 -复杂度:O(M+N) + O(1) +复杂度:O(M + N) + O(1) ```java public boolean Find(int target, int[][] matrix) { - if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return false; - int m = matrix.length, n = matrix[0].length; - int r = 0, c = n - 1; // 从右上角开始 - while (r <= m - 1 && c >= 0) { - if (target == matrix[r][c]) return true; - else if (target > matrix[r][c]) r++; - else c--; + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) + return false; + int rows = matrix.length, cols = matrix[0].length; + int r = 0, c = cols - 1; // 从右上角开始 + while (r <= rows - 1 && c >= 0) { + if (target == matrix[r][c]) + return true; + else if (target > matrix[r][c]) + r++; + else + c--; } return false; } @@ -175,6 +182,8 @@ public boolean Find(int target, int[][] matrix) { # 5. 替换空格 +[NowCoder](https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为 We Are Happy. 则经过替换之后的字符串为 We%20Are%20Happy。 @@ -183,30 +192,28 @@ public boolean Find(int target, int[][] matrix) { 在字符串尾部填充任意字符,使得字符串的长度等于字符串替换之后的长度。因为一个空格要替换成三个字符(%20),因此当遍历到一个空格时,需要在尾部填充两个任意字符。 -令 P1 指向字符串原来的末尾位置,P2 指向字符串现在的末尾位置。P1 和 P2 从后向前遍历,当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。 +令 idxOfOld 指向字符串原来的末尾位置,idxOfNew 指向字符串现在的末尾位置。idxOfOld 和 idxOfNew 从后向前遍历,当 idxOfOld 遍历到一个空格时,就需要令 idxOfNew 指向的位置依次填充 02%(注意是逆序的),否则就填充上 idxOfOld 指向字符的值。 -从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。 +从后向前遍是为了在改变 idxOfNew 所指向的内容时,不会影响到 idxOfOld 遍历原来字符串的内容。 复杂度:O(N) + O(1) ```java public String replaceSpace(StringBuffer str) { int oldLen = str.length(); - for (int i = 0; i < oldLen; i++) { - if (str.charAt(i) == ' ') { + for (int i = 0; i < oldLen; i++) + if (str.charAt(i) == ' ') str.append(" "); - } - } - int idxOfOld = oldLen - 1; - int idxOfNew = str.length() - 1; - while (idxOfOld >= 0 && idxOfNew > idxOfOld) { - char c = str.charAt(idxOfOld--); + + int P1 = oldLen - 1, P2 = str.length() - 1; + while (P1 >= 0 && P2 > P1) { + char c = str.charAt(P1--); if (c == ' ') { - str.setCharAt(idxOfNew--, '0'); - str.setCharAt(idxOfNew--, '2'); - str.setCharAt(idxOfNew--, '%'); + str.setCharAt(P2--, '0'); + str.setCharAt(P2--, '2'); + str.setCharAt(P2--, '%'); } else { - str.setCharAt(idxOfNew--, c); + str.setCharAt(P2--, c); } } return str.toString(); @@ -215,6 +222,8 @@ public String replaceSpace(StringBuffer str) { # 6. 从尾到头打印链表 +[NowCoder](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入链表的第一个节点,从尾到头反过来打印出每个结点的值。 @@ -233,9 +242,8 @@ public ArrayList printListFromTailToHead(ListNode listNode) { listNode = listNode.next; } ArrayList ret = new ArrayList<>(); - while (!stack.isEmpty()) { + while (!stack.isEmpty()) ret.add(stack.pop()); - } return ret; } ``` @@ -245,7 +253,7 @@ public ArrayList printListFromTailToHead(ListNode listNode) { ```java public ArrayList printListFromTailToHead(ListNode listNode) { ArrayList ret = new ArrayList<>(); - if(listNode != null) { + if (listNode != null) { ret.addAll(printListFromTailToHead(listNode.next)); ret.add(listNode.val); } @@ -296,6 +304,8 @@ public ArrayList printListFromTailToHead(ListNode listNode) { # 7. 重建二叉树 +[NowCoder](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 @@ -312,21 +322,20 @@ inorder = [9,3,15,20,7] 前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为树的左子树中序遍历结果,右部分为树的右子树中序遍历的结果。 ```java -private Map inOrderNumsIdx = new HashMap<>(); // 缓存中序遍历数组的每个值对应的索引 +private Map inOrderNumsIndexs = new HashMap<>(); // 缓存中序遍历数组的每个值对应的索引 public TreeNode reConstructBinaryTree(int[] pre, int[] in) { - for (int i = 0; i < in.length; i++) { - inOrderNumsIdx.put(in[i], i); - } + for (int i = 0; i < in.length; i++) + inOrderNumsIndexs.put(in[i], i); return reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1); } private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in, int inL, int inR) { - if (preL == preR) return new TreeNode(pre[preL]); - if (preL > preR || inL > inR) return null; + if (preL > preR) + return null; TreeNode root = new TreeNode(pre[preL]); - int inIdx = inOrderNumsIdx.get(root.val); - int leftTreeSize = inIdx - inL; + int inIndex = inOrderNumsIndexs.get(root.val); + int leftTreeSize = inIndex - inL; root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeSize, in, inL, inL + leftTreeSize - 1); root.right = reConstructBinaryTree(pre, preL + leftTreeSize + 1, preR, in, inL + leftTreeSize + 1, inR); return root; @@ -335,6 +344,8 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in, # 8. 二叉树的下一个结点 +[NowCoder](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 @@ -366,12 +377,14 @@ public class TreeLinkNode { public TreeLinkNode GetNext(TreeLinkNode pNode) { if (pNode.right != null) { TreeLinkNode node = pNode.right; - while (node.left != null) node = node.left; + while (node.left != null) + node = node.left; return node; } else { while (pNode.next != null) { TreeLinkNode parent = pNode.next; - if (parent.left == pNode) return parent; + if (parent.left == pNode) + return parent; pNode = pNode.next; } } @@ -381,9 +394,15 @@ public TreeLinkNode GetNext(TreeLinkNode pNode) { # 9. 用两个栈实现队列 +[NowCoder](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。队列中的元素为 int 类型。 + ## 解题思路 -in 栈用来处理入栈(push)操作,out 栈用来处理出栈(pop)操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,此时先进入的元素先退出,这就是队列的顺序。 +in 栈用来处理入栈(push)操作,out 栈用来处理出栈(pop)操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,先进入的元素先退出,这就是队列的顺序。

@@ -396,23 +415,24 @@ public void push(int node) { } public int pop() throws Exception { - if (out.isEmpty()) { - while (!in.isEmpty()) { + if (out.isEmpty()) + while (!in.isEmpty()) out.push(in.pop()); - } - } - if (out.isEmpty()) { + + if (out.isEmpty()) throw new Exception("queue is empty"); - } + return out.pop(); } ``` # 10.1 斐波那契数列 +[NowCoder](https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 -求菲波那契数列的第 n 项。 +求菲波那契数列的第 n 项,n <= 39。

@@ -420,18 +440,18 @@ public int pop() throws Exception { 如果使用递归求解,会重复计算一些子问题。例如,计算 f(10) 需要计算 f(9) 和 f(8),计算 f(9) 需要计算 f(8) 和 f(7),可以看到 f(8) 被重复计算了。 -

+

递归方法是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,避免重复求解子问题。 ```java public int Fibonacci(int n) { - if(n <= 1) return n; + if (n <= 1) + return n; int[] fib = new int[n + 1]; fib[1] = 1; - for (int i = 2; i <= n; i++) { + for (int i = 2; i <= n; i++) fib[i] = fib[i - 1] + fib[i - 2]; - } return fib[n]; } ``` @@ -440,7 +460,8 @@ public int Fibonacci(int n) { ```java public int Fibonacci(int n) { - if(n <= 1) return n; + if (n <= 1) + return n; int pre2 = 0, pre1 = 1; int fib = 0; for (int i = 2; i <= n; i++) { @@ -457,13 +478,14 @@ public int Fibonacci(int n) { ```java public class Solution { private int[] fib = new int[40]; + public Solution() { fib[1] = 1; fib[2] = 2; - for(int i = 2; i < fib.length; i++) { + for (int i = 2; i < fib.length; i++) fib[i] = fib[i - 1] + fib[i - 2]; - } } + public int Fibonacci(int n) { return fib[n]; } @@ -472,6 +494,8 @@ public class Solution { # 10.2 跳台阶 +[NowCoder](https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=13&tqId=11161&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 @@ -482,13 +506,13 @@ public class Solution { ```java public int JumpFloor(int n) { - if (n == 1) return 1; + if (n == 1) + return 1; int[] dp = new int[n]; dp[0] = 1; dp[1] = 2; - for (int i = 2; i < n; i++) { + for (int i = 2; i < n; i++) dp[i] = dp[i - 1] + dp[i - 2]; - } return dp[n - 1]; } ``` @@ -497,7 +521,8 @@ public int JumpFloor(int n) { ```java public int JumpFloor(int n) { - if (n <= 1) return n; + if (n <= 1) + return n; int pre2 = 0, pre1 = 1; int result = 0; for (int i = 1; i <= n; i++) { @@ -511,27 +536,29 @@ public int JumpFloor(int n) { # 10.3 变态跳台阶 +[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) + ## 题目描述 -一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级……它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 +一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级... 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 ## 解题思路 ```java -public int JumpFloorII(int n) { - int[] dp = new int[n]; +public int JumpFloorII(int target) { + int[] dp = new int[target]; Arrays.fill(dp, 1); - for(int i = 1; i < n; i++) { - for(int j = 0; j < i; j++) { + for (int i = 1; i < target; i++) + for (int j = 0; j < i; j++) dp[i] += dp[j]; - } - } - return dp[n - 1]; + return dp[target - 1]; } ``` # 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 的大矩形,总共有多少种方法? @@ -542,13 +569,13 @@ public int JumpFloorII(int n) { ```java public int RectCover(int n) { - if (n <= 2) return n; + if (n <= 2) + return n; int[] dp = new int[n]; dp[0] = 1; dp[1] = 2; - for (int i = 2; i < n; i++) { + for (int i = 2; i < n; i++) dp[i] = dp[i - 1] + dp[i - 2]; - } return dp[n - 1]; } ``` @@ -557,7 +584,8 @@ public int RectCover(int n) { ```java public int RectCover(int n) { - if (n <= 2) return n; + if (n <= 2) + return n; int pre2 = 1, pre1 = 2; int result = 0; for (int i = 3; i <= n; i++) { @@ -571,42 +599,31 @@ public int RectCover(int n) { # 11. 旋转数组的最小数字 +[NowCoder](https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba?tpId=13&tqId=11159&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1。NOTE:给出的所有元素都大于 0,若数组大小为 0,请返回 0。 ## 解题思路 -### 分治 +当 nums[m] <= nums[h] 的情况下,说明解在 [l, m] 之间,此时令 h = m;否则解在 [m + 1, h] 之间,令 l = m + 1。 -复杂度:O(logN) + O(1),其实空间复杂度不止 O(1),因为分治使用了递归栈,用到了额外的空间,如果对空间有要求就不能用这种方法。 - -```java -public int minNumberInRotateArray(int[] nums) { - return minNumberInRotateArray(nums, 0, nums.length - 1); -} - -private int minNumberInRotateArray(int[] nums, int first, int last) { - if (nums[first] < nums[last]) return nums[first]; - if (first == last) return nums[first]; - int mid = first + (last - first) / 2; - return Math.min(minNumberInRotateArray(nums, first, mid), minNumberInRotateArray(nums, mid + 1, last)); -} -``` - -### 二分查找 +因为 h 的赋值表达式为 h = m,因此循环体的循环条件应该为 l < h,详细解释请见 [Leetcode 题解](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3.md#%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE)。 复杂度:O(logN) + O(1) ```java public int minNumberInRotateArray(int[] nums) { - if (nums.length == 0) return 0; + if (nums.length == 0) + return 0; int l = 0, h = nums.length - 1; - while (nums[l] >= nums[h]) { - if (h - l == 1) return nums[h]; - int mid = l + (h - l) / 2; - if (nums[mid] >= nums[l]) l = mid; - else h = mid; + while (l < h) { + int m = l + (h - l) / 2; + if (nums[m] <= nums[h]) + h = m; + else + l = m + 1; } return nums[l]; } @@ -614,6 +631,8 @@ public int minNumberInRotateArray(int[] nums) { # 12. 矩阵中的路径 +[NowCoder](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 @@ -625,51 +644,50 @@ public int minNumberInRotateArray(int[] nums) { ## 解题思路 ```java -private int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; +private final static int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; private int rows; private int cols; public boolean hasPath(char[] array, int rows, int cols, char[] str) { - if (rows == 0 || cols == 0) return false; + if (rows == 0 || cols == 0) + return false; this.rows = rows; this.cols = cols; - boolean[][] hasUsed = new boolean[rows][cols]; + boolean[][] marked = new boolean[rows][cols]; char[][] matrix = buildMatrix(array); - for (int i = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { - if (backtracking(matrix, str, hasUsed, 0, i, j)) return true; - } - } + for (int i = 0; i < rows; i++) + for (int j = 0; j < cols; j++) + if (backtracking(matrix, str, marked, 0, i, j)) + return true; return false; } -private boolean backtracking(char[][] matrix, char[] str, boolean[][] hasUsed, int pathLen, int row, int col) { - if (pathLen == str.length) return true; - if (row < 0 || row >= rows || col < 0 || col >= cols) return false; - if (matrix[row][col] != str[pathLen]) return false; - if (hasUsed[row][col]) return false; - hasUsed[row][col] = true; - for (int i = 0; i < next.length; i++) { - if (backtracking(matrix, str, hasUsed, pathLen + 1, row + next[i][0], col + next[i][1])) +private boolean backtracking(char[][] matrix, char[] str, boolean[][] marked, int pathLen, int r, int c) { + if (pathLen == str.length) + return true; + if (r < 0 || r >= rows || c < 0 || c >= cols || matrix[r][c] != str[pathLen] || marked[r][c]) + return false; + marked[r][c] = true; + for (int[] n : next) + if (backtracking(matrix, str, marked, pathLen + 1, r + n[0], c + n[1])) return true; - } - hasUsed[row][col] = false; + marked[r][c] = false; return false; } private char[][] buildMatrix(char[] array) { char[][] matrix = new char[rows][cols]; - for (int i = 0, idx = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { + for (int i = 0, idx = 0; i < rows; i++) + for (int j = 0; j < cols; j++) matrix[i][j] = array[idx++]; - } - } return matrix; } ``` # 13. 机器人的运动范围 +[NowCoder](https://www.nowcoder.com/practice/6e5207314b5241fb83f2329e89fdecc8?tpId=13&tqId=11219&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。例如,当 k 为 18 时,机器人能够进入方格(35, 37),因为 3+5+3+7=18。但是,它不能进入方格(35, 38),因为 3+5+3+8=19。请问该机器人能够达到多少个格子? @@ -677,8 +695,8 @@ private char[][] buildMatrix(char[] array) { ## 解题思路 ```java +private static final int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; private int cnt = 0; -private int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; private int rows; private int cols; private int threshold; @@ -689,20 +707,20 @@ public int movingCount(int threshold, int rows, int cols) { this.cols = cols; this.threshold = threshold; initDigitSum(); - boolean[][] hasVisited = new boolean[rows][cols]; - dfs(hasVisited, 0, 0); + boolean[][] marked = new boolean[rows][cols]; + dfs(marked, 0, 0); return cnt; } -private void dfs(boolean[][] hasVisited, int r, int c) { - if (r < 0 || r >= this.rows || c < 0 || c >= this.cols) return; - if (hasVisited[r][c]) return; - hasVisited[r][c] = true; - if (this.digitSum[r][c] > this.threshold) return; - this.cnt++; - for (int i = 0; i < this.next.length; i++) { - dfs(hasVisited, r + next[i][0], c + next[i][1]); - } +private void dfs(boolean[][] marked, int r, int c) { + if (r < 0 || r >= rows || c < 0 || c >= cols || marked[r][c]) + return; + marked[r][c] = true; + if (this.digitSum[r][c] > this.threshold) + return; + cnt++; + for (int[] n : next) + dfs(marked, r + n[0], c + n[1]); } private void initDigitSum() { @@ -714,34 +732,34 @@ private void initDigitSum() { n /= 10; } } - this.digitSum = new int[rows][cols]; - for (int i = 0; i < this.rows; i++) { - for (int j = 0; j < this.cols; j++) { - this.digitSum[i][j] = digitSumOne[i] + digitSumOne[j]; - } - } + digitSum = new int[rows][cols]; + for (int i = 0; i < this.rows; i++) + for (int j = 0; j < this.cols; j++) + digitSum[i][j] = digitSumOne[i] + digitSumOne[j]; } ``` # 14. 剪绳子 +[Leetcode](https://leetcode.com/problems/integer-break/description/) + ## 题目描述 把一根绳子剪成多段,并且使得每段的长度乘积最大。 +For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4). + ## 解题思路 ### 动态规划解法 ```java -public int maxProductAfterCutting(int n) { +public int integerBreak(int n) { int[] dp = new int[n + 1]; dp[1] = 1; - for (int i = 2; i <= n; i++) { - for (int j = 1; j < i; j++) { + for (int i = 2; i <= n; i++) + for (int j = 1; j < i; j++) dp[i] = Math.max(dp[i], Math.max(j * (i - j), dp[j] * (i - j))); - } - } return dp[n]; } ``` @@ -753,12 +771,16 @@ public int maxProductAfterCutting(int n) { 证明:当 n >= 5 时,3(n - 3) - 2(n - 2) = n - 5 >= 0。因此把长度大于 5 的绳子切成两段,令其中一段长度为 3 可以使得两段的乘积最大。 ```java -public int maxProductAfterCutting(int n) { - if (n < 2) return 0; - if (n == 2) return 1; - if (n == 3) return 2; +public int integerBreak(int n) { + if (n < 2) + return 0; + if (n == 2) + return 1; + if (n == 3) + return 2; int timesOf3 = n / 3; - if (n - timesOf3 * 3 == 1) timesOf3--; + if (n - timesOf3 * 3 == 1) + timesOf3--; int timesOf2 = (n - timesOf3 * 3) / 2; return (int) (Math.pow(3, timesOf3)) * (int) (Math.pow(2, timesOf2)); } @@ -766,6 +788,8 @@ public int maxProductAfterCutting(int n) { # 15. 二进制中 1 的个数 +[NowCoder](https://www.nowcoder.com/practice/8ee967e43c2c4ec193b040ea7fbb10b8?tpId=13&tqId=11164&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入一个整数,输出该数二进制表示中 1 的个数。 @@ -803,6 +827,8 @@ public int NumberOf1(int 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 次方。 @@ -817,16 +843,19 @@ public int NumberOf1(int n) { ```java public double Power(double base, int exponent) { - if (exponent == 0) return 1; - if (exponent == 1) return base; + if (exponent == 0) + return 1; + if (exponent == 1) + return base; boolean isNegative = false; if (exponent < 0) { exponent = -exponent; isNegative = true; } double pow = Power(base * base, exponent / 2); - if (exponent % 2 != 0) pow = pow * base; - return isNegative ? (1 / pow) : pow; + if (exponent % 2 != 0) + pow = pow * base; + return isNegative ? 1 / pow : pow; } ``` @@ -844,7 +873,8 @@ public double Power(double base, int exponent) { ```java public void print1ToMaxOfNDigits(int n) { - if (n < 0) return; + if (n <= 0) + return; char[] number = new char[n]; print1ToMaxOfNDigits(number, -1); } @@ -862,8 +892,10 @@ private void print1ToMaxOfNDigits(char[] number, int digit) { private void printNumber(char[] number) { int index = 0; - while (index < number.length && number[index] == '0') index++; - while (index < number.length) System.out.print(number[index++]); + while (index < number.length && number[index] == '0') + index++; + while (index < number.length) + System.out.print(number[index++]); System.out.println(); } ``` @@ -872,7 +904,7 @@ private void printNumber(char[] number) { ## 解题思路 -① 如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,令该节点指向下下个节点,然后删除下一个节点,时间复杂度为 O(1)。 +① 如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,然后令该节点指向下下个节点,再删除下一个节点,时间复杂度为 O(1)。

@@ -884,7 +916,8 @@ private void printNumber(char[] number) { ```java public ListNode deleteNode(ListNode head, ListNode tobeDelete) { - if (head == null || head.next == null || tobeDelete == null) return null; + if (head == null || head.next == null || tobeDelete == null) + return null; if (tobeDelete.next != null) { // 要删除的节点不是尾节点 ListNode next = tobeDelete.next; @@ -892,7 +925,8 @@ public ListNode deleteNode(ListNode head, ListNode tobeDelete) { tobeDelete.next = next.next; } else { ListNode cur = head; - while (cur.next != tobeDelete) cur = cur.next; + while (cur.next != tobeDelete) + cur = cur.next; cur.next = null; } return head; @@ -901,6 +935,8 @@ public ListNode deleteNode(ListNode head, ListNode tobeDelete) { # 18.2 删除链表中重复的结点 +[NowCoder](https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述

@@ -909,37 +945,42 @@ public ListNode deleteNode(ListNode head, ListNode tobeDelete) { ```java public ListNode deleteDuplication(ListNode pHead) { - if (pHead == null) return null; + if (pHead == null || pHead.next == null) + return pHead; ListNode next = pHead.next; - if (next == null) return pHead; if (pHead.val == next.val) { - while (next != null && pHead.val == next.val) next = next.next; + while (next != null && pHead.val == next.val) + next = next.next; return deleteDuplication(next); + } else { + pHead.next = deleteDuplication(pHead.next); + return pHead; } - pHead.next = deleteDuplication(pHead.next); - return pHead; } ``` # 19. 正则表达式匹配 +[NowCoder](https://www.nowcoder.com/practice/45327ae22b7b413ea21df13ee7d6429c?tpId=13&tqId=11205&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 -请实现一个函数用来匹配包括 '.' 和 '\*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而 '\*' 表示它前面的字符可以出现任意次(包含 0 次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 "aaa" 与模式 "a.a" 和 "ab\*ac\*a" 匹配,但是与 "aa.a" 和 "ab\*a" 均不匹配。 +请实现一个函数用来匹配包括 '.' 和 '\*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而 '\*' 表示它前面的字符可以出现任意次(包含 0 次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 "aaa" 与模式 "a.a" 和 "ab\*ac\*a" 匹配,但是与 "aa.a" 和 "ab\*a" 均不匹配。 ## 解题思路 应该注意到,'.' 是用来当做一个任意字符,而 '\*' 是用来重复前面的字符。这两个的作用不同,不能把 '.' 的作用和 '\*' 进行类比,从而把它当成重复前面字符一次。 ```html -p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1]; -p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1]; -p.charAt(j) == '*' : - p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] //a* only counts as empty - p.charAt(j-1) == s.charAt(i) or p.charAt(i-1) == '.': - dp[i][j] = dp[i-1][j] // a* counts as multiple a - or dp[i][j] = dp[i][j-1] // a* counts as single a - or dp[i][j] = dp[i][j-2] // a* counts as empty +if p.charAt(j) == s.charAt(i) : then dp[i][j] = dp[i-1][j-1]; +if p.charAt(j) == '.' : then dp[i][j] = dp[i-1][j-1]; +if p.charAt(j) == '*' : + if p.charAt(j-1) != s.charAt(i) : then dp[i][j] = dp[i][j-2] // a* only counts as empty + if p.charAt(j-1) == s.charAt(i) + or p.charAt(i-1) == '.' : + then dp[i][j] = dp[i-1][j] // a* counts as multiple a + or dp[i][j] = dp[i][j-1] // a* counts as single a + or dp[i][j] = dp[i][j-2] // a* counts as empty ``` ```java @@ -947,30 +988,27 @@ public boolean match(char[] str, char[] pattern) { int m = str.length, n = pattern.length; boolean[][] dp = new boolean[m + 1][n + 1]; dp[0][0] = true; - for (int i = 1; i <= n; i++) { - if (pattern[i - 1] == '*') { + for (int i = 1; i <= n; i++) + if (pattern[i - 1] == '*') dp[0][i] = dp[0][i - 2]; - } - } - for (int i = 1; i <= m; i++) { - for (int j = 1; j <= n; j++) { - if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') { + + for (int i = 1; i <= m; i++) + for (int j = 1; j <= n; j++) + if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') dp[i][j] = dp[i - 1][j - 1]; - } else if (pattern[j - 1] == '*') { - if (pattern[j - 2] == str[i - 1] || pattern[j - 2] == '.') { + else if (pattern[j - 1] == '*') + if (pattern[j - 2] == str[i - 1] || pattern[j - 2] == '.') dp[i][j] = dp[i][j - 1] || dp[i][j - 2] || dp[i - 1][j]; - } else { + else dp[i][j] = dp[i][j - 2]; - } - } - } - } return dp[m][n]; } ``` # 20. 表示数值的字符串 +[NowCoder](https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串 "+100","5e2","-123","3.1416" 和 "-1E-16" 都表示数值。 但是 "12e","1a3.14","1.2.3","+-5" 和 "12e+4.3" 都不是。 @@ -985,74 +1023,64 @@ public boolean isNumeric(char[] str) { # 21. 调整数组顺序使奇数位于偶数前面 +[NowCoder](https://www.nowcoder.com/practice/beb5aa231adc45b2a5dcc5b62c93f593?tpId=13&tqId=11166&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 保证奇数和奇数,偶数和偶数之间的相对位置不变,这和书本不太一样。 ## 解题思路 -复杂度:O(N2) + O(1) - -```java -public void reOrderArray(int[] nums) { - int n = nums.length; - for (int i = 0; i < n; i++) { - if (nums[i] % 2 == 0) { - int nextOddIdx = i + 1; - while (nextOddIdx < n && nums[nextOddIdx] % 2 == 0) nextOddIdx++; - if (nextOddIdx == n) break; - int nextOddVal = nums[nextOddIdx]; - for (int j = nextOddIdx; j > i; j--) { - nums[j] = nums[j - 1]; - } - nums[i] = nextOddVal; - } - } -} -``` - -复杂度:O(N) + O(N) - ```java public void reOrderArray(int[] nums) { + // 奇数个数 int oddCnt = 0; - for (int val : nums) if (val % 2 == 1) oddCnt++; + for (int val : nums) + if (val % 2 == 1) + oddCnt++; int[] copy = nums.clone(); int i = 0, j = oddCnt; for (int num : copy) { - if (num % 2 == 1) nums[i++] = num; - else nums[j++] = num; + if (num % 2 == 1) + nums[i++] = num; + else + nums[j++] = num; } } ``` # 22. 链表中倒数第 K 个结点 +[NowCoder](https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 解题思路 设链表的长度为 N。设两个指针 P1 和 P2,先让 P1 移动 K 个节点,则还有 N - K 个节点可以移动。此时让 P1 和 P2 同时移动,可以知道当 P1 移动到链表结尾时,P2 移动到 N - K 个节点处,该位置就是倒数第 K 个节点。 -## 解题思路 -

```java public ListNode FindKthToTail(ListNode head, int k) { - if (head == null) return null; - ListNode P1, P2; - P1 = P2 = head; - while (P1 != null && k-- > 0) P1 = P1.next; - if (k > 0) return null; - while (P1 != null) { - P1 = P1.next; - P2 = P2.next; + if (head == null) + return null; + ListNode fast, slow; + fast = slow = head; + while (fast != null && k-- > 0) + fast = fast.next; + if (k > 0) + return null; + while (fast != null) { + fast = fast.next; + slow = slow.next; } - return P2; + return slow; } ``` # 23. 链表中环的入口结点 +[NowCoder](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 解题思路 使用双指针,一个指针 fast 每次移动两个节点,一个指针 slow 每次移动一个节点。因为存在环,所以两个指针必定相遇在环中的某个节点上。此时 fast 移动的节点数为 x+2y+z,slow 为 x+y,由于 fast 速度比 slow 快一倍,因此 x+2y+z=2(x+y),得到 x=z。 @@ -1063,7 +1091,8 @@ public ListNode FindKthToTail(ListNode head, int k) { ```java public ListNode EntryNodeOfLoop(ListNode pHead) { - if (pHead == null) return null; + if (pHead == null) + return null; ListNode slow = pHead, fast = pHead; while (fast != null && fast.next != null) { fast = fast.next.next; @@ -1083,13 +1112,16 @@ public ListNode EntryNodeOfLoop(ListNode pHead) { # 24. 反转链表 +[NowCoder](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 解题思路 ### 递归 ```java public ListNode ReverseList(ListNode head) { - if (head == null || head.next == null) return head; + if (head == null || head.next == null) + return head; ListNode next = head.next; head.next = null; ListNode newHead = ReverseList(next); @@ -1115,6 +1147,8 @@ public ListNode ReverseList(ListNode head) { # 25. 合并两个排序的链表 +[NowCoder](https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述

@@ -1125,8 +1159,10 @@ public ListNode ReverseList(ListNode head) { ```java public ListNode Merge(ListNode list1, ListNode list2) { - if (list1 == null) return list2; - if (list2 == null) return list1; + if (list1 == null) + return list2; + if (list2 == null) + return list1; if (list1.val <= list2.val) { list1.next = Merge(list1.next, list2); return list1; @@ -1153,14 +1189,18 @@ public ListNode Merge(ListNode list1, ListNode list2) { } cur = cur.next; } - if (list1 != null) cur.next = list1; - if (list2 != null) cur.next = list2; + if (list1 != null) + cur.next = list1; + if (list2 != null) + cur.next = list2; return head.next; } ``` # 26. 树的子结构 +[NowCoder](https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&tqId=11170&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述

@@ -1169,21 +1209,26 @@ public ListNode Merge(ListNode list1, ListNode list2) { ```java public boolean HasSubtree(TreeNode root1, TreeNode root2) { - if (root1 == null || root2 == null) return false; + if (root1 == null || root2 == null) + return false; return isSubtree(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2); } private boolean isSubtree(TreeNode root1, TreeNode root2) { - if (root1 == null && root2 == null) return true; - if (root1 == null) return false; - if (root2 == null) return true; - if (root1.val != root2.val) return false; + if (root2 == null) + return true; + if (root1 == null) + return false; + if (root1.val != root2.val) + return false; return isSubtree(root1.left, root2.left) && isSubtree(root1.right, root2.right); } ``` # 27. 二叉树的镜像 +[NowCoder](https://www.nowcoder.com/practice/564f4c26aa584921bc75623e48ca3011?tpId=13&tqId=11171&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述

@@ -1192,7 +1237,8 @@ private boolean isSubtree(TreeNode root1, TreeNode root2) { ```java public void Mirror(TreeNode root) { - if (root == null) return; + if (root == null) + return; swap(root); Mirror(root.left); Mirror(root.right); @@ -1207,6 +1253,8 @@ private void swap(TreeNode root) { # 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) + ## 题目描述

@@ -1215,20 +1263,26 @@ private void swap(TreeNode root) { ```java boolean isSymmetrical(TreeNode pRoot) { - if (pRoot == null) return true; + if (pRoot == null) + return true; return isSymmetrical(pRoot.left, pRoot.right); } boolean isSymmetrical(TreeNode t1, TreeNode t2) { - if (t1 == null && t2 == null) return true; - if (t1 == null || t2 == null) return false; - if (t1.val != t2.val) return false; + if (t1 == null && t2 == null) + return true; + if (t1 == null || t2 == null) + return false; + if (t1.val != t2.val) + return false; return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left); } ``` # 29. 顺时针打印矩阵 +[NowCoder](https://www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a?tpId=13&tqId=11172&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 下图的矩阵顺时针打印结果为:1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10 @@ -1242,10 +1296,16 @@ public ArrayList printMatrix(int[][] matrix) { ArrayList ret = new ArrayList<>(); int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1; while (r1 <= r2 && c1 <= c2) { - for (int i = c1; i <= c2; i++) ret.add(matrix[r1][i]); - for (int i = r1 + 1; i <= r2; i++) ret.add(matrix[i][c2]); - if (r1 != r2) for (int i = c2 - 1; i >= c1; i--) ret.add(matrix[r2][i]); - if (c1 != c2) for (int i = r2 - 1; i > r1; i--) ret.add(matrix[i][c1]); + for (int i = c1; i <= c2; i++) + ret.add(matrix[r1][i]); + for (int i = r1 + 1; i <= r2; i++) + ret.add(matrix[i][c2]); + if (r1 != r2) + for (int i = c2 - 1; i >= c1; i--) + ret.add(matrix[r2][i]); + if (c1 != c2) + for (int i = r2 - 1; i > r1; i--) + ret.add(matrix[i][c1]); r1++; r2--; c1++; c2--; } return ret; @@ -1254,6 +1314,8 @@ public ArrayList printMatrix(int[][] matrix) { # 30. 包含 min 函数的栈 +[NowCoder](https://www.nowcoder.com/practice/4c776177d2c04c2494f2555c9fcc1e49?tpId=13&tqId=11173&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的 min 函数。 @@ -1263,18 +1325,15 @@ public ArrayList printMatrix(int[][] matrix) { ```java private Stack stack = new Stack<>(); private Stack minStack = new Stack<>(); -private int min = Integer.MAX_VALUE; public void push(int node) { stack.push(node); - if (min > node) min = node; - minStack.push(min); + minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(), node)); } public void pop() { stack.pop(); minStack.pop(); - min = minStack.peek(); } public int top() { @@ -1288,6 +1347,8 @@ public int min() { # 31. 栈的压入、弹出序列 +[NowCoder](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&tqId=11174&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。 @@ -1313,6 +1374,8 @@ public boolean IsPopOrder(int[] pushA, int[] popA) { # 32.1 从上往下打印二叉树 +[NowCoder](https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 从上往下打印出二叉树的每个节点,同层节点从左至右打印。 @@ -1331,14 +1394,17 @@ public boolean IsPopOrder(int[] pushA, int[] popA) { public ArrayList PrintFromTopToBottom(TreeNode root) { Queue queue = new LinkedList<>(); ArrayList ret = new ArrayList<>(); - if (root == null) return ret; + if (root == null) + return ret; queue.add(root); while (!queue.isEmpty()) { int cnt = queue.size(); - for (int i = 0; i < cnt; i++) { + while (cnt-- > 0) { TreeNode t = queue.poll(); - if (t.left != null) queue.add(t.left); - if (t.right != null) queue.add(t.right); + if (t.left != null) + queue.add(t.left); + if (t.right != null) + queue.add(t.right); ret.add(t.val); } } @@ -1348,6 +1414,8 @@ public ArrayList PrintFromTopToBottom(TreeNode root) { # 32.2 把二叉树打印成多行 +[NowCoder](https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 和上题几乎一样。 @@ -1357,17 +1425,20 @@ public ArrayList PrintFromTopToBottom(TreeNode root) { ```java ArrayList> Print(TreeNode pRoot) { ArrayList> ret = new ArrayList<>(); - if (pRoot == null) return ret; + if (pRoot == null) + return ret; Queue queue = new LinkedList<>(); queue.add(pRoot); while (!queue.isEmpty()) { - int cnt = queue.size(); ArrayList list = new ArrayList<>(); - for (int i = 0; i < cnt; i++) { + int cnt = queue.size(); + while (cnt-- > 0) { TreeNode node = queue.poll(); list.add(node.val); - if (node.left != null) queue.add(node.left); - if (node.right != null) queue.add(node.right); + if (node.left != null) + queue.add(node.left); + if (node.right != null) + queue.add(node.right); } ret.add(list); } @@ -1377,6 +1448,8 @@ ArrayList> Print(TreeNode pRoot) { # 32.3 按之字形顺序打印二叉树 +[NowCoder](https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&tqId=11212&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。 @@ -1386,20 +1459,24 @@ ArrayList> Print(TreeNode pRoot) { ```java public ArrayList> Print(TreeNode pRoot) { ArrayList> ret = new ArrayList<>(); - if (pRoot == null) return ret; + if (pRoot == null) + return ret; Queue queue = new LinkedList<>(); queue.add(pRoot); boolean reverse = false; while (!queue.isEmpty()) { - int cnt = queue.size(); ArrayList list = new ArrayList<>(); - for (int i = 0; i < cnt; i++) { + int cnt = queue.size(); + while (cnt-- > 0) { TreeNode node = queue.poll(); list.add(node.val); - if (node.left != null) queue.add(node.left); - if (node.right != null) queue.add(node.right); + if (node.left != null) + queue.add(node.left); + if (node.right != null) + queue.add(node.right); } - if (reverse) Collections.reverse(list); + if (reverse) + Collections.reverse(list); reverse = !reverse; ret.add(list); } @@ -1409,6 +1486,8 @@ public ArrayList> Print(TreeNode pRoot) { # 33. 二叉搜索树的后序遍历序列 +[NowCoder](https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&tqId=11176&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。假设输入的数组的任意两个数字都互不相同。 @@ -1421,24 +1500,29 @@ public ArrayList> Print(TreeNode pRoot) { ```java public boolean VerifySquenceOfBST(int[] sequence) { - if (sequence == null || sequence.length == 0) return false; + if (sequence == null || sequence.length == 0) + return false; return verify(sequence, 0, sequence.length - 1); } private boolean verify(int[] sequence, int first, int last) { - if (last - first <= 1) return true; + if (last - first <= 1) + return true; int rootVal = sequence[last]; int cutIndex = first; - while (cutIndex < last && sequence[cutIndex] <= rootVal) cutIndex++; - for (int i = cutIndex + 1; i < last; i++) { - if (sequence[i] < rootVal) return false; - } + while (cutIndex < last && sequence[cutIndex] <= rootVal) + cutIndex++; + for (int i = cutIndex + 1; i < last; i++) + if (sequence[i] < rootVal) + return false; return verify(sequence, first, cutIndex - 1) && verify(sequence, cutIndex, last - 1); } ``` # 34. 二叉树中和为某一值的路径 +[NowCoder](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 @@ -1453,19 +1537,20 @@ private boolean verify(int[] sequence, int first, int last) { private ArrayList> ret = new ArrayList<>(); public ArrayList> FindPath(TreeNode root, int target) { - dfs(root, target, new ArrayList<>()); + backtracking(root, target, new ArrayList<>()); return ret; } -private void dfs(TreeNode node, int target, ArrayList path) { - if (node == null) return; +private void backtracking(TreeNode node, int target, ArrayList path) { + if (node == null) + return; path.add(node.val); target -= node.val; if (target == 0 && node.left == null && node.right == null) { ret.add(new ArrayList(path)); } else { - dfs(node.left, target, path); - dfs(node.right, target, path); + backtracking(node.left, target, path); + backtracking(node.right, target, path); } path.remove(path.size() - 1); } @@ -1473,6 +1558,8 @@ private void dfs(TreeNode node, int target, ArrayList path) { # 35. 复杂链表的复制 +[NowCoder](https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&tqId=11178&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。 @@ -1495,9 +1582,8 @@ private void dfs(TreeNode node, int target, ArrayList path) { ```java public RandomListNode Clone(RandomListNode pHead) { - if (pHead == null) { + if (pHead == null) return null; - } // 插入新节点 RandomListNode cur = pHead; while (cur != null) { @@ -1510,9 +1596,8 @@ public RandomListNode Clone(RandomListNode pHead) { cur = pHead; while (cur != null) { RandomListNode clone = cur.next; - if (cur.random != null) { + if (cur.random != null) clone.random = cur.random.next; - } cur = clone.next; } // 拆分 @@ -1529,6 +1614,8 @@ public RandomListNode Clone(RandomListNode pHead) { # 36. 二叉搜索树与双向链表 +[NowCoder](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 @@ -1542,24 +1629,30 @@ private TreeNode pre = null; private TreeNode head = null; public TreeNode Convert(TreeNode root) { - if (root == null) return null; + if (root == null) + return null; inOrder(root); return head; } private void inOrder(TreeNode node) { - if (node == null) return; + if (node == null) + return; inOrder(node.left); node.left = pre; - if (pre != null) pre.right = node; + if (pre != null) + pre.right = node; pre = node; - if (head == null) head = node; + if (head == null) + head = node; inOrder(node.right); } ``` # 37. 序列化二叉树 +[NowCoder](https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 请实现两个函数,分别用来序列化和反序列化二叉树。 @@ -1572,7 +1665,8 @@ public class Solution { private String deserializeStr; public String Serialize(TreeNode root) { - if (root == null) return "#"; + if (root == null) + return "#"; return root.val + " " + Serialize(root.left) + " " + Serialize(root.right); } @@ -1582,11 +1676,13 @@ public class Solution { } private TreeNode Deserialize() { - if (deserializeStr.length() == 0) return null; + if (deserializeStr.length() == 0) + return null; int index = deserializeStr.indexOf(" "); String node = index == -1 ? deserializeStr : deserializeStr.substring(0, index); deserializeStr = index == -1 ? "" : deserializeStr.substring(index + 1); - if (node.equals("#")) return null; + if (node.equals("#")) + return null; int val = Integer.valueOf(node); TreeNode t = new TreeNode(val); t.left = Deserialize(); @@ -1598,6 +1694,8 @@ public class Solution { # 38. 字符串的排列 +[NowCoder](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串 abc,则打印出由字符 a, b, c 所能排列出来的所有字符串 abc, acb, bac, bca, cab 和 cba。 @@ -1608,7 +1706,8 @@ public class Solution { private ArrayList ret = new ArrayList<>(); public ArrayList Permutation(String str) { - if (str.length() == 0) return ret; + if (str.length() == 0) + return ret; char[] chars = str.toCharArray(); Arrays.sort(chars); backtracking(chars, new boolean[chars.length], new StringBuffer()); @@ -1621,8 +1720,10 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuffer s) { return; } for (int i = 0; i < chars.length; i++) { - if (hasUsed[i]) continue; - if (i != 0 && chars[i] == chars[i - 1] && !hasUsed[i - 1]) continue; // 保证不重复 + if (hasUsed[i]) + continue; + if (i != 0 && chars[i] == chars[i - 1] && !hasUsed[i - 1]) // 保证不重复 + continue; hasUsed[i] = true; s.append(chars[i]); backtracking(chars, hasUsed, s); @@ -1634,6 +1735,8 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuffer s) { # 39. 数组中出现次数超过一半的数字 +[NowCoder](https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&tqId=11181&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 解题思路 多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(N)。 @@ -1651,13 +1754,17 @@ public int MoreThanHalfNum_Solution(int[] nums) { } } int cnt = 0; - for (int val : nums) if (val == majority) cnt++; + for (int val : nums) + if (val == majority) + cnt++; return cnt > nums.length / 2 ? majority : 0; } ``` # 40. 最小的 K 个数 +[NowCoder](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 解题思路 ### 快速选择 @@ -1671,14 +1778,13 @@ public int MoreThanHalfNum_Solution(int[] nums) { ```java public ArrayList GetLeastNumbers_Solution(int[] nums, int k) { - if (k > nums.length || k <= 0) return new ArrayList<>(); + if (k > nums.length || k <= 0) + return new ArrayList<>(); int kthSmallest = findKthSmallest(nums, k - 1); ArrayList ret = new ArrayList<>(); - for (int val : nums) { - if (val <= kthSmallest && ret.size() < k) { + for (int val : nums) + if (val <= kthSmallest && ret.size() < k) ret.add(val); - } - } return ret; } @@ -1686,9 +1792,12 @@ public int findKthSmallest(int[] nums, int k) { int l = 0, h = nums.length - 1; while (l < h) { int j = partition(nums, l, h); - if (j == k) break; - if (j > k) h = j - 1; - else l = j + 1; + if (j == k) + break; + if (j > k) + h = j - 1; + else + l = j + 1; } return nums[k]; } @@ -1698,7 +1807,8 @@ private int partition(int[] nums, int l, int h) { while (true) { while (i < h && nums[++i] < nums[l]) ; while (j > l && nums[l] < nums[--j]) ; - if (i >= j) break; + if (i >= j) + break; swap(nums, i, j); } swap(nums, l, j); @@ -1721,13 +1831,13 @@ private void swap(int[] nums, int i, int j) { ```java public ArrayList GetLeastNumbers_Solution(int[] nums, int k) { - if (k > nums.length || k <= 0) return new ArrayList<>(); + if (k > nums.length || k <= 0) + return new ArrayList<>(); PriorityQueue maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1); for (int num : nums) { maxHeap.add(num); - if (maxHeap.size() > k) { + if (maxHeap.size() > k) maxHeap.poll(); - } } ArrayList ret = new ArrayList<>(maxHeap); return ret; @@ -1736,6 +1846,8 @@ public ArrayList GetLeastNumbers_Solution(int[] nums, int k) { # 41.1 数据流中的中位数 +[NowCoder](https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&tqId=11216&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 @@ -1767,17 +1879,18 @@ public class Solution { } public Double GetMedian() { - if (N % 2 == 0) { + if (N % 2 == 0) return (left.peek() + right.peek()) / 2.0; - } else { + else return (double) right.peek(); - } } } ``` # 41.2 字符流中第一个不重复的字符 +[NowCoder](https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "go" 时,第一个只出现一次的字符是 "g"。当从该字符流中读出前六个字符“google" 时,第一个只出现一次的字符是 "l"。 @@ -1792,13 +1905,13 @@ public class Solution { public void Insert(char ch) { cnts[ch]++; queue.add(ch); - while (!queue.isEmpty() && cnts[queue.peek()] > 1) { + while (!queue.isEmpty() && cnts[queue.peek()] > 1) queue.poll(); - } } public char FirstAppearingOnce() { - if (queue.isEmpty()) return '#'; + if (queue.isEmpty()) + return '#'; return queue.peek(); } } @@ -1806,6 +1919,8 @@ public class Solution { # 42. 连续子数组的最大和 +[NowCoder](https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&tqId=11183&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 {6,-3,-2,7,-15,1,2,2},连续子数组的最大和为 8(从第 0 个开始,到第 3 个为止)。 @@ -1813,24 +1928,24 @@ public class Solution { ## 解题思路 ```java -public int FindGreatestSumOfSubArray(int[] nums) { - if (nums.length == 0) return 0; - int ret = Integer.MIN_VALUE; - int sum = 0; - for (int val : nums) { - if (sum <= 0) sum = val; - else sum += val; - ret = Math.max(ret, sum); - } - return ret; -} + public int FindGreatestSumOfSubArray(int[] nums) { + if (nums.length == 0) + return 0; + int ret = Integer.MIN_VALUE; + int sum = 0; + for (int val : nums) { + sum = sum <= 0 ? val : sum + val; + ret = Math.max(ret, sum); + } + return ret; + } ``` # 43. 从 1 到 n 整数中 1 出现的次数 -## 解题思路 +[NowCoder](https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&tqId=11184&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) -> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython) +## 解题思路 ```java public int NumberOf1Between1AndN_Solution(int n) { @@ -1843,6 +1958,8 @@ public int NumberOf1Between1AndN_Solution(int n) { } ``` +> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython) + # 44. 数字序列中的某一位数字 ## 题目描述 @@ -1853,14 +1970,14 @@ public int NumberOf1Between1AndN_Solution(int n) { ```java public int digitAtIndex(int index) { - if (index < 0) return -1; + if (index < 0) + return -1; int digit = 1; while (true) { int amount = getAmountOfDigit(digit); int totalAmount = amount * digit; - if (index < totalAmount) { + if (index < totalAmount) return digitAtIndex(index, digit); - } index -= totalAmount; digit++; } @@ -1871,7 +1988,8 @@ public int digitAtIndex(int index) { * 例如 digit = 2,return 90 */ private int getAmountOfDigit(int digit) { - if (digit == 1) return 10; + if (digit == 1) + return 10; return (int) Math.pow(10, digit - 1) * 9; } @@ -1889,13 +2007,16 @@ private int digitAtIndex(int index, int digit) { * 例如 digit = 2 return 10 */ private int beginNumber(int digit) { - if (digit == 1) return 0; + if (digit == 1) + return 0; return (int) Math.pow(10, digit - 1); } ``` # 45. 把数组排成最小的数 +[NowCoder](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组 {3,32,321},则打印出这三个数字能排成的最小数字为 321323。 @@ -1908,16 +2029,20 @@ private int beginNumber(int digit) { public String PrintMinNumber(int[] numbers) { int n = numbers.length; String[] nums = new String[n]; - for (int i = 0; i < n; i++) nums[i] = numbers[i] + ""; + for (int i = 0; i < n; i++) + nums[i] = numbers[i] + ""; Arrays.sort(nums, (s1, s2) -> (s1 + s2).compareTo(s2 + s1)); String ret = ""; - for (String str : nums) ret += str; + for (String str : nums) + ret += str; return ret; } ``` # 46. 把数字翻译成字符串 +[Leetcode](https://leetcode.com/problems/decode-ways/description/) + ## 题目描述 给定一个数字,按照如下规则翻译成字符串:0 翻译成“a”,1 翻译成“b”... 25 翻译成“z”。一个数字有多种翻译可能,例如 12258 一共有 5 种,分别是 bccfi,bwfi,bczi,mcfi,mzi。实现一个函数,用来计算一个数字有多少种不同的翻译方法。 @@ -1925,23 +2050,31 @@ public String PrintMinNumber(int[] numbers) { ## 解题思路 ```java -public int getTranslationCount(String number) { - int n = number.length(); - int[] counts = new int[n + 1]; - counts[n - 1] = counts[n] = 1; - for (int i = n - 2; i >= 0; i--) { - counts[i] = counts[i + 1]; - int converted = Integer.valueOf(number.substring(i, i + 2)); - if (converted >= 10 && converted <= 25) { - counts[i] += counts[i + 2]; - } +public int numDecodings(String s) { + if (s == null || s.length() == 0) + return 0; + int n = s.length(); + int[] dp = new int[n + 1]; + dp[0] = 1; + dp[1] = s.charAt(0) == '0' ? 0 : 1; + for (int i = 2; i <= n; i++) { + int one = Integer.valueOf(s.substring(i - 1, i)); + if (one != 0) + dp[i] += dp[i - 1]; + if (s.charAt(i - 2) == '0') + continue; + int two = Integer.valueOf(s.substring(i - 2, i)); + if (two <= 26) + dp[i] += dp[i - 2]; } - return counts[0]; + return dp[n]; } ``` # 47. 礼物的最大价值 +[NowCoder](https://www.nowcoder.com/questionTerminal/72a99e28381a407991f2c96d8cb238ab) + ## 题目描述 在一个 m\*n 的棋盘的每一个格都放有一个礼物,每个礼物都有一定价值(大于 0)。从左上角开始拿礼物,每次向右或向下移动一格,直到右下角结束。给定一个棋盘,求拿到礼物的最大价值。例如,对于如下棋盘 @@ -1960,16 +2093,15 @@ public int getTranslationCount(String number) { 应该用动态规划求解,而不是深度优先搜索,深度优先搜索过于复杂,不是最优解。 ```java -public int getMaxValue(int[][] values) { - if (values == null || values.length == 0 || values[0].length == 0) return 0; - int m = values.length; - int n = values[0].length; +public int getMost(int[][] values) { + if (values == null || values.length == 0 || values[0].length == 0) + return 0; + int m = values.length, n = values[0].length; int[] dp = new int[n]; for (int i = 0; i < m; i++) { dp[0] += values[i][0]; - for (int j = 1; j < n; j++) { + for (int j = 1; j < n; j++) dp[j] = Math.max(dp[j], dp[j - 1]) + values[i][j]; - } } return dp[n - 1]; } @@ -1987,17 +2119,18 @@ public int getMaxValue(int[][] values) { public int longestSubStringWithoutDuplication(String str) { int curLen = 0; int maxLen = 0; - int[] indexs = new int[26]; - Arrays.fill(indexs, -1); + int[] preIndexs = new int[26]; + Arrays.fill(preIndexs, -1); for (int i = 0; i < str.length(); i++) { int c = str.charAt(i) - 'a'; - int preIndex = indexs[c]; - if (preIndex == -1 || i - preIndex > curLen) curLen++; - else { + int preIndex = preIndexs[c]; + if (preIndex == -1 || i - preIndex > curLen) { + curLen++; + } else { maxLen = Math.max(maxLen, curLen); curLen = i - preIndex; } - indexs[c] = i; + preIndexs[c] = i; } maxLen = Math.max(maxLen, curLen); return maxLen; @@ -2006,6 +2139,8 @@ public int longestSubStringWithoutDuplication(String str) { # 49. 丑数 +[NowCoder](https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b?tpId=13&tqId=11186&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含因子 7。 习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。 @@ -2014,16 +2149,20 @@ public int longestSubStringWithoutDuplication(String str) { ```java public int GetUglyNumber_Solution(int index) { - if (index <= 6) return index; + if (index <= 6) + return index; int i2 = 0, i3 = 0, i5 = 0; int[] dp = new int[index]; dp[0] = 1; for (int i = 1; i < index; i++) { int n2 = dp[i2] * 2, n3 = dp[i3] * 3, n5 = dp[i5] * 5; dp[i] = Math.min(n2, Math.min(n3, n5)); - if (dp[i] == n2) i2++; - if (dp[i] == n3) i3++; - if (dp[i] == n5) i5++; + if (dp[i] == n2) + i2++; + if (dp[i] == n3) + i3++; + if (dp[i] == n5) + i5++; } return dp[index - 1]; } @@ -2031,9 +2170,11 @@ public int GetUglyNumber_Solution(int index) { # 50. 第一个只出现一次的字符位置 +[NowCoder](https://www.nowcoder.com/practice/1c82e8cf713b4bbeb2a5b31cf5b0417c?tpId=13&tqId=11187&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 -在一个字符串 (1<=字符串长度 <=10000,全部由字母组成) 中找到第一个只出现一次的字符,并返回它的位置。 +在一个字符串 (1 <= 字符串长度 <= 10000,全部由字母组成) 中找到第一个只出现一次的字符,并返回它的位置。 ## 解题思路 @@ -2042,8 +2183,11 @@ public int GetUglyNumber_Solution(int index) { ```java public int FirstNotRepeatingChar(String str) { int[] cnts = new int[256]; - for (int i = 0; i < str.length(); i++) cnts[str.charAt(i)]++; - for (int i = 0; i < str.length(); i++) if (cnts[str.charAt(i)] == 1) return i; + for (int i = 0; i < str.length(); i++) + cnts[str.charAt(i)]++; + for (int i = 0; i < str.length(); i++) + if (cnts[str.charAt(i)] == 1) + return i; return -1; } ``` @@ -2055,12 +2199,15 @@ public int FirstNotRepeatingChar(String str) { BitSet bs1 = new BitSet(256); BitSet bs2 = new BitSet(256); for (char c : str.toCharArray()) { - if (!bs1.get(c) && !bs2.get(c)) bs1.set(c); // 0 0 - else if (bs1.get(c) && !bs2.get(c)) bs2.set(c); // 0 1 + if (!bs1.get(c) && !bs2.get(c)) + bs1.set(c); // 0 0 + else if (bs1.get(c) && !bs2.get(c)) + bs2.set(c); // 0 1 } for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); - if (bs1.get(c) && !bs2.get(c)) return i; + if (bs1.get(c) && !bs2.get(c)) + return i; } return -1; } @@ -2068,6 +2215,8 @@ public int FirstNotRepeatingChar(String str) { # 51. 数组中的逆序对 +[NowCoder](https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数 P。 @@ -2080,38 +2229,43 @@ private int[] tmp; // 在这里创建辅助数组,而不是在 merge() 递归 public int InversePairs(int[] nums) { tmp = new int[nums.length]; - mergeSortUp2Down(nums, 0, nums.length - 1); + mergeSort(nums, 0, nums.length - 1); return (int) (cnt % 1000000007); } -private void mergeSortUp2Down(int[] nums, int first, int last) { - if (last - first < 1) return; - int mid = first + (last - first) / 2; - mergeSortUp2Down(nums, first, mid); - mergeSortUp2Down(nums, mid + 1, last); - merge(nums, first, mid, last); +private void mergeSort(int[] nums, int l, int h) { + if (h - l < 1) + return; + int m = l + (h - l) / 2; + mergeSort(nums, l, m); + mergeSort(nums, m + 1, h); + merge(nums, l, m, h); } -private void merge(int[] nums, int first, int mid, int last) { - int i = first, j = mid + 1, k = first; - while (i <= mid || j <= last) { - if (i > mid) tmp[k] = nums[j++]; - else if (j > last) tmp[k] = nums[i++]; - else if (nums[i] < nums[j]) tmp[k] = nums[i++]; +private void merge(int[] nums, int l, int m, int h) { + int i = l, j = m + 1, k = l; + while (i <= m || j <= h) { + if (i > m) + tmp[k] = nums[j++]; + else if (j > h) + tmp[k] = nums[i++]; + else if (nums[i] < nums[j]) + tmp[k] = nums[i++]; else { tmp[k] = nums[j++]; - this.cnt += mid - i + 1; // nums[i] > nums[j],说明 nums[i...mid] 都大于 nums[j] + this.cnt += m - i + 1; // a[i] > a[j],说明 a[i...mid] 都大于 a[j] } k++; } - for (k = first; k <= last; k++) { + for (k = l; k <= h; k++) nums[k] = tmp[k]; - } } ``` # 52. 两个链表的第一个公共结点 +[NowCoder](https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&tqId=11189&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述

@@ -2135,6 +2289,8 @@ public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { # 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) + ## 题目描述 ```html @@ -2147,43 +2303,30 @@ Output: ## 解题思路 -可以用二分查找找出数字在数组的最左端和最右端,找最左端和最右端在方法实现上的区别主要在于对 nums[m] == K 的处理: - -- 找最左端令 h = m - 1 -- 找最右端令 l = m + 1 - ```java public int GetNumberOfK(int[] nums, int K) { - int first = getFirstK(nums, K); - int last = getLastK(nums, K); - return first == -1 || last == -1 ? 0 : last - first + 1; + int first = binarySearch(nums, K); + int last = binarySearch(nums, K + 1); + return (first == nums.length || nums[first] != K) ? 0 : last - first; } -private int getFirstK(int[] nums, int K) { - int l = 0, h = nums.length - 1; - while (l <= h) { +private int binarySearch(int[] nums, int K) { + int l = 0, h = nums.length; + while (l < h) { int m = l + (h - l) / 2; - if (nums[m] >= K) h = m - 1; - else l = m + 1; + if (nums[m] >= K) + h = m; + else + l = m + 1; } - if (l > nums.length - 1 || nums[l] != K) return -1; return l; } - -private int getLastK(int[] nums, int K) { - int l = 0, h = nums.length - 1; - while (l <= h) { - int m = l + (h - l) / 2; - if (nums[m] > K) h = m - 1; - else l = m + 1; - } - if (h < 0 || nums[h] != K) return -1; - return h; -} ``` # 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) + ## 解题思路 利用二叉搜索数中序遍历有序的特点。 @@ -2198,17 +2341,20 @@ public TreeNode KthNode(TreeNode pRoot, int k) { } private void inOrder(TreeNode root, int k) { - if (root == null) return; - if (cnt > k) return; + if (root == null || cnt >= k) + return; inOrder(root.left, k); cnt++; - if (cnt == k) ret = root; + if (cnt == k) + ret = root; inOrder(root.right, k); } ``` # 55.1 二叉树的深度 +[NowCoder](https://www.nowcoder.com/practice/435fb86331474282a3499955f0a41e8b?tpId=13&tqId=11191&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 @@ -2219,13 +2365,14 @@ private void inOrder(TreeNode root, int k) { ```java public int TreeDepth(TreeNode root) { - if (root == null) return 0; - return 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right)); + return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right)); } ``` # 55.2 平衡二叉树 +[NowCoder](https://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId=13&tqId=11192&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 平衡二叉树左右子树高度差不超过 1。 @@ -2243,17 +2390,20 @@ public boolean IsBalanced_Solution(TreeNode root) { } private int height(TreeNode root) { - if (root == null) return 0; + if (root == null) + return 0; int left = height(root.left); int right = height(root.right); - if (Math.abs(left - right) > 1) isBalanced = false; + if (Math.abs(left - right) > 1) + isBalanced = false; return 1 + Math.max(left, right); } ``` - # 56. 数组中只出现一次的数字 +[NowCoder](https://www.nowcoder.com/practice/e02fdb54d7524710a7d664d082bb7811?tpId=13&tqId=11193&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 一个整型数组里除了两个数字之外,其他的数字都出现了两次,找出这两个数。 @@ -2267,20 +2417,25 @@ private int height(TreeNode root) { diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 ```java -public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) { - int diff = 0; - for (int num : array) diff ^= num; - // 得到最右一位 - diff &= -diff; - for (int num : array) { - if ((num & diff) == 0) num1[0] ^= num; - else num2[0] ^= num; + public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) { + int diff = 0; + for (int num : nums) + diff ^= num; + // 得到最右一位 + diff &= -diff; + for (int num : nums) { + if ((num & diff) == 0) + num1[0] ^= num; + else + num2[0] ^= num; + } } -} ``` # 57.1 和为 S 的两个数字 +[NowCoder](https://www.nowcoder.com/practice/390da4f7a00f44bea7c2f3d19491311b?tpId=13&tqId=11195&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入一个递增排序的数组和一个数字 S,在数组中查找两个数,使得他们的和正好是 S,如果有多对数字的和等于 S,输出两个数的乘积最小的。 @@ -2289,51 +2444,64 @@ public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) { 使用双指针,一个指针指向元素较小的值,一个指针指向元素较大的值。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。 -如果两个指针指向元素的和 sum == target,那么得到要求的结果;如果 sum > target,移动较大的元素,使 sum 变小一些;如果 sum < target,移动较小的元素,使 sum 变大一些。 +- 如果两个指针指向元素的和 sum == target,那么得到要求的结果; +- 如果 sum > target,移动较大的元素,使 sum 变小一些; +- 如果 sum < target,移动较小的元素,使 sum 变大一些。 ```java public ArrayList FindNumbersWithSum(int[] array, int sum) { int i = 0, j = array.length - 1; while (i < j) { int cur = array[i] + array[j]; - if (cur == sum) return new ArrayList(Arrays.asList(array[i], array[j])); - else if (cur < sum) i++; - else j--; + if (cur == sum) + return new ArrayList<>(Arrays.asList(array[i], array[j])); + if (cur < sum) + i++; + else + j--; } - return new ArrayList(); + return new ArrayList<>(); } ``` # 57.2 和为 S 的连续正数序列 +[NowCoder](https://www.nowcoder.com/practice/c451a3fd84b64cb19485dad758a55ebe?tpId=13&tqId=11194&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 -和为 100 的连续序列有 18, 19, 20, 21, 22。 +输出所有和为 S 的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序 + +例如和为 100 的连续序列有: + +``` +[9, 10, 11, 12, 13, 14, 15, 16] +[18, 19, 20, 21, 22]。 +``` ## 解题思路 ```java public ArrayList> FindContinuousSequence(int sum) { ArrayList> ret = new ArrayList<>(); - int first = 1, last = 2; + int start = 1, end = 2; int curSum = 3; - while (first <= sum / 2 && last < sum) { + while (end < sum) { if (curSum > sum) { - curSum -= first; - first++; + curSum -= start; + start++; } else if (curSum < sum) { - last++; - curSum += last; + end++; + curSum += end; } else { ArrayList list = new ArrayList<>(); - for (int i = first; i <= last; i++) { + for (int i = start; i <= end; i++) list.add(i); - } ret.add(list); - curSum -= first; - first++; - last++; - curSum += last; + curSum -= start; + start++; + end++; + curSum += end; } } return ret; @@ -2342,6 +2510,8 @@ public ArrayList> FindContinuousSequence(int sum) { # 58.1 翻转单词顺序列 +[NowCoder](https://www.nowcoder.com/practice/3194a4f4cf814f63919d0790578d51f3?tpId=13&tqId=11197&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 输入:"I am a student." @@ -2350,11 +2520,12 @@ public ArrayList> FindContinuousSequence(int sum) { ## 解题思路 -题目应该有一个隐含条件,就是不能用额外的空间。虽然 Java 的题目输入参数为 String 类型,需要先创建一个字符数组使得空间复杂度为 O(n),但是正确的参数类型应该和原书一样,为字符数组,并且只能使用该字符数组的空间。任何使用了额外空间的解法在面试时都会大打折扣,包括递归解法。正确的解法应该是和书上一样,先旋转每个单词,再旋转整个字符串。 +题目应该有一个隐含条件,就是不能用额外的空间。虽然 Java 的题目输入参数为 String 类型,需要先创建一个字符数组使得空间复杂度为 O(N),但是正确的参数类型应该和原书一样,为字符数组,并且只能使用该字符数组的空间。任何使用了额外空间的解法在面试时都会大打折扣,包括递归解法。 + +正确的解法应该是和书上一样,先旋转每个单词,再旋转整个字符串。 ```java public String ReverseSentence(String str) { - if (str.length() == 0) return str; int n = str.length(); char[] chars = str.toCharArray(); int i = 0, j = 0; @@ -2370,41 +2541,56 @@ public String ReverseSentence(String str) { } private void reverse(char[] c, int i, int j) { - while(i < j) { - char t = c[i]; c[i] = c[j]; c[j] = t; - i++; j--; - } + while (i < j) + swap(c, i++, j--); +} + +private void swap(char[] c, int i, int j) { + char t = c[i]; + c[i] = c[j]; + c[j] = t; } ``` # 58.2 左旋转字符串 +[NowCoder](https://www.nowcoder.com/practice/12d959b108cb42b1ab72cef4d36af5ec?tpId=13&tqId=11196&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 对于一个给定的字符序列 S,请你把其循环左移 K 位后的序列输出。例如,字符序列 S=”abcXYZdef”, 要求输出循环左移 3 位后的结果,即“XYZdefabc”。 ## 解题思路 +将 "abcXYZdef" 旋转左移三位,可以先将 "abc" 和 "XYZdef" 分别旋转,得到 "cbafedZYX",然后再把整个字符串旋转得到 "XYZdefabc"。 + ```java public String LeftRotateString(String str, int n) { - if(str.length() == 0) return ""; - char[] c = str.toCharArray(); - reverse(c, 0, n - 1); - reverse(c, n, c.length - 1); - reverse(c, 0, c.length - 1); - return new String(c); + if (n >= str.length()) + return str; + char[] chars = str.toCharArray(); + reverse(chars, 0, n - 1); + reverse(chars, n, chars.length - 1); + reverse(chars, 0, chars.length - 1); + return new String(chars); } -private void reverse(char[] c, int i, int j) { - while(i < j) { - char t = c[i]; c[i] = c[j]; c[j] = t; - i++; j--; - } +private void reverse(char[] chars, int i, int j) { + while (i < j) + swap(chars, i++, j--); +} + +private void swap(char[] chars, int i, int j) { + char t = chars[i]; + chars[i] = chars[j]; + chars[j] = t; } ``` # 59. 滑动窗口的最大值 +[NowCoder](https://www.nowcoder.com/practice/1624bc35a45c42c0bc17d17fa0cba788?tpId=13&tqId=11217&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组 {2, 3, 4, 2, 6, 2, 5, 1} 及滑动窗口的大小 3,那么一共存在 6 个滑动窗口,他们的最大值分别为 {4, 4, 6, 6, 6, 5}。 @@ -2414,14 +2600,15 @@ private void reverse(char[] c, int i, int j) { ```java public ArrayList maxInWindows(int[] num, int size) { ArrayList ret = new ArrayList<>(); - if (size > num.length || size < 1) return ret; - // 构建最大堆,即堆顶元素是堆的最大值。 PriorityQueue heap = new PriorityQueue((o1, o2) -> o2 - o1); - for (int i = 0; i < size; i++) heap.add(num[i]); + if (size > num.length || size < 1) + return ret; + for (int i = 0; i < size; i++) + heap.add(num[i]); ret.add(heap.peek()); - for (int i = 1; i + size - 1 < num.length; i++) { + for (int i = 1, j = i + size - 1; j < num.length; i++, j++) { heap.remove(num[i - 1]); - heap.add(num[i + size - 1]); + heap.add(num[j]); ret.add(heap.peek()); } return ret; @@ -2430,6 +2617,8 @@ public ArrayList maxInWindows(int[] num, int size) { # 60. n 个骰子的点数 +[Lintcode](https://www.lintcode.com/en/problem/dices-sum/) + ## 题目描述 把 n 个骰子仍在地上,求点数和为 s 的概率。 @@ -2438,29 +2627,28 @@ public ArrayList maxInWindows(int[] num, int size) { ### 动态规划解法 +使用一个二维数组 dp 存储点数出现的次数,其中 dp[i][j] 表示前 i 个骰子产生点数 j 的次数。 + 空间复杂度:O(N2) ```java -private static int face = 6; +public List> dicesSum(int n) { + final int face = 6; + final int pointNum = face * n; + long[][] dp = new long[n + 1][pointNum + 1]; + for (int i = 1; i <= face; i++) + dp[1][i] = 1; -public double countProbability(int n, int s) { - if (n < 1 || s < n) return 0.0; - int pointNum = face * n; - int[][] dp = new int[n][pointNum]; - for (int i = 0; i < face; i++) { - dp[0][i] = 1; - } - for (int i = 1; i < n; i++) { - for (int j = i; j < pointNum; j++) { // 使用 i 个骰子最小点数为 i - for (int k = 1; k <= face; k++) { - if (j - k >= 0) { - dp[i][j] += dp[i - 1][j - k]; - } - } - } - } - int totalNum = (int) Math.pow(6, n); - return (double) dp[n - 1][s - 1] / totalNum; + for (int i = 2; i <= n; i++) + for (int j = i; j <= pointNum; j++) // 使用 i 个骰子最小点数为 i + for (int k = 1; k <= face && k <= j; k++) + dp[i][j] += dp[i - 1][j - k]; + + final double totalNum = Math.pow(6, n); + List> ret = new ArrayList<>(); + for (int i = n; i <= pointNum; i++) + ret.add(new AbstractMap.SimpleEntry<>(i, dp[n][i] / totalNum)); + return ret; } ``` @@ -2469,33 +2657,32 @@ public double countProbability(int n, int s) { 空间复杂度:O(N) ```java -private static int face = 6; - -public double countProbability(int n, int s) { - if (n < 1 || s < n) return 0.0; - int pointNum = face * n; - int[][] dp = new int[2][pointNum]; - for (int i = 0; i < face; i++) { +public List> dicesSum(int n) { + final int face = 6; + final int pointNum = face * n; + long[][] dp = new long[2][pointNum + 1]; + for (int i = 1; i <= face; i++) dp[0][i] = 1; - } int flag = 1; - for (int i = 1; i < n; i++) { - for (int j = i; j < pointNum; j++) { // 使用 i 个骰子最小点数为 i - for (int k = 1; k <= face; k++) { - if (j - k >= 0) { - dp[flag][j] += dp[1 - flag][j - k]; - } - } - } - flag = 1 - flag; + for (int i = 2; i <= n; i++, flag = 1 - flag) { + for (int j = 0; j <= pointNum; j++) + dp[flag][j] = 0; // 旋转数组清零 + for (int j = i; j <= pointNum; j++) // 使用 i 个骰子最小点数为 i + for (int k = 1; k <= face && k <= j; k++) + dp[flag][j] += dp[1 - flag][j - k]; } - int totalNum = (int) Math.pow(6, n); - return (double) dp[flag][s - 1] / totalNum; + final double totalNum = Math.pow(6, n); + List> ret = new ArrayList<>(); + for (int i = n; i <= pointNum; i++) + ret.add(new AbstractMap.SimpleEntry<>(i, dp[1 - flag][i] / totalNum)); + return ret; } ``` # 61. 扑克牌顺子 +[NowCoder](https://www.nowcoder.com/practice/762836f4d43d43ca9deb273b3de8e1f4?tpId=13&tqId=11198&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 五张牌,其中大小鬼为癞子,牌面大小为 0。判断是否能组成顺子。 @@ -2504,22 +2691,26 @@ public double countProbability(int n, int s) { ```java public boolean isContinuous(int[] nums) { - if (nums.length < 5) return false; + if (nums.length < 5) + return false; Arrays.sort(nums); int cnt = 0; - for (int num : nums) if (num == 0) cnt++; + for (int num : nums) + if (num == 0) + cnt++; for (int i = cnt; i < nums.length - 1; i++) { - if (nums[i + 1] == nums[i]) return false; - int interval = nums[i + 1] - nums[i] - 1; - if (interval > cnt) return false; - cnt -= interval; + if (nums[i + 1] == nums[i]) + return false; + cnt -= nums[i + 1] - nums[i] - 1; } - return true; + return cnt >= 0; } ``` # 62. 圆圈中最后剩下的数 +[NowCoder](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 让小朋友们围成一个大圈。然后,他随机指定一个数 m,让编号为 0 的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续 0...m-1 报数 .... 这样下去 .... 直到剩下最后一个小朋友,可以不用表演。 @@ -2530,14 +2721,18 @@ public boolean isContinuous(int[] nums) { ```java public int LastRemaining_Solution(int n, int m) { - if (n == 0) return -1; - if (n == 1) return 0; + if (n == 0) + return -1; + if (n == 1) + return 0; return (LastRemaining_Solution(n - 1, m) + m) % n; } ``` # 63. 股票的最大利润 +[Leetcode](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/) + ## 题目描述 可以有一次买入和一次卖出,买入必须在前。求最大收益。 @@ -2548,26 +2743,35 @@ public int LastRemaining_Solution(int n, int m) { ```java public int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) + return 0; int n = prices.length; - if(n == 0) return 0; int soFarMin = prices[0]; - int max = 0; - for(int i = 1; i < n; i++) { - if(soFarMin > prices[i]) soFarMin = prices[i]; - else max = Math.max(max, prices[i] - soFarMin); + int maxProfit = 0; + for (int i = 1; i < n; i++) { + soFarMin = Math.min(soFarMin, prices[i]); + maxProfit = Math.max(maxProfit, prices[i] - soFarMin); } - return max; + return maxProfit; } ``` # 64. 求 1+2+3+...+n +[NowCoder](https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 求 1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C)。 ## 解题思路 +使用递归解法最重要的是指定返回条件,但是本题无法直接使用 if 语句来指定返回条件。 + +条件与 && 具有短路原则,即在第一个条件语句为 false 的情况下不会去执行第二个条件语句。利用这一特性,将递归的返回条件取非然后作为 && 的第一个条件语句,递归的主体转换为第二个条件语句,那么当递归的返回条件为 true 的情况下就不会执行递归的主体部分,递归返回。 + +以下实现中,递归的返回条件为 n <= 0,取非后就是 n > 0,递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。 + ```java public int Sum_Solution(int n) { int sum = n; @@ -2578,6 +2782,12 @@ public int Sum_Solution(int n) { # 65. 不用加减乘除做加法 +[NowCoder](https://www.nowcoder.com/practice/59ac416b4b944300b617d4f7f111b215?tpId=13&tqId=11201&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +写一个函数,求两个整数之和,要求在函数体内不得使用 +、-、\*、/ 四则运算符号。 + ## 解题思路 a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。 @@ -2585,14 +2795,15 @@ a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进 递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 ```java -public int Add(int num1, int num2) { - if(num2 == 0) return num1; - return Add(num1 ^ num2, (num1 & num2) << 1); +public int Add(int num1,int num2) { + return num2 == 0 ? num1 : Add(num1 ^ num2, (num1 & num2) << 1); } ``` # 66. 构建乘积数组 +[NowCoder](https://www.nowcoder.com/practice/94a4d381a68b47b7a8bed86f2975db46?tpId=13&tqId=11204&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + ## 题目描述 给定一个数组 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]。不能使用除法。 @@ -2603,29 +2814,46 @@ public int Add(int num1, int num2) { public int[] multiply(int[] A) { int n = A.length; int[] B = new int[n]; - for (int i = 0, product = 1; i < n; product *= A[i], i++) { + for (int i = 0, product = 1; i < n; product *= A[i], i++) B[i] = product; - } - for (int i = n - 1, product = 1; i >= 0; product *= A[i], i--) { + for (int i = n - 1, product = 1; i >= 0; product *= A[i], i--) B[i] *= product; - } return B; } ``` # 67. 把字符串转换成整数 +[NowCoder](https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&tqId=11202&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为 0 或者字符串不是一个合法的数值则返回 0。 + +```html +Iuput: ++2147483647 +1a33 + +Output: +2147483647 +0 +``` + ## 解题思路 ```java public int StrToInt(String str) { - if (str.length() == 0) return 0; + if (str.length() == 0) + return 0; char[] chars = str.toCharArray(); boolean isNegative = chars[0] == '-'; int ret = 0; for (int i = 0; i < chars.length; i++) { - if (i == 0 && (chars[i] == '+' || chars[i] == '-')) continue; - if (chars[i] < '0' || chars[i] > '9') return 0; // 非法输入 + if (i == 0 && (chars[i] == '+' || chars[i] == '-')) + continue; + if (chars[i] < '0' || chars[i] > '9') + return 0; // 非法输入 ret = ret * 10 + (chars[i] - '0'); } return isNegative ? -ret : ret; @@ -2640,12 +2868,18 @@ public int StrToInt(String str) {

+[Leetcode : 235. Lowest Common Ancestor of a Binary Search Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/) + 二叉查找树中,两个节点 p, q 的公共祖先 root 满足 p.val <= root.val && root.val <= q.val,只要找到满足这个条件的最低层节点即可。换句话说,应该先考虑子树的解而不是根节点的解,二叉树的后序遍历操作满足这个特性。在本题中我们可以利用后序遍历的特性,先在左右子树中查找解,最后再考虑根节点的解。 ```java public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { - if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q); - if(root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q); + if (root == null) + return root; + if (root.val > p.val && root.val > q.val) + return lowestCommonAncestor(root.left, p, q); + if (root.val < p.val && root.val < q.val) + return lowestCommonAncestor(root.right, p, q); return root; } ``` @@ -2654,11 +2888,14 @@ 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/) + 在左右子树中查找两个节点的最低公共祖先,如果在其中一颗子树中查找到,那么就返回这个解,否则可以认为根节点就是最低公共祖先。 ```java public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { - if (root == null || root == p || root == q) return root; + if (root == null || root == p || root == q) + return root; TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); return left == null ? right : right == null ? left : root; diff --git a/notes/数据库系统原理.md b/notes/数据库系统原理.md index 19a76d1a..050b5af0 100644 --- a/notes/数据库系统原理.md +++ b/notes/数据库系统原理.md @@ -86,7 +86,7 @@ T1 修改一个数据,T2 随后读取这个数据。如 ### 3. 不可重复读 -T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和和第一次读取的结果不同。 +T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。

@@ -235,7 +235,7 @@ lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B) 但不是必要条件,例如以下操作不满足两段锁协议,但是它还是可串行化调度。 ```html -lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)... +lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C) ``` # 四、隔离级别 @@ -318,7 +318,7 @@ InnoDB 的 MVCC 使用到的快照存储在 Undo 日志中,该日志通过回 读取快照中的数据,可以减少加锁所带来的开销。 ```sql -select * from table ....; +select * from table ...; ``` ### 2. 当前读 @@ -428,15 +428,13 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE; 以上学生课程关系中,{Sno, Cname} 为键码,有如下函数依赖: -- Sno, Cname -> Sname, Sdept, Mname - Sno -> Sname, Sdept - Sdept -> Mname -- Sno -> Mname - Sno, Cname-> Grade Grade 完全函数依赖于键码,它没有任何冗余数据,每个学生的每门课都有特定的成绩。 -Sname, Sdept 和 Mname 都函数依赖于 Sno,而部分依赖于键码。当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。 +Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。 **分解后**
diff --git a/notes/正则表达式.md b/notes/正则表达式.md index e21a85c5..38e8b14c 100644 --- a/notes/正则表达式.md +++ b/notes/正则表达式.md @@ -328,7 +328,7 @@ aBCd # 九、前后查找 -前后查找规定了匹配的内容首尾应该匹配的内容,但是又不包含首尾匹配的内容。向前查找用 **?=** 来定义,它规定了尾部匹配的内容,这个匹配的内容在 ?= 之后定义。所谓向前查找,就是规定了一个匹配的内容,然后以这个内容为尾部向前面查找需要匹配的内容。向后匹配用 ?<= 定义。 +前后查找规定了匹配的内容首尾应该匹配的内容,但是又不包含首尾匹配的内容。向前查找用 **?=** 来定义,它规定了尾部匹配的内容,这个匹配的内容在 ?= 之后定义。所谓向前查找,就是规定了一个匹配的内容,然后以这个内容为尾部向前面查找需要匹配的内容。向后匹配用 ?<= 定义(注: javaScript 不支持向后匹配, java 对其支持也不完善)。 **应用** diff --git a/notes/计算机操作系统.md b/notes/计算机操作系统.md index e992bd63..bd907ada 100644 --- a/notes/计算机操作系统.md +++ b/notes/计算机操作系统.md @@ -1,5 +1,5 @@ -* [一、 概述](#一-概述) +* [一、概述](#一概述) * [操作系统基本特征](#操作系统基本特征) * [操作系统基本功能](#操作系统基本功能) * [系统调用](#系统调用) @@ -31,13 +31,13 @@ -# 一、 概述 +# 一、概述 ## 操作系统基本特征 ### 1. 并发 -并发性是指宏观上在一段时间内能同时运行多个程序,而并行性则指同一时刻能运行多个指令。 +并发是指宏观上在一段时间内能同时运行多个程序,而并行则指同一时刻能运行多个指令。 并行需要硬件支持,如多流水线或者多处理器。 @@ -45,7 +45,7 @@ ### 2. 共享 -共享是指系统中的资源可以供多个并发进程共同使用。 +共享是指系统中的资源可以被多个并发进程共同使用。 有两种共享方式:互斥共享和同时共享。 @@ -69,15 +69,17 @@ ### 2. 内存管理 -内存分配、地址映射、内存保护与共享和内存扩充等功能。 +内存分配、地址映射、内存保护与共享、内存扩充等。 ### 3. 文件管理 -文件存储空间的管理、目录管理及文件读写管理和保护等。 +文件存储空间的管理、目录管理、文件读写管理和保护等。 ### 4. 设备管理 -完成用户的 I/O 请求,方便用户使用各种设备,并提高设备的利用率,主要包括缓冲管理、设备分配、设备处理和虛拟设备等功能。 +完成用户的 I/O 请求,方便用户使用各种设备,并提高设备的利用率。 + +主要包括缓冲管理、设备分配、设备处理、虛拟设备等。 ## 系统调用 diff --git a/notes/计算机网络.md b/notes/计算机网络.md index d775d73b..955150e7 100644 --- a/notes/计算机网络.md +++ b/notes/计算机网络.md @@ -61,19 +61,19 @@ 网络把主机连接起来,而互联网是把多种不同的网络连接起来,因此互联网是网络的网络。 -

+

## ISP 互联网服务提供商 ISP 可以从互联网管理机构获得许多 IP 地址,同时拥有通信线路以及路由器等联网设备,个人或机构向 ISP 缴纳一定的费用就可以接入互联网。 -

+

-目前的互联网是一种多层次 ISP 结构,ISP 根据覆盖面积的大小分为主干 ISP、地区 ISP 和本地 ISP。 +目前的互联网是一种多层次 ISP 结构,ISP 根据覆盖面积的大小分为第一层 ISP、区域 ISP 和接入 ISP。 互联网交换点 IXP 允许两个 ISP 直接相连而不用经过第三个 ISP。 -

+

## 主机之间的通信方式 @@ -82,11 +82,11 @@ 2. 对等(P2P):不区分客户和服务器。 -

+

## 电路交换与分组交换 -

+

(以上分别为:电路交换、报文交换以及分组交换) @@ -140,14 +140,7 @@

-### 1. 七层协议 - -如图 a 所示,其中表示层和会话层用途如下: - -1. 表示层:信息的语法、语义以及它们的关联,如加密解密、转换翻译、压缩解压缩; -2. 会话层:不同机器上的用户之间建立及管理会话。 - -### 2. 五层协议 +### 1. 五层协议 1. 应用层:为特定应用程序提供数据传输服务,例如 HTTP、DNS 等。数据单位为报文。 @@ -159,6 +152,15 @@ 5. 物理层:考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。 +### 2. 七层协议 + +其中表示层和会话层用途如下: + +1. 表示层:数据压缩、加密以及数据描述。这使得应用程序不必担心在各台主机中表示/存储的内部格式不同的问题。 +2. 会话层:建立及管理会话。 + +五层协议没有表示层和会话层,而是将这些功能留给应用程序开发者处理。 + ### 3. 数据在各层之间的传递过程 在向下的过程中,需要添加下层协议所需要的首部或者尾部,而在向上的过程中不断拆开首部和尾部。 diff --git a/notes/设计模式.md b/notes/设计模式.md index 93cdd0da..400ede5e 100644 --- a/notes/设计模式.md +++ b/notes/设计模式.md @@ -233,7 +233,7 @@ public class Client { ```java public abstract class Factory { abstract public Product factoryMethod(); - public void doSomethind() { + public void doSomething() { Product product = factoryMethod(); // do something with the product } diff --git a/pics/3b49dd67-2c40-4b81-8ad2-7bbb1fe2fcbd.png b/pics/3b49dd67-2c40-4b81-8ad2-7bbb1fe2fcbd.png new file mode 100644 index 00000000..13f53b6a Binary files /dev/null and b/pics/3b49dd67-2c40-4b81-8ad2-7bbb1fe2fcbd.png differ diff --git a/pics/61942711-45a0-4e11-bbc9-434e31436f33.png b/pics/61942711-45a0-4e11-bbc9-434e31436f33.png new file mode 100644 index 00000000..8f093ef6 Binary files /dev/null and b/pics/61942711-45a0-4e11-bbc9-434e31436f33.png differ diff --git a/pics/a3da4342-078b-43e2-b748-7e71bec50dc4.png b/pics/a3da4342-078b-43e2-b748-7e71bec50dc4.png new file mode 100644 index 00000000..03b37a61 Binary files /dev/null and b/pics/a3da4342-078b-43e2-b748-7e71bec50dc4.png differ