链表节点定义
单链表
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
双链表
public class DListNode{
int val;
DListNode pre;
DListNode next;
DListNode() {};
DListNode(int val, DListNode pre, DListNode next){this.val = val; this.pre = pre;this.next = next;}
}
链表增删改查
- 题目链接:单双链表的增删改查
- 我的解法:一开始,通过52/65用例,暂时用输入输出法找不到问题所在,感觉时index和下标的关系出问题,具体应该是get函数和addAtIndex两个函数有问题,绷不住了。。。
public class ListNode{
int val;
ListNode next;
ListNode() {};
ListNode(int val, ListNode next) {this.val = val; this.next = next;}
}
class MyLinkedList {
int size;//便于判断index是否合理
ListNode head;//虚拟头节点
public MyLinkedList() {
this.size = 0;
this.head = new ListNode(-1, null);
}
public int get(int index) {
System.out.print("get");
if(index < 0 || index > size) return -1;
ListNode p = head.next;
while((p != null) && (index > 0)){p = p.next;index--;}
if(p == null) return -1;
return p.val;
}
public void addAtHead(int val) {
System.out.print("addAtHead");
ListNode newNode = new ListNode(val, null);
newNode.next = head.next;
head.next = newNode;
size++;
// System.out.print("addAtHead");
// ListNode tmp = head.next;
// while(tmp != null)
// {
// System.out.print(tmp.val);
// System.out.print(" ");
// tmp = tmp.next;
// }
}
public void addAtTail(int val) {
System.out.print("addAtTail");
ListNode newNode = new ListNode(val, null);
ListNode p = head;
while(p.next != null){p = p.next;}
p.next = newNode;
size++;
// System.out.print("addAtTail");
// ListNode tmp = head.next;
// while(tmp != null)
// {
// System.out.print(tmp.val);
// System.out.print(" ");
// tmp = tmp.next;
// }
}
public void addAtIndex(int index, int val) {
System.out.print("addAtIndex");
if(index < 0 || index > size) return;
ListNode newNode = new ListNode(val, null);
ListNode p = head.next;
ListNode pre = head;
while((p != null) && (index > 0)){pre = pre.next; p = p.next;index--;}
newNode.next = pre.next;
pre.next = newNode;
size++;
// System.out.print("addAtIndex");
// ListNode tmp = head.next;
// while(tmp != null)
// {
// System.out.print(tmp.val);
// System.out.print(" ");
// tmp = tmp.next;
// }
}
public void deleteAtIndex(int index) {
System.out.print("deleteAtIndex");
if(index < 0 || index > size) return;
ListNode pre = head;ListNode p = head.next;
while((p != null) && (index > 0)){pre = pre.next; p = p.next; index--;}
if(p != null)pre.next = p.next;
else pre.next = null;
size--;
// System.out.print("deleteAtIndex");
// ListNode tmp = head.next;
// while(tmp != null)
// {
// System.out.print(tmp.val);
// System.out.print(" ");
// tmp = tmp.next;
// }
}
}
后来发现就是index从零开始但是size从1开始问题,这个需要在涉及index的函数中注意,其次就是addAtIndex的定义本身很不一样,size==index时直接插就行了,贴一下正确自己的解法,虽然很慢
public class ListNode{
int val;
ListNode next;
ListNode() {};
ListNode(int val, ListNode next) {this.val = val; this.next = next;}
}
class MyLinkedList {
int size;//便于判断index是否合理
ListNode head;//虚拟头节点
public MyLinkedList() {
this.size = 0;
this.head = new ListNode(-1, null);
}
public int get(int index) {
if(index < 0 || index >= size) return -1;
ListNode p = head.next;
while(index > 0){p = p.next;index--;}
return p.val;
}
public void addAtHead(int val) {
ListNode newNode = new ListNode(val, null);
newNode.next = head.next;
head.next = newNode;
size++;
}
public void addAtTail(int val) {
ListNode newNode = new ListNode(val, null);
ListNode p = head;
while(p.next != null){p = p.next;}
p.next = newNode;
size++;
}
public void addAtIndex(int index, int val) {
if(index < 0 || index > size) return;
ListNode newNode = new ListNode(val, null);
if(index == size) {addAtTail(val);return;}
ListNode p = head.next;
ListNode pre = head;
while(index > 0){pre = pre.next; p = p.next;index--;}
newNode.next = pre.next;
pre.next = newNode;
size++;
}
public void deleteAtIndex(int index) {
if(index < 0 || index >= size) return;
ListNode pre = head;ListNode p = head.next;
while(index > 0){pre = pre.next; p = p.next; index--;}
pre.next = p.next;
size--;
}
}
上面addAtHead和addAtTail都可以在内部调用addAtIndex实现,分别为前者addAtIndex(0,val),后者addAtTail(size, val),同时增加size记录链表长度便于判定index是否合法,这样可以在代码中不判空
- 双链表版本
public class DListNode{
int val;
DListNode pre;
DListNode next;
DListNode() {};
DListNode(int val, DListNode pre, DListNode next){this.val = val; this.pre = pre;this.next = next;}
}
class MyLinkedList {
int size;//便于判断index是否合理
DListNode head;//虚拟头节点
DListNode tail;
public MyLinkedList() {
this.size = 0;
this.head = new DListNode(-1, null, null);
this.tail = new DListNode(-1, null, null);
//双向链表互指
head.next = tail;
tail.pre = head;
}
public int get(int index) {
if(index < 0 || index >= size) return -1;
DListNode p = head.next;
if(index >= size/2){//比较巧,那边短从那边开始
p = tail.pre;
index = size-index-1;
while(index > 0){p = p.pre;index--;}
}
else{
while(index > 0){p = p.next;index--;}
}
return p.val;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
if(index < 0 || index > size) return;
size++;
DListNode newNode = new DListNode(val, null, null);
if(index == size) {
newNode.pre = tail.pre;
tail.pre = newNode;
newNode.next = tail;
newNode.pre.next = newNode;
return;}
DListNode p = head.next;
while(index > 0){p = p.next;index--;}
p.pre.next = newNode;
newNode.pre = p.pre;
newNode.next = p;
p.pre = newNode;
}
public void deleteAtIndex(int index) {
if(index < 0 || index >= size) return;
DListNode p = head.next;
while(index > 0) {p = p.next; index--;}
p.pre.next = p.next;
p.next.pre = p.pre;
size--;
}
}
- 复杂度:时空复杂度均为 O ( O( O(调用类中函数次数之和 ) ) )
删除元素
- 题目链接:删除单链表元素
- 我的解法:引入dummy节点统一整体删除操作
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head == null){return null;}
ListNode h = new ListNode(0, head);
ListNode pre = h;ListNode p = head;
while(p != null)
{
if(p.val != val)
{
p = p.next;
pre = pre.next;
}
else
{
pre.next = p.next;
p = pre.next;
}
}
return h.next;
}
}
总结:不同于之前一直使用c++解题,java的指针概念是通过结构体模拟
- 其他解法:不引入虚拟节点,head直接作为头节点
class Solution {
public ListNode removeElements(ListNode head, int val) {
while(head != null && head.val == val) head = head.next;
if(head == null) return null;
ListNode pre = head; ListNode p = head.next;
while(p != null)
{
while(p != null && p.val != val) {p = p.next;pre = pre.next;}
if(p!=null){pre.next = p.next;p = pre.next;}
}
return head;
}
}
比起创建虚拟节点不带头结点的链表执行删除操作更快
删除操作的标配是pre指向前节点,p指向当前节点,要注意当前节点可能为空指针的判断。
- 复杂度:时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
反转链表
- 题目链接:反转单链表
- 我的解法:引入虚拟头节点+头插法
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode h = new ListNode(-1, head);
ListNode pre = head.next;
ListNode p = pre.next;
head.next = null;//头插法一定要记得将最末端置空
while(pre != null)
{
pre.next = h.next;
h.next = pre;
pre = p;
if(pre != null) p = pre.next;
}
return h.next;
}
}
整体比较熟悉,记住在断开链表进行头插前要将断开点节点next指向null,不然会成环。
- 复杂度:时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)