上一篇文章可能讲了太多偏向理论的东西,如今就利用理论来实践一下。本文精选了八道经典的链表实际运用的问题。若是如今都还不能手写一个链表的话建议去上一章按照示例代码重复几遍。面试
什么是回文字符串呢?即正着读反着读都同样,好比abbba这个字符串就是一个回文字符串。那若是在面试中别人问了这个问题该如何经过链表来解决了?请你们思考一段时间,想好以后再看下面答案:算法
class linkedList {
constructor() {
this.head = null
}
isPalindromeString () {
let prev = null;
let slow = this.head;
let fast = this.head;
while (fast && fast.next) {
fast = fast.next.next;
let next = slow.next;
slow.next = prev;
prev = slow;
slow = next;
}
if (fast) {
slow = slow.next;
}
while (slow) {
if (slow.element !== prev.element) {
return false;
}
slow = slow.next;
prev = prev.next;
}
return true;
}
}
let test = new linkedList()复制代码
在这里首先运用两个指针,一个快指针和一个慢指针。快指针跑两步而慢指针跑一步,每一步慢指针都将原来结点的指向而指向前一个地方。当快指针到了边界后就中止遍历,这时须要判断整个链表是奇数仍是偶数,奇数的话则慢指针走一步,以后经过慢指针记录的当前位置再从新遍历链表,看是否相等。下面有个测试用例,只需把head值改掉就能够了:缓存
this.head = {
element: 'a',
next: {
element: 'b',
next: {
element: 'c',
next: {
element: 'c',
next: {
element: 'b',
next: {
element: 'a',
next: null
}
}
}
}
}
}复制代码
顾名思义,就是将整个链表反转过来。若是用双向链表来作则十分的简单,可是本题是单链表来反转,看答案以前请思考一下:bash
reserveLinked () {
let prev = null
let currNode = this.head
while (currNode.next) {
let state = true // 判断边界调节
let next = currNode.next //存储下一个结点
// 链表指针反转
currNode.next = prev
prev = currNode
// 边界判断
if (next.next == null) {
next.next = currNode
state = false
}
currNode = next
if (!state) break
}
}复制代码
这题的解决思路是遍历链表的是时候将每一个结点的指向都取反,这样就能够不用新建一个链表就能实现。数据结构
链表中的环的检测顾名思义就是检测一条链表内是否有环。在看答案以前前思考如何作:post
hasCircle () {
let fastIndex = this.head
let slowIndex = this.head
let currIndex = this.head
while (currIndex.next) {
if (fastIndex.next && fastIndex.next.next) {
fastIndex = fastIndex.next.next
}
else {
return false
}
slowIndex = slowIndex.next
if (slowIndex === fastIndex) {
return true
}
}
}复制代码
本题的作法主要是经过两个指针来实现的:一个快指针一个慢指针。快指针走两步慢指针走一步。恰好到环的入口的时候这两个指针会恰好指向环的入口。下面有一个环的测试数据,能够拿这个来测试:测试
this.circleLink = {
element: 'a',
next: {
element: 'b',
next: {
element: 'c',
next: {
element: 'd',
next: null
}
}
}
}
let temp = this.circleLink
while (temp.next) {
temp = temp.next
if (!temp.next) {
temp.next = this.circleLink
break
}
}
this.head = {
element: 'head',
next: this.circleLink
}复制代码
链表里的另外一个比较常见的操做是链表合并,简单点就是两个有序的链表合并成一个有序链表。其实用双向链表来作的话可能更加简单。在看作法以前先思考一下:ui
class Node {
constructor (element) {
this.element = element
this.next = null
}
}
class linkedList {
constructor() {
this.firstLink = {
element: 1,
next: {
element: 3,
next: {
element: 5,
next: {
element: 7,
next: {
element: 9,
next: null
}
}
}
}
}
this.secondLink = {
element: 2,
next: {
element: 4,
next: {
element: 6,
next: {
element: 8,
next: null
}
}
}
}
}
// 添加结点
insertNode (newElement, item) {
let newNode = new Node(newElement)
if (this.secondLink == null) {
this.secondLink = newNode
}
else {
let currentNode = this.findPrevious(item)
if (currentNode) {
newNode.next = currentNode.next
currentNode.next = newNode
}
else {
newNode.next = this.secondLink
this.secondLink = newNode
}
}
}
// 寻找上一个结点
findPrevious (item) {
let currNode = this.secondLink
while (currNode.next != null && currNode.next.element !== item) {
currNode = currNode.next
if (currNode.next == null && currNode.element !== item) {
currNode = null
break
}
}
return currNode
}
// 链表合并
linkedAdd () {
let firstIndex = this.firstLink
let secondIndex = this.secondLink
while (firstIndex) {
if (firstIndex.element < secondIndex.element) {
this.insertNode(firstIndex.element, secondIndex.element)
firstIndex = firstIndex.next
}
else if (secondIndex.next == null) {
secondIndex.next = {
element: firstIndex.element,
next: null
}
firstIndex = firstIndex.next
secondIndex = secondIndex.next
}
else {
secondIndex = secondIndex.next
}
}
return this.secondLink
}
}复制代码
这里面假设firstLink链表为最深的链表,咱们以他来作循环判断条件。设定两个变量firstIndex和secondIndex来记录两个链表的初始结点,当第一个链表的结点值小于第二个链表的结点值的时候就将第一个链表的结点值插入到第二个链表当前结点值的后面,而后firstIndex就等于下一个结点值。若是secondIndex这个结点值遍历完后,则将firstLink链表剩下的结点所有插入到secondLink链表里。最终secondLink是合并后的链表。this
若是被删除的链表是个双向链表的话则十分的简单,本题的思路主要在于用双指针实现,方法以下:spa
class linkedList {
constructor() {
this.head = {
element: 'a',
next: {
element: 'b',
next: {
element: 'c',
next: {
element: 'd',
next: {
element: 'e',
next: {
element: 'f',
next: {
element: 'g',
next: null
}
}
}
}
}
}
}
}
deletePoint (val) {
let deep = 0
let index = 0
let fastIndex = this.head
let slowIndex = this.head
while (fastIndex) {
++deep
fastIndex = fastIndex.next
}
if (deep < val) {
throw '当前链表长度没有这么长'
}
else if (val === 0) {
throw '必须传大于0的数'
}
while (index < deep - val) {
slowIndex = slowIndex.next
++index
}
this.removeNode(slowIndex.element)
}
removeNode (item) {
let currNode = this.findPrevious(item)
if (currNode.next != null) {
currNode.next = currNode.next.next
}
else if (currNode && !currNode.next) {
this.head = this.head.next
}
}
findPrevious (item) {
let currNode = this.head
while (currNode.next != null && currNode.next.element !== item) {
currNode = currNode.next
}
return currNode
}
}复制代码
先快指针找出当前链表的长度,而后总长度减去要删除的倒数下标就是当前要删除的顺数下标。
这道题就是第一道题里的关键解法,就是用两个指针,一个快指针和慢指针。快指针走两步慢指针走一步,当快指针走完的时候慢指针恰好走到中间:
centerPoint () {
let fastIndex = this.head
let slowIndex = this.head
while (fastIndex) {
if (fastIndex.next == null || fastIndex.next.next == null) {
return slowIndex.element
}
fastIndex = fastIndex.next.next
slowIndex = slowIndex.next
}
}复制代码
LRU全称实际上是最近最少使用,计算机中一般用这种方法里提升缓存的使用率。思想是这样的,若想读取一个3,发现缓存没有则存入缓存,此时链表为(3)。读取一个4,发现缓存没有则存入缓存,此时链表为(4,3)。读取一个5,发现缓存没有则存入缓存,此时链表为(5,4,3)。读取一个3,发现缓存存在则直接读缓存,3在链表中被提早,得(3,5,4)。读取一个2,发现缓存中没有,可是缓存已满,去掉最后一个结点以后插入链表,得(2,3,5)。具体实现以下:
class Node {
constructor (element) {
this.element = element
this.next = null
}
}
// 链表类
class linkedList {
constructor () {
this.countLength = 0 // 链表长度
this.head = null // 头节点
}
find (item) {
let temp = this.head
while (temp) {
if (temp.next && temp.next.element === item) {
return temp
}
temp = temp.next
}
return null
}
LRU (item) {
let newPoint = new Node(item)
let hasPoint = this.find(item)
if (hasPoint == null) {
newPoint.next = this.head
this.head = newPoint
if (this.countLength <= 5) {
this.countLength++
}
else {
let temp = this.head
while (temp.next) {
if (temp.next.next == null) {
temp.next = null
}
temp = temp.next
}
}
}
else {
let temp = hasPoint.next
if (hasPoint.next.next) {
hasPoint.next = hasPoint.next.next
}
else {
hasPoint.next = null
}
temp.next = this.head
this.head = temp
}
}
}复制代码
最后这道题是属于进阶类型的。约瑟夫问题的意思是这样的,N我的围成一圈,从第一我的开始报数,报到m的人出圈,剩下的人继续从1开始报数,报到m的人出圈;如此往复,直到全部人出圈,求最后一我的的编号。约瑟夫问题是循环链表里的经典问题,在看答案以前请思考一下:
class Node {
constructor(element) {
this.element = element
this.next = null
this.state = false
}
}
class linkedList {
constructor() {
this.head = null
this.circleLength = 0
}
newCircle (length) {
let temp = length
this.circleLength = temp
let curr = this.head
while (length >= 0) {
let newNode = new Node(temp - length + 1)
if (curr == null) {
this.head = newNode
curr = this.head
}
else if (length > 0) {
curr.next = newNode
curr = curr.next
}
else {
curr.next = this.head
}
--length
}
}
josephQuestion (n, m) {
let index = 1
let step = 1
this.newCircle(n)
let curr = this.head
while (index <= this.circleLength) {
if (step !== m) {
if (!curr.state) {
++step
}
curr = curr.next
}
else {
if (curr.state) {
curr = curr.next
continue
}
else {
if (this.circleLength === index && !curr.state) {
return curr.element
}
step = 1
++index
curr.state = true
curr = curr.next
}
}
}
}
}
let test = new linkedList()
test.josephQuestion(41, 3)复制代码
本算法的思路是先新建一个循环链表,每n个数就标记为true,到最后index等于链表长度的时候则表示已经标记了index-1个数了,这个时候就只需把最后剩下的返回就能够了。其实能够直接删除掉结点,只不过我嫌麻烦。这道题不用链表的话它是由一个算法的,能够利用递归来写,递归公式以下:
f(1) = 0
f(n) = (f(n-1)+q)%n
非链表的具体解法能够参照一下这篇文章:非链表解法
上一篇文章:数据结构与算法的重温之旅(四)——链表
下一篇文章:数据结构与算法的重温之旅(六)——栈