数组
一种线性表数据结构,用一组连续的内存空间
,存储一组具有相同类型的数据
正是因为这两个限制,它才有了一个堪称“杀手锏”的特性:⭐️ “随机访问”
。
但有利就有弊,这两个限制也让数组的很多操作变得非常低效,比如要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。
数组支持随机访问
,根据下标随机访问的时间复杂度为O(1)
为什么大多数编程语言中,数组要从0开始编号,而不是从1开始呢?
从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移(offset)”。如果用a[k]来表示数组的首地址,a[0]就是偏移为0的位置,也就是首地址,a[k]就表示偏移k个type_size的位置,所以 计算a[k]的内存地址只需要用这个公式: a[k]address =base_address + k * type_size
但是,如果数组从1开始计数,那我们计算数组元素a[k]的内存地址就会变为: a[k]address = base_address + (k-1) * type_size
对比两个公式,我们不难发现,从1开始编号,每次随机访问数组元素都多了一次减法运算,对于CPU来 说,就是多了一次减法指令。
数组作为非常基础的数据结构,通过下标随机访问数组元素又是其非常基础的编程操作,效率的优化就要尽可能做到极致。
所以为了减少一次减法操作,数组选择了从0开始编号,而不是从1开始。
Linked List - 单链表
插入,删除为 O(1)
实战 LeetCode 题目
206. 反转链表
迭代:不断用变量的旧值推出新值的过程
var reverseList = function(head) {
let cur = head, pre = null
while (cur){
const next = cur.next
cur.next = pre
pre = cur
cur = next
}
return pre
};
24. 两两交换链表中的节点
迭代
猿来绘(逻辑清晰,简单易懂)- 24. 两两交换链表中的节点
var swapPairs = function(head) {
let dummyHead = new ListNode()
dummyHead.next = head
// 要交换的两个节点的 前一个节点
let pre = dummyHead, a, b
while (pre && pre.next && pre.next.next) {
a = pre.next
b = a.next
// 两两之间进行交换
pre.next = b
a.next = b.next
b.next = a
// pre 指向交换后的第二个节点
pre = a
}
return dummyHead.next
};
递归
var swapPairs = function(head) {
if (!head || !head.next) {
return head;
}
let nextNode = head.next;
head.next = swapPairs(nextNode.next);
nextNode.next = head;
return nextNode;
};
141. 环形链表
思路
- 设定个时间,如果超时,代表有环
- Set 判重 时间复杂度O(n)
- 快慢指针 O(n)
Set 判重
var hasCycle = function(head) {
let visited = new Set()
while (head) {
if (visited.has(head)) {
return true
}
visited.add(head)
head = head.next
}
return false
};
快慢指针
var hasCycle = function(head) {
let fast = slow = head
while (fast && fast.next) {
fast = fast.next.next
slow = slow.next
if (fast === slow) return true
}
return false
};
142. 环形链表 II
var detectCycle = function(head) {
let fast = slow = head
while (fast && fast.next) {
fast = fast.next.next
slow = slow.next
if(fast === slow) {
// 第一次相遇,设 slow 走了 k, fast 走了 nk (n >= 1), 环起点与相遇点距离为 m, 与 head 距离为 k - m
slow = head
// 走 k - m, 相等时为相遇点
while (slow !== fast) {
fast = fast.next
slow = slow.next
}
return slow
}
}
return null
};
K 个一组翻转链表
// 翻转子链表,例如
// 5 -> 1 -> 2 -> 3 -> 8
// cur pre 1 -> 8
// pre cur 2 -> 1
// pre cur 3 -> 2
// pre cur | 退出 此时 pre = tail , tail 未连接 tail 就退出了
// 3 -> 2 -> 1 -> 8 && 5 -> 1
// pre.next = head => 5 -> 3 -> 2 -> 1 -> 8
const myReverse = (head, tail) => {
let pre = tail.next, cur = head
while (pre !== tail) {
const nex = cur.next
cur.next = pre
pre = cur
cur = nex
}
return [tail, head]
}
var reverseKGroup = function(head, k) {
const hair = new ListNode()
hair.next = head
let pre = hair
while (head) {
let tail = pre
for (let i = 0; i < k; i++) {
tail = tail.next
if(!tail) {
return hair.next
}
}
[head, tail] = myReverse(head, tail)
pre.next = head
pre = tail
head = tail.next
}
return hair.next
};
栈、队列
Stack - First In Last Out (FILO) 先进后出
Queue - First In First Out (FIFO) 先进先出
844. 比较含退格的字符串
重构字符串
var backspaceCompare = function(s, t) {
return build(s) === build(t)
};
const build = function(s) {
let stack = []
for (let i = 0; i < s.length; i++) {
if (s[i] === '#') {
if (s.length > 0) {
stack.pop()
continue
}
}
stack.push(s[i])
}
return stack.join("")
}
var backspaceCompare = function(s, t) {
let i = s.length - 1, j = t.length - 1
let skipS = skipT = 0
while (i >= 0 || j >= 0) {
while (i >= 0) {
if (s[i] === '#') { // 多次匹配 #
skipS++
// 跳过本次的 #
i--
} else if (skipS) {
skipS--
// 跳过匹配的 # 需跳过的字符
i--
} else { // 匹配字符
break
}
}
while (j >= 0) {
if (t[j] === '#') {
skipT++
j--
} else if (skipT) {
skipT--
j--
} else {
break
}
}
if (s[i] !== t[j]) return false
i--
j--
console.log(i,s[i],j,t[j],s[i] === t[j]);
}
return true
};
// console.log(backspaceCompare("bbbextm", "bbb#extm"));
// 5 t 6 t true
// 4 x 5 x true
// 3 e 4 e true
// 2 b 3 # false
// 1 b 0 b true
// 0 b -1 undefined false
// false
232. 用栈实现队列
双栈,一个输入栈,一个输出栈
每次 pop
或 peek
时,若输出栈为空
则将输入栈
的全部数据依次弹出并压入输出栈
,这样输出栈的顺序就是队列从队首往队尾的顺序
var MyQueue = function() {
this.inStack = []
this.outStack = []
};
MyQueue.prototype.push = function(x) {
this.inStack.push(x)
};
MyQueue.prototype.pop = function() {
this.in2out()
return this.outStack.pop()
};
MyQueue.prototype.peek = function() {
this.in2out()
return this.outStack[this.outStack.length - 1]
};
MyQueue.prototype.empty = function() {
return this.inStack.length == 0 && this.outStack.length == 0
};
MyQueue.prototype.in2out = function() {
if (!this.outStack.length) {
while (this.inStack.length) {
this.outStack.push(this.inStack.pop())
}
}
};
20. 有效的括号
var isValid = function (s) {
let pairs = {
'(': ')',
'{': '}',
'[': ']',
}
let stack = []
for (let ch of s) {
if (ch in pairs) {
stack.push(ch)
} else if(!stack || pairs[stack.pop()] != ch) {
return false
}
}
return stack.length === 0
};