【算法】递归&Dfs学习笔记(包含力扣113&93+若干小题)
递归&DFS
-
递归
- 递归问题的关键问题是找到“相似性”——>下一次递归的调用形式;和“出口”——>终止递归的语句。
- 在构造相似性时,如果没有明显相似性需要主动构造;不能相似的原因很可能是缺少参数。
-
递归调用仅仅是被调函数恰为主调函数
注意每次调用的层次不同
注意每次分配形参并非同一个变量
注意返回次序 -
三个简单的递归例子:
-
//打印0-n public static void f(int n){ if(n==-1) return; //出口 f(n-1); //相似性 System.out.println(n); }
-
//累加数组中元素 //begin-a.length public static int f1(int[]a,int begin){ if(begin==a.length) return 0; int x=f1(a,begin+1); return x+a[begin]; }
-
//比较字符串是否相等 public static boolean f3(String a,String b){ if(a.length()!=b.length())return false; if(a.length()==0) return true; if(a.charAt(0)!=b.charAt(0)) return false; else return f3(a.substring(1),b.substring(1)); }
-
-
进阶一点点的递归问题*3
- 在n个球中任意取出m个(不放回),求有多少种不同取法?//特殊球法
-
//平地起风雷 public static int f2(int n,int m){ if(n
return 0; if(n==m) return 1; if(m==0) return 1; return f2(n-1,m-1)+f2(n-1,m);//使用一个特殊球x将取法做人为划分 //取球包含x //取球不包含x }
-
- 求n个元素的全排列
-
public static void f(char[]a,int k){//传入参数用K做区别 if(k==a.length){ for(int i=0;i
){ System.out.print(a[i]+" "); } System.out.println(); } for(int i=k;i //循环本身就是递归出口 char temp=a[i];a[i]=a[k];a[k]=temp;//试探 f(a,k+1); temp=a[i];a[i]=a[k];a[k]=temp;//回溯 } }
-
- 求最长公共子序列
-
//字串----连着的 //子序列----删除任意的 public static int f3(String s1,String s2){ if(s1.length()==0||s2.length()==0){ return 0; } if(s1.charAt(0)==s2.charAt(0)) return f3(s1.substring(1),s2.substring(1))+1; else return Math.max(f3(s1.substring(1),s2),f3(s1,s2.substring(1))); }
-
- 在n个球中任意取出m个(不放回),求有多少种不同取法?//特殊球法
-
蓝桥杯的一些题目(2014年以前
- 求反串(填空)
-
public static String reverseString(String x) { if(x==null || x.length()<2) return x; return reverseString(x.substring(1)) + x.charAt(0); }
-
- 杨辉三角形 (填空)(形如
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1- 计算第m层的第n个系数
-
public static int f(int m,int n){ if(n==0 || n==m) return 1; return f(m-1,n)+f(m-1,n-1); }
- 计算m个A,n个B可以组合成多少个不同的排列(填空)
-
public static int f(int m,int n){ if(n==0 || m==0) return 1; return f(m-1,n)+f(m,n-1); }
-
-
整数的划分问题(编程题)
如,对于正整数n=6,可以划分为:
6
5+1
4+2,4+1+1
3+3,3+2+1,3+1+1+1
2+2+2,2+2+1+1,2+1+1+1+1
1+1+1+1+1+1
现在的问题是,对于给定的正整数n,编写算法打印所有划分
-
public static void f(int n,int[]a,int k){ //a为缓冲 k为当前位置 if(n==0){ for(int i=0;i
){ System.out.print(a[i]+" "); } System.out.println(); return; } for(int i=n;i>0;i--){ //i从n开始,方便写n==0的出口 if(k>0 &&i>a[k-1]) continue;//视频里说return太过分了 a[k]=i; f(n-i,a,k+1); } }
-
-
某财务部门结账时发现总金额不对头。很可能是从明细上漏掉了某1笔或几笔。如果已知明细账目清单,能通过编程找到漏掉的是哪1笔或几笔吗?
如果有多种可能,则输出所有可能的情况。
我们规定:用户输入的第一行是:有错的总金额。
接下来是一个整数n,表示下面将要输入的明细账目的条数。再接下来是n行整数,分别表示每笔账目的金额。
要求程序输出:所有可能漏掉的金额组合。每个情况1行。金额按照从小到大排列,中间用空格分开。
比如:
用户输入:
6
5
3
2
4
3
1
表明:有错的总金额是6;明细共有5笔。
此时、程序应该输出:
1 3 3
1 2 4
3 4
为了方便,不妨假设所有的金额都是整数,每笔金额不超过1000,金额的明细条数不超过100。-
/* 参数说明: err_sum: 题目输入的第一行,即错误的结果 a: 题目输入的第3-n行,即数据集 k: 当前遍历到哪个位置 cur_sum: k位置之前的所有取到元素的累加和 b: 记录元素取还是不取 Boolean[] b = new Boolean[a.length];new出来即可 */ //其实核心思想就是数据集中的数据取还是不取的问题。 public static void f(int err_sum,int[]a,int k,int cur_sum,Boolean[]b){ if(cur_sum>err_sum) return; if(cur_sum==err_sum){ for(int i =0;i
){ if(b[i]==false) System.out.print(a[i]+" "); } System.out.println(); return; } if(k>=a.length) return;//注意出口顺序,此出口如果写在前面是有逻辑错误的 b[k] = false; f(err_sum,a,k+1,cur_sum,b);//不取当前位置 递归 b[k]=true; f(err_sum,a,k+1,cur_sum+a[k],b);//取当前位置 递归 b[k]=false;//回溯 }
-
- 今天(2020.1.7)刚写到的一道力扣
-
98.验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。有效 二叉搜索树定义如下:
1.节点的左子树只包含 小于 当前节点的数。
2.节点的右子树只包含 大于 当前节点的数。
3.所有左子树和右子树自身必须也是二叉搜索树。 -
1 /** 2 * Definition for a binary tree node. 3 * public class TreeNode { 4 * int val; 5 * TreeNode left; 6 * TreeNode right; 7 * TreeNode() {} 8 * TreeNode(int val) { this.val = val; } 9 * TreeNode(int val, TreeNode left, TreeNode right) { 10 * this.val = val; 11 * this.left = left; 12 * this.right = right; 13 * } 14 * } 15 */ 16 class Solution { 17 public boolean isValidBST(TreeNode root,long cur_max,long cur_min){ 18 if(root==null) return true;//一开始写成了判断是否为子节点,然后就出现了是子节点不管三七二十一都true的错误 19 //但其实就这样一直遍历下去就可以啦 20 if(root.val>=cur_max||root.val<=cur_min) return false;//最开始没有想到实际上是有闭区间的 想成了开闭或者闭开区间了 21 return isValidBST(root.left,root.val,cur_min)&&isValidBST(root.right,cur_max,root.val);//我发现我逻辑上真的很差啊 一开始刚好写反 哭哭QAQ 22 } 23 public boolean isValidBST(TreeNode root) { 24 return isValidBST(root,Long.MAX_VALUE,Long.MIN_VALUE); 25 } 26 }
-
- 求反串(填空)
-
小总结
- 递归和循环是可以互通的,递归往往用于循环层数不确定时使用。也就是说如果对于一道题如果感觉能用循环那基本写递归就可以了
- 递归无外乎是寻找相似性和出口的问题,相似性即改变传入参数使得递归向下进行;寻找出口须想好条件以及次序
- 回溯很重要!
-
DFS
-
写在前面
-
深度搜索本质上就是基于递归的思想 所以我放在一起写了
这部分没有听网课 是我照着书写的
下面会写一些书上的例子和我做力扣遇到的例子
-
-
引例
- emm因为是照着算法书写的再加上java和c++都想练到所以可能会C++和java代码串着出现2333
-
#include
using namespace std; //hdu1312 "Red and Black" /* 题目: 只能走黑色 解法: DFS 输入: 第一行W H表示x方向和y方向上的瓷砖数量 下面H行 每行W个字符 “·”表示黑色 “# ”表示红色 “@ ” 表示起始位置 输出: 一个数字 表示从初始瓷砖能到达的瓷砖总数 */ char room[23][23]; int dir[4][2]={ {-1,0}, //left 左上角为{0,0} {0,-1}, //up {1,0}, //right {0,1} //down }; int Wx, Hy, num; #define isInRoom(x,y) (x =0 && y>=0 && y struct node{ int x,y; }; void DFS(int dx,int dy){ node start,next; start.x=dx; start.y=dy; for(int i=0;i<4;i++){ next.x=start.x+dir[i][0]; next.y=start.y+dir[i][1]; if(isInRoom(next.x,next.y)&&room[next.x][next.y]=='.'){ room[next.x][next.y]='#'; num++; DFS(next.x,next.y); } } } int main(){ int x,y,dx,dy; while(cin>>Wx>>Hy){ if(Wx==0&&Hy==0) break; for(y=0;y ){ for(x=0;x ){ cin>>room[x][y]; if(room[x][y]=='@'){ dx=x; dy=y; } } } num=1; DFS(dx,dy); cout< endl; } return 0; } - 测试用例:
-
Sample Input 6 9 ....#. .....# ...... ...... ...... ...... ...... #@...# .#..#. 11 9 .#......... .#.#######. .#.#.....#. .#.#.###.#. .#.#..@#.#. .#.#####.#. .#.......#. .#########. ........... 11 6 ..#..#..#.. ..#..#..#.. ..#..#..### ..#..#..#@. ..#..#..#.. ..#..#..#.. 7 7 ..#.#.. ..#.#.. ###.### ...@... ###.### ..#.#.. ..#.#.. 0 0 Sample Output 45 59 6 13
Red and Black测试用例
-
- 回溯和剪枝之后再补充吧
-
力扣上的一些例子
-
113.路径总和Ⅱ
题目描述:
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
-
题解:
/** * 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 { List
113.路径总和Ⅱ- > ans = new ArrayList
- >() ;
List
now = new ArrayList (); public void dfs(TreeNode root,int targetSum,int num){ if(root==null) return; num+=root.val; now.add(root.val); if(root.right==null&&root.left==null){ if(num==targetSum){ ans.add(new ArrayList(now)); } //temp-=root.val; now.remove(now.size()-1); return; } if(root.left!=null) dfs(root.left,targetSum,num); if(root.right!=null) dfs(root.right,targetSum,num); now.remove(now.size()-1); } public List - > pathSum(TreeNode root, int targetSum) {
dfs(root,targetSum,0);
return ans;
}
}
- 代码中的一些tips:
- ans.add(new ArrayList(now)); 如果不用new的话会把之前的结果直接覆盖掉
- 递归问题比较难的在于参数设计 这题比较简单啦
-
-
93.复原IP地址
题目描述:
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。
例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。
给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你不能重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。- (对不起这是图片)
-
题解:
class Solution { static final int count = 4; List
93.复原IP地址ans = new ArrayList (); int[] segment = new int[count]; public List restoreIpAddresses(String s) { segment = new int[count]; dfs(s,0,0); return ans; } //segID:第几个点(0-3) //segStart:从数组的第几个位置开始出发 public void dfs(String s,int segID,int segStart){ //都划分好 if(segID==count){ if(segStart==s.length()){ StringBuffer temps = new StringBuffer(); for(int i=0;i i){ temps.append(segment[i]); if(i!=count-1) temps.append('.'); } ans.add(temps.toString()); } return; } if(segStart==s.length()){ return; } if(s.charAt(segStart)=='0'){ segment[segID]=0; dfs(s,segID+1,segStart+1); } int temp=0; for(int segEnd = segStart;segEnd segEnd){ temp=temp*10+(s.charAt(segEnd)-'0'); if(temp>0&&temp<=0xFF){ segment[segID] = temp; dfs(s,segID+1,segEnd+1); } else break; } } } - 代码中的一些tips:
- 最后一个循环里的dfs(s,segID+1,segEnd+1); 一开始传成了segStart+1答案莫名其妙的2333
- 要注意对于0的特殊判断 题干有说不能含有前导0
-
-
未完也许会续也许会新写一片记一下回溯
-