这里写目录标题
链表细节:
遍历链表时候
temp!=null && temp.next!=null
条件通常是这样的,这是因为,链表经常在循环中被temp = temp .next两次
也就是链表可能出来时候,既有可能是temp.next = null,也有可能temp == null,这个控制条件必须加进去,防止链表为空。
2.被删除了,就不要temp = temp.next,删除了就相当于next了
leetcode中给定的链表,头部链表并没有设置成空的,所以这里会涉及到一个很糟糕的问题,我们找不到头链表的上线了!!!!!!!!
那没办法,我们自己给头链表创造一个上线好了,这个上线就是dummy节点,最后记得dummy中那个0不能被返回,最后返回的时候返回dummy.next就行了,因为如果head被操作了,那么Head可能不复存在了。
19
这种方法傻逼也能想出来,没啥意义
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(temp == null){
return head;
}
ListNode dummy = head;//创造一个头节点
//这样可以操作到头节点被删除的时候
ListNode temp = head.next;
int count = 0;//统计节点数
while(temp != null){
count++;
temp = temp.next;//统计长度
}
int pos = count - n;//找到要被处理的节点位置
temp = head.next;
for(int i = 0;i < pos ;i++){//找到前一个节点
temp = temp.next;
}
temp.next = temp.next.next;///直接断开链接
return head;
}
}
这个只需要一边遍历方法才是大神的方法。
采用两个间隔为n的指针,同时向前移动。当快指针的下一个节点为最后一个节点时,要删除的节点就是慢指针的下一个节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre = new ListNode(0);
pre.next = head;
ListNode start = pre, end = pre;
while(n != 0) {
start = start.next;
n--;
}
while(start.next != null) {
start = start.next;
end = end.next;
}
end.next = end.next.next;
return pre.next;
}
}
作者:guanpengchn
链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/solution/hua-jie-suan-fa-19-shan-chu-lian-biao-de-dao-shu-d/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
链表处理细节
1.使用temp遍历时,temp = null不能创造节点,temp.next = null时候
temp. next = new ListNode才能创造节点,在虚无上无法创造节点
2.temp.next = new ListNode
temp = temp.next创建之后记得移动
3.链表需要头节点,作为返回值
4.头节点不能动需要代理人去动
5.相好节点要不要被删除,删除了之后就不要temp. =. temp.next了
6用栈往里面压链表节点时候,千万记得要么压节点值,要么把节点
断了,节点后是一大家子子子孙孙。
206题
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
思路
我的代码
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
if(l1 == null || l2 == null){
return null;//任一链表为空则都为空
}
else {
//统计一下l1和l2的长度
ListNode head = new ListNode(0);//建立一个头节点,随便给头节点赋个值就行,可以用来移动
ListNode temp = head;//头节点随便放点东西
//以上两个东西用于遍历链表计算链表长度
int addOne = 0;//用于进行进位
while (true){
//将判断跳出循环句子放最前面,先判断符合不符合,不符合直接跳出循环
//直接用自己链表的头节点进行遍历能很好的节省空间,反正
//给的链表头节点以后也没用了,真的有用的是新的头节点
if(l1 == null && l2 == null && addOne == 0){//三个同时没数才会
break;//说明遍历到最后了没有遍历的必要了循环就此终止
}
int val1,val2;//三个东西计算和的时候,当出现一方为null的时候,一定要让为Null的部分数据为0
//由于l1可能为Nul而且要让l1为null的状态存在,为了做停止循环的判断
//所以l1.val这个取值方法并不安全
if(l1 == null){
val1 = 0;
}
else {
val1 = l1.val;
}
if(l2 == null){
val2 = 0;
}
else {
val2 = l2.val;
}
int sum = val1 + val2 + addOne;
if(sum >= 10){
addOne = 1;
}
else {
addOne = 0;
}
temp.next = new ListNode(sum % 10);//创建新的节点,加入计算好的数值
//无论如何节点保留的都是sum % 10取得余数的结果
temp = temp.next;//temp指向新建立的节点
if(l1 != null){//如果遇到了结尾,那就不再继续往下走指针了,指针永远为null
l1 = l1.next;
}
if(l2 != null){
l2 = l2.next;
}
}
return head.next;
}
}
}
1.相加的数据有三个,l1的数据,l2数据,还有进位
前一次相加结果如果大于,则add位为1
2.使用l1的头节点遍历l1,省空间,反正l1以后也用不到了,l2也是如此
当l1遍历到null的时候,指针就不动了,永远停留在null的位置,只要指针指向null,那么想加的时候(l1或l2)就给一个0值
3.创建新的链表随时接受传递进来的数据
3.什么时候循环停止呢?三个相加的数据,l1,l2,intOne,当l1长度到尽头同时l2尽头,同时addOne也没有了的时候,循环结束了,不再进行遍历了
时间 3 ms,空间39理论上的最优解
递归
思路
根据题意可知链表数字位数是从小到大的
因为两个数字相加会产生进位,所以使用i来保存进位。
则当前位的值为(l1.val + l2.val + i) % 10
则进位值为(l1.val + l2.val + i) / 10
建立新node,然后将进位传入下一层。
错误示范:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int add = 0;
ListNode resNode = new ListNode();
resNode.val = 0;//链表靠头节点进行定位必须有
int v1 = 0,v2 = 0;
while(add == 1 || l1 != null || l2 != null){
//三个条件成立循环继续
l1,l2的值必须要在这里进行判断,因为在移动之后l1,l3很可能变成null,在变成null之后需要进行取值判断,不然l1,l2中取不出来值,会有空指针异常的
if(l1 != null){
v1 = l1.val;
}
if(l2 != null){
v2 = l2.val;
}
int newVal = v1 + v2 + add;
if(newVal >= 10){
add = 1;
}
else
add = 0;
resNode.next = new ListNode(newVal % 10);
resNode = resNode.next;
//如果l1,l2为空,指针不往后走了
//但是l1,l2必须挂一个空的名堂,这是判断跳出的条件
if(l1 != null){
l1 = l1.next;
}
else if(l1 == null){
v1 = 0;
///这里是错误的,因为l1和l2在移动后,是不是空已经没办法预测了,所以后面必须要涉及一下空指针异常的预测问题
}
if(l2 != null){
l2 = l2.next;
}
else if(l2 == null){
v2 = 0;
}
}
return resNode.next;
}
递归算法
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
return dfs(l1, l2, 0);
}
ListNode dfs(ListNode l, ListNode r, int i) {
if (l == null && r == null && i == 0) return null;
int sum = (l != null ? l.val : 0) + (r != null ? r.val : 0) + i;
var node = new ListNode(sum % 10);
node.next = dfs(l != null ? l.next : null, r != null ? r.next : null, sum / 10);
return node;
}
}
作者:dnanki
链接:https://leetcode-cn.com/problems/add-two-numbers/solution/di-gui-si-lu-jian-dan-dai-ma-duan-by-dnanki/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
升序链表题目,合并两个升序链表
将两个升序链表合并到一起。
递归解法:
暴力破解法:
创造一个新的链表,然后将L1,l2进行比较,比较小的那个接到新链表后面
然后l1指针后移动,再次进行比较,比较小那个接到新指针后面
当其中任意一个链表为空时候,将另外不空那个链表剩下部分都接在新链表后面就行了。
这就是暴力破解法,并不完美。
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null){
return l2;
}
else if(l2 == null){
return l1;
}
//接着双指针走,遇到相等下一位,接着走
//暴力解法
//需要哑巴指针
ListNode dummy = new ListNode(0,null);
ListNode temp = dummy;
while(l1 != null && l2 != null){//当任一一方为null循环都结束了,把没为Null一方一插就行了
if(l1.val <= l2.val){
temp.next = l1;
l1 = l1.next;//链表加入别人后,自己就移动了了
temp = temp.next;
}
else if(l1.val > l2.val){
temp.next = l2;
l2 = l2.next;
temp = temp.next;
}
}
//当两个链表之一为空时候,将另一个链表接在后边
if(l1 == null){
temp.next = l2;
}
else if(l2 == null){
temp.next = l1;
}
return dummy.next;
}
}
最合适的还是递归法
21
最开始是这个样子
然后让比较小那个的next指向剩下的一群,比较小那个和前面上面的进行比较
谁比较小谁就后移一位。
当其中任一一个到达Null的时候,将剩余的全部return掉。
最后就炼成一片了
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
//先判断一下特殊情况
//特殊情况就是,这个特殊情况也是整个递归终止的条件
//就是任一一方为空,则整个循环终止
if(l1 == null){
return l2;//任何一方当到达结尾之后,就把另外一方剩余的链表进行返回
}
else if(l2 == null){
return l1;
}
if(l1.val < l2.val){//确定谁当头
l1.next = mergeTwoLists(l1.next,l2);//l1作为上一次大战的胜利者,她的下一位由自己后移之后的那一位与l2当前位数决出
return l1;
//l1作为胜利者,自然有资格被返回
}
else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;//l2作为胜利者,可以被直接返回,
}
}
}
l1和L2再某些规则下进行对决,l1胜出后,可以直接作为胜利者,接到前面那个东西里面,然后l1后移动,用自己后面的兄弟,参与对决。
l2赢了,也可以啊。知道谁先到最后,那么另外一个就把全部结果接到最后去。
24题
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
//遍历链表每一个节点,创造一个临时节点
if(head == null){
return null;
}
ListNode dummy = new ListNode(0,null);
dummy.next = head;
ListNode pre = dummy;//用来插在前头
ListNode temp = head;
while(temp.next != null){
ListNode tran = temp.next;
temp.next = temp.next.next;//先断开连接
pre.next = tran;
tran.next = temp;//完成插入在你前面
//完成战术动作
pre = pre.next.next;//在前头那个指针要跟着移动两次哦
if(temp.next == null){
break;
}
temp = temp.next;
}
return dummy.next;
}
}
先移动点,然后搞定后,移动下一个点
不断重复这个过程呢。
递归做法:
直接上三部曲模版:
找终止条件。 什么情况下递归终止?没得交换的时候,递归就终止了呗。因此当链表只剩一个节点或者没有节点的时候,自然递归就终止了。
找返回值。 我们希望向上一级递归返回什么信息?由于我们的目的是两两交换链表中相邻的节点,因此自然希望交换给上一级递归的是已经完成交换处理,即已经处理好的链表。
本级递归应该做什么。 结合第二步,看下图!由于只考虑本级递归,所以这个链表在我们眼里其实也就三个节点:head、head.next、已处理完的链表部分。而本级递归的任务也就是交换这3个节点中的前两个节点,就很easy了。
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode newHead = head.next;
head.next = swapPairs(newHead.next);//将每一个节点都送进去内卷,内卷之后结果给我
newHead.next = head;
return newHead;
}
}
三件事情:
1.递归终止条件?
到达最后了
2.递归每个回合干嘛?
删除next
3.递归返回什么?
将整理好的头链表返回回去。
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
//检查一下链表有没有重复,
//没有重复直接过
//递归玩一把
if(head == null || head.next == null){
return head;//递归终止条件
}
ListNode next = head.next;
head.next = deleteDuplicates(head.next);//将后面整理好链表传递进来
//后面每一个节点都要卷
if(head.val == next.val ){
head.next = next.next;
//发生冲突后删除节点
}
return head;
}
}
暴力拆迁法
两个指针一前以后,友情提示,这个题目应该不用dummy,因为不会动头节点的
当发生重复时候
直接就这样,删除掉temp那个节点,然后让temp节点后移动一位
当循环中没有重复的时候
两个指针pre和temp都往后移动一步。
if(pre.val == temp.val){
pre.next = temp.next;//直接删除某个节点
temp = temp.next;//然后后面指针移动一下,前面指针不动
}
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null){
return null;
}
//两个指针走
ListNode dummy = new ListNode(0,head);
ListNode pre = head,temp = head;
while(temp != null){
//看看有没有到最后
if(pre.val == temp.val){
pre.next = temp.next;//直接删除某个节点
temp = temp.next;//然后后面指针移动一下,前面指针不动
}
else {
temp = temp.next;
pre = pre.next;
}
}
return dummy.next;
}
}
使用一个栈
每次读进来两个节点,
将两个节点反转后,接上去
然后再次在栈里读进来两个节点
用栈的特点反转一下
temp往后移动两次,随后继续操作
如果栈里只能放一个元素,说明到底了。跳出循环,将栈里最后一个给加上就行。
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
//摘一个节点放进stack里
public void pushNode(Stack<ListNode> stack,ListNode temp){
ListNode trant = new ListNode(temp.next.val);//找个临时接点接住那个值
//断开链接
trant.next = null;//摘了节点
temp.next = temp.next.next;//摘了一个节点
stack.push(trant);//节点压进栈
}
public ListNode popNode(Stack<ListNode> stack,ListNode temp){
if(stack.isEmpty()){
return temp;
}
ListNode trant = stack.pop();//吐出来一个节点
trant.next = temp.next;
temp.next = trant;//接上
temp = temp.next;//向后移动再次重复刚才的操作
temp = popNode(stack,temp);
return temp;
}
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode temp = dummy;
Stack<ListNode> stack = new Stack<ListNode>();
//用一个temp遍历所有节点,temp判断next节点有没有被放进去
while(temp.next != null){
//先摘一个节点,
pushNode(stack,temp);
if(temp.next == null){
//摘了之后看看后面还有节点没有
break;//跳出去循环结束了
}
else{
pushNode(stack,temp);//接着卷下一个节点
}
//两个节点在stack里
temp = popNode(stack,temp);//吐出来接上
}
//一旦循环结束,要么结束,要么跳出来了
if(!stack.isEmpty()){
//不为空
ListNode trant = stack.pop();
trant.next = temp.next;
temp.next = trant;//接上最后一个节点
}
//需要将两个节点放进stack里面
//如果能凑出来两个节点,那么直接反转,接上然后temp向后移动两次
//只有一个节点的话,说明到头了,结束循环,将这一个节点吐出去
return dummy.next;
}
}
61题
这个题目最恶心的部分就是在于,他还有
这种东西,所以很难受,我们必须把这个链表直接变成循环链表,变成循环链表的话,整个链表就没办法了,必须是要进行遍历找到尾巴节点了。
时间复杂度O(n)
其实这个题目你很快就会发现规律在哪里呢?
整个题目,由于有很恶心的4个数,结果让你右移动5位这种恶心的操作存在 所以你需要知道链表的长度
然后k / count这种方式算出,需要把后面多少个节点摘走
当k = 3时候,很容易啊,兄弟,就吧k 到尾巴看作一个整个的链表,然后将这个链表去插在head头上,就可以了!
只要你能定位到移动后面第几个节点之后的链表,剩下的的就是送。
当然你也可以循环k次,每次都把尾巴节点,用头插法插在头前面,不过时间复杂度是O(n2),这个时间复杂度真的不行
我最中意的方法
1.先得到链表长度,不然没办法取摸
2.将链表变成循环链表
当K = 2 时候,定位到k的那个位置的前一个位置
使用temp进行断环,并且k那个点成为新的head.
然后将head返回回去就是我们想要的结果。
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(head == null){
return head;
}
//
int count = 1;//注意这里有个细节,就是统计链表长度,因为正好到最后尾巴
//要停住,所以,尾巴不能统计进去,所以链表长度从1开始,为了把尾巴算进去
//尾巴进不去循环
ListNode dummy = new ListNode(0,head);
ListNode cur = head;
while(cur.next != null){
count++;
cur = cur.next;//统计好整个链表长度
}//统计整个链表长度
//连接成循环链表
cur.next = head;//将整个链表变成循环链表
k = k % count;//确定好你要把后面第几位摘掉
ListNode pos = head;
for(int i = 1;i < count - k;i++){
//定位到位置上去,定位到要干掉的节点的前一个节点
pos = pos.next;
}
head = pos.next;//建立新的头节点
pos.next = null;//断环
return head;
}
}
说白了就是只要重复,那全都不要了。
两个指针,一个指针用来删除,另一个指针用来遍历
记住使用pre.next和temp进行比较
当pre.next终于不等于temp时候让pre.next = temp就可以了。直接断开链接。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null || head.next==null) {
return head;
}
ListNode dummy = new ListNode(-1,head);
ListNode pre = dummy;
ListNode temp = head;
while(temp!=null && temp.next!=null) {
//初始化的时a指向的是哑结点,所以比较逻辑应该是a的下一个节点和b的下一个节点
if(pre.next.val!=temp.next.val) {
pre = pre.next;
temp = temp.next;//没发生冲突直接移动
}
else {
//如果a、b指向的节点值相等,就不断移动b,直到a、b指向的值不相等
while(temp!=null && temp.next!=null && temp.next.val==pre.next.val) {//防止越界
temp = temp.next;
}
pre.next = temp.next;
temp = temp.next;
}
}
return dummy.next;
}
}
链表题目
非常简单题目使用递归来做
203
最终链表后面那个返回的是被整理好的链表。
当head自身的val值,等于val时候,head自身就是需要被删除的对象,这样的情况下,就不需要返回head的值了,将head.next,也就是说我们需要head后面那个值,而不是Head本身,因为head自己要被删除了
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head == null) {
return null;
}
head.next=removeElements(head.next,val);//将后面节点放进去卷
return head.val==val ? head.next : head;
}
}
递归方法唯一问题是,必须要使用head.val进行判断,而不是head.next.val,因为你没有哑巴节点,没把法处理head.val == val这种情况
链表题目,没法反转,就用栈啊!!
既然不能反转,那不用栈?
每次弹出来一个,然后相加,统计进位
长度不想等时候,弹出来的,就和0相加
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
if(l1 == null){
return l2;
}
if(l2 == null){
return l1;
}
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
//存储两个链表
ListNode p1 = l1;
ListNode p2 = l2;
ListNode dummyNew = new ListNode(0,null);
while(p1 != null){
stack1.push(p1.val);
p1 = p1.next;
}
while(p2 != null){
stack2.push(p2.val);
p2 = p2.next;
}
//先将链表放进栈
int add = 0;
while(stack1.size() != 0 || stack2.size()!= 0 || add == 1 ){
//用栈的长度来控制循环,栈的长度是会变化的!!!!
//只要有一个不为0,或者add 为1循环机会继续
//只要有一个还在继续,那么计算还在进行
int s1 = stack2.size() == 0 ? 0:stack2.pop();//长度为0,就弹出来0
int s2 = stack1.size() == 0 ? 0:stack1.pop();
int value = (s1 + s2 + add) % 10;
add = (s1 + s2 + add) >= 10 ? 1:0;
//将数值用头插法放进心恋表
ListNode temp = new ListNode(value,dummyNew.next);//街上尾巴
dummyNew.next = temp;//头插
}
return dummyNew.next;
}
}
86题
最难的部分是元素之间,相对位置不能动。比如4和3,之间相对位置不可以移动,所以很不容易,怎么才能让元素之间相对位置不移动呢
至于所有元素之间,其实是没有排序的,比如x如果是4
那前面也可以是
1 3,2,2,4,5
两个分区元素的相对位置不发生改变,并不是升序排列。
其实这句话就在提醒你,去建立一个两个链表,第一个链表,保持所有小于x的数据,按顺序保存
第二个链表是大于x的数据,也按顺序保存,最后合并在一起。
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode partition(ListNode head, int x) {
if(head == null){
return null;
}
//
ListNode dummySmall = new ListNode(0,null);
ListNode dummyBig = new ListNode(0,null);
ListNode smallTail = dummySmall;//作为尾巴
ListNode bigTail = dummyBig;
ListNode temp = head;//遍历链表
while(temp != null){
if(temp.val < x){
//进小链表
smallTail.next = new ListNode(temp.val,null);
smallTail = smallTail.next;//尾插入small表
temp = temp.next;//移动temp继续遍历
}
else if(temp.val >= x){
bigTail.next = new ListNode(temp.val,null);
bigTail = bigTail.next;
temp = temp.next;
}
}
//合并链表
smallTail.next = dummyBig.next;//将大小链表合并
return dummySmall.next;
}
}
速度太慢了
速度堪称撒比
第二种方法
找到第一个比x大的位置,这个位置就是大链表的起点,我们称为insert点
这个位置之前的位置,就是小链表的尾巴
将所有insert之后遍历到的比x小的都放到insert点之前,也就是小链表尾巴上
将后面面对的比x小的点,放到insert点前面这样只用构建
一个链表
insert点就是大链表起点
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode partition(ListNode head, int x) {
if(head == null){
return null;
}
ListNode dummy = new ListNode(0,head);
ListNode insert = dummy;//因为头节点也可能是插入点
while(insert != null && insert.next != null){
if(insert.next.val >= x){
break;
}
insert = insert.next;
//找到插入点
}
if(insert == null){//insert可能直接到尾巴
return dummy.next;
}
else{
ListNode temp = insert.next;//继续往后遍历
ListNode bigHead = insert.next;//将大连表的头节点记录一下,用来连接每次插入的链表
while(temp != null && temp.next != null){
if(temp.next.val < x){
//记得把符合条件的数据都删了,毕竟插前面了
insert.next = new ListNode(temp.next.val,bigHead);
insert = insert.next;//尾插法,insert就是那个尾巴
ListNode p = temp.next;
temp.next = temp.next.next;//断开连接
//断开之后就不用后移动了
}
else{
temp = temp.next;
}
}
}
return dummy.next;
}
}
92题
可以考率,记录下断开为止,然后将中间链表,压栈里,再从断开位置从栈里把链表拿出来用尾插法进行插入
但是这个方法,看起来很笨重,其实我们可以直接将Left后面的节点用头插法,插在let前面就没问题啦!
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
//先找到Left点
ListNode dummy = new ListNode(0,head);
ListNode pLeft;
ListNode temp = dummy;//要考虑头节点情况
int count = 1;//我已经在一个节点上了
while(temp != null && count != left && temp.next != null ){
count++;
//找到Left前面
temp = temp.next;//找到left左边第一个节点
}
pLeft = temp;//作为头插法的插入头
temp = temp.next;//要避开left,将所有东西,插在left左边
while(temp != null && temp.next != null && count != right){//找到right的前面一个节点
count++;
ListNode p = temp.next;//将后面的节点摘下啦,头差到pLeft上
temp.next = temp.next.next;//摘了
p.next = pLeft.next;
pLeft.next = p;//头插好
}
return dummy.next;
}
}
1171题
这个题目膜拜大神级别算法
class Solution {
public ListNode removeZeroSumSublists(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
Map<Integer, ListNode> map = new HashMap<>();
// 首次遍历建立 节点处链表和<->节点 哈希表
// 若同一和出现多次会覆盖,即记录该sum出现的最后一次节点
int sum = 0;
for (ListNode d = dummy; d != null; d = d.next) {
sum += d.val;
map.put(sum, d);
}
// 第二遍遍历 若当前节点处sum在下一处出现了则表明两结点之间所有节点和为0 直接删除区间所有节点
sum = 0;
for (ListNode d = dummy; d != null; d = d.next) {
sum += d.val;
//如果中间没有0,那取得节点,顶多就是取得到自己本身,也就是说节点本身并不移动
d.next = map.get(sum).next;
//注意细节,在删除了节点之后,temp是需要往后移动的,因为temp是根据自身来判断当前值要不要加的
}
return dummy.next;
}
}
算法解释
真正的大神级别算法,充分利用了hash特习=性。
链表有环,并且返回环第一个入环节点的题目。
人家链表在里面都是先移动一下!!!
不然的话,一开始就是fast = slow的!!!!!!!!
148题
排序链表
链表具有天然递归性,因为递归最好
怎么找到中点?
快慢指针找到中点
链表和数组不一样,不能仅仅通过数组传递下标方式去处理,
链表必须进行切割,不断不断的进行切割
因为链表我们通过的是快慢指针方式
切割之后还需要保存一下右边那个节点
每次返回来的都是两个已经排序好链表
那么我们要做的事情就是合并两个升序链表,这个事我们以前做过了。
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
//链表具有天然递归性
//所以设计到递归的归并排序明显非常适合链表的排序
//每次先对节点进行分割
if( head == null || head.next == null){
return head;//这里千万记得,到最后时刻,就剩一个节点了,就把自己返回回去
}
//链表没办法传递下标,因为只能分割,用快慢指针去分割
//但是注意我们要的是偶数时候前面那个中点
ListNode fast = head;//每次进来都遍历处理一下链表
ListNode slow = head;
while(fast.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;//
}
//循环结束时候slow 就是中点了
//断开slow然后两个链表分别去递归,递归结束后就是两个升序链表,用双指针发进行合并
//以slow为分解线进行切割
ListNode temp = slow.next;
slow.next = null;
//每次传递进去的都是被分割完了的链表
//传递的是分割好的链表
ListNode leftList = sortList(head);
ListNode rightList = sortList(temp);//左右左右两个递归排序结果
ListNode dummy = new ListNode(0);
ListNode moment = dummy;
//两个结果都是升序链表将两个升序链表合并
while(leftList != null && rightList != null){
if(leftList.val <= rightList.val ){
moment.next = leftList;
moment = moment.next;
leftList = leftList.next;
}
else{
moment.next = rightList;
moment = moment.next;
rightList = rightList.next;
}
}
//跳出循环,把没弄完链表加进来
moment.next = leftList == null ? rightList : leftList;
return dummy.next;
}
}
82题
使用链表方式解决问题
这个题目
1.递归终止条件是什么?
答案:当只有一个节点时候就终止了,或者节点为null时候终止
2.每次递归时候,每个head节点要干嘛?
进来一个head,倘若head值和后面值相等,就一直找到head不和后面相等的那个节点,然后返回那个节点,表明我们排序好了链表
倘若head和并不和后面值相等那说明head作为头结点已经身后链表已经被排序好了,直接返回head就可以了。
/**
* Definition for singly-linked list.
* 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; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
//递归
//当只有一个节点时候返回这个节点就行了
if(head == null || head.next == null){
return head;
}
//每次递归你都要和自己后面那个值进行比较
if(head.val == head.next.val){
while(head.val == head.next.val){
head = head.next;
if(head == null || head.next == null){
break;
}
//一直找到重复数的末尾
//然后把自己下一个值返回回去,因为自己仍然是重复的数
}
head.next = deleteDuplicates(head.next);
//自己的下一个应该是已经被排列好的数字
//自己下一个值来自继续内卷
return head.next;
}
else{
head.next = deleteDuplicates(head.next);//下一个职继续内卷,但是head已经是有序链表///可以直接被返回
return head;
}
}
}
328题
奇偶链表题目
还是官方解法比较好
我自己的解法差点是
两个指针遍历
奇数指针先动,借助偶数指针找到下一个点串联起来
然后偶数指针动,借助奇数指针找到下一个点串联起来
一直找找
终止条件是偶数指针为空了,或者偶数指针找到最后一个了
相当于每个点,如果左子树存在,找到左子树的上最右的点,然后将这个点和根节点上的点连接起来。
相当于将根节点的左子树取消了
然后从根节点开始往右走看看有没有左子树
leetcode第23
有一个可以节省空间方式
用一个傻瓜节点,将所有节点串联起来
l1,l2进行比较,如果l2小,那么temp指向l2,l2后移动
最后返回dummy.next就行了
146
关键在于时间复杂度为O(1)这种情况下,就要想到hash表,因为hash表时间复杂度为O(1)
由于时常增删改数据,所以要用到链表。
LRU缓存算法的本质:
分配给缓存一定空间
随时查到需要的数据
当空间满了时候,将长时间用不到的数据删除掉。
思路:
被查询过的数据会排在第一位
每次查询一个数据都往往前排位
很久没被查询过的数据会越来越往后排
当内存满了,还要挤进来数据的时候,排名靠后的数据,就会被挤掉。
伪代码
/* 缓存容量为 2 */
LRUCache cache = new LRUCache(2);
// 你可以把 cache 理解成一个队列
// 假设左边是队头,右边是队尾
// 最近使用的排在队头,久未使用的排在队尾
// 圆括号表示键值对 (key, val)
cache.put(1, 1);
// cache = [(1, 1)]
cache.put(2, 2);
// cache = [(2, 2), (1, 1)]
cache.get(1); // 返回 1
// cache = [(1, 1), (2, 2)]
// 解释:因为最近访问了键 1,所以提前至队头
// 返回键 1 对应的值 1
cache.put(3, 3);
// cache = [(3, 3), (1, 1)]
// 解释:缓存容量已满,需要删除内容空出位置
// 优先删除久未使用的数据,也就是队尾的数据
// 然后把新的数据插入队头
cache.get(2); // 返回 -1 (未找到)
// cache = [(3, 3), (1, 1)]
// 解释:cache 中不存在键为 2 的数据
cache.put(1, 4);
// cache = [(1, 4), (3, 3)]
// 解释:键 1 已存在,把原始值 1 覆盖为 4
// 不要忘了也要将键值对提前到队头
作者:labuladong
链接:https://leetcode.cn/problems/lru-cache/solution/lru-ce-lue-xiang-jie-he-shi-xian-by-labuladong/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
说白了就是按访问时间进行排序。
由于要经常性调动元素,所以要用链表
并且是双向链表,因为双向链表可以自己删除自己
调动元素非常方便
而且我们最好要有个伪装头节点
还有有个伪装尾节点,在头尾都容易删除元素
(这里用LinkedList不也行)
因为时间复杂度为O(1)所以,我们要用hash表来弥补链表查询能力满的问题。
到这里就能回答刚才「为什么必须要用双向链表」的问题了,因为我们需要删除操作。删除一个节点不光要得到该节点本身的指针,也需要操作其前驱节点的指针,而双向链表才能支持直接查找前驱,保证操作的时间复杂度 O(1)。
我们是用hash表直接定位到节点,如果不是双向链表,没办法自己删除自己的。
作者:labuladong
链接:https://leetcode.cn/problems/lru-cache/solution/lru-ce-lue-xiang-jie-he-shi-xian-by-labuladong/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
有头节点和伪装尾节点是为了删除方便。
//我的设计原则
//某个节点被访问后,就会被提升到头节点后面,成为头号热点
//新加进来的节点在头部,说明刚被访问过
//光有一个Map没用,因为Map并不知道哪些东西应该删除。或者保留,某种意义上Map内容也在被双连链表限制
//map中应该key保存关键字,value保存链表中节点,我们用的是双向链表,所以,只要拿到节点地址,直接可以删除。
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/lru-cache/solution/lruhuan-cun-ji-zhi-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。