前端数据结构与算法入门之路(一)(链表)

235 阅读5分钟

船长表情包镇楼

图片名称

链表

  • 结点
    • 数据域
    • 指针域
      • 实现方式包括地址、下标(相对地址)、引用
  • 链状结构
    • 通过指针域的值形成了一个线性结构
  • 特点
    1. 链表中的每个结点至少包含两个部分:数据域和指针域
    2. 链表中的每个结点,通过指针域的值,形成一个线性结构
    3. 查找结点O(n),插入结点O(1),删除结点O(1)
    4. 不适合快速的定位数据,适合动态插入以及删除的应用场景
  • 应用场景
    • 操作系统内的动态内存分配
    • LRU(Least Recently Used)缓存淘汰算法(近期最少使用)

船长金句:学数据结构,学算法,学的是思维方式,不是学的具体程序的实现,具体程序的实现是根据编程的经验来的,同一种思想,可以用不同的方式表达出来

LeetCode肝题(以训练为目的)

    1. 环形链表
// 判断链表是否有环可以使用快慢指针,每次让快指针比慢指针多走一步,如果相遇则有环
var hasCycle = function(head) {
    if (!head) return false
    let p = head, q = head
    while(q && q.next) {
        p = p.next
        q = q.next.next
        if (p == q) return true
    }
    return false
};
    1. 环形链表 II
// 首先判断链表是否有环,当链表有环时让q指针也就是快指针从head开始走,两指针相遇时就是入环的第一个结点
var detectCycle = function(head) {
    if(!head) return null
    let p = head, q = head.next
    while(q && q.next) {
        p = p.next
        q = q.next
        if (p == q) {
            q = head
            while(p != q) {
                p = p.next
                q = q.next
            }
            return p
        }
        q = q.next
    }
    return null
};
    1. 快乐数
// 如果不是快乐数,那么这些数一定是有环的,设计一个next函数返回下一个数
// 当p和q相等证明数是无限循环的,如果q最终走到1则说明是快乐数
var nextNum = function(num) {
    let sum = 0
    while(num > 0) {
        sum += (num % 10) * (num % 10)
        num = parseInt(num / 10)
    }
    return sum
}
var isHappy = function(n) {
    if (n == 1) return true
    let p = nextNum(n), q = nextNum(nextNum(n))
    while(q != 1 && nextNum(q) != 1) {
        p = nextNum(p)
        q = nextNum(q)
        if (p == q) return false
        q = nextNum(q)
    }
    return true
};
    1. 反转链表
// 三指针的方式
var reverseList = function(head) {
    if (!head) return head
    let pre = null, cut = head, p = head.next
    while(cut) {
        cut.next = pre
        pre = cut
        cut = p
        if (p) p = p.next
    }
    return pre
};
// 递归的方式
var reverseList = function(head) {
    if (!head || !head.next) return head
    let tail = head.next, p = reverseList(head.next)
    head.next = tail.next
    tail.next = head
    return p
};
    1. 反转链表 II
// 修改反转链表的实现,使其能反转n个结点
var reverseN = function(head, n) {
    if (n == 1) return head
    let tail = head.next, p = reverseN(head.next, n - 1)
    head.next = tail.next
    tail.next = head
    return p
}
// 定义一个虚头指向head,根据left的值让p指针指向反转结点的前一个结点,right和left的差值加1是反转链表的数量
// 让p的next指向指向反转后的链表
var reverseBetween = function(head, left, right) {
    let ret = new ListNode(null, head), p = ret, num = right - left + 1
    while(--left) {
        p = p.next
    }
    p.next = reverseN(p.next, num)
    return ret.next
};
    1. K 个一组翻转链表
// 反转链表n个结点的方法
var __reverseN = function(head, n) {
    if (n == 1) return head
    let tail = head.next, p = __reverseN(head.next, n - 1)
    head.next = tail.next
    tail.next = head
    return p
}
// 判断是否需要进行反转,如果剩余结点个数小于n,直接返回head
var reverseN = function(head, n) {
    let cut = n, p = head
    while(--n && p) p = p.next
    if (!p) return head
    return __reverseN(head, cut)
}
// 定义一个虚头ret指向head,p指针指向ret(p指向反转结点的前一个结点),q指针指向p.next(也是反转过后下一轮待反转结点的前一个结点)
// 令p.next指向反转后的结点,如果和q相等,则证明反转结束,返回ret.next
// 如果p.next和q不相等,则证明反转结点成功,准备进行下一轮反转,让p指向q,q指向q.next
var reverseKGroup = function(head, k) {
    let ret = new ListNode(null, head), p = ret, q = p.next
    while((p.next = reverseN(q, k)) != q) {
        p = q
        q = q.next
    }
    return ret.next
};
    1. 旋转链表
// 定义p指针指向尾结点并求出链表长度,将链表首尾相连,k对长度取模就是旋转次数
// 向右旋转k次也是向左旋转长度-k次(向左旋转更简单,将head指针指向next)
// 最终把尾结点与头结点的连接断开
var rotateRight = function(head, k) {
    if (!head) return head
    let p = head, cut = 1
    while(p.next) {
        p = p.next
        cut++
    }
    console.log(cut)
    p.next = head
    k = cut - k % cut
    while(k--) {
        head = head.next
        p = p.next
    }
    p.next = null
    return head
};
    1. 两两交换链表中的结点
// 同25. K 个一组翻转链表,是k=2的具体情况
var swapPairs = function(head) {
    let ret = new ListNode(null, head), p = ret, q = p.next
    while((p.next = reverseN(q, 2)) != q) {
        p = q
        q = q.next
    }
    return ret.next
};
    1. 删除链表的倒数第 N 个结点
// 定义一个虚头指向头结点,定义p指针指向虚头,q指针指向头结点
// 让q指针移动n个结点,如果q为空就说明倒数第n个结点是头结点
// 否则让p、q一起移动,当q指针指向空时,p指针指向待删除结点的前一个结点
var removeNthFromEnd = function(head, n) {
    if (!head) return head
    let ret = new ListNode(null, head), p = ret, q = p.next
    while(n--) {
        q = q.next
    }
    if (!q) return head.next
    while(q) {
        p = p.next
        q = q.next
    }
    p.next = p.next.next
    return ret.next
};
    1. 删除排序链表中的重复元素
// 定义指针p遍历比对值
var deleteDuplicates = function(head) {
    let p = head
    while(p && p.next) {
        if (p.val == p.next.val) {
            p.next = p.next.next
        } else {
            p = p.next
        }
    }
    return head
};
    1. 删除排序链表中的重复元素 II
// 定义指针p遍历比对值,当查询到重复元素时定义q指针继续向后查询
var deleteDuplicates = function(head) {
    let ret = new ListNode(null, head), p = ret, q
    while(p.next) {
        if (p.next.next && p.next.val == p.next.next.val) {
            q = p.next.next
            while(q && q.val == p.next.val) {
                q = q.next
            }
            p.next = q
        } else {
            p = p.next
        }
    }
    return ret.next
};
    1. 分隔链表
// 定义两个虚拟头结点,遍历head链表,将小于x的结点接到ret1上,大于等于x的接到ret2上,最后把ret2上的结点接到ret1上
var partition = function(head, x) {
    let ret1 = new ListNode(0, null), ret2 = new ListNode(0, null), p1 = ret1, p2 = ret2, q = head
    while(q) {
        if (q.val < x) {
            p1.next = q
            p1 = p1.next
            q = q.next
        } else {
            p2.next = q
            p2 = p2.next
            q = q.next
        }
    }
    p2.next = null
    p1.next = ret2.next
    return ret1.next
};
    1. 复制带随机指针的链表
var copyRandomList = function(head) {
    if (!head) return head
    let p = head, new_head, q;
    // 复制当前结点并插入当前结点后面,这样得到了一个复制后的链表
    while(p) {
        q = new Node(p.val)
        q.next = p.next
        q.random = p.random
        p.next = q
        p = q.next
    }
    // 遍历链表,修改随机指针的指向
    p = head.next
    while(p) {
        if (p.random) p.random =  p.random.next
        p = p.next
        if (p) p = p.next
    }
    // 将复制的结点连在一起组成新的链表
    p = head
    new_head = head.next
    while(p) {
        q = p.next
        p.next = q.next
        if (p.next) q.next = p.next.next
        p = p.next
    }
    return new_head
};