剑指offer1 java实现,背具体题型

A  例子逻辑(核心难点):数学推导的方式获取规律,推理。
		例子是整个代码的前提,先用例子找逻辑规律,找操作的规律才是核心,如dp和dp-1。找到后才能进行编码,用多个例子完善代码逻辑。 把例子转化成代码表示。找到状态转移方程也就是逻辑规律。先找到题的规律约束。
		自己积累例子逻辑图,必须自己写例子推导。
		
B 循环(实现,简单):
		 循环用法:以循环为单位来分析。
		 循环用法:起止逻辑更新四步。
		 循环对象:是容器(树,数组,list,n)。
		 循环核心:是逻辑。循环要找到要被循环的对象才行。
		 循环表示:for、while、递归代表循环,递归的整个函数就是循环。
 
 		1定义(起):容器[指针],变量定义赋值。面向对象,理解代码的物理含义。
 		2.止if:if有return的就是结束条件,结束条件可能有好多种,结束条件的位置在循环首部。
 		3. 分类和分步:逻辑(操作)  。
				 A分类:if elseif else代表分类,if并列分类可以针对一个对象,也可以针对互补的情况。
			 	B分步: if嵌套或者if if 并列代表分步,或者按照先后顺序。
 优化逻辑:就是去重复计算,空间换时间。 
		 4.更新(指针)。 递归也是对指针变量更新。

3
在这里插入图片描述
set
在这里插入图片描述

set类+repeat 变量含义
class Solution {
    public int findRepeatNumber(int[] nums) {
        Set<Integer> set = new HashSet<Integer>();//Integer不是inteager
        int repeat = -1;
        for (int num : nums) {
            if (!set.add(num)) {//重复
                repeat = num;
                break;
            }
        }
        return repeat;
    }
}

 

在这里插入图片描述在这里插入图片描述

数组索引和值,多种情况
class Solution {
    public int findRepeatNumber(int[] nums) {
        int i = 0;
        while(i < nums.length) {
            if(nums[i] == i) {等于就跳过
                i++;
                continue;
            }
            if(nums[nums[i]] == nums[i]) return nums[i];//核心判断,找到了重复值
            int tmp = nums[i];//交换位置,i不增加继续用替换后的数判断是否重复了。
            nums[i] = nums[tmp];
            nums[tmp] = tmp;
        }
        return -1;
    }
}
 

在这里插入图片描述
在这里插入图片描述

二维数组双指针操作+三种情况大于小于等于+判空情况
    class Solution {
        public boolean findNumberIn2DArray(int[][] matrix, int target) {
            if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {¥
                return false;
            }
            int rows = matrix.length, columns = matrix[0].length;
            int row = 0, column = columns - 1;//双指针
            while (row < rows && column >= 0) {//必须>= 0,循环结束条件和指针有关系
                int num = matrix[row][column];
                if (num == target) {
                    return true;
                } else if (num > target) {
                    column--;
                } else {
                    row++;
                }
            }
            return false;
        }
    }

在这里插入图片描述
在这里插入图片描述

class Solution {
    public String replaceSpace(String s) {
        int length = s.length();
        char[] array = new char[length * 3];
        int size = 0;
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i);
            if (c == ' ') {
                array[size++] = '%';
                array[size++] = '2';
                array[size++] = '0';
            } else {
                array[size++] = c;
            }
        }
        String newStr = new String(array, 0, size);
        return newStr;
    }
}
String类遍历
 class Solution {
    public String replaceSpace(String s) {
       
		StringBuilder builder = new StringBuilder();
		for(int i = 0;i<s.length();i++){//遍历
			if(s.charAt(i)==' ') builder.append("%20");//1.空格' '不是双引号append不是appead
			else builder.append(s.charAt(i));//2.非空格charAt
		}
		
		return builder.toString();
    }
}

 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

栈和链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        Stack<ListNode> stack = new Stack<ListNode>();
        ListNode temp = head;
        while (temp != null) {//1.栈
            stack.push(temp);
            temp = temp.next;
        }
        int size = stack.size();
        int[] print = new int[size];
        for (int i = 0; i < size; i++) {//2.遍历栈
            print[i] = stack.pop().val;
        }
        return print;
    }
}

 
    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode(int x) { val = x; }
     * }
     */
     递归循环法+list存结果+数组存结果:必备
class Solution {
    ArrayList<Integer> tmp = new ArrayList<Integer>();//临时保存,最后要返回给数组的
    public int[] reversePrint(ListNode head) {
        recur(head);//调用递归
        int[] res = new int[tmp.size()];
        for(int i = 0; i < res.length; i++)
            res[i] = tmp.get(i);
        return res;结果用数组存起来
    }
    //递归三要素,递归也是一种遍历
    void recur(ListNode head) {
        if(head == null) return;//结束条件
        recur(head.next);//循环条件,递归就是循环for的作用,循环控制语句
        tmp.add(head.val);//1操作:每次递归遍历的处理核心,循环体,在递归前面就是顺序处理,在递归后面就是倒序处理
    }
}

 

在这里插入图片描述
在这里插入图片描述

有返回值+前后操作递归(分别构建他的左右子树,问题分解,最后考虑边界(空为边界))+双指针
class Solution {
    int[] preorder;//放成员里
    HashMap<Integer, Integer> dic = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        for(int i = 0; i < inorder.length; i++)//1.创建map存位置,好找
            dic.put(inorder[i], i);
        return recur(0, 0, inorder.length - 1);
    }
    TreeNode recur(int root, int left, int right) {
        if(left > right) return null;                          // 递归终止结束条件
        TreeNode node = new TreeNode(preorder[root]);          // 1.建立根节点
        int i = dic.get(preorder[root]);                       // 2.划分根节点、左子树、右子树
        node.left = recur(root + 1, left, i - 1);              // 开启左子树递归循环下一次
        node.right = recur(root + 1 + i - left , i + 1, right); // 开启右子树递归循环下一次,root + i - left + 1难点 i - left  是中间的距离
        return node;                                           // 3.回溯返回根节点
    }
}
 

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    int[] preorder; int[] inorder;
 
    public TreeNode buildTree(int[] preorder, int[] inorder) {
         this.preorder = preorder; this.inorder = inorder;
       
            return recur(0, 0, inorder.length - 1);  //初始
    }
        TreeNode recur(int root, int left, int right) {
            if(left>right)return null;
               TreeNode node = new TreeNode(preorder[root]);  
            int i =0;
            while(preorder[root]!=inorder[i])i++;
          node.left=recur(root+1,left,i-1);
           node.right=recur(root + i - left + 1,i+1,right);
           return node;
        }
}
自己写的
class Solution {
    int[] preorder; int[] inorder;
 
    public TreeNode buildTree(int[] preorder, int[] inorder) {
         this.preorder = preorder; this.inorder = inorder;
       if(preorder.length==0)return null;
            return recur(0, 0, inorder.length - 1);  //初始
    }
        TreeNode recur(int root, int left, int right) {
            if(left>right)return null;  //结束情况
            if(left==right)return new TreeNode(preorder[root]);  //结束情况
               TreeNode node = new TreeNode(preorder[root]);  
            int i =0;
            while(preorder[root]!=inorder[i])i++;
          node.left=recur(root+1,left,i-1);
           node.right=recur(root + i - left + 1,i+1,right);
           return node;
        }
}

边界i情况改写
class Solution {
    int[] preorder; int[] inorder;
 
    public TreeNode buildTree(int[] preorder, int[] inorder) {
         this.preorder = preorder; this.inorder = inorder;
       if(preorder.length==0)return null;
            return recur(0, 0, inorder.length - 1);  //初始
    }
        TreeNode recur(int root, int left, int right) {
           // if(left>right)return null;  
            if(left==right)return new TreeNode(preorder[root]);  
               TreeNode node = new TreeNode(preorder[root]);  
            int i =0;
            while(preorder[root]!=inorder[i])i++;
            if(left<=i-1){
                     node.left=recur(root+1,left,i-1);}
          if(right>=i+1){   
                     node.right=recur(root + i - left + 1,i+1,right);
            }
           return node;
        }
}

删掉多余代码
class Solution {
    int[] preorder; int[] inorder;
 
    public TreeNode buildTree(int[] preorder, int[] inorder) {
         this.preorder = preorder; this.inorder = inorder;
       if(preorder.length==0)return null;
            return recur(0, 0, inorder.length - 1);  //初始
    }
        TreeNode recur(int root, int left, int right) {
           // if(left>right)return null;  
            //if(left==right)return new TreeNode(preorder[root]);  不需要==的情况了,操作直接把最后一次的情况给处理了
               TreeNode node = new TreeNode(preorder[root]);  
            int i =0;
            while(preorder[root]!=inorder[i])i++;
            if(left<=i-1){
                     node.left=recur(root+1,left,i-1);}
          if(right>=i+1){   
                     node.right=recur(root + i - left + 1,i+1,right);
            }
           return node;
        }
}

9.在这里插入图片描述

Deque当作栈操作+删除的三种情况
class CQueue {
    Deque<Integer> stack1;
    Deque<Integer> stack2;
    
    public CQueue() {
        stack1 = new LinkedList<Integer>();
        stack2 = new LinkedList<Integer>();
    }
    
    public void appendTail(int value) {
        stack1.push(value);
    }
    
    public int deleteHead() {
        // 1.如果第二个栈为空
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {//empty
                stack2.push(stack1.pop());
            }
        } 
        if (stack2.isEmpty()) {//2.还为空就是没数了¥¥¥边界
            return -1;
        } else {
            int deleteItem = stack2.pop();//3.正确的时候
            return deleteItem;
        }
    }
}
class CQueue {
    Deque<Integer> stack1;
    Deque<Integer> stack2;
    public CQueue() {
        stack1 = new LinkedList<Integer>();
        stack2 = new LinkedList<Integer>();
    }
    
    public void appendTail(int value) {
        stack1.push(value);
    }
    
    public int deleteHead() {
        if(stack2.isEmpty()){
            while(!stack1.isEmpty()){
               stack2.push(stack1.pop()); 
            }
        }
        //非嵌套if,也不是并列if,只是分步的if
        if(stack2.isEmpty())return -1;
        else return stack2.pop();
    }
}

 

10
在这里插入图片描述
在这里插入图片描述

动态规划:一个for

class Solution {
    public int fib(int n) {
        final int MOD = 1000000007;
        if (n < 2) {//意外情况
            return n;
        }
        int p = 0, q = 0, r = 1;
        for (int i = 2; i <= n; ++i) {
            p = q; 
            q = r; 
            r = (p + q) % MOD;¥¥模大数,不让r溢出来
        }
        return r;
    }
}

 class Solution {
    public int fib(int n) {
        if(n==0)return 0;
            int p = 0, q = 1, r = 1;final int MOD = 1000000007;
             for (int i = 2; i <= n; ++i) {
               r = (p + q) % MOD; 
               p=q;
               q=r;
             }
             return r;

    }
}
 

在这里插入图片描述

递归分析+动态规划
class Solution {
    public int numWays(int n) {
        if (n <= 1) return 1;
        int a = 1, b = 1, c = 0;//从0开始的
        for (int i = 2; i <= n; i++){
            c = (a + b) % 1000000007;
            a = b;
            b = c;
        }
        return c % 1000000007;
    }
}

 

在这里插入图片描述

????
//二分查找操作(有序)+数组双指针+停止条件
class Solution {
    public int minArray(int[] numbers) {
        int low = 0;
        int high = numbers.length - 1;
        while (low < high) {//停止条件+循环条件:两个指针的大小比较
            int pivot = low + (high - low) / 2;//1.取中值
            if (numbers[pivot] < numbers[high]) {//2.比较大小  先比较右边不能重复
                high = pivot;
            } else if (numbers[pivot] > numbers[high]) {
                low = pivot + 1;
            } else {//等于的情况
                high -= 1;
            }
        }
        return numbers[low];
    }
}

 

在这里插入图片描述

二维数组双指针+回溯法(都遍历一次)+前后操作递归(三种情况if语句,成功失败和中间(没用if))
 
class Solution {
    public boolean exist(char[][] board, String word) {
        char[] s = word.toCharArray();
        for(int i = 0;i < board.length;i++){
            for(int j = 0;j < board[0].length;j++){//随意一个进行递归
                if(dfs(board,s,i,j,0)){//boolean类型返回
                    return true;
                }
            }
        }
        return false;
    }
     //迭代,整个函数就是循环,if有return的就是结束条件
    public boolean dfs(char[][] board,char[] word,int i,int j,int index){
    //边界
        //1.字母索引越界或该字母board[i][j]不是对应的word[index],则返回false
        if(i > board.length-1 || i < 0 || j > board[0].length-1 || j < 0 || board[i][j] != word[index]){
            return false;    
        }
        //2.如果已经访问到单词的最后一个字母,则返回true,正确结束条件放在最前面,必不可少。
        if(index == word.length - 1){
            return true;
        }
        char temp = board[i][j];
        //3.修改当前值,代表已经访问过,在后面的递归中存在了
        board[i][j] = '#';
        //递归换位置再代入,二维数组就有两个指针。
        boolean res = dfs(board,word,i+1,j,index+1) || dfs(board,word,i,j+1,index+1) ||
        dfs(board,word,i-1,j,index+1) || dfs(board,word,i,j-1,index+1);//用或  res暂存 
        //4.复原
        board[i][j] = temp;
        return res;
    }
}

 
对于每个节点都有n种情况的必须用递归。暴力穷举升级版.
board复原是为了防止重复进去,重复进去也算路径。注意可以重复进入的题。

回溯递归就是全部都找一次,判断每次有没有找过  
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++) {
            //从[i,j]这个坐标开始查找,随即一个个查找
            if (dfs(board, words, i, j, 0))
                return true;
        }
    }
    return false;
}

boolean dfs(char[][] board, char[] word, int i, int j, int index) {
    //1.边界的判断,如果越界直接返回false。index表示的是查找到字符串word的第几个字符,
    //2.如果这个字符不等于board[i][j],说明验证这个坐标路径是走不通的,直接返回false
    if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[index])
        return false;
    //3.如果word的每个字符都查找完了,直接返回true
    if (index == word.length - 1)
        return true;
    //4.把当前坐标的值保存下来,为了在最后复原
    char tmp = board[i][j];
    //5.然后修改当前坐标的值
    board[i][j] = '.';
    //走递归,沿着当前坐标的上下左右4个方向查找
    boolean res = dfs(board, word, i + 1, j, index + 1) || dfs(board, word, i - 1, j, index + 1) ||
            dfs(board, word, i, j + 1, index + 1) || dfs(board, word, i, j - 1, index + 1);
    //6.递归之后再把当前的坐标复原
    board[i][j] = tmp;
    return res;
}
 

在这里插入图片描述
在这里插入图片描述

队列的添加和遍历(广度优先)
class Solution {
    public int movingCount(int m, int n, int k) {
        if (k == 0) {
            return 1;
        }
        Queue<int[]> queue = new LinkedList<int[]>();
        // 向右和向下的方向数组
        int[] dx = {0, 1};
        int[] dy = {1, 0};
        boolean[][] vis = new boolean[m][n];//存结果的二维数组
        queue.offer(new int[]{0, 0});
        vis[0][0] = true;
        int ans = 1;
        while (!queue.isEmpty()) {
            int[] cell = queue.poll();//取出
            int x = cell[0], y = cell[1];
            for (int i = 0; i < 2; ++i) {
                int tx = dx[i] + x;
                int ty = dy[i] + y;//增加,更换位置操作
                if (tx < 0 || tx >= m || ty < 0 || ty >= n || vis[tx][ty] || get(tx) + get(ty) > k) {//判断是否大于K
                    continue;
                }
                queue.offer(new int[]{tx, ty});//不大于则加入队列
                vis[tx][ty] = true;
                ans++;
            }
        }
        return ans;
    }

    private int get(int x) {//拆分方法
        int res = 0;
        while (x != 0) {
            res += x % 10;
            x /= 10;
        }
        return res;
    }
}
前后操作递归法(即深度优先)+索引操作
class Solution {
    public int movingCount(int m, int n, int k) {¥¥没数组,只操作下标
        // 设置标记数组,标记元素是否访问过#¥
        boolean[][] labels = new boolean[m][n];
        // 图的dfs,起始点只有一个,因此只要从原点进行DFS即可¥¥
        return dfs(m,n,k,0,0,labels);
    }
    private int dfs(int m, int n, int k, int x, int y, boolean[][] labels){
        // 1.如果下标越界,该当前位置元素已经被访问过
        //2.或者数位和大于k,则返回 0,结束递归
        if(x < 0 || y < 0 || x >= m || y >= n || labels[x][y] || x/10+x%10+y/10+y%10 > k)//或者合并返回0
            return 0;
        // 3.如果该位置元素可达,就将标记位设置为 true
        labels[x][y] = true;//判断是否来过
        
        // 继续统计各个方向的可访问位置/* 再次进入下一层递归
     * 这里只遍历了右、下,左、上不需要遍历,
     * 因为是从左上开始的,到右下结束,所以当前节点都是从左上来的
     */
     //4.返回相加
        return 1 + dfs(m,n,k,x+1,y,labels) + dfs(m,n,k,x,y+1,labels);//向右和下递归,循环体
    }
}

class Solution {
    public int movingCount(int m, int n, int k) {
             if (k == 0) {
            return 1;
        }
          boolean[][] labels = new boolean[m][n];
                  return dfs(m,n,k,0,0,labels);
    }
       private int dfs(int m, int n, int k, int x, int y, boolean[][] labels){
           if(x < 0 || y < 0 || x >= m || y >= n || labels[x][y] || x/10+x%10+y/10+y%10 > k)//或者合并返回0
            return 0;
            labels[x][y] = true;//判断是否来过
            return 1+dfs(m,n,k,x+1,y,labels)+dfs(m,n,k,x,y+1,labels);
       }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

class Solution {
    public int cuttingRope(int n) {
                if (n < 2) {
            return n;
        }
        if(n==2)return 1;
        if(n==3)return 2;
        if(n==4)return 4;
          int[] dp = new int[n +1];
          
          dp[1] = 1;//为了防止
          dp[2] = 1;
          dp[3] = 2;
          dp[4] = 4;
          for(int i = 5; i < n + 1; i++){
           for(int j=1;j<i;j++){
               dp[i]=Math.max(dp[i],Math.max(j, dp[j]) * Math.max(i-j, dp[i - j]));
           }
        }
          return dp[n];//返回最后一个
    }
}
 
class Solution {
    public int cuttingRope(int n) {
        if(n == 2)return 1;
        if(n == 3)return 2;
        if(n == 4)return 4;
        int res = 1;
        while(n > 4){
            res *= 3;//找规律,代码最简单
            n -= 3;
        }
        return res*n;
    }
}
 

在这里插入图片描述

在这里插入图片描述

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int m=1,count=0;
        for(int j=0;j<32;j++){
            if((m&n)!=0)count++;
            m=m<<1;
        }
        return count;
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

超时:
 class Solution {
        public double myPow(double x, int n) {
            if(x==0)return 0;
            if(n!=0) {
                double res=1;
                int m=0;
                if(n<0)m=-n;
                else m=n;
                for(int i=0;i<m;i++){
                    res*=x;
                }
                if(n>0)return res;
                else return 1/res;
            }
            if(n==0){
                return 1;
            }
            return 0;
        }
    }
 对n分情况判断
class Solution {
    double res = 1;
    public double myPow(double x, int n) {
        while(n > 0){
            //每次循环就判断n是否是偶数
            if(n%2==0) {
                //如果是偶数那么原数x先平方,然后n的值除以2
                x *= x; 
                n=n/2;
			}
            res *= x;  
            n--;
        }
        //同理
        while(n < 0){
            if(n%2==0) {
                x*= x; 
                n=n/2;
			}            
            res*= 1/x;
            n++;
        }
        return res;
    }
}

 
 

在这里插入图片描述

class Solution {
       public int[] printNumbers(int n) {
        int end=(int)Math.pow(10,n)-1;
        int[] res = new int[end];
        for(int i = 0; i < end; i++)
            res[i]=i+1;//debug查看数字,从0开始的
        return res;
    }
}
 

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
       ListNode p=head;ListNode pre=head;
       if(head.val==val){
           head=head.next;
           return head;
           }
       while(p!=null){
           if(p.val==val){
               pre.next=p.next;
               return head;
           }
           pre=p;
           p=p.next;
       }
       return null;
    }
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

共有点e数字符号四个对象四种情况:模仿从头到尾遍历的过程:带入一个例子
分解变量情况(分情况):根据不同对象(切入点)分出不同的情况、多指针变量的利用
class Solution {
    public boolean isNumber(String s) {
        if(s == null || s.length() == 0){//结束
            return false;
        }
        //定义指针
        boolean numSeen = false;
        boolean dotSeen = false;
        boolean eSeen = false;
        char[] str = s.trim().toCharArray();
        for(int i = 0;i < str.length; i++){   
            if(str[i] >= '0' && str[i] <= '9'){   //1.分情况:数
                numSeen = true;
            }else if(str[i] == '.'){//2.点
                //点.之前不能出现.或者e
                if(dotSeen || eSeen){
                    return false;
                }
                dotSeen = true;
            }else if(str[i] == 'e' || str[i] == 'E'){//3.e
                //e之前不能出现e,必须出现数
                if(eSeen || !numSeen){
                    return false;
                }
                eSeen = true;
                numSeen = false;//重置numSeen,排除123e或者123e+的情况,确保e之后也出现数,bug
            }else if(str[i] == '-' || str[i] == '+'){//4符好
                //+-出现在0位置或者e/E的后面第一个位置才是合法的
                if(i != 0 && str[i-1] != 'e' && str[i-1] != 'E'){$$bug 是与的关系。都符合才是false
                //if( i==0||str[i-1]=='E'||str[i-1]=='e')return true;是错误的,比如+++,范围太大了或,符合一个就成功了
                    return false;
                }
            }else{//5.其他不合法字符
                return false;
            }
        }
        return numSeen;¥¥最后一个数字的情况,肯定是数字,前面也都没返回false。
    }
}

 

在这里插入图片描述

数组双指针变量法
class Solution {
 
        public int[] exchange(int[] nums) {
            int i=0,j=nums.length-1,tmp;//第一步设置索引和变量
            while(i<j){//结束+循环条件
            //首先使i在偶j在奇数上才能交换
                while(i<j&&(nums[i]&1)==1)i++;//1.判断奇偶¥¥i<j的条件
                while(i<j&&(nums[j]&1)==0)j--;//2.偶
                tmp=nums[i];//3.交换奇偶
                nums[i]=nums[j];
                nums[j]=tmp;
            }
            return nums;
    }
}

在这里插入图片描述

遍历链表,第一次遍历是为了计算多少个
单纯遍历两次,计数
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        int n = 0;
        ListNode node = null;
        //1.算n
        for (node = head; node != null; node = node.next) {//遍历链表,开始、结束、循环体,变量更新
            n++;
        }
        2.倒数
        for (node = head; n > k; n--) {
            node = node.next;
        }
//3.返回
        return node;
    }
}
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
         
             int n = 0;
        ListNode node = null;
        //1.算n
        for (node = head; node != null; node = node.next) {//遍历链表,开始、结束、循环体,变量更新
            n++;
        }
        ListNode nodes = head;
        for(int j=0;j<n-k;j++){//正向思维,算第几个。画图算。
                nodes=nodes.next;
        }
        return nodes;
    }
}
 

在这里插入图片描述

画图写过程,指针一定要
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head, pre = null;
        while(cur != null) {
            ListNode tmp = cur.next; // 暂存后继节点 cur.next
            cur.next = pre;          // 修改 next 引用指向
            pre = cur;               // pre 暂存 cur
            cur = tmp;               // cur 访问下一节点
        }
        return pre;
    }
}
自己写的
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null)return null;
            ListNode node=head;
            return recur(node,null);
      
    }
       private ListNode recur(ListNode cur, ListNode pre) {
           if(cur==null)return pre;
           ListNode t=cur.next;
            cur.next=pre;//赋值
            ListNode res=recur(t,cur);
             return res;
    }
}

class Solution {
    public ListNode reverseList(ListNode head) {
        return recur(head, null);    // 调用递归并返回
    }
    private ListNode recur(ListNode cur, ListNode pre) {
        if (cur == null) return pre; // 终止条件
        ListNode res = recur(cur.next, cur);  // 递归后继节点
        cur.next = pre;              // 修改节点引用指向
        return res;                  // 返回反转链表的头节点
    }
}
 
 

25.合并列表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy=new ListNode();
        ListNode p=dummy;
        ListNode p1=l1;
        ListNode p2=l2;
        while(p1!=null&&p2!=null){
            if(p1.val>p2.val){
                p.next=p2;
                p2=p2.next;
            }else{
                p.next=p1;
                p1=p1.next;
            }
            p=p.next;
        }
        if(p1==null){
            p.next=p2;
        }
        if(p2==null){
            p.next=p1;
        }
        return dummy.next;
    }
}

 
 

在这里插入图片描述
在这里插入图片描述

双重前后操作递归(递归是当前这一轮也是最后一轮,两种情况带入完善好代码)+判空为结束条件,有对象必然判空
class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {//作用是挨个比较每个节点
    //结束条件
        //递归条件
    //1.结果并列起来
        return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));前面是判断异常情况,后面是三种递归属于并列的情况,一个true即可。
    }
    boolean recur(TreeNode A, TreeNode B) {//作用是比较AB是否相同
        if(B == null) return true;//结束:B结束才为正确结局$
        if(A == null || A.val != B.val) return false;//结束2:判断a为空¥¥必然判空
        return recur(A.left, B.left) && recur(A.right, B.right);//递归条件+1.与操作
    }
}


自己写的
 递归看成一次遍历的操作,递归看成他的子树
class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
             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) {//作用是比较AB是否相同
      if(B == null) return true;
         if(A == null || A.val != B.val) return false;
        return recur(  A.left,  B.left)&& recur(  A.right,  B.right);
     }
}
 
 class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
             if(A==null||B==null)return false;//非空
             boolean f=false;
             if(A.val==B.val)   f=recur(A,B);//边界
            return  f|| isSubStructure(A.left,B)||isSubStructure(A.right,B); //操作
    }
     boolean recur(TreeNode A, TreeNode B) {//作用是比较AB是否相同
      if(B == null) return true;//边界
         if(A == null || A.val != B.val) return false;//边界
        return recur(  A.left,  B.left)&& recur(  A.right,  B.right);//操作
     }
}

在这里插入图片描述

有返回的后操作递归:先递归到头,在交换,再从尾巴到根(搞清楚递归是怎么跑的)
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;//1.操作:左子树,返回的数据##直接赋值
        root.right = left;//2.右
        return root;//3.返回递归结果
    }
}
自己写的,也可以先交换,再更新
class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root==null )return null;
        TreeNode tmp= root.left;
        root.left=root.right;
        root.right=tmp;
        TreeNode left=mirrorTree(root.left);
        TreeNode right=mirrorTree(root.right);
  
        return root;

    }
}
 

在这里插入图片描述

定义
在这里插入图片描述

在这里插入图片描述

递归
class Solution {
    public boolean isSymmetric(TreeNode root) {
    //结束
    //递归
        return root == null ? true : recur(root.left, root.right);//调用递归和判断异常情况#空为正确
    }
    boolean recur(TreeNode L, TreeNode R) {¥需要这两个索引
        if(L == null && R == null) return true;//正确结束 
        if(L == null || R == null || L.val != R.val) return false;//错误结束
        return recur(L.left, R.right) && recur(L.right, R.left);//进入下一次循环,1.操作:两个循环是与的关系
    }
}
错误代码
    public boolean isSymmetric(TreeNode root) {
            if(root==null)return true;
            if(root.left==null&&root.right==null)return true;
             if(root.left==null&&root.right!=null)return false;
                if(root.right==null&&root.left!=null)return false;
                 if(root.right.val!= root.left.val )return false;//不是只是他的左右子树
                return isSymmetric(root.left)&&isSymmetric(root.right);
    }
 

在这里插入图片描述
在这里插入图片描述

二维数组多指针
定义好边界上下左右、,行列为指针+指针的维护。有指针必维护
class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {//结束
            return new int[0];
        }
        int rows = matrix.length, columns = matrix[0].length;
        int[] order = new int[rows * columns];
        int index = 0;
        int left = 0, right = columns - 1, top = 0, bottom = rows - 1;//指针比较多,定义上下左右变量
        while (left <= right && top <= bottom) {//结束条件 +循环
            for (int column = left; column <= right; column++) {//1.
                order[index++] = matrix[top][column];
            }
            for (int row = top + 1; row <= bottom; row++) {//2.
                order[index++] = matrix[row][right];
            }
            if (left < right && top < bottom) {//结束条件
                for (int column = right - 1; column > left; column--) {¥3.从右-1开始的,模拟出每一步细节再写代码
                    order[index++] = matrix[bottom][column];
                }
                for (int row = bottom; row > top; row--) {//4.
                    order[index++] = matrix[row][left];
                }
            }
            //5.指针更新
            left++;
            right--;
            top++;
            bottom--;
        }
        return order;//6返回
    }
}

 

在这里插入图片描述

数组遍历,分解出步奏
栈的操作过程(边加边弹出)和物理意义,遍历数组
class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Stack<Integer> stack = new Stack<>();
        int i = 0;
        for(int num : pushed) {// 结束条件
            stack.push(num); // 1.   先都num 入栈
            //结束条件 判断非空
            while(!stack.isEmpty() && stack.peek() == popped[i]) { //2 相等就全出栈
                stack.pop();
                i++;//3.维护popped的指针
            }
        }
        return stack.isEmpty();//4.返回 判断依据¥¥
    }
}

在这里插入图片描述

队列遍历(广度优先)
class Solution {
    public int[] levelOrder(TreeNode root) {
        if(root==null) return new int[0];
        Queue<TreeNode> queue=new LinkedList<>(){{add(root);}};¥linklist实现的queen
        ArrayList<Integer> ans=new ArrayList<>();//集合没指针
        while(!queue.isEmpty()){//结束
            TreeNode node=queue.poll();//¥1.取出来
            ans.add(node.val);//¥2.存结果
            if(node.left!=null)queue.add(node.left);//¥3.存结果
            if(node.right!=null)queue.add(node.right);//¥4.存结果
        }
        int [] res=new int[ans.size()];
        for(int i=0;i<ans.size();i++){//5
            res[i]=ans.get(i);
        }
        
    }
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue=new LinkedList<>();
        List<List<Integer>> res=new ArrayList<>();//一行一个list。存结果
        if(root!=null) queue.add(root);//结束+1存
        while(!queue.isEmpty()){//结束
            List<Integer> tmp=new ArrayList<>();//1.临时结果,辅助队列定义
            for(int i=queue.size();i>0;i--){//结束
                TreeNode node=queue.poll();//2.取出来
                tmp.add(node.val);//3.存
                if(node.left!=null)queue.add(node.left);
                if(node.right!=null)queue.add(node.right);
            }
            res.add(tmp);//4.存本次的
        }
        return res;
    }
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

举一反三就是增加了特殊情况if语句
广度遍历利用队列,特殊情况+辅助队列+辅助链表添加头尾
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue=new LinkedList<>();
        List<List<Integer>> res=new ArrayList<>();
        if(root!=null)queue.add(root);//结束
        while(!queue.isEmpty()){//结束
            LinkedList<Integer> tmp=new LinkedList<>();//1.辅助
            for(int i=queue.size();i>0;i--){
                TreeNode node=queue.poll();//2.取出来
                if(res.size()%2==0)tmp.addLast(node.val);//3. 奇数行判断  qubi而在这里的if语句,也是核心
                else tmp.addFirst(node.val);//4. 偶数行判断 
                if(node.left!=null) queue.add(node.left);//5存.  
                if(node.right!=null)queue.add(node.right);//6存.  
            }
            res.add(tmp);//7存.  这里它的size才加一
        }
        return res;//8返回
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

变量的意义
前后操作递归

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        return recur(postorder,0,postorder.length-1);//调用递归,参数为后序数组和范围ij
    }
    boolean recur(int[] postorder,int i,int j){%返回布尔值
        if(i>=j)return true;//结束,终止条件范围为0,i=j。
        int p=i;//定义指针索引¥¥
        while(postorder[p]<postorder[j])p++;//1.找左子树的界
        int m=p;//2.代表中点
        while(postorder[p]> postorder[j])p++;//3.找右子树的界,看是否到了根节点那里,目的是判断是否是后序序列
        //4.判断是否到了
        //递归
        //5.与操作和返回
        return p==j&&recur(postorder,i,m-1)&&recur(postorder,m,j-1);//递归左右子树,返回boolean值
    }
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

回溯法+递归前后都有操作:前面的操作(一般是出口+操作代码)一直执行到尾了在执行递归代码后面的操作。而循环是从头执行到尾,比较好理解。

class Solution {
    LinkedList<List<Integer>> res = new LinkedList<>();//定义 存结果,维护结果list的list
    LinkedList<Integer> path = new LinkedList<>(); //定义 路径实体,进行增删改查的维护
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        recur(root, sum);//递归代入标准参数
        return res;
    }
    void recur(TreeNode root, int tar) {
        if(root == null) return;//结束1:啥也不返回啥也不操作
        path.add(root.val);//1.添加路劲¥¥都要加
        tar -= root.val;//2.算目标
        if(tar == 0 && root.left == null && root.right == null)//结束2:在递归前面正序执行,正确的结束条件¥必须是叶子节点
            res.add(new LinkedList(path));//3.保存当前路径,不需要手动重置path或者tar,因为path在最后都手动删除了最后一个,而tar是迭代变量,会自动随着迭代恢复原来的值¥¥
        recur(root.left, tar);//没结束继续递归
        recur(root.right, tar);
        path.removeLast();//4.维护了path,倒序执行压栈,把路径撤销,sum不用管,上次方法中没变。递归之后恢复
    }
}


共同点:
  都是向res这个ArrayList中填加了一个名为path的链表
不同点:
  res.add(new ArrayList(path)):开辟一个独立地址,地址中存放的内容为path链表,后续path的变化不会影响到res,相遇先new再把path赋值给new
  res.add(path):将res尾部指向了path地址,后续path内容的变化会导致res的变化。
 

在这里插入图片描述
在这里插入图片描述

哈希映射map的使用 +链表的使用
class Solution {
    public Node copyRandomList(Node head) {
        if(head == null) return null;结束¥¥
        Node cur = head;
        Map<Node, Node> map = new HashMap<>();//定义 维护一个map表
        // 1. 遍历复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
        while(cur != null) {
            map.put(cur, new Node(cur.val));//一对映射,新链表的节点相当于都是散的。
            cur = cur.next;
        }
        cur = head;//定义头节点
        // 2. 构建新链表的 next 和 random 指向
        while(cur != null) {
            //给新节点设置next,只能是给新节点连接
            map.get(cur).next = map.get(cur.next);//没办法同时给这两个属性赋值,因为链表得遍历,不能直接找到。
            //给新节点设置random ,只能是给新节点连接
            map.get(cur).random = map.get(cur.random);//给新节点的属性赋值
            cur = cur.next;
        }
        // 3.. 返回新链表的头节点
        return map.get(head);
    }
}

纯链表操作
 class Solution {
    public Node copyRandomList(Node head) {
        if(head == null) return null;
        Node cur = head;
        // 1. 复制各节点,并构建拼接链表
        while(cur != null) {
            Node tmp = new Node(cur.val);
            tmp.next = cur.next;
            cur.next = tmp;
            cur = tmp.next;
        }
        // 2. 构建各新节点的 random 指向
        cur = head;
        while(cur != null) {
            if(cur.random != null)¥判空
                cur.next.random = cur.random.next;
            cur = cur.next.next;
        }
        // 3. 拆分两链表
        cur = head.next;
        Node pre = head, res = head.next;//把引用赋值给了两个指针。
        while(cur.next != null) {
            pre.next = pre.next.next;
            cur.next = cur.next.next;
            pre = pre.next;
            cur = cur.next;
        }
        pre.next = null; // 单独处理原链表尾节点%
        return res;      // 返回新链表头节点%%只返回头节点就行
    }
}
 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

/*
// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;
    public Node() {}
    public Node(int _val) {
        val = _val;
    }
    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
    Node head;    //定义头结点: 即第一个节点
    Node pre; //定义全局 当前遍历节点的前导节点(或者说上一个节点),维护上一个节点¥成员变量
    





  中序遍历递归(中间操作)+前导节点的维护+数据结构操作
    public Node treeToDoublyList(Node root) {
        //dfs将前后节点连接
        dfs(root);
        //结束 入参不是空节点条件下, 将尾节点和头结点连接
        if (head != null){
            head.left = pre;
            pre.right = head;
        }
        return head;
    }
    
    private void dfs(Node root) {
        if (root == null){//结束
            return;
        }
        //1.记录右子树, 因为在递归过程中会修改掉root的右子树(因为pre可能就是root),中间修改了右子树的指向,左子树因为迭代的早,不会受到修改指向的影响
        Node right = root.right;%%%
        dfs(root.left);//循环
        //2中序遍历的第一个节点, 记录为头结点
        if (head == null){//¥¥¥用成员变量保存好第一个节点,为了和最后一个链接
            head = root;
        } else {
            //3.双向链表互相连接
            root.left = pre;//pre存放了前导节点¥¥left不是next
            pre.right = root;
        }
        //4.更新维护前导节点
        pre = root;
        dfs(right);//循环到下一次
    }
}

 

在这里插入图片描述

队列+字符串StringBuilder
public class Codec {
    public String serialize(TreeNode root) {
        if(root == null) return "[]";
        StringBuilder res = new StringBuilder("[");//面向对象,有几个对象,对对象操作。
        Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
        while(!queue.isEmpty()) {
            TreeNode node = queue.poll();//1.取出
            if(node != null) {//结束条件无处不在
                res.append(node.val + ",");//2.字符串拼接
                queue.add(node.left);//3.存
                queue.add(node.right);
            }
            else res.append("null,");//4.
        }
        res.deleteCharAt(res.length() - 1);//5多余的 去掉逗号
        res.append("]");//6.
        return res.toString();//7.转化
    }
队列(用队列一个个存树的节点再拿出来遍历,也可以递归法)+字符串
    public TreeNode deserialize(String data) {
        if(data.equals("[]")) return null;//结束
        String[] vals = data.substring(1, data.length() - 1).split(",");//1.分割
        TreeNode root = new TreeNode(Integer.parseInt(vals[0]));//1转化
        Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
        int i = 1;
        while(!queue.isEmpty()) {//结束
        //循环内都是一样的操作,构造左右子树
            TreeNode node = queue.poll();//1.取出
            if(!vals[i].equals("null")) {//结束
                node.left = new TreeNode(Integer.parseInt(vals[i]));//2.左子树
                queue.add(node.left);//3.存
            }
            i++;//4.维护
            if(!vals[i].equals("null")) {//结束
                node.right = new TreeNode(Integer.parseInt(vals[i]));//5.右子树
                queue.add(node.right);//6存
            }
            i++;//7.维护
        }
        return root;//8¥头节点不参与运算
    }
}





  
 
 

在这里插入图片描述

在这里插入图片描述

Hashset去重复+前递归思路里有双重for+List存String
多层循环控制、多步,具体业务逻辑
class Solution {
    private List<String> result;//存结果的容器
    public String[] permutation(String s) {
        //假设字符串长度为n(n>1),把字符串看成两个部分:第一个字符和后(n-1)个字符。当第一个字符是s中的某个字符时,后面的字符就是剩余的n-1个字符排列,总共的排列方式=n*(n-1个字符的排列),因此用递归来解决
        result=new ArrayList<String>();
        if(s==null||s.length()==0) return new String[0];//结束¥
        if(s.length()==1){##结束
            result.add(s);
            return result.toArray(new String[result.size()]);//list转化为数组,字符串数组去接
        }
        result.add(s);
        allSort(0);//调用
        return result.toArray(new String[result.size()]);¥¥转成String数组
    }
    public void allSort(int index){
        if(index>=result.get(0).length()-1) return;
        int size=result.size();
        int len=result.get(0).length();
        for(int i=0;i<size;i++){//对result遍历
            Set<Character> c_set=new HashSet<>();//定义:防止重复的容器set,每次都新建¥¥这里新建,每轮一个
            c_set.add(result.get(i).charAt(index));//1.一样的字符不再交换
            for(int j=index+1;j<len;j++){//2.依次和后面比较for循环,不重复则交换,index+1是后面的,index是当前位置
            j索引代表的意义
                char[] temp=result.get(i).toCharArray();//3.$$存放临时结果,改变不影响原来的结果,赋值了。这只是一个引用,虚参,实参赋值给他的。
                if(!c_set.contains(temp[j])){//4.依次对比第一个空和后面的字母是否重复,重复交换结果一样没意义,判断重复操作
                    c_set.add(temp[j]);//5.添加不重复的
                    char c=temp[index];//6.交换操作
                    temp[index]=temp[j];
                    temp[j]=c;
                    String s=new String(temp,0,temp.length);//7.转化成String对象,String s=new String(temp);//也行¥¥复制字符串
                    result.add(s);//8.添加进去
                }
            }
        }
        allSort(++index);//递归到index+1,注意位置在for的后面。
    }
}

自己实现,递归改循环
 

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

单次快排+数组多指针
数组+双指针+前操作递归=快排查找(前操作递归是经常用的--正序好理解,后操作递归是树,后递归比较难理解,递归比循环的优势:可以自由控制变量范围灵活多变,但是要确定好结束条件;同时递归也是一种分解思想)
class Solution {
    public int majorityElement(int[] nums) {
		int n = nums.length;
		return qSort(nums,0,n-1,n/2);//递归变量,增加了个中位数,就是目的:找中位数,不用管奇偶,中间肯定过半了
		
	}
	public int qSort(int[] nums,int left,int right,int mid) {
		int i=left,j=right;
		while(i<j) {//确定最左边第一个值的位置
			while(i<j && nums[j]>=nums[left])--j;//必须先右边先减,否则结束报错bug
			while(i<j && nums[i]<=nums[left])++i;
			swap(nums,i,j);
		}
		swap(nums,i,left); //把左边的位置放好
		if(i>mid) return qSort(nums,left,i-1,mid);//看是否是中点,超过了中点,没超过就再次递归进入下一趟循环
		if(i<mid) return qSort(nums,i+1,right,mid);//递归就是,进入下一次循环
		return nums[mid];//是中点
	}
	public void swap(int[] nums,int x,int y) {//多次用的方法要封装
		int tmp = nums[x];
		nums[x]=nums[y];
		nums[y]=tmp;
	}
}

 

快排原理图,最后要把ij和 l最左边交换值,才完成一次快排
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int[] vec = new int[k];
        Arrays.sort(arr);
        for (int i = 0; i < k; ++i) {
            vec[i] = arr[i];
        }
        return vec;
    }
}

 
优先队列的比较(大根堆排序)
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {//输入k目标变量
        int[] vec = new int[k];
        if (k == 0) { // 排除 0 的情况
            return vec;
        }
        PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
            public int compare(Integer num1, Integer num2) {
                return num2 - num1;//降序排列 大根堆,Comparator<Integer>必须指定int
            }
        });//都在一个大括号里,比较器
        for (int i = 0; i < k; ++i) {//先添加k个到队列,维护者k个最小的
            queue.offer(arr[i]);//添加
        }
        for (int i = k; i < arr.length; ++i) {
            if (queue.peek() > arr[i]) {//跟队列头最大的比较
                queue.poll();//删除队头
                queue.offer(arr[i]);//加入新数据
            }
        }
        for (int i = 0; i < k; ++i) {//依次输出最小的k个
            vec[i] = queue.poll();
        }
        return vec;
    }
}

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int[] vec = new int[k];
        if (k == 0) { // 排除 0 的情况
            return vec;
        }
        PriorityQueue<Integer> queue=new PriorityQueue<>((x,y)->(y-x));
        for (int i = 0; i < k; ++i) {//先添加k个到队列,维护者k个最小的
            queue.offer(arr[i]);
             }
        for (int i = k; i < arr.length; ++i) {
            if (queue.peek() > arr[i]) {//跟队列头最大的比较
                queue.poll();//删除队头
                queue.offer(arr[i]);//加入新数据
            }
        }
        for (int i = 0; i < k; ++i) {//依次输出最小的k个
            vec[i] = queue.poll();
        }
        return vec;
    }
}
 

比较必须指定int类型
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

经典快排=前递归+数组双指针
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        // 最后一个参数表示我们要找的是下标为k-1的数
        return quickSearch(arr, 0, arr.length - 1, k - 1);
    }

    private int[] quickSearch(int[] nums, int lo, int hi, int k) {
        // 每快排切分1次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边所有的数;
        int j = partition(nums, lo, hi);//快排
        if (j == k) {
            return Arrays.copyOf(nums, j + 1);
        }
        // 否则根据下标j与k的大小关系来决定继续切分左段还是右段。调不同参数的同一个函数就是递归。
        return j > k? quickSearch(nums, lo, j - 1, k): quickSearch(nums, j + 1, hi, k);
    }

    // 快排切分,返回下标j,使得比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边。
    private int partition(int[] nums, int lo, int hi) {
        int v = nums[lo];
        int i = lo, j = hi + 1;
        while (true) {
            while (++i <= hi && nums[i] < v);
            while (--j >= lo && nums[j] > v);
            if (i >= j) {
                break;
            }
            int t = nums[j];
            nums[j] = nums[i];
            nums[i] = t;
        }
        nums[lo] = nums[j];
        nums[j] = v;
        return j;¥¥ij是相等的
    }
}

 

在这里插入图片描述
在这里插入图片描述

优先队列的使用+中位数分奇偶两种情况来维护大小优先队列
堆排序:大根堆小根堆的使用,奇偶两种情况。。。要理解代码背后的含义概念,背会概念
class MedianFinder {
    // 大顶堆存储较小一半的值
    PriorityQueue<Integer> maxHeap;
    // 小顶堆存储较大一般的值
    PriorityQueue<Integer> minHeap;
    /** initialize your data structure here. */
    public MedianFinder() {
        maxHeap = new PriorityQueue<Integer>((x, y) -> (y - x));//大顶堆的实现
        minHeap = new PriorityQueue<Integer>();
    }
    //增加的方法
    public void addNum(int num) {
        // 长度为奇数时先放入小顶堆,重新排序后在插入到大顶堆
        if(maxHeap.size() != minHeap.size()) {
            minHeap.add(num);//加进去就能重新排好
            maxHeap.add(minHeap.poll());
        } else {
            // 长度为偶数时先放入大顶堆,重新排序后在插入到小顶堆
            maxHeap.add(num);
            minHeap.add(maxHeap.poll());
        }
    }
    
    public double findMedian() {
    //分为奇偶两种情况
        if(maxHeap.size() != minHeap.size()) {
            return minHeap.peek();
        } else {
            return (maxHeap.peek() + minHeap.peek()) / 2.0;
        }
    }
}


  

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

递归分解思想(动态规划)+比较选最大类型的规律
class Solution {
    public int maxSubArray(int[] nums) {//直接用nums当dp
        int res = nums[0];//初始值0,
        for(int i = 1; i < nums.length; i++) {//遍历从1开始,0是初始值
            nums[i] += Math.max(nums[i - 1], 0);//规律选最大
            res = Math.max(res, nums[i]);
        }
        return res;
    }
}
 
 
数组规律,单变量最大值
class Solution {
    public int maxSubArray(int[] nums) {

        int length = nums.length;
        // 最大总和值
        int res = Integer.MIN_VALUE;//不能为0
        // 当前总和
        int curSum = 0;

        for (int i = 0; i < length; i++) {
            if (curSum < 0) {
                // 如果 i 之前总和值小于0,那就从 i 开始重新计算
                curSum = nums[i];
            } else {
                // 否则加上当前的值
                curSum += nums[i];
            }

            // 寻找最大值
            if (curSum > res) {
                res = curSum;
            }
        }

        return res;
    }
}
第二次
class Solution {
    public int maxSubArray(int[] nums) {
        int length=nums.length;
        int res=nums[0];
        int curSum=0;
         for (int i = 0; i < length; i++) {
             if(curSum<0)curSum=nums[i];
             else curSum+=nums[i];
             if(curSum>res)res=curSum;//curSum是变化的指针,而res是最大值变量。
        }
        return res;
    }
}
 

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

递归分解法
找规律用分解法:对象分解成多个部分+结果分解成多种情况(多个步骤)

class Solution {
    public int countDigitOne(int n) {
        //高位
        int high = n;
        //低位
        int low = 0;
        //当前位
        int cur = 0;
        int count = 0;
        int num = 1;
        while (high != 0 || cur != 0) {
            cur = high % 10;
            high /= 10;
            //这里我们可以提出 high * num 因为我们发现无论为几,都含有它 
            if (cur == 0) count += high * num;
            else if (cur == 1) count += high * num + 1 + low; 
            else count += (high + 1) * num;
            //低位
            low = cur * num + low;                  
            num *= 10;
        }
        return count;
    }
}

 

在这里插入图片描述

找规律的方法:代入例子,分变量列,分步列出,最后找出规律
在这里插入图片描述

class Solution {
    public int findNthDigit(int n) {
        int digit = 1;
        long start = 1;初始值三个
        long count = 9;
        while (n > count) { // 1.
            n -= count;//剩下多少位了,含义要确定。
            digit += 1;
            start *= 10;
            count = digit * start * 9;
        }
        long num = start + (n - 1) / digit; // 2.确定该数字,举例输入13,得到num=11,n=4
        return Long.toString(num).charAt((n - 1) % digit) - '0';  // 3.确定该数字的第几个,代码都有确切含义,都翻译过来。n代表剩下多少位了。Long转化数字为字符串。
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

字符串比较大小(大数问题)、前操作(数组双指针操作)递归(快排排序,每次把一个放在正确位置上)
class Solution {
    public String minNumber(int[] nums) {
        String[] strs = new String[nums.length];
        for(int i = 0; i < nums.length; i++)
            strs[i] = String.valueOf(nums[i]);
        quickSort(strs, 0, strs.length - 1);
        StringBuilder res = new StringBuilder();//存结果
        for(String s : strs)
            res.append(s);
        return res.toString();
    }
    void quickSort(String[] strs, int l, int r) {$$数组双指针
        if(l >= r) return;//不进行任何操作,停止条件,一个元素就不用比较交换位置了
        int i = l, j = r;           //l不是1.
        String tmp = strs[i];
        while(i < j) {//前操作,换位置
            while((strs[j] + strs[l]).compareTo(strs[l] + strs[j]) >= 0 && i < j) j--;//&& i < j不用他就会一直移动越界
            while((strs[i] + strs[l]).compareTo(strs[l] + strs[i]) <= 0 && i < j) i++;
            tmp = strs[i];//换i j变量的值
            strs[i] = strs[j];
            strs[j] = tmp;
        }
        strs[i] = strs[l];//换i l的值
        strs[l] = tmp;
        quickSort(strs, l, i - 1);//递归调用,换不同参数,就跟二叉树一样,左右
        quickSort(strs, i + 1, r);//左右递归
    }
}

 

在这里插入图片描述

排序工具+比较工具+字符串
class Solution {
    public String minNumber(int[] nums) {
        String[] strs = new String[nums.length];
        for(int i = 0; i < nums.length; i++)//数组转化成字符串组
            strs[i] = String.valueOf(nums[i]);//int转字符串
        Arrays.sort(strs, (x, y) -> (x + y).compareTo(y + x));//排序,升序排序,结果>0说明y>x
        StringBuilder res = new StringBuilder();
        for(String s : strs)
            res.append(s);
        return res.toString();
    }
}
 
 

在这里插入图片描述

在这里插入图片描述

分解法找规律:动态规划,分情况的菲薄那且数列
分解法:其实就是边界例子+一般例子

class Solution {
    public int translateNum(int num) {
        String str = String.valueOf(num);
        // 使用str.length()+1
        int[] dp = new int[str.length()+1];
        // 我们只能确定第一位有几种翻译方法
        // 真正是从 1 开始,初始化 0 是用来来防止 i-2 出现 -1 的情况
        dp[0] = 1;//边界
        dp[1] = 1;

        for (int i = 2; i <= str.length(); i++) {//范围,遍历完。考虑开始和结束
            // 如果是两位数的话,需要在10~25之间才有效,否则只能翻译一个数字
            if (str.substring(i-2, i).compareTo("10") >= 0 && str.substring(i-2, i).compareTo("25") <= 0) {//字符串比较大小
                dp[i] = dp[i-1] + dp[i-2];
            } else {
                dp[i] = dp[i-1];
            }
        }

        return dp[str.length()];
    }
}
 
优化
class Solution {
    public int translateNum(int num) {
        String str = String.valueOf(num);
        // 我们只能确定第一位有几种翻译方法
        int a = 1;
        int b = 1;
        for (int i = 2; i <= str.length(); i++) {
            // 如果是两位数的话,需要在10~25之间才有效,否则只能翻译一个数字
            if (str.substring(i-2, i).compareTo("10") >= 0 && str.substring(i-2, i).compareTo("25") <= 0) {¥直接字符串比较大小
                int temp = a + b;
                a = b;
                b = temp;
            } else {
                a = b;
            }
        }
        return b;
    }
}
 

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

二维数组dp+举出四个例子(三个边界)(分解法找规律):动态规划
class Solution {
    public int maxValue(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        for(int i = 0; i < m; i++) {//首先都是遍历
            for(int j = 0; j < n; j++) {
                if(i == 0 && j == 0) continue;// 
                if(i == 0) grid[i][j] += grid[i][j - 1] ;//特殊边界条件
                else if(j == 0) grid[i][j] += grid[i - 1][j];
                else grid[i][j] += Math.max(grid[i][j - 1], grid[i - 1][j]);//else 不加else则每次进来都执行,不是分支语句了
            }
        }
        return grid[m - 1][n - 1];//最后一个返回
    }
}

 class Solution {
    public int maxValue(int[][] grid) {
        int m=grid.length,n=grid[0].length;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(i==0&&j==0) grid[i][j]=grid[0][0];//不用continue 
              else if(i == 0) grid[i][j] += grid[i][j - 1] ;// 这里不用continue 必须都是else if 否则执行完上一句还会来判断这句。 
                else if(j==0)grid[i][j]+=grid[i-1][j];
                else grid[i][j]+=Math.max(grid[i][j-1],grid[i-1][j]);
            }
        }
        return grid[m-1][n-1];
    }
}
以上代码逻辑清晰,和转移方程直接对应,但仍可提升效率:当 gridgrid 矩阵很大时, i = 0i=0 或 j = 0j=0 的情况仅占极少数,相当循环每轮都冗余了一次判断。因此,可先初始化矩阵第一行和第一列,再开始遍历递推。

 

 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分情况的分解法(动态规)+hashMap映射表记录+变量结果
class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> dic = new HashMap<>();//保存char的位置,维护位置。
        int res = 0, tmp = 0;//设置最大值结果,动态值
        for(int j = 0; j < s.length(); j++) {
            int i = dic.getOrDefault(s.charAt(j), -1);          // 获取索引 i,不是getIndexof
            dic.put(s.charAt(j), j);                                    // 更新哈希表,覆盖了原来的char位置
            tmp = tmp < j - i ? tmp + 1 : j - i;                   // dp[j - 1] -> dp[j]维护的是临时距离,,tmp < j - i 判断关键,找规律
            res = Math.max(res, tmp);                           // max(dp[j - 1], dp[j])
        }
        return res;
    }
}

错误代码:
 class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character,Integer> dic=new HashMap<>();
        int res = 0, tmp = 0;//设置全局最大值结果,临时结果值
        for(int j = 0; j < s.length(); j++) {
            //没有就加入
        if(dic.getOrDefault(s.charAt(j),-1)==-1){
            dic.put(s.charAt(j),j);
            tmp++;
        }
        //如果有了
        else{
            int d=j-dic.get(s.charAt(j));
            dic.put(s.charAt(j),j);//维护 不是set
            tmp = tmp <d ? tmp + 1 :d;  
             if(res<tmp)res=tmp;
             else tmp=1;
        }
       // 判断最大值是否变化
        if(tmp>res)res=tmp;
    }
     return res;
    }
}


class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> dic = new HashMap<>();//保存char的位置,维护位置。
        int res = 0, tmp = 0;//设置最大值结果,动态值
        for(int j = 0; j < s.length(); j++) {
            int i = dic.getOrDefault(s.charAt(j), -1);          // 获取索引 i,不是getIndexof
            dic.put(s.charAt(j), j);                                    // 更新哈希表,覆盖了原来的char位置
            if(tmp>=j-i){//这才是判断依据,存在不是判断依据,两种情况
                tmp=j-i;
                }
            else tmp++;
            res = Math.max(res, tmp);    
        }    
        return res;
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

多个参数共同决定一个dp,
public int nthUglyNumber(int n) {
        int[] result = new int[n];
        int n2 = 0,n3 = 0,n5 = 0;
        result[0] = 1;//边界,可代入例子来理解代码
        for (int i = 1; i < n; i++) {//i不参与运算
            int num2 = result[n2] * 2;//result是核心操作对象,相当于给res中的每个数轮番乘以235比大小
            int num3 = result[n3] * 3;
            int num5 = result[n5] * 5;
            int min = Math.min(Math.min(num2, num3), num5);//找出最小的
            result[i] = min;
            if (min ==  num2) n2 ++;
            if (min ==  num3) n3 ++;
            if (min ==  num5) n5 ++;
        }
        return result[n-1];
    }
利用set 小根堆(头进尾巴出) +循环
 class Solution {
    private int[] uglyNumber = {2,3,5};
    public int nthUglyNumber(int n) {
        Set<Long> set = new HashSet<>();
        //创建小根堆,每次出堆的都是最小值
        PriorityQueue<Long> queue = new PriorityQueue<>();
        queue.add(1L);
        //记录出堆的个数,出堆的元素完全按照从小到大排序
        int count = 0;
        while (! queue.isEmpty()){
            long cut = queue.poll();
            //如果出堆的个数>=n,当前cut就是第n个丑数
            if(++count >= n){//++count == n 也行,循环退出条件
                return (int) cut;
            }
            for(int num : uglyNumber){
                //排除重复的数字
                if(! set.contains(num * cut)){
                    queue.add(num * cut);
                    set.add(num * cut);
                }
            }
        }
        return -1;
    }
}

class Solution {
        private int[] uglyNumber = {2,3,5};
    public int nthUglyNumber(int n) {
        PriorityQueue<Long> queue = new PriorityQueue<>();
        Set<Long> set = new HashSet<>();
        queue.add(1L);
                int count = 1;//从1开始计数,他是停止变量
        while (! queue.isEmpty()){//这个循环灵活
            long tmp = queue.poll();
            if(count == n){
                return (int) tmp;
            }
       count++;
        for(int num:uglyNumber){
            if(!set.contains(num*tmp)){
                set.add(num*tmp);
                queue.add(num*tmp);
            }
        }
        }

    return -1;
}
}

1.基本类型:long,int,byte,float,double
2.对象类型:Long,Integer,Byte,Float,Double其它一切java提供的,或者你自己创建的类。
 

在这里插入图片描述

hashmap计数

class Solution {
    public char firstUniqChar(String s) {
        Map<Character,Integer> frequency=new HashMap<Character,Integer>();
        for(int i=0;i<s.length();i++){
            char ch=s.charAt(i);
            frequency.put(ch,frequency.getOrDefault(ch,0)+1);//计数
        }
        for(int i=0;i<s.length();i++){
            if(frequency.get(s.charAt(i))==1){
                return s.charAt(i);
            }
        }
        return ' ';
    }
}
 自己写的
 class Solution {
    public char firstUniqChar(String s) {
        Map<Character,Integer> frequency=new HashMap<Character,Integer>();
     for(int i=0;i<s.length();i++){
            if(frequency.containsKey(s.charAt(i)) ){
                frequency.put(s.charAt(i),frequency.get(s.charAt(i))+1);//indexOf返回string,charAt是字符
            }
            else
                frequency.put(s.charAt(i),1);//put不是add
        }   
        for(int i=0;i<s.length();i++){
            if(frequency.get(s.charAt(i))==1)return s.charAt(i);
        }
        return ' ';
    }
}

在这里插入图片描述

没法用循环了
典型后操作(归并操作)递归(考虑极端例子和普通) +辅助数组+考虑剩余的例子
public class MergeSort {
    public static void merge(int[] a, int low, int mid, int high) {
        int[] temp = new int[high - low + 1];//用到辅助数组,暂存序列的
        int i = low;// 左指针
        int j = mid + 1;// 右指针
        int k = 0;
        // 把较小的数先移到新数组中
        while (i <= mid && j <= high) {
            if (a[i] < a[j]) {
                temp[k++] = a[i++];
            } else {
                temp[k++] = a[j++];
            }
        }
        // 把左边剩余的数移入数组
        while (i <= mid) {
            temp[k++] = a[i++];
        }
        // 把右边边剩余的数移入数组
        while (j <= high) {
            temp[k++] = a[j++];
        }
        // 把新数组中的数覆盖nums数组
        for (int k2 = 0; k2 < temp.length; k2++) {
            a[k2 + low] = temp[k2];//递归从low开始设置进去,辅助的位置赋值给真正的位置。
        }
    }

    public static void mergeSort(int[] a, int low, int high) {
        int mid = (low + high) / 2;
        if (low < high) {//也是递归终止条件,=就不做任何事情,带入边界例子当low=high,代入mergeSort则不进行任何操作;当low+1=high时,会把这两个数字合并起来。
            // 左边递归
            mergeSort(a, low, mid);
            // 右边递归
            mergeSort(a, mid + 1, high);
            // 左右归并
            merge(a, low, mid, high);//递归操作:左右合起来,同一个数组a不用多加
            System.out.println(Arrays.toString(a));
        }

    }

    public static void main(String[] args) {
        int a[] = { 51, 46, 20, 18, 65, 97, 82, 30, 77, 50 };
        mergeSort(a, 0, a.length - 1);
        System.out.println("排序结果:" + Arrays.toString(a));
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从分的最后开始排列:理清楚递归的次序,先从7(i) 3(j)开始,第二次是2 6,第三次是3726 第四次是0 1
在这里插入图片描述

在这里插入图片描述

后(逆序操作,边界情况考虑)递归(两种例子) 
归并应用具体情况
class Solution {
    int[] nums, tmp;
    public int reversePairs(int[] nums) {
        this.nums = nums;
        tmp = new int[nums.length];
        return mergeSort(0, nums.length - 1);
    }
    private int mergeSort(int l, int r) {
        // 终止条件
        if (l >= r) return 0;
        // 递归划分
        int m = (l + r) / 2;
        int res = mergeSort(l, m) + mergeSort(m + 1, r);
        // 合并阶段,没有单独当成方法
        int i = l, j = m + 1;//指针
        for (int k = l; k <= r; k++)
            tmp[k] = nums[k];
        for (int k = l; k <= r; k++) {//遍历num的 lr区域并赋值的过程
        //分情况看,两种例子:结束和普通
            if (i == m + 1)//左边结束了,右边加进去
                nums[k] = tmp[j++];
            else if (j == r + 1 || tmp[i] <= tmp[j])//顺序和右边结束了合并在了一起。
                nums[k] = tmp[i++];
            else {//逆序
                nums[k] = tmp[j++];
                res += m - i + 1; // 统计逆序对
            }
        }
        return res;
    }
}

分清指针和变量的意义,画图,数组进行遍历
class Solution {
    int[] nums, tmp;int res;
    public int reversePairs(int[] nums) {
        this.nums = nums;
        tmp = new int[nums.length];
        return mergeSort(0, nums.length - 1);
    }
    private int mergeSort(int l, int r) {
        // 终止条件
        if (l >= r) return 0;
        // 递归划分
        int m = (l + r) / 2;
        int res = mergeSort(l, m) + mergeSort(m + 1, r);
        res=merge(l,r,m,res);//res+=重复加了,,r不是m+1。l r是本次合并的范围,res变量代表每次逆序和
        return res;
    }
    private int merge(int l, int r,int m,int res) {
        int i = l, j =m+1;//指针,分别代表了合并左边数组和右边数组的指针,j不是r
        for (int k = l; k <= r; k++)
            tmp[k] = nums[k];//tmp代表了数据备份
        for (int k = l; k <= r; k++) {//对当前范围的数组进行遍历
        //分情况看,两种例子:结束和普通
            if (i == m + 1)//左边结束了,右边加进去
                nums[k] = tmp[j++];
            else if (j == r + 1 || tmp[i] <= tmp[j])//顺序和右边结束了合并在了一起。
                nums[k] = tmp[i++];
            else {//逆序
                nums[k] = tmp[j++];
                res += m - i + 1; // 统计逆序对
            }
        }
        return res;
    }
}

 

在这里插入图片描述
在这里插入图片描述 视频实现

在这里插入图片描述

图解法找规律,就一个例子即可
/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
  let a = headA,
    b = headB;
  while (a != b) {
    // a 走一步,如果走到 headA 链表末尾,转到 headB 链表
    a = a != null ? a.next : headB;//两种情况可用逻辑符号
    // b 走一步,如果走到 headB 链表末尾,转到 headA 链表
    b = b != null ? b.next : headA;
  }
  return a;
};
 超时
 public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
         ListNode a = headA,
    b = headB;
  while (a != b) {
    if(a.next!=null)a=a.next;
    else a=headB;
    if(b.next!=null)b=b.next ;
    else b=headA;
  }
  return a;
}
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    //统计链表A和链表B的长度
    int lenA = length(headA), lenB = length(headB);
    //如果节点长度不一样,节点多的先走,直到他们的长度一样为止
    while (lenA != lenB) {
        if (lenA > lenB) {
            //如果链表A长,那么链表A先走
            headA = headA.next;
            lenA--;
        } else {
            //如果链表B长,那么链表B先走
            headB = headB.next;
            lenB--;
        }
    }
    //然后开始比较,如果他俩不相等就一直往下走
    while (headA != headB) {
        headA = headA.next;
        headB = headB.next;
    }
    //走到最后,最终会有两种可能,一种是headA为空,
    //也就是说他们俩不相交。还有一种可能就是headA
    //不为空,也就是说headA就是他们的交点
    return headA;
}
//统计链表的长度
private int length(ListNode node) {
    int length = 0;
    while (node != null) {
        node = node.next;
        length++;
    }
    return length;
}

 

在这里插入图片描述

在这里插入图片描述

前(二分查找)操作 二次递归(根据需求) 。循环就等于递归,只是形式不一样。
//也是递归,递归就是调用不同参数的相同方法。
int cnt = 0;    // 计数器count
    public int search(int[] nums, int target) {
        // 初始化low = 0, high = nums.length - 1
        helper(nums, target, 0, nums.length - 1);
        return cnt;
    }

    // 根据算法设计分3种情况,分情况,分步奏
    public void helper(int[] nums, int target, int low, int high) { 
        if (low <= high) {  //停止条件例子,两个指针相等
            int mid = (high - low) / 2 + low;
            if (nums[mid] == target) {
                cnt++;      // 计数一次
                helper(nums, target, low, mid - 1);     
                helper(nums, target, mid + 1, high);    
            } else if (nums[mid] > target) {
                helper(nums, target, low, mid - 1);//没用循环,全部都是递归
            } else {
                helper(nums, target, mid + 1, high);
            }
        }
    }
纯递归实现,递归改了参数调用自己,
 class Solution {
    int cnt=0;
    public int search(int[] nums, int target) {
        helper(nums, target, 0, nums.length - 1);
        return cnt;

    }
    public void helper(int[] nums, int target, int low, int high) { 
        if(low > high)return;
          int mid = (high - low) / 2 + low;
        if (nums[mid] == target) {
                cnt++;      // 计数一次
         helper(nums, target, low, mid - 1);
          helper(nums, target, mid+1, high);
    }
    else if(nums[mid] > target)
     helper(nums, target,low, mid - 1);//low不是0
     else helper(nums, target, mid+1,high);
}
}

自己写的
    public void helper(int[] nums, int target, int low, int high) {
        if(low<=high){
            int mid=(low+high)/2;
            if(nums[mid]<target)
            helper(nums,target,mid+1,high);
            else if(nums[mid]>target)
            helper(nums,target,low,mid-1);
            else{
                cnt++;
                if(mid+1<=high&&nums[mid+1]==target)
                     helper(nums,target,mid+1,high);
                if(mid-1>=low&&nums[mid-1]==target)
                    helper(nums,target,low,mid-1);
            }

    }
     
}

在这里插入图片描述

 循环实现+数组指针和值的关系+数组双指针(二分法)+图解法
 循环和递归:结束条件+指针维护+初始条件,
 区别在于
 1.递归是多次n次循环,循环是一次,当遇到三重循环以上 而且返回数值 要用递归
 2.递归是可以实现(操作和递归的)正序逆序,最后执行结束if语句;循环只能正序,不能倒着来。
 3.他们的结束语句不太一样,循环是结束条件while(i>j)+if语句,迭代是if
class Solution {
    public int missingNumber(int[] nums) {
        int i = 0, j = nums.length - 1;
        while(i <= j) {//括号里面的 相当于递归的终止条件(相反),递归过程就是循环
            int m = (i + j) / 2;//相当于递归里的操作
            if(nums[m] == m) i = m + 1;//相当于递归过程,改变参数
            else j = m - 1;
        }
        return i;//i刚好是空出来的位置的索引
    }
}
 
 递归实现+二分法+缺失具体例子   
 
 class Solution {
    public int missingNumber(int[] nums) {
        //二分
        return binarySearch(nums, 0, nums.length - 1);//初始条件
    }
    public int binarySearch(int[] nums, int left, int right){
        if(left >= right){                         //递归终止条件,这是最后执行的,返回
        //此时分两种情况。要举出例子才能想得到。。。
            //1.如果跟左边相等是说明是右边缺失
            if(nums[left] == left){
                return left+1;
            //2.不等肯定是左边缺失啦
            }else{
                return left;
            }
        }
        int mid = left + (right-left)/2;
        System.out.println(mid);
        //相等说明左序列正常,往右找
        if(nums[mid] == mid){
            return binarySearch(nums, mid + 1, right);//指针维护i++一样,这是最先执行的
        }else{
            return binarySearch(nums, left, mid - 1);
        }
    }
}
 

在这里插入图片描述

后序遍历(三种边界例子)

代码自己默背默写一遍。报错就重新理一遍,看哪里代码逻辑有问题。
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);
    }
}

 

在这里插入图片描述

典型后递归有返回值(递归到极端例子后返回的值对比之后再+1返回)(实质还是两种例子)
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;//空节点极端例子
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;//普通节点例子下:都要比较最大值
    }
}

 队列实现( 广度遍历,递归也可以实现)+辅助队列+计算深度
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        List<TreeNode> queue = new LinkedList<>() {{ add(root); }}, tmp;
        int res = 0;//存结果的
        while(!queue.isEmpty()) {
            tmp = new LinkedList<>();
            for(TreeNode node : queue) {//把队列都清空才深度加1
                if(node.left != null) tmp.add(node.left);
                if(node.right != null) tmp.add(node.right);
            }
            queue = tmp;//最后在赋值给队列,辅助队列
            res++;
        }
        return res;
    }
}

 

在这里插入图片描述

后操作(比较深度)递归有返回(两种极端例子 )
class Solution {
    public boolean isBalanced(TreeNode root) {
        return recur(root)!=-1;
    }
    public int recur(TreeNode root){
    //三种结果
        if(root==null)return 0;//空的极端例子
        int left=recur(root.left);
        if(left==-1)return -1;//极端例子,左子树不是平衡树的例子$
        int right=recur(root.right);
        if(right==-1)return -1;
        //返回当前节点的深度
        return Math.abs(left-right)<2?Math.max(left,right)+1:-1;$//返回深度或者-1
    }
}


class Solution {
    public boolean isBalanced(TreeNode root) {
        return recur(root)!=-1;
    }
    public int recur(TreeNode root){
    //三种结果
        if(root==null)return 0;//空的极端例子
        int left=recur(root.left);
        if(left==-1)return -1;//极端例子,左子树不是平衡树的例子$
        int right=recur(root.right);
        if(right==-1)return -1;
        //返回当前节点的深度
        int b=0;
        if(left>right)
            b=left-right;
            else
            b=right-left;
            if(b>1)return-1;
            else
        return  1+Math.max(left,right);
    }
}

在这里插入图片描述

找出规律:与操作和异或操作的规律,单个例子的分步执行来找出规律。
例子的分类和分步两个属性。 
class Solution {
    public int[] singleNumbers(int[] nums) {
        int x = 0, y = 0, n = 0, m = 1;
        for(int num : nums)               // 1. 遍历异或
            n ^= num;$
        while((n & m) == 0)               // 2. 循环左移,计算 m
            m <<= 1;
        for(int num: nums) {              // 3. 遍历 nums 分组$
            if((num & m) != 0) x ^= num;  // 4. 判断m所在的位置是否为1分为两组,这是与的特点
            else y ^= num;                // 4. 当 num & m == 0
        }
        return new int[] {x, y};          // 5. 返回出现一次的数字
    }
}

 

在这里插入图片描述
在这里插入图片描述

变量循环改变
可以将程序算一遍:双循环结构,可以改编程序,要将程序结构理解掌握并能改写。
class Solution {
    public int singleNumber(int[] nums) {
        int ans=0;//返回的结果
        int bit=0;
        for(int i=31;i>=0;i--){//int32位,因为不知道Int是多少位,从高位算到低位。拉皮条一样。
            for(int j=0;j<nums.length;j++){//算某一位和  $
                bit+=nums[j]>>i&1;//算某一位置上的和 31,&1是判断最低位是不是1..$
            }
            ans=ans*2+bit%3;//从高位算到低位。最后一次才是低位。直接就把结果算出来了(也可以存一个结果数组)。$
            bit=0;
        }
        return ans;
    }
}

在这里插入图片描述
在这里插入图片描述

双指针单循环
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int i = 0, j = nums.length - 1;
        while(i < j) {
            int s = nums[i] + nums[j];$
            if(s < target) i++;
            else if(s > target) j--;
            else return new int[] { nums[i], nums[j] };//返回新建的数组$
        }
        return new int[0];
    }
}

 

在这里插入图片描述
在这里插入图片描述

三种情况
双指针单循环的进一步应用
public int[][] findContinuousSequence(int target) {
    int i = 1; // 滑动窗口的左边界
    int j = 1; // 滑动窗口的右边界
    int sum = 0; // 滑动窗口中数字的和
    List<int[]> res = new ArrayList<>();//结果为数组 list$

    while (i <= target / 2) {//总控制
    //分步奏,先加后减
        if (sum < target) {
            // 右边界向右移动
            sum += j;
            j++;
        } else if (sum > target) {
            // 左边界向右移动
            sum -= i;
            i++;
        } else {
            // 记录一次结果
            int[] arr = new int[j-i];$
            for (int k = i; k < j; k++) {
                arr[k-i] = k;¥是k,要的是索引
            }
            res.add(arr);
            
            // 记录完以后,左边界向右移动,再进行下一次,去掉一个最小值
            sum -= i;¥
            i++;
        }
    }

    return res.toArray(new int[res.size()][]);
}
 

算法总结
算法
算法适应条件
在这里插入图片描述

字符串StringBuilder操作:for循环
class Solution {
    public String reverseWords(String s) {
        String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串
        StringBuilder res = new StringBuilder();
        for(int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表
            if(strs[i].equals("")) continue; // 遇到空单词则跳过
            res.append(strs[i] + " "); // 将单词拼接至 StringBuilder
        }
        return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回
    }
}
递归就是分解法:一分为二。
后操作递归(看似在一起,其实是后操作)+两种递归例子和边界例子。(递归就是写清楚各种例子)
class Solution {
  public String reverseWords(String s) {
        String[] s1 = s.split(" ");//分割字符串,数组就是递归的对象
        String s2 = reverseWords1(s1, s1.length - 1);//调用递归
        if(s2.length() > 0){
            return s2.substring(0, s2.length() - 1);//去掉空格
        } else {
            return s2;
        }
    }
    public String reverseWords1(String[] s, int index){
        if (index == -1){//结束返回
            return "";¥返回空字符
        } else {
            if (s[index].equals("")){//空格的情况,直接返回¥
                return reverseWords1(s, index - 1); 
            } else {
                    return s[index] + " " + reverseWords1(s, index - 1);//不是空格的情况,返回字符串和递归¥
            }
        }
    }
}

在这里插入图片描述

在这里插入图片描述

后操作递归(看似在一起,其实是后操作)+两种递归例子和边界例子。(递归就是写清楚各种例子)
class Solution {
 public String reverseLeftWords(String s, int n) {
        if (n == 0){//循环结束,返回结果
            return s;
        } else {
            return reverseLeftWords(s.substring(1, s.length()) + s.substring(0,1), n - 1);//循环处理:递归每一次把一个首字符放在最后。递归调用:就跟循环的每次步进一样。递归就是循环。
        }
    }
}

class Solution {
 public String reverseLeftWords(String s, int n) {
        if (n == 0){//循环结束,返回结果
            return s;
        } else {
            return reverseLeftWords(s.substring(1,s.length())+s.charAt(0),n-1)  ;//循环处理:递归每一次把一个首字符放在最后。递归调用:就跟循环的每次步进一样。递归就是循环。
        }
    }
}

 class Solution {
 public String reverseLeftWords(String s, int n) {
        if (n == 0){//循环结束,返回结果
            return s;
        } else {
            return s.substring(n,s.length()) + s.substring(0,n);//循环处理:递归每一次把一个首字符放在最后。递归调用:就跟循环的每次步进一样。递归就是循环。
        }
    }
}
 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

deque双向队列维护+大小等于三种例子+int[]存结果(i为指针,每个容器都有自己的指针,指针就是变量)

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 0 || k == 0) return new int[0];¥空
        Deque<Integer> deque = new LinkedList<>();
        int[] res = new int[nums.length - k + 1];//算个数¥
        for(int j = 0, i = 1 - k; j < nums.length; i++, j++) {//i是为了计算窗口形成后,数组的指针
            // 删除 deque 中对应的 nums[i-1]
            if(i > 0 && deque.peekFirst() == nums[i - 1])//等于的情况
                deque.removeFirst();
            // 保持 deque 递减
            while(!deque.isEmpty() && deque.peekLast() < nums[j])//大于的情况,这是个循环不断地删除最后一个小于它的。这样就维护了第一个最大,不用排序了。
                deque.removeLast();
            deque.addLast(nums[j]);//小于的情况和大于情况都加进去,都加进去不分情况,任何情况¥
            // 记录窗口最大值
            if(i >= 0)
                res[i] = deque.peekFirst();//保存结果,getfist就给删掉了¥
        }
        return res;
    }
}

 class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 0 || k == 0) return new int[0];
        Deque<Integer> deque = new LinkedList<>();
        int[] res = new int[nums.length - k + 1];
        // 未形成窗口
        for(int i = 0; i < k; i++) {
            while(!deque.isEmpty() && deque.peekLast() < nums[i])
                deque.removeLast();
            deque.addLast(nums[i]);
        }
        res[0] = deque.peekFirst();
        // 形成窗口后
        for(int i = k; i < nums.length; i++) {
            if(deque.peekFirst() == nums[i - k])//前面删除,出了窗口期了
                deque.removeFirst();
            while(!deque.isEmpty() && deque.peekLast() < nums[i])//判断空
                deque.removeLast();
	            deque.addLast(nums[i]);
	            res[i - k + 1] = deque.peekFirst();//未形成窗口期时不保存结果。
	        }
	        return res;
	    }
	}

 

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

集合代表的意义和维护+一个数一个属操作的情况:从压入第一个开始编写代码,第1 2 3 4 个+时间与空间转换
class MaxQueue {
    Queue<Integer> queue;//为维护一个弹窗队列
    Deque<Integer> deque;//维护一个最大值双向队列,也就是数据库维护
    public MaxQueue() {
        queue = new LinkedList<>();
        deque = new LinkedList<>();//实现
    }
    public int max_value() {//返回最大值
        return deque.isEmpty() ? -1 : deque.peekFirst();
    }
    public void push_back(int value) {//压入一个,传入参数value
        queue.offer(value);//队列压入
        while(!deque.isEmpty() && deque.peekLast() < value)//双向队列删除之前的小于val的值,还要加上不为空的情况(非第一个)
            deque.pollLast();
        deque.offerLast(value);//双向队列压入
    }
    public int pop_front() {//弹出第一个
        if(queue.isEmpty()) return -1;¥¥
        if(queue.peek().equals(deque.peekFirst()))//如果这个是最大值,更新维护最大值队列
            deque.pollFirst();
        return queue.poll();//弹出
    }
}

 

在这里插入图片描述

递归(举例子)
class Solution {//找规律:s共有 6^n种情况,共有5*n+1种不重复结果
    public double[] twoSum(int n) {
        //思路一:遍历 5*n+1种不重复结果,计算每一种不重复结果的组合个数
        double[] res=new double[5*n+1];
        int curSum=n;
        for(int i=0; i<res.length; ++i){
            res[i]=countSum(curSum, n)/Math.pow(6, n);
            ++curSum;
        }
        return res;
    }

    int countSum(int curSum, int n){
        if(n<0 || curSum<0){return 0;}
        if(n==0&&curSum==0){return 1;}//出口
        int sum=0;
        for(int i=1; i<7; ++i){
            sum+=countSum(curSum-i, n-1);//循环
        }
        return sum;
    }
}

 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

把n=1 2 (也是n和n-1)的例子写成代码

三层循环(2+1)(每层循环的意义)+dp辅助数组的变化维护+(递归循环)+举例子:n=1.2
 
class Solution {
    public double[] dicesProbability(int n) {
        double[] dp = new double[6];//初始结果
        Arrays.fill(dp, 1.0 / 6.0);//第一次的例子
        for (int i = 2; i <= n; i++) {//1.筛子数遍历
        //新建空的当前筛子数的临时结果
            double[] tmp = new double[5 * i + 1]; 
            for (int j = 0; j < dp.length; j++) {//2.遍历上次的dp结果
                for (int k = 0; k < 6; k++) {//3.比上次多加了6种可能性,核心操作,每次的增加算法。递推公式
                    tmp[j + k] += dp[j] / 6.0;
                }
            }
            dp = tmp;//上一次的赋值给dp了,dp就是结果,更新维护dp
        }
        return dp;
    }
}

 

在这里插入图片描述
在这里插入图片描述

三种针对题中对象例子:大小王和无大小王和重复问题
class Solution {
    public boolean isStraight(int[] nums) {
        Set<Integer> repeat = new HashSet<>();//根据规律选择容器
        int max = 0, min = 14;
        for(int num : nums) {//遍历每一个数字
            if(num == 0) continue; // 跳过大小王
            max = Math.max(max, num); // 最大牌
            min = Math.min(min, num); // 最小牌
            if(repeat.contains(num)) return false; // 若有重复,提前返回 false
            repeat.add(num); // 添加此牌至 Set
        }
        return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
    }
}

 

在这里插入图片描述

分解法:::从n=0 1 2 3 4去分析完整例子,推导出得出规律。

要用递归思路去分析规律题。
后操作递归有返回值(利用了上一层递归的值)
class Solution {
    public int lastRemaining(int n, int m) {
        return f(n, m);
    }
    public int f(int n, int m) {
        if (n == 1) {
            return 0;//假设n=1时
        }
        int x = f(n - 1, m);//代表上一次的位置
        return (m + x) % n;//上一次的位置+m  %n,,,n不是一直是5,递减的
    }
}

 
class Solution {
    public int lastRemaining(int n, int m) {
        int f = 0;//假设只有一个数字时,是0
        for (int i = 2; i != n + 1; ++i) {//n时递归变化的
            f = (m + f) % i;
        }
        return f;
    }
}

 
用链表去跑模拟
class Solution {
    public int lastRemaining(int n, int m) {
        ArrayList<Integer> list = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            list.add(i);//加数据
        }
        int idx = 0;
        while (n > 1) {
            idx = (idx + m - 1) % n;//去掉数据
            list.remove(idx);
            n--;
        }
        return list.get(0);
    }
}

 

在这里插入图片描述
在这里插入图片描述

分解法+完整例子
最大收益公式规律:当前值-最小值和以往最大收益的比较值

class Solution {
    public int maxProfit(int[] prices) {
        int cost = Integer.MAX_VALUE, profit = 0;初始第0天
        for(int price : prices) {
            cost = Math.min(cost, price);//用一个变量记录最小值 并维护
            profit = Math.max(profit, price - cost);//记录最大收益并维护,
        }
        return profit;
    }
}
 

在这里插入图片描述

后递归返回值的用法和+逻辑运算符
class Solution {
    public int sumNums(int n) {
        boolean flag = n > 0 && (n += sumNums(n - 1)) > 0;//n<0就不算了,n不能等于0
        return n;
    }
}

 

在这里插入图片描述

class Solution {
    public int sumNums(int n) {
        try {
            int[] a = new int[n];
        } catch (NegativeArraySizeException e) {
            return 0;
        }
        return n + sumNums(n - 1);
    }
}
 

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


异或和与的概念

代入1 , 1
class Solution {
    public int add(int a, int b) {
        while(b != 0) { // 当进位为 0 时跳出
            int c = (a & b) << 1;  // c = 进位
            a ^= b; // a = 非进位和
            b = c; // b = 进位
        }
        return a;
    }
}

 

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

图解法+分解法 自己手写一遍代码

分解过程上下三角

class Solution {
    public int[] constructArr(int[] a) {
        int len = a.length;
        if(len == 0) return new int[0];
        int[] b = new int[len];
        b[0] = 1;
        int tmp = 1;
        //下三角规律
        for(int i = 1; i < len; i++) {//i代表第几行
            b[i] = b[i - 1] * a[i - 1];//减少了重复乘法
        }
        //上三角规律
        for(int i = len - 2; i >= 0; i--) {//i代表第几行。。从length-1开始
            tmp *= a[i + 1];//tmp一直在累乘,当作a[i]*a[i-1]*...
            b[i] *= tmp;//这是要求的值
        }
        return b;
    }
}

 

.在这里插入图片描述
在这里插入图片描述

例子完整性+循环框架。
class Solution {
    public int strToInt(String str) {
        char[] c = str.trim().toCharArray();
        if(c.length == 0) return 0;
        int res = 0, bndry = Integer.MAX_VALUE / 10;
        int i = 1, sign = 1;//存符号变量
        if(c[0] == '-') sign = -1;//考虑到正负号
        else if(c[0] != '+') i = 0;
        for(int j = i; j < c.length; j++) {
            if(c[j] < '0' || c[j] > '9') break;//其他符号
            //大数情况上限了,分正负情况,/10是因为这是倒数第一个数字
            if(res > bndry || res == bndry && c[j] > '7') return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            res = res * 10 + (c[j] - '0');//循环转换成数字
        }
        return sign * res;
    }
}

 

在这里插入图片描述
在这里插入图片描述

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        List<TreeNode> path_p = getPath(root, p);
        List<TreeNode> path_q = getPath(root, q);
        TreeNode ancestor = null;
        for (int i = 0; i < path_p.size() && i < path_q.size(); ++i) {
            if (path_p.get(i) == path_q.get(i)) {
                ancestor = path_p.get(i);
            } else {
                break;
            }
        }
        return ancestor;
    }

    public List<TreeNode> getPath(TreeNode root, TreeNode target) {
        List<TreeNode> path = new ArrayList<TreeNode>();
        TreeNode node = root;
        while (node != target) {
            path.add(node);
            if (target.val < node.val) {
                node = node.left;
            } else {
                node = node.right;
            }
        }
        path.add(node);
        return path;
    }
}

 

在这里插入图片描述
三种例子

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while(root != null) {
            if(root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中
                root = root.right; // 遍历至右子节点
            else if(root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中
                root = root.left; // 遍历至左子节点
            else break;
        }
        return root;
    }
}

 

在这里插入图片描述
//前后递归+成员变量存结果

class Solution {

    private TreeNode ans;//结果

    public Solution() {
        this.ans = null;
    }

    private boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) return false;
        boolean lson = dfs(root.left, p, q);
        boolean rson = dfs(root.right, p, q);
        if ((lson && rson) || ((root.val == p.val || root.val == q.val) && (lson || rson))) {//如果是在同一个子树,或者一个是本身另一个是子树,则成功找到
            ans = root;
        } 
        return lson || rson || (root.val == p.val || root.val == q.val);//返回左或者右子存在或者当前节点就是
    }

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        this.dfs(root, p, q);
        return this.ans;
    }
}

 

在这里插入图片描述
在这里插入图片描述

class Solution {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        int[][] matrix_new = new int[n][n];
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                matrix_new[j][n - i - 1] = matrix[i][j];//元素对应规律
            }
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                matrix[i][j] = matrix_new[i][j];
            }
        }
    }
}

 

在这里插入图片描述

class Solution {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        for (int i = 0; i < n / 2; ++i) {
            for (int j = 0; j < (n + 1) / 2; ++j) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[n - j - 1][i];
                matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
                matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
                matrix[j][n - i - 1] = temp;
            }
        }
    }
}

 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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);
            }
        }
    }
}

 

在这里插入图片描述

在这里插入图片描述
只看两个的,1个是初始条件。三个可以看成两个。这就是分解法找规律的思路。

循环好理解,递归不好理解。
在这里插入图片描述
在这里插入图片描述

class Solution {
	//一个映射表,第二个位置是"abc“,第三个位置是"def"。。。
	//这里也可以用map,用数组可以更节省点内存
	String[] letter_map = {" ","*","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
	public List<String> letterCombinations(String digits) {
		//注意边界条件
		if(digits==null || digits.length()==0) {
			return new ArrayList<>();
		}
		iterStr(digits, new StringBuilder(), 0);
		return res;
	}
	//最终输出结果的list
	List<String> res = new ArrayList<>();
	
	//递归函数
	void iterStr(String str, StringBuilder letter, int index) {
		//递归的终止条件,注意这里的终止条件看上去跟动态演示图有些不同,主要是做了点优化
		//动态图中是每次截取字符串的一部分,"234",变成"23",再变成"3",最后变成"",这样性能不佳
		//而用index记录每次遍历到字符串的位置,这样性能更好
		if(index == str.length()) {
			res.add(letter.toString());
			return;
		}
		//获取index位置的字符,假设输入的字符是"234"
		//第一次递归时index为0所以c=2,第二次index为1所以c=3,第三次c=4
		//subString每次都会生成新的字符串,而index则是取当前的一个字符,所以效率更高一点
		char c = str.charAt(index);
		//map_string的下表是从0开始一直到9, c-'0'就可以取到相对的数组下标位置
		//比如c=2时候,2-'0',获取下标为2,letter_map[2]就是"abc"
		int pos = c - '0';
		String map_string = letter_map[pos];
		//遍历字符串,比如第一次得到的是2,页就是遍历"abc"
		for(int i=0;i<map_string.length();i++) {
			//调用下一层递归,用文字很难描述,请配合动态图理解
            letter.append(map_string.charAt(i));
            //如果是String类型做拼接效率会比较低
			//iterStr(str, letter+map_string.charAt(i), index+1);
            iterStr(str, letter, index+1);
            letter.deleteCharAt(letter.length()-1);
		}
	}
}

在这里插入图片描述
在这里插入图片描述

public int[] dailyTemperatures(int[] T) {
    int length = T.length;
    int[] result = new int[length];
    for (int i = 0; i < length; i++) {
        int current = T[i];
        if (current < 100) {
            for (int j = i + 1; j < length; j++) {
                if (T[j] > current) {
                    result[i] = j - i;
                    break;
                }
            }
        }
    }
    return result;
}

 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值