练习过的一些算法题
概述
前端也需要学习算法,算法是每个程序员的内功,内功越强学习新知识越快。
找出字符串中最高key的数量并显示
/**
* 找出字符串中最高key的数量并显示 复杂度为 2n
* @param {字符串} str
*/
function computedCount(str) {
// 将字符串拆成对象形式,value为单个字符出现的次数
const cache = {}
for (let i = 0; i < str.length; i++) {
if (cache.hasOwnProperty(str[i])) {
cache[str[i]] = cache[str[i]] + 1
} else {
cache[str[i]] = 1
}
}
let temp = []
const caches = Object.entries(cache)
// 找出最高的key,可能有几个一样高的key,但是不管几个
// 在temp里面出现的value肯定都是一样的,所以不需要遍历temp
for (let i = 0; i < caches.length; i++) {
if (i === 0) {
temp = [caches[i]]
continue
// [['1', 3]]
} else if (temp[0][1] < caches[i][1]) {
temp = [caches[i]]
} else if (temp[0][1] === caches[i][1]) {
temp.push(caches[i])
}
}
// 将数组转成对象形式
const result = {}
temp.forEach(([key, value]) => {
result[key] = value
})
return result
}
let str = '12311qdaddsss'
// { '1': 3, d: 3, s: 3 }
console.log(computedCount(str))
荷兰国旗
/**
* 荷兰国旗问题 => 分成小于 等于 大于 num 的三个分区
* @param {数组} arr
* @param {需要分割的数字} num
*/
function partition(arr, num) {
const swap = (arr, l, r) => {
if (l === r) return
let temp = arr[l]
arr[l] = arr[r]
arr[r] = temp
}
let l = -1
let r = arr.length
let i = 0
while (i < r) {
if (arr[i] < num) {
swap(arr, ++l, i)
i++
} else if (arr[i] > num) {
swap(arr, i, --r)
} else {
i++
}
}
}
let test = [999, 3, 4, 78, 56, 8, 32, 7, 0, 100]
partition(test, 8)
// [ 0, 3, 4, 7, 8, 32, 56, 78, 100, 999 ]
console.log(test)
数组去重
Array.prototype.unique = function(){
let hash = new Map()
let result = []
let item
for (let i = 0; i < this.length; i++) {
if (Object.prototype.toString.call(this[i]) === '[object Object]'
|| Object.prototype.toString.call(this[i]) === '[object Array]') {
item = JSON.stringify(this[i])
} else {
item = this[i]
}
if (!hash.has(item)) {
hash.set(item, true)
result.push(this[i])
}
}
return result
}
console.log([123,undefined, undefined, { a: 1 }, { a: { b: 1 } }, { a: "1" }, { a: { b: 1 } }, "meili"].unique())
结果对比
[123,undefined, undefined, { a: 1 }, { a: { b: 1 } }, { a: "1" }, { a: { b: 1 } }, "meili"]
[ 123, undefined, { a: 1 }, { a: { b: 1 } }, { a: '1' }, 'meili' ]
冒泡排序
提示
| 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| O(n²) | O(n²) | O(n) | O(1) | 稳定 |
function bubbleSort(arr) {
let length = arr.length
for (let i = 0; i < length - 1; i++) {
for (let j = i + 1; j < length; j++) {
if (arr[i] > arr[j]) {
let temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
}
// [ -10, 2, 4, 52, -2, 3, 2, 56, 23, 1 ]
// [ -10, -2, 4, 52, 2, 3, 2, 56, 23, 1 ]
// [ -10, -2, 1, 52, 4, 3, 2, 56, 23, 2 ]
// [ -10, -2, 1, 2, 52, 4, 3, 56, 23, 2 ]
// [ -10, -2, 1, 2, 2, 52, 4, 56, 23, 3 ]
// [ -10, -2, 1, 2, 2, 3, 52, 56, 23, 4 ]
// [ -10, -2, 1, 2, 2, 3, 4, 56, 52, 23 ]
// [ -10, -2, 1, 2, 2, 3, 4, 23, 56, 52 ]
// [ -10, -2, 1, 2, 2, 3, 4, 23, 52, 56 ]
// [ -10, -2, 1, 2, 2, 3, 4, 23, 52, 56 ]
console.log(arr)
}
return arr
}
let test = [2, -2, 4, 52, -10, 3, 2, 56, 23, 1]
bubbleSort(test)
// [ -10, -2, 1, 2, 2, 3, 4, 23, 52, 56 ]
console.log(test)
选择排序
// 选择排序
function selectSort(arr) {
let length = arr.length
let min = 0
let minIndex = 0
for (let i = 0; i < length - 1; i++) {
min = arr[i]
minIndex = i
for (let j = i + 1; j < length; j++) {
if (min > arr[j]) {
min = arr[j]
minIndex = j
}
}
if (minIndex !== i) {
let temp = arr[i]
arr[i] = min
arr[minIndex] = temp
}
}
return arr
}
let test = [2, -2, 4, 52, -10, 3, 2, 56, 23, 1]
selectSort(test)
console.log(test)
插入排序
//插入排序
function insertSort(arr) {
let length = arr.length
let preIndex, current
for (let i = 1; i < length; i++) {
preIndex = i - 1
current = arr[i]
while (preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex + 1] = arr[preIndex]
preIndex--
}
arr[preIndex + 1] = current
}
return arr
}
let test = [2, -2, 4, 52, -10, 3, 2, 56, 23, 1]
insertSort(test)
console.log(test)
归并排序
//归并排序
function mergeSort(arr) {
const merge = (arr, l, mid, r) => {
let help = []
let i = 0
let p1 = l
let p2 = mid + 1
while (p1 <= mid && p2 <= r) {
help[i++] = (arr[p1] < arr[p2]) ? arr[p1++] : arr[p2++]
}
while (p1 <= mid) {
help[i++] = arr[p1++]
}
while (p2 <= r) {
help[i++] = arr[p2++]
}
console.log('help', help)
for (let i = 0; i < help.length; i++) {
arr[l + i] = help[i]
}
console.log('arr', arr)
}
const sortProcess = (arr, l, r) => {
if (l == r) {
return
}
let mid = Math.floor((l + r) / 2)
sortProcess(arr, l, mid)
sortProcess(arr, mid + 1, r)
merge(arr, l, mid, r)
}
if (arr.length < 2) {
return
}
sortProcess(arr, 0, arr.length - 1)
}
mergeSort(test)
打印
help [ 2, 4 ]
arr [ 2, 4, 52, 3, 2, 56, 23, 1 ]
help [ 3, 52 ]
arr [ 2, 4, 3, 52, 2, 56, 23, 1 ]
help [ 2, 3, 4, 52 ]
arr [ 2, 3, 4, 52, 2, 56, 23, 1 ]
help [ 2, 56 ]
arr [ 2, 3, 4, 52, 2, 56, 23, 1 ]
help [ 1, 23 ]
arr [ 2, 3, 4, 52, 2, 56, 1, 23 ]
help [ 1, 2, 23, 56 ]
arr [ 2, 3, 4, 52, 1, 2, 23, 56 ]
help [ 1, 2, 2, 3, 4, 23, 52, 56 ]
arr [ 1, 2, 2, 3, 4, 23, 52, 56 ]
js 快速排序
function quickSort(arr, left, right) {
//为了防止剩一个数时再进行计算
if (left < right) {
//设置最左边的元素为基准点:pivot
let p = arr[left];
//把要排序的序列中比p大的放到右边,比p小的放到左边,p的下标位置为i
let i = left,
j = right;
while(i<j)
{
//j向左移,找到一个比p小的元素,直到找到小于p的数就停止在j下标上
while(arr[j] >= p && i < j){
j--;
}
//i向右移,找到一个比p大的元素
while(arr[i] <= p && i < j){
i++;
}
//当i和j不相等的时候交换
if (i<j){
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
arr[left] = arr[i];
arr[i] = p;
//i-1,i+1是为了让当前基准点继续参加排序
quickSort(arr,left,i - 1);
quickSort(arr, i + 1, right);
}
return arr;
}
var arr = [1,3,4,2,45,2,92,0,-2];
console.log(quickSort(arr,0,arr.length-1));
三路随机快排
堆排序
两数之和
提示
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]// 没有预先放入对象中是因为nums里面的值可能有重复的所以key会被覆盖
var twoSum = function(nums, target) {
let obj = {}
for (let i = 0; i < nums.length; i++) {
const temp = target - nums[i]
if (obj[temp]) {
return [obj[temp] - 1, i]
}
obj[nums[i]] = i + 1
}
return []
};
console.log(twoSum([3,3], 6)) // [0,1]
无重复字符的最长子串
提示
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例1
输入: “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例2
输入: “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例3
输入: “pwwkew” 输出: 3 解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。 请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
var lengthOfLongestSubstring = function (s) {
var num = 0;
var res = 0;
var m = '';
for (value of s) {
if (!~m.indexOf(value)) {
m += value;
num++;
res = res < num ? num : res;
} else {
m += value;
m = m.slice(m.indexOf(value) + 1) //"acdvdf" 当遇到第二个d时删掉acd保留vdf
num = m.length
}
}
return res
};
// 两种是一样的思路,第一种简洁明了
var lengthOfLongestSubstring = function (s) {
if (s.length === 1) return 1
let obj = {}
let max = 0
for(let v of s){
if (obj[v]) {
let stack = Object.keys(obj)
for (let i = 0; i < stack.length; i++) {
delete obj[stack[i]] //"dvdf" 遍历到d时删掉第一个d,然后继续遍历
if (stack[i] === v) {
obj[stack[i]] = 1
break;
}
}
} else {
obj[v] = 1
}
const length = Object.keys(obj).length
max = Math.max(max, length)
}
return max
};
字符串解码
提示
这道题近半年来广受各大公司的青睐,出现非常频繁,在腾讯仅仅半年就出现了17次。
例子:
'2[abc]3[cjh]' => abcabccjhcjhcjh
'2[a2[b]]' => abbabb
'2[a2[b3[c]]]' => abcccbcccabcccbccc
思路:一看到这个题首先想到的是递归,因为有无限嵌套的可能,但是每种递归都可以用栈来保存我们所需要的数据,所以这道题至少有两种写法。
首先用栈来保存收集到的系数和字符串
function decodeString(str) {
// 系数栈
let coefficientStack = []
// 结果集栈
let resultStack = []
let result = ''
// 初始化系数为0
let coefficient = 0
for (let i = 0; i < str.length; i++) {
let current = str.charAt(i)
switch (current) {
case '[':
// 当碰到'['就将以前收集字符串和系数的推入栈中
// 重新开始收集字符串和系数
coefficientStack.push(coefficient)
resultStack.push(result)
result = ''
coefficient = 0
break;
case ']':
// 碰到']'代表结束:计算当前[]中的字符串,和
let count = coefficientStack.pop()
let tempResult = ''
// 将收集到字符串翻成系数倍
for (let j = 0; j < count; j++) {
tempResult += result
}
// 和前面已经求得的字符串进行拼接
result = resultStack.pop() + tempResult
break;
default:
if (current >= 0 && current <= 9) {
// 系数累积 2[ab16[cd]] 下面的代码为了让16能够存下来,current - '0'是为了隐式转换成数字
coefficient = coefficient * 10 + (current - '0');
} else {
// 字母累加 字符串收集
result += current;
}
break;
}
}
return result
}
用递归的方法来解决
LRU-缓存机制
提示
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
你是否可以在 O(1) 时间复杂度内完成这两种操作? leetcode地址
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
代码实现:
var NodeData = function ({ key = null, value = null }) {
this.key = key
this.value = value
}
var Node = function (data) {
this.pre = null
this.next = null
this.data = new NodeData(data)
}
/**
* @param {number} capacity
*/
var LRUCache = function (capacity) {
var DoublyLinkedList = function () {
this.head = new Node({ key: 'null', value: 'null' })
this.last = new Node({ key: 'null', value: 'null' })
this.head.next = this.last
this.last.pre = this.head
this.length = 0
}
// 在从前往后在指定位置添加节点
DoublyLinkedList.prototype.insertWithDataKeyForward = function (position, node) {
let current = this.head
if (position === 0) {
this.insert(current, node)
}
if (position > 0 && position <= this.length) {
for (let i = 0; i < position; i++) {
current = current.next
}
this.insert(current, node)
}
}
// 在前一个节点后面插入节点
DoublyLinkedList.prototype.insert = function (preNode, currentNode) {
// 保存下一个节点
nextNode = preNode.next
// 当前节点连接上一个节点
preNode.next = currentNode
currentNode.pre = preNode
// 当前节点连接下一个节点
currentNode.next = nextNode
nextNode.pre = currentNode
this.length++
}
// 在从后往前在指定位置添加节点
DoublyLinkedList.prototype.insertWithDataKeyBackward = function (position, node) {
let current = this.last.pre
if (position === 0) {
this.insert(current, node)
}
if (position > 0 && position <= this.length) {
for (let i = 0; i < position; i++) {
current = current.pre
}
this.insert(current, node)
}
}
// 找出位置 从左向右开始找
DoublyLinkedList.prototype.findPosition = function (node) {
}
// 移除当前节点
DoublyLinkedList.prototype.removeCurrentNode = function (currentNode) {
let preNode = currentNode.pre
let nextNode = currentNode.next
preNode.next = nextNode
nextNode.pre = preNode
this.length--
}
// 根据从到右的位置移除对应节点
DoublyLinkedList.prototype.removeWithPosition = function (position) {
if (this.length === 0) return
if (this.position > 0 && this.position <= this.length) {
let currentNode = this.head
for (let i = 0; i < position; i++) {
currentNode = currentNode.next
}
this.removeCurrentNode(currentNode)
}
}
this.list = new DoublyLinkedList()
this.capacity = capacity
// map里面存的是:key:key,value:当前节点
this.map = new Map()
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function (key) {
if (this.map.has(key)){
const curentNode = this.map.get(key)
this.list.removeCurrentNode(curentNode)
this.list.insertWithDataKeyForward(0, curentNode)
return this.map.get(key).data.value
}
return -1
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function (key, value) {
// 如果存在的就在双向链表移除,并且在头结点重新添加
// 如果不存在就,并且capacity已经满了,删除最后一个节点,然后在头结点重写添加
// 如果当前链表已经存在密钥,则删除该节点,在头部添加新节点
if (this.map.has(key)) {
const currentNode = this.map.get(key)
this.list.removeCurrentNode(currentNode)
this.list.insertWithDataKeyForward(0, new Node({key, value}))
this.map.get(key).data.value = value
}else if (this.list.length < this.capacity) {
// 如果节点长度没有比capacity多则直接在头结点添加新节点
const newNode = new Node({ key, value })
this.list.insertWithDataKeyForward(0, newNode)
this.map.set(key, newNode)
} else {
// 节点长度和capacity一样长,删除尾节点,在头部添加新节点
const newNode = new Node({ key, value })
const lastNode = this.list.last.pre
this.list.removeCurrentNode(lastNode)
this.list.insertWithDataKeyForward(0, newNode)
this.map.delete(lastNode.data.key)
this.map.set(key, newNode)
}
};
const arr = [[6, 14], [3, 1], [3], [10, 11], [8], [2, 14], [1], [5], [4], [11, 4], [12, 24], [5, 18], [13], [7, 23], [8], [12], [3, 27], [2, 12], [5], [2, 9], [13, 4], [8, 18], [1, 7], [6]]
arr.forEach(v => {
if (v.length > 1){
cache.put(v[0], v[1])
}
})
console.log(cache.map)
console.log(cache.list.head.next);
全O(1)操作
题目
实现一个数据结构支持以下操作:
- Inc(key) - 插入一个新的值为 1 的 key。或者使一个存在的 key 增加一,保证 key 不为空字符串。
- Dec(key) - 如果这个 key 的值是 1,那么把他从数据结构中移除掉。否者使一个存在的 key 值减一。如果这个 key 不存在,这个函数不做任何事情。key 保证不为空字符串。
- GetMaxKey() - 返回 key 中值最大的任意一个。如果没有元素存在,返回一个空字符串""。
- GetMinKey() - 返回 key 中值最小的任意一个。如果没有元素存在,返回一个空字符串""。
以 O(1) 的时间复杂度实现所有操作。 leetcode地址
// 跟LRU差不多的结构 map + 双向链表
// map存储 key:key,value:值 1+~~ 插入key时,先在map中对应的key加1,然后判断当前curMax、curMin然后决定是否放入头结点或尾结点
// 重点是将curMax、curMin利用好
var NodeData = function ({ key = null, value = null }) {
this.key = key
this.value = value
}
var Node = function (data) {
this.pre = null
this.next = null
this.data = new NodeData(data)
}
var AllOne = function () {
var DoublyLinkedList = function () {
this.head = new Node({ key: 'null', value: 'null' })
this.last = new Node({ key: 'null', value: 'null' })
this.head.next = this.last
this.last.pre = this.head
this.length = 0
}
// 在从前往后在指定位置添加节点
DoublyLinkedList.prototype.insertWithDataKeyForward = function (position, node) {
let current = this.head
if (position === 0) {
this.insert(current, node)
}
if (position > 0 && position <= this.length) {
for (let i = 0; i < position; i++) {
current = current.next
}
this.insert(current, node)
}
}
// 在前一个节点后面插入节点
DoublyLinkedList.prototype.insert = function (preNode, currentNode) {
// 保存下一个节点
nextNode = preNode.next
// 当前节点连接上一个节点
preNode.next = currentNode
currentNode.pre = preNode
// 当前节点连接下一个节点
currentNode.next = nextNode
nextNode.pre = currentNode
this.length++
}
// 在从后往前在指定位置添加节点
DoublyLinkedList.prototype.insertWithDataKeyBackward = function (position, node) {
let current = this.last.pre
if (position === 0) {
this.insert(current, node)
}
if (position > 0 && position <= this.length) {
for (let i = 0; i < position; i++) {
current = current.pre
}
this.insert(current, node)
}
}
// 移除当前节点
DoublyLinkedList.prototype.removeCurrentNode = function (currentNode) {
let preNode = currentNode.pre
let nextNode = currentNode.next
preNode.next = nextNode
nextNode.pre = preNode
this.length--
}
// 根据从到右的位置移除对应节点
DoublyLinkedList.prototype.removeWithPosition = function (position) {
if (this.length === 0) return
if (this.position > 0 && this.position <= this.length) {
let currentNode = this.head
for (let i = 0; i < position; i++) {
currentNode = currentNode.next
}
this.removeCurrentNode(currentNode)
}
}
this.list = new DoublyLinkedList()
this.map = new Map()
this.curMax = 1
this.curMin = 1
};
/**
* Inserts a new key <Key> with value 1. Or increments an existing key by 1.
* @param {string} key
* @return {void}
*/
AllOne.prototype.inc = function (key) {
let curNode
if (this.map.has(key)){
curNode = this.map.get(key)
const curValue = ++curNode.data.value
// 判断是否在头结点、中间结点、尾结点
if (this.curMax <= curValue){
this.curMax = curValue
this.list.removeCurrentNode(curNode)
this.list.insertWithDataKeyForward(0, curNode)
}
else if (curValue - this.curMin === 1) {
// 当前节点本来就在末尾
this.curMin = curValue
if (curNode.pre && curNode.pre.data.value < this.curMin) {
this.list.removeCurrentNode(curNode)
this.list.insertWithDataKeyBackward(1, curNode)
}
// 本身就在最后一个,就没有必要在删除 插入了,跟下面得逻辑一样
// this.list.removeCurrentNode(curNode)
// this.list.insertWithDataKeyBackward(0, curNode)
}
else {
// 当前值在最大值和最小值中间,所以在第一个位置插入节点
this.list.removeCurrentNode(curNode)
this.list.insertWithDataKeyBackward(1, curNode)
}
} else {
curNode = new Node(new NodeData({key, value: 1}))
this.map.set(key, curNode)
this.list.insertWithDataKeyBackward(0, curNode)
// 插入新节点 最小值重置成1
this.curMin = 1
}
};
/**
* Decrements an existing key by 1. If Key's value is 1, remove it from the data structure.
* @param {string} key
* @return {void}
*/
AllOne.prototype.dec = function (key) {
if(!this.map.has(key)){
return
}
const curNode = this.map.get(key)
const curValue = --curNode.data.value
if (curValue === 0) {
this.map.delete(key)
this.list.removeCurrentNode(curNode)
} else {
if (this.curMax <= curValue) {
this.curMax = curValue
// 最大值--,没有必要再次删除 插入,因为本来就是在第一个
// this.list.removeCurrentNode(curNode)
// this.list.insertWithDataKeyForward(0, curNode)
} else if (this.curMin >= curValue) {
this.curMin = curValue
this.list.removeCurrentNode(curNode)
this.list.insertWithDataKeyBackward(0, curNode)
} else {
this.list.removeCurrentNode(curNode)
this.list.insertWithDataKeyBackward(1, curNode)
}
}
};
/**
* Returns one of the keys with maximal value.
* @return {string}
*/
AllOne.prototype.getMaxKey = function () {
if (this.list.length === 0) {
return ''
}
return this.list.head.next.data.key
};
/**
* Returns one of the keys with Minimal value.
* @return {string}
*/
AllOne.prototype.getMinKey = function () {
if (this.list.length === 0) {
return ''
}
return this.list.last.pre.data.key
};
回溯
提示
回溯
,并且每次调用时都会触发调用栈+1,然后在某个时刻return或加判断就会回到某个时刻继续执行下去,下面还有可执行当前函数的语句。考验对递归的使用: 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
示例:
输入: “25525511135” 输出: [“255.255.11.135”, “255.255.111.35”]
/**
* @param {string} s
* @return {string[]}
*/
var restoreIpAddresses = function (s) {
const res = []
// 回溯
const recursion = (result, surplusStr) => {
console.log(result, surplusStr)
// 如果surplusStr比剩余的空数(3个)还大的话可以直接return,因为肯定不是一个合格的ip地址
if (surplusStr.length > (4 - result.length) * 3) {
return
}
if ((result.length === 4 && surplusStr.length !== 0) || (result.length > 4 || result.length < 4 && surplusStr.length === 0) ) {
return
}
if (result.length === 4 && surplusStr.length === 0) {
res.push(result.join('.'))
return
}
recursion([...result, surplusStr[0]], surplusStr.slice(1))
if (surplusStr[0] != 0 && surplusStr.length > 1) {
recursion([...result, surplusStr.slice(0, 2)], surplusStr.slice(2))
}
if (surplusStr[0] != 0 && parseInt(surplusStr.slice(0, 3)) <=255 && surplusStr.length > 2) {
recursion([...result, surplusStr.slice(0, 3)], surplusStr.slice(3))
}
}
recursion([], s)
return res;
};
去掉第一个if优化判断的输出
[ ‘2’ ] ‘5512511135’ [ ‘2’, ‘5’ ] ‘512511135’ [ ‘2’, ‘5’, ‘5’ ] ‘12511135’ [ ‘2’, ‘5’, ‘5’, ‘1’ ] ‘2511135’ [ ‘2’, ‘5’, ‘5’, ‘12’ ] ‘511135’ [ ‘2’, ‘5’, ‘5’, ‘125’ ] ‘11135’ [ ‘2’, ‘5’, ‘51’ ] ‘2511135’ [ ‘2’, ‘5’, ‘51’, ‘2’ ] ‘511135’ [ ‘2’, ‘5’, ‘51’, ‘25’ ] ‘11135’ [ ‘2’, ‘5’, ‘51’, ‘251’ ] ‘1135’ [ ‘2’, ‘55’ ] ‘12511135’ [ ‘2’, ‘55’, ‘1’ ] ‘2511135’ [ ‘2’, ‘55’, ‘1’, ‘2’ ] ‘511135’ [ ‘2’, ‘55’, ‘1’, ‘25’ ] ‘11135’ [ ‘2’, ‘55’, ‘1’, ‘251’ ] ‘1135’ [ ‘2’, ‘55’, ‘12’ ] ‘511135’ [ ‘2’, ‘55’, ‘12’, ‘5’ ] ‘11135’ [ ‘2’, ‘55’, ‘12’, ‘51’ ] ‘1135’ [ ‘2’, ‘55’, ‘125’ ] ‘11135’ [ ‘2’, ‘55’, ‘125’, ‘1’ ] ‘1135’ [ ‘2’, ‘55’, ‘125’, ‘11’ ] ‘135’ [ ‘2’, ‘55’, ‘125’, ‘111’ ] ‘35’ [ ‘25’ ] ‘512511135’ [ ‘25’, ‘5’ ] ‘12511135’ [ ‘25’, ‘5’, ‘1’ ] ‘2511135’ [ ‘25’, ‘5’, ‘1’, ‘2’ ] ‘511135’ [ ‘25’, ‘5’, ‘1’, ‘25’ ] ‘11135’ [ ‘25’, ‘5’, ‘1’, ‘251’ ] ‘1135’
回溯的另一道题:
提示
分类树:
* 测试1
* 测试2
* 测试3
* 需要过滤掉
* 测试4
* 这个是父元素
* 测试5当我输入“测试”时,进行模糊匹配,需要将所有包含“测试”的节点都列出来。结果是这样的。
* 测试1
* 测试2
* 测试3
* 测试4
* 这个是父元素
* 测试5只要是父元素下面得子元素含有“测试”这个字符串,不管本身是否含有这个字符串都必须保留。
时间复杂度将近为O(n2)的写法,简单粗暴
// 判断这条分支上有没有匹配到的
function isNeedBranch (item, keyword) {
let flag1 = false,
flag2 = false
if(item.txt.indexOf(keyword) > -1) {
flag1 = true
} else if(item.children && item.children.length) {
item.children.forEach(child => {
if(isNeedBranch(child, keyword)) {
flag2 = true
}
})
}
return flag1 || flag2
}
// 给每个分支上有所匹配的设置isNeed
function setFlag (data, keyword) {
return data.map(item => {
item.isNeed = isNeedBranch(item, keyword)
if(item.children && item.children.length) {
setFlag(item.children, keyword)
}
return item
})
}
// 过滤掉分支上isNeed为false的元素
function treeFilter (data) {
return data.filter((item, index) => {
if(item && item.children && item.children.length) {
item.children = treeFilter(item.children)
}
return item.isNeed
})
}
setFlag(data, '测试') // 打上是否需要过滤掉的标签
treeFilter(data) // 过滤掉分支上isNeed为false的元素
事件复杂度为O(2n)的回溯
假设只有二层节点的时候的时候
let isNeed = true
node.forEach(item => {
if (item.txt.includes(keyword)) {
node.flag ? '' : node.flag = true
item.isNeed = true
} else {
item.isNeed = false
}
})
但是在正常情况下不会只有二层,所有我们需要写成递归,因为forEach中的item可能还会有children
// 利用回溯,优先遍历子节点,遍历完子节点然后给父节点打上标签,时间复杂度为O(2n)
const setFlag = (nodes, keyword) => {
// 给父节点返回的标志
let isParentNeed = false
nodes.forEach(item => {
if (item.children && item.children.length !== 0) {
item.isNeed = setFlag(item.children, keyword)
}
if (item.txt.includes(keyword)) {
isParentNeed ? '' : isParentNeed = true
item.isNeed = true
} else {
item.isNeed ? isParentNeed = true : item.isNeed = false
}
})
return isParentNeed
}
接下来就是过滤isNeed为false的节点了,完整代码示例:
const data = [{
id: 1,
txt: '测试1',
children: [{
id: 11,
txt: '测11',
children: [{
id: 111,
txt: '测试111',
children: [{
id: 1111,
txt: '这里就不是',
children: []
}]
}]
}, {
id: 12,
txt: '测试12',
children: [{
id: 121,
txt: '测试121',
children: [{
id: 1211,
txt: '测试1211',
children: []
}]
}]
}]
}, {
id: 2,
txt: '测试2',
children: [{
id: 21,
txt: '测试21'
}]
}]
function filterTree(nodes, keyword) {
// 利用回溯,优先遍历子节点,遍历完子节点然后给父节点打上标签,时间复杂度为O(n)
const setFlag = (nodes, keyword) => {
// 给父节点返回的标志
let isParentNeed = false
nodes.forEach(item => {
if (item.children && item.children.length !== 0) {
item.isNeed = setFlag(item.children, keyword)
}
// 两种情况:1.自身含有关键字 2.子元素含有关键字
if (item.txt.includes(keyword)) {
isParentNeed = true
item.isNeed = true
} else {
// 精髓所在
// item的子元素有isNeed,当前元素不能改为false
item.isNeed ? isParentNeed = true : item.isNeed = false
}
})
return isParentNeed
}
const filterNotIsNeed = nodes => {
return nodes.filter(item => {
if (item.children && item.children.length) {
item.children = filterNotIsNeed(item.children)
}
return item.isNeed
})
}
setFlag(nodes, keyword)
return filterNotIsNeed(nodes)
}
console.log(filterTree(data, '测试2'))
上面的方法是先在每个打上节点标识,表示是否保留,然后再遍历一次过滤那些不需要的节点,我原本以为可以做到复杂度为O(n)
,所以当确定当前子元素不需要时就可以直接删除,但是下标需要通过遍历才能取到(如果用过了splice后,后面的index就乱套了),但是由于下标的问题还是会大于O(n)2020-08-01更新
用for循环代替forEach,可以直接删除某个下标,然后i—,可以做到O(n)的时间复杂度
function filterTreeOne(nodes, keyword) {
const setFlagAndRemoveDeepChild = curNodes => {
let isParentNeed = false
for (let i = 0; i < curNodes.length; i++) {
const item = curNodes[i]
if (item.children && item.children.length !== 0) {
item.isNeed = setFlagAndRemoveDeepChild(item.children)
}
if (item.txt.includes(keyword)) {
isParentNeed ? '' : (isParentNeed = true)
item.isNeed = true
} else {
item.isNeed ? (isParentNeed = true) : (item.isNeed = false)
}
if (!item.isNeed) {
curNodes.splice(i, 1)
i--
}
}
return isParentNeed
}
setFlagAndRemoveDeepChild(nodes)
return nodes
}
递归&&动态规划
字符串
提示
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
function permutationsOfStr2(str, result = '') {
if (str.length === 1) {
console.log(result + str[0])
return
}
for (let i = 0; i < str.length; i++) {
const res = [...str]
const s = res.splice(i, 1)
permutationsOfStr2(res, result + s)
}
}
let str = ['a', 'b', 'c']
permutationsOfStr2(str)
提示
字符串的子序列
,则打印,ab,ac,abc,b,bc,cfunction getSubsequence(str, index = 0, result = '') {
if (index === str.length){
console.log(result)
return
}
getSubsequence(str, index + 1, result)
getSubsequence(str, index + 1, result + str[index])
}
在一个矩阵中求最短路径
提示
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入: [ [1,3,1], [1,5,1], [4,2,1] ]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
普通递归 暴力破解 时间复杂度:O(2^i+j) 空间复杂度
+n(递归深度m+n)function walk(matrix, i, j) {
if (i === matrix.length - 1 && j === matrix[0].length - 1) {
return matrix[i][j]
}
if (j === matrix[0].length - 1) {
return matrix[i][j] + walk(matrix, i + 1, j)
}
if (i === matrix.length - 1) {
return matrix[i][j] + walk(matrix, i, j + 1)
}
const right = walk(matrix, i, j + 1)
const down = walk(matrix, i + 1, j)
return matrix[i][j] + Math.min(right, down)
}
动态规划,逆向思维来求出最短路径
两层for循环:时间复杂度
+n,空间复杂度+nfunction dpOneWalk(matrix) {
let dp = JSON.parse(JSON.stringify(matrix))
for (let i = matrix.length - 1; i >= 0; i--) {
for (let j = matrix[0].length - 1; j >= 0; j--) {
if (i === matrix.length - 1 && j !== matrix[0].length - 1) {
dp[i][j] = matrix[i][j] + dp[i][j + 1]
} else if (j === matrix[0].length - 1 && i !== matrix.length - 1) {
dp[i][j] = matrix[i][j] + dp[i + 1][j]
} else if (j !== matrix[0].length - 1 && i !== matrix[0].length - 1) {
dp[i][j] = matrix[i][j] + Math.min(dp[i + 1][j], dp[i][j + 1])
} else {
dp[i][j] = matrix[i][j]
}
}
}
return dp[0][0]
}
上面用的方法是另外新建一个dp数据,但是想想其实也可以不用新建额外的空间,从数组的最后一列最后一个行开始往前走,当前的路径和就是本身加上后面一个,到了倒二层时,就可以用到最后一层的数据了。思路和上面的方法差不多,代码如下:
时间复杂度:m+n,空间复杂度: 1
function dpTwoWalk(matrix) {
for (let i = matrix.length - 1; i >= 0; i--) {
for (let j = matrix[0].length - 1; j >= 0; j--) {
if (i == matrix.length - 1 && j != matrix[0].length - 1)
matrix[i][j] = matrix[i][j] + matrix[i][j + 1];
else if (j == matrix[0].length - 1 && i != matrix.length - 1)
matrix[i][j] = matrix[i][j] + matrix[i + 1][j];
else if (j != matrix[0].length - 1 && i != matrix.length - 1)
matrix[i][j] = matrix[i][j] + Math.min(matrix[i + 1][j], matrix[i][j + 1]);
}
}
return matrix[0][0]
}