[刷题记录]牛客面试笔刷TOP101(一)

news2024/11/18 21:45:49

牛客笔试算法必刷TOP101系列,每日更新中~(主要是记录自己的刷题,所以描述的可能不是很清楚

但如果刚好能帮助到你就更好了)

后续后头复习的时候,记得是看正解啊,别对着错的例子傻傻看了...

目录

1.合并有序链表2023.9.3

2.链表是否有环2023.9.4

3.判断链表中环的入口点

4.链表中倒数最后K个节点2023.9.5

5.删除链表的倒数第n个节点

6.两个链表的第一个公共节点2023.9.6

7.链表相加

8.单链表的排序23.9.7

 9.判断一个链表是否为回文结构

10.链表的奇偶(节点)重排23.9.8

11.删除有序链表中的重复元素

12.删除有序链表中的重复元素||23.9.9

13.二分查找-| 23.9.10

14.二维数组中的查找

15寻找峰值

16.比较版本号23.9.11

17.二叉树的前序遍历

18.二叉树的中序遍历

19二叉树的后序遍历

20.二叉树的层序遍历23.9.12

21.按之字形顺序打印二叉树

22.二叉树的最大深度23.9.13

23.二叉树中和为某一值的路径(一)

24.二叉搜索树与双向链表

25.对称的二叉树23.9.14


1.合并有序链表2023.9.3

合并两个排序的链表_牛客题霸_牛客网 (nowcoder.com)

题意大致为:

将两个链表中的元素按照从小到大的顺序合并成为一个链表.

所给予的条件:

给出的所要合并的链表都是从小到大顺序排列的.

思路:

创建一个新的头节点来方便组装新的链表.分别用两个指针遍历两个链表,比较两个指针所在的节点,较小的节点先一步存放到新链表中,并且相应的指针向后移动一位.

移动后的指针所在的节点再与先前较大的未移动的节点进行比较,循环进行上面的操作.

直到任一链表遍历完毕,再把另一没遍历完的链表剩下的节点连接到新链表的尾巴. 

错解:

public ListNode Merge (ListNode pHead1, ListNode pHead2) {
        // write code here
        ListNode fakeHead = new ListNode(-1);
        ListNode cur = fakeHead;
        while(pHead1.next != null || pHead2.next != null){//我怎么用了或呢!!!
                                                   //明明是把链表都遍历一遍不要管他next
            if(pHead1.val <= pHead2.val){
                cur.next = pHead1;
                cur = cur.next;
                pHead1 = pHead1.next;
            }else{
                cur.next = pHead2;
                cur = cur.next;
                pHead2 = pHead2.next;
            }
        }
        if(pHead1.next == null){//上面错,下面跟着错了.
            cur.next = pHead2;
        }
        if(pHead2.next == null){
            cur.next = pHead1;
        }
        return fakeHead.next;
    }

刚开始,可能脑袋真的不好使了.

明明想的是,要把链表都遍历完整一遍,分别拿每一个节点跟另一个链表的节点进行比较.结果while循环中写的却是.next.还想不出问题在哪里真的是罪过.

还有while循环条件中的连接符号居然用的||.

只要有一个链表遍历完了就结束循环,所以要用&&啊啊啊!!!

真的是出师未捷身先死,下面好好加油吧.

正确题解:

起码脑子里的思路是对的,就是想的跟写的对不太上...

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pHead1 ListNode类 
     * @param pHead2 ListNode类 
     * @return ListNode类
     */
    public ListNode Merge (ListNode pHead1, ListNode pHead2) {
        // write code here
        if(pHead1 == null){
            return pHead2;
        }
        if(pHead2 == null){
            return pHead1;
        }
        ListNode fakeHead = new ListNode(-1);
        ListNode cur = fakeHead;
        while(pHead1 != null && pHead2 != null){//这里之前用了||
            if(pHead1.val <= pHead2.val){
                cur.next = pHead1;

                pHead1 = pHead1.next;
            }else{
                cur.next = pHead2;
                
                pHead2 = pHead2.next;
            }
            cur = cur.next;
        }
        if(pHead1 == null){
            cur.next = pHead2;
        }else{
            cur.next = pHead1;
        }
        return fakeHead.next;
    }
}

2.链表是否有环2023.9.4

判断链表中是否有环_牛客题霸_牛客网 (nowcoder.com)

题意:

判断链表中是否有环,环所指的是:链表中节点的next存放此节点先前节点的地址.

给予的条件:

普通链表一个

思路:

这道题先前有学习过,我学到了两种解法.

①:用快慢指针的方法,快指针的速度是慢指针的两倍.在完全遍历完链表先前,如果快指针与慢指针相遇了,则证明此链表中含有环.反之,证明没有.(闭环追及问题)

②:用HashSet的方法,将链表进行遍历,把遍历到的节点的地址存放到set当中.如果链表有环则必定会有节点重复存放第二次,就可以用contains来判断有没有环.

public static boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        //因为fast一次走两步,在while循环中就要判断能否有足够的位置够一次走两步
        //首先要判断fast!=null,看是否遍历完成,如果没有再看其后面有没有节点
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast){
                return true;
            }
        }
        return false;
    }

3.判断链表中环的入口点

链表中环的入口结点_牛客题霸_牛客网 (nowcoder.com)

思路:首先是要判断链表有没有环,有环才能进行后面的操作

public ListNode EntryNodeOfLoop(ListNode pHead) {
        ListNode fast = pHead;
        ListNode slow = pHead;
        boolean check = false;
        //首先要判断链表有没有环
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                check = true;
                break;
            }
        }
        if(!check){
            //证明没有环
            return null;
        }
        slow = pHead;
        while(slow != fast){
            slow = slow.next;
            fast = fast.next;
        }
        return fast;
    }

4.链表中倒数最后K个节点2023.9.5

链表中倒数最后k个结点_牛客题霸_牛客网 (nowcoder.com)

题意:

根据给出的K,来返回链表中倒数第K个节点及其往后的所有节点.

思路:(有误)

这个题目以前写过了.

用的是快慢指针的方式,倒数第一个与倒数第K个节点之间相差K-1个节点.

所以,可以先让快指针从头节点走k-1步,再让慢指针从头节点开始,与快指针一齐一次走一步,直到快指针到达了末尾节点,此时慢指针所在的节点就是倒数第K个节点.

将慢指针所在的节点返回即可. 

纠正:

其实这里的本质是,控制两端的距离,再平行的进行移动.

快指针其实移动k个节点会好一点,

错解:

这里写的是移动k个节点了,跟想的时候不一样.如果是移动k个节点,看的是末尾后一个位置即空节点与倒数第K个节点的位置距离.

例如,倒数第三个节点,与空节点相差的距离是3.即倒数第三个节点要移动3次才能到达空节点的位置.

先判定再移动是为了能预防出现快指针走过头,走到了null,直接返回null却没有返回slow的情况

如果链表长5,求倒数第6个节点的时候,就会出现.

public ListNode FindKthToTail (ListNode pHead, int k) {
        if(pHead == null){
            return null;
        }
        // write code here
        ListNode fast = pHead;
        ListNode slow = pHead;
        for(int i = 0; i < k; i++){
            fast = fast.next;
            if(fast == null){
                return null;//主要是这里的问题,
                            //因为倒数的k刚好为链表的长度,而fast是从头节点1开始
                            //走到空指针之后就直接返回了
                            //其实应该把if判定条件放到移动指针的上面
                            //而且在外面加一个判断k是否合法的条件
                            //当k小于0或等于0时返回null.
            }
        }
        while(fast != null){//fast要在尾巴节点停下来
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

正解:

2023.9.7补充一下,for循环里面的if主要是为了防止越界的,证明当前fast指针指向的节点不为空,可以继续往下走.是作为条件而不是判断,所以要放在上面而不是下面.

public ListNode FindKthToTail (ListNode pHead, int k) {
        if(pHead == null){
            return null;
        }
        // write code here
        ListNode fast = pHead;
        ListNode slow = pHead;
        for(int i = 0; i < k; i++){
            if(fast == null){
                return null;
            }
            fast = fast.next;
            
        }
        while(fast != null){//fast要在尾巴节点停下来
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

5.删除链表的倒数第n个节点

删除链表的倒数第n个节点_牛客题霸_牛客网 (nowcoder.com)

嗨呀,这个跟上面一起写的.简单啦,我就不信我会错.

思路(否决):

既然是删除,肯定要找到被删除的节点的前驱与后驱.再将他们连接起来.

找到倒数第n个节点,继续用快慢指针的方式来找.

有一个问题,slow的位置恰好倒数第n个的位置,那么其前驱我们就不能知道了.

所以要找倒数第n个位置的前一个,那么slow与fast之间的距离就会增加一位.变成了fast走n+1

还想到了,可能会删除头节点这种情况,想在fast移动之后加上一个判定条件 

思路(第二版):

觉得上面的太麻烦了,还容易出错.因为上面的思路会存在越界的行为.

头节点会变动的题型还是创建一个假的头节点来存放比较好.再加上一个新的指针在slow指针的前一位变动,tmp就是前驱slow是倒数第n个fast是后驱.将tmp与fast连接起来就好了

public ListNode removeNthFromEnd (ListNode head, int n) {
        // write code here
        ListNode fast = head;
        ListNode slow = head;
        ListNode fakeHead = new ListNode(-1);
        fakeHead.next = head;
        ListNode tmp = fakeHead;
        // if(n <= 0){
        //     return null;
        // }  写完才看到,题目保证n一定有效

        for(int i = 0; i < n; i++){
            if(fast == null){//走过头了
                return null;
            }
            fast = fast.next;
        }

        while(fast != null){
            fast = fast.next;
            slow = slow.next;
            tmp = tmp.next;
        }
        
        tmp.next = slow.next;
        return fakeHead.next;
    }

6.两个链表的第一个公共节点2023.9.6

两个链表的第一个公共结点_牛客题霸_牛客网 (nowcoder.com)

思路:

也是炒冷饭了.想到的是先遍历链表,分别得到他们的长度.

用双指针的方式,对于较长的链表先走两个链表长度差的距离.

再让两个指针分别从链表出发,一次走一步直到null,如果其中两指针相对,则证明有公共节点,

然后还学到了第二个方法.

对比上面的方法的优点是:不用考虑哪一个链表比较长,也不用额外写代码来得到链表的长度.虽然他们的本质都是双指针的方式.

就像下图的一样,直接让两个指针从两个链表的头节点开始一起往下走,任一一个指针遍历完所在的链表后,来到空节点时则会跳转到另一条链表的头节点开始遍历.

观察下面的图我们可以发现,这样就能消除链表长度导致的长度差.

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode l1 = pHead1;
        ListNode l2 = pHead2;

        while(l1 != l2){
            if(l1 == null){
                l1 = pHead2;
            }else{
                l1 = l1.next;
            }

            if(l2 == null){
                l2 = pHead1;
            }else{
                l2 = l2.next;
            }
        }
        return l1;
    }

7.链表相加

链表相加(二)_牛客题霸_牛客网 (nowcoder.com)

思路:

我只能想到把链表都给翻转了才进行后面的操作.

然后创建一个新的头节点,创建新的节点.如果现在相加的节点个位溢出了就要移动到下一位去.

先写写看吧.

错解(愚蠢的错误):

最后一个例子没过去,好可惜.

努力找找看哪里错了吧...我看别人的代码好优雅噢.我的有点不堪.

public ListNode addInList (ListNode head1, ListNode head2) {
        // write code here
        //先翻转两个链表
        head1 = rever(head1);
        head2 = rever(head2);
        ListNode fakeHead = new ListNode(-1);
        //创建两个指针来遍历链表
        ListNode l1 = head1;
        ListNode l2 = head2;
        ListNode cur = fakeHead;//遍历新链表
        //存储溢出
        int count = 0;
        while(l1 != null && l2 != null){
            int tmp = l1.val + l2.val + count;
            if(tmp >= 10){
                tmp -= 10;//溢出了,得留到下一个.
                count = 1;
            }else{
                count = 0;
            }
            ListNode node = new ListNode(tmp);
            cur.next = node;
            cur = node;

            l1 = l1.next;
            l2 = l2.next;
        }
        while(l1 != null){
           int tmp = l1.val + count;
           if(tmp >= 10){
                tmp -= 10;
                count = 1;
           }else{
                count = 0;
           }
           ListNode node = new ListNode(tmp); 
           cur.next = node;
           cur = node;
           l1 = l1.next;
        }
        while(l2 != null){
           int tmp = l2.val + count;
           if(tmp >= 10){
                tmp -= 10;
                count = 1;
           }else{
                count = 0;
           }
           ListNode node = new ListNode(tmp); 
           cur.next = node;
           cur = node;
           l1 = l1.next;//什么东西啊,什么东西啊.对自己无语啦.,把这里改成l2就好了
        }
        if(count != 0){
            ListNode node = new ListNode(count);
            cur.next = node;
            cur = node;
        }
        return rever(fakeHead.next);
    }

    public ListNode rever(ListNode head){
        ListNode cur = head.next;
        ListNode pre = head;
        while(cur != null){
            ListNode curNext = cur.next;
            cur.next = pre;
            pre = cur;
            cur = curNext;
        }
        head.next = null;
        return pre;
    }

正解:

在错解里的代码改了.糊涂啊糊涂 

8.单链表的排序23.9.7

单链表的排序_牛客题霸_牛客网 (nowcoder.com)

思路(没想到):

一开始我只想到了把链表放到数组里面,在数组中进行排序之后再赋值到链表上.但是感觉不怎么好,又看到了题解里面说的用分治的思想,其实就是归并排序嘛.但是我内心会对递归有一定的抵触心理...但还是认真看了一遍题解,也自己动手画了一遍图.思路是清晰了,但不知道代码写得怎么样.先动手看看吧.

public ListNode sortInList (ListNode head) {
        // write code here
        //既然是用归并排序递归的思路:
        //递归首先要考虑的是其结束的条件.
        //当只剩一个节点或没有节点的时候结束拆链表的递归操作
        if(head == null || head.next == null){
            return head;//返回当前节点
        }
        //先找到链表的中间点,进行拆.
        //找中间点用到了快慢指针的思想,再加上一个中间点的前驱节点才能进行拆除
        ListNode left = head;
        ListNode mid = head.next;
        ListNode right = mid.next;
        while(right != null || right.next != null){
            //因为要考虑链表节点个数的奇偶情况
            //奇数个的时候right指针走到尾巴节点就该停下来了,再继续走两步会越界
            //所以是right.next!=null的情况
            //偶数个的时候right指针可以走到尾巴节点的后一个空节点
            //还有就是因为right一次走两步的关系,需要判断能否还有足够的节点
            left = left.next;
            mid = mid.next;
            right = right.next.next;
        }
        //走完了,此时mid来到了中间节点的位置,left是mid的前驱节点的位置
        //别忘了head
        //此时前半段链表就被head和left所包裹
        //后半段链表就被mid与right所包裹
        //就要将他们分开了
        left.next = null;
        //继续调用来递归
        ListNode lhead = sortInList(head);//递归前半段
        ListNode rhead = sortInList(mid);//递归后半段
        //将他们进行排序
        //写一个排序的方法
        return sort(lhead,rhead);
    }

    public ListNode sort(ListNode p1,ListNode p2){
        //此处用双指针的方式,来分别遍历两个链表
        //就跟归并排序里的一样了
        //还要判断谁空了就返回另一半
        if(p1 == null){
            return p2;
        }
        if(p2 == null){
            return p1;
        }
        //重新创建一个新的链表来存储
        ListNode cur = new ListNode(-1);
        while(p1 != null && p2 != null){
            if(p1.val > p2.val){
                cur.next = p2;
                p2 = p2.next;
            }else{
                cur.next = p1;
                p1 = p1.next;
            }
        }
        //如果有剩余的节点没有遍历到,就直接加上去
        if(p1 != null){
            cur.next = p1;
        }else{
            cur.next = p2;
        }
        return cur.next;//最后返回排好序的链表
    }

正解:

都没有什么大错误,就是有一些逻辑上的不清楚.

也算是对递归这个心魔没那么恐惧了吧...

public ListNode sortInList (ListNode head) {
        // write code here
        //既然是用归并排序递归的思路:
        //递归首先要考虑的是其结束的条件.
        //当只剩一个节点或没有节点的时候结束拆链表的递归操作
        if(head == null || head.next == null){
            return head;//返回当前节点
        }
        //先找到链表的中间点,进行拆.
        //找中间点用到了快慢指针的思想,再加上一个中间点的前驱节点才能进行拆除
        ListNode left = head;
        ListNode mid = head.next;
        ListNode right = mid.next;
        while(right != null && right.next != null){//错误1:调试后发现错误了||
            //因为要考虑链表节点个数的奇偶情况
            //奇数个的时候right指针走到尾巴节点就该停下来了,再继续走两步会越界
            //所以是right.next!=null的情况
            //偶数个的时候right指针可以走到尾巴节点的后一个空节点
            //还有就是因为right一次走两步的关系,需要判断能否还有足够的节点
            left = left.next;
            mid = mid.next;
            right = right.next.next;
        }
        //走完了,此时mid来到了中间节点的位置,left是mid的前驱节点的位置
        //别忘了head
        //此时前半段链表就被head和left所包裹
        //后半段链表就被mid与right所包裹
        //就要将他们分开了
        left.next = null;
        //继续调用来递归
        ListNode lhead = sortInList(head);//递归前半段
        ListNode rhead = sortInList(mid);//递归后半段
        //将他们进行排序
        //写一个排序的方法
        return sort(lhead,rhead);
    }

    public ListNode sort(ListNode p1,ListNode p2){
        //此处用双指针的方式,来分别遍历两个链表
        //就跟归并排序里的一样了
        //还要判断谁空了就返回另一半
        if(p1 == null){
            return p2;
        }
        if(p2 == null){
            return p1;
        }
        //重新创建一个新的链表来存储
        ListNode head = new ListNode(-1);//补充3:这里忘记加一个遍历的指针了
        //直接用头节点去接了怪不得返回只有一个节点
        ListNode cur = head;
        while(p1 != null && p2 != null){
            if(p1.val >= p2.val){//补充2 加一个=
                cur.next = p2;
                p2 = p2.next;
            }else{
                cur.next = p1;
                p1 = p1.next;
            }
            cur = cur.next;//补充1
        }
        //如果有剩余的节点没有遍历到,就直接加上去
        if(p1 != null){
            cur.next = p1;
        }else{
            cur.next = p2;
        }
        return head.next;//最后返回排好序的链表
    }

 9.判断一个链表是否为回文结构

判断一个链表是否为回文结构_牛客题霸_牛客网 (nowcoder.com)

思路:

找到中间的节点,将后半段链表进行翻转.

用双指针的形式,从开头与中间开始遍历节点.如果两者不一样,则证明为不是回文结构.

 错解:

不是很明白出什么问题了,先放到idea调试看看

主要是翻转之后与前半段的链接没有处理好.

我想实现的是指针分别遍历,没有给他们设null,直到他们相遇或者在偶数情况下在相邻的时候结束循环.

错的原因写在相应的注释里了,链表题还是要好好画图啊.还有记得理清一下循环结束的条件,是用||还是&&

public boolean isPail (ListNode head) {
        // write code here
        //先用双指针的形式找到中间节点,如果是偶数个节点,就找中间靠右的节点
        if(head == null || head.next == null){
            return true;//如果只有一个节点或者为空则
        }
        ListNode slow = head;
        ListNode fast = head;
        boolean count = true;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        //此时slow为中间节点
        //这里本质上还是多此一举了,如果是奇数个直接从中间开始翻转就好了
        //这里的想法也是想把中间节点与翻转后的末尾进行连接,让两个指针能够相遇
        //但是顺序错了,而且这样就会太复杂
        if(fast != null){
            //证明为奇数个数,中间节点向后走一个
            ListNode mid = slow;
            slow = slow.next;
            slow.next = mid;
        }
        //翻转后半段
        slow = reverse(slow);
        while(head != slow || head.next != slow){//还要你,我最近怎么老是用错||与&&
            if(head.val != slow.val){
                count = false;
                break;
            }
            head = head.next;
            slow = slow.next;
        }
        return count;
    }

    public ListNode reverse(ListNode head){
        ListNode cur = head.next;
        ListNode pre = head;
        while(cur != null){
            ListNode curNext = cur.next;
            cur.next = pre;
            pre = cur;
            cur = curNext;
        }
        return pre;
    }

正解:

还是得自己调试和画图才能写出来,下次加油嗷.

public boolean isPail (ListNode head) {
        // write code here
        //先用双指针的形式找到中间节点,如果是偶数个节点,就找中间靠右的节点
        if(head == null || head.next == null){
            return true;//如果只有一个节点或者为空则
        }
        ListNode slow = head;
        ListNode fast = head;
        boolean count = true;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        //此时slow为中间节点
        
        //翻转后半段
        slow = reverse(slow);//得到的slow是翻转后的了,相当于链表的末尾
        
        while(head != slow && head.next != slow){//中间放个&&是同时照顾到链表节点的个数为奇偶情况
            if(head.val != slow.val){
                count = false;
                break;
            }
            head = head.next;
            slow = slow.next;
        }
        return count;
    }

    public ListNode reverse(ListNode head){
        ListNode cur = head.next;
        ListNode pre = head;
        while(cur != null){
            ListNode curNext = cur.next;
            cur.next = pre;
            pre = cur;
            cur = curNext;
        }
        return pre;
    }

10.链表的奇偶(节点)重排23.9.8

链表的奇偶重排_牛客题霸_牛客网 (nowcoder.com)

注意是链表的节点噢

思路(错误):

用双指针的形式,p1指针从head开始,p2从head.next开始.

都为一次走两步,p1遍历奇数节点,p2遍历偶数节点.

感觉能写出来,开心呢!

错解:

好像死循环了...但是我看不出来

debug后发现,主要是分开遍历分别把他们添加到新链表中会改变他们的next,导致在链表中形成环.

public ListNode oddEvenList (ListNode head) {
        // write code here
        if(head == null || head.next == null){//只有一个节点的,直接返回
            return head;
        }
        ListNode p1 = head;
        ListNode p2 = head.next;
        ListNode fakeHead = new ListNode(-1);
        ListNode cur = fakeHead;
        //先遍历奇数节点
        while(p1 != null && p1.next != null){//debug后发现,是这里条件的问题
                                            //链表个数为奇数个时p1会在最后一个节点停下来
                                            //但是没有进行下面的操作,再走到下面的循环就会死循环
                                             //但不是主要的问题...
            cur.next = p1;
            p1 = p1.next.next;
            cur = cur.next;
        }
        while(p2 != null && p2.next != null){
            cur.next = p2;
            p2 = p2.next.next;
            cur = cur.next;
        }
        cur.next = null;
        return fakeHead.next;
    }

正解:

可以一起遍历,两个指针作为相互的下一步的接口.

p1.next = p2.next;奇数的next变成偶数的next,偶数的next是下一个奇数位

然后p1 = p2.next来到刚刚存放的奇数位.

p2.next = p1.next.偶数的next变成奇数的next,奇数的next是下一个偶数位.

就这样把奇数和偶数分开遍历了一遍.并把他们都连接起来了.

最后只要把连接好的偶数位放到奇数位的后边

public ListNode oddEvenList (ListNode head) {
        // write code here
        if(head == null || head.next == null){//只有一个节点的,直接返回
            return head;
        }
        ListNode p1 = head;
        ListNode p2 = head.next;
        //分别把奇数位与偶数位交叉串起来
        ListNode p2Head = p2;
        while(p2 != null && p2.next != null){//因为p2是先遍历的节点,所以以其为结束条件
            p1.next = p2.next;
            p1 = p2.next;
            p2.next = p1.next;
            p2 = p1.next;
        }
        //再把偶数放到奇数后面
        p1.next = p2Head;
        return head;
    }

11.删除有序链表中的重复元素

删除有序链表中重复的元素-I_牛客题霸_牛客网 (nowcoder.com)

思路:

如果下一个节点的val是相同的就while一路next直到下一个不同样,再把他连接上.

用双指针的方式?

public ListNode deleteDuplicates (ListNode head) {
        // write code here
        if(head == null){
            return head;
        }
        ListNode p1 = head;
        ListNode p2 = head.next;
        while(p2 != null){
            if(p1.val == p2.val){
                p2 = p2.next;
            }else{
                p1.next = p2;
                p1 = p1.next;
                p2 = p1.next;
            }
        }
        return head;
    }

 

正解:

思考了一下后,发现.

永远是以p2为结束条件,所以跳出循环的时候p2一定为null.

那最后不管p1的next是怎么样的,都把他给上p2即null就不会出错啦.

public ListNode deleteDuplicates (ListNode head) {
        // write code here
        if(head == null){
            return head;
        }
        ListNode p1 = head;
        ListNode p2 = head.next;
        while(p2 != null){
            if(p1.val == p2.val){
                p2 = p2.next;
            }else{
                p1.next = p2;
                p1 = p1.next;
                p2 = p1.next;
            }
        }
        p1.next = p2;
        return head;
    }

12.删除有序链表中的重复元素||23.9.9

删除有序链表中重复的元素-II_牛客题霸_牛客网 (nowcoder.com)

思路:

只能想到用map的方法来写,计算出现的频率留下只出现一次的节点.

双指针的方式尝试了,但是写不出来..

正解:

双指针的主要思路是,先看后动.

用一个指针p2遍历链表,查看其当前val是否与下一个节点的val相同.

如果相同就next,直到遇到下一个与其val不同的节点.

这时,p2就会在相同节点的最后一个停下来.

然后观察负责连接的指针p1与p2的关系.

如果p1的下一个就是p2,那么证明两个节点为相邻的节点,p1就可以移动一步

如果p1的下一个不是p2,那么证明刚刚p2遇到了重复的元素并进行了跳过.就需要连接p2的next节点(p2会在相同val节点的末端停下).

最后p2继续遍历链表

public ListNode deleteDuplicates (ListNode head) {
        // write code here
        if(head == null || head.next == null){
            return head;
        }
        
        //因为头节点可能会被删掉,所以加一个假的头节点
        ListNode fakeHead = new ListNode(-1);
        fakeHead.next = head;
        ListNode p1 = fakeHead;
        ListNode p2 = head;
        while(p2 != null){
            //p2遍历链表,p1用来连接
            while(p2.next != null && p2.val == p2.next.val){//会在相同点的最后一个停下来
                p2 = p2.next;
            }
            if(p1.next == p2){
                p1 = p1.next;
            }else{
                p1.next = p2.next;
            }
            p2 = p2.next;
        }
        return fakeHead.next;
    }

13.二分查找-| 23.9.10

二分查找-I_牛客题霸_牛客网 (nowcoder.com)

思路:

让我回忆一下二分查找是什么...

在升序数组中,要找到一个target.

三个指针,left,mid,right.

先查看mid所在的数值为多少,如果mid比target小,则证明target在右半边.left = mid + 1,mid = (left+right)/2.

如果mid比target大,则证明target在左半边,right = mid - 1,mid = (left+right)/2.

如果mid == target则返回mid.

public int search (int[] nums, int target) {
        if(nums.length == 0){
            return -1;
        }
        int left = 0;
        int right = nums.length;
        int count = -1;
        while(left <= right){//这里记得要给等号噢,只剩两位数的时候可能会遍历不到
            int mid = (left + right) / 2;
            if(nums[mid] == target){
                count = mid;
                break;
            }else if(nums[mid] > target){
                right = mid - 1;
            }else if(nums[mid] < target){
                left = mid + 1;
            }
        }
        return count;
    }

14.二维数组中的查找

二维数组中的查找_牛客题霸_牛客网 (nowcoder.com)

思路:

升序的二维数组...

先从第一个数组末尾开始向左遍历

遇到比target大的,就向左移动一位.

遇到比target小的,就向下移动一位.

public boolean Find (int target, int[][] array) {
        // write code here
        int y = array[0].length - 1;//得到一行的长度
        int x = 0;//得到一共有多少行
        int n = array.length - 1;
        while(x <= n && y >= 0){
            if(array[x][y] == target){
                return true;
            }else if(array[x][y] > target){
                y--;
            }else if(array[x][y] < target){
                x++;
            }
        }
        return false;
    }

15寻找峰值

寻找峰值_牛客题霸_牛客网 (nowcoder.com)

思路:

想直接暴力模拟,但是会越界.

正解:

采用二分查找的方法,其实我一开始也想到了用二分查找.但判断的是两个边界与mid的关系,而不是mid与周围元素的关系.

这个向左收缩,right = mid 而不是mid-1

public int findPeakElement (int[] nums) {
        // write code here
        if(nums.length == 1){
            return 0;
        }
        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        while(left < right){
            mid = (left + right) / 2;
            if(nums[mid] < nums[mid+1]){
                left = mid + 1;//如果mid的右边元素比mid大证明mid不可能是波峰
                                //区间往右边收缩
            }else{
                //如果mid的左边元素比mid小,mid可能是波峰
                //所以不能-1跳过mid缩小区间了
                right = mid;
            }
        }
        return left;//不断缩小区间,直到两边界相等时,得到的为波峰
    }

16.比较版本号23.9.11

比较版本号_牛客题霸_牛客网 (nowcoder.com)

思路:

以'.'为分隔,获取数字.

每一个"."之间的数值进行比较.

注意的是,要比较一整个数.

例入:0.226与0.38.因为226大于38所以前面的版本号是久一点的.

public int compare (String version1, String version2) {
        // write code here
        int len1 = version1.length();
        int len2 = version2.length();
        int i = 0;
        int j = 0;
        while(i < len1 || j < len2){//可以不关注之间的长度差,注意不越界就行
            int count1 = 0;
            while(i < len1){
                char ch = version1.charAt(i);//获取下标的字符
                if(ch == '.'){//如果是"."就跳过
                    i++;
                    break;
                }else{//如果不是就加起来,记得*10是为了进位
                    count1 = (count1 * 10) +  ch - '0'; 
                    i++;
                }
            }
            int count2 = 0;
            while(j < len2){
                char ch = version2.charAt(j);
                if(ch == '.'){
                    j++;
                    break;
                }else{
                    count2 = (count2 * 10) + ch - '0';
                    j++;
                }
            }
            //此处的比较是按照"."来分割的,比较两个字符串同一位置整个修订号的大小
            if(count1 > count2){
                return 1;
            }else if(count1 < count2){
                return -1;
            }
        }
        return 0;
    }

17.二叉树的前序遍历

二叉树的前序遍历_牛客题霸_牛客网 (nowcoder.com)

思路:

递归的思路,前序是:中序,左节点,右节点

中序就是中间节点在前面,每次往下走一步就把当前root的val加入,然后才去遍历左再遍历右

public int[] preorderTraversal (TreeNode root) {
        // write code here
        List<Integer> list = new ArrayList<>();
        preoder(list,root);
        int[] ret = new int[list.size()];
        for(int i = 0; i < list.size(); i++){
            ret[i] = list.get(i);
        }
        return ret;
    }

    public void preoder(List<Integer> list,TreeNode root){
        if(root == null){
            return;
        }
        list.add(root.val);
        preoder(list,root.left);
        preoder(list,root.right);
    }

18.二叉树的中序遍历

二叉树的中序遍历_牛客题霸_牛客网 (nowcoder.com)

思路:

跟前序遍历的思想差不多,中序嘛就是左节点在前面.

就要找左节点,走走走,遍历到最左下角的左节点.便把其加入,再一步一步回头.

public int[] inorderTraversal (TreeNode root) {
        // write code here
        List<Integer> list = new ArrayList<>();
        inorder(list,root);
        int[] ret = new int[list.size()];
        for(int i = 0; i < list.size(); i++){
            ret[i] = list.get(i);
        }
        return ret;
    }
    public void inorder(List<Integer> list,TreeNode root){
        if(root == null){
            return;
        }
        inorder(list,root.left);
        //先遍历完左节点,再加入
        list.add(root.val);
        inorder(list,root.right);
    }

19二叉树的后序遍历

二叉树的后序遍历_牛客题霸_牛客网 (nowcoder.com)

思路:

都差不多嘛,后序就左 右 中.

先遍历左节点,再遍历右边的节点,最后加入

public int[] postorderTraversal (TreeNode root) {
        // write code here
        List<Integer> list = new ArrayList<>();
        postorder(list,root);
        int[] ret = new int[list.size()];
        for(int i = 0; i < list.size(); i++){
            ret[i] = list.get(i);
        }
        return ret;
    }
    public void postorder(List<Integer> list, TreeNode root){
        if(root == null){
            return;
        }
        postorder(list,root.left);
        postorder(list,root.right);
        list.add(root.val);
    }

20.二叉树的层序遍历23.9.12

求二叉树的层序遍历_牛客题霸_牛客网 (nowcoder.com)

思路:

之前学的数据结构已经忘完了..emm

重新看了一下该怎么层序遍历.

要额外使用一个队列来存放每一层的节点.

public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
        // write code here
        ArrayList<ArrayList<Integer>> ret = new ArrayList<>();//存放结果返回
        if (root == null) {
            return ret;
        }
        Queue<TreeNode> queue = new
        LinkedList<>();//创建一个队列存放每一层的节点
        //先手动放入头节点,作为第一层的元素
        queue.offer(root);
        
        while (!queue.isEmpty()) { //当存放每一层元素的队列不为空时
            int size = queue.size();//记录当前层元素的个数
            //把队列中的一个元素拿出来
            //最左节点的元素是最先放进去的,根据队列也会最先拿出来
            //并放到列表中
            ArrayList<Integer> list = new ArrayList<>();
            while (size != 0) {
                TreeNode tmp = queue.poll();
                list.add(tmp.val);
                //查看其有没有左右节点
                if (tmp.left != null) {
                    queue.offer(tmp.left);
                }
                if (tmp.right != null) {
                    queue.offer(tmp.right);
                }
                //每取出一个元素,size记得--
                size--;        
            }
            ret.add(list);
        }
        return ret;
    }

21.按之字形顺序打印二叉树

按之字形顺序打印二叉树_牛客题霸_牛客网 (nowcoder.com)

思路:

感觉跟上面的层序遍历差不多,设置一个变量,每遍历完一层变换一下,按照此变量去考虑是否要反转list.

public ArrayList<ArrayList<Integer>> Print (TreeNode pRoot) {
        // write code here
        ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
        if(pRoot == null){
            return ret;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(pRoot);
        boolean count = true;
        while(!queue.isEmpty()){
            ArrayList<Integer> list = new ArrayList<>();
            int size = queue.size();
            while(size != 0){
                TreeNode tmp = queue.poll();
                list.add(tmp.val);
                if(tmp.left != null){
                    queue.offer(tmp.left);
                }
                if(tmp.right != null){
                    queue.offer(tmp.right);
                }
                size--;
            }
            if(!count){
                Collections.reverse(list);//每隔一行就翻转一下
            }
            count = !count;
            ret.add(list);
        } 
        return ret;
    }

22.二叉树的最大深度23.9.13

二叉树的最大深度_牛客题霸_牛客网 (nowcoder.com)

思路:

层序遍历?每遍历一层就计数+1

直接写出来啦!( •̀ ω •́ )y

public int maxDepth (TreeNode root) {
        // write code here
        if(root == null){
            return 0;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        int count = 0;
        queue.offer(root);
        while(!queue.isEmpty()){
            int size = queue.size();
            while(size != 0){
                TreeNode tmp = queue.poll();
                if(tmp.left != null){
                    queue.offer(tmp.left);
                }
                if(tmp.right != null){
                    queue.offer(tmp.right);
                }
                size--;
            }
            count++;
        }
        return count;
    }

23.二叉树中和为某一值的路径(一)

二叉树中和为某一值的路径(一)_牛客题霸_牛客网 (nowcoder.com)

思路:

题目要求的是,从头节点到叶子节点一路下来连续的val和为sum.

可以使用递归的方式,先递归左节点的.每走到一个节点就把当前root的val从sum中减去.直到sum为0,且刚好为叶子节点的情况下则返回true;

递归遍历完左节点再去遍历右节点的. 

public boolean hasPathSum (TreeNode root, int sum) {
        // write code here
        if(root == null){
            return false;
        }
        if(root.left == null && root.right == null && sum - root.val == 0){
            return true;
        }
        boolean countLeft = hasPathSum(root.left,sum - root.val);
        boolean countRight = hasPathSum(root.right,sum - root.val);
        return countLeft || countRight;
    }

24.二叉搜索树与双向链表

二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)

思路:

二叉搜索树:每一个节点的值比其左节点大,但比其右节点小.

可以观察,二叉搜索树变成双向链表后,链表的数值是递增的.

而二叉搜索树中,数值大小为:左节点<根节点<右节点.

是不是就很像中序遍历呢,

所以就可以按照中序遍历的思路来完成.

要将节点连接起来,肯定是要使用双指针.

关注链表的头节点,为二叉搜索树的最左节点也是数值最小的节点.

 遍历左节点,

来到中间连接左边.

遍历右节点

public class Solution {
    TreeNode pre = null;
    TreeNode head = null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        //递归
        if(pRootOfTree == null){
            return null;
        }
        Convert(pRootOfTree.left);//遍历左节点
        if(pre == null){//如果是最左节点,则记录一下作为头节点与前驱节点并跳过
            pre = pRootOfTree;
            head = pRootOfTree;
        }else{
            pRootOfTree.left = pre;//此时pRootOfTree为一棵树的根节点,连接左
            pre.right = pRootOfTree;//前驱的右连接根节点
            pre = pRootOfTree;//前驱的指针来到当前根节点,作为下一个根节点的前驱
        }
        Convert(pRootOfTree.right);//遍历右节点
        return head;
    }
}

25.对称的二叉树23.9.14

对称的二叉树_牛客题霸_牛客网 (nowcoder.com)

思路:
对称,左子树的右节点与右子树 的左节点对称.

从根节点开始,使用两个指针分别遍历左子树与右子树.

检查左子树的左节点的值与右子树的右节点是否相同.

左子树的右节点的值与右子树的左节点是否相同

public boolean isSymmetrical (TreeNode pRoot) {
        // write code here
        return tree(pRoot,pRoot);//从头节点,分别用两个指针往左和右遍历
    }

    public boolean tree(TreeNode root1,TreeNode root2){
       if(root1 == null && root2 == null){//当两个指针同时为空结束递归
            return true;
       }
       //当任意一指针为空,或两边数值不相等时返回false
       if(root1 == null || root2 == null || root1.val != root2.val){
            return false;
       }
        //继续遍历相对称的节点
       return tree(root1.left,root2.right) && tree(root1.right,root2.left);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1011597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学Python的漫画漫步进阶 -- 第三步

学Python的漫画漫步进阶 -- 第三步 三、数字类型的数据3.1 Python中的数据类型3.2 整数类型3.3 浮点类型3.4 复数类型3.5 布尔类型3.6 数字类型的相互转换3.6.1 隐式类型的转换3.6.2 显式类型的转换 3.7 练一练3.8 数字类型的总结全部16步完成后 &#xff0c;后续就是介绍项目实…

走进甄云,探寻SRM独角兽成功背后的故事

随着科技的快速发展和全球商业环境的不断变化&#xff0c;中国企业对灵活性、创新性、全球化和效率的需求是迫切的&#xff0c;数字化转型已经成为企业生存和发展的关键因素&#xff0c;对企业具有重要意义&#xff0c;是组织生存和发展的必然趋势。数字化转型涉及整个组织、多…

PMP-项目规划过程组的重要性

一、什么是项目规划过程组 规划过程组包括明确项目全部范围、定义和优化目标&#xff0c;并为实现目标制定行动方案的一组过程。规划过程组中的过程制定项目管理计划的组成部分&#xff0c;以及用于执行项目的项目文件。取决于项目本身的性质&#xff0c;可能需要通过多轮反馈来…

片上网络(1)概述

前言 NoC&#xff1a;On-Chip Networks&#xff0c;片上网络。 由于多核乃至众核时代的到来&#xff0c;用于连接它们的可扩展、低延迟、大带宽的通信结构变得至关重要。 在核心较少时&#xff0c;总线Bus和矩阵/交叉开关Crossbar是主要的互联结构。总线可以提供较低的传输延迟…

云原生Kubernetes:pod基础与配置

目录 一、理论 1.pod 2.pod容器分类 3.镜像拉取策略 4.pod 的重启策略 二、实验 1.Pod容器的分类 2.镜像拉取策略 三、问题 1.apiVersion 报错 2.pod v1版本资源未注册 3.格式错误 4.取行显示指定pod信息 四、总结 一、理论 1.pod (1) 概念 Pod是kubernetes中…

pgzrun 拼图游戏制作过程详解(4,5)

4. 将小拼图位置随机打乱 建立swap_Square(i&#xff0c;j)坐标互换函数 将Gird[i]和Gird[j] 中的小拼图信息进行互换 def swap_Square(i,j): # 两个拼图的位置互换temp_posGird[i].posGird[i].posGird[j].posGird[j].postemp_pos 导入随机数模块 import random 随机抽取…

ruoyi-nbcio移植过程中的一些问题记录

1、打包去掉测试出现 Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.22.2:test 错误 在pom.xml里增加下面 去掉测试 <!--添加配置跳过测试--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId…

2023.05.27系统分析师考试案例分析及解析

案例分析真题1 阅读以下关于软件系统分析与建模的叙述&#xff0c;在纸上回答问题1至3. 说明: 某软件公司拟开发一套汽车租赁系统&#xff0c;科学安全和方便的管理租赁公司的各项业务&#xff0c;提高公司效率&#xff0c;提升利率。注册用户在使用系统镜像车辆预约时需执行…

利用Windows搭建Emby媒体库服务器,轻松实现无公网IP的远程访问

文章目录 1.前言2. Emby网站搭建2.1. Emby下载和安装2.2 Emby网页测试 3. 本地网页发布3.1 注册并安装cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar内网穿透本地设置 4.公网访问测试5.结语 1.前言 在现代五花八门的网络应用场景中&#xff0c;观看视频绝对是主力应用场景之一&…

css+js:实现tab切换线条跟随效果

目录 实现效果原理解析代码实现 实现效果 使用css和js实现一个tab切换的效果 原理解析 如上图 红色框框代表盒子,总宽度记作 totalWidth绿色框框代表每一项,宽度记作itemWidth深蓝色框框代表的是下划线,宽度记作activeWitdh 那么我们可以得到线的左边距是每一项的左边距加上…

通讯网关软件002——利用CommGate X2HTTP-U实现HTTP访问OPC UA Server

本文介绍利用CommGate X2HTTP-U实现HTTP访问OPC UA Server。CommGate X2HTTP是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现上位机通过HTTP来获取OPC UA Server的数据。 【解决方案】设置网关机…

c++ 中的函数指针

以下图片演示了c中函数指针的用法。如下图可见&#xff0c;把函数地址赋值给函数指针&#xff0c;用函数名或者函数名的地址&#xff0c;都可以&#xff0c;c编译器不报错。即 ptr f 和 ptr &f 都对。但准确的话&#xff0c;函数名就是地址&#xff0c;在编译时候&#x…

19 视图定义 union 是根据第一个 select 字段列表顺序,来进行 merge 的

前言 这个问题主要是 在之前存在这样的一个问题, 在生产环境上面 按照 我的直观理解, mysql 应该是根据 key 进行 merge, 所以 select 的顺序应该是 “不重要”??, 但是 结果我理解错了 然后 线上的查询也出现了问题, 发现很奇怪的问题, 明明 key01 列 是 id, 但是有一部…

【Linux问题】This account is currently not available.

在切换mysql用户时候出现下面问题 Last login: Fri Sep 15 09:32:46 CST 2023 on pts/0 This account is currently not available.查看 mysql用户信息 cat /etc/passwd | grep mysql他是 mysql:x:27:27:MySQL Server:/var/lib/mysql:/sbin/nologin把 /sbin /nologin 改成 /b…

使用阿里PAI DSW部署Stable Diffusion WebUI

进入到网址https://pai.console.aliyun.com/里边。 点击创建实例。 把实例名称填写好&#xff0c;选择GPU规格&#xff0c;然后选择实例名称是ecs.gn6v-c8g1.2xlarge。 选择stable-diffusion-webui-env:pytorch1.13-gpu-py310-cu117-ubuntu22.04&#xff0c;然后点击下一步。…

云原生Kubernetes:K8S集群使用带凭证的harbor仓库

目录 一、理论 1.部署harbor 创建私有项目&#xff0c;使用凭证登录 二、实验 1.部署harbor 创建私有项目&#xff0c;使用凭证登录 三、问题 1.harbor页面无法打开 2.生成harbor 登录凭据资源清单报错 3.tomcat-deployment 资源生成报错 一、理论 1.部署harbor 创建私…

CSV文件打开乱码解决方案

思路 借助第三方软件转码即可。 案例 打开CSV文件显示如下&#xff1a; 导入到python中&#xff0c;借助pandas文件读取 另存为csv文件格式即可 代码如下&#xff1a; import pandas as pd df pd.read_csv(rD:\BaiduNetdiskDownload\csv&xlsx版数据\城市产业创新指数…

人工智能现在可以从文本中生成具有CD音质的音乐,而且只会越来越好

想象一下&#xff0c;键入“戏剧性的介绍音乐”并听到一首飙升的交响乐&#xff0c;或者编写“令人毛骨悚然的脚步声”并获得高质量的音效。这是稳定音频的承诺&#xff0c;一个文本到音频的人工智能模型周三宣布由能合成立体声的稳定人工智能44.1千赫来自文字描述的音乐或声音…

antv-G6知识图谱安装--使用(实例)--连接线修改成动态,并添加跟随线移动的光圈,设置分支跟踪定位功能

这系列文章主要是完成一个图谱的自定义修改&#xff08;最近太忙了长篇分段更新自己使用流程&#xff09; 1. 连接线修改成动态&#xff0c;并添加跟随线移动的光圈 2. 自定义卡片样式和文字内容 3. 自定义伸缩节点的样式&#xff0c;并添加动画样式 3. 自定义弹窗样式 4. 自定…

Redis缓存更新策略、详解并发条件下数据库与缓存的一致性问题以及消息队列解决方案

0、前言 我们知道&#xff0c;缓存由于在内存中&#xff0c;数据处理速度比直接操作数据库要快很多&#xff0c;因此常常将数据先读到缓存中&#xff0c;再进行查询、更新等操作。 但与之而来的问题就是&#xff0c;内存中的数据不仅没有持久化&#xff0c;而且需要保证…