给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
图中的每个节点都包含它的值 val
(int
) 和其邻居的列表(list[Node]
)。
class Node {
public int val;
public List<Node> neighbors;
}
深拷贝就是新建一个对象,和源对象的任何属性都相同,只是在内存中的地址不同,然后返回这个对象;浅拷贝就是用一个新指针指向源对象
深拷贝也就是克隆,我们要新建对象,赋值,同时还要处理邻接表,保证每个邻居也都是克隆的,同时为了防止死循环,还要用visited来储存已经处理好的节点,注意如果只考虑防止死循环用集合即可,但是为了考虑返回值,我们要使用哈希表,如果已处理过,我们返回目标节点即可
1.DFS,递归处理
/*
* @lc app=leetcode.cn id=133 lang=java
*
* [133] 克隆图
*/
// @lc code=start
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> neighbors;
public Node() {
val = 0;
neighbors = new ArrayList<Node>();
}
public Node(int _val) {
val = _val;
neighbors = new ArrayList<Node>();
}
public Node(int _val, ArrayList<Node> _neighbors) {
val = _val;
neighbors = _neighbors;
}
}
*/
class Solution {
public Node cloneGraph(Node node) {
HashMap<Integer,Node>visited=new HashMap<>();
return Clone(node, visited);
}
Node Clone(Node node,HashMap<Integer,Node>visited){
//剪枝一:空
if(node==null)return null;
int val=node.val;
//剪枝二:已处理过,直接返回
if(visited.containsKey(val))
return visited.get(val);
//新建节点,赋值
Node res=new Node(val);
//将节点加入哈希表中
visited.put(res.val,res);
//处理节点的邻接表,邻居也必须要是克隆节点
for(Node neighbor:node.neighbors){
Node n=Clone(neighbor, visited);
res.neighbors.add(n);
}
return res;
}
}
2.BFS
//和DFS的区别在于
//DFS时我们可以先处理每个结点的邻居再处理本节点的邻接表,不需要显示储存原节点
//而BFS时我们需要记录本结点的邻接表再逐个处理,因此不如保留源节点来处理邻接表
//处理完邻接表再返回新节点
代码
/*
* @lc app=leetcode.cn id=133 lang=java
*
* [133] 克隆图
*/
// @lc code=start
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> neighbors;
public Node() {
val = 0;
neighbors = new ArrayList<Node>();
}
public Node(int _val) {
val = _val;
neighbors = new ArrayList<Node>();
}
public Node(int _val, ArrayList<Node> _neighbors) {
val = _val;
neighbors = _neighbors;
}
}
*/
class Solution {
public Node cloneGraph(Node node) {
HashMap<Integer,Node>visited=new HashMap<>();
//剪枝一:空
if(node==null)return null;
Deque<Node>queue=new LinkedList<>();
Node res=new Node(node.val);
//和DFS的区别在于
//DFS时我们可以先处理每个结点的邻居再处理本节点的邻接表
//而BFS时我们需要记录本结点的邻接表再逐个处理,因此不如保留源节点来处理邻接表啊
//处理完邻接表再返回新节点
visited.put(res.val, res);
//注意传入队列的是源节点
queue.offer(node);
while(!queue.isEmpty()){
//从队列中取出的是原图的节点p
Node p=queue.poll();
//加入队列的节点比如已建立映射关系,找到其在新图中对应的节点cur
Node cur=visited.get(p.val);
//处理邻接表
for(Node neigh:p.neighbors){
if(visited.containsKey(neigh.val)){
//邻居已处理,直接赋值给新节点
cur.neighbors.add(visited.get(neigh.val));
}else{
//邻居未处理,要将邻居加入队列
//新建一个节点并赋值给新节点的邻接表
//同时记录本节点,加入visited
queue.offer(neigh);
Node temp=new Node(neigh.val);
cur.neighbors.add(temp);
visited.put(temp.val, temp);
}
}
}
return res;
}
}
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
方法一:经典回溯
这是一颗二叉决策树,虽然有剪枝,但是复杂度仍然是2^n级别
class Solution {
int way=0;
public int findTargetSumWays(int[] nums, int target) {
dfs(nums, target, 0);
return way;
}
void dfs(int[]nums,int target,int start){
if(start>=nums.length){
if(start==nums.length&&target==0)way++;
return;
}
int num=nums[start];
dfs(nums, target-num, start+1);
dfs(nums, target+num, start+1);
}
}
用时感人,这合理吗,再想想还有没有更好的方法,遂开始DP
方法二:动态规划
动态规划要找到状态转移方程式,本题有个极其重要的数学推论,如果我们正常思路做,是没有办法找出关系式的,正负分开的数字枚举起来可以是可以但是很复杂,但是枚举原数据很简单,所以我们在这个公式的基础上,目标就是找到,从前面n个数字中找出方法,使得和为neg,然后返回方法数
class Solution {
public int findTargetSumWays(int[] nums, int target) {
//neg=(sum-target)/2
int sum=0;
for(int num:nums){
sum+=num;
}
int sub=sum-target;
//检验是否为非负偶数
if(sub<0||sub%2!=0)
return 0;
int n=nums.length,neg=sub/2;
//dp[i][j]
//从前i个数中选取一些数字,使他们的和为j的方法数
int[][]dp=new int[n+1][neg+1];
//00初始化
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=neg;j++){
//前i个数,从零开始,所以要-1
int num=nums[i-1];
//当前数不能取,和前面没区别
dp[i][j]=dp[i-1][j];
//若当前数字可以取,在原先方法数的基础上,会多出一些方法
//选择了当前数字,可以在前面i个数找出新的sum,所求方法数,所以相加
if(j>=num){
dp[i][j]+=dp[i-1][j-num];
}
}
}
//
return dp[n][neg];
}
}
滚动数组优化空间复杂度
class Solution {
public int findTargetSumWays(int[] nums, int target) {
//neg=(sum-target)/2
int sum=0;
for(int num:nums){
sum+=num;
}
int sub=sum-target;
//检验是否为非负偶数
if(sub<0||sub%2!=0)
return 0;
int n=nums.length,neg=sub/2;
//dp[i][j]
//从前i个数中选取一些数字,使他们的和为j的方法数
int[][]dp=new int[2][neg+1];
//00初始化
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=neg;j++){
//前i个数,从零开始,所以要-1
int num=nums[i-1];
//当前数不能取,和前面没区别
dp[i%2][j]=dp[(i+1)%2][j];
//若当前数字可以取,在原先方法数的基础上,会多出一些方法
//选择了当前数字,可以在前面i个数找出新的sum,所求方法数,所以相加
if(j>=num){
dp[i%2][j]+=dp[(i+1)%2][j-num];
}
}
}
//
return dp[n%2][neg];
}
}