目录
前言
该算法链接来源于
 冲刺春招-精选笔面试 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();
    }
}










