牛客刷题
- 要有一个思想
- 有简化代码的意识
- 要注意
- 强转精度
- for 结束条件
- 判空
- 层序遍历 和 前中后序遍历
- 虚拟头结点
- while(cur!=null && cur.next!=null) 和 while(fast.next!=null && fast.next.next!=null)
- 用for移动 listnode.next
- 答案需要取模 1e9+7(1000000007)
- char[] to String
- 快慢指针和双指针
- set 和 map contains
- 遇到一个元素的示例执行不过
- 定位 string 的空格
- n 数之和
- 去重
- --num 和 num-1
- 二叉树的深度相关
- 用 linkedlist 存储 递归二叉树
- 二叉树回溯找路径的和为target
- 从前序+中序 / 中序+后续 构建二叉树
- 从前序遍历、数组最大值构建二叉树
- 二叉树需要用到 pre 节点时
- k个一组操作
- 目标和 为 target,组合总数
- 全排列
- 求子序列
- 递归+for 找可能的xx
- 左/上边 或者 右/下边 有就 true 或操作
- 链表两数相加
- 链表操作
- 二叉搜索树
- 前缀和
- Tree的最大深度
- 整数反转
- 各位数字之和
- 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内
- 如果都是字符
要有一个思想
解题时,要想以下思路
二分法 基于数组有序
双指针(动态规划) 两个变量代表两个值,根据两个变量,不断修改这两个值,直到满足条件
滑动窗口算法 双指针的一个特例,就是双指针之间的距离是固定的,以相同的步长移动
HashMap 可以监测是不是已经存在要放进去的数
TreeSet 放进去可以自动排序
递归 可以拆分成子问题,子问题的子问题....就可以用递归
优化 空间优化和时间优化
递归
递归用最后一个元素,写递归结束条件,假设当前元素是最后一个元素
if(last满足的条件) return 0;
递归分析时只看倒数第二个元素,假设传入的值是倒数第二个元素,然后从进入方法开始分析,而不是从递归的位置分析
方法最后return 什么,根据方法开始的结束条件决定
递归会占用栈的空间,入栈出栈的空间复杂度,取决于入栈/递归的深度,对于二叉树形式的递归,空间复杂度是log(n)
每次递归都会使得指数减少一半,因此递归的层数为 O(logn)
需要操作的数量 x
如果一个操作可以使的原本需要操作的 x 个元素变成需要操作 x/2 个元素
这个操作的 时间复杂度 就是 logx
如果涉及递归操作,递归需要调用栈,空间复杂度 是 logx
回溯
回溯也类似于递归
一般是二叉树的回溯
递归的最后一步 是回溯
去除 中转列表 的最后一个元素,回溯到上一个元素,还原到上一个元素的列表状态,为之后要加到中转列表中的下一个元素留出位置
二叉树如何可以自底向上查找 -> 二叉树回溯的过程就是从低到上
用 LinkedList.removeLast()
注意使用LinkedList 时应该
LinkedList<Integer> path = new LinkedList<>();
而不能 List<Integer> path = new LinkedList<>();
这样会导致向上转型,就只能用 List 的方法了,而不能再用 LinkedList 的方法
记录路径时若直接执行 res.append(path) ,则是将 path 对象加入了 res ;后续 path 改变时, res 中的 path 对象也会随之改变。
正确做法:res.append(list(path)) ,相当于复制了一个 path 并加入到 res
https://leetcode.cn/problems/permutations/
public List<List<Integer>> permute(int[] nums) {
if(nums.length==0) return res;
backTracking(nums,path);
return res;
}
public void backTracking(int[] nums,LinkedList<Integer> path){
if(nums.length==path.size()) res.add(new ArrayList<>(path)); //注意这里
for(int i=0;i<nums.length;i++){
if(path.contains(nums[i])) continue; // 如果path中已有,则跳过
path.add(nums[i]);
backTracking(nums,path);
path.removeLast(); //回溯上一个元素
}
}
//https://leetcode.cn/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/
//最终返回
List<List<Integer>> res = new ArrayList<>();
//注意这里不能向上转型,用 LinkedList<Integer> path = new LinkedList<>(); 而不能 List<Integer> path = new LinkedList<>();
//中途使用的 路径 列表
LinkedList<Integer> path = new LinkedList<>();
//dfs 前序遍历 深度优先算法 从根节点出发,到叶子结点返回
public List<List<Integer>> pathSum(TreeNode root, int target) {
if(root == null) return res;
recur(root, target);
return res;
}
private void recur(TreeNode root, int target){
if(root==null) return; //递归截止条件
target-=root.val; //减去当前节点 val
path.add(root.val); //添加到 路径 列表中
if(target==0 && root.left==null && root.right == null){
// 记录路径时若直接执行 res.append(path) ,则是将 path 对象加入了 res ;后续 path 改变时, res 中的 path 对象也会随之改变。
// 正确做法:res.append(list(path)) ,相当于复制了一个 path 并加入到 res
res.add(new LinkedList<>(path));
}else{
recur(root.left,target);
recur(root.right,target);
}
//找到找不到都要将 path 最后一个元素移除,否则会使 path 列表中的结点重复
//remove 之后,回溯上一个节点
path.removeLast(); //回溯类型递归最后一步 : 移除最后一个元素,回溯,还原上一个元素状态
}
去重
去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了
//https://leetcode.cn/problems/permutations-ii/submissions/
// 评论代码随想录
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
if(nums.length==0) return res;
boolean[] used = new boolean[nums.length]; //填充used数组
//Arrays.fill(used, false); //不用填充,默认是 false
Arrays.sort(nums); //注意 排序
backTraking(nums,used);
return res;
}
public void backTraking(int[] nums,boolean[] used){
if(path.size()==nums.length) {
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<nums.length;i++){
// used[i - 1] == true, used[i-1] 说明同⼀树⽀nums[i - 1]使⽤过
// used[i - 1] == false,!used[i-1] 说明同⼀树层nums[i - 1]使⽤过
// 如果同⼀树层nums[i - 1]使⽤过则直接跳过
if(i>0 && nums[i-1]==nums[i] && !used[i-1]){
continue;
}
//如果同⼀树⽀nums[i]没使⽤过开始处理
if(!used[i]){
used[i]=true; //标记同⼀树⽀nums[i]使⽤过,防止同一树支重复使用
path.add(nums[i]);
backTraking(nums,used);
path.removeLast(); //回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复
used[i] = false; //回溯
}
}
}
有简化代码的意识
while(i<=mid){
temp[k++] = a[i++];
}
有写成下面这种形式的意识
while(i<=mid) temp[k++] = a[i++];
奇数
x%2==1 或者 x&1=1
要注意
强转精度
double计算比int要慢,所以中间记录的值要设成int型,最后返回的时候再转换成double
如果是 要接收double,就要把最后返回的 int/int 强转
写成(double)int/int
或者 1.0*int/int 也是可以实现强转的
否则损失精度
例子:子数组最大平均值
for 结束条件
ArrayList list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
//list.size()作为结束条件,因为内部有list.add会导致list.size改变,结束条件也会改变,导致死循环
for (int i = 0; i < list.size(); i++) {
list.add(i);
System.out.print(list.get(list.size() - 1));
}
//将list.size放到外面赋值给一个int变量,int变量的值是不变的,结束条件也不会改变
int size = list.size();
for (int i = 0; i < size; i++) {
list.add(i);
System.out.print(list.get(list.size() - 1));
}
判空
item == null || item.size() == 0
如果写成item.size() == 0 || item=null
,item =null
时,item.size() 先判断
导致空指针异常
层序遍历 和 前中后序遍历
层序遍历
层序遍历用 Queue<TreeNode> queue = new LinkedList<>(); 而不是 stack
这里只能 queue.poll() 不能 queue.pop() 因为最终类型是 Queue
如果 LinkedList<TreeNode> queue = new LinkedList<>(); 就可以用 poll
层序遍历 while 条件 while (!queue.isEmpty())
当层序遍历,每层需要单个元素进行操作时,while 内部 if(node!=null),没有 for(count)
当需要对整层操作时,while 内部 for (int i = 0; i < queue.size(); i++)
add(node.left) 和 add(node.right) 没有顺序
queue.add(node); 需要node则添加,不需要则可以不加
public TreeNode searchBST(TreeNode root, int val) {
if(root==null || root.val==val) return root;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
if(node.val==val) return node;
// queue.add(node); 需要node则添加,不需要则可以不加
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
return null;
}
//注意层序遍历用的是 queue = new LinkedList<>(); 而不是 stack
public int[] levelOrder(TreeNode root) {
if(root==null) return new int[0];
ArrayList<Integer> ans = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
int count = queue.size();
for(int i=0;i<count;i++){
TreeNode node = queue.poll(); //LinkedList poll 相当于 stack 的 pop,弹出并且移除元素,poll 相当于 peek 弹出但是不移除
ans.add(node.val);
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
}
int[] res = new int[ans.size()];
for(int i=0;i<ans.size();i++){
res[i] = ans.get(i);
}
return res;
}
//https://leetcode.cn/problems/binary-tree-level-order-traversal/
public List<List<Integer>> levelOrder(TreeNode root) {
ArrayList<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
List<Integer> oneLevel = new ArrayList<>();
// 每次都取出一层的所有数据
int count = queue.size();
for (int i = 0; i < count; i++) {
TreeNode node = queue.poll();
oneLevel.add(node.val);
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
// 每次都往队头塞
result.add(oneLevel);
}
return result;
}
//https://leetcode.cn/problems/xu-lie-hua-er-cha-shu-lcof/solution/mian-shi-ti-37-xu-lie-hua-er-cha-shu-ceng-xu-bian-/
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();
if(node != null) {
res.append(node.val + ",");
queue.add(node.left);
queue.add(node.right);
}
else res.append("null,");
}
res.deleteCharAt(res.length() - 1);
res.append("]");
return res.toString();
}
public TreeNode deserialize(String data) {
if(data.equals("[]")) return null;
String[] vals = data.substring(1, data.length() - 1).split(",");
TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
int i = 1;
while(!queue.isEmpty()) {
TreeNode node = queue.poll();
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++;
}
return root;
}
}
前中后序遍历
前中后序遍历 用 Stack<TreeNode> stack = new Stack<>();
前中后序遍历 while 条件 while(!stack.isEmpty())
while 内部 没有 for
add(node.left) 和 add(node.right) 有顺序
前序遍历 先 add right,保证下次循环,left 在前,根左右
后序遍历 先 add left, 得到 根右左 -> Collections.reverse(res) -> 左右根
前中后序遍历,可以用 Stack 迭代,或者递归
//https://leetcode.cn/problems/binary-tree-preorder-traversal/
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
if(root == null) return ans;
Stack<TreeNode> stack = new Stack<>();
stack.push(root); //push 了 根
while(!stack.isEmpty()){
TreeNode node = stack.pop(); //拿到了 根
ans.add(node.val); //因为前序遍历 根左右,所以先把 根 放进 ans
if(node.right!=null) stack.push(node.right);
// 将根放进 ans ,right 进栈,left 进栈,这样出栈的时候就是 left 先出,下次循环 ans 新增的是left,然后再是 right
if(node.left!=null) stack.push(node.left);
}
return ans;
}
中序遍历,还是用递归吧,层序遍历不好写
//https://leetcode.cn/problems/binary-tree-inorder-traversal/
ArrayList<Integer> res = new ArrayList<>();
public List<Integer> inorderTraversal1(TreeNode root) {
recur(root);
return res;
}
//让递归 先进入 left,再递归进入 right,这样返回的时候,先返回 left ,也就是 先 res.add(left) 然后根右
void recur(TreeNode root) {
if(root==null) return;
recur(root.left);
res.add(root.val);
recur(root.right);
}
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> ans = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
// stack.push(root); 中序遍历,左根右 不需要先把 根 放进去了
TreeNode cur = root;
while(cur!=null||!stack.isEmpty()){
//中序遍历需先判断当前结点是否存在,若存在则将该节点放入栈中,再将当前结点设置为结点的左孩子,
// 若不存在则取栈顶元素为cur,当且仅当栈空cur也为空,循环结束。
if(cur!=null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
ans.add(cur.val);
cur = cur.right;
}
}
return ans;
}
虚拟头结点
链表前面再加一个头指针
,这样就不用再判断是否为第一个结点了
/**
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
ListNode dummy = new ListNode(0);
dummy.next = head;
/**
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
ListNode dummy = new ListNode(0,head);
while(cur!=null && cur.next!=null) 和 while(fast.next!=null && fast.next.next!=null)
写 cur.next !=null 是为了 cur.next 作为指针接 . 时,不会 空指针异常
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
// https://leetcode.cn/problems/delete-node-in-a-linked-list/
public ListNode deleteNode(ListNode head, int val) {
if(head==null) return head;
ListNode dummy = new ListNode(0); //链表前面再加一个头指针,这样就不用再判断是否为第一个结点了
dummy.next = head;
ListNode cur = head;
ListNode pre = dummy;
while(cur!=null){ //不需要 cur.next != null 写 cur.next !=null 是为了 cur.next 作为指针接 . 时,不会 空指针异常
if(cur.val == val) pre.next = cur.next;
pre = cur;
cur = cur.next;
}
return dummy.next;
}
}
while(fast.next!=null && fast.next.next!=null)
写 fast.next.next!=null 是为了如果while 中 fast = fast.next.next =null了,则 npe 空指针异常
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
/**
1、快慢指针找到中间节点
2、反转后半段链表
3、前半段在前,后半段在后,一个一个交叉
*/
public void reorderList(ListNode head) {
if(head==null || head.next==null) return;
ListNode mid = findMid(head);
ListNode l1 = head;
ListNode l2 = mid.next;
mid.next=null; //l1最后一个节点指向null
l2 = reverse(l2);
merge(l1,l2);
}
//寻找中间节点,slow 走一步,fast走两步,fast走到最后,slow 正好走了一半
private ListNode findMid(ListNode node){
ListNode slow = node;
ListNode fast = node;
while(fast.next!=null && fast.next.next!=null){
slow = slow.next;
fast = fast.next.next; //当 fast.next.next=null 时,while(fast.next!=null) 会报错,npe,所以要 while(fast.next!=null && fast.next.next!=null)
}
//比如 1 2 3 4,fast 在 4,则 slow 在 2
return slow;
}
private ListNode reverse(ListNode node){
ListNode pre = null;
ListNode cur = node;
ListNode next = null;
while(cur!=null){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
private void merge(ListNode l1,ListNode l2){
ListNode temp1;
ListNode temp2;
while(l1!=null&&l2!=null){
temp1 = l1.next; //保存下一个节点
temp2 = l2.next; //保存下一个节点
l1.next = l2; //合并,改变下一个节点
l1 = temp1; //移动
l2.next = l1;
l2 =temp2;
}
}
}
用for移动 listnode.next
移动到 left 上一位
for(int i=0;i<left;i++) pre = pre.next;
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(-1,head);
ListNode pre = dummy;
//链表从1开始,画图看从dummy(-1位置的节点)到left前一个要pre.next几次, < left 是正好到left位置
// 这里 left-1 是因为 题目left 为个数,而不是下标
for(int i=0;i<left-1;i++) pre = pre.next;
// 此时 pre 为 left 的前一个节点
ListNode cur = pre.next;
// cur 和next 向右移动,pre 一直在 left的前一个节点
for(int i=0;i<right-left;i++){
ListNode next = cur.next; //next cur.next
cur.next = next.next; //cur.next next.next
next.next = pre.next; //next.next pre.next
pre.next = next; //pre.next next
}
return dummy.next;
}
答案需要取模 1e9+7(1000000007)
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1
遇见这种需要取模的
需要取模的变量 A,和 A 变量有运算关系的变量 B,都使用 long 声明
和 A,B 类变量关联的运算 代码,不单独写,按下一条的格式
两个点,{取余,强转为 int}; 例如 return (int) (result*n%1000000007)
剪绳子
https://leetcode.cn/problems/jian-sheng-zi-ii-lcof/
// 规律
public int cuttingRope(int n) {
if (n == 2) return 1;
if (n == 3) return 2;
if (n == 4) return 4;
long result = 1;
while (n > 4) {
result *= 3;
result = result % 1000000007; //一直对它取余来保证不超过范围
n -= 3;
}
return (int) (result*n%1000000007);
}
char[] to String
char[] arr = s.toCharArray();
String str = new String(arr);
快慢指针和双指针
快慢指针slow 从 0 位置出发,fast 从 1 位置出发
双指针是 l 从 0 位置出发,r 从 nums.length-1 位置出发
//https://leetcode.cn/problems/two-sum/submissions/
//注意不能按照随意顺序返回 不能用map
// 本题为排序数组,可以用双指针
public int[] twoSum1(int[] nums, int target) {
if(nums==null||nums.length==0) return new int[0];
int l = 0;
int r = nums.length-1;
while(l<r){
int sum = nums[l]+nums[r];
if(sum<target) l++;
else if(sum>target) r--;
else return new int[]{l,r};
}
return null;
}
// 快慢指针不好写,二分法
// 选定每一个数,查找该数右边是否有配对数
// 首先固定第一个数,然后寻找第二个数,第二个数等于目标值减去第一个数的差。利用数组的有序性质,可以通过二分查找的方法寻找第二个数。为了避免重复寻找,在寻找第二个数时,只在第一个数的右侧寻找
public int[] twoSum(int[] nums, int target) {
for(int i=0;i<nums.length;i++){
int l=i+1,r=nums.length-1;
while(l<=r){
int mid=(r-l)/2+l;
if(nums[mid] == target-nums[i]) return new int[]{i,mid};
else if(nums[mid]<target-nums[i]) l=mid+1;
else r=mid-1;
}
}
return null;
}
//首尾双指针
public int[] exchange(int[] nums) {
int left=0,right = nums.length-1;
while(left<right){
while(left<right&&(nums[left]&1)==1) left++;
while(left<right&&(nums[right]&1)==0) right--;
swap(nums,left,right);
}
return nums;
}
set 和 map contains
遇到一个元素的示例执行不过
首先考虑代码中循环的 < 改成 <=
// 暴力算法
// https://leetcode.cn/problems/implement-strstr/solution/shua-chuan-lc-shuang-bai-po-su-jie-fa-km-tb86/
public int strStr(String haystack, String needle) {
int hlen = haystack.length();
int nlen = needle.length();
char[] harr = haystack.toCharArray();
char[] narr = needle.toCharArray();
// 枚举原串的「发起点」
for(int i=0;i<=hlen - nlen;i++){ 这里
// 从原串的「发起点」和匹配串的「首位」开始,尝试匹配
int l=i;int r=0;
while(r<nlen && harr[l]==narr[r]){
l++;
r++;
}
if(r==nlen) return i; // 如果能够完全匹配,返回原串的「发起点」下标
}
return -1;
}
定位 string 的空格
while(i>=0 && s.charAt(i) != ' ') i--;
操作
while(i>=0 && s.charAt(i) == ' ') i--;
// 倒叙添加
// https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/solution/mian-shi-ti-58-i-fan-zhuan-dan-ci-shun-xu-shuang-z/
public String reverseWords(String s) {
s = s.trim(); //去除首尾空格
int j = s.length()-1;
int i=j;
StringBuilder res = new StringBuilder();
while(i>=0){
while(i>=0 && s.charAt(i) != ' ') i--; //搜索倒叙的首个空格,i为空格下标
res.append(s.substring(i+1,j+1) + " "); //添加单词 i+1 是因为 i 此时为空格下标, j+1 是因为 j=length-1, substring 左闭右开
while(i>=0 && s.charAt(i) == ' ') i--; //跳过空格,i此时为空格前一个下标
j=i; // j=i 为空格前一个单词最后字母下标
}
return res.toString().trim(); //去除最后多加的空格
}
n 数之和
三数之和四数之和,肯定三点
排序
Arrays.sort(nums);
for里面while(l<r)
去重
1. for去重
if(i>0&&nums[i-1]==nums[i]) continue;
2. while 去重
while(l<r && nums[l] == nums[++l]);
while(l<r && nums[r] == nums[--r]);
去重
if(k > 0 && nums[k] == nums[k - 1]) continue;
while(i < j && nums[i] == nums[++i]);
..
while(i < j && nums[j] == nums[--j]);
// https://leetcode.cn/problems/3sum/solution/3sumpai-xu-shuang-zhi-zhen-yi-dong-by-jyd/
public List<List<Integer>> threeSum1(int[] nums) {
Arrays.sort(nums);//排序,nums变成递增数组
List<List<Integer>> res = new ArrayList<>();
//k < nums.length - 2是为了保证后面还能存在两个数字
for(int k = 0; k < nums.length - 2; k++){
if(nums[k] > 0) break;//若nums[k]大于0,则后面的数字也是大于零(排序后是递增的)
if(k > 0 && nums[k] == nums[k - 1]) continue;//nums[k]值重复了,去重
int i = k + 1, j = nums.length - 1;//定义左右指针
while(i < j){
int sum = nums[k] + nums[i] + nums[j];
if(sum < 0){
while(i < j && nums[i] == nums[++i]);//左指针前进并去重
} else if (sum > 0) {
while(i < j && nums[j] == nums[--j]);//右指针后退并去重
} else {
res.add(new ArrayList<Integer>(Arrays.asList(nums[k], nums[i], nums[j])));
while(i < j && nums[i] == nums[++i]);//左指针前进并去重
while(i < j && nums[j] == nums[--j]);//右指针后退并去重
}
}
}
return res;
}
–num 和 num-1
Math.max(minCount-1, 0)
等价于
Math.max(--minCount,0)
二叉树的深度相关
基本都是递归 第一行 if(node==null) return 0;
或者层序遍历
用 linkedlist 存储 递归二叉树
注意
LinkedList声明时 LinkedList<Integer> path = new LinkedList<>();
不要 List<Integer> path = new LinkedList<>(); 否则不能用 removeLast
recur最后removeLast,回溯
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
if(root==null) return res;
recur(root,target);
return res;
}
private void recur(TreeNode root, int target) {
if(root==null) return;
target-=root.val;
path.add(root.val);
if(target == 0 && root.left==null && root.right==null) res.add(new LinkedList<>(path));
else{
recur(root.left,target);
recur(root.right,target);
}
path.removeLast();
}
二叉树回溯找路径的和为target
回溯
常用代码
target-=root.val;
if(target == 0 && root.left==null && root.right==null) xx操作
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
if(root==null) return res;
recur(root,target);
return res;
}
private void recur(TreeNode root, int target) {
if(root==null) return;
target-=root.val;
path.add(root.val);
if(target == 0 && root.left==null && root.right==null) res.add(new LinkedList<>(path));
else{
recur(root.left,target);
recur(root.right,target);
}
path.removeLast();
}
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null) return false;
targetSum-=root.val;
if(targetSum==0 && root.left==null && root.right==null) return true; //叶子结点且targetSum
else return hasPathSum(root.left,targetSum) || hasPathSum(root.right,targetSum); //在左面或右面找到都可
}
从前序+中序 / 中序+后续 构建二叉树
总之就是跳过 根 所在位置
不论是preorder,还是inorder
重点是,最后的 root.left root.right 递归
中序跳过 rootIndex
前后序包括 rootIndex;前序跳过 0,后续跳过 len-1
Arrays.copyOfRange 左闭右开
public TreeNode buildTree(int[] inorder, int[] postorder) {
int len = inorder.length;
if(len==0) return null;
int rootValue = postorder[len-1];
int rootIndex = 0;
for(int i=0;i<len;i++){
if(inorder[i]==rootValue){
rootIndex = i;
break;
}
}
TreeNode root = new TreeNode(rootValue);
root.left = buildTree(Arrays.copyOfRange(inorder,0,rootIndex),Arrays.copyOfRange(postorder,0,rootIndex));
root.right = buildTree(Arrays.copyOfRange(inorder,rootIndex+1,len),Arrays.copyOfRange(postorder,rootIndex,len-1));
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int len = inorder.length;
if(len==0) return null;
int rootValue = preorder[0];
int rootIndex = 0;
TreeNode root = new TreeNode(rootValue);
for(int i=0;i<len;i++){
if(rootValue==inorder[i]){
rootIndex = i;
break;
}
}
root.left = buildTree(Arrays.copyOfRange(preorder,1,rootIndex+1),Arrays.copyOfRange(inorder,0,rootIndex));
root.right = buildTree(Arrays.copyOfRange(preorder,rootIndex+1,len),Arrays.copyOfRange(inorder,rootIndex+1,len));
return root;
}
从前序遍历、数组最大值构建二叉树
总之就是跳过 根 所在位置
不论是preorder,还是inorder
1. 和上一个不同的是,recur参数的不同
这种recur 为
private TreeNode recur(int[] preorder,int l,int r){}
2. 且递归 中间不包括的是 本次循环的根节点 index
3. for(int i=l;i<=r;i++){ 循环找到rootIndex 包括 <=
//4. 考虑只有左子树没有右子树的情况
if(rootIndex==0){
root.right = recur(preorder,l+1,r);
}else{
root.left = recur(preorder,l+1,rootIndex-1);
root.right = recur(preorder,rootIndex,r);
}
这里也是跳过根节点,前序遍历,根节点在 l 位置,所以跳过 l
//前序遍历的顺序是 根 --> 左 --> 右,再结合二叉搜索树的特点
// 左 < 根 < 右,所以在数组中的表现为第一个元素为根节点,从第一个节点之后所有小于他的数为左节点,其他数为右节点
public TreeNode bstFromPreorder(int[] preorder) {
return build(preorder,0,preorder.length);
}
//在[l,r)范围内构建搜索二叉树
public TreeNode build(int[] preorder,int l,int r){
if(l==r) return null;
TreeNode ans = new TreeNode(preorder[l]);
int i = 0;
// 找到第一个大于pre[l]的元素作为rootIndex
for(i = l;i < r;i++){
if(preorder[i] > preorder[l]) break;
}
//ans是最顶端根,pre[i]是第二个根
ans.left = build(preorder,l+1,i);
ans.right = build(preorder,i,r);
return ans;
}
//不同于https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
// 这里是要找到最大的value作为root,而上面的已经明确了前序后序,分段的第一个或者最后一个就是max
public TreeNode constructMaximumBinaryTree(int[] nums) {
return recur(nums,0,nums.length-1);
}
private TreeNode recur(int[] nums,int l,int r){
if(l>r) return null;
int rootIndex = findMaxIndex(nums,l,r);
TreeNode root = new TreeNode(nums[rootIndex]);
if(rootIndex==0){ //没有左子树,只有右子树
root.right = recur(nums,l+1,r);
}else{
root.left = recur(nums,l,rootIndex-1); //本次递归根节点是 rootIndex 位置的数,所以不包括 rootIndex
root.right = recur(nums,rootIndex+1,r);
}
return root;
}
private int findMaxIndex(int[] nums,int l,int r){
int max = Integer.MIN_VALUE;
int maxIndex = 0;
for(int i=l;i<=r;i++){
if(max < nums[i]){
max = nums[i];
maxIndex = i;
}
}
return maxIndex;
}
// 以升序数组的中间元素作为根节点 root
public TreeNode sortedArrayToBST(int[] nums) {
return recur(nums,0,nums.length-1);
}
private TreeNode recur(int[] nums,int l,int r){
if(l>r) return null;
int rootIndex = l+ (r-l)/2;
TreeNode root = new TreeNode(nums[rootIndex]);
root.left = recur(nums,l,rootIndex-1);
root.right = recur(nums,rootIndex+1,r);
return root;
}
二叉树需要用到 pre 节点时
TreeNode pre = null; 作为类变量
recur 中
{
if(pre!=null){
相关操作;
}
pre = root; //pre = 当前节点
}
//二叉搜索树采用中序遍历,其实就是一个有序数组。
TreeNode max = null;
public boolean isValidBST(TreeNode root) {
if(root==null) return true;
boolean left = isValidBST(root.left); //左 左根右遍历
if (!left) return false;
if (max != null && root.val <= max.val) return false; // 中 结束条件:当前的节点(左节点),小于上一个节点
max = root; // 记录前一个节点
boolean right = isValidBST(root.right); // 右 当前的节点(左节点),小于上一个节点
return right;
}
TreeNode pre;
int res= Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
if(root==null) return 0;
recur(root);
return res;
}
private void recur(TreeNode root){
if(root==null) return;
recur(root.left);
if(pre!=null){
res = Math.min(res,root.val-pre.val); //不用abs因为,前中后有序
}
pre = root;
recur(root.right);
}
k个一组操作
都要考虑 不足 k 个
public ListNode reverseKGroup(ListNode head, int k) {
if(head == null) return null;
ListNode a = head,b = head; // 区间 [a, b) 包含 k 个待反转元素
for(int i = 0 ;i < k;i++){
if (b == null) return a; // 不足 k 个,不需要反转,base case
b = b.next;
}
ListNode newHead = reverse(a,b); // 反转前 k 个元素
a.next = reverseKGroup(b, k); // 递归反转后续链表并连接起来,注意传入的是b,b此时是下一个k段的第一个节点
return newHead;
}
//反转区间a---b
ListNode reverse(ListNode a,ListNode b){
ListNode cur = a, pre = null;
while(cur != b){ //没有到b时。而不是cur!=null 这是从cur到最后都反转了
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
目标和 为 target,组合总数
关于 回溯的两个问题
问题一:不重复使用元素 通过 used 或者 contains()判断
问题二:
1. 全排列:需要用到全部元素
for(int i = 0;i < candidates.length;i++)
通过 path.contains 跳过用过的元素
2. 组合总数:需要用到部分元素
for(int i = index;i < candidates.length;i++)
思考方式:
关键点1:含不含重复数字
关键点2:能不能重复使用
示例1:
假如全排列 1 2 3, 不能重复使用。
for(int i = 0;i < candidates.length;i++)
第一次进入时 i=0,取到 1,list中为1,进入第二次递归
第二次进入时 i=0,取到 1,不能重复使用所以,path.contains(1) continue
示例2:
假如全排列 2 2 3, 不能重复使用。
for(int i = 0;i < candidates.length;i++)
第一次进入时 i=0,取到 2,list中为2,进入第二次递归
第二次进入时 i=0,取到 2
a. if(i>0 && nums[i-1]==nums[i] && used[i-1]) continue; 剪枝,跳过重复的情况
b. 不能重复使用所以if(used[i]) continue;
问题三:是否 输出全部元素 还是 输出子序列
输出全部元素
for(int i = 0;i < candidates.length;i++)
通过 path.contains 跳过用过的元素
全排列中
剪枝,为了避免重复使用
if(path.contains(nums[i])) continue;
组合总数,for(int i = index)
每个数字使用一次 used[i] 或者 contains(nums[i])
有重复数字则剪枝 nums[i] == nums[i-1] continue
输出子序列
for(int i = index;i < candidates.length;i++)
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new LinkedList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtracking(candidates, target, 0);
return res;
}
public void backtracking(int[] candidates, int target, int idx) {
if (target<0) return;
if (target==0) res.add(new ArrayList<>(path));
//注意for中i=idx
for (int i = idx; i < candidates.length; i++) {
path.add(candidates[i]);
//注意下面传的i,每个数字在每个组合中可以多次使用
backtracking(candidates, target-candidates[i], i);
path.remove(path.size() - 1);
}
}
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
backtrack(candidates,target,0);
return res;
}
private void backtrack(int[] candidates,int target,int begin) {
if(target<0) return;
if(target==0) {
res.add(new ArrayList<>(path));
return;
}
for(int i = begin;i < candidates.length;i++) {
if(i > begin && candidates[i] == candidates[i-1]) continue; //剪枝
path.add(candidates[i]);
//每个数字在每个组合中只能使用一次,所以i+1
backtrack(candidates,target-candidates[i],i+1);
path.remove(path.size()-1);
}
}
递归 target 递减
直到 target==0
组合总数是 for (int i = idx; i < candidates.length; i++)
全排列是 for (int i = 0; i < candidates.length; i++)
剪枝,为了避免重复使用得到结果相同
if(i > begin && candidates[i] == candidates[i-1]) continue;
全排列
求子序列
递归+for 找可能的xx
适用于 类似全排列,类比为一棵树
recur(){
if(到达叶子结点) {
操作
return;
}
for(){
操作
recur
回到操作前的状态
}
}
recur 变量必包括 变化的变量,比如下一行
void method(){
剪枝
递归结束条件
for(){
进入递归
递归去除最后一个元素
}
}
for循环的作用在于另寻他路:
你可以用for循环可以实现一个路径选择器的功能,该路径选择器可以逐个选择当前节点下的所有可能往下走下去的分支路径。
例如: 现在你走到了节点a,a就像个十字路口,你从上面来到达了a
可以继续向下走。若此时向下走的路有i条,那么你肯定要逐个的把这i条都试一遍才行。
而for的作用就是可以让你逐个把所有向下的i个路径既不重复,也不缺失的都试一遍
递归可以实现一条路走到黑和回退一步: 一条路走到黑: 递归意味着继续向着for给出的路径向下走一步。
如果我们把递归放在for循环内部,那么for每一次的循环,都在给出一个路径之后,进入递归,也就继续向下走了。
直到递归出口(走无可走)为止。 那么这就是一条路走到黑的实现方法。
递归从递归出口出来之后,就会实现回退一步。
因此for循环和递归配合可以实现回朔:
当递归从递归出口出来之后。上一层的for循环就会继续执行了。
而for循环的继续执行就会给出当前节点下的下一条可行路径。
而后递归调用,就顺着这条从未走过的路径又向下走一步。这就是回朔
// 通过判断path中是否存在数字,排除已经选择的数字
List<List<Integer>> res = new ArrayList<>(); //存放符合条件的结果合集
LinkedList<Integer> path = new LinkedList<>(); //存放符合条件的结果 中转列表
public List<List<Integer>> permute1(int[] nums) {
if(nums == null) return null;
if(nums.length == 0) return res;
dfs1(nums,path);
return res;
}
private void dfs1(int[] nums,LinkedList<Integer> path){
if(nums.length == path.size()) {
res.add(new ArrayList<>(path));
return; //注意这里一定要 return
}
for(int i=0;i<nums.length;i++){
if(path.contains(nums[i])) continue; // 如果path中已有,则跳过
path.add(nums[i]);
dfs1(nums,path);
path.removeLast(); //回溯上一个元素
}
}
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
if(nums==null) return null;
if(nums.length==0) return res;
used = new boolean[nums.length]; //初始化 boolean ,默认为 false;
dfs(nums);
return res;
}
private void dfs(int[] nums){
if(nums.length==path.size()) {
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<nums.length;i++){
if(used[i]) continue;
used[i] = true;
path.add(nums[i]);
dfs(nums);
path.removeLast();
used[i]=false;
}
}
List<String> res;
boolean[] used;
public String[] permutation(String s) {
if(s==null) return null;
if(s.length()==0) return new String[0];
res = new ArrayList<String>();
used = new boolean[s.length()];
char[] arr = s.toCharArray();
Arrays.sort(arr);
StringBuffer perm = new StringBuffer();
backtrack(arr,0,perm);
int size = res.size();
String[] result = new String[size];
for(int i=0;i<size;i++) result[i] = res.get(i);
return result;
}
public void backtrack(char[] arr, int i, StringBuffer perm) {
if(i == arr.length){
res.add(perm.toString());
return;
}
for(int j=0;j<arr.length;j++){
//下面去重
if(used[j] || (j>0 && arr[j-1] == arr[j] && !used[j-1])) continue;
used[j]=true;
perm.append(arr[j]);
backtrack(arr,i+1,perm);
perm.deleteCharAt(perm.length() - 1);
used[j] = false;
}
}
保存所有方案 用 List<List<String>> res = new ArrayList<>();
// https://leetcode-cn.com/problems/n-queens/solution/dai-ma-sui-xiang-lu-51-n-queenshui-su-fa-2k32/
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char[][] chessboard = new char[n][n];
for(char[] ch : chessboard) Arrays.fill(ch,'.'); //填充二维数组,就是填充每一行
backTrack(n,0,chessboard);
return res;
}
public List char2List(char[][] ch){
List<String> list = new ArrayList<>();
for(char[] c:ch) list.add(String.valueOf(c));
return list;
}
public void backTrack(int n,int row,char[][] chessboard){ //n为从上到下的行数,row是列
if(row==n) { //到了最后一行,叶子结点,递归结束条件
res.add(char2List(chessboard));
return;
}
for(int col=0;col<n;col++){ //每行都是从第一列开始,从树上看就是,每层树从第一列开始
if(isValid(row,col,n,chessboard)){ //判断放置是否合法
chessboard[row][col]='Q'; // 放置皇后
backTrack(n,row+1,chessboard);
chessboard[row][col] = '.'; // 回溯,撤销皇后
}
}
}
public boolean isValid(int row,int col,int n,char[][] chessboard){
//列往前查,因为递归是从上往下,从左往右,后面的和下面的接下来会递归到
for(int i=0;i<n;i++){
if(chessboard[i][col]=='Q') return false;
}
//从[n][col] 到 45度 左上角检查
for(int i=row-1,j=col-1;i>=0 && j>=0;i--,j--){
if(chessboard[i][j]=='Q') return false;
}
//从[n][col] 到 135度 右上角检查
for(int i=row-1,j=col+1;i>=0 && j<=n-1;i--,j++){
if(chessboard[i][j]=='Q') return false;
}
// 每次都是要从新的一行的起始位置开始搜,所以都是从0开始。
// 同行和下方式不同考虑的,因为是从上到下,从左到右放置皇后的
return true;
}
找个数,方案数量 用
private int res = 0;
private boolean used[][];
recur(){
if(到达叶子结点) {
操作
return;
}
for(){
used[i][j] = true;
recur
used[i][j] = false;
}
}
private int res = 0;
private boolean used[][];
public int totalNQueens(int n) {
used = new boolean[n][n]; //初始化
// Arrays.fill(); 不用填充,默认false
backTrack(0,n);
return res;
}
public void backTrack(int row,int n){
if(row == n) {
res++;
return;
}
for(int col=0;col<n;col++){
if(check(row,col,n)){
used[row][col]=true;
backTrack(row+1,n);
used[row][col]=false;
}
}
}
public boolean check(int row,int col,int n){
//列往前查,因为递归是从上往下,从左往右,后面的和下面的接下来会递归到
for(int i=0;i<n;i++){
if(used[i][col]) return false;
}
//从 当前[row][col] 到 45度 左上角检查
for(int i=row-1,j=col-1;i>=0 && j>=0;i--,j--){
if(used[i][j]) return false;
}
//从 当前[row][col] 到 135度 右上角检查
for(int i=row-1,j=col+1;i>=0 && j<=n-1;i--,j++){
if(used[i][j]) return false;
}
return true;
}
// https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/
//本题和 矩阵中的路径 的区别是,本题 [i][j] 只向右下方向移动
// https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/solution/mian-shi-ti-13-ji-qi-ren-de-yun-dong-fan-wei-dfs-b/
public int movingCount(int m, int n, int k) {
boolean[][] visited = new boolean[m][n];
return dfs(0,0,m,n,k,visited);
}
private int dfs(int i,int j,int m,int n,int k,boolean[][] visited){
if(i<0 || i>=m || j<0 || j>=n || getSum(i,j) > k || visited[i][j]) return 0;
visited[i][j] = true;
return 1 + dfs(i+1,j,m,n,k,visited) + dfs(i,j+1,m,n,k,visited); //只向右下方向移动
}
//此题条件 1 <= m,n <= 100,所以可以直接 (i/10+i%10 + j/10+j%10)
// 注意是 计算的 getSum(i,j) 因为是依次向右下方移动 [i][j]
private int getSum(int i,int j){
int res = 0;
while(i!=0){
res += i%10;
i/=10;
}
while(j!=0){
res += j%10;
j/=10;
}
return res;
}
// https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/submissions/
//本题和 机器人的运动范围 的区别是,本题 [i][j] 向 上下左右 移动
// https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/solution/mian-shi-ti-12-ju-zhen-zhong-de-lu-jing-shen-du-yo/
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
boolean[][] visited = new boolean[board.length][board[0].length];
for(int i=0;i<board.length;i++){
for(int j=0;j<board[0].length;j++){
if(dfs(board,words,i,j,0,visited)) return true;
}
}
return false;
}
private boolean dfs(char[][] board, char[] words,int i,int j,int k,boolean[][] visited){
if(i>=board.length || i<0 || j>=board[0].length || j<0 || board[i][j]!=words[k] || visited[i][j]) return false;
if(k==words.length-1) return true;
// 递归搜索匹配字符串过程中,需要 标记 board[i][j] 使用过了, 来防止 ”走回头路“ 。
// 在DFS过程中,每个单元格会多次被访问的, visited[i][j] = true 只是要保证在当前匹配方案中不要走回头路。
visited[i][j] = true;
boolean res = dfs(board,words,i+1,j,k+1,visited) || dfs(board,words,i,j+1,k+1,visited) ||
dfs(board,words,i-1,j,k+1,visited) || dfs(board,words,i,j-1,k+1,visited);
// 当匹配字符串成功或者不成功时,会回溯返回,此时需要 取消标记 board[i][j] 为 visited 来”取消对此单元格的标记”。
visited[i][j] = false;
return res;
}
左/上边 或者 右/下边 有就 true 或操作
返回 true false 则 return 左边递归 || 右边递归
需要操作返回体则
{
递归结束条件
修改状态
返回结果操作
左边递归
右边递归
递归重置 removeLast() used[i] = false
}
或者
{
递归结束条件
修改状态
返回结果操作
if(左边没到结束条件){
左边递归
递归重置 removeLast() used[i] = false
}
// 注意不是 else if 因为两边任意一边满足即可
if(右边没到结束条件){
右边递归
递归重置 removeLast() used[i] = false
}
}
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null) return false;
targetSum-=root.val;
if(targetSum==0 && root.left==null && root.right==null) return true; //叶子结点且targetSum
return hasPathSum(root.left,targetSum) || hasPathSum(root.right,targetSum); //在左面或右面找到都可
}
/*
回溯:
使用辅助的递归函数进行回溯,使用两个全局变量记录结果
*/
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
backTracking(root, targetSum);
return res;
}
public void backTracking(TreeNode root, int targetSum){
//递归边界
if(root == null) return;
//递归前修改状态
targetSum -= root.val;
path.add(root.val);
//判断是否找到了结果
if(root.left == null && root.right == null && targetSum == 0) res.add(new ArrayList<>(path));
//继续向下递归
backTracking(root.left, targetSum);
backTracking(root.right, targetSum);
//递归后重置状态
path.removeLast();
}
public void backTracking1(TreeNode root, int targetSum){
//递归边界
if(root == null) return;
//递归前修改状态
targetSum -= root.val;
path.add(root.val);
//判断是否找到了结果
if(root.left == null && root.right == null && targetSum == 0) res.add(new ArrayList<>(path));
//继续向下递归
if(root.left!=null){
backTracking(root.left, targetSum);
path.removeLast(); //递归后重置状态
}
if(root.right!=null){
backTracking(root.right, targetSum);
path.removeLast(); //递归后重置状态
}
}
链表两数相加
核心思想
按题目结果找方向加,进位沿着这个方向进位
如果链表初始方向与寻找的方向相反则翻转链表
加完之后如果与所需结果不一致,则翻转最终的链表
class Solution {
// 按题目结果找方向加,进位沿着这个方向进位,如果链表初始方向与寻找的方向相反则翻转链表
//加完之后如果与所需结果不一致,则翻转最终的链表
// https://leetcode.cn/problems/add-two-numbers/
public ListNode addTwoNumbers2(ListNode l1, ListNode l2) {
ListNode L1 = reverseList(l1);
ListNode L2 = reverseList(l2);
ListNode dummy = new ListNode(0);
ListNode cur = dummy; //辅助,要移动的节点,而不是在dummy上移动
int temp = 0; //记录进位
while(L1!=null||L2!=null||temp!=0){
int x = L1!=null ? L1.val : 0 ; //一者为null则当做0处理
int y = L2!=null ? L2.val : 0 ;
int sum = x+y+temp; //要加上当前的进位
temp = sum/10; //更新进位
ListNode node = new ListNode(sum%10); //取个位数
cur.next = node; //把新节点连起来
cur = cur.next; //当前节点往后移动
if(L1!=null) L1 = L1.next;
if(L2!=null) L2 = L2.next;
}
return reverseList(dummy.next);
}
private ListNode reverseList(ListNode node){
if(node==null || node.next == null) return node;
ListNode pre = null;
ListNode cur = node;
while(cur!=null){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
//使用栈,先进后出 FILO
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
while(l1!=null){
stack1.push(l1.val);
l1 = l1.next;
}
while(l2!=null){
stack2.push(l2.val);
l2 = l2.next;
}
int temp = 0;
ListNode res = null;
while(!stack1.isEmpty() || !stack2.isEmpty() || temp!=0){
int x = stack1.isEmpty() ? 0 : stack1.pop();
int y = stack2.isEmpty() ? 0 : stack2.pop();
int sum = x + y + temp;
temp= sum/10;
ListNode node = new ListNode(sum%10);
// 下面两行相当于将res变为 node(node变成头结点,res变为第二个结点),同时res向左移动一位,变成头结点
node.next = res;
res = node;
}
return res;
}
}
链表操作
只要不在链表本身进行操作,就要新建ListNode
ListNode dummy = new ListNode(0,head);
21. 合并两个有序链表
19. 删除链表的倒数第 N 个结点
剑指 Offer 22. 链表中倒数第k个节点
二叉搜索树
二叉搜索树涉及到【相邻节点】的比较等需要用到前一个或者后一个值的操作
一般定义 TreeNode pre;
一般使用【前序遍历+递归】
二叉搜索树中序遍历升序
class Solution {
// 要求我们将每个节点的值修改为原来的节点值加上所有大于它的节点值之和。这样我们只需要反序中序遍历该二叉搜索树
int preVal=0;
public TreeNode convertBST(TreeNode root) {
if(root==null) return root;
convertBST(root.right);
root.val = root.val+preVal;
preVal = root.val;
convertBST(root.left);
return root;
}
}
class Solution {
TreeNode pre;
int res= Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
if(root==null) return 0;
recur(root);
return res;
}
private void recur(TreeNode root){
if(root==null) return;
recur(root.left);
if(pre!=null){
res = Math.min(res,root.val-pre.val); //不用abs因为,前中后有序
}
pre = root;
recur(root.right);
}
}
众数,出现次数最多的数
class Solution {
ArrayList<Integer> res;
int count;
int maxCount;
TreeNode pre;
public int[] findMode(TreeNode root) {
res = new ArrayList<>();
if(root==null) return new int[0];
recur(root);
int[] arr = new int[res.size()];
for(int i=0;i<arr.length;i++){
arr[i] = res.get(i);
}
return arr;
}
void recur(TreeNode node){
if(node==null) return;
recur(node.left);
int nodeValue = node.val;
if(pre!=null && nodeValue!=pre.val) count=1; // 计数
else count++;
if(maxCount<count){ // 更新结果以及maxCount
res.clear();
res.add(nodeValue);
maxCount = count;
}else if(maxCount==count){
res.add(nodeValue);
}
pre = node;
recur(node.right);
}
}
二叉搜索树中序遍历就是遍历有序数组
左根右升序
右根左降序
// 注意是第K大,所以右根左;第K小才是左根右
// 二叉搜索树的一个特性:通过中序遍历所得到的序列,就是有序的。
// 当遍历到了第K大数的时候,就可以停止遍历了,同时,把遍历到节点对应的数保存下来即可。
int count = 0,ans = 0;
public int kthLargest(TreeNode root, int k) {
recur(root,k);
return ans;
}
void recur(TreeNode node,int k){
if(node.right!=null) recur(node.right,k); //右
count++;
if(count==k){
ans = node.val; //根
return;
}
if(node.left!=null) recur(node.left,k); //左
}
前缀和
统一套路
HashMap<Integer,Integer> map = new HashMap<>();
map.put(0,1);
for(int num:nums){
操作;
res+=map.getOrDefault(操作,0);
map.put(key,map.getOrDefault(key,0)+1);
}
return res;
构建前缀和
for (int i = 1; i < nums.length; i++) nums[i] += nums[i - 1];
从前缀和数组判断
for (int i = 1; i < nums.length; i++) {
if (nums[i] % k == 0) return true;
//不固定长度的滑动窗口
for (int j = 0; j < i - 1; j++) {
if ((nums[i] - nums[j]) % k == 0) return true;
}
}
for(int i=k-1;i<arr.length;i++){
//固定长度的滑动窗口
max = Math.max(max,i==k-1 ? arr[i] : arr[i]-arr[i-k]);
}
// 前缀和
public int maxSubArray(int[] nums) {
int res = nums[0];
for(int i=1;i<nums.length;i++){
if(nums[i-1]>0) nums[i]+=nums[i-1]; //大于 0 才加
res = Math.max(res,nums[i]);
}
return res;
}
public boolean checkSubarraySum1(int[] nums, int k) {
if (nums.length < 2) return false;
for (int i = 1; i < nums.length; i++) nums[i] += nums[i - 1];
for (int i = 1; i < nums.length; i++) {
if (nums[i] % k == 0) return true;
for (int j = 0; j < i - 1; j++) {
if ((nums[i] - nums[j]) % k == 0) return true;
}
}
return false;
}
public boolean checkSubarraySum(int[] nums, int k) {
if (nums.length < 2) return false;
for (int i = 1; i < nums.length; i++) nums[i] += nums[i - 1];
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int n = nums[i] % k;
if (n == 0 && i > 0) {
return true;
}
Integer index = map.get(n);
if (index == null) {
map.put(n, i);
} else if (i - index >= 2) { // 保证子数组长度大于2
return true;
}
}
return false;
}
//https://leetcode.cn/problems/continuous-subarray-sum/solution/de-liao-wo-ba-qian-zhui-he-miao-de-gan-g-c8kp/
//前缀和,注意前缀和从 1 开始填充,否则会落下 nums[0],这里将前缀和为0也统计进来
//前缀和 + 滑动窗口 可以用 hashMap 实现
//看这里理解 https://leetcode.cn/problems/QTMn0o/solution/qian-zhui-he-hash-table-he-wei-k-de-zi-s-knqq/
public int subarraySum2(int[] nums, int k) {
int len = nums.length;
if(len == 1){
if(k==nums[0]) return 1;
else return 0;
}
Map<Integer,Integer> map = new HashMap<>();
//细节,这里需要预存前缀和为 0 的情况,否则会漏掉前几位就满足的情况(当子数组起始位置为0时将无法被匹配到)
//例如输入[1,1,0],k = 2 如果没有这行代码,则会返回0,漏掉了1+1=2,和1+1+0=2的情况
//输入:[3,1,1,0] k = 2 时则不会漏掉
//保证presum[3] - presum[0]表示前面 3 位的和,所以需要map.put(0,1),垫下底
map.put(0,1);
int sum=0,count=0;
//每次寻找子数组时应该在hash表中寻找的键值是sum-k,因为直接寻找k只可以找到那些起始位置为0的子数组,而寻找sum-k因为我们事先插入了一个0的键值,因此这里也不会忽略掉这种情况
for(int i=0;i<nums.length;i++){
sum+=nums[i];
count+= map.getOrDefault(sum-k,0); //当前前缀和已知,判断是否含有 presum - k的前缀和,那么我们就知道某一区间的和为 k 了。
map.put(sum,map.getOrDefault(sum,0)+1);
}
return count;
}
【上面map中存储的key为前缀和,val为前缀和出现的次数】
public int subarraySum(int[] nums, int k) {
if (nums == null || nums.length == 0) return -1;
int res = 0;
for (int i = 1; i < nums.length; i++) nums[i] = nums[i] + nums[i - 1];
for (int i = 0; i < nums.length; i++) {
if (nums[i] == k) res++;
for (int j = 0; j < i; j++) {
if (nums[i] - nums[j] == k) res++;
}
}
return res;
}
public static double findMaxAverage(int[] arr, int k) {
for (int i = 1; i < arr.length; i++) arr[i] += arr[i - 1];
int max = Integer.MIN_VALUE;
for(int i=k-1;i<arr.length;i++){
max = Math.max(max,i==k-1 ? arr[i] : arr[i]-arr[i-k]);
}
return 1.0 * max / k;
}
滑动窗口
public double findMaxAverage(int[] nums, int k) {
int len = nums.length;
int sum = 0;
for (int i = 0; i < k; i++) sum += nums[i];
int maxSum=sum;
for (int i = k; i < len; i++) {
sum = sum - nums[i-k] + nums[i];
maxSum = Math.max(sum,maxSum);
}
return 1.0*maxSum/k;
}
public int numSubarraysWithSum1(int[] nums, int goal) {
if(nums.length==1){
if(goal==nums[0]) return 1;
else return 0;
}
Map<Integer,Integer> map = new HashMap<>();
map.put(0,1);
int sum=0,res=0;
for(int num:nums){
sum+=num;
res+=map.getOrDefault(sum-goal,0);
map.put(sum,map.getOrDefault(sum,0)+1);
}
return res;
}
// 前缀和类 汇总 https://leetcode.cn/problems/subarray-sum-equals-k/solution/by-da-yu-bt-ge0y/
public int subarraysDivByK(int[] nums, int k) {
int sum=0,res=0;
HashMap<Integer,Integer> map = new HashMap<>();
int key=0;
map.put(0,1);
for(int num:nums){
sum+=num;
key = (sum%k+k)%k;
res+=map.getOrDefault(key,0);
map.put(key,map.getOrDefault(key,0)+1);
}
return res;
}
//遇到奇数项就加一,遇到偶数项就加零,将问题转换为求解和为K的子数组个数
//https://leetcode.cn/problems/subarray-sum-equals-k/solution/by-da-yu-bt-ge0y/
public int numberOfSubarrays1(int[] nums, int k) {
int count=0,res= 0;
HashMap<Integer,Integer> map = new HashMap<>();
map.put(0,1);
for(int num:nums){
count+= num%2==1 ? 1 : 0;
res+=map.getOrDefault(count-k,0);
map.put(count,map.getOrDefault(count,0)+1);
}
return res;
}
Tree的最大深度
public int maxDepth(TreeNode root) {
if(root==null) return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;
}
public int maxDepth1(TreeNode root) {
if(root==null) return 0;
Deque<TreeNode> que = new LinkedList<>();
int depth = 0;
que.add(root);
while(!que.isEmpty()){
depth++;
int count = que.size();
for(int i=0;i<count;i++){
TreeNode node = que.pop();
if(node.left!=null) que.add(node.left);
if(node.right!=null) que.add(node.right);
}
}
return depth;
}
// 如果当前节点是最深叶子节点的最近公共祖先,那么它的左右子树的高度一定是相等的,否则高度低的那个子树的叶子节点深度一定比另一个子树的叶子节点的深度小,因此不满足条件。所以只需要dfs遍历找到左右子树高度相等的根节点即出答案
// https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves/solution/liang-chong-si-lu-yi-chong-qian-xu-bian-li-yi-chon/
public TreeNode lcaDeepestLeaves(TreeNode root) {
if(root ==null) return root;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
if(left == right) return root;
else if(left < right) return lcaDeepestLeaves(root.right);
else return lcaDeepestLeaves(root.left);
}
private int maxDepth(TreeNode root){
if(root==null) return 0;
return 1 + Math.max(maxDepth(root.left),maxDepth(root.right));
}
整数反转
翻转,回文数,涉及到前面负号的,都是 while(x!=0)
判断回文数
https://leetcode.cn/problems/palindrome-number/
class Solution {
public boolean isPalindrome(int x) {
int y = x;
if(x == 0) return true;
if(x < 0 || x % 10 == 0) return false;
int res = 0;
while(x != 0){
res = res*10 + x%10;
x /= 10;
}
// System.out.println(x);
// System.out.println(res);
return y == res;
}
}
while(x != 0){ x
res = res*10 + x%10; 反转后的 x x%10
x /= 10; x/10
}
各位数字之和
num 各位数字之和
private int sums(int x){
int s = 0;
while(x != 0) { 第一行 x
s = s + x % 10; 第二行 x%10
x = x / 10; 第三行 x/10
}
return s;
}
// https://leetcode.cn/problems/count-integers-with-even-digit-sum/solution/2180yi-ci-bian-li-zuo-fa-by-wan-jia-guo-co9ir/
public int countEven(int num) {
if(num==1) return 0;
int res = 0;
for(int i=2;i <= num;i++){
if(check(i)) res++;
}
return res;
}
private boolean check(int num){
int res = 0;
while(num!=0){
res += num%10;
num/=10;
}
return res%2==0 ? true : false;
}
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内
考虑用
int[] temp = new int[nums.length]; //长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内
for(int num : nums) temp[num]++;
如果都是字符
如果传入的arr[] 都是字符,则
int[] count = new int[26];