0
点赞
收藏
分享

微信扫一扫

字节校园精选 66 道高频经典笔面试题(含多种思路)(上)

一世独秀 2022-04-01 阅读 74

目录

前言

该算法链接来源于
冲刺春招-精选笔面试 66 题大通关

在这里插入图片描述

以下为我的学习笔记以及汇总,也为了方便其他人更加快速的浏览

第一天

21. 合并两个有序链表(简单)

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:
在这里插入图片描述

示例 2:

示例 3:

提示:

思路
通过递归遍历合并两个有序链表

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        } else if (l2 == null) {
            return l1;
        } else if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

或者通过创建头节点进行遍历
遍历的同时,头节点 和l1 l2 的下一个位置移动
以及最后谁先结束 prev.next = l1 == null ? l2 : l1;
最后返回的值是头结点的下一个next

通过创建一个头节点,以及头指针遍历

关于怎么创建头节点的知识点可看我之前的文章
java中new ListNode(0)常见用法详细区别(全)

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode prehead = new ListNode(-1);

        ListNode prev = prehead;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }

        // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        prev.next = l1 == null ? l2 : l1;

        return prehead.next;
    }
}

146. LRU 缓存(中等)

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

示例:

输入

提示:

1 <= capacity <= 3000
0 <= key <= 10000
0 <= value <= 105
最多调用 2 * 105 次 get 和 put

思路

主要的思路是 通过哈希表加上双向链表
在java中有一个类也是这样,可以通过继承实现它来写
java之LinkedHashMap源码详细解析

public class LRUCache {
    class DLinkedNode {
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;
        public DLinkedNode() {}
        public DLinkedNode(int key, int value) {this.key = key; this.value = value;}
    }

    private Map<Integer, DLinkedNode> map = 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= map.get(key);
        if(node==null) return -1 ;
         // 如果 key 存在,先通过哈希表定位,再移到头部
        removeNode(node);
        addToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node=map.get(key);
        if(node == null){
         // 如果 key 不存在,创建一个新的节点
            DLinkedNode newnode=new DLinkedNode(key,value); 
             // 添加进哈希表
            map.put(key,newnode);  
            //添加头部 并且size加1
            addToHead(newnode);
            size++;
            if(size>capacity){
            //移除尾部,map删除
                DLinkedNode tail=removeTail();
                map.remove(tail.key);
                size--;
            }
        }
        else{
        // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            node.value = value;//不可省略
            removeNode(node);
            addToHead(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 DLinkedNode removeTail() {
        DLinkedNode res = tail.prev;
        removeNode(res);
        return res;
    }
}

25. K 个一组翻转链表(困难)

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

进阶:

你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

在这里插入图片描述

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
示例 2:

在这里插入图片描述

示例 3:

示例 4:

提示:

列表中节点的数量在范围 sz 内
1 <= sz <= 5000
0 <= Node.val <= 1000
1 <= k <= sz


思路:

关于这篇文章的思路,主要结合反转链表的一部分
具体在于中间的衔接点怎么使用,以及k个,前后顺序的状态

/**
 * 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 reverseKGroup(ListNode head, int k) {

        ListNode dummy=new ListNode(0);
        dummy.next=head;

        ListNode pre=dummy;
        ListNode end=dummy;
        

        while(end.next!=null){
            for(int i=0;i<k&&end!=null;i++){
                end=end.next;
            }
            if(end==null)break;
            ListNode next=end.next;
            end.next=null;
            ListNode start= pre.next;
            pre.next=reverse(start);
            start.next=next;
            pre=start;
            end=start;

        }
        return dummy.next; 

    }


    public ListNode reverse(ListNode rear){
        ListNode prev=null;
        ListNode rev=rear;
        while(rev!=null){
            ListNode next=rev.next;
            rev.next=prev;
            
            prev=rev;
            rev=next;
        }
        return prev;
    }

}

关于以上代码可看如下类似的注解解释:
或者看官方的解释,比较易懂
图解k个一组翻转链表的解释

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || head.next == null){
            return head;
        }
        //定义一个假的节点。
        ListNode dummy=new ListNode(0);
        //假节点的next指向head。
        // dummy->1->2->3->4->5
        dummy.next=head;
        //初始化pre和end都指向dummy。pre指每次要翻转的链表的头结点的上一个节点。end指每次要翻转的链表的尾节点
        ListNode pre=dummy;
        ListNode end=dummy;

        while(end.next!=null){
            //循环k次,找到需要翻转的链表的结尾,这里每次循环要判断end是否等于空,因为如果为空,end.next会报空指针异常。
            //dummy->1->2->3->4->5 若k为2,循环2次,end指向2
            for(int i=0;i<k&&end != null;i++){
                end=end.next;
            }
            //如果end==null,即需要翻转的链表的节点数小于k,不执行翻转。
            if(end==null){
                break;
            }
            //先记录下end.next,方便后面链接链表
            ListNode next=end.next;
            //然后断开链表
            end.next=null;
            //记录下要翻转链表的头节点
            ListNode start=pre.next;
            //翻转链表,pre.next指向翻转后的链表。1->2 变成2->1。 dummy->2->1
            pre.next=reverse(start);
            //翻转后头节点变到最后。通过.next把断开的链表重新链接。
            start.next=next;
            //将pre换成下次要翻转的链表的头结点的上一个节点。即start
            pre=start;
            //翻转结束,将end置为下次要翻转的链表的头结点的上一个节点。即start
            end=start;
        }
        return dummy.next;


    }
    //链表翻转
    // 例子:   head: 1->2->3->4
    public ListNode reverse(ListNode head) {
         //单链表为空或只有一个节点,直接返回原单链表
        if (head == null || head.next == null){
            return head;
        }
        //前一个节点指针
        ListNode preNode = null;
        //当前节点指针
        ListNode curNode = head;
        //下一个节点指针
        ListNode nextNode = null;
        while (curNode != null){
            nextNode = curNode.next;//nextNode 指向下一个节点,保存当前节点后面的链表。
            curNode.next=preNode;//将当前节点next域指向前一个节点   null<-1<-2<-3<-4
            preNode = curNode;//preNode 指针向后移动。preNode指向当前节点。
            curNode = nextNode;//curNode指针向后移动。下一个节点变成当前节点
        }
        return preNode;

    }


}

第二天

14. 最长公共前缀(简单)

题目:

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

示例 1:

示例 2:

提示:

1 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i] 仅由小写英文字母组成


思路:

通过对比所有的列,一列一列比较
终止条件有两个(通过两个for进行遍历,而且实时刻刻都是只有一个第一行的每一列进行比较strs[0].charAt(j

  • 这一列的长度等于其遍历的当前列的长度strs[i].length()==j
  • 不同列的相同位置strs[i].charAt(j)!=strs[0].charAt(j)

如果执行完都不返回的话,最后就是本身,输出strs【0】即可

字符串数组,有两个区别,取全部的字符串长度,则为strs.length;,取单个数组内部的字符串则为strs[0].length()(容易弄混)

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) {
            return "";
        }

        int m=strs.length;
        int n=strs[0].length();

        for(int j=0;j<n;j++){//列
            for(int i=1;i<m;i++){//行
                if(strs[i].length()==j||strs[i].charAt(j)!=strs[0].charAt(j)){
                    return strs[0].substring(0,j);
                }
            }
        }
        return strs[0] ;
    }
}

另外一种思路是,对比每一行的,一行一行的比较。最后输出其结果即可

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) {
            return "";
        }

        int m=strs.length;

        String prefix=strs[0];
        for(int i=1;i<m;i++){
            int length=Math.min(strs[i].length(),prefix.length());
            
            int j;
            for(j=0;j<length;j++){               
                if(strs[i].charAt(j)!=prefix.charAt(j) ){
                    
                    break;                  
                }
                
            }
            prefix=strs[i].substring(0,j);

            
            //int index=0;
            //while(index<length&&strs[i].charAt(index)==prefix.charAt(index))index++;
            //prefix=strs[i].substring(0,index);
            
        }
        return prefix;
        
    }
}

3. 无重复字符的最长子串(中等)

题目:

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

示例 2:

示例 3:

提示:

0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成


思路:

通过遍历数组,为了减少一次遍历,可以通过set集合进行遍历
如果出现重复,则删除第一个节点,继续开始从第二个节点开始遍历(因为从第一个节点到查重重复 是不能算入子串的)
集合已经添加进入了,第二个指针可以不用挪到最开始的地方。

class Solution {
    public int lengthOfLongestSubstring(String s) {
     Set<Character>set=new HashSet<>();
     int n=s.length();
     int ans=0,rk=0;

     for(int i=0;i<n;i++){
         if(i!=0) set.remove(s.charAt(i-1));
         while(rk<n&&!set.contains(s.charAt(rk))){
             set.add(s.charAt(rk));
             rk++;
         }
        ans=Math.max(ans,rk-i);
     }
     return ans;
    }
}

以上是因为rk的节点,每次都会往右在挪一个节点位置,所以不用减1
如果rk节点要想减1,可以通过下面的代码设置

class Solution {
    public int lengthOfLongestSubstring(String s) {
        // 哈希集合,记录每个字符是否出现过
        Set<Character> occ = new HashSet<Character>();
        int n = s.length();
        // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
        int rk = -1, ans = 0;
        for (int i = 0; i < n; ++i) {
            if (i != 0) {
                // 左指针向右移动一格,移除一个字符
                occ.remove(s.charAt(i - 1));
            }
            while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
                // 不断地移动右指针
                occ.add(s.charAt(rk + 1));
                ++rk;
            }
            // 第 i 到 rk 个字符是一个极长的无重复字符子串
            ans = Math.max(ans, rk - i + 1);
        }
        return ans;
    }
}

第三天

206. 反转链表(简单)

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:
在这里插入图片描述

示例 2:

在这里插入图片描述

示例 3:

提示:

链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000


1.思路一:
ListNode rear可以在任何时刻定义
也可直接使用ListNode rear=cur.next;
返回的是pre指针,而不是cur指针也不是head指针
具体的逻辑思路如下
在这里插入图片描述

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur=head,pre=null;
        ListNode rear;
        while(cur!=null){
            rear=cur.next;
            cur.next=pre;

            pre=cur;
            cur=rear;      
        }
        return pre;
    }
}

中途的错误做法:
只截取上面片段代码作为讲解:
将rear=cur.next;放在while里面最后定义,rear已经越界了

ListNode cur=head,pre=null;
ListNode rear;
 while(cur!=null){
            
            cur.next=pre;

            pre=cur;
            cur=rear;   
            rear=cur.next;   
        }
            

2.思路二:
使用递归的条件进行反转
递这个用法用在了层层递进
归这个用法用在了每一层的特殊情节,也就是两个链表地址空间的反转
在这里插入图片描述

class Solution {
   public ListNode reverseList(ListNode head) {
    // 1. 递归终止条件
    if (head == null || head.next == null) {
        return head;
    }
    ListNode newhead = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return newhead;

}

199. 二叉树的右视图(中等)

题目:

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例 1:

在这里插入图片描述

示例 2:

示例 3:

提示:

二叉树的节点个数的范围是 [0,100]
-100 <= Node.val <= 100


思路:

主要的思路是层次遍历的应用,在每一层的最后一个节点输出即可
对层次遍历熟悉就好操作

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer>list=new ArrayList<>();
        if(root==null) return list;

        Queue<TreeNode>que=new LinkedList<>();
        que.offer(root);
        while(!que.isEmpty()){          
            int n=que.size();
            for(int i=0;i<n;i++){
                TreeNode node=que.poll();
                if(i==n-1)list.add(node.val);
                if(node.left!=null)que.offer(node.left);
                if(node.right!=null)que.offer(node.right);
            }   
        }
        return list;

    }
}

第四天

1. 两数之和(简单)

题目:

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

示例 2:

提示:

2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?


思路:
常规的思路可以使用暴力遍历,但是复杂度比较高
可以使用哈希表的存储进行存取

通过target-nums【i】进行获取
而且map哈希中是containsKey,返回一个数组类型是new int[]{map.get(target-nums[i]),i};
如果为空值则为return new int[0];

class Solution {
    public int[] twoSum(int[] nums, int target) {

        Map<Integer,Integer>map=new HashMap<>();

        for(int i=0;i<nums.length;i++){

            if(map.containsKey(target-nums[i])){
                return new int[]{map.get(target-nums[i]),i};
            }
            
            map.put(nums[i],i);
            
        }
        return new int[0];
    }
}

15. 三数之和(中等)

题目:

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

示例 2:

示例 3:

提示:

0 <= nums.length <= 3000
-105 <= nums[i] <= 105


思路:

先排序,步步逼近双指针

最主要的条件是 去重而且一开始大于0都要去掉

通过判定总值 跟0的比较,移动指针的位置,使用while一直移动指针,而且最后还要前进一个,如果是L++ 最后要在进一个L。++L是最合适的。再者使用while的同时 一直不会跳出大循环,所以每个小循环都要L<R

列表中添加数组,主要通过这个函数进行转换:Arrays.asList() 详解
而且需要使用ArrayList而不是List这个函数

list.add(new ArrayList<Integer>(Arrays.asList(nums[i],nums[L],nums[R])));

关于这部分的解答 更详细的题解 可看如下链接:
三数之和(排序+双指针,易懂图解)
画解算法:15. 三数之和

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
         List<List<Integer>> list=new ArrayList<List<Integer>>();
         Arrays.sort(nums);
        
        //    < nums.length - 2是为了保证后面还能存在两个数字
         for(int i=0;i<nums.length-2;i++){
             if(nums[i]>0)break;//大于0,则后面的数字也是大于零(排序后是递增的)
             if(i>0&&nums[i]==nums[i-1])continue;//值重复了,去重

             int L=i+1;
             int R=nums.length-1;
             while(L<R){
                 int sum=nums[i]+nums[L]+nums[R];
                 if(sum==0){
                     list.add(new ArrayList<Integer>(Arrays.asList(nums[i],nums[L],nums[R])));
                     while(L<R&&nums[L]==nums[++L]);//左指针前进并去重
                     while(L<R&&nums[R]==nums[--R]);//右指针前进并去重
                 }else if(sum>0){
                     while(L<R&&nums[R]==nums[--R]);//右指针前进并去重
                 }else if(sum<0){
                     while(L<R&&nums[L]==nums[++L]);//左指针前进并去重
                 }
             }
            
         }

         return list;
       


    }
}

第五天

7. 整数反转(中等)

题目

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。

假设环境不允许存储 64 位整数(有符号或无符号)。

示例 1:

示例 2:

示例 3:

示例 4:

提示:


思路一:
这道题的思路很像之前那道算法

【leetcode】数学 - 回文数

但是有个前提条件,而且可以是负数
所以在要加以判断,否则会溢出

class Solution {
    public int reverse(int x) {

        int res=0;
        while(x!=0){
            if(res<Integer.MIN_VALUE/10||res>Integer.MAX_VALUE/10){
                return 0;
            }
            int rev=x%10;
            x=x/10;

            res=res*10+rev;
        }

        return res;

    }
}

215. 数组中的第K个最大元素(中等)

题目:

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

示例 2:

提示:

1 <= k <= nums.length <= 104
-104 <= nums[i] <= 104


思路:

使用快排的思路将其遍历出来
具体快排的思路可看我这篇文章
【数据结构】常见排序算法详细分析(内含java与c++代码)

其快排代码思路如下:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        quicksort(nums,0,nums.length-1);
        return nums[nums.length-k];

    }
    public void quicksort(int [] nums,int left,int right){
        if(left>right)return;//越界返回 
        
        int l=left;
        int r=right;

        int temp=nums[l];//基准的数字

        while(l<r){
            while(l<r&&nums[r]>=temp)r--;//先走右再走左
            while(l<r&&nums[l]<=temp)l++;
            if(l<r){
                int t=nums[l];
                nums[l]=nums[r];
                nums[r]=t;
            }
           
        }
        nums[left]=nums[l];//最后的基准 跟(i与j相等)i互换位置
        nums[l]=temp;

        quicksort(nums,left,l-1); //递归调用
        quicksort(nums,l+1,right);
    }
}

第六天

33. 搜索旋转排序数组(中等)

题目:

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

示例 3:

输入:nums = [1], target = 0
输出:-1

提示:

1 <= nums.length <= 5000
-104<= nums[i] <= 104
nums 中的每个值都 独一无二
题目数据保证 nums 在预先未知的某个下标上进行了旋转
-104 <= target <= 104

进阶:你可以设计一个时间复杂度为 O(log n) 的解决方案吗?


思路:

采用二分查找的方法

将数组一分为二,其中一定有一个是有序的,另一个可能是有序,也能是部分有序。
此时有序部分用二分法查找。无序部分再一分为二,其中一个一定有序,另一个可能有序,可能无序。就这样循环.
在这里插入图片描述
关于该题解看的是如下提示:
官方题解

class Solution {
    public int search(int[] nums, int target) {
        int n=nums.length;

        int l=0,r=n-1;       
        while(l<=r){
            int mid=(l+r)/2;
            if(nums[mid]==target)return mid;
            if(nums[0]<=nums[mid]){
                if(nums[0]<=target&&target<nums[mid]){
                    r=mid-1;
                }else{
                    l=mid+1;
                }
            }else{
                if(nums[mid]<target&&target<=nums[n-1]){
                    l=mid+1;
                }else{
                    r=mid-1;
                }
            }

        }
        return -1;


    }
}

54. 螺旋矩阵(中等)

题目:

给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

示例 1:

在这里插入图片描述

示例 2:

在这里插入图片描述

提示:

m == matrix.length

n == matrix[i].length
1 <= m, n <= 10
-100 <= matrix[i][j] <= 100


思路:

一个循环一个循环的输出,同时要在每个循环中加一个判断,随时计数器结束就停止循环,不然会出现下方某个错误

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        
        List<Integer> list=new ArrayList<>();

        int m=matrix.length;
        int n=matrix[0].length;
        
        int l=0,r=n-1,top=0,bottom=m-1;
        int num=1;
        int sum=m*n;
        while(num<=sum){
            for(int i=l;i<=r&&num<=sum;i++){
                list.add(matrix[top][i]);
                num++;
            }
            top++;//往下加1
            for(int i=top;i<=bottom&&num<=sum;i++){
                list.add(matrix[i][r]);
                num++;
            }
            r--;//往左减1
            for(int i=r;i>=l&&num<=sum;i--){
                list.add(matrix[bottom][i]);
                num++;
            }
            bottom--;//往上加1
            for(int i=bottom;i>=top&&num<=sum;i--){
                list.add(matrix[i][l]);
                num++;
            }
            l++;//往右加1
        }
        return list;

    }
}

每个条件中要加入&&num<=sum,否则一个while出不了循环,特别是长方形会有错误

在这里插入图片描述

示例拓展:
59. 螺旋矩阵 II
在这里插入图片描述

class Solution {
    public int[][] generateMatrix(int n) {

        int [][] ss=new int[n][n];
        
        int l=0,r=n-1,top=0,bottom=n-1;
        int num=1;
        int sum=n*n;

        while(num<=sum){
            for(int i=l;i<=r;i++){
                ss[top][i]=num++;
            }
            top++;//往下走
            for(int i=top;i<=bottom;i++){
                ss[i][r]=num++;
            }
            r--;//往左走
            for(int i=r;i>=l;i--){
                ss[bottom][i]=num++;
            }
            bottom--;//往上走
            for(int i=bottom;i>=top;i--){
                ss[i][l]=num++;
            }
            l++;//往右走
        }
        return ss;

    }
}

第七天

53. 最大子数组和(简单)

题目:
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

示例 2:

示例 3:

提示:

1 <= nums.length <= 105
-104 <= nums[i] <= 104

进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。


具体思路如下:
通过Math函数,将其一个个加上获取最大值
以及通过Math函数,存储各个区域中的最大子数组之和

通过动态规划的思路进行滚动数组

class Solution {
    public int maxSubArray(int[] nums) {
        int pre=0;
        int max=nums[0];
        for(int i=0;i<nums.length;i++){
            pre=Math.max(pre+nums[i],nums[i]);
            max=Math.max(pre,max);
        }
        return max;

    }
}

152. 乘积最大子数组(中等)

题目:

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数。

子数组 是数组的连续子序列。

示例 1:

示例 2:

提示:

1 <= nums.length <= 2 * 104
-10 <= nums[i] <= 10
nums 的任何前缀或后缀的乘积都 保证 是一个 32-位 整数


因为可能为负数,所以要保存最大值和最小值。再者,定义的总数一开始就不应该为0,而是Integer.MIN_VALUE
如果下一个数为负数,则最大值和最小值互换

具体代码如下:

class Solution {
    public int maxProduct(int[] nums) {

        int max=1,min=1;
        int sum=Integer.MIN_VALUE;
        for(int i=0;i<nums.length;i++){
            if(nums[i]<0){
                int temp=max;
                max=min;
                min=temp;
            }
            max=Math.max(nums[i]*max,nums[i]);
            min=Math.min(nums[i]*min,nums[i]);

            sum=Math.max(sum,max);
        }
        return sum;
    }   
}

第八天

20. 有效的括号(简单)

题目:

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

示例 1:

示例 2:

示例 3:

示例 4:

示例 5:

提示:

1 <= s.length <= 104
s 仅由括号 ‘()[]{}’ 组成


思路:

注意一个代码格式:

Map <Character,Character> map=new HashMap<>(){{
            put(')','('); 
            }} ;

通过哈希表存储其键值对
通过栈的形式防其值

  • 如果值没有包括进去,则进栈
  • 如果值包括了进去,则要出栈的话(需要判断其键值是否满足栈顶元素以及栈是否为空,因为可能为 ((),不想此处判断栈是否为空的话,可以在一开始就判断是否为偶数个数)
  • 单纯的进栈还不可以,因为要键值配对,必须把栈清空,所以最后的栈必须为空,示例如下
    在这里插入图片描述
    代码如下:
class Solution {
    public boolean isValid(String s) {
        if(s.length()%2==1)return false;
        Deque <Character>stack=new LinkedList<>();
        Map <Character,Character> map=new HashMap<>(){{
            put(')','(');
            put('}','{');
            put(']','[');
        }};

        for(char c:s.toCharArray()){
            if(map.containsKey(c)){
                if(map.get(c)!=stack.peek()){                   
                    return false;
                }
                stack.pop(); 
            }else {
                stack.push(c);
            }
            
        }
        return stack.isEmpty();

    }
}

200. 岛屿数量(中等)

题目:

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

示例 2:

提示:

m == grid.length

n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值为 ‘0’ 或 ‘1’


思路:

通过深度优先遍历,将其遍历过后的数字都变为一个0,也就是状态变量
如果一开始进入的为1,则sum+1
在遍历的时候终止条件,一般有临界值,以及状态值

class Solution {
    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }

        int m=grid.length;
        int n=grid[0].length;
        
        int sum=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]=='1'){
                    sum++;
                    dfs(grid,i,j);
                }
            }
        }
        return sum;

    }
    public void dfs(char [][] grid,int i,int j){
        int m=grid.length;
        int n=grid[0].length;
        if(i<0||i>=m||j<0||j>=n||grid[i][j]=='0')return ;
        grid[i][j]='0';

        dfs(grid,i-1,j);
        dfs(grid,i+1,j);
        dfs(grid,i,j-1);
        dfs(grid,i,j+1);
    }
}

上面的思路是修改了原数组,如果不想修改原数组,增加一个状态变量的数组,具体代码如下:(核心代码如下)

public int numIslands(char[][] grid) {
        int numIsland = 0;
        int[][] visited = new int[grid.length][grid[0].length];
        for(int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                if(grid[i][j]=='1' && visited[i][j]!=1){
                    search(grid, i, j, visited);
                    numIsland ++;
                }
            }
        }
        return numIsland;
    }

    public void search(char[][] grid, int i, int j, int[][] visited){
        if(i<0 || j<0 || i>grid.length-1 || j>grid[0].length-1 || grid[i][j]=='0'|| visited[i][j] == 1) return ;
        visited[i][j] = 1;
        search(grid, i - 1, j, visited);
        search(grid, i , j-1, visited);
        search(grid, i + 1, j, visited);
        search(grid, i, j + 1, visited);
    }

第九天

105. 从前序与中序遍历序列构造二叉树(中等)

题目:

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:

在这里插入图片描述
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

提示:

1 <= preorder.length <= 3000
inorder.length == preorder.length
-3000 <= preorder[i], inorder[i] <= 3000
preorder 和 inorder 均 无重复 元素
inorder 均出现在 preorder
preorder 保证 为二叉树的前序遍历序列
inorder 保证 为二叉树的中序遍历序列


思路一:

使用递归的思路,因为先序遍历的第一个头节点是关键,因为他是根节点,与中序遍历的根节点相同,中序遍历的左子树,以及右子树就可区分,以此类推,就可找到其构建的二叉树

  • 新建一个二叉树,要先创建一个根节点(找到中序遍历的根节点,然后一个个创建),通过TreeNode root=new TreeNode(preorder[preorder_left]);
  • 每次都要先序遍历的根节点,在遍历中序遍历找到其根节点,复杂度变高了,可以通过set集合存储key value值
  • 注意递归的终止条件是先序遍历,只要前面的节点大于后面的节点,输出为null
  • 另外一个注意事项是,先序遍历和后序遍历的长度大小是一样的
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    Map<Integer,Integer>map;
    public TreeNode buildTree(int[] preorder, int[] inorder) {

        map=new HashMap<>();
        int n=inorder.length;
        for(int i=0;i<n;i++){
            map.put(inorder[i],i);
        }
        return mybuildTree(preorder,inorder,0,n-1,0,n-1);
        

    }
    public TreeNode mybuildTree(int []preorder,int [] inorder,int preorder_left,int preorder_right,int inorder_left,int inorder_right ){
        if(preorder_left>preorder_right)return null;

        int inorder_root=map.get(preorder[preorder_left]);
        int sum=inorder_root-inorder_left;
        
        TreeNode root=new TreeNode(preorder[preorder_left]);
        root.left=mybuildTree(preorder,inorder,preorder_left+1,preorder_left+sum, inorder_left,inorder_root-1);
        root.right=mybuildTree(preorder,inorder,preorder_left+sum+1,preorder_right, inorder_root+1,inorder_right);
        return root;
    }
}

103. 二叉树的锯齿形层序遍历(中等)

题目:

给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

示例 1:
在这里插入图片描述

示例 2:

示例 3:

提示:

树中节点数目在范围 [0, 2000] 内
-100 <= Node.val <= 100


思路:

通过层次遍历,在内部的列表中使用的是双端队列,如果存储1 23 的时候,通过存放为1 32.因为是双端队列,32 存放的时候是通过offerFirst进行存储
因为是双端队列,所以在添加的时候要强转list.add(new LinkedList<Integer>(sonlist));

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> list=new ArrayList<List<Integer>>();
        if(root==null)return list;
        

        Queue<TreeNode> que =new LinkedList<>();
        que.offer(root);
        boolean direction = true;
        while(!que.isEmpty()){
            Deque<Integer> sonlist=new LinkedList<>();
            int n=que.size();
            
            for(int i=0;i<n;i++){
                TreeNode node=que.poll();
                if(direction){
                    sonlist.offerLast(node.val);
                }else{
                    sonlist.offerFirst(node.val);
                }
                if(node.left!=null)que.offer(node.left);
                if(node.right!=null)que.offer(node.right);
            }

            direction=!direction;
            list.add(new LinkedList<Integer>(sonlist));
            
        }

        return list;

    }
}

另一种思路是不使用双端队列
直接使用列表的形式进行添加
特殊行使用Collection进行反转

class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> list=new ArrayList<List<Integer>>();
        if(root==null)return list;
        

        Queue<TreeNode> que =new LinkedList<>();
        que.offer(root);
        boolean direction = true;
        while(!que.isEmpty()){
            List<Integer> sonlist=new ArrayList<>();
            int n=que.size();
            
            for(int i=0;i<n;i++){
                TreeNode node=que.poll();
                sonlist.add(node.val);
                
                if(node.left!=null)que.offer(node.left);
                if(node.right!=null)que.offer(node.right);
            }
            if(!direction){
                Collections.reverse(sonlist);
            }

            direction=!direction;
            list.add(sonlist);
            
        }

        return list;

    }
}

第十天

94. 二叉树的中序遍历(简单)

题目:

给定一个二叉树的根节点 root ,返回它的 中序 遍历。

示例 1:

在这里插入图片描述

示例 2:

示例 3:

示例 4:

在这里插入图片描述

示例 5:

在这里插入图片描述
提示:

树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100

进阶: 递归算法很简单,你可以通过迭代算法完成吗?


思路一:
通过递归 的方式
创建一个函数,将其res列表传入

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        inorder(root, res);
        return res;
    }

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

思路二:

通过迭代的方式进行
利用栈的思想

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        Stack<TreeNode> stk = new Stack<TreeNode>();

        while (root != null || !stk.isEmpty()) {
            while (root != null) {
               
                stk.push(root);
                 root = root.left;

            }

            root = stk.pop();
            res.add(root.val);
            root = root.right;
        }
        return res;
    }
}

102. 二叉树的层序遍历(中等)

题目:

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

示例 1:

在这里插入图片描述

示例 2:

示例 3:

提示:

树中节点数目在范围 [0, 2000] 内
-1000 <= Node.val <= 1000


思路:

通过列表以及队列的方式进行存储

具体大条件是判断其队列不为空
内部存储的是每一层的列表size
注意泛型的类型

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>>list=new ArrayList<>();

        if(root==null)return list;
        
        Queue<TreeNode> que=new LinkedList<>();

        que.offer(root);
        while(!que.isEmpty()){
            List<Integer> sonlist=new ArrayList<>();
            int n=que.size();
            for(int i=1;i<=n;i++){
                
            TreeNode node= que.poll();
            sonlist.add(node.val);
               
                if(node.left!=null){
                    que.offer(node.left);
                }
                if(node.right!=null){
                    que.offer(node.right);
                }
                
            }
            list.add(sonlist);
        }
        
        return list;


    }
}

394. 字符串解码(中等)

题目:

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例 1:

示例 2:

示例 3:

示例 4:

提示:

  • 1 <= s.length <= 30
  • s 由小写英文字母、数字和方括号 ‘[]’ 组成
  • s 保证是一个 有效 的输入。
  • s 中所有整数的取值范围为 [1, 300]

思路:

通过栈的存储,以及StringBulider的存储
遇到】的时候都进栈

错误代码展示

class Solution {
public String decodeString(String s) {
        
        Stack<Character>stack=new Stack<>();

        StringBuilder res=new StringBuilder();
        for(char c:s.toCharArray()){
            if(c!=']'){
                stack.push(c);
            }else{
                StringBuilder ans=new StringBuilder();
                while(!stack.isEmpty()&&Character.isLetter(stack.peek()))
                    ans.insert(0,stack.pop());
                String sub=ans.toString();

                stack.pop();

                ans = new StringBuilder();
                while(!stack.isEmpty()&&Character.isDigit(stack.peek()))
                    ans.insert(0,stack.pop());
                
                int count=Integer.valueOf(ans.toString());


                while(count>0){
                    for(char h:sub.toCharArray()){
                        res.append(h);                       
                    }
                    count--;
                }

            }

        }
        return res.toString();

    }
}

少考虑了这种情况:
在这里插入图片描述
对此在前面的时候不着急直接输出添加进去
而是继续放到栈中,等全部放到栈中之后在一一添加进去

正确代码如下:

  • 判断是否为字符Character.isLetter(stack.peek()
  • 或者是否为数字Character.isDigit(stack.peek()
  • 将其栈从后往前的输出,可以通过insert 无限插入到第0个位置ans.insert(0,stack.pop());
  • 字符串转换为数字Integer.valueOf();
  • SrtingBuilder 输出的结构都要通过toString();先转换为字符串
class Solution {
public String decodeString(String s) {
        
        Stack<Character>stack=new Stack<>();

        StringBuilder res=new StringBuilder();
        for(char c:s.toCharArray()){
            if(c!=']'){
                stack.push(c);
            }else{
                StringBuilder ans=new StringBuilder();
                while(!stack.isEmpty()&&Character.isLetter(stack.peek()))
                    ans.insert(0,stack.pop());
                String sub=ans.toString();

                stack.pop();

                ans = new StringBuilder();
                while(!stack.isEmpty()&&Character.isDigit(stack.peek()))
                    ans.insert(0,stack.pop());
                
                int count=Integer.valueOf(ans.toString());


                while(count>0){
                    for(char h:sub.toCharArray()){
                        stack.push(h);                       
                    }
                    count--;
                }

            }

        }
        while(!stack.isEmpty()){
            res.insert(0,stack.pop());
        }
        return res.toString();

    }
}

第十一天

415. 字符串相加(简单)

题目:

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。

你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

示例 1:

示例 2:

示例 3:

提示:

1 <= num1.length, num2.length <= 104
num1 和num2 都只包含数字 0-9
num1 和num2 都不包含任何前导零


思路:

通过模拟的思路,将其两个字符串从后往前加上,如果有进位就保留进位

通过StringBuilder添加进入,但是记得要reverse反转,之后还要将其输出为字符串toString();

class Solution {
    public String addStrings(String num1, String num2) {
        int i=num1.length()-1,j=num2.length()-1;
        int add=0;
        StringBuilder ans=new StringBuilder();
        while(i>=0||j>=0||add!=0){
            int x=i>=0?num1.charAt(i)-'0':0;
            int y=j>=0?num2.charAt(j)-'0':0;
            int result=x+y+add;
            ans.append(result%10);
            add=result/10;
            i--;
            j--;
        }
        ans.reverse();
        return ans.toString();

    }
}
举报

相关推荐

0 条评论