华为3.23机考

https://leetcode-cn.com/circle/discuss/G4h52E/

1、魔力台阶 100分

科科最近在修炼魔法,一日他来到魔法城堡,城堡里有一个长长的台阶,而台阶的最终点便是魔法奥秘。
这是一个魔力台阶,每个台阶都有一个魔力值,魔力值代表下一步科科最大可以跨越的台阶数。科科当前处在第1级台阶上,但是科科的体力有限,最多只能跨越K次。科科现在拜托你帮他计算下他能否拿到魔法奥秘。
如果能够拿到返回最少跨越的次数,拿不到则返回-1。

解答要求
时间限制:C/C++ 1000ms,其他语言:2000ms
内存限制:C/C++ 256MB,其他语言:512MB

输入
台阶长度n (1<=n<=10^5)
台阶魔力值,[M1, M2..... Mn]由一个长度为n的数组表示,代表1~n级台阶的魔力值。(0<=Mi<=10^5)
最大的跨越次数K(1<=k<=10^5)

输出
输出一个整数,拿到魔法奥秘最少需要跨越的次数,如果拿不到,返回-1

public class tiaoyueyouxi {
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int [] nums=new int[n];
        for (int i = 0; i <n ; i++) {
            nums[i]=sc.nextInt();
        }
        int k=sc.nextInt();
        if(jump(nums)<=k){
            System.out.println(jump(nums));
        }else System.out.println("-1");
        int [] nums=new int[]{2,3,1,1,0,1};
        jump(nums);
    }
    //输入5 [2 3 1 1 4]
    public static int jump(int[] nums){
            int length = nums.length;
            //每次在上次能跳到的范围(end)内选择一个能跳的最远的位置(也就是能跳到max_position位置的点)作为下次的起跳点 !
            int end = 0;// // 上次跳跃可达范围右边界(下次的最右起跳点),下次起跳的范围
            int maxPosition = 0;//// 目前能跳到的最远位置
            int steps = 0;
            for (int i = 0; i < length - 1; i++) {
                maxPosition = Math.max(maxPosition, i + nums[i]);
                if (i == end) {
                    end = maxPosition;//// 目前能跳到的最远位置变成了下次起跳位置的有边界
                    steps++;
                }
            }
            return steps;
    }
}

2 、TLV匹配 200分

描述:两端通过TLV格式的报文来通信,现在收到对端的一个TLV格式的消息包,要求生成匹配后的(tag, length, valueOffset)列表。具体要求如下:
(1)消息包中多组tag、length、value紧密排列,其中tag.length各占1字节(uint8_t) , value所占字节数等于length的值
(2)结果数组中tag值已知,需要填充每个tag对应数据的length和valueOffset值(valueOffset为value在原消息包中的起始偏移量(从0开始,以字节为单位)),即将消息包中的tag与结果数组中的tag进行匹配(可能存在匹配失败的情况,若结果数组中的tag在消息包中找不到,则length和valueOffset都为0)
(3)消息包和结果数组中的tag值都按升序排列,且不重复
(4)此消息包未被篡改,但尾部可能不完整,不完整的一组TLV请丢弃掉

解答要求
时间限制:C/C++ 1000ms,其他语言:2000ms内存限制:C/C++ 32MB,其他语言:64MB

输入
第一行: 一个字符串,代表收到的消息包。字符串长度在10000以内。
说明1: 字符串使用十六进制文本格式(字母为大写)来展示消息包的数据,如OF04ABABABAB代表一组TLV:前两个字符(0F)代表tag值为15,接下来两个字符(04)代表length值为4字节,接下来8个字符即为4字节的value。
说明2: 输入字符串中,每一组TLV紧密排列,中间无空格等分隔符
第二行: 需要匹配的tag数量n (0 < n <1000) 。
后面n行: 需要匹配的n个tag值(十进制表示),递增排列

输出
和需要匹配的n个tag对应的n行匹配结果,每一行由长度和偏移量组成。

样例1

输入:
0F04ABABABAB
1
15

输出:
4 2

解释:
tag15(十六进制0F)对应数据的长度为4,其value从第三个字节开始,因此偏移量为2

样例2

输入:
0F04ABABABAB1001FF
2
15
17

输出:
4 2
0 0

解释:
第二个tag匹配失败

 如OF04ABABABAB代表一组TLV:前两个字符(0F)代表tag值为15,接下来两个字符(04)代表length值为4字节,接下来8个字符即为4字节的value。
说明2: 输入字符串中,每一组TLV紧密排列,中间无空格等分隔符
第二行: 需要匹配的tag数量n (0 < n <1000) 。
后面n行: 需要匹配的n个tag值(十进制表示),递增排列

import java.util.Scanner;

public class TLV匹配 {
    public static int toDigit(char ch, int index) {
        int digit = Character.digit(ch, 16);
        if (digit == -1) {
            throw new RuntimeException("非法字符 " + ch
                    + " 索引下标为 " + index);
        }
        return digit;
    }
    public static byte[] decodeHex(char[] data) {
        int len = data.length;
        if ((len & 0x01) != 0) {
            throw new RuntimeException("数组长度应该为偶数");
        }
        byte[] out = new byte[len >> 1];
        for (int i = 0, j = 0; j < len; i++) {
            int f = toDigit(data[j], j) << 4;
            j++;
            f = f | toDigit(data[j], j);
            j++;
            out[i] = (byte) (f & 0xFF);
        }

        return out;
    }
    public static void TVL(String src, int num, int[] tags) {
        byte[] bytes = decodeHex((src).toCharArray());
        for (int i = 0; i < bytes.length; i++) {
            for (int tag : tags) {
                // 寻找能匹配到的tag
                if (bytes[i] == tag) {
                    // 判断是否是tag,如果是则进行
                    i++;

                    int len = bytes[i];
                    System.out.println(len + " " + (i + 1));
                    i += len;
                }
            }
            // 如果都匹配不到则跳过该长度
            System.out.println(0 + " " + 0);
            i++;
            i += bytes[i];
        }
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String src = scanner.next();
        int num = scanner.nextInt();
        int[] tags = new int[num];
        for (int i =0; i < num; i++) {
            tags[i] = scanner.nextInt();
        }
        TVL(src,num,tags);
    }
}

3、安排面试官 300分

有M个面试官,每个面试官熟悉的编程语言是一个列表,比如["Java", "C++", "Golang"]表示该面试官熟悉Java、C++和Golang三种语言。
有N个面试者,按照面试者的机试选择的语言分配面试官进行面试。现在给定每个面试官最多面试x个人,每个面试者必须被不同的面试官面试2次,请问能否给出一个匹配使得所有的面试者都能被面试到。

解答要求
时间限制:C/C++ 3000ms,其他语言:6000ms内存限制:C/C++ 256MB,其他语言:512MB

输入
第一行输入3个整数,M,N,x。其中0<=M<=100,0<=N<=1000
然后是M行,输入M个面试官熟悉的语言列表,多个语言之间以空格分割,语言字符串本身不存在空格
然后是N行,输入N个面试者的机试所使用的语言。
输出
第一行输出字符串true或者false,表示能否匹配。
如果能匹配,从第二行起输出一个二维数组match,如果第i个面试官面试第j个面试者,match[i][j]== 1,否则match[i][j]==0。二维数组元素之间采用单个空格分割。如果有多种匹配,只需要输出其中一种正确的匹配即可

样例1

输入:
4 6 4
Java C++ Python
Python
C++ Java
Python
Java
Python
C++
Python
C++
Java

输出:
true
1 0 1 0 1 1
0 1 0 1 0 0
1 0 1 0 1 1
0 1 0 1 0 0

import java.util.*;

//一个标准的用于笔试的输入
public class Main {
    static int x,M,N;
    static int[][] match;
    public static void main(String[] args){
        System.out.println("输入");
        Scanner sc = new Scanner(System.in);
        //int M = 0, N= 0,x = 0;
        M = sc.nextInt();
        N = sc.nextInt();
        x = sc.nextInt();
        sc.nextLine();//跳到第二行非常重要
        //String[] officer = new String[M];
        List<String[]> tech = new ArrayList<String[]>();
        for(int i = 0; i<M; i++){
            String[] single = sc.nextLine().split(" ");
            tech.add(single);
        }
        String[] candidates = new String[N];
        for(int i = 0; i<N; i++){
            //String[] single = sc.nextLine().split(" ");
            candidates[i] = sc.nextLine();
        }



        int[] dp_m = new int[M];//记录面试官面了几个人
        int[] dp_n = new int[N];//记录求职者被面了几次
        Arrays.fill(dp_m,0);
        Arrays.fill(dp_n,0);
        match = new int[M][N];
        for(int i = 0; i < M;i++){
            Arrays.fill(match[i],0);
        }
        boolean ans = DFS(dp_m,dp_n,tech,candidates,match,0,0);
        System.out.println(ans);
        for(int i=0;i<match.length;i++){
            for(int j=0;j<match[0].length;j++){
                System.out.print(match[i][j]);
            }
            System.out.println();
        }
}
    private static boolean DFS(int[] dp_m,int[] dp_n,List<String[]> tech,String[] candidates,int[][] match,int row,int col){
        //结束条件:到最后一个人结束时,求职者都被面了两次,面试官没有超过x次
        if(row == M){
            for(int i = 0; i<N; i++){
                if(dp_n[i] != 2){
                    return false;
                }
            }
            return true;
        }
        List<String> techboss = new ArrayList<String>(Arrays.asList(tech.get(row)));//把每个技术官的技能转换为ArrayList列表
        String jobseeker = candidates[col];
        if(techboss.contains(jobseeker)){//技能匹配
            //查看是否有名额
            if(dp_m[row] < x && dp_n[col] < 2){
                match[row][col] = 1;
                dp_m[row]++;
                dp_n[col]++;
                boolean res = false;
                if(col == N-1){
                    res = DFS(dp_m,dp_n,tech,candidates,match,row+1,0);
                }else {
                    res = DFS(dp_m,dp_n,tech,candidates,match,row,col+1);
                }
                if(res) return true;
                //如果不成功则不配对
                match[row][col] = 0;
                dp_m[row]--;
                dp_n[col]--;
            }
        }

        if(col == N-1){//如果是最后一列则换行
            return DFS(dp_m,dp_n,tech,candidates,match,row+1,0);
        }
        return DFS(dp_m,dp_n,tech,candidates,match,row,col+1);
    }


}

字节5.6

1.

小明在玩一款游戏,每次击杀怪物后都能获得一个增益buff,buff可以持续duration秒。假设小明在第n秒击杀怪物,那么在时间区间[n,n+duration-1]处于buff状态(包含n和n+duration-1),第n秒表示buff的开始时间,n+duration-1表示buff的结束时间)

特别的如果在buff结束前再次击杀怪物,buff计时会重置,在新击杀后,buff将会在duration秒后结束。

求小明可获得的buff总持续时间

输入三行:第一行整数n表示整数数组timeseries和duration的长度

第二行为整数数组timeseries,数字之后用空格分割,timeseries[i]表示小明在timeseries[i]秒击杀了一个怪物

第三行为一个整数数组durations,数字之间用空格分隔,durations[i]表示小明第i次击杀获得buff的持续时间。

image-20220509210855622

timeseries[i]表示小明在timeseries[i]秒击杀了一个怪物,durations[i]表示小明第i次击杀获得buff的持续时间。击杀怪物后buff持续时间小于下次击杀的时间,直接把dura当作res,击杀怪物后buff持续时间大于下次击杀的时间,持续时间就是下一次击杀减去这一次击杀
import java.util.*;
public class Main{
    public static void main(String args[]){
    Scanner in=new Scanner(System.in);
    int len=in.nextInt();
    int [] time=new int [len];
    int [] dura=new int [len];
    	for(int i=0;i<len;i++){
          time[i]=in.nextInt();
   	     }
        for(int i=0;i<len;i++){
          dura[i]=in.nextInt();
       } 
    int res=0;
    for(int i=0;i<len-1;i++){
    time[i]+=dura[i];
    if(time[i]<=time[i+1]){
    res+=dura[i];//击杀怪物后buff持续时间小于下次击杀的时间,直接把dura当作res
    }
    else{
        time[i]-=dura[i];
        res+=time[i+1]-time[i];//击杀怪物后buff持续时间大于下次击杀的时间,持续时间就是下一次击杀减去这一次击杀
    }
    }
    if(len>=2) {
    if(time[len-2]<=time[len-1]) res+=dura[len-1];
    else res +=time[len-1]-time[len-2];
                System.out.println(res);
    }
        else{
             System.out.println(dura[0]);
        }

    }
}

2.

image-20220509211020033

image-20220510152512365

import java.util.*;
import java.io.*;

public class Main{
    static class Node{
    Node [] next;
    boolean end;
    Node(){
     next=new Node[10];
     end=false;
    }
    }
      public static void main(String args[])  {
        Scanner sc=new Scanner(System.in);
        //BufferedReader reader =new BufferedReader(new InputStreamReader(System.in));
        int N=sc.nextInt();
        //int N=Integer.parseInt(reader.readLine());
        for(int i=0;i<N;i++){
            int T=sc.nextInt();
            //int T=Integer.parseInt(reader.readLine());

            String [] keep=new String[T];
            for(int j=0;j<T;j++){
                keep[j]=sc.next();
                //keep[j]=reader.readLine();
            }
            M(keep);
        }
    }
    public static void M (String[] keep){
    Node root=new Node();
    for(String s:keep){
     boolean res=add(root,s);
     if(res){
         System.out.println("YES");
         return;
     }
    }
      System.out.println("NO");
    }   
    private static boolean add(Node node,String s){
        int len=s.length();
        boolean find=true;
        for(int i=0;i<len;i++){
            int ind=s.charAt(i)-'0';
            if(node.next[ind]==null){
                node.next[ind]=new Node();
                find=false;
            }
            node=node.next[ind];
            if(node.end==true) return true;
        }
        node.end=true;
        return find;
    }
}

3.

image-20220509211158770


import java.util.*;
public class Main{
    public static void main(String args[]){
    Scanner sc=new Scanner(System.in);
    int num=sc.nextInt();//任务数目
    int has=sc.nextInt();//可支配总时间
    int [][]  keep=new int [2][2];
    int sum=0;
    for(int i=0;i<num;i++){
        int cost=sc.nextInt();//花费的时间
        int get=sc.nextInt();//任务价值
        if(cost>has) break;
        int s1=((get>keep[0][1] && cost+keep[1][0]<=has)?get+keep[1][1]:0);
        int s2=((get>keep[1][1] && cost+keep[0][0]<=has)?get+keep[0][1]:0);
        if(s1>=s2&&s1>sum){
            keep[0][0]=cost;
            keep[0][1]=get;
            sum=s1;
        }
        else if(s2>s1 && s2>sum){
             keep[1][0]=cost;
            keep[1][1]=get;
            sum=s2;
        }
             
    }   
             System.out.println(sum);
    }
  
}

https://blog.csdn.net/weixin_43260719/article/details/119514033

二分查找

二分查找的流程:

1.确定二分的边界

2.编写二分的代码框架

3.设计一个check性质

4.判断一下区间如何更新

5.如果更新方式写的是l=mid,r=mid-1,那么就在算mid的时候+1

二分的模板:   bool check(int x) {/* ... */} // 检查x是否满足某种性质
//求第二个线段的起点
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用://求第一个线段的终点
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

35. 搜索插入位置

image-20211029112746388

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

69. Sqrt(x)

image-20211029112903011

class Solution {
    public int mySqrt(int x) {
        int l=0; int r=x;
        while(l<r){
            int mid=l+r>>1;
            if(mid<=x/mid) r=mid;
            else l=mid+1;
        }
        return l;
    }
}

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

image-20211029113005134

class Solution {
    public int[] searchRange(int[] nums, int target) {
        if(nums.length==0) return new int[]{-1, -1};
      //先找出初始位置,nums[mid]>=target,也就是第二条线段的起点,用模板一
      int l=0,r=nums.length-1;
      while(l<r){
          int mid=l+r>>1;
          if(nums[mid]>=target) r=mid;
          else l=mid+1;
      }
      if(nums[r]!=target) return new int[]{-1, -1};
      //再找出终止位置,nums[mid]<=target,也就是第一条线段的终点,用模板二
      int l1=0,r1=nums.length-1;
      int start=l;
      while(l1<r1){
          int mid=l1+r1+1/2;
          if(nums[mid]<=target) l1=mid;
          else r1=mid-1;
      }
      int end=r1;
      return new int[]{start,end};
}
}

74. 搜索二维矩阵

image-20211029113208686

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        //t>=target,
        if(matrix.length==0||matrix[0].length==0) return false;
        int n=matrix.length;
        int m=matrix[0].length;
        int l=0;int r=m*n-1;
        while(l<r){
            int mid=l+r>>1;
            if(matrix[mid/m][mid%m]>=target) r=mid;
            else l=mid+1;
        }
        return matrix[r/m][r%m]==target;
    }
}

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

image-20211029113304097

class Solution {
    public int findMin(int[] nums) {
        int l=0,r=nums.length-1;
        //一个y=x的函数切成两半,找到第二段线段的起点,用模板一
        while(l<r){
            int mid=l+r>>1;
            if(nums[mid]<=nums[nums.length-1]) r=mid;
            else l=mid+1; 
        }
        return nums[l];
    }
}

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

在题目153的基础上,数组的值可以重复
class Solution {
    public int findMin(int[] nums) {
        int left = 0, right = nums.length - 1;
        while (left < right) {
            int mid = (left + right) / 2;
            if (nums[mid] > nums[right]) left = mid + 1;
            else if (nums[mid] < nums[right]) right = mid;
            else right = right - 1;
        }
        return nums[left];
    }
}

 

278. 第一个错误的版本

image-20211029113437677

/* 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 l=0,r=n;
        //找第二个线段的起点,用模板一
        while(l<r){
            int mid=l+(r-l)/2;//防止溢出
            if(isBadVersion(mid)==true) r=mid;
            else l=mid+1;
        }
        return l;
    }
}

33. 搜索旋转排序数组

image-20211029113551911

class Solution {
    public int search(int[] nums, int target) {
   //先确定在旋转后的函数的哪段,再对这一段进行二分
          int n=nums.length;
          int l=0;
          int r=n-1;
          if(n==0) return -1;
          //找第二个线段的起点
         while(l<r){
             int mid=l+r>>1;
             if(nums[mid]<=nums[n-1]) r=mid;
             else l=mid+1;
         }
         if(target<=nums[n-1]) r=n-1;
         else {l=0; r--;}
         while(l<r){
             int mid=l+r>>1;
             if(nums[mid]>=target) r=mid;
             else l=mid+1;
         }
         if(nums[l]==target) return l;
         return -1;
         
    }
}

374. 猜数字大小

猜数字游戏的规则如下:
每轮游戏,我都会从 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
示例 3:

输入:n = 2, pick = 1
输出:1
public class Solution extends GuessGame {
    public int guessNumber(int n) {
    int l=0;
    int r=n;
     while(l<r){
        int mid=l+(r-l)/2;
        if(guess(mid)<=0) r=mid;//mid>=n,取第二个线段的起点
        else l=mid+1;
    }
    return l;
}
}

275. H 指数 II

image-20211029113852718

class Solution {
    public int hIndex(int[] citations) {
        int n=citations.length;
        int l=0;
        int r=n ;
        while(l<r){
            int mid=l+r+1>>1;
            if(citations[n-mid]>=mid) l=mid;
            else r=mid-1;
        }
        return r;
    }
}

DFS和回溯

深度遍历: 第一步:明确递归参数 第二步:明确递归截止条件 第三步:明确递归函数中的内容 第四步:明确回溯返回值

广度遍历: 第一步:设置队列,添加初始节点 第二步:判断队列是否为空 第三步:迭代操作 弹出队列元素,进行逻辑处理 当前队列元素的下级元素,入队 第四步:在此执行步骤三

17. 电话号码的字母组合

image-20211030152853855

0 <= digits.length <= 4

image-20211030170706498

//先定义一个char型的二维数组,char[0]与char[1]为0;
定义一个res存放最终的结果
dfs方法,定义str(传入的“23”),index表示str当前的字符,StringBuilder是为了拼接结果,res存放最终的结果
    //1.截止条件  当前的index等于str.length(),那么直接传入结果,并return
    //2.候选节点,选择哪个字母,需要注意m[str.charAt(index)-'0'],‘2’ascll码为50,'0'的ascll码为48
    //所以需要减去一个'0',这样就是在m[2]中{'a','b','c'}进行搜索
	加个a,dfe,在delete,加个b,dfs,再delete
class Solution {
    char [][]m=new char[][]{
        {},
        {},{'a','b','c'},{'d','e','f'},
        {'g','h','i'},{'j','k','l'},{'m','n','o'},
        {'p','q','r','s'},{'t','u','v'},{'w','x','y','z'}
    };
    public List<String> letterCombinations(String str) {
        List<String> res=new ArrayList<>();//存放最终结果
        if(str.length()==0) return res;//边界条件
        dfs(str,0,new StringBuilder(),res);
        return res;
    }
    //index 当前到了字符串的下标,Stringbulider是为了拼接结果的
    //res存放最后的stringbuilder结果,stringbuilder要用tostring转化
    void dfs(String str,int index,StringBuilder sb,List<String> res){
        //1.截止条件
        if(index==str.length()){
            res.add(sb.toString());
            return;
        }
        //候选节点,哪个字母*************重点
        for(char c:m[str.charAt(index)-'0']){
            sb.append(c);
            dfs(str,index+1,sb,res);
            sb.deleteCharAt(sb.length()-1);
        }
    }
}
相同思路:
    package leetcode._17;

import java.util.ArrayList;
import java.util.List;

public class Solution17_1 {

    private String letterMap[] = {
            " ",    //0
            "",     //1
            "abc",  //2
            "def",  //3
            "ghi",  //4
            "jkl",  //5
            "mno",  //6
            "pqrs", //7
            "tuv",  //8
            "wxyz"  //9
    };

    private ArrayList<String> res;

    public List<String> letterCombinations(String digits) {

        res = new ArrayList<String>();
        if(digits.equals(""))
            return res;

        findCombination(digits, 0, "");
        return res;
    }

    private void findCombination(String digits, int index, String s){

        if(index == digits.length()){
            res.add(s);
            return;
        }

        Character c = digits.charAt(index);
        String letters = letterMap[c - '0'];
        for(int i = 0 ; i < letters.length() ; i ++){
            findCombination(digits, index+1, s + letters.charAt(i));
        }

        return;
    }

}

首先使用哈希表存储每个数字对应的所有可能的字母,然后进行回溯操作。

回溯过程中维护一个字符串,表示已有的字母排列(如果未遍历完电话号码的所有数字,则已有的字母排列是不完整的)。该字符串初始为空。每次取电话号码的一位数字,从哈希表中获得该数字对应的所有可能的字母,并将其中的一个字母插入到已有的字母排列后面,然后继续处理电话号码的后一位数字,直到处理完电话号码中的所有数字,即得到一个完整的字母排列。然后进行回退操作,遍历其余的字母排列。

回溯算法用于寻找所有的可行解,如果发现一个解不可行,则会舍弃不可行的解。在这道题中,由于每个数字对应的每个字母都可能进入字母组合,因此不存在不可行的解,直接穷举所有的解即可。
class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> combinations = new ArrayList<String>();
        if (digits.length() == 0) {
            return combinations;
        }
        Map<Character, String> phoneMap = new HashMap<Character, String>() {{
            put('2', "abc");
            put('3', "def");
            put('4', "ghi");
            put('5', "jkl");
            put('6', "mno");
            put('7', "pqrs");
            put('8', "tuv");
            put('9', "wxyz");
        }};
        backtrack(combinations, phoneMap, digits, 0, new StringBuffer());
        return combinations;
    }

    public void backtrack(List<String> combinations, Map<Character, String> phoneMap, String digits, int index, StringBuffer combination) {
        if (index == digits.length()) {
            combinations.add(combination.toString());
        } else {
            char digit = digits.charAt(index);
            String letters = phoneMap.get(digit);
            int lettersCount = letters.length();
            for (int i = 0; i < lettersCount; i++) {
                combination.append(letters.charAt(i));
                backtrack(combinations, phoneMap, digits, index + 1, combination);
                combination.deleteCharAt(index);
            }
        }
    }
}

39. 组合总和

image-20211030183159061

image-20211030183759772

解法1,需要优化,只是为了便于理解:
    class Solution {
    public List<List<Integer>> combinationSum(int[] p, int t) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();
      dfs(p,t,new ArrayList(),res);
        return res;
    }

    public void dfs(int []p,int t,List<Integer> chain,List<List<Integer>> res){
        int s=sum(chain);
        //截止条件
        if(s>=t){
            if(s==t){
                List<Integer> temp=new ArrayList<>(chain);//这里为了去重
                Collections.sort(temp);
                if(!res.contains(temp)){
                    res.add(temp);
                }
            }
            return;
        }
        //候选节点
        for(int i=0;i<p.length;i++){
            int c=p[i];
            chain.add(c);
            dfs(p,t,chain,res);
            chain.remove(chain.size()-1);
        }
        
    }
    int sum(List<Integer> chain){
        int res=0;
        for(int i:chain)
            res+=i;
        return res;
    }
}
官方解法:
https://leetcode-cn.com/problems/combination-sum/solution/zu-he-zong-he-by-leetcode-solution/

public class Solution {

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        int len = candidates.length;
        List<List<Integer>> res = new ArrayList<>();
        if (len == 0) {
            return res;
        }

        Deque<Integer> path = new ArrayDeque<>();
        dfs(candidates, 0, len, target, path, res);
        return res;
    }

    /**
     * @param candidates 候选数组
     * @param begin      搜索起点
     * @param len        冗余变量,是 candidates 里的属性,可以不传
     * @param target     每减去一个元素,目标值变小
     * @param path       从根结点到叶子结点的路径,是一个栈
     * @param res        结果集列表
     */
    private void dfs(int[] candidates, int begin, int len, int target, Deque<Integer> path, List<List<Integer>> res) {
        // target 为负数和 0 的时候不再产生新的孩子结点
        if (target < 0) {
            return;
        }
        if (target == 0) {
            res.add(new ArrayList<>(path));
            return;
        }

        // 重点理解这里从 begin 开始搜索的语意
        for (int i = begin; i < len; i++) {
            path.addLast(candidates[i]);

            // 注意:由于每一个元素可以重复使用,下一轮搜索的起点依然是 i,这里非常容易弄错
            dfs(candidates, i, len, target - candidates[i], path, res);

            // 状态重置
            path.removeLast();
        }
    }
}

46. 全排列

image-20211030190012400

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res =new ArrayList<List<Integer>>();
        boolean[] pb=new boolean[nums.length];
        dfs(nums,pb,new ArrayList<Integer>(),res);
        return res;
    }
    public void dfs(int[] nums,boolean[]pb,List<Integer> chain,List<List<Integer>> res){
       //截止条件
       if(chain.size()==nums.length){
           res.add(new ArrayList(chain));
           return;
       }
       //候选节点
       for(int i=0;i<nums.length;i++){
           int c=nums[i];
           //pb[i]==false的时候认为没用过,初始值为false
           if(!pb[i]){
               chain.add(c);
               pb[i]=true;
               dfs(nums,pb,chain,res);
               chain.remove(chain.size()-1);
               pb[i]=false;
           }
       }
    }
}
官方解法:
class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res =new ArrayList<List<Integer>>();
        List<Integer> output=new ArrayList<Integer>();
        for(int num:nums){
            output.add(num);
        }
        int n=nums.length;
        backtrack(n,output,res,0);
        return res;
    }
    public void backtrack(int n,List<Integer> output,List<List<Integer>> res,int first){
        if(first==n) res.add(new ArrayList<Integer>(output));
        for(int i =first;i<n;i++){
            Collections.swap(output,first,i);
            backtrack(n,output,res,first+1);
            Collections.swap(output,first,i);
        }
    }
}

47. 全排列 II

image-20211031140839816

//思路与46类似,唯一不同的是需要先对数组进行排序,然后对前后数是否相同来进行判断和回溯,pb可以优化为全局变量,也可以不优化。
class Solution {
boolean[] pb;
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> res =new ArrayList<List<Integer>>();
        pb=new boolean[nums.length];
        Arrays.sort(nums);
        dfs(nums,new ArrayList<Integer>(),res,0);
        return res;
    }
    public void dfs(int[] nums,List<Integer> chain,List<List<Integer>> res,int index){
       //截止条件
       if(index==nums.length){
           res.add(new ArrayList(chain));
           return;
       }
       //候选节点
       for(int i=0;i<nums.length;i++){
           int c=nums[i];
           if(pb[i] || (i > 0 && nums[i] == nums[i - 1] && !pb[i - 1])){
               continue;
           }
               chain.add(c);
               pb[i]=true;
               dfs(nums,chain,res,index+1);
               chain.remove(index);
               pb[i]=false;
           }
       }
}

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

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

示例 2:

输入:nums = [0]
输出:[[],[0]]
class Solution {
     List<List<Integer>> res=new ArrayList<List<Integer>>();
     List<Integer> list=new ArrayList<Integer>();
    public List<List<Integer>> subsets(int[] nums) {
        res.add(new ArrayList<Integer>());
         dfs(nums,0);
         return res;
    }
    public void dfs(int [] nums,int start){
        if(start>=nums.length) return;
        for(int i=start;i<nums.length;i++){
            list.add(nums[i]);
            res.add(new ArrayList<Integer>(list));
            dfs(nums,i+1);
            list.remove(list.size()-1);
        }
    }
}

90. 子集 II

class Solution {
      List<List<Integer>> res=new ArrayList<List<Integer>>();
     List<Integer> list=new ArrayList<Integer>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if(nums==null||nums.length==0) return res;
        Arrays.sort(nums);
        res.add(new ArrayList<Integer>());
        dfs(nums,0);
        return res;
    }
    public void dfs(int [] nums,int start){
        if(start>=nums.length) return;
        for(int i=start;i<nums.length;i++){
            if(i>start&&nums[i]==nums[i-1]) continue;
            list.add(nums[i]);
            res.add(new ArrayList<Integer>(list));
            dfs(nums,i+1);
            list.remove(list.size()-1);
        }
    }
}

22. 括号生成

image-20211031143841491

class Solution {
    public List<String> generateParenthesis(int n) {
        char [] p=new char[]{'(',')'};
        int []pb={n,n};
        List<String> res=new ArrayList<String>();
        dfs(n,pb,p,"",res);
        return res;
    }
    public void dfs(int n,int[]pb,char[] p,String str,List<String> res){
        //截止条件
        if(str.length()==2*n){
            res.add(str);
            return;
        }
        //候选节点,用str的时候不用删除,因为str+p[0]是创建了一个新空间,原来的str还是没有变
        if(pb[0]>0){
            pb[0]--;
            dfs(n,pb,p,str+p[0],res);
            pb[0]++;
        }
        if(pb[1]>0&&pb[0]!=pb[1]){
            pb[1]--;
            dfs(n,pb,p,str+p[1],res);
            pb[1]++;
        }

    }
}
//官方解速度较快,用了StringBuilder,思路与17比较相似
class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList<String>();
        backtrack(ans, new StringBuilder(), 0, 0, n);
        return ans;
    }

    public void backtrack(List<String> ans, StringBuilder cur, int open, int close, int max) {
        if (cur.length() == max * 2) {
            ans.add(cur.toString());
            return;
        }
        if (open < max) {
            cur.append('(');
            backtrack(ans, cur, open + 1, close, max);
            cur.deleteCharAt(cur.length() - 1);
        }
        if (close < open) {
            cur.append(')');
            backtrack(ans, cur, open, close + 1, max);
            cur.deleteCharAt(cur.length() - 1);
        }
    }
}
 

93. 复原 IP 地址

image-20211031154042497

/暴力
class Solution {
    public List<String> restoreIpAddresses(String s) {
        List<String> list = new ArrayList();
        for(int a=1; a<4; a++){
            for(int b=1; b<4; b++){
                for(int c=1; c<4; c++){
                    for(int d=1; d<4; d++){
                        if(a+b+c+d==s.length()){
                            String s1 = s.substring(0, a);
                            String s2 = s.substring(a, a+b);
                            String s3 = s.substring(a+b, a+b+c);
                            String s4 = s.substring(a+b+c, a+b+c+d);

                            if(check(s1)&&check(s2)&&check(s3)&&check(s4)){
                                String ip = s1+"."+s2+"."+s3+"."+s4;
                                list.add(ip);
                            }
                        }
                    }
                }
            }
        }
        return list;
    }

    public boolean check(String s){
        if(Integer.valueOf(s)<=255){
            if(s.charAt(0)!='0' || s.charAt(0)=='0'&&s.length()==1) 
                return true;
        }
        return false;
    }
}

993. 二叉树的堂兄弟节点

image-20211101151615073

//DFS思路一,父节点判断有问题,需要改进
class Solution {
    int dfs(TreeNode root,int x,TreeNode father){
        if(root==null) return -1;
        if(root.val==x) return 0;
        int l;
        father =root;
        l=dfs(root.left,x,father);
        if(l!=-1) return l+1;
        father=root;
        l=dfs(root.right,x,father);
        if(l!=-1) return l+1;
        return -1;
    }
    public boolean isCousins(TreeNode root, int x, int y) {
        TreeNode father_x=null;
        TreeNode father_y=null;
        int d1,d2;
        d1=dfs(root,x,father_x);
        d2=dfs(root,y,father_y);
        return d1==d2&&father_x!=father_y;
    }
}
//DFS思路二
class Solution {
    public boolean isCousins(TreeNode root, int x, int y) {
        int[] xi = dfs(root, null, 0, x);
        int[] yi = dfs(root, null, 0, y);
        return xi[1] == yi[1] && xi[0] != yi[0];
    }
    //返回的数组分别存放父节点的val与深度
    //root为根节点,fa为父节点,depth为深度,t为待搜索值,
    int[] dfs(TreeNode root, TreeNode fa, int depth, int t) {
        //边界条件
        if (root == null) return new int[]{-1, -1}; // 使用 -1 代表为搜索不到 t
        //截止条件
        if (root.val == t) {
            return new int[]{fa != null ? fa.val : 1, depth}; // 使用 1 代表搜索值 t 为 root
        }
        //候选节点
        int[] l = dfs(root.left, root, depth + 1, t);
        if (l[0] != -1) return l;
        return dfs(root.right, root, depth + 1, t);
    }
}
//BFS 问题求解树中的状态表现为一类存储的数据结构,用队列存储一个对象数组
class Solution {
    public boolean isCousins(TreeNode root, int x, int y) {
        int[] xi = bfs(root, x);
        int[] yi = bfs(root, y);
        return xi[1] == yi[1] && xi[0] != yi[0];
    }
    //类似层序遍历
    int[] bfs(TreeNode root, int t) {
        Deque<Object[]> d = new ArrayDeque<>(); // 存储值为 [cur, fa, depth]
        d.addLast(new Object[]{root, null, 0});
        while (!d.isEmpty()) {
            int size = d.size();
            while (size-- > 0) {
                Object[] poll = d.pollFirst();
                TreeNode cur = (TreeNode)poll[0], 
                fa = (TreeNode)poll[1];
                int depth = (Integer)poll[2];
                if (cur.val == t) return new int[]{fa != null ? fa.val : 0, depth};
                if (cur.left != null) d.addLast(new Object[]{cur.left, cur, depth + 1});
                if (cur.right != null) d.addLast(new Object[]{cur.right, cur, depth + 1});
            }
        }
        return new int[]{-1, -1};
    }
}

542. 01 矩阵

image-20211101185943052

class Solution {
    public int[][] updateMatrix(int[][] matrix) {
        // 首先将所有的 0 的坐标都入队,并且将 1 的位置设置成 -1,表示该位置是 未被访问过的 1
        Queue<int[]> queue = new LinkedList<>();
        int m = matrix.length, n = matrix[0].length;//行数和列数
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    queue.offer(new int[] {i, j});
                } else {
                    matrix[i][j] = -1;
                } 
            }
        }
        
        int[] dx = new int[] {-1, 1, 0, 0};//左右
        int[] dy = new int[] {0, 0, -1, 1};//上下
        while (!queue.isEmpty()) {
            int[] point = queue.poll();//得到0的坐标
            int x = point[0], y = point[1];//0的x,y
            for (int i = 0; i < 4; i++) {
                int newX = x + dx[i];
                int newY = y + dy[i];
                // 如果四邻域的点是 -1,表示这个点是未被访问过的 1
                // 所以这个点到 0 的距离就可以更新成 matrix[x][y] + 1。
                if (newX >= 0 && newX < m && newY >= 0 && newY < n 
                        && matrix[newX][newY] == -1) {
                    matrix[newX][newY] = matrix[x][y] + 1;
                    queue.offer(new int[] {newX, newY});
                }
            }
        }

        return matrix;
    }
}

488. 祖玛游戏(困难)

你正在参与祖玛游戏的一个变种。
在这个祖玛游戏变体中,桌面上有 一排 彩球,每个球的颜色可能是:红色 'R'、黄色 'Y'、蓝色 'B'、绿色 'G' 或白色 'W' 。你的手中也有一些彩球。
你的目标是 清空 桌面上所有的球。每一回合:

从你手上的彩球中选出 任意一颗 ,然后将其插入桌面上那一排球中:两球之间或这一排球的任一端。
接着,如果有出现 三个或者三个以上 且 颜色相同 的球相连的话,就把它们移除掉。
如果这种移除操作同样导致出现三个或者三个以上且颜色相同的球相连,则可以继续移除这些球,直到不再满足移除条件。
如果桌面上所有球都被移除,则认为你赢得本场游戏。
重复这个过程,直到你赢了游戏或者手中没有更多的球。
给你一个字符串 board ,表示桌面上最开始的那排球。另给你一个字符串 hand ,表示手里的彩球。请你按上述操作步骤移除掉桌上所有球,计算并返回所需的 最少 球数。如果不能移除桌上所有的球,返回 -1 。

示例 1:
输入:board = "WRRBBW", hand = "RB"
输出:-1
解释:无法移除桌面上的所有球。可以得到的最好局面是:
- 插入一个 'R' ,使桌面变为 WRRRBBW 。WRRRBBW -> WBBW
- 插入一个 'B' ,使桌面变为 WBBBW 。WBBBW -> WW
桌面上还剩着球,没有其他球可以插入。

示例 2:
输入:board = "WWRRBBWW", hand = "WRBRW"
输出:2
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'R' ,使桌面变为 WWRRRBBWW 。WWRRRBBWW -> WWBBWW
- 插入一个 'B' ,使桌面变为 WWBBBWW 。WWBBBWW -> WWWW -> empty
只需从手中出 2 个球就可以清空桌面。

示例 3:
输入:board = "G", hand = "GGGGG"
输出:2
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'G' ,使桌面变为 GG 。
- 插入一个 'G' ,使桌面变为 GGG 。GGG -> empty
只需从手中出 2 个球就可以清空桌面。

示例 4:
输入:board = "RBYYBBRRB", hand = "YRBGB"
输出:3
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'Y' ,使桌面变为 RBYYYBBRRB 。RBYYYBBRRB -> RBBBRRB -> RRRB -> B
- 插入一个 'B' ,使桌面变为 BB 。
- 插入一个 'B' ,使桌面变为 BBB 。BBB -> empty
只需从手中出 3 个球就可以清空桌面。
class Solution {

    // 手中最多只有5个球,所以,操作次数不会多于5
    int INF = 6;

    public int findMinStep(String board, String hand) {
        // 记忆化缓存
        Map<String, Integer> memo = new HashMap<>();
        // 递归开始
        int ans = dfs(board, hand.toCharArray(), memo);
        // 判断结果
        return ans >= INF ? -1 : ans;
    }

    private int dfs(String board, char[] hand, Map<String, Integer> memo) {
        // 如果board全部消除完了,直接返回
        if (board.length() == 0) {
            return 0;
        }

        // 如果缓存中已经处理过了,直接返回
        if (memo.containsKey(board)) {
            return memo.get(board);
        }

        // 存储本次递归的结果
        int ans = INF;

        // 将手中的球填序的board的任意位置尝试去消除
        for (int i = 0; i < hand.length; i++) {
            char c = hand[i];
            if (c != '0') {
                for (int j = 0; j < board.length(); j++) {
                    // 构造新的board,插入到旧board的任意位置
                    StringBuilder newBoard = new StringBuilder()
                            .append(board.substring(0, j))
                            .append(c)
                            .append(board.substring(j));
                    // 尝试消除
                    removeSame(newBoard, j);
                    // 表示这个球已经用过了
                    hand[i] = '0';
                    // 进入下一次递归
                    ans = Math.min(ans, dfs(newBoard.toString(), hand, memo) + 1);
                    // 回溯,恢复状态
                    hand[i] = c;
                }
            }
        }

        // 记录到缓存中
        memo.put(board, ans);
        // 返回结果
        return ans;
    }

    private void removeSame(StringBuilder board, int index) {
        // 移除三个以上连续的
        if (index < 0 ) {
            return;
        }
        // 从index的位置向两边扩散
        int left = index, right = index;
        char c = board.charAt(index);
        // 注意这里的操作
        while (--left >= 0 && board.charAt(left) == c) ;
        while (++right < board.length() && board.charAt(right) == c) ;

        // 扩散完了两边的right和left位置的值都是不等于 c 的,需要减一表示 c 出现的次数
        int count = right - left - 1;
        // 大于等于3才消除
        if (count >= 3) {
            board.delete(left + 1, right);
            // 连锁反应,比如 YYGGGY,移除了中间的G,三个Y挨一块了,也要移除
            removeSame(board, left);
        }
    }
}

 

岛屿类题目题解:https://mp.weixin.qq.com/s?__biz=MzA5ODk3ODA4OQ==&mid=2648167208&idx=1&sn=d8118c7c0e0f57ea2bdd8aa4d6ac7ab7&chksm=88aa236ebfddaa78a6183cf6dcf88f82c5ff5efb7f5c55d6844d9104b307862869eb9032bd1f&token=1064083695&lang=zh_CN#rd

200. 岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

示例 1:
输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值为 '0' 或 '1'
class Solution {
    private int res;
    public int numIslands(char[][] grid) {
     res = 0;
    for (int i = 0; i < grid.length; i++) {
        for (int j = 0; j < grid[0].length; j++) {
            if (grid[i][j] == '1') {
                dfs(grid,i,j);
               res++;
        }
    }
    }
       return res;
    }
    
    public void dfs(char[][] grid,int r,int c){
        if( r<0||r>=grid.length||c<0||c>=grid[0].length) return;
        if(grid[r][c]!='1') return;
        grid[r][c]='2';
        dfs(grid,r-1,c);
        dfs(grid,r+1,c);
        dfs(grid,r,c-1);
        dfs(grid,r,c+1);

    }
   
}

695. 岛屿的最大面积

image-20211111143237078

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
       int res = 0;
        for (int i = 0; i < grid.length; i ++) {
            for (int j = 0; j < grid[0].length; j ++) {
                if (grid[i][j] == 1) {                  
                res=Math.max(res, dfsGrid(grid, i, j)) ;             
  }
            }
        }
        return res;
    }
      private int dfsGrid(int[][] grid, int row, int col) {
        if (row >= grid.length || col >= grid[0].length || row < 0 || col < 0) {
            return 0;
        }

        if (grid[row][col] != 1) {
            return 0;
        }

        grid[row][col] = 2;
        return 1+
        +dfsGrid(grid, row - 1, col)
        +dfsGrid(grid, row + 1, col)
        +dfsGrid(grid, row, col - 1)
        +dfsGrid(grid, row, col + 1);
    }
}

463. 岛屿的周长

image-20211111143324243

class Solution {
    public int islandPerimeter(int[][] grid) {
 for (int r = 0; r < grid.length; r++) {
        for (int c = 0; c < grid[0].length; c++) {
            if (grid[r][c] == 1) {
                // 题目限制只有一个岛屿,计算一个即可
                return dfs(grid, r, c);
            }
        }
    }
    return 0;
}
int dfs(int[][] grid, int r, int c) {
    // 函数因为「坐标 (r, c) 超出网格范围」返回,对应一条黄色的边
    if ( r >= grid.length || c >= grid[0].length || r < 0 || c < 0) {
        return 1;
    }
    // 函数因为「当前格子是海洋格子」返回,对应一条蓝色的边
    if (grid[r][c] == 0) {
        return 1;
    }
    // 函数因为「当前格子是已遍历的陆地格子」返回,和周长没关系
    if (grid[r][c] != 1) {
        return 0;
    }
    grid[r][c] = 2;
    return dfs(grid, r - 1, c)
        + dfs(grid, r + 1, c)
        + dfs(grid, r, c - 1)
        + dfs(grid, r, c + 1);
}
}

](https://leetcode-cn.com/problems/binary-tree-tilt/submissions/)

563. 二叉树的坡度

给定一个二叉树,计算 整个树 的坡度 。
一个树的 节点的坡度 定义即为,该节点左子树的节点之和和右子树节点之和的 差的绝对值 。如果没有左子树的话,左子树的节点之和为 0 ;没有右子树的话也是一样。空结点的坡度是 0 。
整个树 的坡度就是其所有节点的坡度之和。

class Solution {
    int sum = 0;
    public int findTilt(TreeNode root) {
        dfs(root);
        return sum;
    }


    public int dfs(TreeNode root){
        if(root == null){
            return 0;
        }
        // 求出左子树的所有值
        int left = dfs(root.left);
        // 求出右子树的所有值
        int right = dfs(root.right);
        // 左子树和右子树差的绝对值
        sum = sum + Math.abs(left - right);
        // 及时更新当前点的值,为左子树+右子树
        return root.val + left + right;
    }
} 

397. 整数替换

给定一个正整数 n ,你可以做如下操作:

如果 n 是偶数,则用 n / 2替换 n 。
如果 n 是奇数,则可以用 n + 1或n - 1替换 n 。
n 变为 1 所需的最小替换次数是多少?
 class Solution {
      Map<Long, Integer> map = new HashMap<>();
    public int integerReplacement(int n) {
        return dfs(n * 1L);
    }
    int dfs(long n) {
        if (n == 1) return 0;
        if (map.containsKey(n)) return map.get(n);//记忆
        int ans = n % 2 == 0 ? dfs(n / 2) : Math.min(dfs(n + 1), dfs(n - 1));
        map.put(n, ++ans);
        return ans;
    }
}

79. 单词搜索

DFS 解析:
递归参数: 当前元素在矩阵 board 中的行列索引 i 和 j ,当前目标字符在 word 中的索引 k 。
终止条件:
返回 false
    (1) 行或列索引越界 或
    (2) 当前矩阵元素与目标字符不同 或 
    (3) 当前矩阵元素已访问过 ( (3) 可合并至 (2) ) 。
返回 truetrue : k = len(word) - 1 ,即字符串 word 已全部匹配。
递推工作:
标记当前矩阵元素: 将 board[i][j] 修改为 空字符 '' ,代表此元素已访问过,防止之后搜索时重复访问。
搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用 或 连接 (代表只需找到一条可行路径就直接返回,不再做后续 DFS ),并记录结果至 res 。
还原当前矩阵元素: 将 board[i][j] 元素还原至初始值,即 word[k] 。
返回值: 返回布尔量 res ,代表是否搜索到目标字符串。
使用空字符(Python: '' , Java/C++: '\0' )做标记是为了防止标记字符与矩阵原有字符重复。当存在重复时,此算法会将矩阵原有字符认作标记字符,从而出现错误。
 
class Solution {
    public boolean exist(char[][] board, String word) {
         char[] words = word.toCharArray();
        for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board[0].length; j++) {
                if(dfs(board, words, i, j, 0)) return true;
            }
        }
        return false;
    }
    
    boolean dfs(char[][] board, char[] word, int i, int j, int k) {
        //i是行,j是列,k是word的索引
        if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;//
        if(k == word.length - 1) return true;
        board[i][j] = '\0';
        boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || 
                      dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
        board[i][j] = word[k];
        return res;
    }   
}

剑指 Offer 13. 机器人的运动范围

深度优先搜索: 可以理解为暴力法模拟机器人在矩阵中的所有路径。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
剪枝: 在搜索中,遇到数位和超出目标值、此元素已访问,则应立即返回,称之为 可行性剪枝 。
算法解析:
递归参数: 当前元素在矩阵中的行列索引 i 和 j ,两者的数位和 si, sj 。
终止条件: 当 ① 行列索引越界 或 ② 数位和超出目标值 k 或 ③ 当前元素已访问过 时,返回 00 ,代表不计入可达解。
递推工作:
标记当前单元格 :将索引 (i, j) 存入 Set visited 中,代表此单元格已被访问过。
搜索下一单元格: 计算当前元素的 下、右 两个方向元素的数位和,并开启下层递归 。
回溯返回值: 返回 1 + 右方搜索的可达解总数 + 下方搜索的可达解总数,代表从本单元格递归搜索的可达解总数。

 
class Solution {
    int m, n, k;
    boolean[][] visited;
    public int movingCount(int m, int n, int k) {
        this.m = m; this.n = n; this.k = k;
        this.visited = new boolean[m][n];
        return dfs(0, 0, 0, 0);
    }
    public int dfs(int i, int j, int si, int sj) {
        if(i >= m || j >= n || k < si + sj || visited[i][j]) return 0;
        visited[i][j] = true;
        return 1 + dfs(i + 1, j, (i + 1) % 10 != 0 ? si + 1 : si - 8, sj) + dfs(i, j + 1, si, (j + 1) % 10 != 0 ? sj + 1 : sj - 8);
    }
}
 class Solution {
    public int movingCount(int m, int n, int k) {
  if(k==0) return 1;
        //创建二位数组 保存矩阵中每个点
        int[][] arr = new int[m][n];
        //循环遍历矩阵中的每个点,将不符合要求的点设为-1
        for(int i = 0;i<m;++i){
            for(int j = 0;j<n;++j){
                if(sum(i,j)>k) arr[i][j] = -1;
            }
        }
        //DFS将符合要求的坐标值为1
        dfs(0,0,arr);
        int ans=0;
        for(int i = 0;i<m;++i){
            for(int j = 0;j<n;++j){
                if(arr[i][j]==1) ++ans;
            }
        }
        return ans;
    }
    void dfs(int x,int y,int[][] arr){
        if(x<0||y<0||x>=arr.length||y>=arr[0].length||arr[x][y]==1||arr[x][y]==-1) return;
        arr[x][y]=1;
        dfs(x,y+1,arr);
        dfs(x+1,y,arr);
        dfs(x,y-1,arr);
        dfs(x-1,y,arr);
    }
    //计算两个坐标点的和  (12,15)--> 1+2+1+5=9
    public int sum(int x,int y){
        int res = 0;
        while(x>0){
            res += x%10;
            x /= 10;
        }
        while(y>0){
            res += y%10;
            y /= 10;
        }
        return res;
    }
    }

字符串问题

125. 验证回文串

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

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

示例 2:
输入: "race a car"
输出: false
解释:"raceacar" 不是回文串
//
class Solution {
    public boolean isPalindrome(String s) {
        StringBuffer sgood = new StringBuffer();
        int length = s.length();
        for (int i = 0; i < length; i++) {
            char ch = s.charAt(i);
            if (Character.isLetterOrDigit(ch)) {
                sgood.append(Character.toLowerCase(ch));
            }
        }
        StringBuffer sgood_rev = new StringBuffer(sgood).reverse();
        return sgood.toString().equals(sgood_rev.toString());
    }
}

 

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

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

示例 1:

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

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

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

class Solution {
    public int lengthOfLongestSubstring(String s) {
     HashMap<Character, Integer> map = new HashMap<>();
        int maxLen = 0;//用于记录最大不重复子串的长度
        int left = 0;//滑动窗口左指针
        for (int i = 0; i < s.length() ; i++)
        {
            /**
            1、首先,判断当前字符是否包含在map中,如果不包含,将该字符添加到map(字符,字符在数组下标),
             此时没有出现重复的字符,左指针不需要变化。此时不重复子串的长度为:i-left+1,与原来的maxLen比较,取最大值;

            2、如果当前字符 ch 包含在 map中,此时有2类情况:
             1)当前字符包含在当前有效的子段中,如:abca,当我们遍历到第二个a,当前有效最长子段是 abc,我们又遍历到a,
             那么此时更新 left 为 map.get(a)+1=1,当前有效子段更新为 bca;
             2)当前字符不包含在当前最长有效子段中,如:abba,我们先添加a,b进map,此时left=0,我们再添加b,发现map中包含b,
             而且b包含在最长有效子段中,就是1)的情况,我们更新 left=map.get(b)+1=2,此时子段更新为 b,而且map中仍然包含a,map.get(a)=0;
             随后,我们遍历到a,发现a包含在map中,且map.get(a)=0,如果我们像1)一样处理,就会发现 left=map.get(a)+1=1,实际上,left此时
             应该不变,left始终为2,子段变成 ba才对。

             为了处理以上2类情况,我们每次更新left,left=Math.max(left , map.get(ch)+1).
             另外,更新left后,不管原来的 s.charAt(i) 是否在最长子段中,我们都要将 s.charAt(i) 的位置更新为当前的i,
             因此此时新的 s.charAt(i) 已经进入到 当前最长的子段中!
             */
            if(map.containsKey(s.charAt(i)))
            {
                left = Math.max(left , map.get(s.charAt(i))+1);
            }
            //不管是否更新left,都要更新 s.charAt(i) 的位置!
            map.put(s.charAt(i) , i);
            maxLen = Math.max(maxLen , i-left+1);
        }
        
        return maxLen;
 
    }
}
abcda
    abba
class Solution {
    public int lengthOfLongestSubstring(String s) {
     HashMap<Character, Integer> map = new HashMap<>();
        int maxLen = 0;//用于记录最大不重复子串的长度
        int left = 0;//滑动窗口左指针
        for (int i = 0; i < s.length() ; i++)
        {
            if(map.containsKey(s.charAt(i)))
            {
                left = Math.max(left , map.get(s.charAt(i))+1);
            }
            //不管是否更新left,都要更新 s.charAt(i) 的位置!
            map.put(s.charAt(i) , i);
            maxLen = Math.max(maxLen , i-left+1);
        }
        
        return maxLen;
 
    }
}
 

5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:

输入:s = "cbbd"
输出:"bb"

class Solution {
    public String longestPalindrome(String s) {
     if (s == null || s.length() == 0) {
            return "";
        }
        int strLen = s.length();
        int left = 0;
        int right = 0;
        int len = 1;
        int maxStart = 0;
        int maxLen = 0;

        for (int i = 0; i < strLen; i++) {
            left = i - 1;
            right = i + 1;
            while (left >= 0 && s.charAt(left) == s.charAt(i)) {
                len++;
                left--;
            }
            while (right < strLen && s.charAt(right) == s.charAt(i)) {
                len++;
                right++;
            }
            while (left >= 0 && right < strLen && s.charAt(right) == s.charAt(left)) {
                len = len + 2;
                left--;
                right++;
            }
            if (len > maxLen) {
                maxLen = len;
                maxStart = left;
            }
            len = 1;
        }
        return s.substring(maxStart + 1, maxStart + maxLen + 1);
 
    }
}

public String longestPalindrome(String s) {
        if (s == null || s.length() < 2) {
            return s;
        }
        int strLen = s.length();
        int maxStart = 0;  //最长回文串的起点
        int maxEnd = 0;    //最长回文串的终点
        int maxLen = 1;  //最长回文串的长度

        boolean[][] dp = new boolean[strLen][strLen];

        for (int r = 1; r < strLen; r++) {
            for (int l = 0; l < r; l++) {
                if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1])) {
                    dp[l][r] = true;
                    if (r - l + 1 > maxLen) {
                        maxLen = r - l + 1;
                        maxStart = l;
                        maxEnd = r;

                    }
                }

            }

        }
        return s.substring(maxStart, maxEnd + 1);

    }

 

链表

剑指 Offer 24. 反转链表

206. 反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev=null;//指向前一个节点
        ListNode cur=head;//指向当前
        while(cur!=null){
            ListNode next=cur.next;//先记录下一个节点,不然指向prev后就找不到next了
            cur.next=prev;//指向prev
            prev=cur;//pre后移
            cur=next;//cur后移
        }
        return prev;
    }
}

BM15 删除有序链表中重复的元素-I

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 * }
 */

public class Solution {
    /**
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    public ListNode deleteDuplicates (ListNode head) {
        // write code here
     ListNode cur = head;
        while(cur != null){
            while(cur.next != null && cur.val == cur.next.val){
                cur.next = cur.next.next;
            }
            cur = cur.next;
        }
        return head;
    }
}

BM2 链表内指定区间反转

在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。

curr:指向待反转区域的第一个节点 left;
Cur_next:永远指向 curr 的下一个节点,循环过程中,curr 变化以后 Cur_next 会变化;
pre:永远指向待反转区域的第一个节点 left 的前一个节点,在循环过程中不变。
import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 * }
 */

public class Solution {
    /**
     * 
     * @param head ListNode类 
     * @param m int整型 
     * @param n int整型 
     * @return ListNode类
     */
    public ListNode reverseBetween (ListNode head, int m, int n) {
        // write code here
        ListNode dummyNode=new ListNode(-1);
        dummyNode.next=head;
        ListNode pre=dummyNode;
        for(int i=0;i<m-1;i++){
            pre=pre.next;
        }
        ListNode cur=pre.next;
        ListNode curnext;
        for(int i=0;i<n-m;i++){
            curnext=cur.next;
            cur.next=curnext.next;
            curnext.next=pre.next;
            pre.next=curnext;
        }
        return dummyNode.next;
    }
}

BM8 链表中倒数最后k个结点

import java.util.*;

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

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pHead ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode FindKthToTail (ListNode pHead, int k) {
        // write code here
        if(pHead==null) return pHead;
        ListNode fast=pHead;
        ListNode slow=pHead;
        while(k-->0){
            if(fast==null) return null;
            fast=fast.next;
        }
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }
}

21. 合并两个有序链表

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

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

示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
   ListNode pre =new ListNode(0);
   ListNode cur =pre;
   while(l1!=null&&l2!=null){
       if(l1.val<l2.val){
           cur.next=l1;
           l1=l1.next;
       }
       else{
           cur.next=l2;
           l2=l2.next;
       }
       cur=cur.next;
   }
      cur.next=l1==null?l2:l1;
      return pre.next;
     }
}

143. 重排链表

注意到目标链表即为将原链表的左半端和反转后的右半端合并后的结果。
这样我们的任务即可划分为三步:
1.找到原链表的中点(参考「876. 链表的中间结点」)。
2.我们可以使用快慢指针来找到链表的中间节点。
将原链表的右半端反转(参考「206. 反转链表」)。
3.我们可以使用迭代法实现链表的反转。
将原链表的两端合并。
因为两链表长度相差不超过 11,因此直接合并即可
class Solution {
    public void reorderList(ListNode head) {
        if (head == null) {
            return;
        }
        ListNode mid = middleNode(head);
        ListNode l1 = head;
        ListNode l2 = mid.next;
        mid.next = null;
        l2 = reverseList(l2);
        mergeList(l1, l2);
    }

    public ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }

    public void mergeList(ListNode l1, ListNode l2) {
        ListNode l1_tmp;
        ListNode l2_tmp;
        while (l1 != null && l2 != null) {
            l1_tmp = l1.next;
            l2_tmp = l2.next;

            l1.next = l2;
            l1 = l1_tmp;

            l2.next = l1;
            l2 = l2_tmp;
        }
    }
}

148. 排序链表

class Solution {
    public ListNode sortList(ListNode head) {
        return sortList(head, null);
    }

    public ListNode sortList(ListNode head,ListNode tail){
        if(head ==null) return head;
        //左闭右开
        if(head.next==tail) {
            head.next=null;
            return head;
        }
        ListNode slow =head;
        ListNode fast =head;
     while (fast != tail&&fast.next!=tail) {
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode mid=slow;
        ListNode l1=sortList(head,mid);
        ListNode l2=sortList(mid,tail);
        ListNode l3= merge(l1,l2);
        return l3;
    }
    public ListNode merge(ListNode l1,ListNode l2){
        ListNode pre=new ListNode(0);
        ListNode cur=pre;
        while(l1!=null&&l2!=null){
            if(l1.val<l2.val){
                cur.next=l1;
                l1=l1.next;
            }else{
                cur.next=l2;
                l2=l2.next;
            }
            cur=cur.next;
        }
        if(l1!=null){
            cur.next=l1;
        }
        else{
            cur.next=l2;
        }
        return pre.next;
    }
}

114. 二叉树展开为链表


23. 合并K个升序链表(困难)

给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

示例 2:
输入:lists = []
输出:[]
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists==null||lists.length==0) return null;
       return merge(lists,0,lists.length-1);
    }

    public ListNode merge(ListNode[] lists,int left,int right){
        if(left==right) return lists[left];
        int mid =left+(right-left)/2;
        ListNode l1=merge(lists,left,mid);
        ListNode l2=merge(lists,mid+1,right);
        return mergeTwoLists(l1,l2);
    }
    public ListNode mergeTwoLists(ListNode l1,ListNode l2){
         ListNode dummyhead =new ListNode(0);
         ListNode cur=dummyhead;
         while(l1!=null&&l2!=null){
            if(l1.val<l2.val){
                 cur.next=l1;
                 l1=l1.next;
               }
                else{
                 cur.next=l2;
                 l2=l2.next;
                }
            cur=cur.next;
            }
           cur.next=l1!=null?l1:l2;
            return dummyhead.next;
        }
}

public class Solution { 
    public ListNode mergeKLists(ArrayList<ListNode> lists) {
        if(lists==null||lists.size()==0) return null;
               return merge(lists,0,lists.size()-1);

    }
   
     public ListNode merge(ArrayList<ListNode> lists,int left,int right){
        if(left==right) return lists.get(left);
        int mid =left+(right-left)/2;
        ListNode l1=merge(lists,left,mid);
        ListNode l2=merge(lists,mid+1,right);
        return mergeTwoLists(l1,l2);
    }
    public ListNode mergeTwoLists(ListNode l1,ListNode l2){
         ListNode dummyhead =new ListNode(0);
         ListNode cur=dummyhead;
         while(l1!=null&&l2!=null){
            if(l1.val<l2.val){
                 cur.next=l1;
                 l1=l1.next;
               }
                else{
                 cur.next=l2;
                 l2=l2.next;
                }
            cur=cur.next;
            }
           cur.next=l1!=null?l1:l2;
            return dummyhead.next;
        }
}

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

image-20211115112948797

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode hair = new ListNode(0);//新建哑节点
        hair.next = head;//指向头节点
        ListNode pre = hair;//pre和tail都从头节点的pre点开始,tail后移k个

        while (head != null) {
            ListNode tail = pre;
            // 查看剩余部分长度是否大于等于 k
            for (int i = 0; i < k; ++i) {
                tail = tail.next;
                if (tail == null) {
                    return hair.next;
                }
            }
            ListNode nex = tail.next;
            ListNode[] reverse = myReverse(head, tail);
            head = reverse[0];
            tail = reverse[1];
            // 把子链表重新接回原链表
            pre.next = head;
            tail.next = nex;
            pre = tail;
            head = tail.next;
        }

        return hair.next;
    }

    public ListNode[] myReverse(ListNode head, ListNode tail) {
        ListNode prev = tail.next;
        ListNode cur = head;
        while (prev != tail) {
            ListNode nex = cur.next;
            cur.next = prev;
            prev = cur;
            cur = nex;
        }
        return new ListNode[]{tail, head};
    }
}

2074. 反转偶数长度组的节

image-20211115142528703

     int step = 1;
        int group = 1;
        ListNode hair = new ListNode();
        hair.next = head;
        ListNode pre = head;
        ListNode tail = head;
        while (tail.next != null){
            if (group % 2 == 0){
                if (step == group){
                    ListNode[] nodes = reverse(pre.next, tail);
                    pre.next = nodes[0];
                    pre = nodes[1];
                    tail = nodes[1];
                    group++;
                    step = 0;
                }
                tail = tail.next;
                step++;
            }
            else {
                if (step == group){
                    group++;
                    step = 0;
                    pre = tail;
                }
                tail = tail.next;
                step++;
            }
        }
        //不足group,但是满足偶数的情况
        if (step % 2 == 0){
            ListNode[] nodes = reverse(pre.next, tail);
            pre.next = nodes[0];
        }
        return hair.next;
    }
    //反转
    public ListNode[] reverse(ListNode head, ListNode tail){
        ListNode p = head;
        ListNode pre=tail.next;
        while (pre != tail){
            ListNode nex = p.next;
            p.next = pre;
            pre = p;
            p = nex;
        }
        return new ListNode[]{pre, head};
    }

912. 排序数组

image-20211029111905203

//quick sort
class Solution {
     public int[] sortArray(int[] nums) {
          quick_sort(nums,0,nums.length-1);
        return nums;
    }
void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;
    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j){
            int tem=q[i];
            q[i]=q[j];
            q[j]=tem;
        }  
    }
    quick_sort(q, l, j);
    quick_sort(q, j + 1, r);
}
}
//merge sort
class Solution {
    int[] tmp;
    public int[] sortArray(int[] nums) {
        tmp = new int[nums.length];
        merge_sort(nums,0,nums.length-1);
        return nums;
    }
    void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(q, l, mid);//对左边递归归并排序
    merge_sort(q, mid + 1, r);//对右边递归归并排序
    int k = 0;//k代表存入新数组tmp的元素个数
    int i = l, j = mid + 1;
    while (i <= mid && j <= r)//直到一个数组排序完之后跳出while
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else tmp[k++] = q[j++];
    //将剩余的数组直接放入tem数组
    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];
    //将tmp数组覆盖q数组
    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
}

二叉树

144. 二叉树的前序遍历

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

示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
示例 2:
输入:root = []
输出:[]
//使用递归
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        preorder(root, res);
        return res;
    }
    public void preorder(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }
        res.add(root.val);
        preorder(root.left, res);
        preorder(root.right, res);
    }
}
//使用队列
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode node = root;
        while (!stack.isEmpty() || node != null) {
            while (node != null) {
                res.add(node.val);
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            node = node.right;
        }
        return res;
    }
}

94. 二叉树的中序遍历

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

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

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        Deque<TreeNode> stk = new LinkedList<TreeNode>();
        while (root != null || !stk.isEmpty()) {
            while (root != null) {
                stk.push(root);
                root = root.left;
            }
            root = stk.pop();
            res.add(root.val);
            root = root.right;
        }
        return res;
    }
}

145. 二叉树的后序遍历

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

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

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }

        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode prev = null;
        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            if (root.right == null || root.right == prev) {
                res.add(root.val);
                prev = root;
                root = null;
            } else {
                stack.push(root);
                root = root.right;
            }
        }
        return res;
    }
}

剑指 Offer 33. 二叉搜索树的后序遍历序列

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        return recur(postorder, 0, postorder.length - 1);
    }
    boolean recur(int[] postorder, int i, int j) {
        if(i >= j) return true;
        int p = i;
        while(postorder[p] < postorder[j]) p++;
        int m = p;//第一个大于根节点的索引,左子树是i到m-1,右子树是m到j-1
        while(postorder[p] > postorder[j]) p++;//继续往后找第二个大于根节点的索引,直到根节点
        return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);
    }
}

102. 二叉树的层序遍历

示例:
二叉树:[3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回其层序遍历结果:

[
  [3],
  [9,20],
  [15,7]
]
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res =new ArrayList<List<Integer>>();//最终结果的数组
        if(root ==null) return res;//边界条件
        Queue<TreeNode> queue =new LinkedList<TreeNode>();//用于存储节点的队列
        queue.offer(root);//根节点入队
        while(!queue.isEmpty()){
            List<Integer> q2=new ArrayList<Integer>();//存每一层的数组
            int currentsize =queue.size();//一层的节点数
            for(int i=1;i<=currentsize;i++){
            TreeNode node=queue.poll();
            q2.add(node.val);
            if(node.left!=null){
                queue.offer(node.left);
            }
            if(node.right!=null){
                queue.offer(node.right);
            }

            }
            res.add(q2);
        }
        return res;
    }
}

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

image-20211102144123189

思路:
把中序遍历的元素与对应的索引传入哈希表,得到中序遍历中根节点的索引,以及左子树的个数,右子树的个数
前序遍历:根左右  中序遍历:左根右
[3,9,20,15,7]  [9,3,15,20,7]
根为3              
class Solution {
    private Map<Integer, Integer> indexMap;
//preorder_left前序的左边界, int preorder_right, int inorder_left, int inorder_right
    public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
        if (preorder_left > preorder_right) {
            return null;
        }
        // 前序遍历中的第一个节点就是根节点
        int preorder_root = preorder_left;//0
        // 在中序遍历中定位根节点
        int inorder_root = indexMap.get(preorder[preorder_root]);
        // 先把根节点建立出来
        TreeNode root = new TreeNode(preorder[preorder_root]);
        // 得到左子树中的节点数目
        int size_left_subtree = inorder_root - inorder_left;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从左边界+1 开始的size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int n = preorder.length;
        // 构造哈希映射,帮助我们快速定位根节点
        indexMap = new HashMap<Integer, Integer>();
        for (int i = 0; i < n; i++) {
            indexMap.put(inorder[i], i);
        }
        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }
}

106. 从中序与后序遍历序列构造二叉树

根据一棵树的中序遍历与后序遍历构造二叉树。

注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7
思路:
把中序遍历的元素与对应的索引传入哈希表,得到中序遍历中根节点的索引,以及左子树的个数,右子树的个数
后序遍历:左右根  中序遍历:左根右
[9,15,7,20,3]  [9,3,15,20,7]
      
//105题的思路
class Solution {
    private Map<Integer, Integer> indexMap;
  public TreeNode myBuildTree(int[] postorder, int[] inorder, int postorder_left, int postorder_right, int inorder_left, int inorder_right) {
        if (inorder_left > inorder_right) {
            return null;
        }
        int postorder_root = postorder_right;
        int inorder_root = indexMap.get(postorder[postorder_root]);
        TreeNode root = new TreeNode(postorder[postorder_root]);
        int size_left_subtree = inorder_root - inorder_left;
       //这时候左子树是从postorder_left到postorder_left+size_left_subtree-1
      //右子树是从postorder_left + size_left_subtree 到 postorder_right-1
      root.left = myBuildTree(postorder, inorder,postorder_left, postorder_left + size_left_subtree-1, inorder_left, inorder_root - 1);
        root.right = myBuildTree(postorder, inorder, postorder_left + size_left_subtree , postorder_right-1, inorder_root + 1, inorder_right);
        return root;
    }


    public TreeNode buildTree(int[] inorder, int[] postorder) {
        int n=inorder.length;
        indexMap=new HashMap<Integer,Integer>();
        for(int i=0;i<n;i++){
            indexMap.put(inorder[i],i);
        }
        return myBuildTree(postorder,inorder,0,n-1,0,n-1);
    }
}
//思路2
class Solution {
    int post_idx;
    int[] postorder;
    int[] inorder;
    Map<Integer, Integer> idx_map = new HashMap<Integer, Integer>();

    public TreeNode helper(int in_left, int in_right) {
        // 如果这里没有节点构造二叉树了,就结束
        if (in_left > in_right) {
            return null;
        }
        // 选择 post_idx 位置的元素作为当前子树根节点
        int root_val = postorder[post_idx];
        TreeNode root = new TreeNode(root_val);

        // 根据 root 所在位置分成左右两棵子树
        int index = idx_map.get(root_val);

        // 下标减一
        post_idx--;
        // 构造右子树
        root.right = helper(index + 1, in_right);
        // 构造左子树
        root.left = helper(in_left, index - 1);
        return root;
    }

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        this.postorder = postorder;
        this.inorder = inorder;
        // 从后序遍历的最后一个元素开始
        post_idx = postorder.length - 1;

        // 建立(元素,下标)键值对的哈希表
        int idx = 0;
        for (Integer val : inorder) {
            idx_map.put(val, idx++);
        }
        
        return helper(0, inorder.length - 1);
    }
}

129. 求根节点到叶节点数字之和

image-20211110191000291

class Solution {
    public int sumNumbers(TreeNode root) {
        return dfs(root,0);
    }
    public int dfs(TreeNode root,int pre){
        if(root==null) return 0;
        int sum=root.val+pre*10;
        if(root.left==null&&root.right==null) return sum;
        else return dfs(root.left,sum)+dfs(root.right,sum);
    }
}

404. 左叶子之和

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        int sum=0;
        if(root==null) return sum;
        if(root.left!=null&&root.left.left==null&&root.left.right==null){
              sum=root.left.val;
        }
        return sum+sumOfLeftLeaves(root.left)+sumOfLeftLeaves(root.right);
      }
   
}

剑指 Offer 27. 二叉树的镜像

image-20220415203622990

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if (root == null) {
            return null;
        }
        TreeNode left = mirrorTree(root.left);
        TreeNode right = mirrorTree(root.right);
        root.left = right;
        root.right = left;
        return root;
    }
}

剑指 Offer 26. 树的子结构

image-20220415204231859

class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        //这个函数对这棵树进行前序遍历:即处理根节点,再递归左子节点,再递归处理右子节点
        //特殊情况是:当A或B是空树的时候 返回false
        //用||关系可以达到 不同顺序遍历的作用
        if(A==null||B==null){
            return false;
        }
        return recur(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
    }
    //此函数的作用是从上个函数得到的根节点开始递归比较 是否是子树
    boolean recur(TreeNode A, TreeNode B){
        //结束条件
        //当最后一层B已经为空的,证明则B中节点全是A中节点
        if(B==null){
            return true;
        }
        //这里因为有上一个条件,则说明 A已经为空了,B却不为空,则一定不是子数
        if(A==null){
            return false;
        }
        //处理本次递归,即处理当前节点
        if(A.val!=B.val){
            return false;
        }
        //递归,同时递归左右两个子节点
        return recur(A.left,B.left)&&recur(A.right,B.right);
    }
}

剑指 Offer 28. 对称的二叉树

class Solution {
    public boolean isSymmetric(TreeNode root) {
        return check(root,root);
      }
      public boolean check(TreeNode p,TreeNode q){
          if(p==null && q==null) return true;
          if(p==null || q==null ||p.val!=q.val) return false;
          return check(p.left,q.right)&&check(p.right,q.left);
      }
}

剑指 Offer 34. 二叉树中和为某一值的路径

image-20220531170311372

/**
 * 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 {
     List<List<Integer>> res=new ArrayList<List<Integer>>();
     List<Integer> sum=new ArrayList<Integer>();
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        dfs(root,target);
        return res;
    }
    public void dfs(TreeNode root,int target){
        if(root==null) return;
        sum.add(root.val);
         target=target-root.val;
        if(target==0&&root.left==null&&root.right==null){
            res.add(new ArrayList(sum));//写 res.add(sum)就会无法输出
        }
        dfs(root.left,target);
        dfs(root.right,target);
        sum.remove(sum.size()-1);
    } 
}
变量 sum 所指向的列表 在深度优先遍历的过程中只有一份 ,深度优先遍历完成以后,回到了根结点,成为空列表。

在 Java 中,参数传递是 值传递,对象类型变量在传参的过程中,复制的是变量的地址。这些地址被添加到 res 变量,但实际上指向的是同一块内存地址,因此我们会看到 66 个空的列表对象。解决的方法很简单,在 res.add(path); 这里做一次拷贝即可。

 

257. 二叉树的所有路径image-20220601114456241

/**
 * 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 {
    List<String> res=new ArrayList<String>();
    public List<String> binaryTreePaths(TreeNode root) {
          dfs(root,"");        
          return res;
    }
    public void dfs(TreeNode root,String path){
        if(root==null) return;
        StringBuilder ans=new StringBuilder(path);
        ans.append(Integer.toString(root.val));
        if(root.left==null&&root.right==null){
            res.add(ans.toString());
        }
        else{
        ans.append("->");
        dfs(root.left,ans.toString());
        dfs(root.right,ans.toString());
        }
    }
}

剑指 Offer 54. 二叉搜索树的第k大节点

image-20220415204525836

class Solution {
    int res, k;
    public int kthLargest(TreeNode root, int k) {
        this.k = k;
        dfs(root);
        return res;
    }
    void dfs(TreeNode root) {
        if(root == null) return;
        dfs(root.right);
        if(k == 0) return;
        if(--k == 0) res = root.val;
        dfs(root.left);
    }
}

 

剑指 Offer 68 - II. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null) return null; // 如果树为空,直接返回null
        if(root == p || root == q) return root; 
        // 如果 p和q中有等于 root的,那么它们的最近公共祖先即为root(一个节点也可以是它自己的祖先)
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        // 递归遍历左子树,只要在左子树中找到了p或q,则先找到谁就返回谁
        TreeNode right = lowestCommonAncestor(root.right, p, q); 
        // 递归遍历右子树,只要在右子树中找到了p或q,则先找到谁就返回谁
        if(left == null) return right; 
        // 如果在左子树中 p和 q都找不到,则 p和 q一定都在右子树中,右子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
        else if(right == null) return left; 
        // 否则,如果 left不为空,在左子树中有找到节点(p或q),这时候要再判断一下右子树中的情况,如果在右子树中,p和q都找不到,则 p和q一定都在左子树中,左子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
        else return root; 
        //否则,当 left和 right均不为空时,说明 p、q节点分别在 root异侧, 最近公共祖先即为 root
    }
}

动态规划

如何求解递推问题

1.确定递推状态(函数符号f(x)以及这个函数符号的含义描述,f(x)所对应的值)
2.确定递推公式
3.分析边界条件
4.程序实现

如何求解动态规划问题

1.确定状态,需要开一个数组dp[i],dp[i][j]代表什么
确定状态需要两个意识1.最后一步2.子问题
2.转移方程
3.初始条件和边界情况

70. 爬楼梯

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

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

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 阶 + 1 阶 + 1 阶
2.  1 阶 + 2 阶
3.  2 阶 + 1 阶
//斐波那契数列  f[n]=f[n-1]+f[n-2]
class Solution {
    public int climbStairs(int n) {
        int f[] =new int[n+1];
        f[0]=1; f[1]=1;
        for(int i=2;i<=n;i++){
            f[i]=f[i-1]+f[i-2];
        }
        return f[n];
    }
}

class Solution {
    public int climbStairs(int n) {
        if(n<=2) return n;
        int first=1;
        int second =2;
        for(int i=3;i<=n;i++){
            second=first+second;
            first=second-first;
        }
        return second;
    }
}

746. 使用最小花费爬楼梯

数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。
每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。
请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。
示例 1:
输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。

示例 2:
输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。

提示:
cost 的长度范围是 [2, 1000]。
cost[i] 将会是一个整型数据,范围为 [0, 999] 。
思路:最后一步要么跨两步,要么跨一步
0-dp[n-1]
0-dp[n-2]
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int[] dp = new int[n + 1];
        dp[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];
    }
}

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();
        // dp[i][j] 表示从点 (i, j) 到底边的最小路径和。
        int[][] dp = new int[n + 1][n + 1];
        // dp[i][j]代表从底边走到(i,j)点所获得的最小值
        for (int i = n - 1; i >= 0; i--) {
            for (int j = 0; j <= i; j++) {
                dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j);
            }
        }
        return dp[0][0];
    }
}

119. 杨辉三角 II

//错误解
class Solution {
    public List<Integer> getRow(int rowIndex) {
        List<List<Integer>> res=new ArrayList<List<Integer>>();
        for(int i=0;i<=rowIndex;i++){
            List<Integer> ans=new ArrayList<Integer>();
            for(int j=0;j<=rowIndex;j++){
                if(i==0||i==1||j==0||j==rowIndex){
                    ans.add(1);
                }else
                {ans.add(res.get(i-1).get(j-1)+res.get(i-1).get(j));}
            }
            res.add(ans);
        }
        return res.get(rowIndex);
    }
}
//正确
class Solution {
    public List<Integer> getRow(int rowIndex) {
        List<List<Integer>> C = new ArrayList<List<Integer>>();
        for (int i = 0; i <= rowIndex; ++i) {
            List<Integer> row = new ArrayList<Integer>();
            for (int j = 0; j <= i; ++j) {
                if (j == 0 || j == i) {
                    row.add(1);
                } else {
                    row.add(C.get(i - 1).get(j - 1) + C.get(i - 1).get(j));
                }
            }
            C.add(row);
        }
        return C.get(rowIndex);
    }
}

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

53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

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

示例 2:
输入:nums = [1]
输出:1
class Solution {
    public int maxSubArray(int[] nums) {
        int sum = 0;
        int ans = nums[0];
        for(int num:nums){
            if(sum >=0){
                sum+=num;
            }
            else if(sum <0){
                sum=num;
            }
            ans = Math.max(ans,sum);
        }
        return ans;
    }
}
//优化后,更能体现动态规划思想

122. 买卖股票的最佳时机 II

给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: prices = [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: prices = [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
class Solution {
    public int maxProfit(int[] prices) {
        int profit = 0;
        int pricesSize = prices.length;
        int temp = 0;
        if(pricesSize == 1) return 0;
        for (int i= 0;i+1<pricesSize;i++){
            temp=prices[i+1]-prices[i];
            if(temp>0){
                profit += temp;
            }
        }
        return profit;
    }
}

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 。
class Solution {
    public int rob(int[] nums) {
       int n = nums.length;
    int[] dp = new int[n+1];
    dp[0] = 0;
    dp[1] = nums[0];
    for(int i=2;i<=n;i++){
        dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i-1]);
    }
    return dp[n];
    }
}

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 = Integer.MIN_VALUE, imax = 1, imin = 1;
        for(int i=0; i<nums.length; i++){
            if(nums[i] < 0){ //num[i]<0的话,imax和imin交换
              int tmp = imax;
              imax = imin;
              imin = tmp;
            }
            imax = Math.max(imax*nums[i], nums[i]);
            imin = Math.min(imin*nums[i], nums[i]);
            
            max = Math.max(max, imax);
        }
        return max;
    }
}

 

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

322. 零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。

示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1
    
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:

输入:coins = [1], amount = 0
输出:0
示例 4:

输入:coins = [2,5,7], amount = 27
输出:5
public class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount + 1];//从1开始拼,0元拼不出
        int n=coins.length;
        dp[0] = 0;
        //dp[0],dp[1]...dp[27]拼0,1,2,3..27所需要的硬币个数
        for (int i = 1; i <= amount; i++) {
            dp[i]=Integer.MAX_VALUE;//要取一个最大的值
            //最后的Coins[j] 
            //dp[i]=min{dp[i-coins[0]]+1,+......dp[i-coins[n-1]]+1}
            for (int j = 0; j < coins.length; j++) {
                if (coins[j] <= i&&dp[i-coins[j]]!=Integer.MAX_VALUE) {//条件是为了i - coins[j]不是负数,以及max value加1不为负数
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
            if(dp[amount]==Integer.MAX_VALUE) dp[amount]=-1;
        }
        return dp[amount]  ;
    }
}
 
public class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, max);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if (coins[j] <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

518. 零钱兑换 II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。 
题目数据保证结果符合 32 位带符号整数。

示例 1:
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1 
//思路:dp[0]是初始化条件为1, amount = 5, coins = [1, 2, 5]
dp[i]=dp[i]+dp[i-coins]
第一轮:dp[i]都为1
第二轮:dp[2]=dp[2]+dp[0]=2 dp[3]=dp[3]+dp[1]=2 dp[4]=dp[4]+dp[2]=3 dp[5]=dp[5]+dp[3]=3
第三轮:dp[5]=dp[5]+dp[0]=4
class Solution {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1;
        for (int coin : coins) {
            for (int i = coin; i <= amount; i++) {
                dp[i] += dp[i - coin];
            }
        }
        return dp[amount];
    }
}

32. 最长有效括号

给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1:

输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"

示例 2:

输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"

示例 3:

输入:s = ""
输出:0
class Solution {
    public int longestValidParentheses(String s) {
        int maxans = 0;
        Deque<Integer> stack = new LinkedList<Integer>();
        stack.push(-1);
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                stack.push(i);
            } else {
                stack.pop();
                if (stack.isEmpty()) {
                    stack.push(i);
                } else {
                    maxans = Math.max(maxans, i - stack.peek());
                }
            }
        }
        return maxans;
    }
}
 

128. 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

示例 1:

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
class Solution {
    public int longestConsecutive(int[] nums) {
      // 建立一个存储所有数的哈希表,同时起到去重功能
        Set<Integer> set = new HashSet<>();
        for (int num : nums) {
            set.add(num);
        }
        int ans = 0;
        // 遍历去重后的所有数字
        for (int num : set) {
            int cur = num;
            // 只有当num-1不存在时,才开始向后遍历num+1,num+2,num+3......
            //比如,现有元素[1,2,4,3,5],当2,3,4,5发现均有比自己小1的元素存在,那么它们就不会开始+1遍历,而1是连续序列中最小的元素,没有比自己小1的元素存在,所以会开始+1遍历。通过上述方式便可将时间复杂度优化至O(n)。
            if (!set.contains(cur - 1)) {
                while (set.contains(cur + 1)) {
                    cur++;
                }
            }
            // [num, cur]之间是连续的,数字有cur - num + 1个
            ans = Math.max(ans, cur - num + 1);
        }
        return ans;
    }
}


解题思路3:哈希表记录连续区间长度(动态规划)
这是一种非常巧妙的做法,与思路2相同的一点是也利用了Map减小遍历次数。但很重要的一点不同是其value表示的是num所在的连续区间长度。举个例子,当Map的key为5,value为3时,这就表明当前有一个包含5且长度为3的连续区间,当然有多种可能,可以是[3,5],[4,6],[5,7]。

具体做法是:

遍历nums数组中的所有数字num
当num是第一次出现时:
(1)分别获取到左相邻数字num-1的连续区间长度left和右相邻数字num+1的连续区间长度right;
(2)计算得到当前的区间长度为curLen=left+right+1curLen=left+right+1;
(3)更新最长区间长度ans以及左右边界的区间长度。
如果不理解这样做的原因,可以先看一遍代码,再看我的后续解释。

代码3

class Solution {
    public int longestConsecutive(int[] nums) {
        // key表示num,value表示num所在连续区间的长度
        Map<Integer, Integer> map = new HashMap<>();
        int ans = 0;
        for (int num : nums) {
            // 当map中不包含num,也就是num第一次出现
            if (!map.containsKey(num)) {
                // left为num-1所在连续区间的长度,更进一步理解为:左连续区间的长度
                int left = map.getOrDefault(num - 1, 0);
                // right为num+1所在连续区间的长度,更进一步理解为:右连续区间的长度
                int right = map.getOrDefault(num + 1, 0);
                // 当前连续区间的总长度
                int curLen = left + right + 1;
                ans = Math.max(ans, curLen);
                // 将num加入map中,表示已经遍历过该值。其对应的value可以为任意值。
                map.put(num, -1);
                // 更新当前连续区间左边界和右边界对应的区间长度
                map.put(num - left, curLen);
                map.put(num + right, curLen);
            }
        }
        return ans;
    }
}

 

1218. 最长定差子序列

给你一个整数数组 arr 和一个整数 difference,请你找出并返回 arr 中最长等差子序列的长度,该子序列中相邻元素之间的差等于 difference 。
子序列 是指在不改变其余元素顺序的情况下,通过删除一些元素或不删除任何元素而从 arr 派生出来的序列。
示例 1:
输入:arr = [1,2,3,4], difference = 1
输出:4
解释:最长的等差子序列是 [1,2,3,4]。

示例 2:
输入:arr = [1,3,5,7], difference = 1
输出:1
解释:最长的等差子序列是任意单个元素。

示例 3:
输入:arr = [1,5,7,8,5,3,4,2,1], difference = -2
输出:4
解释:最长的等差子序列是 [7,5,3,1]。
//dp.getOrDefault(v - difference, 0) + 1),如果有V-DIFFERENCE的key,那么得到对应的value,如果没有,那么得到0,
//例如 arr = [1,3,5,7], difference = 1
//第一轮  dp.put(1,dp.getOrDefault(1 - 1, 0) + 1) dp.put(1,1)
//第二轮  dp.put(3,dp.getOrDefault(3 - 1, 0) + 1) dp.put(3,1)
//例如 arr = [1,2,3,4], difference = 1
//第一轮  dp.put(1,dp.getOrDefault(1 - 1, 0) + 1) dp.put(1,1)
//第二轮  dp.put(2,dp.getOrDefault(2 - 1, 0) + 1) dp.put(2,2)
//第三轮  dp.put(3,dp.getOrDefault(3 - 1, 0) + 1) dp.put(3,3)
//dp.put(4,4)
//dp[v]=dp[v−d]+1 dp[v]的子列数 为dp[v-d]的子列数+1
class Solution {
    public int longestSubsequence(int[] arr, int difference) {
        int ans = 0;
        Map<Integer, Integer> dp = new HashMap<Integer, Integer>();
        for (int v : arr) {
            dp.put(v, dp.getOrDefault(v - difference, 0) + 1);
            ans = Math.max(ans, dp.get(v));
        }
        return ans;
    }
}

300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

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

示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
//dp[n]=dp[j]+1 ,dp[n]表示最后的n结尾的递增子序列的长度,他前面肯定存在一个dp[j]+1
class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        int[] dp = new int[nums.length];
        dp[0] = 1;
        int maxans = 1;
        for (int i = 1; i < nums.length; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            maxans = Math.max(maxans, dp[i]);
        }
        return maxans;
    }
}

354. 俄罗斯套娃信封问题

给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。
当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
注意:不允许旋转信封。

 
示例 1:
输入:envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:3
解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。
示例 2:

输入:envelopes = [[1,1],[1,1],[1,1]]
输出:1

629. K个逆序对数组

给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个逆序对的不同的数组的个数。
逆序对的定义如下:对于数组的第i个和第 j个元素,如果满i < j且 a[i] > a[j],则其为一个逆序对;否则不是。
由于答案可能很大,只需要返回 答案 mod 109 + 7 的值。

示例 1:
输入: n = 3, k = 0
输出: 1
解释: 
只有数组 [1,2,3] 包含了从1到3的整数并且正好拥有 0 个逆序对。

示例 2:
输入: n = 3, k = 1
输出: 2
解释: 
数组 [1,3,2] 和 [2,1,3] 都有 1 个逆序对。

说明:
n 的范围是 [1, 1000] 并且 k 的范围是 [0, 1000]。
思路:f[i][j]定义从1-i,有j个逆序对的数组数量
class Solution {
    int mod = (int)1e9+7;
    public int kInversePairs(int n, int k) {
        long[][] f = new long[n + 1][k + 1];
        long[][] sum = new long[n + 1][k + 1];
        f[1][0] = 1;
        Arrays.fill(sum[1], 1);
        for (int i = 2; i <= n; i++) {
            for (int j = 0; j <= k; j++) {
                f[i][j] = j < i ? sum[i - 1][j] : sum[i - 1][j] - sum[i - 1][j - (i - 1) - 1];
                f[i][j] = (f[i][j] + mod) % mod;
                sum[i][j] = j == 0 ? f[i][j] : sum[i][j - 1] + f[i][j];
                sum[i][j] = (sum[i][j] + mod) % mod;
            }
        }
        return (int)f[n][k];
    }
}

 

877. 石子游戏

题解:https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247489400&idx=1&sn=0b629d3669329a6bf4f6ec71c2571ce7&chksm=fd9cbc67caeb357132fe0a1ca6240e2183748d94039100f539193d3eeb1dc223e0ddd4aa9584&token=2094656911&lang=zh_CN#rd

亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。
游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。
亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。
假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。

示例:
输入:[5,3,4,5]
输出:true
解释:
亚历克斯先开始,只能拿前 5 颗或后 5 颗石子 。
假设他取了前 5 颗,这一行就变成了 [3,4,5] 。
如果李拿走前 3 颗,那么剩下的是 [4,5],亚历克斯拿走后 5 颗赢得 10 分。
如果李拿走后 5 颗,那么剩下的是 [3,4],亚历克斯拿走后 4 颗赢得 9 分。
这表明,取前 5 颗石子对亚历克斯来说是一个胜利的举动,所以我们返回 true 。

提示:
2 <= piles.length <= 500
piles.length 是偶数。
1 <= piles[i] <= 500
sum(piles) 是奇数。
区间DP
class Solution {
    public boolean stoneGame(int[] piles) {
   int n = piles.length;
        int[][] f = new int[n + 2][n + 2]; 
        for (int len = 1; len <= n; len++) { // 枚举区间长度
            for (int l = 1; l + len - 1 <= n; l++) { // 枚举左端点
                int r = l + len - 1; // 计算右端点
                int a = piles[l - 1] - f[l + 1][r];
                int b = piles[r - 1] - f[l][r - 1];
                f[l][r] = Math.max(a, b);
            }
        }
        return f[1][n] > 0;
    }
}

375. 猜数字大小 II

我们正在玩一个猜数游戏,游戏规则如下:

我从 1 到 n 之间选择一个数字。
你来猜我选了哪个数字。
如果你猜到正确的数字,就会 赢得游戏 。
如果你猜错了,那么我会告诉你,我选的数字比你的 更大或者更小 ,并且你需要继续猜数。
每当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。如果你花光了钱,就会 输掉游戏 。
给你一个特定的数字 n ,返回能够 确保你获胜 的最小现金数,不管我选择那个数字 。
 输入:n = 10
输出:16
解释:制胜策略如下:
- 数字范围是 [1,10] 。你先猜测数字为 7 。
    - 如果这是我选中的数字,你的总费用为 $0 。否则,你需要支付 $7 。
    - 如果我的数字更大,则下一步需要猜测的数字范围是 [8,10] 。你可以猜测数字为 9 。
        - 如果这是我选中的数字,你的总费用为 $7 。否则,你需要支付 $9 。
        - 如果我的数字更大,那么这个数字一定是 10 。你猜测数字为 10 并赢得游戏,总费用为 $7 + $9 = $16 。
        - 如果我的数字更小,那么这个数字一定是 8 。你猜测数字为 8 并赢得游戏,总费用为 $7 + $9 = $16 。
    - 如果我的数字更小,则下一步需要猜测的数字范围是 [1,6] 。你可以猜测数字为 3 。
        - 如果这是我选中的数字,你的总费用为 $7 。否则,你需要支付 $3 。
        - 如果我的数字更大,则下一步需要猜测的数字范围是 [4,6] 。你可以猜测数字为 5 。
            - 如果这是我选中的数字,你的总费用为 $7 + $3 = $10 。否则,你需要支付 $5 。
            - 如果我的数字更大,那么这个数字一定是 6 。你猜测数字为 6 并赢得游戏,总费用为 $7 + $3 + $5 = $15 。
            - 如果我的数字更小,那么这个数字一定是 4 。你猜测数字为 4 并赢得游戏,总费用为 $7 + $3 + $5 = $15 。
        - 如果我的数字更小,则下一步需要猜测的数字范围是 [1,2] 。你可以猜测数字为 1 。
            - 如果这是我选中的数字,你的总费用为 $7 + $3 = $10 。否则,你需要支付 $1 。
            - 如果我的数字更大,那么这个数字一定是 2 。你猜测数字为 2 并赢得游戏,总费用为 $7 + $3 + $1 = $11 。
在最糟糕的情况下,你需要支付 $16 。因此,你只需要 $16 就可以确保自己赢得游戏。
区间DP
class Solution {
    public int getMoneyAmount(int n) {
        int[][] f = new int[n + 10][n + 10];
        for (int len = 2; len <= n; len++) {
            for (int l = 1; l + len - 1 <= n; l++) {
                int r = l + len - 1;
                f[l][r] = 0x3f3f3f3f;
                for (int x = l; x <= r; x++) {
                    int cur = Math.max(f[l][x - 1], f[x + 1][r]) + x;
                    f[l][r] = Math.min(f[l][r], cur);
                }
            }
        }
        return f[1][n];
    }
}

62. 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?

示 1:
输入:m = 3, n = 7
输出:28

示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下

示例 3:
输入:m = 7, n = 3
输出:28

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] f = new int[m][n];
        
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if(i==0||j==0){
                    f[i][j]=1;
                }
                else{ f[i][j] = f[i - 1][j] + f[i][j - 1];}
            }
        }
        return f[m - 1][n - 1];
    }
}

63. 不同路径 II

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
思路:有障碍

f[i][j]=1.0(如果i,j有障碍)2. 1 i和j都为0  3.f[i][j-1]如果i=1,为第一行 f[i-1][j] 第一列 4.其他f[i][j-1]+f[i-1][j]
    
class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m=obstacleGrid.length;
        int n=obstacleGrid[0].length;
        if(m==0||n==0) return 0;
        int [][]f=new int[m][n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(obstacleGrid[i][j]==1){
                    f[i][j]=0;
                }
                else if(i==0&&j==0){
                    f[i][j]=1;
                }
                else{
                    if(i-1>=0){
                        f[i][j]+=f[i-1][j];
                    }
                    if(j-1>=0){
                        f[i][j]+=f[i][j-1];
                    }
                }
            }
        }
        return f[m-1][n-1];
    }
}

剑指 Offer II 091. 粉刷房子

假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。

例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。
请计算出粉刷完所有房子最少的花费成本。
示例 1:
输入: costs = [[17,2,17],[16,16,5],[14,3,19]]
输出: 10
解释: 将 0 号房子粉刷成蓝色,1 号房子粉刷成绿色,2 号房子粉刷成蓝色。
     最少花费: 2 + 5 + 3 = 10。
     
示例 2:
输入: costs = [[7,6,2]]
输出: 2
先定义状态:(i从0开始)
dp[i][0] 代表 第i间房子涂红色时 前i+1间房子累计所需的 最小成本;
dp[i][1] 代表 第i间房子涂蓝色时 前i+1间房子累计所需的 最小成本;
dp[i][2] 代表 第i间房子涂绿色时 前i+1间房子累计所需的 最小成本;
确定状态转换:
题目要求:相邻的两间房子不能涂相同的颜色,那只要我们当前要涂的颜色和前一间不同就好 (废话)
假设我们现在到了第3间房子,我们考虑一下涂那种颜色:
假设涂红色:所以前一家房子只能是蓝色或者绿色,从他们中选一个最便宜的吧!
那就是 dp[2][0] = Math.min(dp[1][1],dp[1][2])+costs[2][0]
蓝色和绿色同理,由此我们可以得到状态转换方程!

        dp[i][0] = Math.min(dp[i-1][1],dp[i-1][2])+costs[i][0];
        dp[i][1] = Math.min(dp[i-1][0],dp[i-1][2])+costs[i][1];
        dp[i][2] = Math.min(dp[i-1][0],dp[i-1][1])+costs[i][2];
确定最终结果:
简单啦,当然是最后一间房子涂三种颜色中最便宜的一种啦~
class Solution {
    public int minCost(int[][] costs) {
        int n=costs.length;
        int[][] dp = new int[n][3];
        //第一件房子
        for(int i=0;i<3;i++)
            dp[0][i] = costs[0][i];
        for(int i=1;i<n;i++){
            //状态转换
            dp[i][0] = Math.min(dp[i-1][1],dp[i-1][2])+costs[i][0];
            dp[i][1] = Math.min(dp[i-1][0],dp[i-1][2])+costs[i][1];
            dp[i][2] = Math.min(dp[i-1][0],dp[i-1][1])+costs[i][2];
        }
        return Math.min( dp[n-1][0],
                    Math.min(dp[n-1][1],dp[n-1][2]));
    }
}

64. 最小路径和

image-20211118111638813

class Solution {
    //从(0,0)到(m-1,n-1).前一步就是(m-2,n-1)或者(m-1,n-2)
    public int minPathSum(int[][] grid) {
        int m=grid.length;
        int n=grid[0].length;
        int[][] f = new int[m][n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
               if(i==0&&j==0){
                   f[0][0]=grid[0][0];
               }
               if(i==0&&j>=1){
                   f[i][j]=f[i][j-1]+grid[i][j];
               }
               if(j==0&&i>=1){
                   f[i][j]=f[i-1][j]+grid[i][j];
               }
               if(i>=1&&j>=1){
               f[i][j]=grid[i][j]+Math.min(f[i-1][j],f[i][j-1]);}
            }
        }
        return f[m-1][n-1];
    }
}
//简洁版本
class Solution {
    public int minPathSum(int[][] grid) {
        int m=grid.length;
        int n=grid[0].length;
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(i == 0 && j == 0) continue;
                else if(i == 0)  grid[i][j] = grid[i][j - 1] + grid[i][j];
                else if(j == 0)  grid[i][j] = grid[i - 1][j] + grid[i][j];
                else grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
            }
        }
        return grid[m-1][n-1];
    }
}
 
 

剑指 Offer 46. 把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/solution/mian-shi-ti-46-ba-shu-zi-fan-yi-cheng-zi-fu-chua-6/

示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"

image-20220420164540917

class Solution {
    public int translateNum(int num) {
        String s = String.valueOf(num);
        int a = 1, b = 1;
        for(int i = 2; i <= s.length(); i++) {
            String tmp = s.substring(i - 2, i);//切割(0,2) 就是0和1
            int c = tmp.compareTo("10") >= 0 && tmp.compareTo("25") <= 0 ? a + b : a;
            b = a;
            a = c;
        }
        return a;
    }

 

146. LRU 缓存

/* 缓存容量为 2 */
LRUCache cache = new LRUCache(2);
// 你可以把 cache 理解成一个队列
// 假设左边是队头,右边是队尾
// 最近使用的排在队头,久未使用的排在队尾
// 圆括号表示键值对 (key, val)

cache.put(1, 1);
// cache = [(1, 1)]

cache.put(2, 2);
// cache = [(2, 2), (1, 1)]

cache.get(1);       // 返回 1
// cache = [(1, 1), (2, 2)]
// 解释:因为最近访问了键 1,所以提前至队头
// 返回键 1 对应的值 1

cache.put(3, 3);
// cache = [(3, 3), (1, 1)]
// 解释:缓存容量已满,需要删除内容空出位置
// 优先删除久未使用的数据,也就是队尾的数据
// 然后把新的数据插入队头

cache.get(2);       // 返回 -1 (未找到)
// cache = [(3, 3), (1, 1)]
// 解释:cache 中不存在键为 2 的数据

cache.put(1, 4);    
// cache = [(1, 4), (3, 3)]
// 解释:键 1 已存在,把原始值 1 覆盖为 4
// 不要忘了也要将键值对提前到队头

 

class LRUCache {
    private class Node {
        Node prev, next;
        int key, value;

        private Node(int k, int v) {
            this.key = k;
            this.value = v;
        }
    }

    private class DoubleList {
        Node head = new Node(0, 0);
        Node tail = new Node(0, 0);
        int size;

        private DoubleList() {
            head.next = tail;
            tail.prev = head;
            size = 0;
        }

        private void addFirst(Node n) {
            Node headNext = head.next;
            head.next = n;
            headNext.prev = n;
            n.prev = head;
            n.next = headNext;
            size++;
        }

        private void remove(Node n) {
            n.prev.next = n.next;
            n.next.prev = n.prev;
            size--;
        }

        private Node removeLast() {
            Node last = tail.prev;
            remove(last);
            return last;
        }

        private int size() {
            return size;
        }

    }

    // key -> Node(key, val)
    private Map<Integer, Node> map;
    // node(k1, v1) <-> Node(k2, v2)...
    private DoubleList cache;
    private int capacity;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>(capacity);
        cache = new DoubleList();
    }

    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        int val = map.get(key).value; // 利⽤ put ⽅法把该数据提前
        put(key, val);
        return val;
    }

    public void put(int key, int value) {
        Node n = new Node(key, value);
        if (map.containsKey(key)) {
            cache.remove(map.get(key));
            cache.addFirst(n);
            map.put(key, n);
        } else {
            if (cache.size() == capacity) {
                // delete last element in list
                Node last = cache.removeLast();
                map.remove(last.key);
            }
            cache.addFirst(n);
            map.put(key, n);
        }
    }
}


/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
 posted on 2022-06-16 21:32  “樂·~  阅读(428)  评论(0编辑  收藏  举报