剑指Offer—刷题记录

用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTaildeleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

两个栈,一个栈用来插入、另一个用来删除,删除之前都要先判断stack.isEmpty(),栈是否为空。

class CQueue {
   Stack<Integer> stackA;
    Stack<Integer> stackB;
    public CQueue(){
        stackA=new Stack<>();//插入
        stackB=new Stack<>();//删除
    }

    public void appendTail(int value){
        stackA.add(value);
    }

    public int deleteHead(){
        if(!stackB.isEmpty()){
            return stackB.pop();
        }
       
        if(stackB.isEmpty()){
            while(!stackA.isEmpty()){
               stackB.push(stackA.pop());
            }
          
        }
       
          if(!stackB.isEmpty()){
                return  stackB.pop();
            }
            return -1 ;
    }
}
包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

两个栈,一个栈用来做push、pop的动作,另一个栈是辅助栈,用来做min,其中,push时要维护最小值入辅助栈,pop时,也要维护辅助栈的栈顶元素是否是主栈的pop元素

class MinStack {
Stack<Integer> stackA;
Stack<Integer> stackB;
public MinStack(){
stackA=new Stack<>();//push、pop
stackB=new Stack<>();//min栈顶最小
}
public void push(int value){
        stackA.push(value);
        //压入时,要有判断维护最小值入栈
        if(stackB.isEmpty()||value<=stackB.peek()){
            stackB.push(value);
    }
}
public void pop(){
    if(!stackA.isEmpty()){
   if(stackA.pop().equals(stackB.peek())){//包装类
       stackB.pop();
   }
    }
}
public int top(){
    return stackA.peek();
}
public int min(){
return stackB.peek();
}
}
从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

遍历链表的模板: ListNode temp=head;while(temp!=null){ 。。。 temp=temp.next; }

用栈的先入后出特性进行倒叙打印

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

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

除了遍历链表的模板之外,最关键的就是

双指针的移动:

​ ListNode temp=cur.next;//暂存
​ cur.next=dummynode;//指向前面
​ dummynode=cur;//第一个指针向后移动
​ cur=temp;//第二个指针向后移动

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
      ListNode cur=head;
      ListNode dummynode=null;
      while(cur!=null){
          ListNode temp=cur.next;
          cur.next=dummynode;
          dummynode=cur;
          cur=temp;
      }
      return dummynode;
    }
}
!!复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

遍历链表模板+hash的键值映射到旧节点与新节点。

先put节点,再构建关系,关系时通过映射关系找到键上next、random,值上next、random

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        Node cur=head;
        Map<Node,Node> map=new HashMap<>();
        while(cur!=null){
            map.put(cur,new Node(cur.val));
            cur=cur.next;
        }
        cur=head;
        while(cur!=null){
            map.get(cur).next=map.get(cur.next);
            map.get(cur).random=map.get(cur.random);
            cur=cur.next;
        }
        return map.get(head);
    }
}
替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

StringBuilder的append方法可以将字符或者字符串从尾部添加进去,toString转为String

字符串相关题目:大多需要分隔遍历,模板:

for(char c:s.toCharArray()){

if条件依次判断修改

}

class Solution {
    public String replaceSpace(String s) {
        StringBuilder str=new StringBuilder();
       for(char c:s.toCharArray()){
           if(c==' '){
               str.append("%20");
           }else{
               str.append(c);
           }
       }
        return str.toString();
    }
}
II. 左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

字符串的常用函数实现

a) Boolean equals(Object obj);//比较字符串内容是否相同,区分大小写
 b) Boolean equalsIgnoreCase(Object obj)//比较字符串内容是否相同,不含大小写。
 c) Boolean contains(String str)比较大字符串中是否含有小字符串
 d) Boolean startsWith(String str)判断字符是否以某个指定的字符串开始
 e) Boolean endsWith(String str)判断字符是否以某个指定的字符串str结尾
 f) Boolean isEmpty();判断字符串是否为空。
  i. String s1=””//字符串内容为空
  ii. String s2=null//字符串对象为空
  iii. s1.isEmpty() //true
  iv. s2.isEmpty()//显示异常

a) int length()获取字符串的长度
b) char charAt(int index)获取指定索引位置的字符
c) int indexOf(int ch)返回指定字符ch在此字符串中第一次出现处的索引
i. 此处的ch是int类型的是因为字符其实都是可以由数字转换ASCAII值
d) int indexOf(String str)返回指定字符串str首字母在此字符串中第一次出现的索引
e) int indexOf(int ch,int fromIndex)返回指定字符ch在此字符串中从指定位置后第一次出现的索引
f) int indexOf(String str,int fromIndex)返回指定字符串str首字母在此字符串中从指定位置后第一次出现的索引
g) String substring(int start)从指定位置start开始截取字符串,默认到末尾
h) String substring(int start,int end)从指定位置开始截取字符串,到end位置,包含start、但是不包含end,如果找不到的话会返回-1

a) byte [] getBytes():把字符串转换为字节数组
  i. byte [] b=s.getBytes()//输出的为字符的ASCII码值
b) char[] toCharArray()把字符串转换为字符数组
  i. char []c=s.toCharArray()
 c) static String valueOf(char [] cha)把字符数组转成字符串
 d) static String valueOf(int i)把int类型转换成字符串
  i. 注意String valueOf方法可以把任意类型数据转换为字符串
 e) String toLowerCase()把字符串转换为小写字母
  i. 注意:转为小写字母是产生一个新的字符串,原来的字符串s本身并没有变化
 f) String toUpperCase()把字符串转换为大写字母
 g) String concat(String str)把字符串拼接
 h) public int lastIndexOf(int ch): 返回指定字符在此字符串中最后一次出现处的索引,

替换

i. String replace (char old,char new) 将字符串中的每个old字符用new字符替代
 ii. String replace(String old ,String new)将字符串中的每个old字符串用new字符串替代,两个字符串old和new长度可以不一样

去除字符串两空格
 i. String trim()去除两端的空格,中间的空格不会去掉

class Solution {
    public String reverseLeftWords(String s, int n) {
        String str1=s.substring(0,n);
        String str2=s.substring(n,s.length());
        return str2+str1;
    }
}
数组中重复的数字

找出数组中重复的数字。在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

利用了hashset的不可重复特性:add加入重复元素,返回false,同时判断一些边界条件,return -1;

class Solution {
    public int findRepeatNumber(int[] nums) {
        if(nums.length==0){
            return -1;
        }
        Set<Integer> set=new HashSet<>();
        for(int num:nums){
            if(!set.add(num)){
                return num;//返回数组从左到右第一个重复的数字
            }
        }
        return -1;
    }
}
在排序数组中查找数字 I

统计一个数字在排序数组中出现的次数。

二分查找法+有序性

统计次数

利用左右指针,在查找过程中,发现如果最后退出循环了,那么左指针要么停在第一次出现,右指针要么出现在最后一次出现

class Solution {
    public int search(int[] nums, int target) {
        int left=0;
        int right=nums.length-1;
        int count=0;
        while(left<=right){
            int mid=(left+right)/2;
            
            if(nums[mid]<target){
                left=mid+1;
            }
            if(nums[mid]>=target){
                right=mid-1;
            }
        }
//上面会将二分指针左指针移动到
// if(nums[mid]>=target){
//                 right=mid-1;
//             }   
//第一次出现的地方
        while(left!=nums.length&&nums[left]==target){
            count++;
            left++;
        }
        return count;
    }
}
0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

遍历,利用下标i==元素nums[i],加上最后一个元素的边界条件

class Solution {    public int missingNumber(int[] nums) {     for(int i=0;i<nums.length;i++){         if(nums[i]!=i){           return i;              }     }     return nums[nums.length-1]+1;    }}
旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2][1,2,3,4,5] 的一次旋转,该数组的最小值为1。

利用旋转数组的特性,当遇到有一个数比它后面的一个数大的时候,后面的这个数就是最小的,普通的遍历就可以了,如果旋转没有旋转,就返回第一个元素。

class Solution {    public int minArray(int[] numbers) {     1   // Arrays.sort(numbers);        // return numbers[0];     2   int min=numbers[0];        for(int i=0;i<numbers.length-1;i++){            if(numbers[i]>numbers[i+1]){               min= numbers[i+1];            }        }        return min;    }}
二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

利用二维数组的特性,从上到下,从左到右都是递增的,排序特性,双指针当遇到逻辑条件时,元素下标随之一起变化,

直到找到目标元素返回。

线性查找法:

当matrix[i][j]小于target,那么target一定在它的右边
当matrix[i][j]大于target,那么target一定在它的上面

模板 :

int i=0,j=matrix[0].length-1;
while(i<matrix.length && j>=0){}

class Solution {    public boolean findNumberIn2DArray(int[][] matrix, int target) {        if(matrix.length==0){            return false;        }        int i=0,j=matrix[0].length-1;       while(i<matrix.length && j>=0){           if(target>matrix[i][j]){               i++;           }else if(target<matrix[i][j]){               j--;           }else{               return true;           }       }       return false;        }    }
I. 从上到下打印二叉树

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

一个队列用来存储同一层的节点(先进先出),一个list用来存储打印节点的输出,

需要遍历树的每层节点,

模板:

queue.add(root);

while(!queue.isEmpty()){

其他逻辑

if(temp.left!=null){
queue.add(temp.left);
}

if(temp.right!=null){
queue.add(temp.right);
}

}

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */class Solution {    public int[] levelOrder(TreeNode root) {        if(root==null){            return new int[0];        }     Queue<TreeNode> queue=new LinkedList<>();     queue.add(root);     List<TreeNode> list=new ArrayList<>();     while(!queue.isEmpty()){         TreeNode temp=queue.poll();         list.add(temp);         if(temp.left!=null){             queue.add(temp.left);         }         if(temp.right!=null){             queue.add(temp.right);         }     }     int size=list.size();     int[] arr=new int[size];     for(int i=0;i<size;i++){         arr[i]=list.get(i).val;     }     return arr;    }}
从上到下打印二叉树 II

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

外层遍历二叉树的每一层都是一样的。

对每一层的队列再次进行for循环遍历,需要注意的是最好是从最后一个索引位置开始向后,依次将队列中的节点取出

for(int i=queue.size()-1;i>=0;i–){
TreeNode temp=queue.poll();
templist.add(temp.val);

}

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
       Queue<TreeNode> queue=new LinkedList<>();
       List<List<Integer>> list=new ArrayList<>();
       if(root==null){
        return list;
        }
       queue.add(root);
       while(!queue.isEmpty()){
           List<Integer> templist=new ArrayList<>();      
           for(int i=queue.size()-1;i>=0;i--){
               TreeNode temp=queue.poll();
               templist.add(temp.val);
               if(temp.left!=null){
                   queue.add(temp.left);
               }
               if(temp.right!=null){
                   queue.add(temp.right);
               }
           }
           list.add(templist);
       }
       return list;
    }
}
III. 从上到下打印二叉树 III

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

遍历二叉树每层的模板

queue.add(root);

while(!queue.isEmpty()){

if(temp.left!=null){
queue.add(temp.left);

}
if(temp.right!=null){
queue.add(temp.right);

}

遍历每层节点的队列仍然需要:for(int i=queue.size()-1;i>=0;i–){}

需要奇偶输出;在最后添加到外层动态数组中,添加反转逻辑

if(index%2!=0){
Collections.reverse(templist);
list.add(templist);
}else{
list.add(templist);
}
index++;

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */class Solution {    public List<List<Integer>> levelOrder(TreeNode root) {       Queue<TreeNode> queue=new LinkedList<>();       List<List<Integer>> list=new ArrayList<>();       if(root==null){           return list;       }       queue.add(root);       int index=0;       while(!queue.isEmpty()){           List<Integer> templist=new LinkedList<>();           for(int i=queue.size()-1;i>=0;i--){               TreeNode temp=queue.poll();               templist.add(temp.val);               if(temp.left!=null){                   queue.add(temp.left);               }               if(temp.right!=null){                   queue.add(temp.right);               }               }                if(index%2!=0){                   Collections.reverse(templist);                   list.add(templist);               }else{                   list.add(templist);               }               index++;           }    return list;       }}

寻找二叉树中对称或相同结构

对称性递归:同时考虑对称的两部分(左右子树)

对称性递归解决的二叉树问题大多是判断性问题(bool类型函数)

1、不需要构造辅助函数。这一类题目有两种情况:第一种是单树问题,且不需要用到子树的某一部分(比如根节点左子树的右子树),只要利用根节点左右子树的对称性即可进行递归。第二种是双树问题,即本身题目要求比较两棵树,那么不需要构造新函数。

2、需要构造辅助函数。这类题目通常只用根节点子树对称性无法完全解决问题,必须要用到子树的某一部分进行递归,即要调用辅助函数比较两个部分子树。形式上主函数参数列表只有一个根节点,辅助函数参数列表有两个节点

1、递归结束条件:特殊情况的判断
如果是单树问题,一般来说只要进行以下判断:

if(!root) return true/false;
if(!root->left) return true/false/递归函数;
if(!root->right) return true/false/递归函数;
如果是双树问题(根节点分别为p,q),一般来说进行以下判断:

if(!p && !q)return true/false;
if(!p || !q)return true/false;

2、返回值
通常对称性递归的返回值是多个条件的复合判断语句
可能是以下几种条件判断的组合:
节点非空的判断
节点值比较判断
(单树)调用根节点左右子树的递归函数进行递归判断
(双树)调用两棵树的左右子树的递归函数进行判断

相同的树

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的

特殊情况的判定,也是退出条件

if(pnull&&qnull){
return true;
}

直接就返回值:返回值是条件的组合判断

根节点都不为空,且值相同,之后就是递归判断两节点的左右子树

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode() {} *     TreeNode(int val) { this.val = val; } *     TreeNode(int val, TreeNode left, TreeNode right) { *         this.val = val; *         this.left = left; *         this.right = right; *     } * } */class Solution {    public boolean isSameTree(TreeNode p, TreeNode q) {        if(p==null&&q==null){            return true;        }        return p!=null&&q!=null&&p.val==q.val&&isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);    }}
二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

特殊条件/退出条件:空树,返回0

返回值:root的左子树、右子树的递归,最后再加上根节点1

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if(root==null){
           return 0;
        }
        return Math.max(maxDepth((root.left)),maxDepth((root.right)))+1;
    }
}
平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

特殊条件:当节点为空时,判断为true

返回值,左右子树的高度差的绝对值小于1&&递归平衡判断左子树&&递归平衡判断右子树

求高度:

特殊条件:节点无子树时,为0

返回值,左右子树的高度最大值判断+1

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root==null){
            return true;
        }
        return Math.abs(height(root.left)-height(root.right))<=1&&isBalanced(root.left)&&isBalanced(root.right);
    }
    public int height(TreeNode root){
        if(root==null){
            return 0;
        }
        return Math.max(height(root.left),height(root.right))+1;
    }
}
单值二叉树

如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。

只有给定的树是单值二叉树时,才返回 true;否则返回 false

特殊值:

节点为空,返回false

节点左右子树有一个没有时,根节点与左右子树的值不相等

返回false

返回值:递归返回左右子树

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode() {} *     TreeNode(int val) { this.val = val; } *     TreeNode(int val, TreeNode left, TreeNode right) { *         this.val = val; *         this.left = left; *         this.right = right; *     } * } */class Solution {    public boolean isUnivalTree(TreeNode root) {        if(root==null){            return true;        }        if(root.left!=null&&root.left.val!=root.val||root.right!=null&&root.right.val!=root.val){            return false;        }        return   isUnivalTree(root.left)&&isUnivalTree(root.right);    }}
另一棵树的子树

给你两棵二叉树 rootsubRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

特殊判断:有一颗树为空就不成立
这道题的思路比较特殊,先判断两棵树是否是相同,如果相同那么就是满足题意的,
然后判断一棵树的左子树是否是另一颗树的子树/一棵树的右子树是否是另一颗树的子树

特殊条件:

首先,判断一棵树如果为空,那么就直接false

接着,判断两个树是否完全相同-》调用相同树的方法,特殊条件:判断是否都为空,返回值:判断都不为空的条件下,递归判断两个树的左右子树是否完全相同

最后,return,递归判断是否是子结构

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode() {} *     TreeNode(int val) { this.val = val; } *     TreeNode(int val, TreeNode left, TreeNode right) { *         this.val = val; *         this.left = left; *         this.right = right; *     } * } */class Solution {    public boolean isSubtree(TreeNode root, TreeNode subRoot) {        if(root==null||subRoot==null){            return false;        }        if(isSamtree(root,subRoot)){            return true;        }        return isSubtree(root.left,subRoot)||isSubtree(root.right,subRoot);    }    public boolean isSamtree(TreeNode root,TreeNode subRoot){        if(root==null&&subRoot==null){            return true;        }        return root!=null&&subRoot!=null&&isSamtree(root.left,subRoot.left)&&isSamtree(root.right,subRoot.right);    }}
翻转二叉树

翻转一棵二叉树

这个也需要递归,

特殊条件:节点为空时,返回null

这个不是返回boolean:需要有逻辑处理

关键在于递归存储,以及再赋值指针

临时左节点存储递归的root.right

临时右节点存储递归的root.left

最后返回将临时左右节点指针指向root的左右子树

返回root

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode() {} *     TreeNode(int val) { this.val = val; } *     TreeNode(int val, TreeNode left, TreeNode right) { *         this.val = val; *         this.left = left; *         this.right = right; *     } * } */class Solution {    public TreeNode invertTree(TreeNode root) {        if(root==null){            return null;        }         TreeNode templeft=invertTree(root.right);        TreeNode tempright=invertTree(root.left);        root.left=templeft;        root.right=tempright;        return root;    }}
合并二叉树

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

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

特殊条件:

1、其中有一个空返回另一个树的根节点
2、都不空的话先把两棵树根节点值相加

逻辑:递归合并左右子树(以第一棵树为合并后的树),

返回值:root1(一般为根节点)

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode() {} *     TreeNode(int val) { this.val = val; } *     TreeNode(int val, TreeNode left, TreeNode right) { *         this.val = val; *         this.left = left; *         this.right = right; *     } * } */class Solution {    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {        if(root1==null){            return root2;        }        if(root2==null){            return root1;        }        if(root1!=null&&root2!=null){            root1.val+=root2.val;        }        root1.left=mergeTrees(root1.left,root2.left);        root1.right=mergeTrees(root1.right,root2.right);        return root1;    }}
对称的二叉树

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

特殊条件·:

1、根节点为null,返回true

2、左右子节点为null,返回true

3、当左右子节点又一个不为空,一个为空,返回false

4、返回值:条件组合,判断左右子节点的val是否相等,以及并上递归判断条件

left.val==right.val也可以用if条件另起

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
     if(root==null){
         return true;
     }
     return isSame(root.left,root.right); 
    }
    public boolean isSame(TreeNode left,TreeNode right){
        if(left==null&&right==null){
            return true;
        }
        if((left!=null&&right==null)||(left==null&&right!=null)){
            return false;
        }
        return left.val==right.val&&isSame(left.left,right.right)&&isSame(left.right,right.left);
    }
}
树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

子结构不能只利用根节点进行对称性递归,需要构造辅助函数,判断当两棵树根节点值相同时一棵树是否为另一棵树子结构

主函数用来根节点不同时的递归

辅助函数用来判断根节点相同时,B是不是A的子结构,

两个结束条件:一个条件是B子树为空,一个条件是A子树为空或者值不等

一般判断值是否相等要另起:

  if(A==null||A.val!=B.val){//递归结束条件2:B的一个节点A的对应位置没有 / A,B对应位置节点值不同,此时必然不可能是子结构        return false;    }

返回值:递归,并组合

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */class Solution {    public boolean isSubStructure(TreeNode A, TreeNode B) {        if(A==null||B==null){            return false; //特殊判断        }       // 根节点相同的话直接进入比较,根节点不相同看B是不是A的左/右子树的子结构        return isSame(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);            }  //判断如果A,B根节点相同,B是不是A的子结构    public boolean isSame(TreeNode A,TreeNode B){        if(B==null){//递归结束条件1:A的一个节点B的对应位置没有,可以认为是子结构            return true;        }        if(A==null||A.val!=B.val){//递归结束条件2:B的一个节点A的对应位置没有 / A,B对应位置节点值不同,此时必然不可能是子结构            return false;        }        return isSame(A.left,B.left)&&isSame(A.right,B.right);//返回值:继续在对应位置递归判断    }}
斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下

状态定义: 设 dp为一维数组,其中 dp[i]的值代表 斐波那契数列第 i个数字 。
转移方程: dp[i + 1] = dp[i] + dp[i - 1],即对应数列定义 f(n + 1) = f(n) + f(n - 1);
初始状态: dp[0] = 0, dp[1] = 1,即初始化前两个数字;
返回值: dp[n] ,即斐波那契数列的第 n 个数字。

动态规划四要素:

状态定义: int[] dp=new int[n+1];

初始状态:

dp[0]=0;
dp[1]=1;

转移方程:一般需要配合循环条件一起使用

for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
dp[i]%=1000000007;
}

返回值:返回 dp[n]

特殊条件:

if(n<2){
return n;
}

class Solution {    public int fib(int n) {       if(n<2){           return n;       }       int[] dp=new int[n+1];       dp[0]=0;       dp[1]=1;       for(int i=2;i<=n;i++){           dp[i]=dp[i-1]+dp[i-2];           dp[i]%=1000000007;       }       return dp[n];    }}
II. 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

动态规划四要素:

状态定义: int[] dp=new int[n+1];

初始状态:

dp[0]=1;
dp[1]=1;

转移方程:一般需要配合循环条件一起使用

for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
dp[i]%=1000000007;
}

返回值:返回 dp[n]

特殊条件:

if(n<2){
return 1;
}

class Solution {
    public int numWays(int n) {
    if(n<2){
        return 1;
    }
    int[] dp=new int[n+1];
    dp[0]=1;
    dp[1]=1;
    for(int i=2;i<=n;i++){
        dp[i]=dp[i-1]+dp[i-2];
        dp[i]%=1000000007;
    }
    return dp[n];
    }
}
股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

动态规划四要素:

状态定义:prices[i]-min

初始状态:

int max=0;
int min=prices[0];

转移方程:一般需要配合循环条件一起使用

max=Math.max(prices[i]-min,max);

返回值:max;

特殊条件:

if(prices.length==0){
return 0;
}

class Solution {
    public int maxProfit(int[] prices) {
       if(prices.length==0){
           return 0;
       }
       int max=0;
       int min=prices[0];
       for(int i=0;i<prices.length;i++){
           if(prices[i]<min){
               min=prices[i];
           }
           max=Math.max(prices[i]-min,max);
       }
       return max;
    }
}
连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CGOVFu7b-1641635647507)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220101013833570.png)]

动态规划四要素:

状态定义:dp[i]

初始状态:

dp[0]=nums[0]

转移方程:

如果dp[i-1]<0;dp[i]=nums[i];

如果dp[i-1]>0;dp[i]=dp[i-1]+nums[i];

或者

pre=Math.max(pre+nums[i],nums[i]);

返回值:返回dp中的最大值

class Solution {    public int maxSubArray(int[] nums) {        int pre=0,max=nums[0];        for(int i=0;i<nums.length;i++){//pre用来存储连续子数组之和            pre=Math.max(pre+nums[i],nums[i]);            max=Math.max(pre,max);        }        return max;    }}
class Solution {    public int maxSubArray(int[] nums) {        int[] dp=new int[nums.length];        dp[0]=nums[0];        for(int i=1;i<nums.length;i++){          if(dp[i-1]>0){              dp[i]=dp[i-1]+nums[i];//连续子数组之和,如果前面的数大于0,继续连续子数组          }else{//前面的数之和小于0,就不再连续了,重新计数              dp[i]=nums[i];          }        }        int max=Integer.MIN_VALUE;        for(int i=0;i<dp.length;i++){            if(dp[i]>max){                max=dp[i];            }        }        return max;    }}
礼物的最大价值

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物

动态规划四要素:

状态定义:dp[i] [j] 前面格子的价值和

初始状态:

if(j0&&i0){

}

转移方程:

  if(j==0&&i==0){                    dp[i][j]=grid[0][0];                }else if(j==0&&i>0){                    dp[i][j]=grid[i][j]+dp[i-1][j];                }else if(i==0&&j>0){                    dp[i][j]=grid[i][j]+dp[i][j-1];                }else if(i>0&&j>0){                    dp[i][j]=grid[i][j]+Math.max(dp[i-1][j],dp[i][j-1]);                }

返回值:返回定义状态的最后一格

class Solution {    public int maxValue(int[][] grid) {        int n=grid.length;        int m=grid[0].length;        int[][] dp=new int[n][m];        for(int i=0;i<n;i++){            for(int j=0;j<m;j++){                if(j==0&&i==0){                    dp[i][j]=grid[0][0];                }else if(j==0&&i>0){                    dp[i][j]=grid[i][j]+dp[i-1][j];                }else if(i==0&&j>0){                    dp[i][j]=grid[i][j]+dp[i][j-1];                }else if(i>0&&j>0){                    dp[i][j]=grid[i][j]+Math.max(dp[i-1][j],dp[i][j-1]);                }            }        }        return dp[n-1][m-1];    }}

翻译规则:

  • 可以单独作为一位来翻译
  • 如果第 i - 1 位和第 i位组成的数字在 10 到 25 之间,可以把这两位连起来翻译

动态规划四要素:

状态定义:dp[i],容量为数据源的大小,为当下标为字符串的i索引时,所有的可能方案

初始状态:

 dp[0]=1;//初始化 dp[1]=1;

转移方程:当连续两个字符组合在10-25之间时, dp[i]=dp[i-2]+dp[i-1];否则dp[i]=dp[i-1];通常需要和for循环配合使用

if(str.substring(i-2,i).compareTo(“10”)>=0&&str.substring(i-2,i).compareTo(“25”)<=0){
dp[i]=dp[i-2]+dp[i-1];
}else{
dp[i]=dp[i-1];
}

返回值:返回定义状态的最后一个

class Solution {
    public int translateNum(int num) {
        String str=String.valueOf(num);
        int[] dp=new int[str.length()+1];//定义初始化状态
        dp[0]=1;//初始化
        dp[1]=1;
        for(int i=2;i<str.length()+1;i++){
            //转移方程
            if(str.substring(i-2,i).compareTo("10")>=0&&str.substring(i-2,i).compareTo("25")<=0){
  dp[i]=dp[i-2]+dp[i-1];
            }else{
                dp[i]=dp[i-1];
            }
          
        }
        return dp[str.length()];

    }
}
最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度

滑动窗口+哈希表

如果该字符串没有在哈希表中,表示该字符不重复,无需移动左边界,将该字符串及对应下标加入哈希表中

如果该字符存在哈希表中,表示找到了重复的元素,此时我们需要移动左边界left
若left小于哈希表中该字符对应的index下标,则移动至index + 1(因为index已经重复了,需要跳过)
若left大于哈希表中该字符对应的index下标,表示重复的内容在左边界以外,忽略即可
将当前字符串对应的下标更新哈希表中该字符串对应的下标

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character,Integer> map=new HashMap<>();   
        int max=0;
        int left=-1;
        for(int i=0;i<s.length();i++){
            if(map.containsKey(s.charAt(i))){
                left=Math.max(left,map.get(s.charAt(i)));//实时更新左指针
            }
            map.put(s.charAt(i),i);//更新hash
            max=Math.max(max,i-left);
        }  
        return max;                                                                               
    }
}
class Solution {
    public int lengthOfLongestSubstring(String s) {
      Map<Character,Integer> map=new HashMap<>();
      int left=0;
      int max=0;
      for(int i=0;i<s.length();i++){
          if(map.get(s.charAt(i))!=null){
              left=Math.max(left,map.get(s.charAt(i))+1);//map.get(s.charAt(i))已经重复了,所以要从下一个位置开始
          }
          map.put(s.charAt(i),i);
          max=Math.max(max,i-left+1);//加上1是算上自己这一位
      }
      return max;
    }
}

hash键存储字符,值存储当前字符的位置,左指针字符最后一次出现的位置,注意这样与字符串的字符结合计算长度的题目要小心,时长加上1,主要看算不算自己这一位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W4wsMbHZ-1641635647508)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220101204801677.png)]

双指针:前指针、当前指针、后指针;前后指针

定义三指针:

pre=head,cur=head.next、

遍历节点 :

while(cur!=null){

pre=cur;

cur=cur.next;

}

遍历指定到节点:

while(cur!=null&&cur.val!=val){
pre=cur;
cur=cur.next;
}

删除当前cur节点:

pre.next=cur.next;让前面的指针指向后面的节点就可以了

/** * 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) {        if(head.val==val){            return head.next;        }        ListNode pre=head;        ListNode cur=head.next;        while(cur!=null&&cur.val!=val){           pre=cur;           cur=cur.next;        }        if(cur!=null){           pre.next=cur.next;        }        return head;       }}
链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点

遍历链表:

while(temp!=null){
len++;
temp=temp.next;
}

遍历到指定的位置:

for(int i=0;i<len-k;i++){
temp=temp.next;
}

/** * Definition for singly-linked list. * public class ListNode { *     int val; *     ListNode next; *     ListNode(int x) { val = x; } * } */class Solution {    public ListNode getKthFromEnd(ListNode head, int k) {        ListNode temp=head;        int len=0;        while(temp!=null){            len++;            temp=temp.next;        }        temp=head;        for(int i=0;i<len-k;i++){            temp=temp.next;        }        return temp;        // 上面循环把节点遍历到指定节点的前一个节点就停止    }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QkLABzjk-1641635647509)(https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/)]

虚拟头节点

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的

首先判断特殊情况,

其次,建立虚拟节点作为新链表

循环遍历两个链表,记住,如果循环退出说明此时有一个链表节点到了null

接着在循环内,判断两个链表的两个节点的值的大小,将值大的那一方链表添加临时节点的next

条件出来之后,将临时节点后移

循环退出,然后把还没有null的节点的剩下部分添加到临时节点之后

/** * 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) {        if(l1==null){            return l2;        }        if(l2==null){            return l1;        }          ListNode temp=new ListNode(0);        ListNode dummy=temp;        while(l1!=null&&l2!=null){            if(l1.val<l2.val){                temp.next=l1;                l1=l1.next;            }else{                temp.next=l2;                l2=l2.next;            }            temp=temp.next;        }        if(l1==null){            temp.next=l2;        }else if(l2==null){            temp.next=l1;                   }        return dummy.next;    }}
两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。

set集合的不可重复性,循环将headA链表节点添加到集合中,再将headB链表节点添加到集合中,如果第一个次不成功就退出,这就是第一个公共节点

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

定义双指针 i , j 分列数组左右两端,循环执行:

指针 i从左向右寻找偶数;
指针 j从右向左寻找奇数;
将 偶数 nums[i] 和 奇数 nums[j]交换

x&1 位运算 等价于x*%2 取余运算,即皆可用于判断数字奇偶性。

普通的枚举法
class Solution {    public int[] exchange(int[] nums) {        List<Integer> list=new ArrayList<>();        for(int i=0;i<nums.length;i++){            if(nums[i]%2!=0){                list.add(nums[i]);            }        }        for(int i=0;i<nums.length;i++){            if(nums[i]%2==0){                list.add(nums[i]);            }        }        for(int i=0;i<nums.length;i++){            nums[i]=list.get(i);        }        return nums;    }}
双指针(两端)交换元素

同时移动指针比较元素,

交换元素模板

int temp=nums[j];
nums[j]=nums[i];
nums[i]=temp;

class Solution {    public int[] exchange(int[] nums) {       int i=0;       int j=nums.length-1;       for(int k=0;k<nums.length/2;k++){           while(i<j && nums[i]%2!=0) i++;           while(i<j && nums[j]%2==0)j--;           //正好奇偶放反了           int temp=nums[j];           nums[j]=nums[i];           nums[i]=temp;        }       return nums;    }}
第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

用hashmap记录字符出现的次数,然后再遍历字符串s,找到第一个次数为1的就退出

记录次数模板:

for(int i=0;i<s.length();i++){
if(map.containsKey(s.charAt(i))){
map.put(s.charAt(i),map.get(s.charAt(i))+1);
}else{
map.put(s.charAt(i),1);
}

转换成:成对键值对:

for (Map.Entry<Character, Integer> entry : map.entrySet()) {
int pos = entry.getValue();

}

class Solution {
    public char firstUniqChar(String s) {
       Map<Character,Integer> map=new HashMap<>();
       for(int i=0;i<s.length();i++){
           if(map.containsKey(s.charAt(i))){
               map.put(s.charAt(i),map.get(s.charAt(i))+1);
           }else{
               map.put(s.charAt(i),1);
           }
       }
       char c=' ';
       for(int i=0;i<s.length();i++){
           if(map.get(s.charAt(i))==1){
               c=s.charAt(i);
               break;
           }
       }
       return c;
    }
}
和为s的两个数字

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即

第一次写

      // 二分查找法
int left=0;
int right=nums.length-1;
//利用排序的递增的这个性质,二分查找法进行sum
while(left<right){
    int sum=nums[left]+nums[right];
    if(sum == target){
        return new int[]{nums[left],nums[right]};
    }
    //递增的性质,left++可以使得sum增大
    if(sum<target){
        left++;
        //递增的性质,right--可以使得sum减少
    }else {
        right--;
    }
}
return new int[]{nums[left],nums[right]};

        // int left=0;
        // int right=nums.length-1;
        // while(left<right){
        //     int sum=nums[left]+nums[right];
        //     if(sum==target){
        //         return new int[]{nums[left],nums[right]}; 
        //     }
        //     if(sum<target){
        //         left++;
        //     }else{
        //         right--;
        //     }
        // }
        // return new int[] {nums[left],nums[right]};
class Solution {
    hashmap存储数组中的值,然后看是否包含target-nums[i]这个键,包含了就返回
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map=new HashMap<>();
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(target-nums[i])){
                return new int[]{nums[i],target-nums[i]};
            }else{
                map.put(nums[i],i);
            }
        }
        return new int[0];
    }
}
左右指针相加,大于目标值右指针向左移动,小于目标值,左指针向右移动
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int left=0;
        int right=nums.length-1;
        while(left<right){
            if(nums[left]+nums[right]>target){
                right--;
            }else if(nums[left]+nums[right]<target){
                left++;
            }else if(nums[left]+nums[right]==target){
                return new int[]{nums[left],nums[right]};
            }
        }
        return new int[0];
    }
}
翻转单词顺序](https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof/)

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。

将其切分为字符串数组,遍历字符串数组,跳过空字符串,然后拼接字符串

class Solution {
    public String reverseWords(String s) {
        StringBuilder str=new StringBuilder();
        String[] strarr=s.split(" ");
        for(int i=strarr.length-1;i>=0;i--){
            if("".equals(strarr[i])){
              continue;
            }else{
                str.append(strarr[i]);
                str.append(" ");
            } 
        }
        return str.toString().trim();
    }
}
返回子数组

数组是Arrays.copyOfRange(src, 0, 2);0

列表是subList(1, 3)

最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4

完全的API

class Solution {    public int[] getLeastNumbers(int[] arr, int k) {            Arrays.sort(arr);        return Arrays.copyOfRange(arr,0,k);    }}
把数组排成最小的数

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

排序算法:快速排序,先把数字转换成字符串数组,再进行快排比较。快排模板

class Solution {    public String minNumber(int[] nums) {        StringBuilder str=new StringBuilder();        String[] strarr=new String[nums.length];        for(int i=0;i<strarr.length;i++){            strarr[i]=String.valueOf( nums[i]) ;        }        quicksort(strarr,0,strarr.length-1);        for(String s:strarr){            str.append(s);        }        return str.toString();    }    public void quicksort(String[] str,int left, int right){        if(left<right){            int i=left;            int j=right;           String temp=str[i];            while(i<j){                 while(i<j&&((str[j]+temp).compareTo(temp+str[j])>=0)){                    j--;                }                if(i<j){                    str[i++]=str[j];                }                while(i<j&&((str[i]+temp).compareTo(temp+str[i])<=0)){                    i++;                }                if(i<j){                    str[j--]=str[i];                }                           }            str[i]=temp;            quicksort(str,left,i-1);            quicksort(str,i+1,right);        }    }}

前缀和(提示:连续子数组)

hashmap记录前缀和

初始化、遍历数组、构造连续子数组和、判定条件、hashmap记录前缀和次数

1、和为 k 的子数组

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

如果存在该key值,证明以数组某一点为起点到当前位置满足题意,count加等于将该key值对应的value

class Solution {    public int subarraySum(int[] nums, int k) {        Map<Integer,Integer> map=new HashMap<>();//容器        int sum=0,count=0;//和、次数        map.put(0,1);//初始化        for(int i=0;i<nums.length;i++){//循环遍历            sum+=nums[i];//连续子数组和            count+=map.getOrDefault(sum-k,0);//个数            map.put(sum,map.getOrDefault(sum,0)+1);//记录前缀和        }        return count;    }}模板:      map.put(0,1);//初始化 for(int i=0;i<nums.length;i++){//循环遍历            sum+=nums[i];//连续子数组和         与题目条件相符的判断: count+=map.getOrDefault(sum-k,0);//记录个数         依次将前缀和记录:  map.put(sum,map.getOrDefault(sum,0)+1);//记录前缀和        }
2、0 和 1 个数相同的子数组

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

将数组中的 0 视作 −1,则原问题转换成「求最长的连续子数组,其元素和为 0」;前缀和

遍历数组,当元素为1时,新数组对应元素为1,元素为0时,新数组对应元素为-1;实现时,可以用一个变量维护前缀和,遇到1加1,遇到0减1,

hash表中存储的是前缀和以及出现第一次前缀和的下标,如果前缀和在hash中已经存在,说明存在了相同数量的连续子数组preindex,则要更新的最长连续子数组的长度为i-preindex

class Solution {    public int findMaxLength(int[] nums) {        Map<Integer,Integer> map=new HashMap<>();        int sum=0;        int maxlength=0;        map.put(0,-1);//初始化        for(int i=0;i<nums.length;i++){            if(nums[i]==1){//转化,遇到1加1,遇到0减1                sum++;            }else{                sum--;            }            //hashmap存储:键是前缀和,值是当前的元素下标            //hashmap中查找是否存在sum,存在更新最大长度,通常使用:最大=Math.max(最大,变值);            //hash中不存在sum,就把sum存入hash中,value中存入下标i            if(map.containsKey(sum)){              maxlength=Math.max(maxlength,i- map.get(sum));            }else{                map.put(sum,i);            }        }        return maxlength;    }}
左右两边子数组的和相等

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

寻找判定条件:中心下标:total==2*sum+nums[i];此时返回下标i

初始化,循环遍历,判定条件,进行前缀和:sum+=num[i];

class Solution {    public int pivotIndex(int[] nums) {        int total=Arrays.stream(nums).sum();        int sum=0;        for(int i=0;i<nums.length;i++){            //中心下标:total-sum=sum            //这个sum是上一次的和,需要加上当前元素num[i]            if(total==2*sum+nums[i]){                return i;            }            sum+=nums[i];        }        return -1;    }}
二进制中1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为 汉明重量).)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7z1q7C8G-1641635647509)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220103135147503.png)]

  1. 判断 n 最右一位是否为 1,根据结果计数。
  2. 将 n 右移一位(本题要求把数字 n看作无符号数,因此使用 无符号右移 操作)。
public class Solution {    public int hammingWeight(int n) {        int res = 0;        while(n != 0) {当n不为0时循环            res += n & 1;判断最右边一位是否为1,计算1的个数            n >>>= 1;二进制右移1位,        }        return res;    }}
不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6sdHwav-1641635647510)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220103140035807.png)]

循环判断

class Solution {    public int add(int a, int b) {        while(b!=0){            int c=(a&b)<<1;//与运算+左移            a=a^b;//异或            b=c;//将进位后的b        }        return a;    }}
数组中数字出现的次数

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)

class Solution {    public int[] singleNumbers(int[] nums) {        Set<Integer> set=new HashSet<>();        for(int i=0;i<nums.length;i++){            if(!set.add(nums[i])){                set.remove(nums[i]);            }        }        int[] number=new int[2];        int item=0;        for(int num:set){            number[item]=num;            item++;        }        return number;    }}

1.任何数和本身异或则为0

2.任何数和 0 异或是本身

class Solution {    public int[] singleNumbers(int[] nums) {     int ret=0     for(int n:nums){         ret^=n;     }     int div=1;    //       while((div&ret)==0){         div<<1=1;//乘     }     int a=0,b=0;     for(int n:nums){         if((div&n)!=0){             a^=n;         }else{             b^=n;         }     }     return new int[]{a,b};    }}
数组中数字出现的次数 II

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

hashmap记录数组中数字出现的次数

然后通过Map.Entry,进行getkey和getvalue

判断value是否有1的,有则直接退出

class Solution {    public int singleNumber(int[] nums) {        Map<Integer,Integer> map=new HashMap<>();        for(int i=0;i<nums.length;i++){            if(map.containsKey(nums[i])){                map.put(nums[i],map.get(nums[i])+1);            }else{                map.put(nums[i],1);            }        }        int num=0;        for(Map.Entry<Integer,Integer> entry:map.entrySet()){            int key=entry.getKey(),value=entry.getValue();            if(value==1){                num=key;                break;            }        }        return num;    }}
构建乘积数组

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

两次遍历,正反遍历,上三角和下三角:注意索引

初始化:数组 B ,其中 B[0] = 1;辅助变量 tmp = 1;
计算 B[i]的 下三角 各元素的乘积,直接乘入 B[i];
计算 B[i]的 上三角 各元素的乘积,记为 tmp ,并乘入 B[i];
返回 B

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 temp=1;        for(int i=1;i<len;i++){            b[i]=b[i-1]*a[i-1];        }//暂时存储了下三角        for(int i=len-2;i>=0;i--){            temp*=a[i+1];            b[i]*=temp;//temp计算的是上三角的乘积        }        return b;    }}
剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YwqS5K3V-1641635647510)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220103172119443.png)]

class Solution {    public int cuttingRope(int n) {        定义状态dp[i]        int[] dp=new int[n+1];        初始化        dp[2]=1;3开始求        for(int i=3;i<=n;i++){            对绳子减长度为j的一段,其中取值小于i大于等于2            for(int j=2;j<i;j++){                减去j后,剩下的可以减也可以不减,取最大的                dp[i]=Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));                当j是不同的i的时候,在不同长度i的dp[i]状态下求出max            }        }        return dp[n];最后一个就是长度为n的    }}

贪心算法

每次操作都是局部最优解,最后得到全局最优解(一般都需要先排序,之后进行循环,里面有条件判断操作)

1、分配问题(孩子吃饼干)每次都选择饥饿度最小的孩子

2、区间问题(区间)

3、买卖股票的最佳时机()

尽可能分多的3小段,每次循环减3,res乘积乘以3,

class Solution {    public int cuttingRope(int n) {        if(n==2){            return 1;        }        if(n==3){            return 2;        }        if(n==4){            return 4;        }        long res=1;        // 最优情况下每段都是3    while(n>4){        res=res*3%1000000007;        n-=3;    }    // 乘以剩余的n    return (int) (res*n%1000000007);    }}

最优情况:尽可能每段都是3

最大乘积为: res=res*3%1000000007; n=n-3;

滑动窗口(双指针)

和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

题目要求的是正整数,连续的,并且至少含有两个数。所以我们使用两个指针,一个left指向1,一个right指向2,他们分别表示窗口的左边界和右边界。然后计算窗口内元素的和

连续-》两个指针,左右边界

如果窗口内的值大于target,说明窗口大了,left往右移一步

如果窗口内的值小于target,说明窗口小了,right往右移一步

如果窗口内的值等于target,说明找到了一组满足条件的序列,把它加入到列表中,并将它们整体往前移动一个索引

连续时:左右指针初始化1,2这样连续

至少含有n个数:left<=target/n

判断不同情况时,指针移动

// 序列小于target,右指针右移动
if(sum<target){
// 注意右指针右移动同时要维护sum,sum要加上右指针
right++;
sum+=right;
// 序列大于target,左指针右移动
}else if(target<sum){
// 注意左指针移动同时,先维护sum,再left++
sum-=left;
left++;

}

else{

// 左右指针同时向右移动
// 注意左指针移动同时,先维护sum,再left++
sum-=left;
left++;
// 注意右指针右移动同时要维护sum,sum要加上右指针
right++;
sum+=right;

}

class Solution {
    public int[][] findContinuousSequence(int target) {
        int left=1;
        int right=2;
        int sum=left+right;
        List<int[]> list=new ArrayList<>();
        // 至少含有两个数
        while(left<=target/2){
            // 序列小于target,右指针右移动
            if(sum<target){
                // 注意右指针右移动同时要维护sum,sum要加上右指针
                 right++;
                sum+=right;
                // 序列大于target,左指针右移动
            }else if(target<sum){
                 // 注意左指针移动同时,先维护sum,,再left++
                sum-=left;
                left++;
            }else{
                // 找到了连续序列等于target
                int[] arr=new int[right-left+1];
                for(int k=left;k<=right;k++){//带上右指针的数
                    arr[k-left]=k;
                }
                list.add(arr);
            // 左右指针同时向右移动 
              // 注意左指针移动同时,先维护sum,,再left++
            sum-=left;
            left++;
            // 注意右指针右移动同时要维护sum,sum要加上右指针
            right++;
            sum+=right;  
            }
        }
        // 二位数组
        return list.toArray(new int[list.size()][]);
    }
}
圆圈中最后剩下的数字

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

while(n>1){
// 要删除第m个,因为索引从0开始,索引要减1,其次是环形的所以除以每次剩余的数组长度
index=(index+m-1)%n;
list.remove(index);
n–;
}

list.add(i);

list.remove(index);

list.get(0);

class Solution {
    public int lastRemaining(int n, int m) {
        // 环形链表,删除节点需要依次遍历,时间复杂度非常高,动态数组删除时间复杂度如果有指定索引的话,还是可以的
        List<Integer> list=new ArrayList<>();
        for(int i=0;i<n;i++){
            list.add(i);
        }
        int index=0;
        while(n>1){
            // 要删除第m个,因为索引从0开始,索引要减1,其次是环形的所以除以每次剩余的数组长度
            index=(index+m-1)%n;
            list.remove(index);
            n--;
        }
        return list.get(0);
    }
}

递归

    public int f(int n,int m){        结束条件        if(n==1){            return 0;        }        return (m+f(n-1,m))%n;下一个要删除的索引位置更新    }
回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

class Solution {    public int countSubstrings(String s) {     boolean[][] dp=new boolean[s.length()][s.length()];     int ans=0;    //  依次遍历字符串的各个字符     for(int j=0;j<s.length();j++){        // 从0开始 分隔其中的每个子字符串         for(int i=0;i<=j;i++){            //  如果字符串的字符相等             if (s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[i + 1][j - 1])) {                    dp[i][j] = true;                    ans++;                }         }     }     return ans;    }}

s.charAt(i) == s.charAt(j) ,必须相等

此外dp【i+1】【j-1】也必须为true或者当j - i < 2 ;因为这个时候是:aba或则和aa;这种情况

当满足上述条件的时候, dp【i】【j】 = true;

动态规划:二层循环:外层j从0到len,内层i从0到j

状态定义: dp【i】【j】

转移方程:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pKYmNrUd-1641635647511)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220104225008720.png)]

回溯算法:

深度优先搜索 算法是一种用于遍历或搜索树或图的算法。这个算法会 尽可能深 的搜索树的分支。当结点 v 的所在边都己被探寻过,搜索将 回溯 到发现结点 v 的那条边的起始结点。这一过程一直进行到已发现从源结点可达的所有结点为止

image.png

每一个结点表示了求解全排列问题的不同的阶段,这些阶段通过变量的「不同的值」体现,这些变量的不同的值,称之为「状态」;
使用深度优先遍历有「回头」的过程,在「回头」以后, 状态变量需要设置成为和先前一样 ,因此在回到上一层结点的过程中,需要撤销上一次的选择,这个操作称之为「状态重置」;
深度优先遍历,借助系统栈空间,保存所需要的状态变量,在编码中只需要注意遍历到相应的结点的时候,状态变量的值是正确的,具体的做法是:往下走一层的时候,path 变量在尾部追加,而往回走的时候,需要撤销上一次的选择,也是在尾部操作,因此 path 变量是一个栈;
深度优先遍历通过「回溯」操作,实现了全局使用一份状态变量的效果。

全排列

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

含重复元素
class Solution {    private List<List<Integer>> res=new ArrayList<>();    public List<List<Integer>> permute(int[] nums) {        if(nums.length==0){            return res;        }        // 判断是否重复        boolean[] flag=new boolean[nums.length];        // 创建的是内层的list         // 记录「路径」        List<Integer> list=new ArrayList<>();        dfs(nums,0,list,flag);        return res;    }    public void dfs(int[] nums,int index,List<Integer> list,boolean[] flag){       // 结束条件        if(index==nums.length){            // 将路径添加到res记录中去            res.add(new ArrayList<>(list));            return;        }        for(int i=0;i<nums.length;i++){            // 剪枝             // 排除不合法的选择            if(list.contains(nums[i])){               continue;             }             // 做选择            list.add(nums[i]);            flag[i]=true;            // index+1每次往下伸一个长度            // 进入下一层决策树            dfs(nums,index+1,list,flag);            // 取消选择            list.remove(list.size()-1);            flag[i]=false;        }    }}

路径: List list=new ArrayList<>();list是路径

选择列表: list.add(nums[i]);不包含元素时添加到list

结束条件:

if (depth == len) {    res.add(new ArrayList<>(path));    return;}

全局变量: private List<List> res=new ArrayList<>();

状态变量: boolean[] flag=new boolean[nums.length]; index

index:递归的终止条件是: 一个排列中的数字已经选够了 ,因此我们需要一个变量来表示当前程序递归到第几层,我们把这个变量叫做 depth,或者命名为 index ,表示当前要确定的是某个全排列中下标为 index 的那个数是多少;
used:布尔数组 used,初始化的时候都为 false 表示这些数还没有被选择,当我们选定一个数的时候,就将这个数组的相应位置设置为 true ,这样在考虑下一个位置的时候,就能够以 O(1)O(1) 的时间复杂度判断这个数是否被选择过,这是一种「以空间换时间」的思想。

在遍历的时候,如果能够提前知道这一条分支不能搜索到满意的结果,就可以提前结束,这一步操作称为 剪枝

全排列 II

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

不含重复元素

image.png

class Solution {

    private List<List<Integer>> res=new ArrayList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        if(nums.length==0){
            return res;
        }
        boolean[] flag=new boolean[nums.length];
        List<Integer> list=new ArrayList<>();
        排序是为了后面去重剪枝
   *******     Arrays.sort(nums);

        dfs(nums,0,list,flag);
        return res;
    }

    public void dfs(int[] nums,int index,List<Integer>list,boolean[] flag){
结束条件
        if(index==nums.length){
            res.add(new ArrayList<>(list));
            return;
        }

        for(int i=0;i<nums.length;i++){
            剪枝
            //true跳过,已经被选过了,被添加进去了
       *****     if(flag[i]){
                continue;
            }
            // 必须要先排序才能有这个
            // 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
            // 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
       *****     if(i>0&&nums[i]==nums[i-1]&&!flag[i-1]){
                continue;
            }
            对称操作
            flag[i]=true;
            list.add(nums[i]);
            
            dfs(nums,index+1,list,flag);
            
            flag[i]=false;
            // 倒着撤销
            list.remove(list.size()-1);
            
        }

    }
}
矩阵中的路径

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

dfs+剪枝

矩阵问题:

矩阵中的每一个元素为基准,

遍历每个元素,做深度优先搜索,因为每一个元素为起点都有可能

dfs:

边界条件:结束条件:

字母索引越界或不合法、border【i】【j】不是对应的word【index】返回false

如果搜索到最后一个字母,返回true

dfs的对称:

修改当前值表明已经走过了这个元素,所以在border【i】【j】!=word【index】可以体现出来

递归dfs

复原

返回递归结果

class Solution {
    public boolean exist(char[][] board, String word) {
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[i].length;j++){
                if(dfs(board,word,0,i,j)){
                    return true;
                }
            }
        }
        return false;
    }

    public boolean dfs(char[][] board,String word,int index,int i,int j){
      if(i<0||j<0||i>board.length-1||j>board[0].length-1||board[i][j]!=word.charAt(index)){
          return false;
      }
      if(index==word.length()-1){
          return true;
      }
      char c=board[i][j];
      board[i][j]='#';
    //  四个方向
      boolean res=dfs(board,word,index+1,i+1,j)||dfs(board,word,index+1,i,j+1)||dfs(board,word,index+1,i-1,j)||dfs(board,word,index+1,i,j-1);
      board[i][j]=c;
      return res;
    }
}

A&&B如果A为false,就不会计算B

A||B如果A为true,就不会计算B

求1+2+…+n

1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)

if(A && B)  // 若 A 为 false ,则 B 的判断不会执行(即短路),直接判定 A && B 为 falseif(A || B) // 若 A 为 true ,则 B 的判断不会执行(即短路),直接判定 A || B 为 true
n > 1 && sumNums(n - 1) // 当 n = 1 时 n > 1 不成立 ,此时 “短路” ,终止后续递归
class Solution {    public int sumNums(int n) {        boolean b=n>1&&(n+=sumNums(n-1))>0;        return n;    }}

当n小于1时,直接为false,后面的就不需要计算了,后面时迭代过程,会一直为true

二叉搜索树的最近公共祖先

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

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

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

迭代:

循环搜索: 当节点 root为空时跳出;
当 p, q都在 root的 右子树 中,则遍历至 root.right;
否则,当 p, q都在 root的 左子树 中,则遍历至 root.left;
否则,说明找到了 最近公共祖先 ,跳出。
返回值: 最近公共祖先 root。

已经为二叉搜索树了,比较容易判断两个节点的值

判断root与p、q的值的大小,看是否再同一侧,在同一侧就继续迭代下去,否则说明不是同一侧,异侧

/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */class Solution {    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {        while(root!=null){            if(root.val<p.val&&root.val<q.val){                root=root.right;            }else if(root.val>p.val&&root.val>q.val){                root=root.left;            }else{                break;            }        }        return root;    }}
二叉树的最近公共祖先

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

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

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null){
            return null;
        }
        if(root.val==p.val||root.val==q.val){
            return root;
        }
        TreeNode newleft=lowestCommonAncestor(root.left,p,q);
        TreeNode newright=lowestCommonAncestor(root.right,p,q);
        if(newleft!=null&&newright!=null){
            return root;
        }else if(newright==null&&newleft!=null){
            return newleft;
        }else if(newleft==null&&newright!=null){
            return newright;
        }else{
            return null;
        }     
    }
}
二叉树中和为某一值的路径

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

注意使用LinkedList<>时前后不能省略。

终止条件:root为空,直接返回

递推:

路劲更新:将不为空的root值加入到路径中

更新状态变量,即目标值

判断路劲是否符合条件,将其加入到res中

先序遍历,递归左右子节点

路径恢复,回溯,将节点从路径中删除

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // 外层存储
    LinkedList<List<Integer>> res=new LinkedList<>();
    // 内层存储
    LinkedList<Integer> list=new LinkedList<>();
    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 ;
        }
        //路劲选择
        list.add(root.val);
        // 状态变量
        target-=root.val;
        // 将路劲加入res中
        if(root.left==null&&root.right==null&&target==0){
            res.add(new LinkedList<Integer>(list));
        }
        // 递归
        dfs(root.left,target);
        dfs(root.right,target);
        // 撤销
        list.removeLast();
    }
}

二叉搜索树

二叉搜索树是一棵有序的二叉树,所以我们也可以称它为二叉排序树。具有以下性质的二叉树我们称之为二叉搜索树:若它的左子树不为空,那么左子树上的所有值均小于它的根节点;若它的右子树不为空,那么右子树上所有值均大于它的根节点。它的左子树和右子树分别也为二叉搜索树。

二叉搜索树的中序遍历是:左=>根=>右; 二叉搜索树的中序遍历从小到大是有序的。

中序遍历

//打印中序遍历
void dfs(TreeNode* root ) 
{
    if(!root) return;
    dfs(root->left); 	//左
    print(root->val);   //根
   	dfs(root->right);	//右
}


// 打印中序遍历倒序
void dfs(TreeNode root) {
    if(root == null) return;
    dfs(root.right); // 右
    System.out.println(root.val); // 根
    dfs(root.left); // 左
}

二叉树的中序遍历是左、根、右;遍历结果是递增序列

二叉树的中序遍历的倒叙:右、根、左;遍历结果是递减序列

二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

private int res,k;全局变量

先判断roo是否为null

再进行右递归

之后进行根节点的处理

左后进行左递归

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private int res,k;
    public int kthLargest(TreeNode root, int k) {
       this.k=k;
        dfs(root);
        return res;
    }
    public void dfs(TreeNode root){
        if(root==null){
            return ;
        }
        dfs(root.right);
        // 如果k减去1之后为0了,说明上一次,也就是root就是第k大
        if(k==0){
            return;
        }
        // 记录此时k减去1时的值
        if(--k==0){
            res=root.val;
        }
        dfs(root.left); 
    }
}

优先队列

boolean offer(E e)入队列
E poll()出队列
E peek()得到队首元素
int size()返回集合中的元素个数

用 Priority Queue 创建一个优先级队列

PriorityQueue<Integer> queue=new PriorityQueue<>();默认是最小堆

入队列

queue.offer(10);
queue.offer(2);
queue.offer(5);

得到队首元素

System.out.println(queue.peek());
// 结果为:2

出队列

System.out.println(queue.poll());
// 结果为:2

返回集合中元素个数

System.out.println(queue.size());
// 结果为:2

定义大根堆

// 定义的某个要比较类型的比较器
class IntegerComparator implements Comparator<Integer>{
    @Override
    public int compare(Integer o1,Integer o2){
        // 如果第二个元素-第一个元素就是大根堆的实现方式,反之则为小根堆的创建方式,可以从源码去了解
        return o2-o1;
    }
}
public class TestDemo{
    public static void main(String[] args){
        PriorityQueue<Integer> maxHeap=new PriorityQueue<>(IntegerComparator);
    }
}
丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

最小堆,每次弹出的都是最小的堆,这样的话就是按照顺序的

class Solution {    public int nthUglyNumber(int n) {      int[] nums={2,3,5};    //   最小堆      PriorityQueue<Long> queue=new PriorityQueue<>();    //   set去重      Set<Long> set=new HashSet<>();    //   初始化堆和set      queue.offer(1L);      set.add(1L);    //   设置丑数      int ugly=0;    //   遍历n      for(int i=0;i<n;i++){        //   弹出最小堆的堆顶        long temp=queue.poll();        ugly=(int) temp;        // 维护下一个丑数        for(int j=0;j<nums.length;j++){            // 将上一个丑数分别乘以2,3,5            long next=temp*nums[j];            // 如果set中没有重复,也就是最小堆里面没有重复,就将其加入到最小堆中            if(set.add(next)){                queue.offer(next);            }        }      }      return ugly;        }}
1~n 整数中 1 出现的次数

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

当某一位等于 0 的时候,这一位一共可以出现的数字个数就是:high * digit

当某一位等于 1 的时候,这一位一共可以出现的数字个数就是:high * digit + low + 1

当某一位等于 2/3/4/…/9 的时候,这一位一共可以出现的数字个数就是:(high + 1) * digit

class Solution {
    public int countDigitOne(int n) {
        long digit=1;
        int high=n/10,cur=n%10,low=0;
        // 记录1的个数
        int res=0;
        while(high!=0||cur!=0){
            循环退出的条件是 high 和 cur 都等于 0。因为都等于 0 就意味着 n 已经彻底遍历完了。
            if(cur==0){
                res+=high*digit;
            }else if(cur==1){
                res+=high*digit+low+1;
            }else{
                res+=(high+1)*digit;
            }
                low+=cur*digit;
        cur=high%10;
        high=high/10;
        digit*=10;没移动一位,就需要乘以10(digit 等于几当前的数位就是几位数)
        }
        return res;
    }
}

        // 高位
        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;
            统计规律得到的:
            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;
    }
}
打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

遍历

class Solution {
    public int[] printNumbers(int n) {
        int len=(int)Math.pow(10,n)-1;
        int[] res=new int[len];
        for(int i=0;i<res.length;i++){
            res[i]=i+1;
        }
        return res;
    }
}

String.valueOf(boolean b) : 将 boolean 变量 b 转换成字符串
String.valueOf(char c) : 将 char 变量 c 转换成字符串
String.valueOf(char[] data) : 将 char 数组 data 转换成字符串
String.valueOf(char[] data, int offset, int count) :
将 char 数组 data 中 由 data[offset] 开始取 count 个元素 转换成字符串
String.valueOf(double d) : 将 double 变量 d 转换成字符串
String.valueOf(float f) : 将 float 变量 f 转换成字符串
String.valueOf(int i) : 将 int 变量 i 转换成字符串
String.valueOf(long l) : 将 long 变量 l 转换成字符串
String.valueOf(Object obj) : 将 obj 对象转换成 字符串, 等于 obj.toString()

处理大数的情况,int32超出

class Solution {
      int[] res;//存放最终结果
        char[] num;//存放字符数组
        int count=0,n;//
    public int[] printNumbers(int n) {
        // 大数情况
        this.n=n;
        num=new char[n];
        res=new int[(int)(Math.pow(10,n)-1)];
        dfs(0);
        return res;
    }

    public void dfs(int n){
        if(n==this.n){
            // 终止条件
            String temp=String.valueOf(num);
            int curnum=Integer.parseInt(temp);
            // 如果
            if(curnum!=0){
                res[count++]=curnum;
            }
            return;
        }
    for(int i='0';i<='9';i++){
        num[n]=(char)i;
        dfs(n+1);
    }
    }
}
数字序列中某一位的数字

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。请写一个函数,求任意第n位对应的数字。

各 digit 下的数位数量 count的计算公式:count=9×start×digit

可将求解分为三步:

确定 n 所在 数字 的 位数 ,记为 digit ;
确定 n 所在的 数字 ,记为 num ;
确定 n 是 num 中的哪一数位,并返回结果。

1. 确定所求数位的所在数字的位数

digit, start, count = 1, 1, 9
while n > count:
n -= count
start *= 10 # 1, 10, 100, …
digit += 1 # 1, 2, 3, …
count = 9 * start * digit # 9, 180, 2700, …

2. 确定所求数位所在的数字

num = start + (n - 1) // digit

3. 确定所求数位在 numnum 的哪一数位

s = str(num) # 转化为 string
res = int(s[(n - 1) % digit]) # 获得 num 的 第 (n - 1) % digit 个数位,并转化为 int

class Solution {
    public int findNthDigit(int n) {
        int digit=1;//位数   表示一位数
        long count=9;//表示某位数的个数是多少,一位数个数为9
        long start=1;//表示某位数的第一个数,一位数的第一个数是1
        判断第n位数字所属的数是几位数,n > count表明第n为数字所属的数比当前位数更高
        while(n>count){
            
            n-=count;
            digit+=1;
            start*=10;
            count=digit*start*9;
        }
         //num为该几位数里面的第几个数
        long num=start+(n-1)/digit;
        return Long.toString(num).charAt((n-1)%digit)-'0';//在该数的第几位
    }
}
数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

归并排序
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++) {
            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;
    }
}
传统归并排序算法
import java.util.Arrays;
public class MergeSort1 {
    private static int count;
    public static void main(String[] args) {

        int[] arr={7,5,6,4};
        int[] temp=new int[arr.length];
        merge(arr,0,arr.length-1,temp);
        System.out.println(Arrays.toString(arr));
        System.out.println(count);
    }

    public static void merge(int[] nums,int left,int right,int[] temp){
        if(left<right){
           int mid=(left+right)/2;
            merge(nums,left,mid,temp);
            merge(nums,mid+1,right,temp);
//            上述为分解国过程
            sort(nums,left,mid,right,temp);
        }
    }
    public static void sort(int[] nums,int left,int mid,int right,int[] temp){
        int i=left;// 初始化i, 左边有序序列的初始索引
        int j=mid+1;//初始化j, 右边有序序列的初始索引
        int t=0;// 指向temp 数组的当前索引

        //先把左右两边(有序)的数据按照规则填充到temp 数组
        //直到左右两边的有序序列,有一边处理完毕为止
        while(i<=mid&&j<=right){
            //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
            //即将左边的当前元素,填充到temp 数组
            //然后t++, i++
            if(nums[i]<=nums[j]){
                temp[t]=nums[i];
                t++;
                i++;
            }
            else{
                temp[t]=nums[j];
                t++;
                j++;
//                count+=t-i+1;
            }
        }
        while(i<=mid){
            temp[t]=nums[i];
            t++;
            i++;
        }
        while(j<=right){
            temp[t]=nums[j];
            t++;
            j++;
        }
        //将temp 数组的元素拷贝到arr
        // 注意,并不是每次都拷贝所有
        t=0;
        int templeft=left;
        while(templeft<=right){
            nums[templeft]=temp[t];
            t++;
            templeft++;
        }
    }
}
机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0]的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

深度优先搜索;

结束条件:

if(i<0||j<0||i>=m||j>=n||b[i][j]){
return;
}

if(sum(i)+sum(j)>k){
return;
}

逻辑处理:

b[i][j]=true;
count++;

路劲选择:
dfs(m,n,b,i+1,j,k);
dfs(m,n,b,i-1,j,k);
dfs(m,n,b,i,j+1,k);
dfs(m,n,b,i,j-1,k);

外部函数处理:对一个整数位的各个位数求和

private int sum(int m){
int n=0;
while(m!=0){
// [35, 38]
// 3+5+3+8
n=n+m%10;
m=m/10;
}
return n;
}

初始化:从0,0开始

class Solution {
    public int count=0;
    public int movingCount(int m, int n, int k) {
       boolean[][] b=new boolean[m][n];
    //    从0,0开始
       dfs(m,n,b,0,0,k);
       return count;
    }
    private void dfs(int m,int n,boolean[][] b,int i, int j,int k ){
        // 结束条件// 它每次可以向左、右、上、下移动一格(不能移动到方格外)
        if(i<0||j<0||i>=m||j>=n||b[i][j]){
            return;
        }
        // 行坐标和列坐标的数位之和大于k的格子
        if(sum(i)+sum(j)>k){
            return;
        }
        b[i][j]=true;
        count++;
        dfs(m,n,b,i+1,j,k);
        dfs(m,n,b,i-1,j,k);
        dfs(m,n,b,i,j+1,k);
        dfs(m,n,b,i,j-1,k);
    }
    private int sum(int m){
        int n=0;
        while(m!=0){
            //  [35, 38]
            // 3+5+3+8
            n=n+m%10;
            m=m/10;
        }
        return n;
    }
}

动态规划解决问题一般分为三步:

  1. 表示状态
  2. 找出状态转移方程
  3. 边界处理
n个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率

当投掷完 n*n* 枚骰子后,各个点数出现的次数

动态规划:

动态定义:dp【i】[j]表示当有i个骰子,掷出和为j的概率

初始化状态:for(){double[1]【j】=1/6}

动态规划:计算其他情况

当前最大值=6*骰子个数

(有1个骰子,掷出和为1~6的可能性) *= (有i-1个骰子,掷出和为j-(1~6)的可能性)

转移方程

=== dp[i][j] += dp[1][k] * dp[i - 1][j - k];====

class Solution {
    public double[] dicesProbability(int n) {
        if (n <= 0) {
            return new double[0];
        }

        double[][] dp = new double[n + 1][6 * n + 1];   // dp[i][j]表示:当有i个骰子时,掷出和为j的几率

        /*
            初始化 一颗骰子的情况
        */
        for (int i = 1; i <= 6; i++) {
            dp[1][i] = (double) 1 / 6;
        }

        /*
            动态规划,计算 其他情况
        */
        for (int i = 2; i <= n; i++) {2开始,计算小于n的情况,
                最大值维护
                小于最大值
               
              
            int curMaxValue = 6 * i;    // 当前最大值 = 6 * 骰子个数
            for (int j = i; j <= curMaxValue; j++) {
                /*
                    我们可以这样推导:有i个骰子,掷出和为j的可能性 == (有1个骰子,掷出和为1~6的可能性) *= (有i-1个骰子,掷出和为j-(1~6)的可能性)
                    根据推导,我们能够知道:
                        dp[i][j] += dp[1][k] * dp[i - 1][j - k];
                        k为 合理情况下 的 1~6
                */
                k从1走小于等于6
                  转移方程
                for (int k = 1; k <= 6; k++) {
                    if (j - k <= 0) {   // k不能比j大
                        continue;
                    }
                    dp[i][j] += dp[1][k] * dp[i - 1][j - k];
                }
            }
        }

        // 将 计算结果 录入 结果数组
        double[] result = new double[5 * n + 1];
        int value = n;
        for (int i = 0; i < result.length; i++) {
            result[i] = dp[n][value++];
        }

        return result;
    }
}
正则表达式匹配

请实现一个函数用来匹配包含'. ''*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a""ab*ac*a"匹配,但与"aa.a""ab*a"均不匹配。

动态规划

状态定义:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3PsHVuzK-1641635647512)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220107215214374.png)]

转移方程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aWeu32yP-1641635647513)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220107215037205.png)]

初始条件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1dHIyNe8-1641635647513)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220107215158717.png)]

class Solution {
    public boolean isMatch(String s, String p) {      
        int n=s.length();
        int m=p.length();
    // 状态定义:s的前面i个和p的前面j个能否匹配
        boolean[][] f=new boolean[n+1][m+1];状态定义
// 两层遍历n,m
        for(int i=0;i<=n;i++){
            for(int j=0;j<=m;j++){
                // 如果是空,
                // 空
                if(j==0){根据不同情况进行动态规划
                    // 如果前面i  j都为空的话, 0  0 为true,自动匹配
                    f[i][j]=i==0;
                    // 非空
                }else{
                    // 非*
                    if(p.charAt(j-1)!='*'){
                        // 如果B的第j-1个字符不是*
                        if(i>0&&(s.charAt(i-1)==p.charAt(j-1)||p.charAt(j-1)=='.')){根据不同情况进行动态规划
                            f[i][j]=f[i-1][j-1];
                        }
                        // *
                    }else{
                        // 是*的话,
                        // 不看
                        if(j>=2){根据不同情况进行动态规划
                           f[i][j] =f[i][j]||f[i][j-2];
                        }
                        // '.可以匹配任意一个字符,所以有A.charAt(i-1)==B.charAt(j-2)||B.charAt(j-2)=='.''
                        if(i>=1&&j>=2&&(s.charAt(i-1)==p.charAt(j-2)||p.charAt(j-2)=='.')){根据不同情况进行动态规划
                            f[i][j]|=f[i-1][j];
                        }
                    }
                }
            }
        }
return f[n][m];
    }
}

*二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

  • 后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。
  • 二叉搜索树定义: 左子树中所有节点的值 << 根节点的值;右子树中所有节点的值 >> 根节点的值;其左、右子树也分别为二叉搜索树。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C5YTXVEj-1641635647513)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220107222717413.png)]

递归:

终止条件:

判断是否为二叉搜索树:

【i,m-1】节点小于根节点,【m,j-1】大于根节点

class Solution {
    public boolean verifyPostorder(int[] postorder) {
    return recur(postorder,0,postorder.length-1);
    }
    public boolean recur(int[] postorder,int i,int j){
      
        // 子树只有一个节点
        if(i>=j){
            return true;
        }
        int p=i;
          // 左
        while(postorder[p]<postorder[j]){
            p++;
        }
        // j是根节点,上述是寻找第一个大于根节点的p
        int m=p;
             // 右
        while(postorder[p]>postorder[j]){
            p++;
        }
        // 分治思想
        return p==j&&recur(postorder,i,m-1)&&recur(postorder,m,j-1);
// 假设被分到最小的一块了,就是j为i,或者比i要小,什么时候
// 当i=m-1,m=j-1,就是说这里就只有一个节点
// 最底层是这个,然return true
// 那么假设中间都是return true
// 最后一个状态就是最初进入的状态
// 那么就是相当于返回到最初状态:m为中间数,j为根节点,数组最后一个数
// 数组中所有数都和根节点比较    
    }
}
重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

递归

  1. 一般情况下,方法的结束顺序和调用顺序是相反的,比如方法 a 调用方法 b,方法 b 调用方法 c,那么结束顺序是:c 先结束,然后是 b 结束,最后是 a 结束。这个特点其实就是符合栈的后进先出的特点,所以方法的调用一般使用栈来实现,这个栈就是方法调用系统栈。
  2. 每次调用方法的时候,都会将这个方法的栈帧压入系统栈中,栈帧主要包括这个方法的参数、局部变量以及返回地址。
  3. 当方法结束的时候,对应的栈帧会弹出系统栈。

方法调用自己

  • 方法调用自己的意义在于递归的解决问题。

  • 那么递归的意义在于解决符合下面三个特点的问题:

    1. 这个问题可以拆解成若干个子问题,如果所有子问题解决了,那么这个大问题就解决了
    2. 所有子问题的求解方法和大问题的求解方法是一样的
    3. 存在结果已知的最小子问题

二叉树前序遍历的顺序为:

先遍历根节点;

随后递归地遍历左子树;

最后递归地遍历右子树。

二叉树中序遍历的顺序为:

先递归地遍历左子树;

随后遍历根节点;

最后递归地遍历右子树。

Picture1.png

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    // 前:根左右:第一个肯定是根节点,第二个肯定是左子树的根节点
    // 中:左根右:可以由根节点的索引将左右子树分为两半
    // map用来存储中序遍历节点及其索引
    Map<Integer,Integer> map=new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for(int i=0;i<preorder.length;i++){
            map.put(inorder[i],i);
        }  
       return  build(preorder,0,0,inorder.length-1);
    }
    // 递归
    public TreeNode build(int[] preorder,int pre_root_index,int in_left_index,int in_right_index){
        // 递归结束条件
        if(in_left_index>in_right_index){
            return null;
        }
        // 找到根节点在中序遍历中的索引位置
        int in_root_index=map.get(preorder[pre_root_index]);
        //  创建一个根节点
        TreeNode node=new TreeNode(preorder[pre_root_index]);
        // 寻找node的左节点
        //前序遍历的位置就是:根节点的下标+1
        // 中序遍历的位置就是:左边界不变,右边界就是根节点的左边一个单位in_root_index-1
        node.left=build(preorder,pre_root_index+1,in_left_index,in_root_index-1);
        // 寻找node的右节点
        // 前序遍历的位置:根节点下标+左子树的长度+1
        // 中序遍历的位置:1,左边界in_root_index+1,2右边界,in_right_index
        node.right=build(preorder,pre_root_index+in_root_index-in_left_index+1,in_root_index+1,in_right_index);
        return node;
    }
}
二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

/*
// 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 pre,head;
    // 二叉搜索树的中序遍历就是一个排序的数组
    public Node treeToDoublyList(Node root) {
        //如果root为空,表明空树,直接返回null
        if(root==null)return null;
        //构建双向链表:pre.right=cur;cur.left=pre
        dfs(root);
        // 双向构建好了,构建循环:head.left=tail,tail.right=head;
        head.left=pre;
        pre.right=head;
        // 放回根节点就可以了
        return head;
    }
    public void dfs(Node cur){
        // 递归结束条件
        if(cur==null){
            return;
        }
        // 左
        dfs(cur.left);
        // 如果pre不是null的话说明cur的左边有节点,如果为null的话,说明cur为头节点
        // 根
        if(pre!=null){
            pre.right=cur;//建立右向
        }else{
            head=cur;
        }
        cur.left=pre;//建立左向
        // 移动pre,让pre指向cur,因为下一次递归时,cur会移动到right,就是下一个节点,pre也要根着移动
        pre=cur;
        // 右
        dfs(cur.right);
        // 递归结束的时候是cur指向null,而pre就是在它之前,就是尾节点
    }
}
数值的整数次方

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。

提升效率:

b>>=1;除以2

(b&1)==1;判断一个数是不是奇数

class Solution {
    public double myPow(double x, int n) {
        // 特殊情况0
        if(x==0){
            return 0;
        }
        // 当b为-2147483648时会越界,转换为long
        long b=n;
        double res=1.0;
        // 特殊情况处理,当幂为负数的时候,转为正数处理
        if(b<0){
            x=1/x;
            b=-b;
        }
          // 判断 n 是奇数还是偶数
        // 比如我们已经知道了一个数的4次方(100)
        // 1、求9次方(1001)的时候,即为 4次方 * 4次方 * x 
        // 2、求8次方(1000)的时候,即为 4次方 * 4次方
        while(b!=0){
            // 判断是不是奇数,因为如果是奇数的话,让它乘以自己的同时再乘以一个x
            if((b&1)==1){
                res*=x;
            }
            //如果是偶数的话,只让它乘以自己
            x*=x;
            // 并维护指数b除以2
            b>>=1;
        }
        return res;
    }
}

顺时针打印矩阵的顺序是 “从左向右、从上向下、从右向左、从下向上” 循环

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jxLfueEG-1641635647514)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220108110633871.png)]

解释一下为什么是这样的:因为从左向右,边界是左、右,但是打印的时候是顺时针,从左向右的这一条线再逐层往下,所以上边界加1,到底的时候就是上边界大于下边界的时候,当然下边界也在逐层往上,就是说当上下边界相同的时候,就到中心位置了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W4NqYUp7-1641635647514)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220108110147210.png)]

  • res[x++] 等价于先给 res[x] 赋值,再给 x 自增 11 ;
  • ++t > b 等价于先给 t 自增 11 ,再判断 t > b 逻辑表达式
class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if(matrix.length==0){
            return new int[0];
        }
        // x是存储数组的指针
        int l=0,r=matrix[0].length-1,t=0,b=matrix.length-1,x=0;
        int[] res=new int[(r+1)*(b+1)];
        while(true){
            // 从左往右,边界就是t b
            for(int i=l;i<=r;i++){
                // 因为它是从上边开始下降的所以是t
                res[x++]=matrix[t][i];
            }
            if(++t>b){
                break;
            }
            // 从上到下,边界就是l r
            for(int i=t;i<=b;i++){
                 // 因为它是从右边开始移动的所以是r
                res[x++]=matrix[i][r];
            }
            if(l>--r){
                break;
            }
            // 从右到左,边界就是t b
            for(int i=r;i>=l;i--){
           // 因为它是从下边开始移动的所以是b
           res[x++]=matrix[b][i];
            }
            if(t>--b){
                break;
            }
            // 从左到右,边界就是l r
            for(int i=b;i>=t;i--){
                // 因为它是从左边开始移动的,所以是l
                res[x++]=matrix[i][l];
            }
            if(++l>r){
                break;
            }
        }
        return res;
    }
}
把字符串转换成整数

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

int32:-2147483648****2147483647

判断的条件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zbw3GvIO-1641635647515)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220108145959201.png)]

class Solution {
    public int strToInt(String str) {
        // 去除字符串的首空格,并将其转为字符数组
      char[] c=str.trim().toCharArray();
    //   如果字符数组长度为0,则直接返回0
      if(c.length==0){
          return 0;
      }
// 符号位
      int sign=1;
    //   存储结果
      int res=0;
    //   索引位
      int index=0;
    //   阈值
    int thread=Integer.MAX_VALUE/10;
// 判断第一个字符是不是-
    if(c[0]=='-'){
        // 是负号,符号位置为-1,并且索引位向后移动
        sign=-1;
        index++;
        // 如果为加号
        // 索引位向后移动
    }else if(c[0]=='+'){
        index++;
    }
    // 除去索引位,向后遍历拼接
    for(int j=index;j<c.length;j++){
        // 如果不是数字直接退出
        if(c[j]<'0'||c[j]>'9'){
            break;
        }
        // 如果超出最大阈值,或者前面等于最大阈值,但是后面一位是大于7的,也就是说等于7的时候还是正常拼接
        if(res>thread||res==thread&&c[j]>'7'){
            // 用来判断是否超过最大值
            // 如果为正数或者负数直接返回最大值
            return sign==1?Integer.MAX_VALUE:Integer.MIN_VALUE;
        }
        // 将前面的数加上本索引位的数字
        res=res*10+c[j]-'0';
    }
    // 返回正常数字
    return sign*res;
    }
} 
II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_valuepush_backpop_front均摊时间复杂度都是O(1)。若队列为空,pop_frontmax_value 需要返回 -1

class MaxQueue {
    // 普通队列
    Queue<Integer> queue;
    Deque<Integer> deque;
    public MaxQueue() {
        queue=new LinkedList<>();
        deque=new LinkedList<>();
    }
    // 获取最大值,返回双端队列一直维护的队头
    public int max_value() {
        if(deque.isEmpty()){
            return -1;
        }
        return deque.peekFirst();

    } 
    // 入队列
    public void push_back(int value) {
        queue.offer(value);
        while(!deque.isEmpty()&&value>deque.peekLast()){
            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();
    }
}

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue obj = new MaxQueue();
 * int param_1 = obj.max_value();
 * obj.push_back(value);
 * int param_3 = obj.pop_front();
 */
滑动窗口的最大值

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值

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 resIndex=0;
    // 存储滑动窗口的最大值,因为窗口大小为k,
        int[] res=new int[nums.length-k+1];
        for(int i=0;i<nums.length;i++){
            // 清理超过滑动窗口的元素,i-k位置的元素
            if(!deque.isEmpty()&&deque.peek()==i-k){
                deque.remove();
            }
            // 维护单调递减队列(清除队列内的元素<新入队的元素)
            // 删除所有比新入队元素小的旧元素
            while(!deque.isEmpty()&&nums[i]>=nums[deque.peekLast()]){
                deque.removeLast();
            }
            // 新元素入队
            deque.add(i);
            // 将滑动窗口的最大值添加到结果数组中
            // 当滑动窗口内的元素满的时候才做
            if(i>=k-1){
                res[resIndex++]=nums[deque.peek()];
            }
        }
       return res;
    }
}
据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

思路:先将数组进行排序,在插入过程中也对元素进行排序,(二分查找插入法)然后返回其中的中位数或者两个中位数的平均值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LDYZuhd3-1641635647515)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20220108164201887.png)]

是一棵顺序存储完全二叉树

大顶堆:每个结点的值都大于或等于其左右孩子结点的值

小顶堆:每个结点的值都小于或等于其左右孩子结点的值

对于n个元素的序列{R0, R1, … , Rn}当且仅当满足下列关系之一时,称之为堆:

(1) Ri <= R2i+1 **且 Ri <= R2i+2 (**小根堆)

(2) Ri >= R2i+1 **且 Ri >= R2i+2 (**大根堆)

img

设当前元素在数组中以**R[i]**表示,那么,

(1) 它的左孩子结点是:R[2*i+1];

(2) 它的右孩子结点是:R[2*i+2];

(3) 它的父结点是:R[(i-1)/2];

(4) R[i] <= R[2*i+1] 且 R[i] <= R[2i+2]。

img

(1)根据初始数组去构造初始堆(构建一个完全二叉树,保证所有的父结点都比它的孩子结点数值大)。这个是logn级别

(2)每次交换第一个和最后一个元素,输出最后一个元素(最大值)这个是n级别,然后把剩下元素重新调整为大根堆。

img

当两堆总大小为偶数时,即两堆大小相等,先将新元素插入maxHeap,重新排序后将新的最值拿出并插入到minHeap;
当两堆总大小为奇数时,即两堆大小不等,先将新元素插入minHeap,重新排序后将新的最值拿出并插入到maxHeap

41-2.png

class MedianFinder {
    // 优先队列实现的就是堆
    PriorityQueue<Integer> min;
    PriorityQueue<Integer> max;
    /** initialize your data structure here. */
    public MedianFinder() {
        // 初始化默认小顶堆
min=new PriorityQueue<Integer>();
// 参数是为了构造大顶堆
max=new PriorityQueue<Integer>((x,y)->(y-x));
    }
    public void addNum(int num) {
        // 如果数组元素为奇数
        if(min.size()!=max.size()){
            min.add(num);
            max.add(min.poll());
            // 如果数组大小为偶数
        }else{
            max.add(num);
            min.add(max.poll());
        }
    }
    public double findMedian() {
        // 奇数取小顶堆堆顶
       if(min.size()!=max.size()){
         return min.peek();
        //  偶数取平均
        }else{
          return (min.peek()+max.peek())/2.0;
        }
    }
}
/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */
序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

序列化 Serialize :

借助队列,对二叉树做层序遍历,并将越过叶节点的 null 也打印出来。

特例处理: 若 root 为空,则直接返回空列表 “[]” ;
初始化: 队列 queue (包含根节点 root );序列化列表 res ;
层序遍历: 当 queue 为空时跳出;
节点出队,记为 node ;
若 node 不为空:① 打印字符串 node.val ,② 将左、右子节点加入 queue ;
否则(若 node 为空):打印字符串 “null” ;
返回值: 拼接列表,用 ‘,’ 隔开,首尾添加中括号

反序列化 Deserialize :

用队列按层构建二叉树,借助一个指针 i 指向节点 node 的左、右子节点,每构建一个 node 的左、右子节点,指针 i 就向右移动 1 位。

特例处理: 若 data 为空,直接返回 null ;
初始化: 序列化列表 vals (先去掉首尾中括号,再用逗号隔开),指针 i = 1 ,根节点 root (值为 vals[0] ),队列 queue(包含 root );
按层构建: 当 queue 为空时跳出;
节点出队,记为 node ;
构建 node 的左子节点:node.left 的值为 vals[i] ,并将 node.left 入队;
执行 i += 1 ;
构建 node 的右子节点:node.left 的值为 vals[i] ,并将 node.left 入队;
执行 i += 1 ;
返回值: 返回根节点 root 即可;

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        // 如果根节点为null,返回空数组字符串
        if(root==null){
            return "[]";
        }
        // 构建一个stringbuilder对象,用来存储结果
        StringBuilder str=new StringBuilder();
        // 初始化一个[
        str.append("[");
        // 构建一个队列用来存储层序遍历的节点
        Queue<TreeNode> queue=new LinkedList<>();
        // 先将root根节点加入队列
        queue.add(root);
        // 层序遍历,队列不为空时
        while(!queue.isEmpty()){
            // 队列队头出队
            TreeNode node=queue.poll();
            // 
            if(node!=null){
                // 将节点的值拼到字符串中
                str.append(node.val+",");
                // 将节点的左右子节点加入队列中
                queue.add(node.left);
                queue.add(node.right);
            }else{
                // 如果node为null,说明已经到叶子节点了此时将null也添加进去
                str.append("null,");
            }
        }
         // 删除最后一个逗号
            str.deleteCharAt(str.length()-1);
            // 加入]
            str.append("]");
        return str.toString();
    }
    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        // 如果字符串为空数组,返回null
        if(data.equals("[]")){return null;}
        // 不为空,将出去[] 的中字符串由,分隔成字符串数组
        String[] vals=data.substring(1,data.length()-1).split(",");
        // 构建根节点,就是序列数组的第一个值,将字符串转为int 并构建root
        TreeNode root=new TreeNode(Integer.parseInt(vals[0]));
        // 构建用来存储临时节点
        Queue<TreeNode> queue=new LinkedList<>();
        // 将根节点添加进去
        queue.add(root);
        // 字符串数组的下一个开始索引
        int i=1;
        // 队列不为空时
        while(!queue.isEmpty()){
            // 队头出队
            TreeNode node=queue.poll();
            // 如果当前字符串不为null的话
            if(!vals[i].equals("null")){
                // 由于层序遍历,所以先添加到左子节点,再添加到右子节点
                node.left=new TreeNode(Integer.parseInt(vals[i]));
                queue.add(node.left);
            }
            // 此时字符串数组后移一位
            i++;
            if(!vals[i].equals("null")){
                // 由于层序遍历,所以先添加到左子节点,再添加到右子节点
                node.right=new TreeNode(Integer.parseInt(vals[i]));
                queue.add(node.right);
            }
            i++;
        }
        // queue的作用是为了使得同一层的节点在一个队列中,poll出队是为构建node的left和right的指向
     return root;   
    }
}

// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刹那永恒HB

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值