链表
203. 移除链表元素
初始版本:原始链表上操作
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode cur = head;
while(cur!= null && cur.val == val){
cur = cur.next;
head = cur;
}//先判断头节点
while(cur!= null && cur.next != null){
if(cur.next.val == val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}//确认此节点不为val后判断下一节点
return head;
}
简化版本:虚拟头节点
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode newhead = new ListNode(0,head);
ListNode cur = newhead;
while(cur.next!= null){
if(cur.next.val == val){
cur.next = cur.next.next;
}
else{
cur = cur.next;
}
}//cur不会为空,所以不用判断 cur != null
return newhead.next;
}
}
707. 设计链表
初版设计
class MyLinkedList {
public int val;
public MyLinkedList next;
public MyLinkedList() {
val = -2;//表示无任何节点
}
public int get(int index) {
MyLinkedList cur = this;
for(int i=0; i<index && cur!= null ; i++ ) {
cur = cur.next;
}
if(cur == null || cur.val == -2){
return -1;
}else{
return cur.val;
}
}
public void addAtHead(int val) {
if(this.val == -2){
this.val = val;
}else{
MyLinkedList temp = new MyLinkedList();
temp.val = this.val;
temp.next = this.next;
this.val = val;
this.next = temp;
}
}
public void addAtTail(int val) {
if(this.val == -2){
this.val = val;
}else{
MyLinkedList cur = this;
MyLinkedList tail = new MyLinkedList();
tail.val = val;
while(cur.next != null){
cur = cur.next;
}
cur.next = tail;
}
}
public void addAtIndex(int index, int val) {
if(index <= 0){
addAtHead(val);
}else{
MyLinkedList cur = this;
MyLinkedList beforeCur = cur;
int i;
for(i=0; i<index && cur!= null ; i++ ) {
beforeCur = cur;
cur = cur.next;
}
if(i == index){
MyLinkedList temp = new MyLinkedList();
temp.val = val;
temp.next = beforeCur.next;
beforeCur.next = temp;
}
}
}
public void deleteAtIndex(int index) {
MyLinkedList newhead = new MyLinkedList();
newhead.next =this;//新头节点
MyLinkedList beforeCur = newhead;
MyLinkedList cur = newhead.next;
int i;
if(index == 0){
if(this.next != null){
this.val = this.next.val;
this.next = this.next.next;
return;
}
else{
this.val = -2;
}
}
for(i=0; i<index && cur.next!= null ; i++ ) {
beforeCur = cur;
cur = cur.next;
}
if(i == index){
beforeCur.next = cur.next;
}
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
代码随想录版
通过虚拟头节点来实现对所有结点的统一操作,引入size来实现链表的越界检查和方便的索引访问。
class ListNode{
ListNode next;
int val;
ListNode(){
}
ListNode(int val){
this.val = val;
}
ListNode(int val,ListNode next){
this.val =val;
this.next = next;
}
}
class MyLinkedList {
int size; //方便越界检查
ListNode sentinelHead; //虚拟头节点
public MyLinkedList() {
sentinelHead = new ListNode();
sentinelHead.val = 0;
size = 0;
}
public int get(int index) {
if(index < 0 || index >= size){
return -1;
}
ListNode position = sentinelHead;
//虚拟头节点,所以需要<=
for(int i = 0 ; i<=index ; i++){
position = position.next;
}
return position.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 >size){
return;
}else if(index < 0){
index = 0;
}
ListNode position = sentinelHead;
//找到前驱,所以<
for(int i = 0 ; i < index ; i++){
position = position.next;
}
ListNode addPoint = new ListNode(val,position.next);
position.next = addPoint;
size++;
}
public void deleteAtIndex(int index) {
if(index<0||index>=size){
return;
}
ListNode position = sentinelHead;
//找到前驱
for(int i = 0 ; i < index ; i++){
position = position.next;
}
position.next = position.next.next;
size--;
}
206. 反转链表
初版设计
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode aftcur = null;
ListNode precur = null;
while(cur!= null){
aftcur = cur.next;
cur.next = precur;
precur =cur;
cur = aftcur;
}
return precur;
}
}
递归法
class Solution {
public ListNode reverse(ListNode pre,ListNode cur){
if(cur == null){
return pre;
}
ListNode aftcur = cur.next;
cur.next = pre;
return reverse(cur,aftcur);
}
public ListNode reverseList(ListNode head) {
return reverse(null,head);
}
}
第二种递归
// 从后向前递归
class Solution {
ListNode reverseList(ListNode head) {
// 边缘条件判断
if(head == null) return null;
if (head.next == null) return head;
// 递归调用,翻转第二个节点开始往后的链表
ListNode last = reverseList(head.next);
// 翻转头节点与第二个节点的指向
head.next.next = head;
// 此时的 head 节点为尾节点,next 需要指向 NULL
head.next = null;
return last;
}
}
24. 两两交换链表中的节点
初版设计
无虚拟头节点
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode pre = new ListNode();
ListNode aft = null;
ListNode cur = head;
if(head != null && head.next!= null){
head = head.next;
}
while(cur!=null){
if(cur.next != null){
pre.next = cur.next;
aft = cur.next.next;;
cur.next.next =cur;
cur.next = aft;
pre = cur;
cur = aft;
}else{
cur= cur.next;
}
}
return head;
}
}
优化版
优化了判断条件,引入dummy头节点来实现头结点的转换
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(0,head);
ListNode pre = dummyHead;
ListNode aft = null;
ListNode cur = head;
while(cur!=null && cur.next != null){
pre.next = cur.next;
aft = cur.next.next;
cur.next.next =cur;
cur.next = aft;
pre = cur;
cur = aft;
}
return dummyHead.next;
}
}
递归版
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode next = head.next;//head是第一个节点,next是第二个节点,newNode是第三个节点。
ListNode newNode = swapPairs(next.next);//递归到链尾
next.next = head;
head.next = newNode;//从队尾三个元素开始操作
return next;//返回原第二个节点,即交换后的头节点
}
}
19. 删除链表的倒数第 N 个结点
初版双指针
双指针思想,满足条件时慢指针开始动
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(0,head);
ListNode cur = head;
ListNode preDeletecur = dummyHead;
ListNode deletecur = head;
for(int i=0; cur != null ; i++) {
if(i - n>=0) {//每次判断快指针走的长度,进而更改慢指针。
preDeletecur = deletecur;
deletecur = deletecur.next;//可以优化,因为删除不用记本节点只需要记前驱节点
}
cur = cur.next;
}
preDeletecur.next = deletecur.next;
return dummyHead.next;
}
}
代码随想录版本
因为初版在每次循环中都需要额外的以此判断,而如果快指针已经满足条件后面就无需判断,所以可以优化,让快指针先走·。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(0,head);
ListNode preDeletecur = dummyHead;
ListNode cur = head;
while(--n > 0){
cur = cur.next;
}
cur = cur.next;//再走一步,记忆需删除节点的前驱。
while(cur != null){
cur = cur.next;
preDeletecur = preDeletecur.next;
}
preDeletecur.next = preDeletecur.next.next;
return dummyHead.next;
}
}
面试题 02.07. 链表相交
暴力解法,遍历两链表
时间复杂度为O(n^2),重复取值严重。(732ms)
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
while(curA != null){
ListNode curB = headB;
while(curB != null){
if(curA == curB){
return curB;
}
curB = curB.next;
}
curA = curA.next;
}
return null;
}
}
代码随想录,尾部对齐
可以利用相交后,后续节点重复的原则,将链表尾部对齐后,同时遍历两链表即可找出重复部分。
时间复杂度O(3n)。(1ms)
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenghtA = 0;
int lenghtB = 0;
while(curA != null) {
curA = curA.next;
lenghtA++;
}//先两遍遍历得出两个链表的长度大小
while(curB != null) {
curB = curB.next;
lenghtB++;
}
int gap;
if(lenghtB >lenghtA){
curA = headB;
curB = headA;
gap = lenghtB - lenghtA;
}else{
curA = headA;
curB = headB;
gap = lenghtA - lenghtB;
}
while(gap-- > 0){
curA = curA.next;
}
while(curA != curB ){
curA = curA.next;
curB = curB.next;
}
return null;
}
}
哈希解法
时间复杂度O(m+n),空间复杂度O(m)
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set<ListNode> visited = new HashSet<ListNode>();
ListNode temp = headA;
while (temp != null) {
visited.add(temp);
temp = temp.next;
}
temp = headB;
while (temp != null) {
if (visited.contains(temp)) {
return temp;
}
temp = temp.next;
}
return null;
}
}
双指针法
其实也等价于尾部对齐,让两指针遍历完自己的指针后在遍历对方的,如果相交,则在交点相遇,如果不相交,则返回null。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
}
142. 环形链表 II
初版:HashSet
借鉴链表相交思想,利用新空间存储节点,来判断重复出现的节点。
时间复杂度O(n),空间复杂度O(n)
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode cur = head;
Set<ListNode> visited = new HashSet<ListNode>();
while( cur != null) {
if(visited.contains(cur)) {
return cur;
}
visited.add(cur);
cur = cur.next;
}
return null;
}
}
双指针法
还可以利用环的性质,如果存在环,则一快一慢指针向后遍历一定会在环中相遇,而相遇的点与环的入口存在关系,如下图所示,(x + y) * 2 = x + y + n (y + z),则可以得到 x = n (y + z) - y 或 x = (n - 1) (y + z) + z,表示如果一指针从头节点走,一指针从相遇点走(步长都为1),则一定会在入口相遇,即可得到环的入口节点。
时间复杂度O(n),空间复杂度O(1)
我的:
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
break;
}
}
if(fast == null || fast.next == null){
return null;
}
slow = head;
while(slow != fast){
slow = slow.next;
fast = fast.next;
}
return fast;
}
}
代码随想录:
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
while(fast!= null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {//相遇节点
slow = head;//调整一指针到头部
while( slow != fast){
slow = slow.next;
fast = fast.next;
}//x = (n - 1) (a + b) + b,相遇时即环入口
return fast;
}
}
return null;
}
}