算法入门篇九 暴力递归

牛客网 左程云老师的算法入门课

暴力递归

原则

 汉诺塔问题

问题

  • 打印n层汉诺塔从左边移动到最右边的过程 

思想

一共六个过程,左到右、左到中,中到左,中到右,右到左,右到中,互相嵌套使用

左到右

  • 将1-》N-1移动到中间的那个杆上
  • 将N从最左边移动到最右边
  • 将1》-N-1移动到最右边的那个杆上

左到中

  • 将1-N-1从左移动到右
  • N从左移动到中
  • 将1-》N-1从右移动到中

。。。。。。

代码

package class08;
 
public class Code01_Hanoi {
 
    public static void hanoi(int n) {
        if (n > 0) {
            func(n, "左", "右", "中");
        }
    }
 
    // 1~i 圆盘 目标是from -> to, other是另外一个
    public static void func(int N, String from, String to, String other) {
        if (N == 1) { // base
            System.out.println("Move 1 from " + from + " to " + to);
        } else {
            func(N - 1, from, other, to);
            System.out.println("Move " + N + " from " + from + " to " + to);
            func(N - 1, other, to, from);
        }
    }
     
     
     
     
    public static void printAllWays(int n) {
        leftToRight(n);
    }
     
    public static void leftToRight(int n) {
        if(n== 1) {
            System.out.println("Move 1 from left to right");
            return ;
        }
        leftToMid(n-1);
        System.out.println("Move " +n + " from left to right");
        midToRight(n-1);
    }
     
    public static void leftToMid(int n) {
        if(n== 1) {
            System.out.println("Move 1 from left to mid");
            return ;
        }
        leftToRight(n-1);
        System.out.println("Move " +n + " from left to mid");
        rightToMid(n-1);
    }
     
    public static void midToRight(int n) {
         
    }
     
    public static void rightToMid(int n) {
         
    }
     
     
     
     
     
     
     
     
     
     
     
 
    public static void main(String[] args) {
        int n = 3;
        hanoi(n);
    }
 
}

例子

打印一个字符串的全部子序列,包括空字符串

(从左往右依次尝试的模型)

  • 需要保证字符的前后顺序
  • “abc” =》a,b,c,ab,ac,bc,abc,null
  • 暴力穷举

打印字符串的全部排列

思路

  • 全排列,先前所选用的字符,后面后不可以选择了。

代码

package class08;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

public class Code03_PrintAllPermutations {

	public static ArrayList<String> Permutation(String str) {
		ArrayList<String> res = new ArrayList<>();
		if (str == null || str.length() == 0) {
			return res;
		}
		char[] chs = str.toCharArray();
		process(chs, 0, res);
		return res;
	}

	// str[i..]范围上,所有的字符,都可以在i位置上,后续都去尝试
	// str[0..i-1]范围上,是之前做的选择
	// 请把所有的字符串形成的全排列,加入到res里去
	public static void process(char[] str, int i, ArrayList<String> res) {
		if (i == str.length) {
			res.add(String.valueOf(str));
		}
		boolean[] visit = new boolean[26]; // visit[0 1 .. 25]
		for (int j = i; j < str.length; j++) {
			if (!visit[str[j] - 'a']) {
				visit[str[j] - 'a'] = true;
				swap(str, i, j);
				process(str, i + 1, res);
				swap(str, i, j);
			}
		}
	}

	public static void swap(char[] chs, int i, int j) {
		char tmp = chs[i];
		chs[i] = chs[j];
		chs[j] = tmp;
	}

	public static List<String> getAllC(String s) {
		List<String> ans = new ArrayList<>();
		ArrayList<Character> set = new ArrayList<>();
		for (char cha : s.toCharArray()) {
			set.add(cha);
		}
		process(set, "", ans);
		return ans;
	}

	public static void process(ArrayList<Character> list, String path, List<String> ans) {
		if (list.isEmpty()) {
			ans.add(path);
			return;
		}
		HashSet<Character> picks = new HashSet<>();
		//set中每一个字符,都可以作为当前字符,但是一旦当前决定要,后续就不能再次使用了 (去重)
		for (int index = 0; index < list.size(); index++) {
			if (!picks.contains(list.get(index))) {
				picks.add(list.get(index));
				String pick = path + list.get(index);
				ArrayList<Character> next = new ArrayList<>(list);
				next.remove(index);
				process(next, pick, ans);
			}
		}
	}

	public static void main(String[] args) {
		String s = "aac";
		List<String> ans = getAllC(s);
		for (String str : ans) {
			System.out.println(str);
		}
	}

}

数字转化

题目要求

思路 

  • 输入原始的字符串111,下面012对应其位置,对于第i个节点,i可以自己实现转化,或者连带i+1实现转化

  • 但是需要判定i和i+1是否超出26,因为26最大代表z

  • 遇到0的时候,要判定:比如“102”,如果1自己单独做决定,那么0自己不可以做决定,0和2也不可以做决定,因此0只可以和且在一起,才是有效的

代码

package class08;

public class Code06_ConvertToLetterString {

	public static int number(String str) {
		if (str == null || str.length() == 0) {
			return 0;
		}
		return process(str.toCharArray(), 0);
	}

	// i之前的位置,如何转化已经做过决定了, 不用再关心
	// str[i... ] 有多少种转化的结果
	public static int process(char[] str, int i) {
		if (i == str.length) { // base case
			//来到终止的位置,之前的路径代表一种有效的方法
			return 1;
		}
		// i没有到终止位置
		if (str[i] == '0') {//“102”的情形,0做不了决定
			return 0;
		}
		// str[i]字符不是‘0’
		if (str[i] == '1') {
			int res = process(str, i + 1); // i自己作为单独的部分,后续有多少种方法
			if (i + 1 < str.length) {
				res += process(str, i + 2); // (i和i+1)作为单独的部分,后续有多少种方法
			}
			return res;
		}
		if (str[i] == '2') {
			int res = process(str, i + 1); // i自己作为单独的部分,后续有多少种方法
			// (i和i+1)作为单独的部分并且没有超过26,后续有多少种方法
			if (i + 1 < str.length && (str[i + 1] >= '0' && str[i + 1] <= '6')) {
				res += process(str, i + 2); // (i和i+1)作为单独的部分,后续有多少种方法
			}
			return res;
		}
		// str[i] == '3' ~ '9'
		return process(str, i + 1);
	}

	public static int dpWays(String s) {
		if (s == null || s.length() == 0) {
			return 0;
		}
		char[] str = s.toCharArray();
		int N = str.length;
		int[] dp = new int[N + 1];
		dp[N] = 1;
		for (int i = N - 1; i >= 0; i--) {
			if (str[i] == '0') {
				dp[i] = 0;
			} else if (str[i] == '1') {
				dp[i] = dp[i + 1];
				if (i + 1 < N) {
					dp[i] += dp[i + 2];
				}
			} else if (str[i] == '2') {
				dp[i] = dp[i + 1]; 
				if (i + 1 < str.length && (str[i + 1] >= '0' && str[i + 1] <= '6')) {
					dp[i] += dp[i + 2];
				}
			} else {
				dp[i] = dp[i + 1];
			}
		}
		return dp[0];
	}

	public static void main(String[] args) {
		System.out.println(number("11111"));
	}

}

背包问题

题目要求

代码

package class08;

public class Code07_Knapsack {

	public static int getMaxValue(int[] w, int[] v, int bag) {
		return process(w, v, 0, 0, bag);
	}

	// index   当前货物的货物号
	//w【index】当前货物的重量
	//v【index】当前货物的价值
	//alreadyW:0至index-1已经做出的决定,所形成的目前的重量
	//bag:袋子的总共重量(固定参数)
	//index。。。后续所有货物的自由选择,返回最大的价值
	public static int process(int[] w, int[] v, int index, int alreadyW, int bag) {
		if (alreadyW > bag) {//超重了
			return -1;
		}
		// 重量没超
		if (index == w.length) {
			return 0;
		}
		int p1 = process(w, v, index + 1, alreadyW, bag);//不要当前index货物,获得的最大价值
		int p2next = process(w, v, index + 1, alreadyW + w[index], bag);//要当前货物,当前货物的价值 + 后续得到的价值
		int p2 = -1;
		if (p2next != -1) {
			p2 = v[index] + p2next;
		}
		return Math.max(p1, p2);

	}

	public static int maxValue(int[] w, int[] v, int bag) {
		return process(w, v, 0, bag);
	}

	// 只剩下rest的空间了,
	// index...货物自由选择,但是不要超过rest的空间
	// 返回能够获得的最大价值
	public static int process(int[] w, int[] v, int index, int rest) {
		if (rest <= 0) { // base case 1
			return 0;
		}
		// rest >=0
		if (index == w.length) { // base case 2
			return 0;
		}
		// 有货也有空间
		int p1 = process(w, v, index + 1, rest);
		int p2 = Integer.MIN_VALUE;
		if (rest >= w[index]) {
			p2 = v[index] + process(w, v, index + 1, rest - w[index]);
		}
		return Math.max(p1, p2);
	}

	public static int dpWay(int[] w, int[] v, int bag) {
		int N = w.length;
		int[][] dp = new int[N + 1][bag + 1];
		for (int index = N - 1; index >= 0; index--) {
			for (int rest = 1; rest <= bag; rest++) {
				dp[index][rest] = dp[index + 1][rest];
				if (rest >= w[index]) {
					dp[index][rest] = Math.max(dp[index][rest], v[index] + dp[index + 1][rest - w[index]]);
				}
			}
		}
		return dp[0][bag];
	}

	public static void main(String[] args) {
		int[] weights = { 3, 2, 4, 7 };
		int[] values = { 5, 6, 3, 19 };
		int bag = 11;
		System.out.println(maxValue(weights, values, bag));
		System.out.println(dpWay(weights, values, bag));
	}

}

取牌

思路

  • 第一种情形,只剩下一张牌的时候,直接取最后一张牌就可以了

  • 如果取最左边的牌,计算我的成绩为arr【L】+S(arr,L+1,R)

  • 如果取最右边的牌,计算我的成绩为arr【R】+S(arr,L,R-1)

  • 对于上面两种情形取最大值就是我的选择

  • 其中S为黑盒函数,因为先手和后手是互换的角色,当先手取完牌之后,其角色就变成后手了

代码

package class08;

public class Code08_CardsInLine {

	public static int win1(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1));
	}

	public static int f(int[] arr, int i, int j) {
		if (i == j) {
			return arr[i];
		}
		return Math.max(arr[i] + s(arr, i + 1, j), arr[j] + s(arr, i, j - 1));
	}

	public static int s(int[] arr, int i, int j) {
		if (i == j) {
			return 0;//后手没有牌可以拿
		}
		return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));//要让对方拿最小的值
	}

	public static int win2(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int[][] f = new int[arr.length][arr.length];
		int[][] s = new int[arr.length][arr.length];
		for (int j = 0; j < arr.length; j++) {
			f[j][j] = arr[j];
			for (int i = j - 1; i >= 0; i--) {
				f[i][j] = Math.max(arr[i] + s[i + 1][j], arr[j] + s[i][j - 1]);
				s[i][j] = Math.min(f[i + 1][j], f[i][j - 1]);
			}
		}
		return Math.max(f[0][arr.length - 1], s[0][arr.length - 1]);
	}

	public static void main(String[] args) {
		int[] arr = { 1, 9, 1 };
		System.out.println(win1(arr));
		System.out.println(win2(arr));

	}

}

N皇后问题

思路

  • 假设先前一个皇后的位置是(a,b),那么再次摆一个皇后的位置是(c,d),因为是从上往下执行,因此可以保证每个皇后之间不共行

  • 通过比对b和d判定皇后之间是否共列

  • 通过比对|c-a|==|d-b|来判定是否处于一个斜线上

代码

package class08;

public class Code09_NQueens {

	public static int num1(int n) {
		if (n < 1) {
			return 0;
		}
		// record[0] ?  record[1]  ?  record[2]
		int[] record = new int[n]; // record[i] -> i行的皇后,放在了第几列
		return process1(0, record, n);
	}

	// 潜台词:record[0..i-1]的皇后,任何两个皇后一定都不共行、不共列,不共斜线
	// 目前来到了第i行
	// record[0..i-1]表示之前的行,放了的皇后位置
	// n代表整体一共有多少行
	// 返回值是,摆完所有的皇后,合理的摆法有多少种
	public static int process1(int i, int[] record, int n) {
		if (i == n) { // 终止行
			return 1;
		}
		int res = 0;
		for (int j = 0; j < n; j++) { // 当前行在i行,尝试i行所有的列  -> j
			// 当前i行的皇后,放在j列,会不会和之前(0..i-1)的皇后,不共行共列或者共斜线,
			// 如果是,认为有效
			// 如果不是,认为无效
			if (isValid(record, i, j)) {
				record[i] = j;
				res += process1(i + 1, record, n);
			}
		}
		return res;
	}

	// record[0..i-1]你需要看,record[i...]不需要看
	// 返回i行皇后,放在了j列,是否有效
	public static boolean isValid(int[] record, int i, int j) {
		for (int k = 0; k < i; k++) { // 之前的某个k行的皇后
			if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
				return false;
			}
		}
		return true;
	}

	// 请不要超过32皇后问题,如果类型改为long,则可以计算64皇后问题
	public static int num2(int n) {
		if (n < 1 || n > 32) {
			//throw new RuntimeException("问题太大,难以计算");
			//运行错误:不用try catch 和 抛出声明
			//预期错误:使用try catch ,比如,将异常捕获,然后告诉使用者,输入类型出错 
			return 0;
		}
		int limit = n == 32 ? -1 : (1 << n) - 1;
		return process2(limit, 0, 0, 0);
	}

	//优化版本 递归使用、使用二进制类型计算
	// colLim 列的限制,1的位置不能放皇后,0的位置可以
	// leftDiaLim 左斜线的限制,1的位置不能放皇后,0的位置可以
	// rightDiaLim 右斜线的限制,1的位置不能放皇后,0的位置可以
	public static int process2(int limit, 
			int colLim, 
			int leftDiaLim,
			int rightDiaLim) {
		if (colLim == limit) { // base case
			return 1;
		}
		// 所有候选皇后的位置,都在pos上
		int pos = limit & (~(colLim | leftDiaLim | rightDiaLim));
		int mostRightOne = 0;
		int res = 0;
		while (pos != 0) {
			mostRightOne = pos & (~pos + 1);
			pos = pos - mostRightOne;
			res += process2(limit, 
					colLim | mostRightOne,
					(leftDiaLim | mostRightOne) << 1,
					(rightDiaLim | mostRightOne) >>> 1);
		}
		return res;
	}

	public static void main(String[] args) {
		int n = 14;

		long start = System.currentTimeMillis();
		System.out.println(num2(n));
		long end = System.currentTimeMillis();
		System.out.println("cost time: " + (end - start) + "ms");

		start = System.currentTimeMillis();
		System.out.println(num1(n));
		end = System.currentTimeMillis();
		System.out.println("cost time: " + (end - start) + "ms");

	}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值