链表实现的技巧:
- 理解指针或引用的含义
将变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到变量。java与指针对应的是引用 - 警惕指针丢失和内存泄漏
指针操作的时候一定要注意顺序,比如:
P的下一个结点指向Xp->next = x; // 将 p 的 next 指针指向 x 结点; x->next = p->next; // 将 x 的结点的 next 指针指向 b 结点;
这时X将他的下一个结点指向P的下一个结点的时候(由于P的下一个已经指向X了),这会就是自己指向自己了,因此链表断裂成凉拌 - 删除链表结点时,记得手动释放内存空间(java无视)
- 利用哨兵简化开发(下面有代码示例)
如果引入哨兵,不管任何时候,不管链表是否是空,head指针一定会指向这个哨兵结点,因为每次删除和添加的时候,不用判断链表是否为空等边界情况,简化了代码。- 有哨兵结点的链表叫做带头链表
- 没有哨兵结点的链表叫做不带头链表
/**
* 链表的结点
*/
public class LinkNode {
public int val;
public LinkNode next;
public LinkNode(int val) {
this.val = val;
}
}
/**
* 单链表的实现
*/
public class MyLinkedList {
private LinkNode head;
private int length;
public MyLinkedList() {
//创建一个哨兵,简化代码
head = new LinkNode(-1);
length = 0;
}
/**
* 获取链表中第 index 个节点的值。如果索引无效,则返回-1。
*
* @return
*/
public int get(int index) {
if (index < 0 || index >= length) {
return -1;
}
LinkNode node = head;
for (int i = 0; i <= index; i++) {
node = node.next;
}
return node.val;
}
/**
* 在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
*
* @param val
*/
public void addAtHead(int val) {
LinkNode node = new LinkNode(val);
//这块虽然head有初始值,但是每次取值是从head.next值开始取的,所以头哨兵在取值的时候忽略了
node.next = head.next;
//始终将哨兵结点放在第一位
head.next = node;
length++;
}
/**
* 将值为 val 的节点追加到链表的最后一个元素。
*
* @param val
*/
public void addAtTail(int val) {
LinkNode node = new LinkNode(val);
LinkNode curr = head;
while (curr.next != null){
curr = curr.next;
}
curr.next = node;
length++;
}
/**
* 在链表中的第 index 个节点之前添加值为 val 的节点。
* 如果 index 等于链表的长度,则该节点将附加到链表的末尾。
* 如果 index 大于链表长度,则不会插入节点。
* 如果index小于0,则在头部插入节点。
*
* @param index
* @param val
*/
public void addAtIndex(int index, int val) {
if (index <= 0) {
addAtHead(val);
return ;
}
if (index > length) {
return;
}
if (index == length) {
addAtTail(val);
return ;
}
LinkNode curr = head;
//计算index结点的前一个结点,i < index ->到循环终止的时候取的是index前一个数据
for (int i = 0; i < index; i++) {
//因为循环条件i<index,取得已经是index前一个数据了,但是因为有头哨兵的存在,所以要从head.next开始算
curr = curr.next;
//而之前用的的i == index 条件,是直接定位到index结点了。这点谨记
// if(i == index){
// node.next = curr;
// last.next = node;
// }
}
LinkNode node = new LinkNode(val);
node.next = curr.next;
curr.next = node;
length++;
}
/**
* 如果索引 index 有效,则删除链表中的第 index 个节点。
*
* @param index
*/
public void deleteAtIndex(int index) {
if (index < 0 || index >= length) {
return;
}
LinkNode curr = head;
for (int i = 0; i < index; i++) {
curr = curr.next;
}
curr.next = curr.next.next;
length--;
}
public void printLnik() {
LinkNode curr = head;
for (int i = 0; i < length; i++) {
System.out.print(curr.next.val + " ");
curr = curr.next;
}
System.out.println();
}
public static void main(String[] args) {
MyLinkedList linkList = new MyLinkedList();
linkList.addAtIndex(0, 1);
linkList.printLnik();
System.out.println(linkList.get(0));
System.out.println(linkList.get(1));
}
/**
* 判断链表中是否有环
* @param head
* @return
*/
public boolean hasCycle(LinkNode head) {
LinkNode curr = head;
HashSet<Object> hashSet = new HashSet<>();
while (curr.next != null){
if(hashSet.contains(curr)){
return true;
}
hashSet.add(curr);
curr = curr.next;
}
return false;
}
/**
* 反转链表
* @param head
* @return
*/
public LinkNode reverseList(LinkNode head) {
LinkNode prev = null; //翻转后新的链表
LinkNode curr = head; //当前链表
while (curr != null){
LinkNode tem = curr.next; //将链表除头结点之外的其他结点赋值给tem指针
curr.next = prev; //将当前链表的头结点和prev链表结合(翻转后的链表)
prev = curr; //然后将结合后的新链表赋值给prev链表(翻转后的链表)
curr = tem; //将tem指针赋值给当前链表进行下一轮翻转(tem指针链表到这已经少了第一个结点)
}
return prev;
}
/**
* 删除链表中倒数第n个结点
* @param head
* @param n
* @return
*/
public LinkNode removeNthFromEnd(LinkNode head, int n) {
LinkNode fast = head;
LinkNode slow = head;
//因为是带头链表,所以 不存在为空的情况,并且因为有头结点,所以这块是n
//思路利用快慢指针
for (int i = 0; i < n ; i++) {
fast = fast.next;
}
while (fast.next != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head;
}
/**
* 链表的中心结点
* 如果有两个中间结点,则返回第二个中间结点。
* @param head
* @return
*/
public LinkNode middleNode(LinkNode head) {
LinkNode fast = head;
LinkNode slow = head;
while (fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
//因为链表是带头链表,所以所下一个
return slow.next;
}
/**
* 判断链表中是否有环(利用快慢指针)
* 若链表中存在环,则快指针一定会追上慢指针
* @param head
* @return
*/
public boolean hasCycleF(LinkNode head) {
LinkNode fast = head;
LinkNode curr = head;
while (fast.next != null){
if(fast == curr)
return true;
fast = fast.next;
curr = curr.next;
}
return false;
}
}