数组、链表、栈、队列

数组

一种线性表数据结构,用一组连续的内存空间,存储一组具有相同类型的数据
正是因为这两个限制,它才有了一个堪称“杀手锏”的特性:⭐️ “随机访问”
但有利就有弊,这两个限制也让数组的很多操作变得非常低效,比如要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。
数组支持随机访问,根据下标随机访问的时间复杂度为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. 环形链表

思路

  1. 设定个时间,如果超时,代表有环
  2. Set 判重 时间复杂度O(n)
  3. 快慢指针 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. 用栈实现队列

双栈,一个输入栈,一个输出栈

每次 poppeek 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈的顺序就是队列从队首往队尾的顺序

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
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值