2022寒假算法题汇总

一、算法入门

1、简单版

2022-1-11

704. 二分查找

题目及示例
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

代码

class Solution {
    public int search(int[] nums, int target) {
       int begin=0;
       int end=nums.length-1;
       int mid;
        while(begin<=end){
            mid=(begin+end)/2;
            if(target>nums[mid]){
                begin=mid+1;
            }else if(target<nums[mid]){
                end=mid-1;
            }else{
                return mid;
            }
        }
        return -1;
    }
}

题解:

  1. 二分查找体现的是分治法的思想,
  2. 题目中说数组是升序的,即是有序的,所以不需要排序,降低了难度。
  3. 设置起始下标为0,终止下标为数组长度-1(nums.length-1),从而得到mid=(begin+end)/2
  4. 把target与nums[mid]相比较,如果target>nums[mid],则target在数组的上边部分,所以begin=mid+1;如果target<nums[mid],则target在数组的下边部分;如果target=nums[mid],则返回数组下标。
  5. 一直循环,终止条件是begin>end,找不到则返回-1
  6. 时间复杂度为O(log2n),空间复杂度为O(1)
278. 第一个错误的版本(※)

题目及示例

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数

示例 1:

输入:n = 5, bad = 4
输出:4
解释:
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
示例 2:

输入:n = 1, bad = 1
输出:1

提示:1 <= bad <= n <= 231 - 1

错误代码(时间复杂度太高,超时)

/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int first=1;
        int last=n;
        int result=1;
        if(n==1){
            result=1;
        }else{
            while(first+1<=last){
                int mid=(first+last)/2;
                if(isBadVersion(mid)&&isBadVersion(mid+1)){
                    last=mid;
                }else if((!isBadVersion(mid))&&(!isBadVersion(mid+1))){
                    first=mid;
                }else if((!isBadVersion(mid))&&isBadVersion(mid+1)){
                    result=mid+1;
                    break;
                }
            }
        }
        return result;
    }
}

正确代码

/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int first=1;
        int last=n;
        while(first<last){//循环直至左右端点相同
            int mid=first+(last-first)/2;//防止计算时溢出
            if(isBadVersion(mid)){
                last=mid;//[first,mid]
            }else {
                first=mid+1;//[mid+1,last]
            }
        }
        return first;//first==last
    }
}

题解:

  1. 首先,题目中提到尽量减少对调用 API 的次数,即是少调用isBadVersion(version) 函数。但是错误代码中,我调用了六次此函数,会导致时间的增加。因为我一直认为必须要前一个版本是false,后一个版本是true才行。
  2. 正确代码中并不在while循环中找到第一个错误版本,而是通过缩小区间,最后只剩一个(first=last),first或者last就是第一个错误版本。
  3. 最重要的一点,我提交了好几次,都是超时。原因在于n和bad都是很大的数字
    1 <= bad <= n <= 231 - 1),mid不能直接等于(first+last)/2,要等于first+(last-first)/2,以防止计算时溢出。
374. 猜数字大小(与278. 第一个错误的版本相同的解决方法)

题目及示例

猜数字游戏的规则如下:

每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。
你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

-1:我选出的数字比你猜的数字小 pick < num
1:我选出的数字比你猜的数字大 pick > num
0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num
返回我选出的数字。

示例 1:

输入:n = 10, pick = 6
输出:6

示例 2:

输入:n = 1, pick = 1
输出:1

提示:1 <= n <= 2^31 - 1 1 <= pick <= n

代码:

public class Solution extends GuessGame {
    public int guessNumber(int n) {
        int first=1;
        int last=n;
        while(first<last){
            int mid=first+(last-first)/2;
            if(guess(mid)<=0){
                last=mid;//[first,mid]
            }else{
                first=mid+1;//[mid+1,last]
            }
        }
        return first;//first=end
    }
}
35. 搜索插入位置

题目及示例

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log2 n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4
示例 4:

输入: nums = [1,3,5,6], target = 0
输出: 0

代码

class Solution {
    public int searchInsert(int[] nums, int target) {
       int begin=0;
       int end=nums.length-1;
       int mid;
        while(begin<=end){
            mid=(begin+end)/2;
            if(target>nums[mid]){
                begin=mid+1;
            }else if(target<nums[mid]){
                end=mid-1;
            }else{
                return mid;
            }
        }
        return begin;
    }
}

题解

  1. 这一题和704二分查找差不多,不同的是没查到目标数字的话就输出应该插入的下标,正好是begin。
  2. 时间复杂度为O(log2n) ,空间复杂度为O(1)

2022-1-12

977. 有序数组的平方

题目及示例

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

代码:

class Solution {
    public int[] sortedSquares(int[] nums) {
        int []a=new int[nums.length];
        for(int i=0;i<nums.length;i++){
            a[i]=nums[i]*nums[i];
        }
        Arrays.sort(a);
        return a;
    }
}

题解

  1. 先进行平方,然后用java自带的Arrays.sort函数进行排序。
  2. 时间复杂度为O(n),空间复杂度为O(n)
189. 轮转数组

题目及示例
给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]

示例 2:

输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]

提示:
1 <= nums.length <= 105
-2^31 <= nums[i] <= 2^31 - 1
0 <= k <= 105
代码

class Solution {
    public void rotate(int[] nums, int k) {
        int []a=new int[nums.length];
        int n=nums.length;
        for(int i=0;i<n;i++){
            a[(i+k)%n]=nums[i];
        }
         //for(int i=0;i<n;i++){
         //   nums[i]=a[i];
        //}
        // System.out.print("[");
        // for(int j=0;j<n;j++){
        //     System.out.print(nums[j]);
        //     if(j!=n-1){
        //         System.out.print(",");
        //     }
        // }
        // System.out.print("]");
        System.arraycopy(a,0,nums,0,n);//浅度复制,把a数组赋值给nums数组
    }
}

题解:

  1. 这题我犯了一个错误,以为只要a[(i+k)%n]=nums[i],输入a数组即可,但是还要把a数组赋值给nums才行
  2. 我用的是for循环输出,看到官方解法, System.arraycopy(a,0,nums,0,n);
  3. System中提供了一个native静态方法arraycopy(),可以使用这个方法来实现数组之间的复制。对于一维数组来说,这种复制属性值传递修改副本不会影响原来的值。对于二维或者一维数组中存放的是对象时,复制结果是一维的引用变量传递给副本的一维数组,修改副本时,会影响原来的数组
  4. public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
    src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,destPos表示m目标数组要复制的起始位置,length表示要复制的长度。
  5. 时间复杂度为O(n),空间复杂度为O(n)
283. 移动零

题目及示例
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

代码:

class Solution {
    public void moveZeroes(int[] nums) {
        for(int i=0;i<nums.length;i++){
            for(int j=i;j<nums.length;j++){
                if(nums[i]==0&&nums[j]!=0){
                    nums[i]=nums[j];
                    nums[j]=0;
                    break;
                }
            }
        }
    }
}

题解:

  1. 双重for循环,遇到0,再遇到一个不是0的数,则交换。
  2. 对于数组长度很大的不适用
  3. 时间复杂度为O(n),空间复杂度为O(1)

2022-1-13

344. 反转字符串

题目及示例

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = [“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]

示例 2:

输入:s = [“H”,“a”,“n”,“n”,“a”,“h”]
输出:[“h”,“a”,“n”,“n”,“a”,“H”]

提示:
1 <= s.length <= 105
s[i] 都是 ASCII 码表中的可打印字符

代码:

class Solution {
    public void reverseString(char[] s) {
        for(int i=0;i<s.length/2;i++){
            char t;
            t=s[i];
            s[i]=s[s.length-i-1];
            s[s.length-i-1]=t;
        }
    }
}

题解:

  1. 从中间分开,第一个与最后一个字符交换,依次递推。
  2. 时间复杂度为O(n),空间复杂度为O(1)
557. 反转字符串中的单词 III

题目及示例
给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例:

输入:“Let’s take LeetCode contest”
输出:“s’teL ekat edoCteeL tsetnoc”

提示:
在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。

代码1:

class Solution {
    public String reverseWords(String s) {
        String []str=s.split(" ");
        String afterReverse="";
        for(int i=0;i<str.length;i++){
            StringBuffer sb = new StringBuffer(str[i]);
            if(i!=str.length-1){
                afterReverse+=sb.reverse().toString()+" ";
            }else{
                afterReverse+=sb.reverse().toString();
            }
            
        }
        return afterReverse;
    }
}

代码2:

class Solution {
    public String reverseWords(String s) {
        String []str=s.split(" ");
        StringBuffer sb = new StringBuffer();
        StringBuffer sb1 = new StringBuffer();
        for(int i=0;i<str.length;i++){
            sb = new StringBuffer(str[i]);
            if(i!=str.length-1){
                sb1.append(sb.reverse());
                sb1.append(" ");
            }else{
                sb1.append(sb.reverse());
            }
        }
        return sb1.toString();
    }
}

题解:

  1. 首先把字符串以空格为分割,分成字符串数组;for循环,用reverse反转,toString转成字符串
  2. 代码2和代码1不同之处在于,用了StringBuffer的append方法把反转后的字符串存在其中,这样比字符串相加所用的时间短。
  3. 时间复杂度为O(n),空间复杂度为O(n)

2022-1-14

876. 链表的中间结点

题目及示例
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。

示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。

提示:给定链表的结点数介于 1 和 100 之间。

代码:

/**
 - 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 middleNode(ListNode head) {
        ListNode slow=head,fast=head;
        while(fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        return slow;
    }
}

题解:

  • 快慢指针法,slow是慢指针,一次走一步;fast是快指针,一次走两步。
  • 当fast走到尽头的时候,slow正好走到中间结点位置
19. 删除链表的倒数第 N 个结点

题目及示例

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

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

示例 2:
输入:head = [1], n = 1
输出:[]

示例 3:
输入:head = [1,2], n = 1
输出:[1]

提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz

代码:

/**
 - 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 removeNthFromEnd(ListNode head, int n) {
       ListNode dummy=new ListNode(0,head);
       ListNode first=head;
       ListNode second=dummy;
       for(int i=0;i<n;i++){
           first=first.next;
       }
       while(first!=null){
           first=first.next;
           second=second.next;
       }
       second.next=second.next.next;
       ListNode ans=dummy.next;//头结点
       return ans;
    }
}

题解:

  • first指向头指针,second指向哑指针(哑指针指向头指针)。两者相隔n,则当first指向末尾时,second的下一个就是要删除的结点。
  • 删除节点:second指向删除节点的下一个结点(second.next=second.next.next;)
  • 头结点是哑结点的下一个结点。

2022-1-17

3. 无重复字符的最长子串

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

示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:

输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

示例 4:
输入: s = “”
输出: 0

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

代码:

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n=s.length();
        Set<Character> set=new HashSet<>();
        int right=-1;
        int max=0;
        for(int left=0;left<n;left++){
            if(left!=0){
                set.remove(s.charAt(left-1));
            }
            while(right+1<n&&!set.contains(s.charAt(right+1)))
            {
                set.add(s.charAt(right+1));
                right++;
            }
            max=Math.max(max,right-left+1);
        }
        return max;
    }
}

题解:

  1. 弹窗法,用hashset来存储字符串里的字符。当以第一个字符为起始字符,看最长不重复子字符长度,然后看第一个字符为起始字符,看最长不重复子字符长度,依次递推。最后取最大长度的子字符的长度。
  2. 当set集合里没有该字符的时候,向集合里加入该字符,右指针右移。碰到相同字符后结束循环。
  3. 下一次循环即是左指针向右移a,集合中取出左指针前面的那个字符,继续向后边判断是否有重复字符。
  4. 子字符串的长度是右指针减去左指针加一。
  5. set的方法 删除:remove(),增加 :add()
  6. 时间复杂度:O(N),其中 N 是字符串的长度。左指针和右指针分别会遍历整个字符串一次。
  7. 空间复杂度:O(∣Σ∣),其中 Σ 表示字符集(即字符串中可以出现的字符),∣Σ∣ 表示字符集的大小。在本题中没有明确说明字符集,因此可以默认为所有 ASCII 码在 [0, 128)内的字符,即∣Σ∣=128。我们需要用到哈希集合来存储出现过的字符,而字符最多有 ∣Σ∣ 个,因此空间复杂度为 O(∣Σ∣)。
567. 字符串的排列

剑指 Offer II 014. 字符串中的变位词一样
题目及示例

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。
换句话说,s1 的排列之一是 s2 的 子串 。

示例 1:
输入:s1 = “ab” s2 = “eidbaooo”
输出:true
解释:s2 包含 s1 的排列之一 (“ba”).

示例 2:
输入:s1= “ab” s2 = “eidboaoo”
输出:false

提示:
1 <= s1.length, s2.length <= 104
s1 和 s2 仅包含小写字母

代码:

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        int n=s1.length();
        int m=s2.length();
        if(n>m){
            return false;
        }
        int []cnt1=new int[26];
        int []cnt2=new int[26];
        for(int i=0;i<n;i++){
            ++cnt1[s1.charAt(i)-'a'];
            ++cnt2[s2.charAt(i)-'a'];
        }
        if(Arrays.equals(cnt1,cnt2)){
            return true;
        }
        for(int i=n;i<m;i++){
            ++cnt2[s2.charAt(i)-'a'];
            --cnt2[s2.charAt(i-n)-'a'];
            if(Arrays.equals(cnt1,cnt2)){
                return true;
            }
        }
        return false;
    }
}

题解:

  1. 弹窗法,s2中和s1相同长度的子串才有可能是s1的排列之一。所以弹窗长度为s1的长度n。
  2. 设置两个int数组是用来记录s1、s2的字符出现的次数,如果s1的排列一致是s2中的子串,则两个数组相等。
  3. 每次弹窗向右移n个字符,如果相等,则为true;不相等,则为false
  4. 时间复杂度:O(n+m+∣Σ∣),其中 n 是字符串 s1的长度,m 是字符串 s2的长度,Σ 是字符集,这道题中的字符集是小写字母,∣Σ∣=26。
  5. 空间复杂度:O(∣Σ∣)。

2022-1-18

733. 图像渲染

题目及示例

有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。

给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。

为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。

最后返回经过上色渲染后的图像。

示例 1:

输入:
image = [[1,1,1],[1,1,0],[1,0,1]]
sr = 1, sc = 1, newColor = 2
输出: [[2,2,2],[2,2,0],[2,0,1]]
解析:
在图像的正中间,(坐标(sr,sc)=(1,1)),
在路径上所有符合条件的像素点的颜色都被更改成2。
注意,右下角的像素没有更改为2,
因为它不是在上下左右四个方向上与初始点相连的像素点。

注意:
image 和 image[0] 的长度在范围 [1, 50] 内。
给出的初始点将满足 0 <= sr < image.length 和 0 <= sc < image[0].length。
image[i][j] 和 newColor 表示的颜色值在范围 [0, 65535]内。

代码:

class Solution {
    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
       int currColor=image[sr][sc];
       if(currColor!=newColor){
           dfs(image,sr,sc,currColor,newColor);
        }
        return image;
    }
    public void dfs(int [][]image,int x,int y,int currColor,int newColor){
        int []dx={1,0,0,-1};
        int []dy={0,1,-1,0};//上,右,左,下
        if(image[x][y]==currColor){
            image[x][y]=newColor;
            for(int i=0;i<4;i++){
                int mx=x+dx[i];
                int my=y+dy[i];
                if(mx>=0&&mx<image.length
                &&my>=0&&my<image[0].length){
                    dfs(image,mx,my,currColor,newColor);
                }
            }
        }
    }
}

题解:

  1. 深度搜索法dfs,其中用到了递归(栈)。用dx和dy数组存储上右左下的行列下标变化。
  2. 如果对应坐标处的颜色值等于当前值,则对应坐标处的颜色值等于新颜色值。
  3. 然后看是否有上右左下的下标值是否超过边界,如果没有超过边界,则进行深度搜索。
  4. 时间复杂度:O(n×m),其中 n 和 m 分别是二维数组的行数和列数。最坏情况下需要遍历所有的方格一次。
  5. 空间复杂度:O(n×m),其中 n 和 m 分别是二维数组的行数和列数。主要为栈空间的开销。
695. 岛屿的最大面积

题目及示例
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。

示例 1:
输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。

示例 2:
输入:grid = [[0,0,0,0,0,0,0,0]]
输出:0

提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 50
grid[i][j] 为 0 或 1

代码:

class Solution {

    public int maxAreaOfIsland(int[][] grid) {
        int ans=0;
        int n=grid.length;
        int m=grid[0].length;
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                ans=Math.max(ans,dfs(grid,i,j));
            }
        }
        return ans;
    }
    public int dfs(int [][]grid,int x,int y){
        if(x<0||x==grid.length||y<0||
        y==grid[0].length||grid[x][y]==0){
            return 0;
        }
        grid[x][y]=0;
        int []di={1,0,0,-1};
        int []dj={0,1,-1,0};//上,右,左,下
        int ans=1;
        for(int k=0;k<4;k++){
            int mi=x+di[k];
            int mj=y+dj[k];
            ans+=dfs(grid,mi,mj);
        }
        return ans;
    }
}

题解:

  1. 深度优先搜索dfs。有多个岛屿,求面积最大的岛屿。
  2. 在dfs函数里,如果下标越界或者对应值为0,则岛屿面积为0。为了防止重复计算岛屿,所以把遍历过的岛屿的值变成0。
  3. 求岛屿面积,看上下左右的值是否为1,即是把上下左右的下标传进去,再次进行深度搜索。
  4. n是数组的行数,m是数组的列数,时间复杂度为O(mn),空间复杂度为O(mn)。

2022-1-19

617. 合并二叉树

题目及示例
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例 1:

输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/
4 5
/ \ \
5 4 7
注意: 合并必须从两个树的根节点开始。

代码:

/**
 * 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 TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if(root1==null){
            return root2;
        }
        if(root2==null){
            return root1;
        }
        TreeNode t=new TreeNode(root1.val+root2.val);
        t.left=mergeTrees(root1.left,root2.left);
        t.right=mergeTrees(root1.right,root2.right);
        return t;

    }

}

题解:

  1. 深度搜索:如果一颗子树为null,则返回另一颗子树。左边子树进行深度搜索,右边子树进行深度搜索。如果两个子树对应位置都是null,则返回null;如果一个子树为null,另一个有值,则返回那个有值的,如果两个都有值,那么两个数相加。
  2. 时间复杂度:O(min(m,n)),其中 m 和 n 分别是两个二叉树的节点个数。对两个二叉树同时进行广度优先搜索,只有当两个二叉树中的对应节点都不为空时才会访问到该节点,因此被访问到的节点数不会超过较小的二叉树的节点数。
  3. 空间复杂度:O(min(m,n)),其中 m 和 n 分别是两个二叉树的节点个数。空间复杂度取决于队列中的元素个数,队列中的元素个数不会超过较小的二叉树的节点数。
116. 填充每个节点的下一个右侧节点指针

题目及示例
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。

进阶:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。

示例:

输入:root = [1,2,3,4,5,6,7]
输出:[1,#,2,3,#,4,5,6,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,’#’ 标志着每一层的结束。

提示:
树中节点的数量少于 4096
-1000 <= node.val <= 1000

代码:

/*
// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;
    public Node next;

    public Node() {}
    
    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, Node _left, Node _right, Node _next) {
        val = _val;
        left = _left;
        right = _right;
        next = _next;
    }
};
*/

class Solution {
    public Node connect(Node root) {
        if (root == null) {
            return null;
        }
        connect(root.left, root.right);
        return root;
    }

    public void connect(Node left, Node right) {
        if (left != null && right != null) {
            left.next = right;
            connect(left.left, left.right);
            connect(left.right, right.left);
            connect(right.left, right.right);
        }
    }
}
  1. 把二叉树进行层次遍历,即是一层的左边的下一个是同一层的右边,即left.next=right ,连接下一层的时候是左子树的左结点、左子树的右节点、右子树的左节点、右子树的右结点。
  2. n是二叉数的结点个数,时间复杂度为O(n),空间复杂度为O(1)

2022-1-20

542. 01 矩阵

给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

示例 1:
输入:mat = [[0,0,0],[0,1,0],[0,0,0]]
输出:[[0,0,0],[0,1,0],[0,0,0]]

示例 2:
输入:mat = [[0,0,0],[0,1,0],[1,1,1]]
输出:[[0,0,0],[0,1,0],[1,2,1]]

提示:
m == mat.length
n == mat[i].length
1 <= m, n <= 104
1 <= m * n <= 104
mat[i][j] is either 0 or 1.
mat 中至少有一个 0

代码:

class Solution {
    static int [][]dir={{-1,0},{1,0},{0,-1},{0,1}};
    public int[][] updateMatrix(int[][] mat) {
        int n=mat.length;
        int m=mat[0].length;
        int [][]dist=new int[n][m];
        Queue<int[]> q=new LinkedList<int[]>();
        boolean [][] b=new boolean[n][m];
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(mat[i][j]==0){
                    q.offer(new int[]{i,j});//进队列
                    b[i][j]=true;
                }
            }
        }
        //广度优先搜索
        while(!q.isEmpty()){
            int []cell=q.poll();//出队列
            int i=cell[0],j=cell[1];
            for(int d=0;d<4;d++){
                int ni=i+dir[d][0];
                int nj=j+dir[d][1];
                if(ni>=0&&ni<n&&nj>=0&&nj<m&&!b[ni][nj]){
                    dist[ni][nj]=dist[i][j]+1;
                    q.offer(new int[]{ni,nj});
                    b[ni][nj]=true;
                }
            }
        }
        return dist;
    }
}

题解:

  1. 把矩阵中是零的下标i,j加入队列。如果队列不为空,由零向上下左右扩散,每扩散一步,数值就加一。把扩散的下标都依次加入队列中。遍历过的用标志数组置为true。
  2. 矩阵行数为n,列数为m,时间复杂度为O(nm),空间复杂度为O(nm)。
994. 腐烂的橘子

题目及示例
在给定的网格中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。
返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。

示例 1:
输入:[[2,1,1],[1,1,0],[0,1,1]]
输出:4

示例 2:
输入:[[2,1,1],[0,1,1],[1,0,1]]
输出:-1
解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个正向上。

示例 3:
输入:[[0,2]]
输出:0
解释:因为 0 分钟时已经没有新鲜橘子了,所以答案就是 0 。

提示:
1 <= grid.length <= 10
1 <= grid[0].length <= 10
grid[i][j] 仅为 0、1 或 2

代码:

class Solution {
    //配合得到下上左右的元素的二维数组
    static int [][]dir={{-1,0},{1,0},{0,-1},{0,1}};
    public int orangesRotting(int[][] grid) {
        int n=grid.length;
        int m=grid[0].length;
        int total=0;
        Queue<Integer> q=new LinkedList<Integer>();
        Map<Integer,Integer> depth=new HashMap<Integer,Integer>();
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(grid[i][j]==2){
                    //转化成索引唯一的一维数组
                    int code=i*m+j;
                    q.offer(code);//存储腐烂橘子
                    //存储句子变为腐烂的时间,key为句子的以为数组下标,
                    //value为变腐烂的时间,刚开始时间为0。
                    depth.put(code,0);
                }
            }
        }
        //广度优先搜索
        while(!q.isEmpty()){
            int code=q.poll();//出队列
            int i=code/m,j=code%m;
            for(int d=0;d<4;d++){
                int ni=i+dir[d][0];
                int nj=j+dir[d][1];  
                if(ni>=0&&ni<n&&nj>=0&&nj<m&&grid[ni][nj]==1){
                    grid[ni][nj]=2;
                    int ncode=ni*m+nj;
                    q.offer(ncode);
                    //计数的关键,元素grid[i][j]的上下左右元素的腐烂时间应该一致
                    depth.put(ncode,depth.get(code)+1);
                    total=depth.get(ncode);
                }
            }
        }
        //如果有元素为1,说明还有新鲜橘子,返回-1
        for(int []row:grid){
            for(int v:row){
                if(v==1){
                    return -1;
                }
            }
        }
        return total;
    }
}

题解:

  1. 542. 01 矩阵类似,广度优先搜索,用map来记录腐烂橘子时间。腐蚀一轮,元素grid[i][j]的上下左右元素的腐烂时间应该一致,在上一轮的时间加一。
  2. 矩阵行数为n,列数为m,时间复杂度为O(mn),空间复杂度为O(mn)。

20222-1-21

77. 组合

题目及示例
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。

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

示例 2:
输入:n = 1, k = 1
输出:[[1]]

提示:
1 <= n <= 20
1 <= k <= n

代码:

class Solution {
    List<List<Integer>> list =new ArrayList<List<Integer>>();
    List<Integer> li=new ArrayList<Integer>();
    public List<List<Integer>> combine(int n, int k) {
       dfs(1,n,k);
       return list;
    }
    public void dfs(int cur,int n,int k){
        //临时数组li的长度和【cur,n】的长度相加如果小于k,则数组为空。
        if(li.size()+n-cur+1<k){
            return;
        }
        //临时数组li长度为k,则加入到答案数组list
        if(li.size()==k){
            list.add(new ArrayList<Integer>(li));
            return;
        }
        //如果选择当前位置,把cur加入到li数组
        li.add(cur);
        dfs(cur+1,n,k);
        //如果不选择当前位置
        li.remove(li.size()-1);
        dfs(cur+1,n,k);
    }
}

题解:

  1. 递归实现组合型枚举
  2. 时间复杂度为O((n k)*k),空间复杂度为O(n+k)=O(n)
46. 全排列

题目及示例
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:
输入:nums = [1]
输出:[[1]]

提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums 中的所有整数 互不相同

代码

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> ans=new ArrayList<List<Integer>>();
        List<Integer> output=new ArrayList<Integer>();
        for(int num:nums){
            output.add(num);
        }
        int n=nums.length;
        backtrack(n,output,ans,0);
        return ans;
    }
    public void backtrack(int n,List<Integer> output,
    List<List<Integer>> ans,int first){
        if(first==n){
            ans.add(new ArrayList<Integer>(output));
        }
        for(int i=first;i<n;i++){
            //动态维护数组
            Collections.swap(output,first,i);
            //继续递归填下一个数
            backtrack(n,output,ans,first+1);
            //撤销操作
            Collections.swap(output,first,i);
        }
    }
}

题解

  1. 回溯法,定义backtrack(first,output)函数,填第first位置,当前排列为output
  2. 当first==n时,表示已经填完了,把output放到答案数组中,递归结束。
  3. 当first<n时,思考应该放入哪个未放入的数字,然后继续调用函数backtrack(first+1,output)。回溯的时候撤销这个位置的数,继续尝试。
  4. 怎么判断一个数字是否被加入,可以用标记数组,也可以用另外一种方法。把数组分为两个部分,左边是加入排列的数字,右边是为加入的数字。如果要把一个数字加入的话就交换当前下标的数字和要加入的数字
  5. n 为序列的长度, 时间复杂度为:O(n×n!),空间复杂度为O(n)。

2022-1-22

70. 爬楼梯
198. 打家劫舍

和动态规划的题一样

120. 三角形最小路径和

题目及示例
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。

示例 1:
输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
2
3 4
6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

示例 2:
输入:triangle = [[-10]]
输出:-10

提示:
1 <= triangle.length <= 200
triangle[0].length == 1
triangle[i].length == triangle[i - 1].length + 1
-104 <= triangle[i][j] <= 104

代码:

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int n=triangle.size();
        int [][]a=new int[n][n];
        a[0][0]=triangle.get(0).get(0);
        for(int i=1;i<n;i++){
            a[i][0]=a[i-1][0]+triangle.get(i).get(0);
            for(int j=1;j<i;j++){
                a[i][j]=Math.min(a[i-1][j-1],a[i-1][j])+triangle.get(i).get(j);
            }
            a[i][i]=a[i-1][i-1]+triangle.get(i).get(i);

        }
        int min=a[n-1][0];
        for(int k=1;k<n;k++){
            min=Math.min(min,a[n-1][k]);
        }
        return min;
    }
}

题解:

  1. 设置一个数组存储在第i行第j行的最短路径,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。所以可以得a[i][j]=Math.min(a[i-1][j],a[i-1][j-1])+triangle.get(i).get(j)
  2. 第 i 行有 i+1 个元素,它们对应的 j 的范围为 [0,i]。
  3. 看边界:当j等于1时,即a[i][0]=a[i-1][0]+triangle.get(i).get(0), 没有a[i-1][j-1]
  4. 当j等于i时,即a[i][i]=a[i-1][i-1]+triangle.get(i).get(i),每行的最后元素只能由上一行的最后一个元素到此。
  5. 最后求最后一行的所有数中的最小值,即是以最后一行为结尾的路径最小值。
  6. 时间复杂度为O(n2),空间复杂度(n2)

2、基础版

2022-2-7

34. 在排序数组中查找元素的第一个和最后一个位置

题目及示例

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:

你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:

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

提示:

0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
代码:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int leftIndex=binarySearch(nums,target,true);
        int rightIndex=binarySearch(nums,target,false)-1;
        System.out.println(leftIndex+" "+rightIndex);
        if(leftIndex<=rightIndex&&rightIndex<nums.length&&nums[leftIndex]==target&&nums[rightIndex]==target){
            return new int[]{leftIndex,rightIndex};
        }
        return new int[]{-1,-1};
    }
    public int binarySearch(int []nums,int target,boolean flag){
        int left=0;
        int right=nums.length-1;
        int ans=nums.length;
        while(left<=right){
            int mid=(left+right)/2;
            if(nums[mid]>target||(nums[mid]>=target&&flag)){
                right=mid-1;
                ans=mid;
            }else{
                left=mid+1;
            }
        }
        return ans;
    }
}

题解:

  1. 二分查找,寻找leftindexs是数组中第一个大于等于target的下标,寻找数组中第一个大于target的下标,可以用一个布尔值来区分两种情况。
  2. 然后验证左右下标是否符合条件
  3. 时间复杂度为O(log n),空间复杂度为O(1)

2022-2-16

153. 寻找旋转排序数组中的最小值

题目及示例
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。
示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

提示:

n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums 中的所有整数 互不相同
nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

代码:

class Solution {
    public int findMin(int[] nums) {
        //第一种方法
        // Arrays.sort(nums);
        // return nums[0];

        //第二种方法
        int ans=nums[0];
        for(int i=0;i<nums.length-1;i++){
            if(nums[i]>nums[i+1]){
                ans=nums[i+1];
                break;
            }
        }
        return ans;
    }
}

题解:

  1. 逆序的那后一个数字是最小值,如果没有逆序,那么最小值是第一个数。
  2. 时间复杂度为O(n),空间复杂度为O(1)

二、剑指offer

2022-1-11

剑指 Offer II 001. 整数除法

题目及示例
给定两个整数 a 和 b ,求它们的除法的商 a/b ,要求不得使用乘号 ‘*’、除号 ‘/’ 以及求余符号 ‘%’ 。
注意:
整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231−1]。本题中,如果除法结果溢出,则返回 231 − 1

示例 1:
输入:a = 15, b = 2
输出:7
解释:15/2 = truncate(7.5) = 7

示例 2:
输入:a = 7, b = -3
输出:-2
解释:7/-3 = truncate(-2.33333…) = -2

示例 3:
输入:a = 0, b = 1
输出:0

提示:

-231 <= a, b <= 231 - 1
b != 0

代码

class Solution {
    public int divide(int a, int b) {
        int count=0;
        boolean flag=false;
        if(b==0){
            return 0;
        }
        if(a==Integer.MIN_VALUE){
            if(b==1){
                return Integer.MIN_VALUE;
            }
            if(b==-1){
                return Integer.MAX_VALUE;
            }
        }
        if(b==Integer.MIN_VALUE){
            if(a==Integer.MIN_VALUE){
                return 1;
            }else{
                return 0;
            }
        }
        if((a>0&&b<0)||(a<0&&b>0)){
           flag=true;
        }
       //防止数字溢出
        if(a>0){
            a=-a;
        }   
        if(b>0){
            b=-b;
        }       
        
        while(a<=b){
            a-=b;
            count++;
        }
        if(flag){
            count=-count;
        }
        return count;
    }
}

题解

  1. 用减法代替乘法,最重要的一点是要考虑特殊情况Integer.MIN_VALUE、Integer.MAX_VALUE和0。
  2. 设置flag标识是否要变符号,当除数和被除数符号不同时flag=true,结果取反,否则不变。
  3. 时间复杂度为O(n),空间复杂度为O(1)
剑指 Offer II 002. 二进制加法

题目及示例
给定两个 01 字符串 a 和 b ,请计算它们的和,并以二进制字符串的形式输出。

输入为 非空 字符串且只包含数字 1 和 0。

示例 1:

输入: a = “11”, b = “10”
输出: “101”
示例 2:

输入: a = “1010”, b = “1011”
输出: “10101”

提示:

每个字符串仅由字符 ‘0’ 或 ‘1’ 组成。
1 <= a.length, b.length <= 10^4
字符串如果不是 “0” ,就都不含前导零。

代码:

class Solution {
    public String addBinary(String a, String b) {
        return Integer.toBinaryString(
            Integer.parseInt(a,2)+Integer.parseInt(b,2)
        );
    }
}

题解:

  1. 把二进制转化成十进制,然后再传回成二进制字符串
  2. Integer.parseInt(String s,int radix)就是求“int radix”进制数“String s”的十进制数是多少。
  3. redix表示几进制,s表示二进制字符串 ,Integer.parseInt(a,2)表示二进制数a的十进制数
  4. Integer.toBinaryString()方法是将int类型的数字转化为二进制然后以字符串形式输出
  5. 如果 a 的位数是 n,b的位数为 m,这个算法的渐进时间复杂度为O(n+m)。
  6. 空间复杂度为O(1)
剑指 Offer II 003. 前 n 个数字二进制中 1 的个数

题目及示例

给定一个非负整数 n ,请计算 0 到 n 之间的每个数字的二进制表示中 1 的个数,并输出一个数组。

示例 1:

输入: n = 2
输出: [0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10
示例 2:

输入: n = 5
输出: [0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101

说明 :

0 <= n <= 10^5

代码:

class Solution {
    public int[] countBits(int n) {
        int []a=new int[n+1];
        for(int i=0;i<=n;i++){
            int j=i;
            while(j!=0){
                if(j%2==1){
                    a[i]++;
                }
                j=j/2;
            }
        }
       return a;
    }
}

题解

  1. 二进制的1的个数是对2取余等于1的次数
  2. 时间复杂度为O(n),空间复杂度是O(n)

2022-1-12

剑指 Offer II 004. 只出现一次的数字

题目及示例
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

示例 1:

输入:nums = [2,2,3,2]
输出:3
示例 2:

输入:nums = [0,1,0,1,0,1,100]
输出:100

提示:

1 <= nums.length <= 3 * 104
-231 <= nums[i] <= 231 - 1
nums 中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次
代码(哈希表法):

class Solution {
    public int singleNumber(int[] nums) {
        Map<Integer,Integer> map=new HashMap<Integer,Integer>();
        for(int num:nums){
            map.put(num,map.getOrDefault(num,0)+1);
        }
        int ans=0;
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){
            int num=entry.getKey();
            int occ=entry.getValue();
            if(occ==1){
                ans=num;
                break;
            }
        }
        return ans;
    }
}

题解:

  1. Map.getOrDefault(Object key, V defaultValue);如果在Map中存在key,则返回key所对应的的value。如果在Map中不存在key,则返回默认值。key的值相同,使value的值加一。
  2. map.put(num, map.getOrDefault(num, 0) + 1);表示:如果存在key,每次操作后num对应的value值加1,如果不存在key,则返回默认值0。
  3. Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示的是Map中的一个实体(key-value)。
  4. map.entrySet()的返回值是一个Set集合,此集合的类型为Map.Entry。
  5. Map.Entry里面包含getKey()和getValue()方法,getKey()获取key,即是数组的值;getValue()获取值,即是出现的次数。
  6. 时间复杂度:O(n),其中 n是数组的长度。
  7. 空间复杂度:O(n)。哈希映射中包含最多 ⌊n/3⌋+1 个元素,即需要的空间为 O(n)。
剑指 Offer II 005. 单词长度的最大乘积

题目及示例

给定一个字符串数组 words,请计算当两个字符串 words[i] 和 words[j] 不包含相同字符时,它们长度的乘积的最大值。假设字符串中只包含英语的小写字母。如果没有不包含相同字符的一对字符串,返回 0。

示例 1:
输入: words = [“abcw”,“baz”,“foo”,“bar”,“fxyz”,“abcdef”]
输出: 16
解释: 这两个单词为 “abcw”, “fxyz”。它们不包含相同字符,且长度的乘积最大。

示例 2:
输入: words = [“a”,“ab”,“abc”,“d”,“cd”,“bcd”,“abcd”]
输出: 4
解释: 这两个单词为 “ab”, “cd”

示例 3:
输入: words = [“a”,“aa”,“aaa”,“aaaa”]
输出: 0
解释: 不存在这样的两个单词。

提示:
2 <= words.length <= 1000
1 <= words[i].length <= 1000
words[i] 仅包含小写字母

代码:

class Solution {
    public int maxProduct(String[] words) {
        int n=words.length;
        int []arr=new int[n];
        for(int i=0;i<n;i++){
            int a=0;
            for(int j=0;j<words[i].length();j++){
               a|=(1<<(words[i].charAt(j)-'a'));
            }
            arr[i]=a;
        }
        int max=0;
        for(int i=0;i<n-1;i++){
             for(int j=i+1;j<n;j++){
              if((arr[i]&arr[j])==0){
                  max=Math.max(max,words[i].length()*words[j].length());
              }
            }
        }
        return max;
    }
}

题解:

  1. 把words数组里的每一个字符串转变成二进制数,存放在int数组里。例如果是a的话则第一位为1,b的话则第一位为1。
  2. words[i].charAt(j)-‘a’)即是判断向左移动几位。然后指定位置置1,即是1<<(words[i].charAt(j)-‘a’),与0或,得出字符串的二进制数。
  3. 如果两个字符串的二进制数相与结果为0,说明两个字符串没有相同的字符串,最大乘积取前一个最大乘积和二个字符串长度的乘积。
  4. 时间复杂度为O(n),空间复杂度为O(n)
剑指 Offer II 006. 排序数组中两个数字之和

题目及示例

给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。
假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。

示例 1:
输入:numbers = [1,2,4,6,10], target = 8
输出:[1,3]
解释:2 与 6 之和等于目标数 8 。因此 index1 = 1, index2 = 3

示例 2:
输入:numbers = [2,3,4], target = 6
输出:[0,2]

示例 3:
输入:numbers = [-1,0], target = -1
输出:[0,1]

代码:

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int []a=new int[2];
        int left=0;
        int right=numbers.length-1;
        if(numbers==null||numbers.length<=0){
            return a;
        }
        while(left<right){
            if(numbers[left]+numbers[right]>target){
                right--;
            }else if(numbers[left]+numbers[right]<target){
                left++;
            }else if(numbers[left]+numbers[right]==target){
                a[0]=left;
                a[1]=right;
                break;
            }
        }
        return a;
    }
}

题解

  1. 双指针法,left=0,right=nums.length,如果相加大于目标值,则right指针向左移;如果相加小于目标值,则left指针向右移
  2. 遍历数组, 时间复杂度为O(n);
  3. 用了一个辅助数组,空间复杂度为O(n)

2022-1-13

剑指 Offer II 007. 数组中和为 0 的三个数

题目及示例

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

示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

示例 2:
输入:nums = []
输出:[]

示例 3:
输入:nums = [0]
输出:[]

提示:
0 <= nums.length <= 3000
-10^5 <= nums[i] <= 10^5

代码:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int n=nums.length;
         if(nums==null||n<3){
            return new ArrayList<>();
        }
        List<List<Integer>> list=new ArrayList<>();
        Arrays.sort(nums);
        for(int i=0;i<n-2;i++){
            if(i>0&&nums[i]==nums[i-1]) continue;
            int target=-nums[i];
            int left=i+1;
            int right=n-1;
            while(left<right){
                 if(nums[left]+nums[right]==target){
                    list.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    //去重
                    while(left<right&&nums[left]==nums[++left]);//如果两个数相等,则进入循环,不能进行下面的步骤;不相等的时候则继续下面的步骤
                    while(left<right&&nums[right]==nums[--right]);
                }else if(nums[left]+nums[right]>target){
                    right--;
                }else{
                    left++;
                }
            }
           
        }
        return list;
    }
}

题解:

  1. 双指针法,先进行排序,然后一个指针指向当前元素的下一个元素,另一个指向最后一个元素,目标值是当前元素。
  2. 如果目标值和双指针的元素之和相等,则把这三个值加入到数组中,前指针向后移,后指针前移;如果目标值大于双指针的元素之和,前指针后移;如果目标值小于双指针的元素之和,后指针前移;
  3. 时间复杂度为O(nlog2n),
  4. 空间复杂度为O(n2
剑指 Offer II 008. 和大于等于 target 的最短子数组

题目及示例

给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:
输入:target = 4, nums = [1,4,4]
输出:1

示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105

代码1:(前缀和)

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int count=Integer.MAX_VALUE;
        int n=nums.length;
        if(n==0){
            return 0;
        }
        int []sum=new int[n+1];
        for(int i=1;i<n+1;i++){
            sum[i]=sum[i-1]+nums[i-1];
        }
        for(int i=1;i<n+1;i++){
            int s=target+sum[i-1];
            int bound=Arrays.binarySearch(sum,s);
            if(bound<0){
                bound=-bound-1;
            }
            if(bound<=n){
                count=Math.min(count,bound-(i-1));
            }
        }
        return count==Integer.MAX_VALUE?0:count;
    }
}

题解1:

  1. sum[i]用来存储nums[0]到nums[i-1]的前缀和。
  2. 二分查找,找到一个大于等于i的下标bound,使得sum[bound]-sum[i-1]>s(Arrays.binarySearch(sum,s);)
  3. 最小长度为bound-(i-1)
  4. 时间复杂度为O(nlog2n),空间复杂度为O(n)

代码2(弹窗法):

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left=0;
        int min=Integer.MAX_VALUE;
        int total=0;
        for(int right=0;right<nums.length;right++){
            total+=nums[right];
            while(left<=right&&total>=target){
                total-=nums[left];
                min=Math.min(min,right-left+1);
                left++;
            }
        }
        return min==Integer.MAX_VALUE?0:min;
    }
}

题解2:

  1. 如果前缀和total大于等于目标值,最小子数组长度是在之前最小长度和right-left+1之间取最小值,total减去left指向的值,弹窗右移。
  2. 时间复杂度为O(n),空间复杂度为O(1)
剑指 Offer II 009. 乘积小于 K 的子数组

给定一个正整数数组 nums和整数 k ,请找出该数组内乘积小于 k 的连续的子数组的个数。

示例 1:
输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8 个乘积小于 100 的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于100的子数组。

示例 2:
输入: nums = [1,2,3], k = 0
输出: 0

提示:
1 <= nums.length <= 3 * 104
1 <= nums[i] <= 1000
0 <= k <= 106

代码:

class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {
        int left=0;
        int ret=0;
        int total=1;
        for(int right=0;right<nums.length;right++){
            total*=nums[right];
            while(left<=right&&total>=k){
                total/=nums[left];
                left++;
            }
            if(left<=right){
               ret+=(right-left)+1;
            }
        }
        return ret;
    }
}

题解:

  1. 滑窗法,left和right都指向首部,right自加。
  2. 然后算left和right之间的数字相乘,如果total大于目标值,则除于left指向的值,如果没有的话,产生的子数组个数是right-left+1
  3. 时间复杂度为O(n2),空间复杂度为O(1)

2022-1-14

剑指 Offer II 010. 和为 k 的子数组

题目及示例

给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。

示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2
解释: 此题 [1,1] 与 [1,1] 为两种不同的情况

示例 2 :
输入:nums = [1,2,3], k = 3
输出: 2

提示:
1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107

代码:

class Solution {
    public int subarraySum(int[] nums, int k) {
        Map<Integer,Integer> map=new HashMap<>();
        map.put(0,1);
        int sum=0;//前缀和
        int count=0;//记录子数组的个数
        for(int i=0;i<nums.length;i++){
            sum+=nums[i];
            count+=map.getOrDefault(sum-k,0);
            map.put(sum,map.getOrDefault(sum,0)+1);
        }
        return count;
    }
}

题解:

  1. 这一题我本以为可以用弹窗法来解出,但是弹窗法适用于正整数。如果有负数,那么不知道是应该扩充右边,还是缩短左边。
  2. 用map存放前缀和和出现的次数,可以避免重复。
  3. 用sum暂时存放前缀和,map.getOrDefault(sum-k,0);是看map中是否有sum-k值,如果有的话就返回sum-k这个key值对应的value,没有的话则返回0。寻找sum-k其实就是找子数组的起点,寻找k只可以找到那些起始位置为0的子数组。
  4. map.put(sum,map.getOrDefault(sum,0)+1); 如果map中有sum,则在原本sum对应的value值上再加1,如果map没有sum的话,则设置为1。避免的前缀和相同导致的重复。
  5. 遍历一遍数组,时间复杂度为O(n)
  6. 用了map数组,空间复杂度为O(n)
剑指 Offer II 011. 0 和 1 个数相同的子数组

题目及示例

给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

示例 1:
输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。

示例 2:
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。

提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1

代码:

class Solution {
    public int findMaxLength(int[] nums) {
        Map<Integer,Integer> map=new HashMap<>();
        int counter=0;//前缀和
        int max=0;
        map.put(0,-1);
        for(int i=0;i<nums.length;i++){
            if(nums[i]==1){
                counter++;
            }else{
               counter--; 
            }
            if(map.containsKey(counter)){
                int prevIndex=map.get(counter);
                max=Math.max(max,i-prevIndex);
            }else{
                map.put(counter,i);
            }
        }
        return max;  
    }
}

题解:
前缀和+哈希表法

  1. counter暂存前缀和,当元素为1时,counter++;当元素为0时,counter–
  2. 用哈希表存储前缀和的第一次出现的下标。
  3. 如果counter 的值在哈希表中已经存在,则取出 counter 在哈希表中对应的下标 prevIndex,nums 从下标 prevIndex+1 到下标 i的子数组中有相同数量的 0和 1,该子数组的长度为i−prevIndex,使用该子数组的长度更新最长连续子数组的长度;
  4. map.containsKey()判断某key值是否存在,存在则为true,不存在则为false
  5. 如果counter 的值在哈希表中不存在,则将当前counter和当前下标 i的键值对存入哈希表中。
  6. 时间复杂度为O(n),空间复杂度为O(n)
剑指 Offer II 012. 左右两边子数组的和相等

题目及示例

给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。

示例 1:
输入:nums = [1,7,3,6,5,6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。

示例 2:
输入:nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心下标。

示例 3:
输入:nums = [2, 1, -1]
输出:0
解释:
中心下标是 0 。
左侧数之和 sum = 0 ,(下标 0 左侧不存在元素),
右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0 。

提示:
1 <= nums.length <= 104
-1000 <= nums[i] <= 1000

代码:

class Solution {
    public int pivotIndex(int[] nums) {
        int n=nums.length;
        int total=0;
        int sum=0;
        for(int i=0;i<n;i++){
            total+=nums[i];
        }
        for(int i=0;i<n;i++){
            if(2*sum+nums[i]==total){
                return i;
            }
            sum+=nums[i];
        }
        return -1;
    }
}

题解:

  1. 全部元素之和为total,左边元素之和为sum,右边元素之和为total-sum-numi
  2. 左右元素相等即是sum=total-sum-numi,即是2*sum+numi=total
  3. 如果2*sum+numi=total,那么返回numi的下标i
  4. 如果没有中间下标,则返回-1
  5. 时间复杂度为O(n),空间复杂度为O(1)

2022-1-17

剑指 Offer II 013. 二维子矩阵的和

题目及示例

给定一个二维矩阵 matrix,以下类型的多个请求:
计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。
实现 NumMatrix 类:
NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
int sumRegion(int row1, int col1, int row2, int col2) 返回左上角 (row1, col1) 、右下角 (row2, col2) 的子矩阵的元素总和。

示例 1:
输入:
[“NumMatrix”,“sumRegion”,“sumRegion”,“sumRegion”]
[[[[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
输出:
[null, 8, 11, 12]

解释:
NumMatrix numMatrix = new NumMatrix([[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]]);
numMatrix.sumRegion(2, 1, 4, 3); // return 8 (红色矩形框的元素总和)
numMatrix.sumRegion(1, 1, 2, 2); // return 11 (绿色矩形框的元素总和)
numMatrix.sumRegion(1, 2, 2, 4); // return 12 (蓝色矩形框的元素总和)

提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 200
-105 <= matrix[i][j] <= 105
0 <= row1 <= row2 < m
0 <= col1 <= col2 < n
最多调用 104 次 sumRegion 方法
代码:

class NumMatrix {
    int [][]sum;//前缀和
    public NumMatrix(int[][] matrix) {
        int n=matrix.length;
        if(n>0){
            int m=matrix[0].length;
            sum=new int[n][m+1];
            for(int i=0;i<n;i++){
                for(int j=0;j<m;j++){
                   sum[i][j+1]=sum[i][j]+matrix[i][j];
                }  
            }
        }
       
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        int total=0;
        for(int i=row1;i<=row2;i++){
            total+=sum[i][col2+1]-sum[i][col1];
        }
        return total;
    }
}

题解:

  1. 行前缀和,for循环每行的前缀和相加
  2. 行为n,列为m, 时间复杂度为O(mn)
  3. 行为n,列为m, 空间复杂度为O(mn)

2022-1-18

剑指 Offer II 015. 字符串中的所有变位词

题目及示例
给定两个字符串 s 和 p,找到 s 中所有 p 的 变位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
变位词 指字母相同,但排列不同的字符串。

示例 1:
输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的变位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的变位词。

示例 2:
输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的变位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的变位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的变位词。

提示:
1 <= s.length, p.length <= 3 * 104
s 和 p 仅包含小写字母

代码:

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> list=new ArrayList<>();
        int []a=new int[26];
        int []b=new int[26];
        int n=p.length();
        int m=s.length();
        if(n>m){
            return list;
        }
        for(int i=0;i<n;i++){
            a[p.charAt(i)-'a']++;
            b[s.charAt(i)-'a']++; 
        }
        if(Arrays.equals(a,b)){
            list.add(0);
        }
        for(int i=n;i<m;i++){
            b[s.charAt(i)-'a']++;
            b[s.charAt(i-n)-'a']--; 
            if(Arrays.equals(a,b)){
                list.add(i-n+1);
            }
        }
        return list;
    }
}

题解:

  1. 567. 字符串的排列剑指 Offer II 014. 字符串中的变位词一样的解题思路
  2. 只是返回值从true\false变成存储下标的可变数组。
  3. 时间复杂度为O(n+m),空间复杂度为O(n+m)
剑指 Offer II 016. 不含重复字符的最长子字符串

3. 无重复字符的最长子串 一样

2022-1-20

剑指 Offer II 018. 有效的回文

题目及示例

给定一个字符串 s ,验证 s 是否是 回文串 ,只考虑字母和数字字符,可以忽略字母的大小写。
本题中,将空字符串定义为有效的 回文串 。

示例 1:
输入: s = “A man, a plan, a canal: Panama”
输出: true
解释:“amanaplanacanalpanama” 是回文串

示例 2:
输入: s = “race a car”
输出: false
解释:“raceacar” 不是回文串

提示:
1 <= s.length <= 2 * 105
字符串 s 由 ASCII 字符组成

代码:

class Solution {
    public boolean isPalindrome(String s) {
        boolean flag=true;
        List<Character> list=new ArrayList<>();
        String str=s.toLowerCase();
        for(int i=0;i<s.length();i++){
            if((str.charAt(i)>='0'&&str.charAt(i)<='9')||(str.charAt(i)>='a'&&str.charAt(i)<='z'))          {
               list.add(str.charAt(i));
            }
        }
         for(int i = 0;i < list.size()/2; i ++){
           if(list.get(i)!=list.get(list.size()-i-1)){
               flag=false;
               break;
           }
        }
        return flag;
    }
}

题解:

  1. 首先去除非数字和字母的干扰,把大写字母转化成小写字母。然后把字符串转化成字符数组,因为字符个数不确定,所以用了List。
  2. 遍历List,回文字符串首字母和尾字母相等,依次递推,如果有一个不相等,则为false,否则则为true。
  3. 字符串长度为n,可变字符数组长度为m, 时间复杂度为O(m+n),空间复杂度为O(m)

2022-1-21

剑指 Offer II 019. 最多删除一个字符得到回文

题目及示例
给定一个非空字符串 s,请判断如果 最多 从字符串中删除一个字符能否得到一个回文字符串。

示例 1:
输入: s = “aba”
输出: true

示例 2:
输入: s = “abca”
输出: true
解释: 可以删除 “c” 字符 或者 “b” 字符

示例 3:
输入: s = “abc”
输出: false

提示:

1 <= s.length <= 105
s 由小写英文字母组成

代码:

class Solution {
    public boolean validPalindrome(String s) {
        for(int left=0,right=s.length()-1;left<right;left++,right--){
            if(s.charAt(left)!=s.charAt(right)){
                return hui(s,left+1,right)|| hui(s,left,right-1);
            }
        }
        return true;
    }
    //判断字符串s的[left,right]是否回文
    public boolean hui(String s,int left,int right){
        while(left<right){
            if(s.charAt(left++)!=s.charAt(right--)){
                return false;
            }
        }
        return true;
    }

}

题解:

  1. 另一个函数用来判断字符串s的[left,right]是否回文,首字母和最后一个字母比较,然后依次递推。这和回文字符串不同的点在于可以有一次去除字符的机会,所以如果left+1和right对应的字符相等或者left和rigth-1对应的字符相等也是可以的。
  2. 时间复杂度为O(n2),空间复杂度为O(1)

2022-1-22

剑指 Offer II 020. 回文子字符串的个数

题目及实例
给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:
输入:s = “abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”

示例 2:
输入:s = “aaa”
输出:6
解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”

提示:
1 <= s.length <= 1000
s 由小写英文字母组成

代码:

class Solution {
    public int countSubstrings(String s) {
        int n=s.length();
        int total=0;
        for(int i=0;i<n;i++){
            int ans=0;
            int num=i+1;
            while(num<=n){
                // System.out.println(s.substring(i,num));
                // System.out.println(hui(s.substring(i,num)));
                if(hui(s.substring(i,num))){
                    ++ans;
                }
                num++;
            }
            total+=ans;
        }
        return total; 
    }
     //判断字符串s是否回文
    public boolean hui(String s){
        StringBuffer sb = new StringBuffer(s);  
		sb.reverse();//将str倒置的方法
		String s1=new String(sb);
		if(s.equals(s1)){
			return true;
		}
        return false;
    }
}

题解:

  1. 判断回文字符串的方法之一:把字符串反转,如果反转后的字符串和字符串相同,那么是回文字符串,否则不是回文字符串。注意:字符串比较相等要用equals(),用==不行
  2. 然后看子串是否为回文字符串,如果是临时增量加一。注意:临时增量和结束下标在每次for循环的时候,临时增量置0,便于下次计数,结束下标等于其实下标加一。
  3. 时间复杂度为O(n2),空间复杂度为O(1)

2022-1-25

剑指 Offer II 022. 链表中环的入口节点

题目及示例
给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

提示:
链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105
pos 的值为 -1 或者链表中的一个有效索引

代码:

/**
 - Definition for singly-linked list.
 - class ListNode {
 -     int val;
 -     ListNode next;
 -     ListNode(int x) {
 -         val = x;
 -         next = null;
 -     }
 - }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> set=new HashSet<>();
        while(head!=null){
            if(!set.add(head)){
                return head;
            }
            head=head.next;
        }
        return head;
    }
}

题解:

  • 141. 环形链表差不多,用set存放节点,如果有重复的节点,说明此节点为循环的第一个节点。
  • 时间复杂度为O(n),空间复杂度为O(n)
剑指 Offer II 023. 两个链表的第一个重合节点

题目及示例
给定两个单链表的头节点 headA 和 headB ,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。

示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at ‘2’
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

提示:
listA 中节点数目为 m
listB 中节点数目为 n
0 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA 和 listB 没有交点,intersectVal 为 0
如果 listA 和 listB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

代码:

/**
 - Definition for singly-linked list.
 - public class ListNode {
 -     int val;
 -     ListNode next;
 -     ListNode(int x) {
 -         val = x;
 -         next = null;
 -     }
 - }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> set=new HashSet<>();
        ListNode ans=new ListNode();
        while(headA!=null){
            set.add(headA);
            headA=headA.next;
        } 
        while(headB!=null){
            if(!set.add(headB)){
               ans=headB;
               break;
            }
            headB=headB.next;
        }
        return ans.val==0?null:ans;
    }
}

题解:

  • 时间复杂度为O(n),空间复杂度为O(n)

2022-1-26

剑指 Offer II 024. 反转链表
剑指 Offer II 025. 链表中的两数相加

题目及示例
给定两个 非空链表 l1和 l2 来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例1:
输入:l1 = [7,2,4,3], l2 = [5,6,4]
输出:[7,8,0,7]

示例2:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[8,0,7]

示例3:
输入:l1 = [0], l2 = [0]
输出:[0]

提示:
链表的长度范围为 [1, 100]
0 <= node.val <= 9
输入数据保证链表代表的数字无前导 0

进阶:如果输入链表不能修改该如何处理?换句话说,不能对列表中的节点进行翻转。
代码:

/**
 - Definition for singly-linked list.
 - public class ListNode {
 -     int val;
 -     ListNode next;
 -     ListNode() {}
 -     ListNode(int val) { this.val = val; }
 -     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 - }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        l1=reverse(l1);
        l2=reverse(l2);
        int d=0;//进位
        ListNode node=new ListNode(0),next=null;
        while(l1!=null||l2!=null||d!=0){
            int a=l1==null?0:l1.val;
            int b=l2==null?0:l2.val;
            int sum=a+b+d;
            d=sum/10;
            node =new ListNode(sum%10,next);
            next=node;//逆转链表
            l1=l1==null?l1:l1.next;
            l2=l2==null?l2:l2.next;
        }
        return node;
    }
    public ListNode reverse(ListNode head){
        ListNode prev=null;
        ListNode curr=head;
        while(curr!=null){
            ListNode next=curr.next;
            curr.next=prev;
            prev=curr;
            curr=next;
        }
        return prev;
    }
}

题解:

  • 逆转链表,进行相加,设置一个变量作为进位=两个链表对应的数以及进位相加除于10,把对10取余的数加入链表,然后反转链表
  • 时间复杂度为O(n),max(n1,n2),n1 是链表1的元素总数,n2 是链表2的元素总数
  • 空间复杂度为O(n)

2022-1-27

剑指 Offer II 027. 回文链表

题目及示例
给定一个链表的 头节点 head ,请判断其是否为回文链表。
如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。

示例 1:
输入: head = [1,2,3,3,2,1]
输出: true

示例 2:
输入: head = [1,2]
输出: false

提示:
链表 L 的长度范围为 [1, 105]
0 <= node.val <= 9

代码:

/**
 * 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 boolean isPalindrome(ListNode head) {
        ListNode head1=head;
        ListNode index=mid(head);
        ListNode head2=reverse(index.next);
        boolean flag=true;
        while(flag&&head2!=null){
            if(head1.val!=head2.val){
               flag=false;
            }
            head1=head1.next;
            head2=head2.next;
       }
        return flag;
    }
    //找到链表的中心位置
    public ListNode mid(ListNode head){
        ListNode slow=head;
        ListNode fast=head;
        while(fast.next!=null&&fast.next.next!= null){
            fast=fast.next.next;
            slow=slow.next;
        }
        return slow;
    }
    public ListNode reverse(ListNode head){
        ListNode prev=null;
        ListNode curr=head;
        while(curr!=null){
            ListNode next=curr.next;
            curr.next=prev;
            prev=curr;
            curr=next;
        }
        return prev;
    }
}

题解:

  • 步骤:1、找到链表中间元素(便于后半段反转);2、反转后半段;3、比较前半段和后半段是否相等
  • 1、找到链表中间元素(便于后半段反转):利用快慢指针,慢指针走一步,快指针走两步,等快指针走到尽头时,慢指针走到中间位置。
  • 2、反转后半段:用一个结点来存前一个节点,让当前结点指向前一个节点,然后都往前移,前一个节点等于当前结点,当前结点等于下一个节点。
  • 3、比较前半段和后半段是否相等:比较两段的元素是否相等,如果有一个不相等,不是回文链表,否则是回文
  • n是链表的长度,时间复杂度为O(n),空间复杂度为O(n)

2022-1-28

剑指 Offer II 028. 展平多级双向链表

题目及示例
多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。

给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。

示例 1:
输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6]

示例 2:
输入:head = [1,2,null,3]
输出:[1,3,2]
解释:

输入的多级列表如下图所示:

1—2---NULL
|
3—NULL

示例 3:
输入:head = []
输出:[]

如何表示测试用例中的多级链表?

以 示例 1 为例:

1—2---3—4---5—6–NULL
|
7—8---9—10–NULL
|
11–12–NULL
序列化其中的每一级之后:

[1,2,3,4,5,6,null]
[7,8,9,10,null]
[11,12,null]
为了将每一级都序列化到一起,我们需要每一级中添加值为 null 的元素,以表示没有节点连接到上一级的上级节点。

[1,2,3,4,5,6,null]
[null,null,7,8,9,10,null]
[null,11,12,null]
合并所有序列化结果,并去除末尾的 null 。

[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]

提示:
节点数目不超过 1000
1 <= Node.val <= 105

代码:

/*
// Definition for a Node.
class Node {
    public int val;
    public Node prev;
    public Node next;
    public Node child;
};
*/

class Solution {
    public Node flatten(Node head) {
        dfs(head);
        return head;     
    }
    public Node dfs(Node node){
        Node cur=node;
        //记录链表的最后一个节点
        Node last=null;
        while(cur!=null){
            //next表示下一个节点
            Node next=cur.next;
            //如果有子节点,先处理子节点
            if(cur.child!=null){
                //子节点最后一个节点
                Node childLast=dfs(cur.child);
                next=cur.next;
                //将node与child相连
                cur.next=cur.child;
                cur.child.prev=cur;
                //如果next不为空,就将last与next相连
                if(next!=null){
                    childLast.next=next;
                    next.prev=childLast;
                }
                //将child置为空
                cur.child=null;
                //最后一个节点变成子节点的最后一个节点
                last=childLast;
            }else{
                //如果没有子节点,则最后一个节点是当前节点
                last=cur;
            }
            cur=next;
        }
        return last;
    }
}

题解:

  1. 深度搜索,当链表有子链表的话,则把子链表加入到当前节点后面,依次递推。
  2. 具体:把node和node的下一个节点next断开,将node与child相连,将链表的最后一个节点last与next相连。
  3. n 是链表中的节点个数,时间复杂度为O(n),空间复杂度为O(n)。
剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器

题目及示例
设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构:

insert(val):当元素 val 不存在时返回 true ,并向集合中插入该项,否则返回 false 。
remove(val):当元素 val 存在时返回 true ,并从集合中移除该项,否则返回 false 。
getRandom:随机返回现有集合中的一项。每个元素应该有 相同的概率 被返回。

示例 :

输入: inputs = [“RandomizedSet”, “insert”, “remove”, “insert”, “getRandom”, “remove”, “insert”, “getRandom”]
[[], [1], [2], [2], [], [1], [2], []]
输出: [null, true, false, true, 2, true, false, 2]
解释:
RandomizedSet randomSet = new RandomizedSet(); // 初始化一个空的集合
randomSet.insert(1); // 向集合中插入 1 , 返回 true 表示 1 被成功地插入

randomSet.remove(2); // 返回 false,表示集合中不存在 2

randomSet.insert(2); // 向集合中插入 2 返回 true ,集合现在包含 [1,2]

randomSet.getRandom(); // getRandom 应随机返回 1 或 2

randomSet.remove(1); // 从集合中移除 1 返回 true 。集合现在包含 [2]

randomSet.insert(2); // 2 已在集合中,所以返回 false

randomSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2

提示:

-231 <= val <= 231 - 1
最多进行 2 * 105 次 insert , remove 和 getRandom 方法调用
当调用 getRandom 方法时,集合中至少有一个元素

代码:

class RandomizedSet {
    Set<Integer> set=new HashSet<Integer>();
    /** Initialize your data structure here. */
    public RandomizedSet() {

    }
    
    /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
    public boolean insert(int val) {
        if(set.add(val)){
            return true;
        }
        return false;
    }
    
    /** Removes a value from the set. Returns true if the set contained the specified element. */
    public boolean remove(int val) {
        if(set.remove(val)){
            return true;
        }
        return false;
    }
    
    /** Get a random element from the set. */
    public int getRandom() {
        List <Integer> list = new ArrayList<Integer>(set);
        int n=list.size();
        return list.get(new Random().nextInt(n));
    }
}

/**
 - Your RandomizedSet object will be instantiated and called as such:
 - RandomizedSet obj = new RandomizedSet();
 - boolean param_1 = obj.insert(val);
 - boolean param_2 = obj.remove(val);
 - int param_3 = obj.getRandom();
 */

题解:
用set数组,如果有重复数值,则会加入失败;若无指定数值,移除失败。

  • 从set中取出指定位置的元素,set转成list可以解决,用list的get方法
  • 时间复杂度为O(1),空间复杂度为O(n)

三、数据结构

1、 数据结构入门

2022-1-11

217. 存在重复元素

题目及示例

给定一个整数数组,判断是否存在重复元素。
如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。

示例 1:

输入: [1,2,3,1]
输出: true
示例 2:

输入: [1,2,3,4]
输出: false
示例 3:

输入: [1,1,1,3,3,4,3,2,4,2]
输出: true

代码

class Solution {
    public boolean containsDuplicate(int[] nums) {
        boolean flag=false;
        Arrays.sort(nums);
        for(int i=0;i<nums.length-1;i++){
            if(nums[i]==nums[i+1]){
                flag=true;
                break;
            }
        }
        return flag;
    }
}

题解

  1. 找数组中的重复元素可以用双重循环,但是这样的话时间复杂度很大。
  2. 先把数据进行排序,然后判断相邻的数字有没有相等的,判断是否有重复元素。
  3. flag是判断是否有重复元素的标志,初始为false,如果相邻元素相等,则flag=true。
  4. 时间复杂度为O(n),空间复杂度为O(1)。
53. 最大子数组和

题目及示例

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

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:

输入:nums = [1]
输出:1
示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

提示:
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4

代码:

class Solution {
    public int maxSubArray(int[] nums) {
        int []a=new int[nums.length+1];
        a[0]=nums[0];
        for(int i=1;i<nums.length;i++){
            if(a[i-1]+nums[i]>nums[i]){
                a[i]=a[i-1]+nums[i];
            }else{
                a[i]=nums[i];
            }
        }
        int max=a[0];
        for(int i=1;i<nums.length;i++){
           if(a[i]>max){
               max=a[i];
           }
        }
        return max;
    }
}

题解:

  1. 设置一个数组a,用来计算最大子数组和,通过比较前一个a数组值和当前num的值之和与前num的值,谁大就赋值给当前a[i]。
  2. a[i]=a[i-1]+nums[i]>nums[i]?a[i-1]+nums[i]:nums[i]
  3. 找到数组a中的最大值,然后返回。
  4. 时间复杂度为O(n),空间复杂度为O(n)

2022-1-12

1. 两数之和

题目及示例

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

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

提示:
2 <= nums.length <= 1^4
-10^9 <= nums[i] <= 10^9
-10^9 <= target <= 10^9
只会存在一个有效答案

代码:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int a[]=new int[2];
        for(int i=0;i<nums.length-1;i++) {
			for(int j=i+1;j<nums.length;j++) {
				if(nums[i]+nums[j]==target) {
                    a[0]=i;
                    a[1]=j;
					break;
				}
			}
		}
        return a;
    }
}

题解:

  1. 如果两个数之和等于目标值,把那两个数的下标存在数组中。
  2. 时间复杂度为O(n),空间复杂度O(n)
88. 合并两个有序数组

题目及示例

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:

输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:

输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

代码

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        for(int i=m;i<nums1.length;i++){
            nums1[i]=nums2[i-m];
        }
        Arrays.sort(nums1);
    }
}

题解:

  1. 把数组nums2加入到nums1中,然后用Arrays.sort()函数排序
  2. 时间复杂度为O(n),空间复杂度为O(1)

2022-1-13

350. 两个数组的交集 II

题目及示例
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
代码:

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int index1=0,index2=0;
        ArrayList<Integer> list = new ArrayList<Integer>();
        while(index1!=nums1.length&&index2!=nums2.length){
            if(nums1[index1]==nums2[index2]){
                list.add(nums1[index1]);
                index1++;
                index2++;
            }else if(nums1[index1]>nums2[index2]){
                index2++;
            }else if(nums1[index1]<nums2[index2]){
                index1++;
            }
        }
        int[] a = new int[list.size()];
        for(int i = 0;i<list.size();i++){
            a[i] = list.get(i);
        }
        return a;
    }
}

题解:

  1. 双指针,最开始都指向下标0。进行比较如果数组1的值大于数组2,则index2++;如果数组1的值小于数组2,则index1++;
  2. 因为不知道重叠数组的个数不能定义成固定长度的数组,所以定义可变数组ArrayList,再用for循环转换回int数组。
  3. 时间复杂度为O(n),空间复杂度为O(n)
121. 买卖股票的最佳时机

题目及示例

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104
代码:

class Solution {
    public int maxProfit(int[] prices) {
        int []dp=new int[prices.length];
        dp[0]=prices[0];
        int max=0;
        for(int i=1;i<prices.length;i++){
            dp[i]=(dp[i-1]<prices[i])? dp[i-1]:prices[i];
            max=(prices[i]-dp[i])>max? prices[i]-dp[i]:max;
        }
        return max;
    }
}

题解:

  1. 动态规划,dp数组是记录在第i点之前最低点的数值,dp[i]=min(dp[i-1],prices[i]),第i点之前的最小值是第i-1点之前最小值与当前股票值中的最小值。
  2. 最大利润即是当前价格减去当前价格之前的最小值。如果此时刻大于之前的最大值,则代替之前的最大值。
  3. 时间复杂度为O(n),遍历数组;
  4. 空间复杂度为O(n),辅助数组dp。

2022-1-14

118. 杨辉三角

题目及示例

给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。

示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

示例 2:
输入: numRows = 1
输出: [[1]]

提示: 1 <= numRows <= 30

代码:

class Solution {
    public List<List<Integer>> generate(int numRows) {
        List<List<Integer>> list=new ArrayList<List<Integer>>();
        for(int i=0;i<numRows;i++){
             List<Integer> li=new ArrayList<Integer>();
            for(int j=0;j<i+1;j++){
                if(j==0||j==i){
                    li.add(1);
                }else{
                    li.add(list.get(i-1).get(j-1)+list.get(i-1).get(j));
                }
            } 
            list.add(li);
        }
        return list;
    }
}

题解:

  1. List<List< Integer >>用来存储二维可变数组,List< Integer >放的是每一行的一维数组。
  2. 每一行的第一个数和最后一个数都是1,第i行第j列的数字等于第i-1行第j-1列的数字与第i-1行第j列的数字之和。
  3. list的实现类有两个,一个是ArrayList,另一个是LinkedList。这里用的是数组–ArrayList,add()添加值,get()获取某个位置的值。
566. 重塑矩阵

题目及示例

在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。
给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c ,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。
如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

示例 1:
输入:mat = [[1,2],[3,4]], r = 1, c = 4
输出:[[1,2,3,4]]

示例 2:
输入:mat = [[1,2],[3,4]], r = 2, c = 4
输出:[[1,2],[3,4]]

提示:
m == mat.length
n == mat[i].length
1 <= m, n <= 100
-1000 <= mat[i][j] <= 1000
1 <= r, c <= 300

代码:

class Solution {
    public int[][] matrixReshape(int[][] mat, int r, int c) {
        int m=mat.length;
        int n=mat[0].length;
        int []a=new int[(m*n)];
        int [][]b=new int[r][c];
        if(r*c!=m*n){
            return mat;
        }
        int ans=0;
        for(int i=0;i<m;i++){
           for(int j=0;j<n;j++){
               a[ans]=mat[i][j];
               ans++;
            } 
        }
        int k=0;
        for(int i=0;i<r;i++){
           for(int j=0;j<c;j++){
               b[i][j]=a[k];
               k++;
            } 
        }
        return b;
    }
}

题解:

  1. 首先求出考虑是否可以重塑,条件就是看原本矩阵的数字个数和改造矩阵的个数是否相等,即是原本矩阵行数乘于列数是否等于改造矩阵列数乘于列数,不等于则返回原本矩阵,等于则进行下列操作。
  2. 把原本二维矩阵转变成一维矩阵,便于之后进行改变矩阵的取值。
  3. 用for循环来进行重塑。
  4. 时间复杂度为O(n^2), 空间复杂度为O(n^2)

2022-1-17

73. 矩阵置零

题目及示例
给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。

示例 1:
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]

示例 2:
输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]

提示:
m == matrix.length
n == matrix[0].length
1 <= m, n <= 200
-231 <= matrix[i][j] <= 231 - 1

代码:

class Solution {
    public void setZeroes(int[][] matrix) {
        int n=matrix.length;
        int m=matrix[0].length;
        boolean []row=new boolean[n];
        boolean []col=new boolean[m];
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
               if(matrix[i][j]==0){
                   row[i]=col[j]=true;
               }  
            }
        }
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
               if(row[i]||col[j]){
                   matrix[i][j]=0;
               }
            }
        }
    }
}

题解:

  1. 设置两个标志数组标志某行或者某列是否有零,有的话设置为true。
  2. 再次遍历,当标志数组为true时,设置矩阵数组里的数为零。
  3. 时间复杂度为O(nm),空间复杂度为O(m+n)
36. 有效的数独

题目及示例
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

注意:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
空白格用 ‘.’ 表示。

示例 1:
输入:board =
[[“5”,“3”,".",".",“7”,".",".",".","."]
,[“6”,".",".",“1”,“9”,“5”,".",".","."]
,[".",“9”,“8”,".",".",".",".",“6”,"."]
,[“8”,".",".",".",“6”,".",".",".",“3”]
,[“4”,".",".",“8”,".",“3”,".",".",“1”]
,[“7”,".",".",".",“2”,".",".",".",“6”]
,[".",“6”,".",".",".",".",“2”,“8”,"."]
,[".",".",".",“4”,“1”,“9”,".",".",“5”]
,[".",".",".",".",“8”,".",".",“7”,“9”]]
输出:true

示例 2:
输入:board =
[[“8”,“3”,".",".",“7”,".",".",".","."]
,[“6”,".",".",“1”,“9”,“5”,".",".","."]
,[".",“9”,“8”,".",".",".",".",“6”,"."]
,[“8”,".",".",".",“6”,".",".",".",“3”]
,[“4”,".",".",“8”,".",“3”,".",".",“1”]
,[“7”,".",".",".",“2”,".",".",".",“6”]
,[".",“6”,".",".",".",".",“2”,“8”,"."]
,[".",".",".",“4”,“1”,“9”,".",".",“5”]
,[".",".",".",".",“8”,".",".",“7”,“9”]]
输出:false
解释:除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。

提示:
board.length == 9
board[i].length == 9
board[i][j] 是一位数字(1-9)或者 ‘.’

代码:

class Solution {
    public boolean isValidSudoku(char[][] board) {
        int [][]rows=new int[9][9];
        int [][]cols=new int[9][9];
        int [][][]sub=new int[3][3][9];
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                char tmp=board[i][j];
                if(tmp!='.'){
                    int index=tmp-'0'-1;
                    rows[i][index]++;
                    cols[j][index]++;
                    sub[i/3][j/3][index]++;
                    if(rows[i][index]>1||
                    cols[j][index]>1||
                    sub[i/3][j/3][index]>1){
                        return false;
                    }
                }
            }
        }
        return true;
    }
}

题解:

  1. 用rows,cols和sub数组来统计行列和九格里1-9出现的次数。
  2. 如果出现次数大于1,则有重复数字。
  3. 因为矩阵里数字的个数是固定的,所以时间复杂度为O(1),空间复杂度为O(1)

2022-1-18

387. 字符串中的第一个唯一字符

题目及示例
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

示例:
s = “leetcode”
返回 0

s = “loveleetcode”
返回 2

代码:

class Solution {
    public int firstUniqChar(String s) {
        Map<Character,Integer> map=new LinkedHashMap<>();
        for(int i=0;i<s.length();i++){
            map.put(s.charAt(i)
            ,map.getOrDefault(s.charAt(i),0)+1);
        }
        char ans='0';
        //遍历map,通过value值等于1找到key值
        for(Map.Entry<Character,Integer> entry:map.entrySet()){
            char ch=entry.getKey();
            int num=entry.getValue();
            if(num==1){
                ans=ch;
                break;
            }
        }
        for(int i=0;i<s.length();i++){
           if(ans==s.charAt(i)){
               return i;
           }
        }
        return -1;
    }
}

题解:

  1. map记录字符和字符出现的次数,用LinkedHashMap可以按照输入的顺序输出,HashMap是乱序的。
  2. 找到第一个出现次数为1的字符,即是遍历map,获取key和value值,value=1对应的key值
  3. 然后遍历字符串找到此字符的下标。
  4. 遍历map的方法之一:Map.Entry<Character,Integer> entry:map.entrySet()
  5. n表示字符串s的字符个数,时间复杂度为O(n),空间复杂度为O(n)
383. 赎金信

题目及示例
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:
输入:ransomNote = “a”, magazine = “b”
输出:false

示例 2:
输入:ransomNote = “aa”, magazine = “ab”
输出:false

示例 3:
输入:ransomNote = “aa”, magazine = “aab”
输出:true

提示:
1 <= ransomNote.length, magazine.length <= 105
ransomNote 和 magazine 由小写英文字母组成

代码:

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int n=ransomNote.length();
        int m=magazine.length();
        int []a=new int[26];
        if(n>m){
            return false;
        }
        for(int i=0;i<m;i++){
          a[magazine.charAt(i)-'a']++;
        }
        for(int i=0;i<n;i++){
            a[ransomNote.charAt(i)-'a']--;
        }
        for(int i = 0; i < 26; i++) 
        { 
          if(a[i]<0){
              return false;
          } 
        } 
        return true; 
      
    }
}

题解:

  1. 如果ransomNote的长度大于magazine,一定不符合条件
  2. 用int数组来统计字符出现的次数,magazine出现的字符是加一,而ransomNote出现字符是减一,如果int数组出现小于0的数字,则说明ransomNote出现的字符个数大于magazine出现的字符个数,不符合条件
  3. 时间复杂度为O(m+n),其中 m 是字符串 ransomNote 的长度,n是字符串 magazine 的长度
  4. 空间复杂度:O(∣S∣),S 是字符集,这道题中 S 为全部小写英语字母,因此 ∣S∣=26。
242. 有效的字母异位词

383. 赎金信类似
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

示例 1:
输入: s = “anagram”, t = “nagaram”
输出: true

示例 2:
输入: s = “rat”, t = “car”
输出: false

提示:
1 <= s.length, t.length <= 5 * 104
s 和 t 仅包含小写字母

代码:

class Solution {
    public boolean isAnagram(String s, String t) {
        int n=s.length();
        int m=t.length();
        int []a=new int[26];
        if(n!=m){
            return false;
        }
        for(int i=0;i<n;i++){
            a[s.charAt(i)-'a']++;
        }
        for(int i=0;i<m;i++){
            a[t.charAt(i)-'a']--;
        }
        for(int i = 0; i < 26; i++) 
        { 
          if(a[i]!=0){
              return false;
          } 
        } 
        return true; 
    }
}

2022-1-19

141. 环形链表

题目及示例
给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。

示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

提示:
链表中节点的数目范围是 [0, 104]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引 。

代码:

/**
 - Definition for singly-linked list.
 - class ListNode {
 -     int val;
 -     ListNode next;
 -     ListNode(int x) {
 -         val = x;
 -         next = null;
 -     }
 - }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
       Set<ListNode> set=new HashSet<>();
       while(head!=null){
           if(!set.add(head)){
               return true;
           }
           head=head.next;
       }
       return false;
    }
}

题解:

  • 用哈希表来存储链表节点,如果遍历到一个节点,已经存在哈希表中了,则说明链表是环形链表,否则就把该节点加入哈希表。如果已经存在在哈希表中,那么set.add(head)就加入失败,返回false,如果不存在哈希表中,则返回true,加入成功。
  • n是节点数,时间复杂度为O(n),空间复杂度为O(n)
21. 合并两个有序链表

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

示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:
输入:l1 = [], l2 = []
输出:[]

示例 3:
输入:l1 = [], l2 = [0]
输出:[0]

提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列

代码:

/**
 - Definition for singly-linked list.
 - public class ListNode {
 -     int val;
 -     ListNode next;
 -     ListNode() {}
 -     ListNode(int val) { this.val = val; }
 -     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 - }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1==null){
            return list2;
        }else if(list2==null){
            return list1;
        }else if(list1.val<list2.val){
            list1.next=mergeTwoLists(list1.next,list2);
            return list1;
        }else{
            list2.next=mergeTwoLists(list1,list2.next);
            return list2;
        }

    }
}

题解:

  • 递归,如果其中一个链表为空,则返回另一个链表。首先比较一下两个链表的首节点大小,谁小谁作为新链表的首节点。
  • 依次递推,下一个节点从小的那个首节点的下一个节点和另一个链表的结点相比较。
  • 时间复杂度:O(n+m),其中 n 和 m 分别为两个链表的长度
  • 空间复杂度:O(n+m),其中 n 和 m 分别为两个链表的长度
203. 移除链表元素

题目及示例
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

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

示例 2:
输入:head = [], val = 1
输出:[]

示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]

提示:
列表中的节点数目在范围 [0, 104] 内
1 <= Node.val <= 50
0 <= val <= 50

代码:

/**
 * 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 removeElements(ListNode head, int val) {
       if(head==null){
           return head;
       }
       head.next=removeElements(head.next, val);
       return head.val==val?head.next:head;
    }
}

题解:

  • 递归:递归的结束条件、递归循环体。
  • 当结点指向null是结束循环,结点的下一个节点就是循环体,如果首节点是那个值,则首结点是head.next,如果不是,则是首结点head
  • 时间复杂度:O(n),其中 n是链表的长度。递归过程中需要遍历链表一次。
  • 空间复杂度:O(n),其中 n是链表的长度。空间复杂度主要取决于递归调用栈,最多不会超过 n层。

2022-1-20

206. 反转链表

题目及示例
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

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

示例 2:
输入:head = [1,2]
输出:[2,1]

示例 3:
输入:head = []
输出:[]

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

代码:

/**
 - 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 reverseList(ListNode head) {
        ListNode prev=null;
        ListNode curr=head;
        while(curr!=null){
            ListNode next=curr.next;
            curr.next=prev;
            prev=curr;
            curr=next;
        }
        return prev;
    }
}

题解:

  • 迭代法,反转链表,让后一个节点指向前一个结点,需要把前一个结点和当前结点存起来,然后再进行后一个结点指向前一个结点。往后进行,前一个结点变成当前结点,当前结点等候后一个节点,直到当前结点为null。
  • n是链表的长度,时间复杂度为O(n),空间复杂度为O(1)
83. 删除排序链表中的重复元素

题目及示例
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。
返回同样按升序排列的结果链表。

示例 1:
输入:head = [1,1,2]
输出:[1,2]

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

提示:
链表中节点数目在范围 [0, 300] 内
-100 <= Node.val <= 100
题目数据保证链表已经按升序排列

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null){
            return head;
        }
        ListNode cur=head;
        //cur.next==null 说明是链表的最后一个元素
        while(cur.next!=null){
            if(cur.val==cur.next.val){
                cur.next=cur.next.next;
            }else{
                cur=cur.next;
            }
        }
        return head;
    }
}

题解:

  • 如果当前元素和后一个元素相等,则当前元素指向后一个元素的后一个元素,即是curr.next=curr.next.next.如果不相等,向后遍历。
  • 注意循环条件是当前元素的指向不是null,即不是链表最后一个元素。当遍历到最后一个元素的时候循环结束。
  • 链表的长度为n,时间复杂度为O(n),空间复杂度为O(1)

2022-1-21

20. 有效的括号

题目及示例
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

示例 1:
输入:s = “()”
输出:true

示例 2:
输入:s = “()[]{}”
输出:true

示例 3:
输入:s = “(]”
输出:false

示例 4:
输入:s = “([)]”
输出:false

示例 5:
输入:s = “{[]}”
输出:true

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

代码:

class Solution {
    public boolean isValid(String s) {
       if(s.isEmpty()){
           return true;
       }
       Stack<Character> stack=new Stack<Character>();
       for(char c:s.toCharArray()){
           if(c=='('){
               stack.push(')');
           }else if(c=='['){
               stack.push(']');
           }else if(c=='{'){
               stack.push('}');
           }else if(stack.empty()||c!=stack.pop()){
               return false;
           }
       }
       if(stack.empty()){
           return true;
       }
       return false;
    }
}

题解:

  1. 栈,当遇到左括号的时候把对应的右括号入栈,当遇到右括号把栈顶元素出栈进行匹配,如果相等则继续,如果不相等则说明括号不匹配,当没到最后一个元素栈就空了,后面的括号没法匹配了,说明括号不匹配。
  2. 如果最后匹配上了,栈就空了,说明括号匹配。如果空字符串则一定匹配。
  3. n为字符串的长度,时间复杂度为O(n),空间复杂度为O(n)
232. 用栈实现队列

题目及示例
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:
你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

进阶:
你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。

示例:
输入:
[“MyQueue”, “push”, “push”, “peek”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

提示:
1 <= x <= 9
最多调用 100 次 push、pop、peek 和 empty
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)

代码:

class MyQueue {
    private int front;
    private Stack<Integer> s1 = new Stack<>();
    private Stack<Integer> s2 = new Stack<>();
    public MyQueue() {

    }
    
    public void push(int x) {
        if(s1.empty()){
            front=x;
        }
        while(!s1.isEmpty()){
            s2.push(s1.pop());
        }
        s2.push(x);
        while(!s2.isEmpty()){
            s1.push(s2.pop());
        }
        
    }
    
    public int pop() {
        int p=s1.peek();
        s1.pop();
        if(!s1.empty()){
            front=s1.peek();
        }
        return p;
    }
    
    public int peek() {
        return front;
    }
    
    public boolean empty() {
        return s1.isEmpty();
    }
}

/**
 - Your MyQueue object will be instantiated and called as such:
 - MyQueue obj = new MyQueue();
 - obj.push(x);
 - int param_2 = obj.pop();
 - int param_3 = obj.peek();
 - boolean param_4 = obj.empty();
 */

题解:

  • 用两个栈实现队列,栈是“先进后出”,队列是“先进先出”。熔
  • 进栈push: 先把栈1里的元素出栈放入栈2中,再把元素放进栈2,然后再把栈2里的元素放进栈1,这样就可以让后放进的元素排在后面,实现先进先出。
  • 出栈pop:先获取栈顶元素方便返回。然后栈1出栈,让首元素移除,那么首元素变成了现在的栈顶元素。
  • 栈顶元素peek:在函数外部定义全局变量front,front指向栈顶。
  • 是否为空 empty:用栈的原始方法–isEmpty()
  • 栈内元素个数为n,for循环,时间复杂度为O(n)
  • 用了两个栈,空间复杂度为O(2n)=O(n)

2022-1-22

144. 二叉树的前序遍历

题目及示例
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]

示例 2:
输入:root = []
输出:[]

示例 3:
输入:root = [1]
输出:[1]

示例 4:
输入:root = [1,2]
输出:[1,2]

示例 5:
输入:root = [1,null,2]
输出:[1,2]

提示:
树中节点数目在范围 [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> preorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<Integer>();
        preorder(root,list);
        return list;
    }
    public void preorder(TreeNode root,List<Integer> list){
        if(root==null){
            return;
        }
        list.add(root.val);
        preorder(root.left,list);
        preorder(root.right,list);
    }
}
94. 二叉树的中序遍历

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

示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]

示例 2:
输入:root = []
输出:[]

示例 3:
输入:root = [1]
输出:[1]

示例 4:
输入:root = [1,2]
输出:[2,1]

示例 5:
输入:root = [1,null,2]
输出:[1,2]

提示:
树中节点数目在范围 [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> inorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<Integer>();
        inorder(root,list);
        return list;
    }
     public void inorder(TreeNode root,List<Integer> list){
        if(root==null){
            return;
        }
        inorder(root.left,list);
        list.add(root.val);
        inorder(root.right,list);
    }
}
145. 二叉树的后序遍历

题目及示例
给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。

示例 1:
输入:root = [1,null,2,3]
输出:[3,2,1]

示例 2:
输入:root = []
输出:[]

示例 3:
输入:root = [1]
输出:[1]

提示:
树中节点的数目在范围 [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> postorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<Integer>();
        postorder(root,list);
        return list;
    }
     public void postorder(TreeNode root,List<Integer> list){
        if(root==null){
            return;
        }
        postorder(root.left,list);
        
        postorder(root.right,list);
        list.add(root.val);
    }
}

题解三合一

  • 前序遍历是根结点->左结点–>右结点;中序遍历是左结点->根结点–>右结点;后序遍历是左结点–>右结点–>根结点(记忆方法:是什么序遍历,根j点)

2022-1-24

102. 二叉树的层序遍历*

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

示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

示例 2:
输入:root = [1]
输出:[[1]]

示例 3:
输入:root = []
输出:[]

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

代码:

/**
 - 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<List<Integer>>();
        if(root==null){
            return list;
        }
        Queue<TreeNode> q=new LinkedList<TreeNode>();
        q.offer(root);//进队列
        while(!q.isEmpty()){
           List<Integer> li=new ArrayList<Integer>();
           int n=q.size();//获取队列长度
           for(int i=0;i<n;i++){
               TreeNode node=q.poll();//出队列
               li.add(node.val);
               if(node.left!=null){
                   q.offer(node.left);//进队列
               }
               if(node.right!=null){
                   q.offer(node.right);//进队列
               }
           }
           list.add(li);
        }
        return list;
    }
   
}

题解:

  • 广度优先搜索(队列)
  • 根节点入队列,然后遍历队列出队列,打印节点值。把节点的左节点、右节点加入队列,继续上述步骤。
  • 每个点进队出队各一次,时间复杂度为O(n)
  • 队列中的元素个数不超过n个,空间复杂度为O(n)
104. 二叉树的最大深度

题目及示例
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
返回它的最大深度 3 。

代码:

class Solution {
    public int maxDepth(TreeNode root) {
        if(root==null){
            return 0;
        }else{
            int l=maxDepth(root.left); 
            int r=maxDepth(root.right);
            return Math.max(l,r)+1;
        }
    }
}

题解:

  1. 深度优先搜索(递归),二叉树的高度等于左子树和右子树高度的最大值加一。
  2. 时间复杂度:O(n),其中 n 为二叉树节点的个数。每个节点在递归中只被遍历一次。
  3. 空间复杂度:O(height),其中height 表示二叉树的高度。递归函数需要栈空间,而栈空间取决于递归的深度,因此空间复杂度等价于二叉树的高度。
101. 对称二叉树

题目及示例
给你一个二叉树的根节点 root , 检查它是否轴对称。

示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true

示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false

提示:
树中节点数目在范围 [1, 1000] 内
-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 boolean isSymmetric(TreeNode root) {
        return check(root,root);
    }
    public boolean check(TreeNode p,TreeNode q){
        //如果只有root为空,则一定对称
        if(p==null&&q==null){
            return true;
        }
        //如果有一边是null,一边有值,那么一定不对称
        if(p==null||q==null){
            return false;
        }
        return p.val==q.val&&check(p.right,q.left)&&check(p.left,q.right);
    }
}

题解:

  • 比较左子树和右子树的镜像,如果相同,那么则对称,否则不对称。实现方式:设置两个指针,当一个指针指向左边时另一个指针指向右边,比较值是否相等,深度优先遍历
  • 二叉树结点数为n,时间复杂度为O(n),空间复杂度为O(n)

2、数据结构基础

2022-1-25

136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:
输入: [2,2,1]
输出: 1

示例 2:
输入: [4,1,2,1,2]
输出: 4

代码:

class Solution {
    public int singleNumber(int[] nums) {
        int ans=0;
        for(int i=0;i<nums.length;i++){
            ans^=nums[i];
        }
        return ans;
    }
}

题解:

  1. 异或,可以把重复的数字变成0,只出现一次的是1
  2. 时间复杂度为O(n),空间复杂度为O(1)
169. 多数元素

题目及示例
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:
输入:[3,2,3]
输出:3

示例 2:
输入:[2,2,1,1,1,2,2]
输出:2

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

代码:

class Solution {
    public int majorityElement(int[] nums) {
        int n=nums.length;
        int num=0;
        Map<Integer,Integer> map=new HashMap<Integer,Integer>();
        for(int i=0;i<n;i++){
            map.put(nums[i],map.getOrDefault(nums[i],0)+1);
        }
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){
            int key=entry.getKey();
            int value=entry.getValue();
            if(value>n/2){
                num=key;
                break;
            }
        }
        return num;
    }
}

题解:

  1. 用map来记录数字和数字出现的次数
  2. 时间复杂度为O(n),空间复杂度为O(n)

2022-1-26

75. 颜色分类

题目及示例
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库的sort函数的情况下解决这个问题。

示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]

提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2

进阶:
你可以不使用代码库中的排序函数来解决这道题吗?
你能想出一个仅使用常数空间的一趟扫描算法吗?
代码

class Solution {
    public void sortColors(int[] nums) {
        int n=nums.length;
        //冒泡排序
        for(int i=0;i<n-1;i++){
            for(int j=i+1;j<n;j++){
                if(nums[j]<nums[i]){
                    int t=nums[j];
                    nums[j]=nums[i];
                    nums[i]=t;
                }
            }
        }
    }
}

题解:

  1. 这道题的本质是排序,有很多种排序,冒牌,快排等
  2. 时间复杂度为O(n2),空间复杂度为O(1)
706. 设计哈希映射

题目及示例

不使用任何内建的哈希表库设计一个哈希映射(HashMap)。

实现 MyHashMap 类:

MyHashMap() 用空映射初始化对象
void put(int key, int value) 向 HashMap 插入一个键值对 (key, value) 。如果 key 已经存在于映射中,则更新其对应的值 value 。
int get(int key) 返回特定的 key 所映射的 value ;如果映射中不包含 key 的映射,返回 -1 。
void remove(key) 如果映射中存在 key 的映射,则移除 key 和它所对应的 value 。

示例:
输入:
[“MyHashMap”, “put”, “put”, “get”, “get”, “put”, “get”, “remove”, “get”]
[[], [1, 1], [2, 2], [1], [3], [2, 1], [2], [2], [2]]
输出:
[null, null, null, 1, -1, null, 1, null, -1]

解释:
MyHashMap myHashMap = new MyHashMap();
myHashMap.put(1, 1); // myHashMap 现在为 [[1,1]]
myHashMap.put(2, 2); // myHashMap 现在为 [[1,1], [2,2]]
myHashMap.get(1); // 返回 1 ,myHashMap 现在为 [[1,1], [2,2]]
myHashMap.get(3); // 返回 -1(未找到),myHashMap 现在为 [[1,1], [2,2]]
myHashMap.put(2, 1); // myHashMap 现在为 [[1,1], [2,1]](更新已有的值)
myHashMap.get(2); // 返回 1 ,myHashMap 现在为 [[1,1], [2,1]]
myHashMap.remove(2); // 删除键为 2 的数据,myHashMap 现在为 [[1,1]]
myHashMap.get(2); // 返回 -1(未找到),myHashMap 现在为 [[1,1]]

提示:
0 <= key, value <= 106
最多调用 104次 put、get 和 remove 方法

代码:

class MyHashMap {
    private class Pair{
        private int key;
        private int value;
        public Pair(int key,int value){
            this.key=key;
            this.value=value;
        }
        public int getKey(){
            return key;
        }
        public void setKey(int key){
            this.key=key;
        }
        public int getValue(){
            return value;
        }
        public void setValue(int value){
            this.value=value;
        }
    }
    private static final int BASE=769;
    private LinkedList []data;
    public MyHashMap() {
        data=new LinkedList[BASE]; 
        for(int i=0;i<BASE;i++){
            data[i]=new LinkedList<>();
        }
    }
    
    public void put(int key, int value) {
        int h=hash(key);
        Iterator<Pair> iterator =data[h].iterator();
        while(iterator.hasNext()){
            Pair pair =iterator.next();
            if(pair.getKey()==key){
                pair.setValue(value);
                return;
            }
        }
        data[h].offerLast(new Pair(key,value));
    }
    
    public int get(int key) {
        int h=hash(key);
        Iterator<Pair> iterator =data[h].iterator();
        while(iterator.hasNext()){
            Pair pair =iterator.next();
            if(pair.getKey()==key){
                return pair.value;
            }
        }
        return  -1; 
    }
    
    public void remove(int key) {
        int h=hash(key);
        Iterator<Pair> iterator =data[h].iterator();
        while(iterator.hasNext()){
            Pair pair =iterator.next();
            if(pair.getKey()==key){
                data[h].remove(pair);
                return;
            }
        }
    }
    private static int hash(int key){
        return key%BASE;
    }
}

/**
 - Your MyHashMap object will be instantiated and called as such:
 - MyHashMap obj = new MyHashMap();
 - obj.put(key,value);
 - int param_2 = obj.get(key);
 - obj.remove(key);
 */

题解:

  • 首先定义一个类,里面初始化和get、set方法,LinkedList存储放入的key\value数组
  • put方法:迭代器遍历,如果和已有的key相等,设置值为valuemr,如果没有的话把新的key和value都加入到LinkedList数组里
  • get方法:迭代器遍历,如果和已有的key相等,返回对应的值value
  • remove方法:迭代器遍历,如果和已有的key相等,就移除对应的值key和value
  • 迭代器: Iterator< Pair > iterator =data[h].iterator();hasNext()判断是否有下一个数; iterator .next()是指定的下一个值
  • 时间复杂度:O( b/n)。其中 n 为哈希表中的元素数量,b为链表的数量。假设哈希值是均匀分布的,则每个链表大概长度为b/n
  • 空间复杂度:O(n+b)。

2022-1-27

119. 杨辉三角 II

给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。

示例 1:
输入: rowIndex = 3
输出: [1,3,3,1]

示例 2:
输入: rowIndex = 0
输出: [1]

示例 3:
输入: rowIndex = 1
输出: [1,1]

提示:
0 <= rowIndex <= 33

代码:

class Solution {
    public List<Integer> getRow(int rowIndex) {
        List<List<Integer>> list=new ArrayList<List<Integer>>();
        for(int i=0;i<=rowIndex;i++){
            List<Integer> li=new ArrayList<>();
            for(int j=0;j<i+1;j++){
                if(j==0||j==i){
                    li.add(1);
                }else{
                    li.add(list.get(i-1).get(j-1)+list.get(i-1).get(j));
                }
            }
            list.add(li);
        }
        return list.get(rowIndex);
    }
}

题解:

  1. 杨辉三角,每行第一个元素和对后一个元素都是1,一个元素等于上一行对应列的元素加上上一行前一列元素。
  2. 时间复杂度为O(rowIndex2 ),空间复杂度为O(n)
48. 旋转图像

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]

示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

提示:

n == matrix.length == matrix[i].length
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000
代码:

class Solution {
    public void rotate(int[][] matrix) {
        int n=matrix.length;
        int [][] ans=new int [n][n];
        //第i行第j列的-->倒数第i(n-1-i)列,第j行
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                ans[j][n-1-i]=matrix[i][j];
            }
        }
        //再赋值给原数组
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                matrix[i][j]=ans[i][j];
            }
        }

    }
}

题解

  1. 用辅助数组,第i行第j列的–>倒数第i(n-1-i)列,第j行。再把辅助数组赋值给原数组。
  2. 时间复杂度为O(n2),空间复杂度为O(n2

2022-2-7

240. 搜索二维矩阵 II

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

示例 1:

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true
示例 2:

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false

提示:

m == matrix.length
n == matrix[i].length
1 <= n, m <= 300
-109 <= matrix[i][j] <= 109
每行的所有元素从左到右升序排列
每列的所有元素从上到下升序排列
-109 <= target <= 109
代码:

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        for(int i=0;i<matrix.length;i++){
            int left=0;
            int right=matrix[i].length-1;
           while(left<=right){
                int mid=(left+right)/2;
                if(matrix[i][mid]==target){
                    return true;
                }else if(matrix[i][mid]<target){
                    left=mid+1;
                }else{
                   right=mid-1; 
                }
            }
        }
        return false;
    }
}

题解;

  1. 二分查找,因为从左到右是升序排列的.
  2. 时间复杂度为O(nlogn),空间复杂度为O(1).
435. 无重叠区间

题目及示例
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

注意:

可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:

输入: [ [1,2], [2,3], [3,4], [1,3] ]

输出: 1

解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2:

输入: [ [1,2], [1,2], [1,2] ]

输出: 2

解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:

输入: [ [1,2], [2,3] ]

输出: 0

解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

代码:

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
       if(intervals.length==0){
           return 0;
       }

       Arrays.sort(intervals,new Comparator<int[]>(){
           public int compare(int [] interval1,int []interval2){
           	//如果返回值>0交换,即是前面的数字大于后面的数字,则交换,进行升序排列
               return interval1[1]-interval2[1];
           }
       });
       int n=intervals.length;
       int right=intervals[0][1];
       int ans=1;
       for(int i=0;i<n;i++){
           //此区间左端点如果和上一个区间右端点不重合,那么不移出区间多一个,右端点变成此区间的右端点
           if(intervals[i][0]>=right){
               ans++;
               right=intervals[i][1];
           }
       }
       return n-ans;
    }
}

题解:

  1. 贪心法, Arrays.sort(intervals,new Comparator<int[]>(){})中new Comparator<int[]>可以自定义排序,如果返回值>0交换,即是前面的数字大于后面的数字,则交换,进行升序排列
  2. 然后进行贪心选择,如果此区间左端点如果和上一个区间右端点不重合,那么不移出区间多一个,右端点变成此区间的右端点。
  3. 时间复杂度为O(nlogn),升序需要nlogn时间
  4. 空间复杂度为O(logn),排序需要使用的栈空间

四、动态规划

2022-1-11

509. 斐波那契数

题目和实例

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你 n ,请计算 F(n) 。

示例 1:

输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:

输入:3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

代码:

class Solution {
    public int fib(int n) {
        if(n==0){
            return 0;
        }
        if(n==1){
            return 1;
        }
        return fib(n-1)+fib(n-2);
    }
}

题解:

  1. 根据递归方法,F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1
  2. 时间复杂度为O(2^n),空间复杂度为O(1)
1137. 第 N 个泰波那契数

题目及示例

泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。

示例 1:

输入:n = 4
输出:4
解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4

示例 2:

输入:n = 25
输出:1389537

错误代码(超时)

class Solution {
    public int tribonacci(int n) {
        if(n==0){
            return 0;
        }
        if(n==1||n==2){
            return 1;
        }
        return tribonacci(n-1)+tribonacci(n-2)+tribonacci(n-3);
    }
}

代码:

class Solution {
    public int tribonacci(int n) {
        if(n==0){
            return 0;
        }
        if(n==1||n==2){
            return 1;
        }
        int p=0,q=0,r=1,s=1;
        for(int i=3;i<=n;i++){
           p=q;
           q=r;
           r=s;
           s=p+q+r;
        }
        return s;
    }
}

题解

  1. 泰波那契序列如果按照斐波那契数列的递归方法来会导致超时。
  2. 所以此题需要动态的进行推进,p为Tn,q代表Tn+1,r为Tn+2,s为Tn+3,Tn+3 = Tn + Tn+1 + Tn+2 ,而Tn\Tn+1\ Tn+2都是在递推变化的。
  3. 时间复杂度为O(n),空间复杂度为O(1)。

2022-1-12

70. 爬楼梯

题目及示例
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。

示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
3. 1 阶 + 1 阶 + 1 阶
4. 1 阶 + 2 阶
5. 2 阶 + 1 阶
代码

class Solution {
    public int climbStairs(int n) {
        int p=0,q=0,s=1;
        for(int i=1;i<=n;i++){
            p=q;
            q=s;
            s=p+q;
        }
        return s;
    }
}

题解:

  1. 上3层楼梯,上1阶,需要考虑后面两阶怎么走;上2阶,需要考虑后面一阶怎么走。递推下去,上n层楼梯,上1阶,需要考虑后面(n-1)阶怎么走;上2阶,需要考虑后面(n-2)阶怎么走。
  2. f(n)=f(n-1)+f(n-2) n>=3
  3. 直接返回f(n-1)+f(n-2),会超时;用for循环来求f(n)
  4. 时间复杂度:循环执行 n次,O(n)。
  5. 空间复杂度:O(1)。
746. 使用最小花费爬楼梯

题目及示例
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。

示例 1:
输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。

  • 支付 15 ,向上爬两个台阶,到达楼梯顶部。
    总花费为 15 。

示例 2:
输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。

  • 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
  • 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
  • 支付 1 ,向上爬一个台阶,到达楼梯顶部。
    总花费为 6 。

提示:
2 <= cost.length <= 1000
0 <= cost[i] <= 999

代码1

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n=cost.length;
        int []dp=new int[n+1];
        dp[0]=0;
        dp[1]=0;
        for(int i=2;i<=n;i++){
            dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[n];
    }
}

代码2(优化)

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n=cost.length;
        int prev=0,curr=0;
        for(int i=2;i<=n;i++){
            int next=Math.min(curr+cost[i-1],prev+cost[i-2]);
            prev=curr;
            curr=next;
        }
        return curr;
    }
}

题解

  1. dp数组记录登到第i阶台阶需要最小的花费。
  2. 题中支付费用后即可选择向上爬一个或者两个台阶,
  3. 爬一个台阶的话,dp[i-1]+cost[i-1];
  4. 爬二个台阶的话,dp[i-2]+cost[i-2];
  5. 取其中的最小值 :dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
  6. 代码2优化了,空间复杂度减小。prev表示前一个值,curr表示当前值,next表示后一个值。
  7. 代码1:时间复杂度:O(n) 空间复杂度:O(n)
  8. 代码2:时间复杂度:O(n) 空间复杂度:O(1)

2022-1-13

198. 打家劫舍

题目及示例

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400

代码:

class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        if(n==0){
            return 0;
        }
        if(n==1){
           return nums[0]; 
        }
        int [] dp=new int[nums.length];
        dp[0]=nums[0];
        dp[1]=Math.max(nums[0],nums[1]);
       
        for(int i=2;i<n;i++){
           dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[n-1];
    }
}

题解:

  1. dp数组记录到第i房子的最大盗取金额
  2. 如果偷盗第i间,则不能偷盗第i-1间,偷盗金额是前i-2间的最高金额加上当前屋子的金额
  3. 如果不偷盗第i间,则偷窃总金额为前 i-1 间房屋的最高总金额
  4. 时间复杂度为O(n),空间复杂度为O(n)
213. 打家劫舍 II

题目及示例

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:
输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:
输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:
输入:nums = [0]
输出:0

提示:
1 <= nums.length <= 100
0 <= nums[i] <= 1000

代码

class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        if(n==0){
            return 0;
        }
        if(n==1){
           return nums[0]; 
        }
        if(n==2){
           return Math.max(nums[0],nums[1]); 
        }
        return Math.max(robRange(0,n-2,nums),robRange(1,n-1,nums));
    }
    public int robRange(int start,int end,int nums[]){
        int first=nums[start];
        int second=Math.max(nums[start],nums[start+1]);
        for(int i=start+2;i<=end;i++){
            int tmp=Math.max(second,first+nums[i]);
            first=second;
            second=tmp;
        }
        return second;
    }
}

题解

  1. 此题与198.打家劫舍的不同点在于,最后一间房屋是与第一间房屋挨着。所以如果我们选择第一间就不选择最后一间,选择最后一间就不选择第一间了。
  2. 有两种区间,一种是【0,n-2】,另一种是【1,n-1】。
  3. 时间复杂度为O(n),空间复杂度为O(1)

2022-1-14

740. 删除并获得点数

题目及示例
给你一个整数数组 nums ,你可以对它进行一些操作。

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。

开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

示例 1:

输入:nums = [3,4,2]
输出:6
解释:
删除 4 获得 4 个点数,因此 3 也被删除。
之后,删除 2 获得 2 个点数。总共获得 6 个点数。
示例 2:

输入:nums = [2,2,3,3,3,4]
输出:9
解释:
删除 3 获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。

提示:

1 <= nums.length <= 2 * 10^4
1 <= nums[i] <= 10^4

代码:

class Solution {
    public int deleteAndEarn(int[] nums) {
        int max=0;
        for(int val:nums){
            max=Math.max(max,val);
        }
        int []sum=new int[max+1];
        for(int val:nums){
            sum[val]+=val;
        }
        return rob(sum);
    }
    public int rob(int[] nums) {
        int first=nums[0];
        int second=Math.max(nums[0],nums[1]);
        for(int i=2;i<nums.length;i++){
            int tmp=second;
            second=Math.max(first+nums[i],second);
            first=tmp;
        }
        return second;
    }
}

题解:

  1. 求出nums数组的最大值,即是sum数组的长度,sum数组存着对应数的值之和,然后进行动态规划。
  2. 时间复杂度:O(N+M),其中 N 是数组nums 的长度,M是 nums 中元素的最大值。
  3. 空间复杂度:O(M)。
55. 跳跃游戏

题目及示例

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。

示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

提示:
1 <= nums.length <= 3 * 10^4
0 <= nums[i] <= 10^5
代码:

class Solution {
    public boolean canJump(int[] nums) {
        int n=nums.length;
        int max=nums[0];
        if(n==1){
            return true;
        }
        for(int i=1;i<n;i++){
            if(i<=max){
                max=Math.max(max,i+nums[i]);
                if(max>=n-1){
                    return true;
                }
            }
        }
        return false;
    }
}

题解:

  1. 每走一步都计算一下最大区间是多少,最大区间是前一个最大区间值和当前小标和当前下标的值之和的最大值。如果最大区间大于等于最后一个值的下标,则可以到达,否则不可以到达。
  2. 时间复杂度为O(n),空间复杂度为O(1)
45. 跳跃游戏 II

题目及示例

给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。

示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:
输入: nums = [2,3,0,1,4]
输出: 2

提示:
1 <= nums.length <= 104
0 <= nums[i] <= 1000
代码:

class Solution {
    public int jump(int[] nums) {
        int p=nums.length-1;
        int count=0;
        while(p>0){
            for(int i=0;i<p;i++){
                if(i+nums[i]>=p){
                    p=i;
                    count++;
                    break;
                }
            }
        }
        return count;
    }
}

题解:

  1. 55.跳跃游戏不同的是这题的贪心策略是从后向前,找最远的可以到达最后一个值的位置,然后依次向前推。
  2. 两个for循环,时间复杂度为O(n)。
  3. 空间复杂度为O(1)

2022-1-17

53. 最大子数组和

题目及示例

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

示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:
输入:nums = [1]
输出:1

示例 3:
输入:nums = [5,4,-1,7,8]
输出:23

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

代码:

class Solution {
    public int maxSubArray(int[] nums) {
        int []a=new int[nums.length+1];
        a[0]=nums[0];
        for(int i=1;i<nums.length;i++){
            if(a[i-1]+nums[i]>nums[i]){
                a[i]=a[i-1]+nums[i];
            }else{
                a[i]=nums[i];
            }
        }
        int max=a[0];
        for(int i=1;i<nums.length;i++){
           if(a[i]>max){
               max=a[i];
           }
        }
        return max;
    }
}

题解:

  1. a数组用来存储最大和的。如果前面的和与当前的nums对应值加一起大于当前的nums对应值,即前面的和是大于0的,则后一个和等于前面的和加上当前的nums对应值,否则后一个和等于当前的nums对应值。
  2. 然后求出a数组的最大值,最大值即是最大和。
  3. 时间复杂度为O(n),空间复杂度为O(n)

2022-1-18

152. 乘积最大子数组

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

示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

代码:

class Solution {
    public int maxProduct(int[] nums) {
       int max=nums[0],min=nums[0],ans=nums[0];
       int n=nums.length;
       for(int i=1;i<n;i++){
           int mx=max,mn=min;
           max=Math.max(mx*nums[i],Math.max(mn*nums[i],nums[i]));
           min=Math.min(mn*nums[i],Math.min(mx*nums[i],nums[i]));
           ans=Math.max(ans,max);
       }
       return ans;
    }
}

题解:

  1. 这题和最大和的连续子数组的解法不同之处在于没有最优子结构,局部最优解不能导致全局最优解。
  2. 最大乘积,当前值是正数,希望前面的乘积是个较大的正数;当前值是负数,希望前面的乘积是一个较小的负数。所以最大值\最小值在最大值乘于当前值、最小值乘于当前值和当前值之间选。
  3. 时间复杂度为O(n),空间复杂度为O(1)

2022-1-19

1014. 最佳观光组合

题目及示例
给你一个正整数数组 values,其中 values[i] 表示第 i 个观光景点的评分,并且两个景点 i 和 j 之间的 距离 为 j - i。

一对景点(i < j)组成的观光组合的得分为 values[i] + values[j] + i - j ,也就是景点的评分之和 减去 它们两者之间的距离。

返回一对观光景点能取得的最高分。

示例 1:
输入:values = [8,1,5,2,6]
输出:11
解释:i = 0, j = 2, values[i] + values[j] + i - j = 8 + 5 + 0 - 2 = 11

示例 2:
输入:values = [1,2]
输出:2

提示:
2 <= values.length <= 5 * 104
1 <= values[i] <= 1000

代码:

class Solution {
    public int maxScoreSightseeingPair(int[] values) {
        int n=values.length;
        int ans=0;
        int max=values[0]+0;
        for(int j=1;j<n;j++){
            ans=Math.max(ans,max+values[j]-j);
            max=Math.max(max,values[j]+j);
        }
        return ans;
    }
}

题解:

  1. 动态规划,最大值初始化为第一个数的值和下标,然后减去后面的值减去下标。最大值更新为当前值和下标。
  2. 时间复杂度为O(n),空间复杂度为O(1)

2022-1-20

309. 最佳买卖股票时机含冷冻期

题目及示例
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

代码1:

class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        if(n==0){
            return 0;
        }
        //f[i][0]:手中持有股票的最大收益
        //f[i][1]:手中不持有股票,并且处于冷冻期内
        //f[i][3]:手中不持有股票,并且不处于冰冻期内
        int [][]f=new int[n][3];
        f[0][0]=-prices[0];//手中持有股票收益是第一天收购股票的价格负值
        for(int i=1;i<n;i++){
            //手中持有股票:1、前一天可以是持有股票,2、也可以是不持有股票不处于冷冻期.当天买入
            f[i][0]=Math.max(f[i-1][0],f[i-1][2]-prices[i]);
            //手中不持有股票,并且处于冷冻期内:1、当天卖出,前一天持有股票 ,获得当天卖出股票的钱
            f[i][1]=f[i-1][0]+prices[i];
            //手中不持有股票,并且不处于冷冻期内:1、前一天不持有股票不处于冷冻期 2、前一天不持有股票处于冷冻期
            f[i][2]=Math.max(f[i-1][2],f[i-1][1]);
        }
        //最后一天还没有出售掉股票是没有意义的
        return Math.max(f[n-1][1],f[n-1][2]);
    }
}

代码2(空间优化):

题解:

  1. 分析三种情况,然后取各自情况的最大值。最后取后两种情况的最大值。
  2. 代码1:时间复杂度为O(n),空间复杂度为O(n)
  3. 代码2:时间复杂度为O(n),空间复杂度为O(1)

2022-1-21

139. 单词拆分*

题目及示例
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以由 “leet” 和 “code” 拼接成。

示例 2:
输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以由 “apple” “pen” “apple” 拼接成。
注意,你可以重复使用字典中的单词。

示例 3:
输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false

提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s 和 wordDict[i] 仅有小写英文字母组成
wordDict 中的所有字符串 互不相同
代码:

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> wordDictSet=new HashSet(wordDict);
        boolean []dp=new boolean[s.length()+1];
        dp[0]=true;
        for(int i=1;i<=s.length();i++){
            for(int j=0;j<i;j++){
                if(dp[j]&&wordDictSet.contains(s.substring(j,i))){
                    dp[i]=true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}

题解:

  1. 不太懂,时间复杂度为O(n2),空间复杂度为O(n)

2022-1-22

413. 等差数列划分

tim
如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。

例如,[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。
给你一个整数数组 nums ,返回数组 nums 中所有为等差数组的 子数组 个数。

子数组 是数组中的一个连续序列。

示例 1:

输入:nums = [1,2,3,4]
输出:3
解释:nums 中有三个子等差数组:[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] 自身。
示例 2:

输入:nums = [1]
输出:0

提示:

1 <= nums.length <= 5000
-1000 <= nums[i] <= 1000

错误代码

class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        int n=nums.length;
        int total=0;
        if(n<3){
            return 0;
        }
        Map<Integer,Integer> map=new HashMap<Integer,Integer>();
        for(int i=0;i<n-1;i++){
            map.put(nums[i+1]-nums[i],map.getOrDefault(nums[i+1]-nums[i],0)+1);
        }
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){
            int num=entry.getValue();
            total+=jie(num-1);
            System.out.println(num);
        }
        return total;
    }
    public int jie(int num){
        // if(num==1){
        //     return 1;
        // }
        // return num+jie(num-1);
        int total=0;
        for(int i=1;i<=num;i++){
            total+=num;
        }
        return total;
    }
}

正确代码:

class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        int n=nums.length;
        int total=0;
        if(n<3){
            return total;
        }
        int d=nums[0]-nums[1];
        int t=0;
        for(int i=2;i<n;i++){
            if(nums[i-1]-nums[i]==d){
                ++t;
            }else{
                d=nums[i-1]-nums[i];
                t=0;
            }
            total+=t;
        }
        return total;
    }
}

题解:

  1. 错误代码错在没有考虑前面等差序列和后面等差序列的差值是一样,但是中间又断开了,所以思路是错误的。
  2. 正确代码,因为子数组必须三个及其以上,所以下标从2开始。
  3. 如果等于差值则加一,如果不等于则新值等于差值,且增值为0,重新计算。
  4. 时间复杂度为O(n),空间复杂度为O(1)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值