代码随想录:链表

链表

C++结构体与类的区别,结构体中的默认所有成员都是公有的,类中默认所有成员都是私有的。但是两者都可以设置public, private, protected 成员变量,成员函数,也都可以设置构造函数,析构函数,并且都有继承和多态特性。C++中类和结构体基本一致。
2021/7/5 先复习类的构造函数,析构函数,多态等,复习完之后再上链表。

明天早上先用C++代码构造一个链表再说,然后对于删除元素部分,申请内存,删除内存的操作要贯通熟悉一下

特殊点要多用const指明该变量不可被更改,以及函数入口参数使用引用而不是传值

链表构造:
struct ListNode {
    int val;
    ListNode* next;
	ListNode() : val{ 0 }, next{ nullptr } {}
    ListNode(int x) : val{x}, next{nullptr} {}
	ListNode(int x, ListNode* next) : val{ x }, next{ next } {}
};
ListNode* createList(const vector<int>& ListData) { // 以后要多传引用,用const指定这是个不可更改的量
	if (ListData.empty()) {
		return nullptr;
	}//检测传入数组是否为空
	ListNode* head = new ListNode{ ListData[0],nullptr };//在堆区存储申请内存存储头结点
	ListNode* current = head; //常规区域定义一个指针变量
	for (int i = 1; i < ListData.size(); i++) {
		current->next = new ListNode(ListData[i], nullptr);
		current = current->next;
	}
	return head;
}
/*这里有一个细节就是delete之后head变成了悬空指针,之后立刻使用head = newhead; 对悬空指针进行了确定,直到head变成了nullptr;
最终所有的悬空指针都得到了处理.*/
void freeList(ListNode* head) {
	while (head != nullptr) {
		ListNode* newhead = head->next;//常规区域定义一个指针变量
		delete head; 这里释放了 head 指向的堆区内存,但 head 变量本身仍然存在,只是它现在是悬空指针(dangling pointer)
		head = newhead; //给悬空指针重新赋值
	}
}
int main() {
	//首先得构造一个链表
	vector<int> ListData{1,2,6,3,4,5,6 };
	ListNode* head = createList(ListData);
	ListNode* current = head;
	while (current!= nullptr) {
		cout << current->val << endl;
		current = current->next;
	}
	freeList(head);
	return 0;
}

移除链表元素:按照代码随想录的思路自己写

我的代码:
ListNode* removeElements(ListNode* head, int val) {
	//方法1,不添加虚拟头节点.要注意对于释放节点的删除
	/*ListNode* deleteNode = nullptr;
	ListNode* searchNode = nullptr;
	ListNode* prevNode = nullptr;
	while (head != nullptr) {
		if (head->val == val) {
			deleteNode = head;
		    head = head->next;
			delete deleteNode;
			deleteNode = nullptr;
		}
		else {
			break;
		}
	}//这一部分解决的是头节点出现要删除的值的时候
	if (head == nullptr) {
		return head;
	}
	searchNode = head->next;
	prevNode = head;
	while (searchNode != nullptr) {
		if (searchNode->val == val) {
			prevNode->next = searchNode->next;
			deleteNode = searchNode;
			searchNode = searchNode->next;
			delete deleteNode;
			deleteNode = nullptr;
		}
		else {
			searchNode = searchNode->next;
			prevNode = prevNode->next;
		}
	}
	return head;*/
	//方法2,添加虚拟头节点.
	ListNode* preHeadNode = new ListNode;
	preHeadNode->next = head;
	ListNode* searchNode = head;
	ListNode* lastNode = preHeadNode;
	ListNode* deleteNode = nullptr;
	while (searchNode != nullptr) {
		if (searchNode->val == val) {
			lastNode->next = searchNode->next;
			deleteNode = searchNode;
			searchNode = searchNode->next;
			delete deleteNode;
			deleteNode = nullptr;
		}
		else {
			searchNode = searchNode->next;
			lastNode = lastNode->next;
		}
	}
	head = preHeadNode->next;
	delete preHeadNode;
	return head;
}
使用虚拟头结点是最轻松的,不用再顾及若头节点要被删除的结果。
最简代码如下:
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* preHeadNode = new ListNode; //构造虚拟头结点
        preHeadNode->next = head;//虚拟头节点接到最前端去
        ListNode* searchNode = preHeadNode;//从最开始进行索引
        ListNode* deleteNode = nullptr;//用于删除结点的堆区内存
        while (searchNode->next != nullptr) {
            if (searchNode->next->val == val) {
                deleteNode = searchNode->next;
                searchNode->next = searchNode->next->next;
                delete deleteNode;
                deleteNode = nullptr;//考虑到悬空指针的问题。
            }
            else {
                searchNode = searchNode->next;
            }
        }//这一步的方法很简洁,而且关键点是将lastnode于currentnode使用单个变量就解决了,后边可以学习使用这种方式。
        head = preHeadNode->next;
        delete preHeadNode;
        return head;
    }
};

设计链表 ;删除链表结点时,一定要注意释放内存,并把悬空指针赋值为空指针;void函数内部可以用return的。

//类内搭建一个链表:
class MyLinkedList {
public:
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val) : val{ val }, next{ nullptr }{}//构造列表初始化
    };//在类的内部定义一个链表结点结构体
    MyLinkedList() {
        dummyHead = new LinkedNode{ 0 };//虚拟头结点
        size = 0;//实际链表长度
    }
    int get(int index) {
        if (index > size - 1 || index < 0) {
            return -1;
        }
        LinkedNode* searchNode = dummyHead;   //只是定义了一个指针变量,并没有申请堆区内存,不考虑释放的问题
        for (int i = 0; i <= index; i++) {
            searchNode = searchNode->next;
        }
        return searchNode->val;
    }
    void addAtHead(int val) {
        LinkedNode* addNode = new LinkedNode{ val };
        addNode->next = dummyHead->next;
        dummyHead->next = addNode;
        size++;
    }
    void addAtTail(int val) {
        LinkedNode* tailNode = new LinkedNode{ val };
        LinkedNode* searchNode = dummyHead->next;
        while (searchNode->next != nullptr) {
            searchNode = searchNode->next;
        }
        searchNode->next = tailNode;
        size++;
    }

    void addAtIndex(int index, int val) {
        if (index >=0 && index <= size) {
            LinkedNode* addNode = new LinkedNode{ val };
            LinkedNode* searchNode = dummyHead;
            for (int i = 0; i < index; i++) {
                searchNode = searchNode->next;
            }
            addNode->next = searchNode->next;
            searchNode->next = addNode;
            size++;
        }
    }
    void deleteAtIndex(int index) {
        if (index >= 0 && index < size)
        {
            LinkedNode* searchNode = dummyHead;
            LinkedNode* deleteNode;
            for (int i = 0; i < index; i++)
            {
                searchNode = searchNode->next;
            }
            deleteNode = searchNode->next;
            searchNode->next = searchNode->next->next;
            delete deleteNode;//释放该堆区内存
            deleteNode = nullptr; //将悬挂指针赋值为空指针
            size--;
        }
    }
private:
    int size;
    LinkedNode* dummyHead; //类MyLinkedList内部存储了一个链表,它只需要记住链表头结点就好了。
};

反转链表,重要部分:不要忽略边缘条件判断这一点
代码随想录里用到了递归,今天搞清楚递归调用的内存占用以及数据返回。

//反转链表
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
		ListNode* prev = nullptr;
		ListNode* searchNode = head;
		ListNode* temp = nullptr;
		while (searchNode != nullptr) { //其实这里就是边缘条件判断了,不要忽略边缘条件判断这一点
			temp = searchNode->next;
			searchNode->next = prev;
			prev = searchNode;
			searchNode = temp;
		}//最终保留的头结点是prev
		return prev;
    }
};

递归写法尝试:

//反转链表,使用递归调用来写
class Solution {
public:
	ListNode* diguireverse(ListNode* prev, ListNode* searchNode) {
		//递归写法
		//1.先写边界条件
		if (searchNode == nullptr) {
			return prev;
		}
		//2.考虑顺序逻辑
		ListNode* temp = searchNode->next;
		searchNode->next = prev;
		prev = searchNode;
		searchNode = temp;
		return diguireverse(prev,searchNode);
	}//递归先考虑边界条件,再考虑顺序逻辑就比较好写了。
    ListNode* reverseList(ListNode* head) {
		//ListNode* prev = nullptr;
		//ListNode* searchNode = head;
		return diguireverse(nullptr, head);
    }
};
//使用递归调用写一个阶乘
int jiecheng(int n) {
	//第一步:先考虑边界条件,最后那部分
	if (n == 0 || n == 1) {
		return 1;
	}
	//第二步考虑顺序逻辑
	return n * jiecheng(n - 1);
}

递归写法的步骤顺序:
1.考虑边界条件2.按照顺序写法去写。
如何理解递归调用:
可以去想象调用函数入栈的操作,执行一个函数时,先把相关变量压入栈中,全部执行完之后再把返回值拿出栈。
递归过程中,计算返回值时需要用到另外一个函数,而计算返回值的相关变量还压在栈底,这时需要调用另外一个函数,即继续向栈中压入新函数的变量,就这样不断调用,一直到达边界条件后,才会出现一个准确的返回值,出现在栈顶部分,此时再往回走,之前的函数返回值都可以拿到一个准确的变量,一直到达栈的底部拿到最终的返回值,这个返回值出栈,栈中就不在有东西了,具体示意图绘制如下:
在这里插入图片描述
复杂比较难理解的反转链表方法:后续可以多想一下,值得思考。这个想起来确实比较复杂一些。这个递归的形式比较特殊,还是比较开阔眼界的。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
		//换一种方法:直接从尾部进行反转,还是用递归
		//先写边界条件
		if (head == nullptr||head->next==nullptr) {
			return head;//来到了末尾
		}
		ListNode* newhead = reverseList(head->next);
		head->next->next = head;
		head->next = nullptr;
		return newhead;
    }
};

两两交换链表中的节点

//我的写法
class Solution {
public:
	ListNode* swapPairs(ListNode* head) {
		ListNode* dummyNode = new ListNode();
		dummyNode->next = head;
		ListNode* cur = dummyNode;
		//虚拟头结点搭建好了
		ListNode* oneNode;
		ListNode* twoNode;
		ListNode* thirdNode;
		if (head == nullptr) {
			return nullptr;
		}
		if (head->next == nullptr) {
			return head;
		}
		while (cur->next!=nullptr) {//考虑偶数个(>=2个)情况
			if (cur->next->next == nullptr) {//考虑奇数个(>=3)情况
				break;
			}
			oneNode = cur->next;
			twoNode = cur->next->next;
			thirdNode = cur->next->next->next;
			cur->next = twoNode;
			twoNode->next = oneNode;
			oneNode->next = thirdNode;
			cur = oneNode;
		}
		head = dummyNode->next;
		delete dummyNode;
		return head;
	}
//代码随想录中的写法
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
        dummyHead->next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
        ListNode* cur = dummyHead;
        while(cur->next != nullptr && cur->next->next != nullptr) {
            ListNode* tmp = cur->next; // 记录临时节点
            ListNode* tmp1 = cur->next->next->next; // 记录临时节点

            cur->next = cur->next->next;    // 步骤一
            cur->next->next = tmp;          // 步骤二
            cur->next->next->next = tmp1;   // 步骤三

            cur = cur->next->next; // cur移动两位,准备下一轮交换
        }
        ListNode* result = dummyHead->next;
        delete dummyHead;
        return result;
    }
};

我的写法和代码随想录中c++写法区别:1.不需要单独考虑空链表和只有一个节点的链表,这些在while的判断表达式中已经包括了;2.这里有一个重要的点就是,&&和||都是短路判断,&&左边的表达式为0就不会再计算右边的表达式,||左边的表达式计算为1就不会再计算右边的表达式。这个很重要,对于:

while(cur->next != nullptr && cur->next->next != nullptr) 

如果是空链表,左侧计算完就不会再计算右边;如果是单个节点链表,左侧计算为1,右侧计算为0.不会执行while内部表达式,直接返回本身。3.我的代码中的twoNode代表一对节点的右侧,这个节点不用单独拿出来,直接用->next->next操作即可。

顺序写法和递归写法(两两交换链表中的节点)

class Solution {
public:
	ListNode* swapPairs(ListNode* head) {
		ListNode* dummyNode = new ListNode();
		dummyNode->next = head;
		ListNode* cur = dummyNode;
		//虚拟头结点搭建好了
		ListNode* oneNode;
		ListNode* twoNode;
		ListNode* thirdNode;
		while (cur->next!=nullptr && cur->next->next == nullptr) {//考虑偶数个(>=2个)情况
			oneNode = cur->next;
			twoNode = cur->next->next;
			thirdNode = cur->next->next->next;
			cur->next = twoNode;
			twoNode->next = oneNode;
			oneNode->next = thirdNode;
			cur = oneNode;
		}
		head = dummyNode->next;
		delete dummyNode;
		return head;
	}
	ListNode* diguiswapPairs(ListNode* head) {//改成递归算法
		//考虑终止条件
		if (head == nullptr || head->next == nullptr) {
			return head;
		}
		//按照顺序思路编写
		ListNode* dummyNode = new ListNode();
		dummyNode->next = head;
		ListNode* cur = dummyNode;
		//虚拟头结点搭建好了
		ListNode* oneNode = cur->next;
		ListNode* twoNode = cur->next->next;
		ListNode* thirdNode = cur->next->next->next;
		cur->next = twoNode;
		twoNode->next = oneNode;
		oneNode->next = thirdNode;
		cur = oneNode;
		ListNode* newhead = diguiswapPairs(cur->next);
		cur->next = newhead;
		head = dummyNode->next;
		delete dummyNode;
		return head;
	}
};

小总结:任何可以通过一定循环操作进行的代码,都可以用递归写法解决(1.写终止条件,2.按照顺序思路编写),以后遇到类似情况都可以用递归思路去写,可以锻炼自己的逻辑思维。

删除链表倒数第n个节点

代码随想录里的思路非常好。双指针先锁定相对距离,然后一步一步滑动前进,非常简洁漂亮的思路。

class Solution {
public:
	ListNode* removeNthFromEnd(ListNode* head, int n) {
		ListNode* dummyNode = new ListNode();
		dummyNode->next = head;
		ListNode* fastNode = head;//fastnode走n步之后为nullptr,那么走之前的位置就是要删除的节点
		ListNode* slowNode = dummyNode;
		ListNode* deleteNode;
		while (n-- && fastNode != nullptr) {
			fastNode = fastNode->next;
		};
		while (fastNode != nullptr) {
			fastNode = fastNode->next;
			slowNode = slowNode->next;
		}
		//循环出来后fastNode就等于nullptr了
		deleteNode = slowNode->next;
		slowNode->next = slowNode->next->next;
		delete deleteNode;
		head = dummyNode->next;
		delete dummyNode;
		return head;
	}
};

链表相交

代码随想录的思路非常好。代码随想录中用到了swap函数,应该是STL中的,这一部分需要后期加深。
补充:C++中swap函数的用法。swap是STL库中的交换两个变量的函数的,输入变量是引用形式输入。

class Solution {
public:
	ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
		//代码随想录中的思路太妙了
		ListNode* curA = headA;
		ListNode* curB = headB;
		int aLength=0, bLength = 0;
		while (curA != nullptr) {
			aLength++;
			curA = curA->next;
		}
		while (curB != nullptr) {
			bLength++;
			curB = curB->next;
		}
		curA = headA;
		curB = headB;
		if (aLength >= bLength) {
			int space = aLength - bLength;
			while (space--) {
				curA = curA->next;
			}
			while (curA != nullptr) {
				if (curA == curB) {
					return curA;
				}
				curA = curA->next;
				curB = curB->next;
			}
		}
		else {
			int space = bLength - aLength;
			while (space--) {
				curB = curB->next;
			}
			while (curB != nullptr) {
				if (curA == curB) {
					return curA;
				}
				curA = curA->next;
				curB = curB->next;
			}
		}
		return nullptr;
	}
};

环形链表

快慢指针法,这道题就比较有难度了,需要认真分析问题并将问题简化才能编出精简的代码。
圆环问题的思路值得反复回味。
在这里插入图片描述

//链表相交
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
		//用快慢指针法
		ListNode* fast = head;
		ListNode* slow = head;
		while (fast!=nullptr&&fast->next!=nullptr) {//防止没有环的情况,以及只有空链表和单个链表的情况,这个思路还是挺缜密的
			fast = fast->next->next;
			slow = slow->next;
			if (fast == slow)
			{
				ListNode* index1 = head;
				ListNode* index2 = fast;
				while (index1 != index2)
				{
					index1 = index1->next;
					index2 = index2->next;
				}
				return index1;
			}
		}
		return nullptr;
    }
};
  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值