├── .gitignore ├── Javascript实现各种API.md ├── LICENSE ├── README.md ├── SUMMARY.md ├── assets └── pics │ └── main.png ├── javascript重要概念.md ├── leetcode题解.md └── 剑指offer题解.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf 17 | -------------------------------------------------------------------------------- /Javascript实现各种API.md: -------------------------------------------------------------------------------- 1 | # Javascript实现各种API 2 | 3 | * [call](#1-call) 4 | * [bind](#2-bind) 5 | * [new](#3-new) 6 | * [instanceof](#4-instanceof) 7 | * [Object.assign](#5-objectassign) 8 | * [JSON.stringify](#6-jsonstringify) 9 | * [throttle 函数节流](#7-throttle-函数节流) 10 | * [debounce 函数防抖](#8-debounce-函数防抖) 11 | * [Array.isArray](#9-arrayisarray) 12 | 13 | ## 1. call 14 | 15 | 函数作用:改变函数内部 this 指针指向,并运行函数 16 | 17 | 实现思路: 18 | 19 | a. 将要执行的函数设置为对象的 fn 属性 20 | 21 | b. 使用 eval 关键字执行 fn 函数 22 | 23 | c. 执行完毕后删除对象的 fn 属性 24 | 25 | d. 为防止对象本来就具有 fn 属性,先把它原来的 fn 属性保存起来 26 | 27 | ```js 28 | Function.prototype.divCall = function (obj) { 29 | var obj = obj || window 30 | var flag = false, temp 31 | if (obj.hasOwnProperty('fn')){ 32 | flag = true; 33 | temp = obj.fn 34 | } 35 | obj.fn = this 36 | 37 | var args = [] 38 | for (var i = 1; i < arguments.length; i++) 39 | args.push('arguments[' + i + ']') 40 | var result = eval('obj.fn(' + args + ')') 41 | delete obj.fn 42 | if (flag) 43 | obj.fn = temp 44 | return result 45 | } 46 | ``` 47 | 48 | ## 2. bind 49 | 50 | 函数作用:改变函数内部 this 指针指向,并返回改变 this 指针指向后的函数 51 | 52 | 函数作用:改变函数内部 this 指针指向 53 | 54 | 实现思路: 55 | 56 | a. 将函数参数分两部分,一部分在执行 bind 时传入,一部分在执行函数是传入,最后使用 apply 执行函数 57 | 58 | b. 如果 bind 后的函数被当做构造函数,则绑定 this 指针 59 | 60 | c. 让新函数原型链继承原函数 61 | 62 | ```js 63 | Function.prototype.divBind = function (obj) { 64 | var func = this 65 | var args = Array.prototype.slice.call(arguments, 1) 66 | var returnFunc = function() { 67 | args = args.concat(Array.prototype.slice.call(arguments)) 68 | return func.apply(this instanceof returnFunc ? this : obj, args) 69 | } 70 | var Dump = function (){} 71 | Dump.prototype = func.prototype 72 | returnFunc.prototype = new Dump() 73 | return returnFunc 74 | } 75 | ``` 76 | 77 | ## 3. new 78 | 79 | 操作符作用:新建一个对象 80 | 81 | 实现思路: 82 | 83 | a. 新建空对象、让对象的 \_\_proto\_\_ 指向函数的 prototype、执行构造函数、返回该对象 84 | 85 | b. 如果构造函数返回的值是对象或函数,则返回构造函数返回的对象或函数 86 | 87 | ```js 88 | function divNew() { 89 | var obj = new Object() 90 | var Constructor = Array.prototype.shift.call(arguments) 91 | obj.__proto__ = Constructor.prototyep 92 | var ret = Constructor.apply(obj, arguments) 93 | return (typeof ret === 'object' || typeof ret === 'function') ? ret : obj 94 | } 95 | ``` 96 | 97 | ## 4. instanceof 98 | 99 | 操作符作用:判断实例是否属于某个类 100 | 101 | 实现思路: 102 | 103 | a. 基于原型链,沿着原型链寻找,所以迭代条件是 L = L. 104 | 105 | b. 空对象的原型的原型指向 null,所以终止条件是 L === null 106 | 107 | ```js 108 | function instanceOf(L, R) { 109 | R = R.prototype 110 | L = L.__proto__ 111 | while (true){ 112 | if (L === null) 113 | return false 114 | if (R === L) 115 | return true 116 | L = L.__proto__ 117 | } 118 | } 119 | ``` 120 | 121 | ## 5. Object.assign 122 | 123 | 函数作用:浅拷贝一层对象 124 | 125 | 实现思路: 126 | 127 | a. 将第一个参数之后的函数参数拷贝到第一个函数参数对象中 128 | 129 | b. 如果第一个参数不是对象,则将其转化成相应的包装对象 130 | 131 | ```js 132 | function convertObj(obj) { 133 | var objType = typeof obj 134 | if (obj === undefined || obj === null) { 135 | throw new Error('Can\'t convert') 136 | } else if (objType === 'object') { 137 | return obj 138 | } else if (objType === 'string') { 139 | return new String(obj) 140 | } else if (objType === 'number') { 141 | return new Number(obj) 142 | } else if (objType === 'boolean') { 143 | return new Boolean(obj) 144 | } else { 145 | return new Object(obj) 146 | } 147 | } 148 | Object.myAssign = function () { 149 | var obj = convertObj(arguments[0]) 150 | for (var i = 1; i < arguments.length; i++) { 151 | for (var key in arguments[i]) { 152 | if (arguments[i].hasOwnProperty(key)) { 153 | obj[key] = arguments[i][key] 154 | } 155 | } 156 | } 157 | return obj 158 | } 159 | ``` 160 | 161 | ## 6. JSON.stringify 162 | 163 | 函数作用:将对象转换成一个 json 格式的字符串保存起来 164 | 165 | 实现思路: 166 | 167 | a. 因为对象中可能还包括子对象,所以递归地 stringify 对象 168 | 169 | b. 根据对象是否是数组,分情况格式化 170 | 171 | c. 为满足边界条件,根据变量类型返回相应的值 172 | 173 | ```js 174 | function jsonStringify(obj) { 175 | if (obj === undefined || typeof obj === 'symbol') { 176 | return undefined 177 | } else if (typeof obj === 'string') { 178 | return '\"' + obj + '\"' 179 | } else if (typeof obj !== 'object' || obj === null) { 180 | return obj + '' 181 | } else { 182 | var isObjArray = Array.isArray(obj) 183 | var res = '' 184 | var func = arguments.callee 185 | if (isObjArray) { 186 | res += '[' 187 | obj.forEach(function (item) { 188 | var shouldBeNull = (item === undefined || item === null || typeof item === 'symbol') 189 | res += (shouldBeNull ? 'null' : func(item)) + ',' 190 | }) 191 | if (res[res.length - 1] === ',') 192 | res = res.substring(0, res.length - 1) 193 | res += ']' 194 | } else { 195 | res += '{' 196 | for (var key in obj) { 197 | if (typeof obj[key] !== 'symbol' && obj[key] !== undefined) 198 | res += '\"' + key + '\":' + func(obj[key]) + ',' 199 | } 200 | if (res[res.length - 1] === ',') 201 | res = res.substring(0, res.length - 1) 202 | res += '}' 203 | } 204 | return res 205 | } 206 | } 207 | ``` 208 | 209 | ## 7. throttle 函数节流 210 | 211 | 函数作用:让某个函数在每个规定时间间隔内只会被触发一次 212 | 213 | 实现思路: 214 | 215 | a. 设置定时器,如果在定时器时间范围内触发事件, 216 | 217 | b. 为解决 setTimeout 执行过程中 this 指向问题和函数参数传递问题,使用 apply 函数绑定 this 指针 218 | 219 | ```js 220 | function throttle(func, wait){ 221 | var timeout 222 | return function(){ 223 | var that = this 224 | var args = arguments 225 | if (!timeout) { 226 | timeout = setTimeout(function (){ 227 | timeout = null; 228 | func.apply(that, args) 229 | }, wait) 230 | } 231 | } 232 | } 233 | ``` 234 | 235 | ## 8. debounce 函数防抖 236 | 237 | 函数作用:让某个函数在事件触发 n 秒后才执行,如果一个事件触发的 n 秒内又触发了这个事件,那就以新的事件的触发时间为准 238 | 239 | 实现思路: 240 | 241 | a. 设置定时器,如果在定时器时间范围内触发事件,则先清除定时器再重新设置定时器 242 | 243 | b. 为解决 setTimeout 执行过程中 this 指向问题和函数参数传递问题,使用 apply 函数绑定 this 指针 244 | 245 | ```js 246 | function debounce (func, wait){ 247 | var timeout 248 | return function (){ 249 | var args = arguments 250 | var that = this 251 | clearTimeout(timeout) 252 | timeout = setTimeout(function(){ 253 | func.apply(that, args) 254 | }, wait) 255 | } 256 | } 257 | ``` 258 | 259 | ## 9. Array.isArray 260 | 261 | 函数作用:判断变量是否为数组 262 | 263 | 实现思路: 264 | 265 | 使用 Object 的 toString 方法,对于任何数组调用该方法都会返回 '\[object Array\]',而其他基本类型和普通对象类型则会返回其他字符串 266 | 267 | ```js 268 | Array.prototype.divIsArray(){ 269 | return Object.prototype.toString.call(arr) === '[object Array]' 270 | } 271 | ``` 272 | 273 | 274 | 275 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Neal Sean 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | #### FRONTEND KNOWLEDGE 6 | 7 | [![GitHub license](https://img.shields.io/github/license/CoderNie/frontend_knowledge.svg)](https://github.com/CoderNie/frontend_knowledge/blob/master/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/CoderNie/frontend_knowledge.svg)](https://github.com/CoderNie/frontend_knowledge/stargazers) 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | #### Javascript 20 | 21 | * [Javascript重要概念](javascript重要概念.md) 22 | 23 | 解释原型链,闭包,事件循环等重要概念 24 | 25 | * [Javascript实现各种API](Javascript实现各种API.md) 26 | 27 | 实现bind,new,JSON.stringify,节流,防抖等API 28 | 29 | 30 | #### 算法 31 | 32 | * [剑指offer 题解](剑指offer题解.md) 33 | 34 | 目录根据原书第二版进行编排,使用 javascript 编写代码。 35 | 36 | * [Leetcode 题解](leetcode题解.md) 37 | 38 | Leetcode 1-200,使用 c++ 编写代码。 39 | 40 | 41 | #### React(待更新) 42 | 43 | * [React重要概念](待更新) 44 | 45 | 详解 DOM diff、生命周期、异步setState 等重要概念 46 | 47 | * [React16](待更新) 48 | 49 | 详解 React16 新特性 50 | 51 | 52 | * [Redux](待更新) 53 | 54 | 55 | * [MobX](待更新) 56 | 57 | 58 | 59 | #### Vue(待更新) 60 | 61 | 62 | #### nodejs 63 | 64 | * [Typescript](待更新) 65 | 66 | * [nestjs](待更新) 67 | 68 | 69 | 70 | #### 前端工具 71 | 72 | * [webpack4](待更新) 73 | 74 | #### 后记 75 | 76 | ##### 关于 77 | 78 | 作者根据前端、计算机方面的书籍以及相关官方技术文档总结学习笔记,希望对大家有所帮助。 79 | 当您引用本仓库内容或者对内容进行修改演绎时,请遵循文末的开源协议,谢谢。 80 | 81 | ##### License 82 | 83 | MIT -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [Javascript实现各种原生API](Javascript实现各种API.md) 5 | * [Javascript重要概念](javascript重要概念.md) 6 | * [Leetcode 题解](leetcode题解.md) 7 | * [剑指offer题解](剑指offer题解.md) 8 | 9 | -------------------------------------------------------------------------------- /assets/pics/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeboy0127/frontendTutorial/90cb8199d6eaf9183a100f7ed3d59bae350bb1aa/assets/pics/main.png -------------------------------------------------------------------------------- /javascript重要概念.md: -------------------------------------------------------------------------------- 1 | # Javascript重要概念 2 | 3 | * [类](#1-类) 4 | * [原型链](#2-原型链) 5 | * [继承](#3-继承) 6 | * [作用域链](#4-作用域链) 7 | * [闭包](#5-闭包) 8 | * [异步机制](#6-异步机制) 9 | 10 | ## 1. 类 11 | 12 | > 和其他面向对象的语言(如Java)不同,Javascript语言对类的实现和继承的实现没有标准的定义,而是将这些交给了程序员,让程序员更加灵活地(当然刚开始也更加头疼)去定义类,实现继承。(以下不讨论ES6中利用class、extends关键字来实现类和继承;实质上,ES6中的class、extends关键字是利用语法糖实现的) 13 | 14 | #### 我对类的理解 15 | 16 | 首先,我先说说我对类的理解:类是包含了一系列【属性/方法】的集合,可以通过类的构造函数创建一个实例对象(例如人类是一个类,而每一个人就是一个实例对象),而这个实例对象中会包含两方面内容: 17 | 18 | **a. 类的属性** 19 | 20 | 属性就是每一个实例所特有的,属于个性。(例如每个人的名字都不相同) 21 | 22 | **b. 类的方法** 23 | 24 | 方法就是每一个实例所共享的,属于共性。(例如每个人都要吃饭) 25 | 26 | #### Javascript对类的实现 27 | 28 | **a.利用函数创建类,利用new关键字生成实例对象**(话不多说,先上代码,以下没有特别说明的话,我都会先上代码,然后进行解释说明) 29 | 30 | ```js 31 | function Human() { 32 | console.log('create human here') 33 | } 34 | var fakeperson = Human() // undefined 35 | var person = new Human() // {} 36 | ``` 37 | 38 | 这里Human既是一个普通函数,也是一个类的构造函数,当调用Human\(\)的时候,它作为一个普通函数会被执行,会输出create human here,但是没有返回值(即返回undefined);而当调用new Human\(\)时,也会输出create human here并且返回一个对象。因为我们用Human这个函数来构造对象,所以我们也把Human称作构造函数。**所以通过定义构造函数,就相当于定义了一个类,通过new关键字,即可生成一个实例化的对象。** 39 | 40 | **b.利用构造函数实现类的属性** 41 | 42 | ```js 43 | function Human(name) { 44 | this.name = name 45 | } 46 | var person_1 = new Human('Jack') 47 | var person_2 = new Human('Rose') 48 | console.log(person_1.name) // Jack 49 | console.log(person_2.name) // Rose 50 | ``` 51 | 52 | 这里的Human构造函数中多了一个参数并且函数体中多了一句this.name = name,这句话的中的this指针指向new关键字返回的实例化对象,所以根据构造函数参数的不同,其生成的对象中的具有的属性name的值也会不同。而这里的name就是这个类的属性 53 | 54 | **c.利用prototype实现类的方法** 55 | 56 | 这里因为要用到原型链的知识,所以放到原型链后面说。 57 | 58 | ## 2. 原型链 59 | 60 | #### 类的prototype是什么? 61 | 62 | 在Javascript中,每当我们定义一个构造函数,Javascript引擎就会自动为这个类中添加一个prototype(也被称作原型) 63 | 64 | #### 对象的\_\_proto\_\_是什么? 65 | 66 | 在Javascript中,每当我们使用new创建一个对象时,Javascript引擎就会自动为这个对象中添加一个\_\_proto\_\_属性,并让其指向其类的prototype 67 | 68 | ```javascript 69 | function Human(name) { 70 | this.name = name 71 | } 72 | console.log(Human.prototype) 73 | var person_test1 = new Human('Test1') 74 | var person_test2 = new Human('Test2') 75 | console.log(person_test1.__proto__) 76 | console.log(person_test2.__proto__) 77 | console.log(Human.prototype === person_test1.__proto__) // true 78 | console.log(Human.prototype === person_test2.__proto__) // true 79 | ``` 80 | 81 | 我们会发现Human.prototype是一个对象,Human类的实例化对象person\_test1、person\_test2下都有一个属性\_\_proto\_\_也是对象,并且它们都等于Human.prototype,我们知道在Javascript中引用类型的相等意味着他们所指向的是同一个对象。所以我们可以得到结论,任何一个实例化对象的\_\_proto\_\_属性都指向其类的prototype。 82 | 83 | #### 对象的\_\_proto\_\_有什么作用? 84 | 85 | ```javascript 86 | var Pproto = { 87 | name:'jack' 88 | } 89 | var person = { 90 | __proto__:Pproto 91 | } 92 | console.log(person.name) // jack 93 | person.name = 'joker' 94 | console.log(person.name) // joker 95 | ``` 96 | 97 | 我们发现最开始我们并没有给person定义name属性,为什么console出来jack呢?这就是Javascript著名的原型链的结果啦。话不多说,先上图: 98 | 99 | 100 | ![proto](https://upload-images.jianshu.io/upload_images/12275140-ef84166de7582907.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "prototype\_fig.png") 101 | 102 | 103 | 当我们访问person.name时,发生了什么呢?首先它会访问person对象本身的属性,如果本身没有定义name属性的话,它会去寻找它的\_\_proto\_\_属性对象,在这个例子中person的\_\_proto\_\_属性对应的是Pproto对象,所以person的\_\_proto\_\_指向了Pproto,然后我们发现Pproto对象是具有name属性的,那么person.name就到此为止,返回了jack,但是如果我们又给person加上了一个自身的属性name呢?这时,再次person.name就不会再寻找\_\_proto\_\_了,因为person本身已经具有了name属性,而且其值为joker,所以这里会返回joker. 104 | 105 | > 我们注意到上图中Pproto的\_\_proto\_\_指向了Object,这是因为每一个通过字面量的方式创建出来的对象它们都默认是Object类的对象,所以它们的\_\_proto\_\_自然指向Object.prototype。 106 | 107 | #### 利用prototype实现类的方法 108 | 109 | ```javascript 110 | function Human(name) { 111 | this.name = name 112 | } 113 | Human.prototype.eat = function () { 114 | console.log('I eat!') 115 | } 116 | var person_1 = new Human('Jack') 117 | var person_2 = new Human('Rose') 118 | person_1.eat() // I eat! 119 | person_2.eat() // I eat! 120 | console.log(person_1.eat === person_2.eat) // true 121 | ``` 122 | 123 | 这里我们在构造函数外多写了一句:Human.prototype.eat = function\(\) {...} 这样以后每个通过Human实例化的对象的\_\_proto\_\_都会指向Human.prototype,并且根据上述原型链知识,我们可以知道只要构造函数中没有定义同名的方法,那么每个对象访问say方法时,访问的其实都是Human.prototype.say方法,这样我们就利用prototype实现了类的方法,所有的对象实现了共有的特性,那就是eat 124 | 125 | ## 3. 继承 126 | 127 | #### 我对继承的理解 128 | 129 | 假如有n(n>=2)个类,他们的一些【属性/方法】不一样,但是也有一些【属性/方法】是相同的,所以我们每次定义它们的时候都要重复的去定义这些相同的【属性/方法】,那样岂不是很烦?所以一些牛逼的程序员想到,能不能像儿子继承父亲的基因一样,让这些类也像“儿子们”一样去“继承”他们的“父亲”(而这里的父亲就是包含他们所具有的相同的【属性/方法】)。这样我们就可以多定义一个类,把它叫做父类,在它的里面包含所有的这些子类所具有的相同的【属性/方法】,然后通过继承的方式,让所有的子类都可以访问这些【属性/方法】,而不用每次都在子类的定义中去定义这些【属性/方法】了。 130 | 131 | #### 原型链实现继承(让子类继承了父类的方法) 132 | 133 | ```javascript 134 | function Father() { 135 | } 136 | Father.prototype.say = function() { 137 | console.log('I am talking...') 138 | } 139 | function Son() { 140 | } 141 | var sonObj_1 = new Son() 142 | console.log(sonObj_1.say) // undefined 143 | 144 | // 原型链实现继承的关键代码 145 | Son.prototype = new Father() 146 | 147 | var sonObj_2 = new Son() 148 | console.log(sonObj_2.say) // function() {...} 149 | ``` 150 | 151 | 看到这句Son.prototype = new Father()你可能有点蒙圈,没关系,我先上个原型链的图,你分分钟就能明白了 152 | 153 | ![jicheng.png](https://upload-images.jianshu.io/upload_images/12275140-f14db41a403a03f9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 154 | 155 | 对着图我们想一想,首先,一开始Son、Father两个类没有什么关系,所以在访问say的时候肯定是undefined,但是当我们使用了Son.prototype = new Father()后,我们知道通过new Son()生成的对象都会有\_\_proto\_\_属性,而这个属性指向Son.prototype,而这里我们又让它等于了一个Father的对象,而Father类又定义了静态方法say,所以这里我们的sonObj_2通过沿着原型链寻找,寻找到了say方法,于是就可以访问到Father类的静态方法say了。这样就实现了子类继承了父类的方法,那么如何让子类继承父类的属性呢? 156 | 157 | #### 构造函数实现继承(让子类继承了父类的属性) 158 | 159 | ```javascript 160 | function Father(name) { 161 | this.name = name 162 | } 163 | function Son() { 164 | Father.apply(this, arguments) 165 | this.sing = function() { 166 | console.log(this.name + ' is singing...') 167 | } 168 | } 169 | var sonObj_1 = new Son('jack') 170 | var sonObj_2 = new Son('rose') 171 | sonObj_1.sing() // jack is singing... 172 | sonObj_2.sing() // rose is singing... 173 | ``` 174 | 175 | 在这个例子中,通过在Son的构造函数中利用apply函数,执行了Father的构造函数,所以每一个Son对象实例化的过程中都会执行Father的构造函数,从而得到name属性,这样,每一个Son实例化的Son对象都会有不同的name属性值,于是就实现了子类继承了父类的属性 176 | 177 | #### 组合方式实现继承(组合 原型链继承 + 构造函数继承) 178 | 179 | 顾名思义,就是结合上述两种方法,然后同时实现对父类的【属性/方法】的继承,代码如下: 180 | 181 | ```javascript 182 | function Father(name) { 183 | this.name = name 184 | } 185 | Father.prototype.sayName = function() { 186 | console.log('My name is ' + this.name) 187 | } 188 | function Son() { 189 | Father.apply(this, arguments) 190 | } 191 | Son.prototype = new Father('father') 192 | var sonObj_1 = new Son('jack') 193 | var sonObj_2 = new Son('rose') 194 | sonObj_1.sayName() // My name is jack 195 | sonObj_2.sayName() // My name is rose 196 | ``` 197 | 198 | 这里子类Son没有一个自己的方法,它的sayName方法继承自父类的静态方法sayName,构造函数中继承了父类的构造函数方法,所以得到了非静态的name属性,因此它的实例对象都可以调用静态方法sayName,但是因为它们各自的name不同,所以打印出来的name的值也不同。看到这里,大家可能认为这已经是一种完美无缺的Javascript的继承方式了,但是还差一丢丢,因为原型链继承不是一种纯粹的继承原型的方式,它有副作用,为什么呢?因为在我们调用Son.prototype = new Father()的时候,不仅仅使Son的原型指向了一个Father的实例对象,而且还让Father的构造函数执行了一遍,这样就会执行this.name = name;所以这个Father对象就不纯粹了,它具有了name属性,并且值为father,那为什么之后我们访问的时候访问不到这个值呢?这又是因为原型链的原因啦,话不多说先上图: 199 | 200 | ![combo.png](https://upload-images.jianshu.io/upload_images/12275140-6a06ff573861a090.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 201 | ![combo1.png](https://upload-images.jianshu.io/upload_images/12275140-c2c71b4b9178d33c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 202 | 203 | 所以这里父类的构造函数在进行原型链继承的时候也执行了一次,并且在原型链上生成了一个我们永远也不需要访问的name属性,而这肯定是占内存的(想象一下name不是一个字符串,而是一个对象),那么我们怎么能让原型链继承更纯粹一点呢?让它只继承原型的方法)呢? 204 | 205 | #### 寄生组合方式实现继承 206 | 207 | 为了让原型链继承的更纯粹,这里我们引入一个Super函数,让Father的原型寄生在Super的原型上,然后让Son去继承Super,最后我们把这个过程放到一个闭包内,这样Super就不会污染全局变量啦,话不多说上代码: 208 | 209 | ```javascript 210 | function Father(name) { 211 | this.name = name 212 | } 213 | Father.prototype.sayName = function() { 214 | console.log('My name is ' + this.name) 215 | } 216 | function Son() { 217 | Father.apply(this, arguments) 218 | } 219 | (function () { 220 | function Super(){} 221 | Super.prototype = Father.prototype 222 | Son.prototype = new Super() 223 | }()) 224 | var sonObj_1 = new Son('jack') 225 | ``` 226 | 227 | 这个时候再去打印sonObj1就会发现,它的原型中已经没有name属性啦,如下所示: 228 | 229 | ![jisheng.png](https://upload-images.jianshu.io/upload_images/12275140-4d336c6423c3c184.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 230 | 231 | 232 | ## 4. 作用域链 233 | 234 | 有点类似于原型链(proto chain),Javascript中变量遵从作用域链(scope chain)规则。 235 | 236 | ![scope_chain.png](https://upload-images.jianshu.io/upload_images/12275140-ba9362bf04fe64a3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 237 | 如上图所示,在Javascript中,每一个函数体对应于一个作用域。 238 | 239 | **当访问一个变量时,我们会先访问当前作用域内是否有定义该变量,如果没有就会在该作用域外的作用域内寻找是否有改变量,依此类推,一直寻找到全局变量。如果全局变量中依旧没有定义该变量,就会返回undefined。** 240 | 241 | 我们来看下下面这个例子: 242 | 243 | ```javascript 244 | var milk = '外面的特仑苏' 245 | function wrapper1() { 246 | var milk = '里面的特仑苏' 247 | console.log('我要喝' + milk) //我要喝里面的特仑苏 248 | } 249 | function wrapper2() { 250 | console.log('我要喝' + milk) //我要喝外面的特仑苏 251 | } 252 | wrapper1() 253 | wrapper2() 254 | ``` 255 | 256 | 在上述例子中,我们在*wrapper1*函数体内定义了变量*milk*,因此*wrapper1*在寻找完当前作用域即可以得到**里面的特仑苏**,而在*wrapper2*函数体内没有定义变量*milk*,它会沿着作用域链去寻找全局变量,然后得到了*外面的特仑苏*。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 257 | 258 | 259 | ## 5. 闭包 260 | 261 | #### 什么是闭包 262 | 263 | >**维基百科:**在计算机科学中,**闭包**(Closure),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 264 | 265 | 按照作用域链的规则,我们无法在某函数体外访问到该函数内的局部变量。但是出于某些目的我们想在函数体外访问到函数体内的局部变量,我们该怎么做呢?请看下面例子: 266 | 267 | ```javascript 268 | function wrapper1() { 269 | var milk = '里面的特仑苏' 270 | function drink() { 271 | console.log('我喝了' + milk) 272 | } 273 | return drink 274 | } 275 | var result = wrapper1() 276 | result() //我喝了里面的特仑苏 277 | ``` 278 | 279 | 我们在函数体内再创建一个函数,并且把这个内部函数*drink*作为外部函数*wrapper1*的返回值。我们通过执行函数*wrapper1*获得了它的返回值*drink*,并且执行它,就成功的访问到了它的内部变量*milk*(里面的特仑苏)。 280 | 回想维基百科中闭包的定义,再结合上述例子:*drink*函数就是一个闭包,因为它引用了处于它外部的变量*milk*。这个被引用的外部变量*milk*和函数*drink*一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 281 | 282 | #### 闭包的用途 283 | 284 | 从上述例子中不难看出,闭包的一个用途就是可以访问函数的内部变量。从而可以实现一些面向对象的功能,例如设置类的隐私变量。闭包的另一个用途就是可以使变量一直保存在内存之中,不被垃圾回收机制所回收。看下面这个例子: 285 | 286 | ```javascript 287 | var change; 288 | function wrapper() { 289 | var milk = '特仑苏' 290 | function drink() { 291 | console.log('我喝了' + milk) 292 | } 293 | change = function () { 294 | milk = 'AD钙奶' 295 | } 296 | return drink 297 | } 298 | var result = wrapper() 299 | result() //我喝了特仑苏 300 | change() 301 | result() //我喝了AD钙奶 302 | ``` 303 | 304 | 可以看到,*wrapper*执行之后,*milk*变量一直能被访问到,原因就是*result*引用了*wrapper*内部的*drink*函数,*drink*函数又引用了milk变量,因此它一直不会被垃圾回收机制所回收。 305 | 306 | #### 慎用闭包 307 | 因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则可能会造成内存泄露。解决方法是,在使用完闭包函数之后,将变量设置为undefined。比如在上例中,在使用完result之后,将result设置为null或者undefined。 308 | 309 | 310 | ## 6. 异步机制 311 | 312 | >Javascript作为一种单线程语言,是如何实现异步编程的呢? 313 | 314 | 相信不少人对Javascript单线程表示怀疑:为何单线程可以实现异步操作呢?其实Javascript确实是单线程的(我们不妨把这个线程称作主线程),但它实现异步操作的方式确实借助了浏览器的其他线程的帮助。那其他线程是怎么帮助Javascript主线程来实现异步的呢?答案就是任务队列(task queue)和事件循环(event loop)。 315 | 316 | #### 任务队列 317 | 318 | 首先,作为单线程语言,在Javascript中定义的任务都会在主线程中执行。但是并不是每个任务都会立刻执行,而这种不立刻执行的任务我们称作异步任务。相反,那些立刻执行的任务我们把它们称作同步任务。而这些异步任务都会交给浏览器的其他线程去执行,但是主线程需要了解这些异步任务执行的状态,才方便进行下一步操作。 319 | 320 | >打个比方,主线程准备做饭,所以下达一个异步任务去买菜,异步任务买完菜之后得告诉主线程:“我买完菜啦”,这个时候主线程才好开始做饭。 321 | 322 | 而我们知道因为Javascript是单线程,所以上述的“下一步操作”没法直接定义在主函数里(不然就被当做同步任务直接执行了),那这些应该定义在哪里呢?答案就是**异步任务的回调函数中**。在Javascript异步机制中,任务队列就是用来维护异步任务回调函数的队列。这样一个队列用来存放这些回调函数,它们会等到主线程执行完所有的同步函数之后按照先进先出的方式挨个执行。那么执行完任务队列之后呢?Javascript主线程就执行完毕了吗?当然不是,不然网页加载完毕之后,谁来处理后续与用户的交互事件(比如点击事件)呢? 323 | 324 | #### 事件循环 325 | 326 | ![javascript_asyc.jpg](https://upload-images.jianshu.io/upload_images/12275140-2962d9dbb54f1cc6.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 327 | 328 | 我们通过上图来更加形象的了解Javascript的异步机制。 329 | 执行同步任务 -> 检查任务队列中是否有任务 -> [有如果则执行] -> 检查任务队列中是否有任务 -> [有如果则执行] -> ...... 330 | 可见主线程在执行完同步任务之后,会无限循环地去检查任务队列中是否有新的“任务”,如果有则执行。而这些任务包括我们在异步任务中定义的回调函数,也包括用户交互事件的回调函数。通过事件循环,Javascript不仅很好的处理了异步任务,也很好的完成了与用户交互事件的处理。因为在完成异步任务的回调函数之后,任务队列中的任务都是由事件所产生的,因此我们也把上述的循环过程叫做**事件循环**。 331 | 332 | #### 异步机制实践 333 | 334 | ``` 335 | console.log('定时器去买菜吧') 336 | setTimeout(function(){ 337 | console.log('菜买完了,主线程去做菜吧') 338 | }, 0) 339 | console.log('你先去买菜,我先看个世界杯') 340 | ``` 341 | 在浏览器中执行上述代码,兴许能更好地理解Javascript的异步机制。 342 | 343 | #### 宏任务与微任务 344 | 345 | 宏任务:script、setTimeout、setInterval、setImmediate、I/O、UI rendering 346 | 微任务:Promise(原生)、process.nextTick 347 | 348 | ##### 执行顺序: 349 | 350 | 事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。 351 | 352 | ##### 观察者优先级: 353 | 354 | idle观察者 > I/O观察者 > check观察者。 355 | idle观察者:process.nextTick 356 | I/O观察者:一般性的I/O回调,如网络,文件,数据库I/O等 357 | check观察者:setImmediate,setTimeout 358 | ``` 359 | setTimeout(() => { 360 | console.log('timeout') 361 | }) 362 | new Promise(function(resolve) { 363 | console.log('promise') 364 | resolve() 365 | }).then(() => { 366 | console.log('then') 367 | }) 368 | console.log('main') 369 | ``` 370 | 执行顺序是: 371 | promise、main、then、timeout 372 | 373 | #### 总结 374 | 375 | 总而言之,Javascript单线程的背后有浏览器的其他线程为其完成异步服务,这些异步任务为了和主线程通信,通过将回调函数推入到任务队列等待执行。主线程所做的就是执行完同步任务后,通过事件循环,不断地检查并执行任务队列中回调函数。 376 | 377 | 378 | 379 | 380 | -------------------------------------------------------------------------------- /leetcode题解.md: -------------------------------------------------------------------------------- 1 | * [1. Two Sum](#1-two-sum) 2 | * [2. Add Two Numbers](#2-add-two-numbers) 3 | * [3. Longest Substring Without Repeating Characters](#3-longest-substring-without-repeating-characters) 4 | * [4. Median of Two Sorted Arrays](#4-median-of-two-sorted-arrays) 5 | * [5. Longest Palindromic Substring](#5-longest-palindromic-substring) 6 | * [6. ZigZag Conversion](#6-zigzag-conversion) 7 | * [7. Reverse Integer](#7-reverse-integer) 8 | * [8. String to Integer \(atoi\)](#8-string-to-integer-atoi) 9 | * [9. Palindrome Number](#9-palindrome-number) 10 | * [10. Regular Expression Matching](#10-regular-expression-matching) 11 | * [11. Container With Most Water](#11-container-with-most-water) 12 | * [12. Integer To Roman](#12-integer-to-roman) 13 | * [13. Roman To Integer](#13-roman-to-integer) 14 | * [14. Longest Common Prefix](#14-longest-common-prefix) 15 | * [15. 3Sum](#15-3sum) 16 | * [16. 3Sum Closest](#16-3sum-closest) 17 | * [17. Letter Combinations of a Phone Number](#17-letter-combinations-of-a-phone-number) 18 | * [18. 4Sum](#18-4sum) 19 | * [19. Remove Nth Node From End of List](#19-remove-nth-node-from-end-of-list) 20 | * [20. Valid Parentheses](#20-valid-parentheses) 21 | * [21. Merge Two Sorted Lists](#21-merge-two-sorted-lists) 22 | * [22. Generate Parentheses](#22-generate-parentheses) 23 | * [23. Merge k Sorted Lists](#23-merge-k-sorted-lists) 24 | * [24. Swap Nodes in Pairs](#24-swap-nodes-in-pairs) 25 | * [25. Reverse Nodes in k-Group](#25-reverse-nodes-in-k-group) 26 | * [26. Remove Duplicates from Sorted Array](#26-remove-duplicates-from-sorted-array) 27 | * [27. Remove Element](#27-remove-element) 28 | * [28. Implement strStr\(\)](#28-implement-strstr) 29 | * [29. Devide Two Integers](#29-devide-two-integers) 30 | * [30. Substring with Concatenation of All Words](#30-substring-with-concatenation-of-all-words) 31 | 32 | ### 33 | 34 | ### 1. Two Sum 35 | 36 | #### 解题思路: 37 | 38 | #### 暴力解法O\(N^2\): 39 | 40 | 嵌套两层循环:第一层:i 从 0 到 n - 2;第二层:j 从 i + 1 到 n - 1;判断 nums\[i\] + nums\[j\] == target ,如果成立则是正确答案 41 | 42 | #### map解法O\(N\*logN\): 43 | 44 | 从 0 到 n - 1 依次遍历,利用map存放**每一个数值的下标**,在map中寻找是否有使(nums\[i\] + x == target)成立的x的存在,如果存在则返回i和它的下标(即myMap\[ target - nums\[i\] \]\)。 45 | 46 | 复杂度分析:因为只遍历了一次数组,map每次的查询的时间复杂度为O\(logN\)所以整体复杂度为O\(N\*logN\)、如果这里使用hash\_map可以将查询复杂度降低到O\(1\),从而使得整体复杂度为O\(N\),但是hash\_map不是标准的C++库,所以这里没有使用。 47 | 48 | 实现代码: 49 | 50 | ```javascript 51 | // 1. Two Sum 52 | vector twoSum(vector& nums, int target) { 53 | map myMap; 54 | vector result; 55 | for (int i = 0; i < nums.size(); i++) { 56 | if (myMap.find(target - nums[i]) == myMap.end()) { 57 | myMap[nums[i]] = i; 58 | } else { 59 | result = {myMap[target - nums[i]], i}; 60 | break; 61 | } 62 | } 63 | return result; 64 | } 65 | ``` 66 | 67 | ### 2. Add Two Numbers 68 | 69 | #### 解题思路: 70 | 71 | 从左到右遍历链表,依次相加,每一个位置生成一个新的结点即可。 72 | 73 | 时间复杂度:O\( max\( len\(l1\), len\(l2\) \) \) 74 | 75 | #### 考虑边界条件: 76 | 77 | 1.进位的的处理:carry表示进位,当最后一位还有进位时,即使 l1 和 l2 均为NULL的情况下,还需要生成一个新的结点,所以while的条件中加入了 carry != 0 判断项。 78 | 79 | 2.返回头结点:当头结点为NULL的时候记录头结点,并且让p等于头结点;后续情况让 p->next 等于新的结点,并让 p 指向 p->next。 80 | 81 | 实现代码: 82 | 83 | ```javascript 84 | // 2. Add Two Numbers 85 | ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { 86 | ListNode *head = NULL, *p; 87 | int carry = 0, sum; 88 | while (l1 != NULL || l2 != NULL || carry != 0) { 89 | sum = 0; 90 | if (l1 != NULL) { 91 | sum += l1->val; 92 | l1 = l1->next; 93 | } 94 | if (l2 != NULL) { 95 | sum += l2->val; 96 | l2 = l2->next; 97 | } 98 | sum += carry; 99 | carry = sum / 10; 100 | sum %= 10; 101 | ListNode *newNode = new ListNode(sum); 102 | if (head == NULL) { 103 | head = newNode; 104 | p = newNode; 105 | } else { 106 | p->next = newNode; 107 | p = p->next; 108 | } 109 | } 110 | return head; 111 | } 112 | ``` 113 | 114 | ## 3. Longest Substring Without Repeating Characters 115 | 116 | #### 解题思路: 117 | 118 | #### 暴力解法 O\(N^3\): 119 | 120 | 从每个点遍历、依次遍历这个点后的每一个点、通过遍历该点之前的点判断该点是否出现过。听上去有点拗口,代码在下方,这个暴力方法Leetcode也可以AC,但是不推荐使用。 121 | 122 | #### 头尾标记法 O\(N\*logN\): 123 | 124 | 头标记指向当前最长无重复字符串的头部,尾标记指向其尾部。**通过一个 map 来记录出现过的字符最后出现的位置。** 125 | 126 | 依次遍历数组,如果当前字符已出现过,则让头标记指向其最后出现过的位置的后一个位置。然后每次通过头、尾标记计算当前无重复字符串的长度,并与已知最大值作比较。这里查询map的复杂度为 O\(logN\),遍历的复杂度为 O\(N\),因此整体复杂度为 O\(N\*logN\)。如果这里使用hash\_map可以将查询复杂度降低到O\(1\),从而使得整体复杂度为O\(N\),但是hash\_map不是标准的C++库,所以这里没有使用。 127 | 128 | 实现代码: 129 | 130 | ```javascript 131 | // 3. Longest Substring Without Repeating Characters 132 | // 暴力解法 133 | int lengthOfLongestSubstring_bruteForce(string s) { 134 | int res = 0, sum; 135 | for (int i = s.size() - 1; i >= 0; i--) { 136 | sum = 1; 137 | for (int j = i - 1; j >= 0; j--) { 138 | bool flag = true; 139 | for (int k = i; k > j; k--) { 140 | if (s[j] == s[k]) { 141 | flag = false; 142 | break; 143 | } 144 | } 145 | if (flag) { 146 | sum++; 147 | } else { 148 | break; 149 | } 150 | } 151 | res = max(res, sum); 152 | } 153 | return res; 154 | } 155 | // 头尾标记法 156 | int lengthOfLongestSubstring(string s) { 157 | map myMap; 158 | int res = 0; 159 | for (int i = 0, j = 0; j < s.size(); j++){ 160 | if (myMap.find(s[j]) != myMap.end()) { 161 | i = max(i, myMap[s[j]] + 1); 162 | } 163 | myMap[s[j]] = j; 164 | res = max(res, j - i + 1); 165 | } 166 | return res; 167 | } 168 | ``` 169 | 170 | ## 4. Median of Two Sorted Arrays 171 | 172 | #### 解题思路: 173 | 174 | 这道题咋一看像二分查找,但是仔细看题,发现有两个有序数组,而且不是让我们找一个特定的数,而是要找两个数组合并后的中位数,这样一看就比较难了,也难怪归类为hard类别。这道题除了一下介绍的二分查找法,还有两个数组分别进行二分查找的方法,不过代码量相对更多\(也可能是因为笔者水平不够导致代码量过大\),而且看了下面的二分查找法后,感叹于该算法作者的脑洞,所以在这里只介绍该种方法。 175 | 176 | #### 暴力方法 O\(\(m + n\)\*log\(m + n\)\): 177 | 178 | 将两个数组合并,然后进行快排,中间的数即中位数。由于题目说了复杂度不能超过O\(log\(m + n\)\),所以这个方法当然回Time Limit Excess,所以我们得探究一种更高效的解法。 179 | 180 | #### 二分查找法 O\(log\(min\(m, n\)\)\): 181 | 182 | 首先分别把两个数组分成两边,大概为下面这种形式:\(A表示nums1, B表示nums2\) 183 | 184 | ``` 185 | left_part | right_part 186 | A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1] 187 | B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1] 188 | ``` 189 | 190 | 因为有序数列的性质,我们知道只要我们满足以下两个条件,我们就可以找到中位数了: 191 | 192 | > **条件一:len\(A\_left\) + len\(B\_left\) = len\(A\_right\) + len\(B\_right\)** 193 | > 194 | > **条件二:max\[A\_left, B\_left\] <= min\[A\_right, B\_right\]** 195 | 196 | 为了使问题简单化,我们先只考虑 m + n 为偶数的情况下,只要满足上述两个条件,我们的中位数就等于左边的最大值加上右边的最小值除以二。 197 | 198 | 为了满足条件一,我们只要令** i + j == m + n - i - j \(+ 1\)**即可(这里加一是为了之后考虑 m + n 为奇数的情况) 199 | 200 | 而为了满足条件二,根据有序数组的性质,我们知道只需要满足 **A\[i - 1\] <= B\[j\] 且 B\[j - 1\] <= A\[i\] **即可。 201 | 202 | 接下来开始我们的算法探究: 203 | 204 | 假设我们首先随机选择一个 i \(这里 0 <= i < m\),所以我们根据条件一,可以求得 j = \(m + n + 1\) / 2 - i; 205 | 206 | 为了满足条件二,我们开始分别比较 A\[i - 1\] 与 B\[j\] 和 B\[j - 1\] 与 A\[i\]: 207 | 208 | 不难知道可能会有四种情况: 209 | 210 | * A\[i - 1\] <= B\[j\] 且 B\[j - 1\] <= A\[i\] :这不正是我们要找的 i 吗?可以直接返回答案 211 | * A\[i - 1\] > B\[j\] 且 B\[j - 1\] > A\[i\] :根据有序数列的性质,再利用反证法不难证明这种情况不可能存在 212 | * A\[i - 1\] <= B\[j\] 且 B\[j - 1\] > A\[i\]:为了使情况更加接近我们的答案,也就是情况1。也就是要使 B\[j - 1\] <= A\[i\],因为现在 B\[j - 1\] > A\[i\],所以我们要想办法缩小 B\[j - 1\],扩大A\[i\],所以当然是让我们的 i 增大,否则情况会越来越远离我们的正确答案。 213 | * A\[i - 1\] > B\[j\] 且 B\[j - 1\] <= A\[i\]:与情况3恰恰相反,这里我们应该缩小我们的 i。 214 | 215 | 那我们如何缩小和扩大我们的 i 呢,那就是采用二分查找的方式啦,首先将 i 置为数组A的中间下标,如果需要增大,则把其设为上半区的中间下标,反之则设为下半区的中间下标,所以这种搜索方式的时间复杂度和二分查找的时间复杂度一样,为了使时间复杂度尽量的小,我们使A成为长度更小的那个数组,如果初始A比B长,我们则交换它们的位置。 216 | 217 | #### 考虑边界条件: 218 | 219 | 1.如果不存在满足条件二的情况会怎么样呢?也就是 i 走到了数组A的尽头,依旧没法满足条件二,这个时候我们不难知道如果 i 走到数组A的最左端,那么它一定是在不断地经历情况4,而这时 A\[0\] > B\[j\],那么我们不难知道这时left\_part的最大值就是B\[j - 1\];反之我们也可以推出 i 到了A的最右端、j 到了B的最左端或者最右端的情况。 220 | 221 | 2.m + n 为奇数。这个时候我们不难推出 left\_part 的最大值就是中位数。 222 | 223 | 3.A或B为空数组,因为当数组空的时候无法对数组进行下标访问,所以我们在进行二分查找前就应该对该情况进行特殊处理,处理方式也是很简单的啦。 224 | 225 | 实现代码: 226 | 227 | ```javascript 228 | // 4. Median of Two Sorted Arrays 229 | double findMedianSortedArrays(vector& nums1, vector& nums2) { 230 | // 使得 nums1 短于 nums2 231 | int m = nums1.size(); 232 | int n = nums2.size(); 233 | if (m > n) { 234 | vector temp = nums1; 235 | nums1 = nums2; 236 | nums2 = temp; 237 | m = m + n; 238 | n = m - n; 239 | m = m - n; 240 | } 241 | // 考虑数组长度为0的边界情况 242 | if (m == 0) { 243 | if (n == 0) { 244 | return 0; 245 | } else { 246 | if (n % 2 == 1) { 247 | return nums2[n / 2]; 248 | } else { 249 | return (double)(nums2[n / 2] + nums2[n / 2 - 1]) / 2; 250 | } 251 | } 252 | } 253 | int iMin = 0, iMax = m, sizeSum = (m + n + 1) / 2, i, j; 254 | while (iMin <= iMax) { 255 | i = (iMax + iMin) / 2; 256 | j = sizeSum - i; 257 | if (nums2[j - 1] > nums1[i] && i < iMax) { 258 | iMin = i + 1; 259 | } else if (nums1[i - 1] > nums2[j] && i > iMin) { 260 | iMax = i - 1; 261 | } else { 262 | int maxLeft, minRight; 263 | if (i == 0) { 264 | maxLeft = nums2[j - 1]; 265 | } else if (j == 0) { 266 | maxLeft = nums1[i - 1]; 267 | } else { 268 | maxLeft = max(nums1[i - 1], nums2[j - 1]); 269 | } 270 | if ((m + n) % 2 == 1) { 271 | return maxLeft; 272 | } 273 | if (i == m) { 274 | minRight = nums2[j]; 275 | } else if (j == n) { 276 | minRight = nums1[i]; 277 | } else { 278 | minRight = min(nums1[i], nums2[j]); 279 | } 280 | return (double)(maxLeft + minRight) / 2; 281 | } 282 | } 283 | return 0; 284 | } 285 | ``` 286 | 287 | ## 5. Longest Palindromic Substring 288 | 289 | #### 解题思路: 290 | 291 | ##### 暴力解法 O\(N^3\): 292 | 293 | 字符串有 n\(n-1\)/2 个子串,对每个子串进行检测,看其是否是回文子串。因此复杂度为 O\(N^3\)。 294 | 295 | ##### 从字符串中间开始扩展 O\(N^2\): 296 | 297 | 把字符串的每个点分别当成回文子串的中间点,开始往两端扩展,不断检测,直到两端不相等为止。因此复杂度为 O\(N^2\)。 298 | 299 | ##### 动态规划 O\(N^2\): 300 | 301 | 用 dp\[i\]\[j\] 表示下标为 i 开头 j 结尾的子串是否是回文子串。 302 | 303 | 转移方程:dp\[i\]\[j\] = \(dp\[i + 1\]\[j - 1\] && s\[i\] == s\[j\]\) 【含义:当且仅当子串首尾两端相等,且去除首尾两端依旧是回文串时,该子串才会是回文串】 304 | 305 | 初始条件:对于每个长度为1的子串 dp\[i\]\[i\] 都为回文串;对于每个长度为2的子串 dp\[i\]\[i + 1\],当其首尾两端相等时,其为回文串,否则不是。 306 | 307 | 实现代码: 308 | 309 | ```javascript 310 | // 5. Longest Palindromic Substring (动态规划) 311 | string longestPalindrome(string s) { 312 | int length = s.size(); 313 | if (length == 0) return s; 314 | int resI = 0, resJ = 0; 315 | bool dp[length + 1][length + 1]; 316 | for (int i = 0; i <= length; i++) 317 | dp[i][i] = true; 318 | for (int i = 0; i < length; i++) { 319 | if (s[i] == s[i + 1]) { 320 | dp[i][i + 1] = true; 321 | if (resJ - resI < 1) { 322 | resI = i; 323 | resJ = i + 1; 324 | } 325 | } else { 326 | dp[i][i + 1] = false; 327 | } 328 | } 329 | for (int gap = 2; gap < length; gap++) { 330 | for (int i = 0; i + gap < length; i++) { 331 | int j = i + gap; 332 | if (s[i] == s[j] && dp[i + 1][j - 1]) { 333 | dp[i][j] = true; 334 | if (resJ - resI < j - i) { 335 | resI = i; 336 | resJ = j; 337 | } 338 | } else { 339 | dp[i][j] = false; 340 | } 341 | } 342 | } 343 | return s.substr(resI, resJ - resI + 1); 344 | } 345 | ``` 346 | 347 | ## 6. ZigZag Conversion 348 | 349 | #### 解题思路: 350 | 351 | 这道题倒没有特别的方法,就按照题目意思来模拟 Z 字形即可,用一个字符串数组来存放每一行的字符串,最后进行拼接即可。 352 | 353 | ##### 考虑边界条件: 354 | 355 | 当numRows等于1的时候,因为point无法增加也无法减小,所以没办法共用后面的代码,考虑到numRows等于1的时候,答案就是原字符串,所以这里直接返回s即可。 356 | 357 | 实现代码: 358 | 359 | ```javascript 360 | // 6. ZigZag Conversion 361 | string convert(string s, int numRows) { 362 | if (numRows == 1) return s; 363 | string res; 364 | bool shouldIncrease = true; 365 | string strArr[numRows]; 366 | int point = 0; 367 | for (char c : s) { 368 | strArr[point] += c; 369 | if (point == numRows - 1) { 370 | shouldIncrease = false; 371 | } else if (point == 0) { 372 | shouldIncrease = true; 373 | } 374 | if (shouldIncrease) { 375 | point++; 376 | } else { 377 | point--; 378 | } 379 | } 380 | for (string str: strArr) { 381 | res += str; 382 | } 383 | return res; 384 | } 385 | ``` 386 | 387 | ## 7. Reverse Integer 388 | 389 | #### 解题思路: 390 | 391 | 挨个遍历,不断把末位数赋给新的值即可。 392 | 393 | ##### 考虑边界条件: 394 | 395 | 当结果溢出时返回0,所以为了不让中间值溢出,采用 long 类型来保存结果。 396 | 397 | 实现代码: 398 | 399 | ```javascript 400 | // 7. Reverse Integer 401 | int reverse(int x) { 402 | long result = 0, longX = abs((long)x); 403 | while (longX > 0) { 404 | result = result * 10 + longX % 10; 405 | longX /= 10; 406 | } 407 | result = (x > 0) ? result : -result; 408 | if (result > INT32_MAX || result < INT32_MIN) { 409 | return 0; 410 | } else { 411 | return (int)result; 412 | } 413 | } 414 | ``` 415 | 416 | ## 8. String to Integer \(atoi\) 417 | 418 | #### 解题思路: 419 | 420 | 遍历字符串然后进行分情况讨论:( isInit 表示数字是否已经开始,通过 isInit 的值判断是否为开头,如果为 true 表示不是开头) 421 | 422 | \(1\) 空格:如果为开头空格则continue,否则跳出循环 423 | 424 | \(2\) 正负号:如果为开头正负号则设置isNeg的值,否则跳出循环 425 | 426 | \(3\) 数字:将 isInit 置为true,累加结果 427 | 428 | \(4\) 其他符号:跳出循环 429 | 430 | ##### 考虑边界条件: 431 | 432 | 当结果溢出时根据正负返回 INT32\_MAX 或者 INT32\_MIN,所以为了不让中间值溢出,采用 long 类型来保存结果。 433 | 434 | 实现代码: 435 | 436 | ``` 437 | // 8. String to Integer (atoi) 438 | int myAtoi(string str) { 439 | long result = 0; 440 | bool isInit = false; 441 | bool isNeg = false; 442 | for (char c : str) { 443 | if (c == ' ') { 444 | if (isInit) { 445 | break; 446 | } else { 447 | continue; 448 | } 449 | } else if (c == '-' || c == '+') { 450 | if (!isInit) { 451 | isInit = true; 452 | } else { 453 | break; 454 | } 455 | isNeg = (c == '-'); 456 | } else if (c >= 48 && c <= 57) { 457 | isInit = true; 458 | result = result * 10 + (c - 48); 459 | if (result > INT32_MAX) { 460 | return isNeg ? INT32_MIN : INT32_MAX; 461 | } 462 | } else { 463 | break; 464 | } 465 | } 466 | return (int)(isNeg ? -result : result); 467 | } 468 | ``` 469 | 470 | ## 9. Palindrome Number 471 | 472 | #### 解题思路: 473 | 474 | 利用第七题的代码,将数字反转,判断与原数字是否相等即可,这里考虑到负数全部都不是回文数字,所以直接返回false。 475 | 476 | 实现代码: 477 | 478 | ```javascript 479 | // 9. Palindrome Number 480 | int reverse(int x) { 481 | long result = 0, longX = abs((long)x); 482 | while (longX > 0) { 483 | result = result * 10 + longX % 10; 484 | longX /= 10; 485 | } 486 | result = (x > 0) ? result : -result; 487 | if (result > INT32_MAX || result < INT32_MIN) { 488 | return 0; 489 | } else { 490 | return (int)result; 491 | } 492 | } 493 | bool isPalindrome(int x) { 494 | if (x < 0) { 495 | return false; 496 | } else { 497 | return (x == reverse(x)); 498 | } 499 | } 500 | ``` 501 | 502 | ### 10. Regular Expression Matching 503 | 504 | #### 解题思路: 505 | 506 | ##### 动态规划: 507 | 508 | 用 dp\[i\]\[j\] 表示 s 的前 i 个字符组成的字符串和 p 的 前 j 个字符组成的字符串是否匹配。 509 | 510 | 转移方程: 511 | 512 | 当 p\[j - 1\] == '\*' 时:因为 \* 可以表示匹配零位或者多位,正则匹配这里要做贪心考虑,分三种情况,只要其中一种满足即为true: 513 | 514 | * 匹配零位:则 dp\[i\]\[j\] = dp\[i\]\[j - 2\] 515 | * 匹配一位:则 dp\[i\]\[j\] = dp\[i - 1\]\[j - 2\] && \(满足最后一位匹配\) 516 | * 匹配多位:则一位一位匹配,dp\[i\]\[j\] = dp\[i - 1\]\[j\] && \(满足最后一位匹配\) 517 | 518 | 当 p\[j - 1\] != '\*' 时,dp\[i\]\[j\] 当且仅当 dp\[i - 1\]\[j - 1\]为true时,并且最后一位匹配成功时,才为true。 519 | 520 | 初始状态: 521 | 522 | 显然,当 s 不为空,p 为空的时候dp\[i\]\[j\] = false; 523 | 524 | 其次,当 s 为空,p不为空的时候,考虑到 \* 可以匹配零位,所以利用状态转移方程判断其是否应该为true。 525 | 526 | 实现代码: 527 | 528 | ```javascript 529 | // 10. Regular Expression Matching 530 | bool isMatch(string s, string p) { 531 | int n = s.size(); 532 | int m = p.size(); 533 | // initial 534 | bool dp[n + 1][m + 1]; 535 | for (int i = 0; i < n + 1; i++) { 536 | for (int j = 0; j < m + 1; j++) { 537 | dp[i][j] = false; 538 | } 539 | } 540 | // start 541 | dp[0][0] = true; 542 | for (int i = 1; i < n + 1; i++) { 543 | dp[i][0] = false; 544 | } 545 | for (int j = 1; j < m + 1; j++) { 546 | if (j % 2 == 0) { 547 | dp[0][j] = dp[0][j - 2] && p[j - 1] == '*'; 548 | } else { 549 | dp[0][j] = false; 550 | } 551 | } 552 | // trans 553 | bool compare; 554 | for (int i = 1; i < n + 1; i++) { 555 | for (int j = 1; j < m + 1; j++) { 556 | if (p[j - 1] != '*') { 557 | dp[i][j] = dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '.'); 558 | } else { 559 | compare = (s[i - 1] == p[j - 2] || p[j - 2] == '.'); 560 | dp[i][j] = dp[i][j - 2] || (dp[i - 1][j - 2] && compare) || (dp[i - 1][j] && compare); 561 | } 562 | } 563 | } 564 | return dp[n][m]; 565 | } 566 | ``` 567 | 568 | ## 11. Container With Most Water 569 | 570 | #### 解题思路: 571 | 572 | 这道题可以考虑暴力枚举的方式,复杂度应该是O\(N^2\),嵌套循环即可实现,下面重点讲复杂度只有O\(N\)的**头尾标记法**。 573 | 574 | 考虑到: 575 | 576 | **容器的容量 = 两端中较矮的板子长度(L) \* 两端的距离(d)** 577 | 578 | 在数组两端设置头尾标记,以头尾标记为容器的两端,假设头标记对应的板子长度比较短,那么现在 容器的容量 = 头标记板子 \* 头尾标记的距离。那么以头标记为一端的所有容器的容量必然小于当前容器,因为其他以头标记为一端的容器的**L**一定小于或等于当前容器,而距离**d**一定小于当前容器。所以这些容器都可以无需遍历,因此让头标记向尾部移动一个单位;假设尾标记对应的板子长度比较短,则以此类推,让尾标记向头部移动一个单位,直至头尾标记相遇则停止寻找。 579 | 580 | 实现代码: 581 | 582 | ```javascript 583 | // 11. Container With Most Water 584 | int maxArea(vector& height) { 585 | int res = 0, i = 0, j = height.size() - 1; 586 | while (i != j) { 587 | res = max(res, (j - i) * min(height[i], height[j])); 588 | if (height[i] > height[j]) { 589 | j--; 590 | } else { 591 | i++; 592 | } 593 | } 594 | return res; 595 | } 596 | ``` 597 | 598 | ## 12. Integer To Roman 599 | 600 | #### 解题思路: 601 | 602 | 这道题没有特别多技巧,直接按照转换规则进行转换即可,对每一位进行遍历,注意考虑4和9的特殊情况即可。 603 | 604 | 在查看Leetcode讨论区的时候还看到了一种脑洞大开的“全部映射法”,这里贴上来给大家欣赏一下,也开拓了一下思路,这种方法告诉我们在情况不多的时候,可以考虑一下这种思路,代码更为简洁。 605 | 606 | 实现代码: 607 | 608 | ```javascript 609 | // 12. Integer to Roman 610 | string intToRoman(int num) { 611 | map romanMap; 612 | romanMap[1] = 'I'; 613 | romanMap[5] = 'V'; 614 | romanMap[10] = 'X'; 615 | romanMap[50] = 'L'; 616 | romanMap[100] = 'C'; 617 | romanMap[500] = 'D'; 618 | romanMap[1000] = 'M'; 619 | string res; 620 | for (int i = 3; i >= 0; i--) { 621 | int fold = pow(10, i); 622 | if (num / fold == 9) { 623 | res += romanMap[fold]; 624 | res += romanMap[fold * 10]; 625 | } else if (num / fold == 4) { 626 | res += romanMap[fold]; 627 | res += romanMap[fold * 5]; 628 | } else if (num / fold > 0) { 629 | if (num / fold >= 5) { 630 | res += romanMap[fold * 5]; 631 | num -= 5 * fold; 632 | } 633 | for (int i = 0; i < num / fold; i++) { 634 | res += romanMap[fold]; 635 | } 636 | } 637 | num %= fold; 638 | } 639 | return res; 640 | } 641 | // 脑洞大开的全部映射法 642 | string intToRoman(int num) { 643 | string M[] = {"", "M", "MM", "MMM"}; 644 | string C[] = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"}; 645 | string X[] = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"}; 646 | string I[] = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}; 647 | return M[num/1000] + C[(num%1000)/100] + X[(num%100)/10] + I[num%10]; 648 | } 649 | ``` 650 | 651 | ## 13. Roman To Integer 652 | 653 | #### 解题思路: 654 | 655 | 这道题和上一题一样,直接按照转换规则进行转换即可,对每一位进行遍历,注意考虑4和9的特殊情况即可。 656 | 657 | 实现代码: 658 | 659 | ```javascript 660 | // 13. Roman To Integer 661 | int romanToInt (string s) { 662 | int res = 0; 663 | for (int i = 0; i < s.size(); i++) { 664 | if (s[i] == 'I') { 665 | res += 1; 666 | } else if (s[i] == 'V') { 667 | res += 5; 668 | if (i - 1 >= 0 && s[i - 1] == 'I') { 669 | res -= 2; 670 | } 671 | } else if (s[i] == 'X') { 672 | res += 10; 673 | if (i - 1 >= 0 && s[i - 1] == 'I') { 674 | res -= 2; 675 | } 676 | } else if (s[i] == 'L') { 677 | res += 50; 678 | if (i - 1 >= 0 && s[i - 1] == 'X') { 679 | res -= 20; 680 | } 681 | } else if (s[i] == 'C') { 682 | res += 100; 683 | if (i - 1 >= 0 && s[i - 1] == 'X') { 684 | res -= 20; 685 | } 686 | } else if (s[i] == 'D') { 687 | res += 500; 688 | if (i - 1 >= 0 && s[i - 1] == 'C') { 689 | res -= 200; 690 | } 691 | } else if (s[i] == 'M') { 692 | res += 1000; 693 | if (i - 1 >= 0 && s[i - 1] == 'C') { 694 | res -= 200; 695 | } 696 | } 697 | } 698 | return res; 699 | } 700 | ``` 701 | 702 | ## 14. Longest Common Prefix 703 | 704 | #### 解题思路: 705 | 706 | 这道题从头到尾每一个字符遍历,如果出现不同或者已经不够长,则把已知的共同前缀返回即可。 707 | 708 | **考虑边界条件:** 709 | 当输入字符串数组为空时,返回空字符串""。 710 | 711 | 实现代码: 712 | 713 | ```javascript 714 | // 14. Longest Common Prefix 715 | string longestCommonPrefix(vector& strs) { 716 | if (strs.size() == 0) return ""; 717 | int length = 0; 718 | while (true) { 719 | if (strs[0].size() < length + 1) break; 720 | for (int i = 0; i < strs.size() - 1; i++) 721 | if (strs[i + 1].size() < length + 1 || strs[i][length] != strs[i + 1][length]) 722 | return strs[0].substr(0, length); 723 | length++; 724 | } 725 | return strs[0].substr(0, length); 726 | } 727 | ``` 728 | 729 | ## 15. 3Sum 730 | 731 | #### 解题思路: 732 | 733 | **双指针法**枚举,复杂度O\(N^2\)。 734 | 735 | 首先,对数组进行排序。然后,从第一个数字 i 开始遍历,每一层遍历中有两个指针 p, q 分别指向该数字后续的数组中的头尾两端,通过判断这三个数组的和与0的关系,移动头尾指针: 736 | 737 | **如果和大于0,尾指针前移;如果和小于0,头指针后移;如果和等于0,分别移动头尾指针。** 738 | 739 | 这里注意要考虑到数组中处理出现重复数字的情况。 740 | 741 | **如果 i 与 i - 1重复则直接跳过该项的遍历,如果 p 重复则 p++,如果 q 重复则 q++。** 742 | 743 | **考虑边界条件:** 744 | 745 | 当输入数组长度不足3时,返回空字符串数组。 746 | 747 | 实现代码: 748 | 749 | ```javascript 750 | // 15. 3Sum 751 | vector > threeSum(vector& nums) { 752 | vector > res; 753 | if (nums.size() < 3) return res; 754 | sort(nums.begin(), nums.end()); 755 | int p, q, sum; 756 | for (int i = 0; i < nums.size() - 2; i++) { 757 | if (nums[i] > 0) 758 | break; 759 | else if (i > 0 && nums[i] == nums[i - 1]) 760 | continue; 761 | p = i + 1; 762 | q = nums.size() - 1; 763 | while (p < q) { 764 | sum = nums[i] + nums[p] + nums[q]; 765 | if (sum == 0) { 766 | res.push_back({nums[i], nums[p], nums[q]}); 767 | while (nums[p] == nums[p + 1] && p < q) 768 | p++; 769 | p++; 770 | while (nums[q] == nums[q - 1] && p < q) 771 | q--; 772 | q--; 773 | } else if (sum > 0) { 774 | while (nums[q] == nums[q - 1] && p < q) 775 | q--; 776 | q--; 777 | } else { 778 | while (nums[p] == nums[p + 1] && p < q) 779 | p++; 780 | p++; 781 | } 782 | } 783 | } 784 | return res; 785 | } 786 | ``` 787 | 788 | ## 16. 3Sum Closest 789 | 790 | #### 解题思路: 791 | 792 | 与15题如出一辙,采用双指针法枚举,复杂度O\(N^2\)。 793 | 794 | 首先,对数组进行排序。然后,从第一个数字 i 开始遍历,每一层遍历中有两个指针 p, q 分别指向该数字后续的数组中的头尾两端,通过判断这三个数组的和与0的关系,移动头尾指针: 795 | 796 | **如果和大于0,返回target;如果和小于0,头指针后移;如果和等于0,分别移动头尾指针。** 797 | 798 | 这里注意要考虑到数组中处理出现重复数字的情况。 799 | 800 | **如果 i 与 i - 1重复则直接跳过该项的遍历,如果 p 重复则 p++,如果 q 重复则 q++。** 801 | 802 | 实现代码: 803 | 804 | ```javascript 805 | // 16. 3Sum Closest 806 | int threeSumClosest(vector& nums, int target) { 807 | sort(nums.begin(), nums.end()); 808 | int p, q, sum; 809 | int gap = INT_MAX; 810 | int res; 811 | for (int i = 0; i < nums.size() - 2; i++) { 812 | if (i > 0 && nums[i] == nums[i - 1]) continue; 813 | p = i + 1; 814 | q = nums.size() - 1; 815 | while (p < q) { 816 | sum = nums[i] + nums[p] + nums[q]; 817 | if (sum == target) { 818 | return target; 819 | } else if (sum > target) { 820 | if (sum - target < gap) { 821 | gap = sum - target; 822 | res = sum; 823 | } 824 | while (nums[q] == nums[q - 1] && p < q) 825 | q--; 826 | q--; 827 | } else { 828 | if (target - sum < gap) { 829 | gap = target - sum; 830 | res = sum; 831 | } 832 | while (nums[p] == nums[p + 1] && p < q) 833 | p++; 834 | p++; 835 | } 836 | } 837 | } 838 | return res; 839 | } 840 | ``` 841 | 842 | ## 17. Letter Combinations of a Phone Number 843 | 844 | #### 解题思路: 845 | 846 | 经典的排列问题,直接用回溯法解决。 847 | 848 | 下面的解法是使用循环实现非递归的回溯法。linshi作为中间变量,每一个数字按下之后的结果。 849 | 850 | 实现代码: 851 | 852 | ```javascript 853 | // 17. Letter Combinations of a Phone Number 854 | vector letterCombinations(string digits) { 855 | if (digits.size() == 0) return {}; 856 | map digitalMap; 857 | digitalMap[2] = "abc"; 858 | digitalMap[3] = "def"; 859 | digitalMap[4] = "ghi"; 860 | digitalMap[5] = "jkl"; 861 | digitalMap[6] = "mno"; 862 | digitalMap[7] = "pqrs"; 863 | digitalMap[8] = "tuv"; 864 | digitalMap[9] = "wxyz"; 865 | vector linshi; 866 | vector res = {""}; 867 | for (int i = 0; i < digits.size(); i++) { 868 | string tails = digitalMap[digits[i] - 48]; 869 | for (int j = 0; j < res.size(); j++) { 870 | for (int n = 0; n < tails.size(); n++) { 871 | linshi.push_back(res[j] + tails[n]); 872 | } 873 | } 874 | res = linshi; 875 | linshi = {}; 876 | } 877 | return res; 878 | } 879 | ``` 880 | 881 | ## 18. 4Sum 882 | 883 | #### 解题思路: 884 | 885 | 和15题3Sum如出一辙,在3Sum的解法外面再套一层即可。 886 | 887 | 实现代码: 888 | 889 | ```javascript 890 | // 18. 4Sum 891 | vector> fourSum(vector& nums, int target) { 892 | vector > res; 893 | if (nums.size() < 4) return res; 894 | sort(nums.begin(), nums.end()); 895 | int p, q, sum; 896 | for (int j = 0; j < nums.size() - 3; j++) { 897 | if (j > 0 && nums[j] == nums[j - 1]) 898 | continue; 899 | int target3 = target - nums[j]; 900 | for (int i = j + 1; i < nums.size() - 2; i++) { 901 | if (i > j + 1 && nums[i] == nums[i - 1]) 902 | continue; 903 | p = i + 1; 904 | q = nums.size() - 1; 905 | while (p < q) { 906 | sum = nums[i] + nums[p] + nums[q]; 907 | if (sum == target3) { 908 | res.push_back({nums[j], nums[i], nums[p], nums[q]}); 909 | while (nums[p] == nums[p + 1] && p < q) 910 | p++; 911 | p++; 912 | while (nums[q] == nums[q - 1] && p < q) 913 | q--; 914 | q--; 915 | } else if (sum > target3) { 916 | while (nums[q] == nums[q - 1] && p < q) 917 | q--; 918 | q--; 919 | } else { 920 | while (nums[p] == nums[p + 1] && p < q) 921 | p++; 922 | p++; 923 | } 924 | } 925 | } 926 | } 927 | return res; 928 | } 929 | ``` 930 | 931 | ## 19. Remove Nth Node From End of List 932 | 933 | #### 解题思路: 934 | 935 | **快慢指针思想**,让两个指针 p, q都指向头结点,p 先向后移动 n + 1 步,然后p, q一起向后移动,当 p 到达尾结点时,q指向目标节点的前驱结点,做删除操作,然后按照题目要求返回头结点即可。 936 | 937 | 实现代码: 938 | 939 | ```javascript 940 | // 20. Valid Parentheses 941 | bool isValid(string s) { 942 | stack brackets; 943 | map bracketMap; 944 | bracketMap[')'] = '('; 945 | bracketMap[']'] = '['; 946 | bracketMap['}'] = '{'; 947 | for (int i = 0; i < s.size(); i++) { 948 | if (s[i] == '(' || s[i] == '[' || s[i] == '{') { 949 | brackets.push(s[i]); 950 | } else if (s[i] == ')' || s[i] == ']' || s[i] == '}') { 951 | if (!brackets.empty() && brackets.top() == bracketMap[s[i]]) { 952 | brackets.pop(); 953 | } else { 954 | return false; 955 | } 956 | } 957 | } 958 | if (brackets.empty()) return true; 959 | else return false; 960 | } 961 | ``` 962 | 963 | ## 20. Valid Parentheses 964 | 965 | #### 解题思路: 966 | 967 | **栈思想**,从左往右遍历,如果是左括号则入栈,右括号则出栈,最后判断栈是否为空,空则为有效括号组,否则无效。 968 | 969 | 实现代码: 970 | 971 | ```javascript 972 | // 20. Valid Parentheses 973 | bool isValid(string s) { 974 | stack brackets; 975 | map bracketMap; 976 | bracketMap[')'] = '('; 977 | bracketMap[']'] = '['; 978 | bracketMap['}'] = '{'; 979 | for (int i = 0; i < s.size(); i++) { 980 | if (s[i] == '(' || s[i] == '[' || s[i] == '{') { 981 | brackets.push(s[i]); 982 | } else if (s[i] == ')' || s[i] == ']' || s[i] == '}') { 983 | if (!brackets.empty() && brackets.top() == bracketMap[s[i]]) { 984 | brackets.pop(); 985 | } else { 986 | return false; 987 | } 988 | } 989 | } 990 | if (brackets.empty()) return true; 991 | else return false; 992 | } 993 | ``` 994 | 995 | ## 21. Merge Two Sorted Lists 996 | 997 | #### 解题思路: 998 | 999 | 从头至尾一起遍历,每次比较头结点的大小,先添加小的结点,直到某一个链表为空为止,最后再将另一个还不为空的链表添加到末尾即可。 1000 | 1001 | 实现代码: 1002 | 1003 | ```javascript 1004 | // 21. Merge Two Sorted Lists 1005 | ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { 1006 | ListNode* head = new ListNode(0); 1007 | ListNode* p = head; 1008 | while (l1 != NULL && l2 != NULL) { 1009 | if (l1->val < l2 ->val) { 1010 | p->next = l1; 1011 | l1 = l1->next; 1012 | } else { 1013 | p->next = l2; 1014 | l2 = l2->next; 1015 | } 1016 | p = p->next; 1017 | } 1018 | if (l1 != NULL) { 1019 | p->next = l1; 1020 | } else if (l2 != NULL) { 1021 | p->next = l2; 1022 | } 1023 | return head->next; 1024 | } 1025 | ``` 1026 | 1027 | ## 22. Generate Parentheses 1028 | 1029 | #### 解题思路: 1030 | 1031 | **回溯法**,用一个数组记录【当前字符串,左括号数量,右括号数量】,做 2n 次循环,每次循环遍历当前数组,对左括号不足 n 的添加左括号,右括号不足左括号的添加右括号(确保parentheses是有效的) 1032 | 1033 | 实现代码: 1034 | 1035 | ```javascript 1036 | // 22. Generate Parentheses O(2^N) 1037 | vector generateParenthesis(int n) { 1038 | if (n == 0) return {}; 1039 | // nowString, leftCount, rightCount 1040 | vector > > res = {{"", {0, 0}}}; 1041 | vector > > linshi; 1042 | for (int i = 0; i < 2 * n; i++) { 1043 | for (int j = 0; j < res.size(); j++) { 1044 | string oldString = res[j].first; 1045 | if (res[j].second.first < n) { 1046 | linshi.push_back({oldString + '(', {res[j].second.first + 1, res[j].second.second}}); 1047 | } 1048 | if (res[j].second.first > res[j].second.second) { 1049 | linshi.push_back({oldString + ')', {res[j].second.first, res[j].second.second + 1}}); 1050 | } 1051 | } 1052 | res = linshi; 1053 | linshi = {}; 1054 | } 1055 | vector result; 1056 | for (int i = 0; i < res.size(); i++) { 1057 | result.push_back(res[i].first); 1058 | } 1059 | return result; 1060 | } 1061 | ``` 1062 | 1063 | ## 23. Merge k Sorted Lists 1064 | 1065 | #### 解题思路: 1066 | 1067 | 考虑使用**优先队列**,这道题相当于21题的加强版,但是如果单纯使用21题的每轮比较法,那么每轮只要要比较 k 次,而我们知道这其中肯定会存在很多重复的比较,所以我们可以使用二叉堆来保存每一轮的比较信息,而在C++中STL已经为我们实现了这种数据结构,那就是优先队列priority\_queue,直接调用即可。 1068 | 1069 | 实现代码: 1070 | 1071 | ```javascript 1072 | // 23. Merge k Sorted Lists 1073 | ListNode* mergeKLists(vector& lists) { 1074 | ListNode *dump = new ListNode(0), *p = dump, *nowPoint; 1075 | // val, node 1076 | priority_queue, vector>, greater> > pQueue; 1077 | for (ListNode* node : lists) { 1078 | if (node != NULL) 1079 | pQueue.push({node->val, node}); 1080 | } 1081 | while (!pQueue.empty()) { 1082 | nowPoint = pQueue.top().second; 1083 | p->next = nowPoint; 1084 | p = p->next; 1085 | pQueue.pop(); 1086 | if (nowPoint->next != NULL) { 1087 | pQueue.push({nowPoint->next->val, nowPoint->next}); 1088 | } 1089 | } 1090 | return dump->next; 1091 | } 1092 | ``` 1093 | 1094 | ## 24. Swap Nodes in Pairs 1095 | 1096 | #### 解题思路: 1097 | 1098 | 两两交换即可,这里每轮需要变换三个指向,第一个的next要指向第二个的next,第二个的next要指向第一个,前驱的next要指向第二个,每轮过后让 p 指向第一个即可。 1099 | 1100 | 实现代码: 1101 | 1102 | ```javascript 1103 | // 24. Swap Nodes in Pairs 1104 | ListNode* swapPairs(ListNode* head) { 1105 | ListNode *dump = new ListNode(0), *p = dump, *first, *second; 1106 | dump->next = head; 1107 | while (p != NULL && p->next != NULL && p->next->next != NULL) { 1108 | first = p->next; 1109 | second = p->next->next; 1110 | first->next = second->next; 1111 | second->next = first; 1112 | p->next = second; 1113 | p = first; 1114 | } 1115 | return dump->next; 1116 | } 1117 | ``` 1118 | 1119 | ## 25. Reverse Nodes in k-Group 1120 | 1121 | #### 解题思路: 1122 | 1123 | 这题相当于24题的加强版,将24题中的2变成了k,换汤不换药,稍微复杂一点,所以建议把每一步的思路理清楚,不然很容易弄错,大的思路是先检查是否还剩下足够数量的结点;然后进行反向操作:反向操作分为三步,中间结点关系反向,头结点指向处理 和 尾结点指向处理;最后让 p 结点后挪即可。 1124 | 1125 | 实现代码: 1126 | 1127 | ```javascript 1128 | // 25. Reverse Nodes in k-Group 1129 | ListNode* reverseKGroup(ListNode* head, int k) { 1130 | if (k == 1) return head; 1131 | vector nodeList; 1132 | ListNode *dump = new ListNode(0), *p = dump, *former, *latter, *nextLatter, *q; 1133 | dump->next = head; 1134 | while (true) { 1135 | // check count of left nodes 1136 | q = p; 1137 | for (int i = 0; i < k + 1; i++) { 1138 | if (q != NULL) { 1139 | q = q->next; 1140 | } else { 1141 | return dump->next; 1142 | } 1143 | } 1144 | // deal with medial relations 1145 | former = p->next; 1146 | latter = former->next; 1147 | for (int i = 0; i < k - 1; i++) { 1148 | // save latter->next as next latter 1149 | nextLatter = latter->next; 1150 | // latter->next = former 1151 | latter->next = former; 1152 | // save latter as next former 1153 | former = latter; 1154 | latter = nextLatter; 1155 | } 1156 | // deal with head 1157 | q = p->next; 1158 | p->next = former; 1159 | // deal with tail 1160 | q->next = latter; 1161 | 1162 | p = q; 1163 | } 1164 | return dump->next; 1165 | } 1166 | ``` 1167 | 1168 | ## 26. Remove Duplicates from Sorted Array 1169 | 1170 | #### 解题思路: 1171 | 1172 | 按照题意,把数组中不重复出现的数字**保存在数组前端**即可。 1173 | 1174 | 实现代码: 1175 | 1176 | ```javascript 1177 | // 26. Remove Duplicates from Sorted Array 1178 | int removeDuplicates(vector& nums) { 1179 | if (nums.size() == 0) return 0; 1180 | int j = 1; 1181 | for (int i = 1; i < nums.size(); i++) { 1182 | if (nums[i] != nums[i - 1]) { 1183 | nums[j++] = nums[i]; 1184 | } 1185 | } 1186 | return j; 1187 | } 1188 | ``` 1189 | 1190 | ## 27. Remove Element 1191 | 1192 | #### 解题思路: 1193 | 1194 | 这道题和 26 题几乎没有区别,把判断条件稍微改变即可。 1195 | 1196 | 实现代码: 1197 | 1198 | ```javascript 1199 | // 27. Remove Element 1200 | int removeElement(vector& nums, int val) { 1201 | int j = 0; 1202 | for (int i = 0; i < nums.size(); i++) { 1203 | if (nums[i] != val) { 1204 | nums[j++] = nums[i]; 1205 | } 1206 | } 1207 | return j; 1208 | } 1209 | ``` 1210 | 1211 | ### 28. Implement strStr\(\) 1212 | 1213 | #### 解题思路: 1214 | 1215 | 字符串匹配算法,选择比较多,最容易的就是**暴力匹配**,高阶一点可以使用**KMP**,这里简单起见,采用暴力解法,嘻嘻。 1216 | 1217 | 实现代码: 1218 | 1219 | ```javascript 1220 | // 28. Implement strStr() 1221 | int strStr(string haystack, string needle) { 1222 | if (needle.size() == 0) return 0; 1223 | int hLen = haystack.size(), nLen = needle.size(); 1224 | for (int i = 0; i < hLen; i++) { 1225 | if (hLen - i < nLen) { 1226 | return -1; 1227 | } else { 1228 | if (haystack.substr(i, nLen).compare(needle) == 0) { 1229 | return i; 1230 | } 1231 | } 1232 | } 1233 | return -1; 1234 | } 1235 | ``` 1236 | 1237 | ### 29. Devide Two Integers 1238 | 1239 | #### 解题思路: 1240 | 1241 | 这道题如果用减法去实现会造成时间复杂度过高,从而导致时间溢出,所以这里采用**位运算法**,从 2 的 31 次方开始除,一直除到 2 的 0 次方,为了防止内存溢出,我们把结果和中间变量保存在 long 类型中的。 1242 | 1243 | 实现代码: 1244 | 1245 | ```javascript 1246 | // 29. Divide Two Integers 1247 | int divide(int dividend, int divisor) { 1248 | if (dividend > INT32_MAX || dividend < INT32_MIN || divisor > INT32_MAX || divisor < INT32_MIN) return INT32_MAX; 1249 | long son = abs((long)divisor), father = abs((long)dividend), res = 0, base = 1, sum = 0; 1250 | for (int i = 31; i >= 0; i--) { 1251 | if (sum + (son << i) <= father) { 1252 | sum += son << i; 1253 | res += base << i; 1254 | } 1255 | } 1256 | if ((dividend >= 0 && divisor > 0) || (dividend < 0 && divisor < 0)) { 1257 | return (res > INT32_MAX) ? INT32_MAX : res; 1258 | } else { 1259 | return (-res < INT32_MIN) ? INT32_MAX : -res; 1260 | } 1261 | } 1262 | ``` 1263 | 1264 | ## 30. Substring with Concatenation of All Words 1265 | 1266 | #### 解题思路: 1267 | 1268 | 使用**哈希表**存储 words 数组中各个单词的数量,然后使用暴力匹配即可。 1269 | 1270 | 实现代码: 1271 | 1272 | ```javascript 1273 | // 30. Substring with Concatenation of All Words 1274 | vector findSubstring(string s, vector& words) { 1275 | vector result; 1276 | if (words.size() == 0 || words[0].size() == 0 || s.size() < words.size() * words[0].size()) { 1277 | return result; 1278 | } 1279 | unordered_map counts; 1280 | for (string word : words) { 1281 | if (counts.find(word) == counts.end()) { 1282 | counts[word] = 1; 1283 | } else { 1284 | counts[word]++; 1285 | } 1286 | } 1287 | int wordCount = words.size(), wordLength = words[0].size(), strLength = s.size(); 1288 | for (int i = 0; i <= strLength - wordLength * wordCount; i++) { 1289 | unordered_map innerCounts = counts; 1290 | bool flag = true; 1291 | for (int j = 0; j < wordCount; j++) { 1292 | string nowStr = s.substr(i + j * wordLength, wordLength); 1293 | if (innerCounts.find(nowStr) == innerCounts.end() || innerCounts[nowStr] == 0) { 1294 | flag = false; 1295 | break; 1296 | } else { 1297 | innerCounts[nowStr]--; 1298 | } 1299 | } 1300 | if (flag) { 1301 | result.push_back(i); 1302 | } 1303 | } 1304 | return result; 1305 | } 1306 | ``` 1307 | 1308 | 1309 | 1310 | -------------------------------------------------------------------------------- /剑指offer题解.md: -------------------------------------------------------------------------------- 1 | ## 剑指offer题解 2 | 3 | 4 | 5 | ### 1. 6 | 7 | 8 | 9 | --------------------------------------------------------------------------------