-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
yangjin
committed
Oct 8, 2019
1 parent
04218dd
commit e14898f
Showing
3 changed files
with
186 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# 数组 | ||
|
||
## 概念和特点 | ||
|
||
基本每种编程语言都有数组这种数据结构。 | ||
|
||
> 数组是一种线性表结构,在内存里,是一组连续的内存空间,用来存储一组类型相同的数据。 | ||
|
||
### 随机访问 | ||
|
||
数组是一组连续的内存空间,并存储类型相同的数据。所以只要知道被分配的内存的首地址,就可以通过地址偏移随机访问数组的元素,这就是数组杀手锏的特性**随机访问**。 | ||
|
||
### 低效的插入和删除 | ||
|
||
由于数组的内存是连续的,为了保证连续性,为了保证连续性,在第 i 个位置插入一个元素时,需要将 i~n 个元素都顺序的往后移一位。 | ||
|
||
而删除的时候也是一样的,当删除第 i 个位置的数据之后,也需要移动数据,不然就会出现间断,不连续了。 | ||
|
||
## 数组长度和内存大小 | ||
|
||
在平常的代码中,很多时候,定义一个数组时我们没有指定长度,会给我们分配多少的内存空间呢?以及内存不够的时候如何处理呢? | ||
|
||
参考这篇博客[从Chrome源码看JS Array的实现](https://www.yinchengli.com/2017/04/16/chrome-js-array/),浏览器给我们的初始内存空间为 4,当往第存储第 5 个值的时候,将进行**自动扩容**。 | ||
|
||
new_capacity = old_capacity /2 + old_capacity + 16 | ||
|
||
申请一块更大的内存,把老的数据拷贝过去,然后再将新的数据插入。 | ||
|
||
```js | ||
var arr = []; | ||
for (let i = 0; i < 100; i++) { | ||
arr[i] = i * i; | ||
} | ||
``` | ||
|
||
不过值得注意的是,扩容操作涉及内存的申请和数据搬移,是比较耗时的。所以如果事先知道要存储的数据大小,最好在**创建数组的时候指定数据大小**。 | ||
|
||
```js | ||
var arr = new Array(100); | ||
for (let i = 0; i < 100; i++) { | ||
arr[i] = i * i; | ||
} | ||
``` | ||
|
||
## 复杂度 | ||
|
||
- 读取:O(1) | ||
- 插入:最好时间复杂度为 O(1),最坏时间复杂度为 O(n),平均时间复杂度为 O(n) | ||
- 删除:最好时间复杂度为 O(1),最坏时间复杂度为 O(n),平均时间复杂度为 O(n) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
# 练习 | ||
|
||
## 数组中重复的数字 | ||
|
||
### 题目 | ||
|
||
在一个长度为 n 的数组里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为 7 的数组 {2, 3, 1, 0, 2, 5, 3},那么对应的输出是重复的数字 2 或者 3。 | ||
|
||
### 解法一 | ||
|
||
先把输入的数组排序。然后再从排好序的数组中找出重复的数字,需要重头遍历扫描排序后的数组,将元素和它后面的一个相比较,然后找出重复的数字。排序一个长度为 n 的数组需要 O(nlogn) 的时间 | ||
|
||
- 时间复杂度:O(nlogn) + O(n) = O(nlogn) | ||
- 空间复杂度:O(n) | ||
|
||
### 解法二 | ||
|
||
利用哈希表,从头到尾按顺序扫描数组的每个数字,每扫描到一个数字的时候,都可以用 O(1) 的时间来判断哈希表里是否已经包含了该数字。如果哈希表里还没有改数字,就把它加入哈希表。如果哈希表里已经存在该数字,就找到一个重复的数字。 | ||
|
||
- 时间复杂度:O(n) | ||
- 空间复杂度:O(n) | ||
|
||
### 解法三 | ||
|
||
注意到数组中的数字都在 0~n-1 的范围内。如果这个数组没有重复的数字,那么当数组排好序之后数字 i 将出现在下标 i 的位置。由于数组有重复的数字,有些位置可能存在多个数字,同时有的位置可能没有数字。 | ||
|
||
从头到尾一次扫描这个数组中的元素每个数字。当扫描到下标为 i 的数字时,首先比较这个数字(用 m 表示)是不是等于 i。如果是,则接着扫描下一个数字;如果不是,则再拿它和第 m 个数字进行比较。如果它和第 m 个数字相等,就找到了一个重复的数字(该数字在 i 和 m 的位置都出现了);如果它和第 m 个数字不想等,则把第 i 个数字和第 m 个数字交换,把 m 放到属于它的位置。接下来再重复上面过程,直到找到第一重复的数字。 | ||
|
||
```js | ||
function duplicate(arr) { | ||
if(!arr || arr.length === 0) return; | ||
|
||
let length = arr.length; | ||
if (arr.find(item => item < 0 || item > length - 1)) { | ||
return new Error('数组中的数字超出氛围 0~n-1'); | ||
} | ||
|
||
for (let i = 0; i < length; i++) { | ||
while (arr[i] !== i) { | ||
if (arr[i] === arr[arr[i]]) { | ||
return arr[i]; | ||
} | ||
let temp = arr[i]; | ||
arr[i] = arr[temp]; | ||
arr[temp] = temp; | ||
} | ||
} | ||
return; | ||
} | ||
|
||
// 测试用例 | ||
duplicate([2, 3, 1, 0, 2, 5, 3]); // 2 | ||
duplicate([5, 1, 2, 6, 0, 4, 3]); // undefined | ||
duplicate([5, 1, 2, 6, 0, 4, 7]); // 数组中的数字超出氛围 0~n-1 | ||
``` | ||
|
||
- 时间复杂度:O(n) | ||
- 空间复杂度:o(1) | ||
|
||
## 不修改数组找出重复的数字 | ||
|
||
在一个长度为 n+1 的数组里的所有数字都在 1~n 的范围内,所有数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为 8 的数组 {2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字 2 或者 3。 | ||
|
||
### 解法一 | ||
|
||
这道题目和上一道非常类似,但是不能修改数组。可以创建一个长度为 n+1 的辅助数组,然后逐一把原数组的每个数字复制到辅助数组。如果原数组被复制的数字是 m,则把它复制到辅助数组下标为 m 的位置。这个时候就很容易发现那个数字是重复的了。 | ||
|
||
- 时间复杂度:O(n) | ||
- 空间复杂度:O(n) | ||
|
||
### 解法二 | ||
|
||
假如没有重复数字,那么 1~n 的范围里只有 n 个数字。看了,在某个范围里的数字的个数对解决这个问题非常关键。 | ||
|
||
把 1~n 的数字从中间的数字 m 分为两部分,前面一半为 1~m,后面一半为 m+1~n。如果 1~m 的数字的数目超过 m,那么这一半的区间里一定包含重复的数字;否则,另一半 m+1~n 的区间里一定包含重复的数字。继续把包含重复数字的区间一分为二,知道找到一个重复的数字。 | ||
|
||
这个过程和二分查找很类似,但是多了一个统计区间里数字的数目。 | ||
|
||
```js | ||
function getDuplication(arr) { | ||
if(!arr || arr.length === 0) return; | ||
|
||
let length = arr.length; | ||
if (arr.find(item => item < 1 || item > length - 1)) { | ||
return new Error('数组中的数字超出氛围 1~n'); | ||
} | ||
|
||
let countRange = function(arr, length, start, end) { | ||
let count = 0; | ||
for (let i = 0; i < length; i++) { | ||
if (arr[i] >= start && arr[i] <= end) { | ||
++count; | ||
} | ||
} | ||
return count; | ||
} | ||
|
||
let start = 1; | ||
let end = length - 1; | ||
while (end >= start) { | ||
let middle = Math.floor((end + start) / 2); | ||
let count = countRange(arr, length, start, middle); | ||
if (end === start) { | ||
if (count === 1) { | ||
return start; | ||
} else { | ||
break; | ||
} | ||
} | ||
if (count > (middle - start + 1)) { | ||
end = middle; | ||
} else { | ||
start = middle + 1; | ||
} | ||
} | ||
return -1; | ||
} | ||
|
||
// 测试用例 | ||
getDuplication([2,3,5,4,3,2,6,7]); // 1 | ||
|
||
``` | ||
|
||
这种算法相当于以时间换空间。并且,值得注意的是,这种算法不能保证找出所有重复的数字。例如,不能找出 [2,3,5,4,3,2,6,7] 中重复的 2.这是因为 1~2 的范围里有 1 和 2 两个数字,整个范围的数字也出现的两次,此时,这个算法里面不能确定是每个数字各出现一次还是某个数字出现了两次。 | ||
|
||
- 时间复杂度:O(nlogn) | ||
- 空间复杂度:O(1) |