目录
(1)双循环实现(因为有“跳步”的存在,所以时间复杂度为O(n))
前言
学习数据结构和算法的记录
1. 一个数组旋转 K 步
(1)方法一
时间复杂度为O(n)
function rotate(arr,k){
const len = arr.length;
if(!len || !k) return arr
//取余数避免重复操作
const step = Math.abs(k % len);//取绝对值
for(let i = 0; i< step;i++){
let n = arr.pop()
if(n) arr.unshift(n)
}
return n
}
(2)方法二
function rotate(arr,k){
const len = arr.length;
if(!len || !k) return arr;
//取余数避免重复操作
const step = Math.abs(k % len)
const slice1 = arr.slice(-step)
const slice2 = arr.slice(0,len - step)
//不会影响原数组
const slice3 = slice1.concat(slice2)
return slice3
}
(3)方法三
function rotate (arr,k){
const len = arr.length
if(!len || !k) return arr
//取余数避免重复操作
const step = k % len
if(step == 0 || isNaN(step)) return arr
const slice1 = arr.slice(-k)
const slice2 = arr.slice(0,-k)
const slice3 = slice1.concat(slice2)
//不会修改原数组
return slice3
}
2.判断一个字符串是否匹配括号
function isMatch(left,right){
if(left == '{' && right == '}') return true;
if(left == '[' && right == ']') return true;
if(left == '(' && right == ')') return true;
return false
}
function MatchBracket(str){
const len = str.length
if(!len) return true
const leftSymbol = '{[(';
const rightSymbol = '}])'
const stack = []
for(let i =0 ; i < len ; i++){
const s = str[i]
//入栈
if(leftSymbol.includes(s)){
stack.push(s)
}else if(rightSymbol.includes(s)){
const left = stack[ stack.length - 1 ]
//出栈
if(isMatch(left,s)){
stack.pop()
}else{
return false
}
}
}
return stack.length
}
3. 数组创建链表
function creatLinkList(arr){
const len = arr.length;
if(!len) return throw new Error('arr is empty')
let curNode = {
value : arr[ arr.length -1 ],
}
if(len == 1) return curNode
for(let i = len - 2 ; i >= 0 ; i --){
curNode = {
value:arr[i],
next:curNode
}
}
return curNode
}
4.JS 反转单向链表
(1)方法一
//node 的结构为 {value:1,next:{value:2}}
function reserveLink(node){
let curNode = null
let preNode = null
let nextNode = node;
while(nextNode){
if(!preNode){
delete curNode.next
}
if(preNode && curNode){
curNode.next = preNode
}
preNode = curNode
curNode = nextNode
nextNode = nextNode.next
}
curNode.next = preNode
return curNode
}
(2)方法二
//node 的结构为 {value:1,next:{value:2}}
function reserveLink(node){
let curNode = {
value:node.value
}
let nextNode = node.next
while(nextNode){
curNode = {
value : nextNode.value,
next:curNode
}
nextNode = nextNode.next
}
return curNode
}
5.两个栈实现一个队列
(1)方法一
class MyQueue{
private stack1 = []
private stack2 = []
add(n){
this.stack1.push(n)
}
delete(){
const stack1 = this.stack1
const stack2 = this.stack2
while(stack1.length){
const n = stack1.pop()
if(n) stack2.push(n)
}
const res = stack2.pop()
while(stack2.length){
const n = stack2.pop()
if(n) stack1.push(n)
}
return res || null
}
get length(){
return this.stack1.length
}
}
(2)方法二
class MyQueue {
private stack1 = []
private stack2 = []
add(n){
this.stack1.push(n)
}
delete(){
const stack1 = this.stack1;
const stack2 = this.stack2
//继续吐出上次未吐完的
if(stack2.length) return stack2.pop()
while(stack1.length){
const n = stack1.pop()
if(n) stack2.push(n)
}
let res = stack2.pop()
return res || null
}
}
6.链表实现队列
class MyQueue{
private head = null
private tail = null
private len = 0
add(n){
const curNode = {
value:n,
next:null
}
if(!head) this.head = curNode
const tailNode = this.tail
if(tailNode) tailNode.next = curNode
this.tail = curNode
this.len ++
}
delete(){
const headNode = this.head
if(headNode == null) return null
if(len == 0) return null
const val = headNode.value
this.head = headNode.next
this.len --
return val
}
get length(){
return this.len
}
}
7.二分查找
(1)循环实现
function binarySearch(arr,target){
const len = arr.length
if(!len) return -1
let startIndex = 0;
let endIndex = len -1;
while( startIndex <= endIndex){
const midIndex = Math.floor((startIndex + endIndex) / 2)
const midValue = arr[midIndex]
if(target < midValue){
//目标值较小,则继续在左侧寻找
endIndex = midIndex - 1
}else if(target > midValue){
//目标值较大,则继续在右侧寻找
starIndex = midIndex + 1
}else{
return midIndex
}
}
//找不到目标值时
return -1
}
(2)递归
function binarySearch(arr,target,startIndex,endIndex){
const len = arr.length;
if(!len) return -1
if(!startIndex) startIndex = 0
if(!endIndex) endIndex = len - 1
//相遇则代表没有找到
if(startIndex > endIndex) return -1
const midIndex = Math.floor((startIndex + endIndex) / 2)
const midValue = arr[midIndex]
if(target < midValue){
//目标值较小,则继续在左侧寻找
endIndex = midIndex - 1
binarySearch(arr,target,startIndex,endIndex)
}else if(target > midValue){
//目标值较大,则继续在右侧寻找
startIndex = midIndex + 1
binarySearch(arr,target,startIndex,endIndex)
}else{
return midIndex
}
}
8.寻找一个数组中和为 n 的两个数
(1)常规思路双循环(时间复杂度O(n^2))
function findTwoNunbers(arr,n){
const res = []
const len = arr.length
if(!len) return res
for(let i = o; i < len -1; i++){
const n1 = arr[i]
let flag = false
for(let j = i + 1; j < len; j ++){
const n2 = arr[j]
if(n1 + n2 === n){
res.push(n1)
res.push(n2)
flag = true
break
}
}
if(flag) break
}
return res
}
(2)嵌套二分查找,时间复杂度为O(logn)
function findTwoNumbers(arr,n){
const res = []
const len = arr.length
if(!len) return res
for(let i = 0; i < len -1; i++){
const n1 = arr[i]
const n2 = n - n1
if(binarySearch(arr,n2)){
res.push(n1)
res.push(n2)
break
}
}
return res
}
(3)数组是有序的,利用双指针查找,时间复杂度O(n)
function findTwoNumbers(arr,n){
const len = arr.length
const res = []
if(!len) return res
let i = 0
let j = len - 1
while(i < j){
const n1 = arr[i]
const n2 = arr[j]
const sum = n1 + n2
if(sum > n){
//如果 sum 比 n 大,则 j 向前移动
j--
}else if(sum < n){
//如果 sum 比 n 小,则 i 向后移动
i++
}else{
res.push(n1)
res.push(n2)
break
}
}
return res
}
9.二叉搜索树的第k最小值
前言:二叉搜索树的遍历顺序:前序、中序、后序
(1)定义二叉搜索树
const node ={
value:5,
left:{
value:3,
left:{
value:2,
left:null,
right:null
},
right:{
value:4,
left:null,
right:null
}
},
right:{
value:7,
left:{
value:6,
left:null,
right:null
},
right:{
value:8,
left:null,
right:null
}
}
}
(2)二叉搜索树三种遍历实现的代码
let arr = []
//二叉搜索树 前序遍历 root -> left -> right
function preOrderTraverse(node){
if(!node) return null
console.log(node.value)
arr.push(node.value)
preOrderTraverse(node.left)
preOrderTraverse(node.right)
}
//二叉搜索树 中序遍历(遍历后数据是有序的) left -> root -> right
function inOrderTraverse(node){
if(!node) return null
inOrderTraverse(node.left)
console.log(node.value)
arr.push(node.value)
inOrderTraverse(node.right)
}
//二叉搜索树后序遍历 left -> right -> root
function postOrderTraverse(node){
if(!node) return null
postOrderTraverse(node.left)
postOrderTraverse(node.right)
console.log(node.value)
arr.push(node.value)
}
//前序遍历后结果
preOrderTraverse(node)
//arr 数据为 [5, 3, 2, 4, 7, 6, 8]
//中序遍历后结果
arr = []
inOrderTraverse(node)
//arr 数据为 [2, 3, 4, 5, 6, 7, 8]
//后序遍历后结果
arr =[]
postOrderTraverse(node)
//arr 数据为 [2, 4, 3, 6, 8, 7, 5]
(3)求二叉搜索树的第k最小值
function getKthValue(node,k){
//二叉树中序循环
inOrderTraverse(node)
return arr[k] || null
}
10. 求斐波那契数列的第 n 值
(1)递归(时间复杂度为 2^n)
function fibonacci(n){
if(n <= 0) return 0
if(n === 1) return 1
return fibonacci(n - 1) + fibonacci(n - 2)
}
(2)循环(时间复杂度为O(n))
function fibonacci(n){
if( n <= 0) return 0
if( n === 1) return 1
let n1 = 1;// n-1
let n2 = 0;//n - 2
let res = 0
for(let i = 2; i <= n; i++){
res = n1 + n2
//记录中间值
n2 = n1
n1 = res
}
return res
}
11.移动0到数组的末尾
(1)循环实现 (时间复杂度为O(n^2))
function moveZero(arr){
const len = arr.length
if(!len || len < 2) return
let zeroLen = 0
for(let i = 0; i < len - zeroLen; i++){
if(arr[i] == 0){
arr.push(0)
arr.splice(i,1) //这个操作本身就是 O(n)
zeroLen ++
}
}
}
(2)循环+双指针实现(时间复杂度O(n))
//方法一
function moveZero(arr){
const len = arr.length
if(!len || len < 2) return
let i
let j = - 1;//数组中第一个 0 的下标
for(let i = 0 ; i< len ; i++){
if(arr[i] == 0 && j < 0){
//赋值 j 第一个 0 的下标
j = i
}
if(arr[i] !== 0 && j >= 0){
const n = arr[j]
arr[j] = arr[i]
arr[i] = n
j++
}
}
}
//方法二
function moveZero(arr){
const len = arr.length
if(!len || len < 2) return
let j = arr.indexOf(0)
let i = j + 1
while(i < len){
if(arr[i] !== 0){
arr[j] = arr[i]
arr[i] = 0
j++
}
i++
}
}
12.获取字符串中连续最多的字符以次数
(1)双循环实现(因为有“跳步”的存在,所以时间复杂度为O(n))
function findCountinounsChart1(str){
const len = str.length
const res = {
str:'',
len:0,
}
if(!len) return res
let tempLen = 0
for(let i = 0; i < len; i++){
tempLen = 0
for(let j = 0; j < len; j++){
if(str[i] == str[j]){
tempLen++
}
if(str[i] !== str[j] || j == len - 1){
if(tempLen > res.len){
res.len = tempLen
res.str = str[i]
}
if(i < len - 1){
i = j - 1 //跳步,j - 1 是因为下一次进入循环 i++
}
break
}
}
}
return res
}
(2)双指针实现(时间复杂度O(n))
function findCountinounsChart2(str){
const len = str.length
const res = {
str:'',
len:0
}
let tempLen = 0
let i = 0
let j = 0
for(; i < len; i++){
if(str[i] == str[j]){
tempLen++
}
if(str[i] !== str[j] || i == len - 1){
if(tempLen > res.len){
res.len = tempLen
res.str = str[j]
}
tempLen = 0
if(i < len - 1){
j = i
i-- //细节为了下次进入循环i 和 j 保持一致
}
}
}
return res
}
13.用S实现快速排序
//方法一与方法二时间复杂度统一为O(n*logn)
//方法一,用 splice 实现
function quickSort1(arr){
const len = arr.length
if(!len) return arr
const midIndex = Math.floor(len / 2)
const midValue = arr.splice(midIndex,1)
const left = [] //小于中间值的放在left
const right = [] //大于中间值的放在right
//splice 会更改原数组,所以这里需要使用 arr.length
for(let i = 0; i < arr.length; i++){
const n = arr[i]
if(n < midValue){
left.push(n)
}else{
right.push(n)
}
}
return quickSort1(left).concat([midValue],quickSort1(right))
}
//方法二 用 slice 实现
function quickSort2(arr){
const len = arr.length
if(!len) return len
const midIndex = Math.floor(len / 2)
const midValue = arr.slice(midIndex,midIndex + 1)
const left = []
const right = []
for(let i = 0; i < len; i++){
if(i !== midIndex){
const n = arr[i]
if(n < midValue){
left.push(n)
}else{
right.push(n)
}
}
}
return quickSort2(left).concat([midValue],quickSort2(right))
}
14. 获取1-10000之前所有的对称数(回文数)
(1)思路1-使用数组反转、比较
function findPalindromeNumbers1(max){
const res = []
if(max <= 0) return res
for(let i = 1; i <= max; i++){
const s = i.toString()
if(s == s.split('').reverse().join('')){
res.push(i)
}
}
return res
}
(2)思路2-字符串头尾比较
function findPalindromeNumbers1(max){
const res = []
if(max <= 0) return res
for(let i = 1; i <= max; i++){
const s = i.toString()
const len = s.length
let startIndex = 0
let endIndex = len - 1
let flag = true
while(startIndex < endIndex){
if(s[startIndex] !== s[endIndex]){
flag = false
break
}else{
startIndex++
endIndex--
}
}
flag && res.push(i)
}
return res
}
(3)思路3 生成翻转数
function findPalindromeNumbers1(max){
const res = []
if(max <= 0) return res
for(let i = 1; i <= max; i++){
let n = i
let rev = 0
while(n > 0){
rev = rev * 10 + n % 10
n = Math.floor(n/10)
}
rev == i && res.push(i)
}
return res
}
(4)性能分析
- 思路1 - 看似O(n),但数组转换,操作都需要时间,所以慢
- 思路2 VS 思路3 ,操作数字更快(电脑原型就是计算器)
15.用 JS 实现数字千分位格式化
(1)用数组实现
function format1(n:number):string{
n = Math.floor(n)
const s = n.toString()
const arr = s.split('').reverse()
return arr.reduce((prev,val,index) =>{
if(index%3 == 0){
if(prev){
return val +','+prev
}else{
return val
}
}else{
return val + prev
}
},'')
}
(2)字符拆分
function format2(n:number):string{
n = Math.floor(n)
const s = n.toString()
const len = s.length
let res = ''
for(let i = len - 1; i >= 0; i++){
const j = len - i
if(j % 3 == 0){
if(i == 0){
res = s[i] + res
}else{
res = ',' + s[i] + res
}
}else{
res = s[i] + res
}
}
return res
}
(3)数字实现
function format3(n:number):string{
const num = Math.floor(n)
let format = ''
while(num > 0){
let res:number|string = num % 1000
num = Math.floor(num/1000)
if(num > 0){
if(res == 0){
res = ',000'
}else if(res < 10){
res = ',00'+res
}else if(res < 100){
res = ',0'+res
}else if(res < 1000){
res = ','+res
}
}else{
//小于等于0则说明是最后三位,则无需加千位分隔符
res = res
}
format = res + format
}
return format
}
16.切换字母大小写
(1)正则表达式
function switchLetterCase1(str:string):string{
let res = ''
if(!str.length) return res
const reg1 = /[a-z]/ //小写字母正则
const reg2 = /[A-Z]/ //大写字母正则
for(let i = 0; i < str.length; i++){
const n = str[i]
if(reg1.test(n)){
res = res + n.toLocaleUpperCase()
}else if(reg2.test(n)){
res = res + n.toLocaleLowerCase()
}else{
res = res + n
}
}
return res
}
(2)通过 SSCII 码判断
function switchLetterCase1(str:string):string{
let res = ''
if(!str.length) return res
for(let i = 0; i < str.length; i++){
const n = str[i]
const code = n.charCodeAt(0)
if(code >= 65 && code <= 90){
//大写转小写
res = res + n.toLocaleLowerCase()
}else if(code >= 97 && code <= 122){
//小写转大写
res = res + n.toLocaleUpperCase()
}else{
res = res + c
}
}
return res
}
总结
继续学习