回溯算法
概念
实际上一个类似枚举的搜索尝试过程。
满足回溯条件的某个状态的点称为“回溯点”。
搜索过程
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根节点出发深度搜索解空间树。
当搜索到某一节点时,要先判断该节点是否包含问题的解。
如果包含,就从该节点出发继续探索下去;如果不包含,则逐层向其祖先元素节点回溯。
解题步骤
定义一个解空间,包含所有的解;
利用适于搜索的方法组织解空间;
利用深度优先法搜索解空间;
利用限界函数避免移动到不可能产生解的子控件。
广度优先搜索(BFS Breadth-First Search)
基本过程:从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。
通常借助队列的先进先出特性来实现。
具体操作:
把起始节点放入queue
重复下述两步,直至queue为空为止;
从queue中取出队列头的节点;
找出与此点临近的且未遍历的点,进行标记,然后全部放入queue中。
做题步骤
根判断是否为空,为空返回;
队列根元素先进;
循环取队头;
看值左右,如果存在就进队列;
目标和(leetcode494)
需求
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例
输入:nums = [1,1,1,1,1], target = 3 输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
输入:nums = [1], target = 1 输出:1
回溯
class Solution {
// 定义变量count 用于统计表达式等于target的数量
int count = 0 ;
public int findTargetSumWays(int[] nums, int target) {
backrack(nums,target,0,0);
return count;
}
public void backrack(int[] nums,int target,int index,int sum){
//回溯点
if(index == nums.length){
if(sum == target){
count++;
}
}else{
backrack(nums,target,index+1,sum+nums[index]);
backrack(nums,target,index+1,sum-nums[index]);
}
}
}
动态规划
2022/7/4写这个明天一早就写。(计划)
2022/7/6 下午写的。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
// 题目分析,计算数组的和 以及定义要添加负号的元素的和
// neg = 添加负号元素的和 sum-neg 表示添加正号元素的和 target = sum-2*neg
int len = nums.length;
int sum = 0;
for(int num : nums){
sum += num;
}
int neg = (sum-target) / 2 ;
// 特殊值
if(neg < 0 || (sum - target) %2 != 0)
return 0;
// 定义状态
int[][] dp = new int[len+1][neg+1];
// 表示前i个元素中,选取元素,使结果等于j的个数
// 特殊值
// 如果没有元素 则dp[0][j] = 0;其中dp[0][0]的结果是1 没有选元素,结果0 这个是满足的。
dp[0][0] = 1;
for(int j = 1; j < neg+1 ; j++){
dp[0][j] = 0;
}
// 没有选择选择
// 用于保存当前i对应的nums中的值
int num = 0;
// 转移方程
for(int i = 1 ; i < len+1;i++){
num = nums[i-1];
for(int j = 0 ; j < neg+1 ; j++){
if(j < num){
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = dp[i-1][j] + dp[i-1][j-num];
}
}
}
return dp[len][neg];
}
}
offer32 从上到下打印二叉树I
需求
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
示例
给定二叉树: [3,9,20,null,null,15,7] 输出 [3,9,20,15,7]
方法:广度优先搜索
算法流程
public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
1 特例处理
当树的根节点为空,则直接返回空列表
2 初始化
打印结果列表res=[] 包含根节点的队列 queue = [root]
3 BFS循环 当队列queue为空时跳出
出队:队首元素出队,记为node
打印:将node.val添加到列表res的尾部
添加子节点:若node的(左右)子节点不为空,则将(左右子节点加入到队列queue)。
4 返回res列表
代码
/**
* 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) {
// BFS
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.add(root);
List<Integer> list = new ArrayList<Integer>();
if(root == null)
return new int[0];
TreeNode node = new TreeNode(root.val);
while(!que.isEmpty()){
node = que.poll();
list.add(node.val);
if(node.left != null) que.add(node.left);
if(node.right != null) que.add(node.right);
}
int[] arr = new int[list.size()];
for(int i=0 ; i < list.size() ; i++)
arr[i] = list.get(i);
return arr;
}
}
offer32 从上到下打印二叉树II
需求
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
示例
方法:广度优先搜索
incompatible types: ArrayList<ArrayList< Integer>> cannot be converted to List<List< Integer>>
代码错误分析
操作:
List<List<Integer>> = new ArrayList<ArrayList<Integer>>();
报错:
incompatible types: ArrayList<ArrayList<Integer>> cannot be converted to List<List<Integer>>
解决:
List<List<Integer>> = new ArrayList<List<Integer>>();
将第二个ArrayList改为List就可以了。
原因:
这是一个泛型应用的常踩坑。
代码
/**
* 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) {
// BFS 广度优先搜索
List<List<Integer>> list = new ArrayList<List<Integer>>();
// if(list1.isEmpty())
// return new List<List<Integer>>();
if(root == null)
return new ArrayList<List<Integer>>();
TreeNode node;
Queue<TreeNode> que = new LinkedList<TreeNode>();
if(root != null) que.add(root);
while(!que.isEmpty()){
List<Integer> list1 = new ArrayList<Integer>();
for(int i = que.size() ; i > 0 ; i--){
// 这里必须采用倒叙的形式,因为que队列的长度会改变。
node = que.poll();
list1.add(node.val);
if(node.left != null) que.add(node.left);
if(node.right != null) que.add(node.right);
}
list.add(list1);
}
return list;
}
}
重点
使用队列.sise() 倒叙遍历队列,因为正序的话,由于修改了que的元素个数,que.size()会发生变化,致使结果出现错误;
offer32 从上到下打印二叉树III
需求
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
示例
方法1:层序遍历 + 双端队列
分析
需要注意的是存储TreeNode节点的队列是queue普通的队列,而List< List< Integer>> 集合的元素是 双端队列 LinkedList
代码
/**
* 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) {
// 层级遍历 + 双端队列
List<List<Integer>> list = new ArrayList<List<Integer>>();
Queue<TreeNode> que = new LinkedList<TreeNode>();
if(root == null) return new ArrayList<List<Integer>>();
else que.add(root);
TreeNode node;
while(! que.isEmpty()){
LinkedList<Integer> temp = new LinkedList<Integer>();
for(int i = que.size() ; i > 0 ; i--){
// if(list.size() %2 == 0) node = que.pollFirst();
// else node = que.pollLast();
// 不可以通过奇偶数 抽取元素队首还是队尾。因为que会一直添加元素的。
// temp.add(node.val);
node = que.poll();
if(list.size() % 2 != 0) temp.addFirst(node.val);
else temp.addLast(node.val);
if(node.left != null) que.add(node.left);
if(node.right != null) que.add(node.right);
}
list.add(temp);
}
return list;
}
}
时间复杂度O(N) : N为二叉树的节点数量,BFS需要循环N次,也就是for循环那里
空间复杂度O(N) : 最差的情况是满二叉树,最多有N/2个节点同时在LinkedList队列中
方法2:层序遍历 + 双端队列(奇偶层逻辑分离)
分析
代码
/**
* 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) {
// 层次遍历 + 双端队列(奇偶层分离)
Deque<TreeNode> que = new LinkedList<TreeNode>();
List<List<Integer>> list = new ArrayList<List<Integer>>();
if(root == null) return new ArrayList<List<Integer>>();
else que.addLast(root);
TreeNode node;
while(! que.isEmpty()){
// 奇数层
List<Integer> temp = new ArrayList<Integer>();
for(int i = que.size() ; i > 0 ; i--){
node = que.removeFirst();
temp.add(node.val);
if(node.left != null) que.addLast(node.left);
if(node.right != null) que.addLast(node.right);
}
list.add(temp);
if(que.isEmpty()) break;
temp = new ArrayList<Integer>();
for(int i = que.size(); i > 0 ; i--){
node = que.removeLast();
temp.add(node.val);
if(node.right != null) que.addFirst(node.right);
if(node.left != null) que.addFirst(node.left);
}
list.add(temp);
}
return list;
}
}
时间复杂度O(N) : N为二叉树的节点数量,BFS需要循环N次,也就是for循环那里
空间复杂度O(N) : 最差的情况是满二叉树,最多有N/2个节点同时在LinkedList队列中
方法3:层序遍历 + 倒序
分析
通过层级判断,然后使用Collections.reverse完成翻转。
/**
* 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) {
// 层序遍历 + 倒序Collections.reverse();
List<List<Integer>> list = new ArrayList<List<Integer>>();
Queue<TreeNode> que = new LinkedList<TreeNode>();
if(root == null) return new ArrayList<List<Integer>>();
else que.add(root);
TreeNode node;
while(! que.isEmpty()){
List<Integer> temp = new ArrayList<Integer>();
for(int i = que.size() ; i > 0 ; i--){
node = que.poll();
temp.add(node.val);
if(node.left != null) que.add(node.left);
if(node.right != null) que.add(node.right);
}
if(list.size() %2 != 0) Collections.reverse(temp);
list.add(temp);
}
return list;
}
}
offer26 数的子结构
需求
方法
/**
* 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;
return (recur(A,B) || isSubStructure(A.left,B) || isSubStructure(A.right,B));
}
public boolean recur(TreeNode A,TreeNode B){
if(B == null)
return true;
if(A == null)
return false;
if(A.val != B.val)
return false;
return recur(A.left,B.left) && recur(A.right,B.right);
}
}
分析