基本概念:
链表中的元素在内存中不必是连续的空间,链表的每个元素由一个存储元素本身的节点和一个指向下一个圆的引用组成。
相对于数组,链表的优点:
1、内存空间不是必须连续的,可以充分利用计算机的内存,实现领过的内存动态管理。
2、链表不必在创建时就确定大小,并且大小可以五险的延伸下去。
3、链表在插入和删除数据时,时间复杂度可以达到O(1)。相对数组效率高很多。
相对于数组,链表的缺点:
1、链表范围任何一个位置的元素是,都需要从头开始访问。
2、无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的元素。
链表中常见的操作:
append(element)向链表尾部添加一个新的项。insert(position, element)向链表的特定位置插入一个新的项。get(position)获取对应位置的元素。indexOf(element)返回元素在链表中的索引。如果链表中没有该元素就返回-1。update(position, element)修改某个位置的元素。removeAt(position)从链表的特定位置移除一项。remove(element)从链表中移除一项。isEmpty()如果链表中不包含任何元素,返回 trun,如果链表长度大于 0 则返回 false。size()返回链表包含的元素个数,与数组的 length 属性类似。toString()由于链表项使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString 方法,让其只输出元素的值。
function LinkedList() {
// 内部的类:节点类
function Node(data) {
this.data = data;
this.next = null;
}
// 属性
this.head = null;
this.length = 0;
// 1、追加方法
LinkedList.prototype.append = function (data) {
// 1、创建新的节点
var newNode = new Node(data);
// 2、判断是否添加的是第一个节点
if(this.length === 0) {
this.head = newNode
} else {
var current = this.head
while (current.next) {
current = current.next
}
current.next = newNode;
}
this.length += 1;
}
// 2、toString方法
LinkedList.prototype.toString = function() {
// 1、定义变量
var current = this.head;
var listString = "";
while (current) {
listString += current.data + " "
current = current.next
}
return listString;
}
// 3、插入操作
LinkedList.prototype.insert = function (position, data) {
// 1、对position进行越界判断
if(position < 0 || position > this.length) return false;
// 2、根据data创建newNode
var newNode = new Node(data);
// 3、判断插入的位置是否是第一个
if(position == 0) {
newNode.next = this.head;
this.head = newNode;
} else {
var index = 0;
var current = this.head;
var previous = null;
while (index++ < position) {
previous = current;
current = current.next
}
newNode.next = current;
previous.next = newNode;
}
this.length += 1;
return true;
}
// 4、get方法
LinkedList.prototype.get = function(position) {
// 1、判断边界
if(position < 0 || position >= this.length) return null;
var index = 0;
var current = this.head;
// 2、获取对应的data
while (index++ < position) {
current = current.next;
}
return current.data;
}
// 5、indexOf方法
LinkedList.prototype.indexOf = function(data) {
var index = 0;
var current = this.head;
while (current) {
if(current.data === data) {
return index
}
current = current.next;
index += 1;
}
return -1;
}
// 6、update方法
LinkedList.prototype.update = function(position, newData) {
if(position < 0 || position >= this.length) return false;
var index = 0;
var current = this.head;
while (index++ < position) {
current = current.next;
}
// 将position里的data修改成newData
current.data = newData;
return true;
}
// 7、removeAt方法
LinkedList.prototype.removeAt = function(position, newData) {
if(position < 0 || position >= this.length) return false;
var index = 0;
var current = this.head;
if(position === 0) {
this.head = this.head.next
} else {
var index = 0;
var previous = null;
var current = this.head;
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
}
this.length -= 1;
return true;
}
// 8、removeAt方法
LinkedList.prototype.remove = function(newData) {
// 1、获取data在列表中的位置
var position = this.indexOf(newData);
// 2、根据位置信息,删除节点
return this.removeAt(position);
}
LinkedList.prototype.isEmpty = function() {
return this.length === 0;
}
LinkedList.prototype.size = function() {
return this.length
}
}
// 测试代码
var list = new LinkedList()
list.append('abc');
list.append('cba')
list.append('nba')
console.log(list.toString())
list.insert(0, 'aaa')
list.insert(3, 'bnn')
list.insert(5, 'ddd')
console.log(list.toString())
console.log(list.get(0))
console.log(list.get(3))
console.log(list.indexOf('bnn'));
console.log(list.indexOf('aa1'));
list.update(0, 'aa1')
console.log(list.toString());
list.removeAt(0)
list.removeAt(3)
console.log(list.toString());
list.remove('abc');
list.remove('cba');
console.log(list.toString());
console.log(list.isEmpty());
console.log(list.size());
leetcode刷题:
var hasCycle = function(head) {
if(!head) return false;
// 定义两个慢指针,如果链表有环,则两个指针一定会相遇
let pre = head, cur = head;
while(cur && cur.next) {
pre = pre.next
cur = cur.next.next;
if(pre === cur) {
return true;
}
}
return false;
};
var detectCycle = function(head) {
if(!head) return null;
//定义两个快慢指针
let pre = head, cur = head;
while(cur && cur.next) {
pre = pre.next
cur = cur.next.next
// 当快慢指正重叠时,当前指针到环入口节点与头结点到环入口节点距离相同
if(pre === cur) {
var temp = head;
// 判断节点是否相同,如果相同,即是环的入口节点
while(temp !== pre) {
temp = temp.next;
pre = pre.next;
}
return pre
}
}
return null;
};
如果传进的数是2,如图所示,会出现环。如果出现环就一定不是快乐数,即判断是否会出现环。
var isHappy = function(n) {
let pre = n, cur = getNext(n);
while(cur !== pre && cur !== 1) {
pre = getNext(pre);
cur = getNext(getNext(cur));
}
// 可能等于1;可能链表有环,cur === pre
return cur === 1;
};
// 算出平方和
var getNext = function(n) {
let t = 0;
while(n) {
t += (n % 10) * (n % 10);
n = Math.floor(n / 10);
}
return t;
}
25. K 个一组翻转链表
var reverseKGroup = function(head, k) {
if(!head) return null;
let hair = new ListNode(-1, head), pre = hair;
do {
pre.next = reverse(pre.next, k);
for(let i = 0; i < k && pre; i++) {
pre = pre.next;
}
if(!pre) break;
} while(1);
return hair.next
};
var reverse = function(head, n) {
let pre = head, cur = head, con = n;
while(--n && pre) {
pre = pre.next;
}
if(!pre) return head;
pre = null;
while(con--) {
[cur.next, pre, cur] = [pre, cur, cur.next];
}
head.next = cur;
return pre;
}
61. 旋转链表
刚开始链表是一个接近环形的链表
当K=2时,向右旋转两个节点
思路:将链表设置成环形,然后顺时针旋转K个单位,然后断开对应的连接。
var rotateRight = function(head, k) {
// 判断我们的这个节点是不是空的
if(!head) return null;
// 找到我们链表的尾结点,穿成环,获取到链表的长度
let cur = head, size = 1;
while(cur.next) cur = cur.next, size += 1;
cur.next = head;
// 找到第size-k个节点,断开连接
for(let i = 0; i < size - k % size - 1; i++) {
head = head.next;
}
cur = head.next;
head.next = null;
return cur;
};
24. 两两交换链表中的节点
思路:判断temp.next和temp.next.next是否为空,两个都不为空则进行交换,
var swapPairs = function(head) {
if(!head) return null;
let hair = new ListNode(-1,head), temp = hair;
while(temp.next && temp.next.next) {
let pre = temp.next, cur = temp.next.next;
pre.next = cur.next;
cur.next = pre;
temp.next = cur;
temp = pre;
}
return hair.next;
};
剑指 Offer 22. 链表中倒数第k个节点
思路,定义一个虚拟节点,pre指向虚拟节点,cur指向头结点,然后将cur移动到K个节点上。然后pre和cur同时向后移动,当cur为null时,删除pre.next.next节点
var removeNthFromEnd = function(head, n) {
if(!head) return null;
let hair = new ListNode(-1, head), pre = hair, cur = head;
for(let i = 0; i < n; i++) {
cur = cur.next
}
if(!cur) return head.next;
while(cur) {
cur = cur.next;
pre = pre.next;
}
// 删除操作
pre.next = pre.next.next;
return hair.next;
};
83. 删除排序链表中的重复元素
思路:定义两个指正,一个指向头结点,一个指向头结点的下一个节点。循环链表,当pre与cur的值相同时,cur向后移动,当不一样时,pre.next指向cur,pre等于cur,cur等于cur.next.
var deleteDuplicates = function(head) {
if(!head) return null;
let pre = head, cur = head.next;
while(cur) {
if(pre.val !== cur.val) {
pre.next = cur;
pre = cur;
cur = cur.next;
} else {
cur = cur.next;
}
}
pre.next = null;
return head;
};
82. 删除排序链表中的重复元素 II
思路:判断hair.next.val与cur.next.val是否相等,如果相等,cur向后移动,如果不相等,pre.next = cur.next,同时cur向后异动,,最后返回虚拟头结点的next。
var deleteDuplicates = function(head) {
if(!head) return null;
let hair = new ListNode(-1, head), pre = hair, cur = head;
while(cur && cur.next) {
if(pre.next.val !== cur.next.val) {
pre = pre.next;
cur = cur.next;
}else {
while(cur && cur.next && pre.next.val === cur.next.val) {
cur = cur.next;
}
pre.next = cur.next;
cur = cur.next;
}
}
return hair.next;
};
后续还会出其他的数据结构的内容。欢迎各位大佬支出文章中的不足。算法中,解题流程的图可能画的不是很完整,如果有小伙伴需要,留言,我可以出完整图演示。