LEETCODE链表题目

链表细节:
遍历链表时候

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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值