回溯,滑动窗口,并查集
递归
树的前序遍历,中序遍历,后序遍历:
//前序
void preOrder(root){
sys...
preOrder(root.left);
preOrder(root.right);
}
//中序
void inOrder(root){
inOrder(root.left);
sys...
inOrder(root.right);
}
//后序
void postOrder(root){
postOrder(root.left);
postOrder(root.right);
sys...
}
回溯
回想dfs:
public void dfs(root){
for(child:root){
...
dfs(child);
...
}
}
可以知道dfs与回溯是类似的:
- 回溯的重点在于要做出选择和撤销选择
leedcode 46 全排列
class Solution {
List<List<Integer>> res =new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
//路径,(已经搜索过)
LinkedList <Integer> track=new LinkedList<>();
backtrack(nums,track);
return res;
}
void backtrack(int [] nums ,LinkedList <Integer> track){
if(track.size()==nums.length){
res.add(new LinkedList(track));
return;
}
for(int i=0;i<nums.length;i++){
//或者用一个vis数组记录
if(track.contains(nums[i]))
continue;
track.add(nums[i]);
backtrack(nums,track);
track.removeLast();
}
}
}
//改用vis比if(track.contains(nums[i]))更快
class Solution {
List<List<Integer>> res =new LinkedList<>();
int []vis;
public List<List<Integer>> permute(int[] nums) {
if (nums == null || nums.length == 0) {
res.add(new ArrayList<Integer>());
return res;
}
int n=nums.length;
vis=new int[n];
for(int i=0;i<n;i++)vis[i]=0;
//路径,(已经搜索过)
LinkedList <Integer> track=new LinkedList<>();
backtrack(nums,track);
return res;
}
void backtrack(int [] nums ,LinkedList <Integer> track){
if(track.size()==nums.length){
res.add(new LinkedList(track));
return;
}
for(int i=0;i<nums.length;i++){
if( vis[i]==1) continue;
vis[i]=1;
track.add(nums[i]);
backtrack(nums,track);
vis[i]=0;
track.removeLast();
}
}
}
leetcode全排列2 (有重复数子)
class Solution {
List<List<Integer>> res =new LinkedList<>();
int []vis;
public List<List<Integer>> permuteUnique(int[] nums) {
if (nums == null || nums.length == 0) {
res.add(new ArrayList<Integer>());
return res;
}
Arrays.sort(nums);
int n=nums.length;
vis=new int[n];
for(int i=0;i<n;i++)vis[i]=0;
//路径,(已经搜索过)
LinkedList <Integer> track=new LinkedList<>();
backtrack(nums,track);
return res;
}
void backtrack(int [] nums ,LinkedList <Integer> track){
if(track.size()==nums.length){
res.add(new LinkedList(track));
return;
}
for(int i=0;i<nums.length;i++){
//同一层,而且之前那个没有用过
if( vis[i]==1||(i>0&&nums[i]==nums[i-1]&&(vis[i-1]==0))) continue;
vis[i]=1;
track.add(nums[i]);
backtrack(nums,track);
vis[i]=0;
track.removeLast();
}
}
}
//先要排序
leetcode 组合总和39:
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer> combine = new ArrayList<Integer>();
dfs(candidates, target, ans, combine, 0);
return ans;
}
public void dfs(int[] candidates, int target, List<List<Integer>> ans, List<Integer> combine, int idx) {
if (idx == candidates.length) {
return;
}
if (target == 0) {
ans.add(new ArrayList<Integer>(combine));
return;
}
// 直接跳过
dfs(candidates, target, ans, combine, idx + 1);
// 选择当前数
if (target - candidates[idx] >= 0) {
combine.add(candidates[idx]);
dfs(candidates, target - candidates[idx], ans, combine, idx);
combine.remove(combine.size() - 1);
}
}
}
leetcode组合总和2,有重复数字:
class Solution {
List<List<Integer>> list=new ArrayList<>();
List<Integer> path=new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
dfs(candidates,target,0);
return list;
}
private void dfs(int[] candidates, int target,int index){
if(target==0){
list.add(new ArrayList<>(path));
return;
}
for(int i=index;i<candidates.length;i++){
if(candidates[i]<=target){
//同一层的话
if(i>index&&candidates[i]==candidates[i-1]){
continue;
}
path.add(candidates[i]);
dfs(candidates,target-candidates[i],i+1);
path.remove(path.size()-1);
}
}
}
}
leetcode 78子集问题(最入门的一道)
class Solution {
List<List<Integer>> ans =new ArrayList<List<Integer>>();
public List<List<Integer>> subsets(int[] nums) {
dfs(0,nums);
return ans;
}
List<Integer> res=new ArrayList<Integer>();
void dfs(int i,int []nums){
if(i==nums.length){
ans.add(new ArrayList(res));
return ;
}
//子集都有两种选择,要么在要么不在结果中
res.add(nums[i]);
dfs(i+1,nums);
res.remove(res.size()-1);
dfs(i+1,nums);
}
}
leetcode90子集2
class Solution {
List<List<Integer>> res =new LinkedList<>();
LinkedList<Integer> ans=new LinkedList<>();
int n;
public List<List<Integer>> subsetsWithDup(int[] nums) {
//排序
Arrays.sort(nums);
n=nums.length;
traverse(nums,ans,0);
return res;
}
void traverse(int []nums ,LinkedList<Integer> track,int p){
if(p==n+1){
return ;
}
res.add(new LinkedList(track));
for(int i=p;i<n;i++){
//并不用像排列一样,同一层剪枝需要。。。
if(i>p&&nums[i]==nums[i-1]){
continue;
}
track.add(nums[i]);
traverse(nums,track,i+1);
track.removeLast();
}
}
}
在做有重复数字的题时,把握不好return,还是for+continue比较保险
leetcode 93复原ip地址
class Solution {
//四段:
static final int seg_count=4;
int []segments=new int[seg_count];
List<String> ans=new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
//string segID segSTART
dfs(s,0,0);
return ans;
}
void dfs(String s,int segID,int segSTART){
if(segID==seg_count){
if(segSTART==s.length()){
StringBuffer ip=new StringBuffer();
for(int i=0;i<seg_count;i++){
ip.append(segments[i]);
if(i!=seg_count-1){
ip.append('.');
}
}
ans.add(ip.toString());
} return;
}
if(segSTART==s.length()) return ;
//先判断是否有零
if(s.charAt(segSTART)=='0'){
segments[segID]=0;
dfs(s,segID+1,segSTART+1);
}
//因为要比较大小,所以要转成int类型
//一定要枚举每一种可能性
//addr放在外面
int addr =0;
for(int i=segSTART;i<s.length();i++){
addr=addr*10+(s.charAt(i)-'0');
if(addr>0&&addr<=0xFF){
segments[segID]=addr;
dfs(s,segID+1,i+1);
}else {
break;
}
}
}
}
一道dfs的题目(记得new一个vis数组)
leetcode529扫雷
class Solution {
public char[][] updateBoard(char[][] board, int[] click) {
if(board[click[0]][click[1]] == 'M'){
board[click[0]][click[1]] = 'X';
return board;
}
//不必要重复判断
int[][] visited = new int[board.length][board[0].length];
search(board, click[0], click[1], visited);
return board;
}
public void search(char[][] board,int x,int y,int [][]visited){
if(x >= 0 && y >= 0 && x < board.length && y < board[0].length && visited[x][y] == 0){
visited[x][y] = 1;
if(board[x][y] == 'E'){
//递归的终点
if(mine(board, x, y) > 0){
board[x][y] = (char)('0' + mine(board, x, y));
return;
}
else{
board[x][y] = 'B';
search(board, x - 1, y, visited);
search(board, x - 1, y - 1, visited);
search(board, x - 1, y + 1, visited);
search(board, x + 1, y, visited);
search(board, x + 1, y - 1, visited);
search(board, x + 1, y + 1, visited);
search(board, x, y + 1, visited);
search(board, x, y - 1, visited);
}
}
}
}
int mine(char[][] board,int x,int y){
return boom(board, x - 1, y) + boom(board, x - 1, y - 1) + boom(board, x - 1, y + 1) + boom(board, x + 1, y) + boom(board, x + 1, y - 1) + boom(board, x + 1, y + 1) + boom(board, x, y + 1) + boom(board, x, y - 1);
}
public int boom(char[][] board, int x, int y){
return (x >= 0 && y >= 0 && x < board.length && y < board[0].length && board[x][y] == 'M')?1:0;
}
}
递归的总结:
1、递归的终点!!!
2、return 容易出错
leetcode473火柴
class Solution {
int [] cnt;
public boolean makesquare(int[] nums) {
int total=0;
for(int num:nums){
total+=num;
}
if(total==0||total%4!=0) return false;
Arrays.sort(nums);
cnt=new int[4];
return traver(nums.length-1,nums,total/4);
}
boolean traver(int index ,int []nums, int target){
if(index==-1){
if(cnt[0]==cnt[1]&&cnt[1]==cnt[2]&&cnt[2]==cnt[3]) return true;
return false;
}
for(int i=0;i<4;i++){
//加上(i>0&&cnt[i]==cnt[i-1])剪枝,,,
if(cnt[i]+nums[index]>target||(i>0&&cnt[i]==cnt[i-1]))continue;
cnt[i]+=nums[index];
if(traver(index-1,nums,target)) return true;
cnt[i]-=nums[index];
}
return false;
}
总结之前遇到需要遍历的题目,不管是排列还是火柴棒,如果都是要做出选择,则首先应当考虑怎样做出选择
-
从终止条件思考:边要有四个,火柴也要全部用完,都有终止条件!!!也可以想到要递归
-
回溯的思想:从要做选择,到底是边选择火柴,还是火柴选择边。—>也就是递归的终止条件是遍历完了所有的边,还是遍历完了所有的火柴。
两种方法: -
如果是dfs(4条边),先凑一条边(for所有的火柴),也就是边选择火柴,可以用vis[i]来记录每条边是否被用到,但最后要判断是不是所有的边都被用到(用target/4)。
-
如果是travel(所有的火柴),火柴选择边,则对于每个火柴,我们都可以选择四条边(for四条边),并且此时应该想办法剪枝。
滑动窗口
leetcode3 [无重复字符的最长子串]
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s.length()==1||s.length()==0) return s.length();
int right=0,left=0;
Set<Character> window = new HashSet<Character>();
int res=0;
//用集合的话,可以先添加第一个
window.add(s.charAt(right));
right++;
while(right<s.length())
{
char c=s.charAt(right);
while(left<s.length()&&window.contains(c)){
char d=s.charAt(left);
left++;
window.remove(d);
}
window.add(c);
res=Math.max(res,right-left+1);
right++;
}
return res;
}
}
//题目求最长无重复,故可以算出所有无重复,求最大值
leetcode424[替换后的最长重复字符]
class Solution {
public int characterReplacement(String s, int k) {
int [] num =new int[26];
int right=0,left=0;
int res=0;
while(right<s.length()){
int c=s.charAt(right)-'A';
num[c]++;
res=Math.max(res,num[c]);
//如果大于则
if(right-left+1>k+res){
int d=s.charAt(left)-'A';
num[d]--;
left++;
}
right++;
}
//res一直都是最大的
return right-left;
//return res+k;
}
}
//题目最长重复字符,故可以先算出最长的重复,如果此后的有比它大的,则再进行比较
两种思路,是求出所有的无重复,或者所有的重复,还是先求出最大的无重复,或者最大的重复。
leetcode1004 最长连续1的个数
class Solution {
public int longestOnes(int[] A, int K) {
int right=0,left=0;
int count=0;
int res=0;
while(right<A.length){
if(A[right]==0) count++;
if(count>K){
if(A[left++]==0) --count;
}
res=Math.max(res,right-left+1);
right++;
}
return res;
}
}
//这就是求出了所有的重复
while(right<…){
.//…对新添加的right进行处理
while(…某些条件.){
left++;
}
res =max(res ,left-right+1);
right++;
}
leetcode1208尽可能让所有字符相等
class Solution {
public int equalSubstring(String s, String t, int maxCost) {
int n=s.length();
int [] newarray=new int[n];
for(int i=0;i<n;i++){
int a=t.charAt(i)-'A';
int b=s.charAt(i)-'A';
newarray[i]=Math.abs(a-b);
}
int left =0,right =0;
int windowcost=0;
int res=0;
while(right<n){
windowcost+=newarray[right];
while(windowcost>maxCost){
//res =Math.max(right-left,res);
windowcost-=newarray[left]; left++;
}
res=Math.max(res,right-left+1);
right++;
}
return res;
}
}
leetcode1493删掉一个数后
class Solution {
public int longestSubarray(int[] nums) {
int left=0,right=0;
int n=nums.length;
int count=0;
int res=0;
while(right<n){
if(nums[right]==0) count++;
while(count>1){
if(nums[left]==0)count--;
left++;
}
res=Math.max(res,right-left);
right++;
}
return res;
}
}
leetcode295
leetcode209
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left=0,right=0;
int n=nums.length;
int res=10001;
int rest=target;
while(right<n){
rest-=nums[right];
while(rest<=0){
res=Math.min(res,right-left+1);
rest+=nums[left];
left++;
}
//res=Math.min(res,right-left+1);
right++;
}
return res==10001?0:res;
}
}
leetcode76最小覆盖子串
class Solution {
public String minWindow(String s, String t) {
int right=0,left=0;
int n1=s.length();
int n2 =t.length();
int []need =new int[128];
int []window =new int[128];
for(int i=0;i<n2;i++){
need[t.charAt(i)-'A']++;
}
int vaild=0;
int ans=100001;
String res="";
while(right<n1){
int c=s.charAt(right)-'A';
if(need[c]>0&&window[c]<need[c]){
vaild++;
}
window[c]++;
while(vaild>=n2){
int d=s.charAt(left)-'A';
if(right-left+1<ans){
ans=right-left+1;
res=s.substring(left,right+1);
}
if(need[d]>0&&window[d]<=need[d]){
vaild--;
}
window[d]--;
left++;
}
right++;
}
return res;
}
}
leetcode438找到所有字母的异位词
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int n1=s.length();
int n2=p.length();
int left=0,right=0;
int[] need=new int[27];
int[] window=new int[27];
for(int i=0;i<n2;i++){
need[p.charAt(i)-'a']++;
}
List <Integer> res=new ArrayList<>();
int vaild=0;
while(right<n1){
int c=s.charAt(right)-'a';
if(need[c]>0&&window[c]<need[c])vaild++;
window[c]++;
while(right-left+1>=n2){
if(vaild==n2) res.add(left);
int d=s.charAt(left)-'a';
if(need[d]>0){
if(window[d]>0&&window[d]<=need[d]){
vaild--;
}
}
window[d]--;
left++;
}
right++;
}
return res;
}
}
并查集
-
想像一个问题:若某个家族人员过于庞大,要判断两个是否是亲戚,确实不容易,给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
-
出现并集、寻找根
1、初始化 int fa[MAXN]; //设置n个子集 //一开始,将每个元素得父节点设置为自己 void init(int n) { for (int i = 1; i <= n; ++i) fa[i] = i; } 2、查找 //递归,一层一层的访问父节点,直到访问到根节点(根节点的标志就是父节点是本身) int find(int x) { if(fa[x] == x) return x; else return find(fa[x]); } 3、合并 //将前一个元素的父节点设置为后一个元素的父节点 void merge(int i, int j) { fa[find(i)] = find(j); } 4、路径压缩 int find(int x) { if(x == fa[x]) return x; else{ fa[x] = find(fa[x]); //父节点设为根节点 return fa[x]; //返回父节点 } } 通常可以简写成: int find(int x) { return x == fa[x] ? x : (fa[x] = find(fa[x])); } 5、按秩合并 (1)初始化(秩) void init(int n) { for (int i = 1; i <= n; ++i) { fa[i] = i; rank[i] = 1; } } (2)合并 void merge(int i, int j) { int x = find(i), y = find(j); //先找到两个根节点 if (rank[x] <= rank[y]) fa[x] = y; else fa[y] = x; if (rank[x] == rank[y] && x != y) rank[y]++; //如果深度相同且根节点不同,则新的根节点的深度+1 }
- 例题:c++编写
```c++
1、程序自动分析
//离散化
#include <iostream>
#include <map>
#include <cstdio>
using namespace std;
//fa数组
int pre[110000<<4];
struct ask{
int p1;int p2;int e;
}a[1000];
//对应得离散化
map<int,int> Map;
int tot=0;
//离散化
int reset(int x){
//Map.find(x)!=Map.end()判断是否已经初始化
if(Map.find(x)!=Map.end())return Map[x];
Map[x]=++tot;
return tot;
}
int find(int x){
int r=x;
//一直找父节点
while(pre[r]!=r){
r=pre[r];
}
return r;
}
//合并
void join(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx!=fy)
pre[fx]=fy;
}
int main()
{
int total;
int n;
int fx,fy;
int t=read();
int p1,p2,k;Map.clear();
while(t--){
tot=0;
n=read();
//至少2*n个数
for(int i=1;i<=n*2;i++){
pre[i]=i;
}
for(int i=1;i<=n;i++){
//
p1=a[i].p1=read();
p2=a[i].p2=read();
a[i].e=read();
p1=reset(p1);
p2=reset(p2);
//如果相等,,,合并
if(a[i].e){
fx=find(p1);
fy=find(p2);
pre[fx]=fy;
}
}
bool flag=true;
for(int i=1;i<=n;i++){
if(!a[i].e){
p1=reset(a[i].p1);
p2=reset(a[i].p2);
fx= find(p1);
fy=find(p2);
//如果已经合并得不相等
if(fx==fy)
{ flag=false;
break;
}
}
}
if(flag)
puts("YES");
else puts("NO");
}
return 0;
}
leetcode947
class Solution {
int []fa;
int n;
public void init(){
n=0;
fa=new int [10001];
for(int i=1;i<10001;i++){
fa[i]=i;
}
}
public int find(int x){
if(fa[x]==x){
return x;
}
else {
fa[x]=find(fa[x]);
return fa[x];
}
}
public void merge(int i,int j){
fa[find(i)]=find(j);
n++;
}
public int removeStones(int[][] stones) {
int len=stones.length;
init();
for(int i=0;i<len;i++){
for(int j=i;j<len;j++){
if(find(i)!=find(j))
if(stones[i][0]==stones[j][0]||stones[j][1]==stones[i][1]){
merge(i,j);
}
}
}
return n;
}
}