1.面试题38 字符串的排列
class JianZhiOffer {
public static void main(String[] args) {
JianZhiOffer p = new JianZhiOffer();
System.out.println(p.Permutation("abc").toString());
}
public ArrayList Permutation(String str) {
ArrayList list = new ArrayList<>();
if(str==null)
return list;
PermutationHelper(str.toCharArray(), 0, list);
System.out.println(list);
Collections.sort(list); //字典序输出
return list;
}
public void PermutationHelper(char[] cs, int i, List list) {
if(i==cs.length-1){
String str = cs.toString();
if(!list.contains(str)) //如果列表中没有,就添加
list.add(str);
}
for(int j=i;j
说明:
for(int j=i;j
这里的交换是每次j从i开始i交换(比如i=0,那么j=0,i和j交换,j=1,i和j交换,j=2,i和j交换)。看纸上笔记,其实类似于先序遍历。
代码实现:
public class Solution {
ArrayList> list1 = new ArrayList<>();
ArrayList list2 = new ArrayList<>();
public ArrayList> FindPath(TreeNode root,int target) {
if(root==null)
return list1;
SumFindPath(root,target);
return list1;
}
public void SumFindPath(TreeNode root,int target){
if(root==null)
return;
list2.add(root.val);
target = target - root.val;
if(target==0&&root.left==null&&root.right==null)
{
list1.add(new ArrayList(list2));
}
SumFindPath(root.left,target); //target
SumFindPath(root.right,target); //target
list2.remove(list2.size()-1);
}
}
代码实现:
class JianZhiOffer{
public static void main(String[] args) {
//测试用例一
char[]ch = {'a','b','t','g','c','f','c','s','j','d','e','h'};
char[] str = {'b','f','c','e'};
//char[] str = {'a','b','f','b'};
//测试用例二
//char[]ch = {'A','B','C','E','S','F','C','S','A','D','E','E'};
//char[] str = {'A','B','C','C','E','D'};
Solution s = new Solution();
System.out.println(s.hasPath(ch,3,4,str));
}
}
class Solution {
public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
int flag[] = new int[matrix.length]; //每一个位置对应一个字符判断是不是去过
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (helper(matrix, rows, cols, i, j, str, 0, flag))
return true;
}
}
return false;
}
private boolean helper(char[] matrix,int rows,int cols,int i,int j,char[] str,int k,int[] flag){
int index = i*cols + j; //与机器人的题目不同,这里用一位数组表示的二维数组
if(i<0||i>=rows||j<0||j>=cols||flag[index]==1||matrix[index]!=str[k]){
return false;
}
if(k==str.length-1) //要找到的字符已经找到了,直接返回true
return true;
flag[index] = 1;
boolean flag1 = helper(matrix,rows,cols,i-1,j,str,k+1,flag);
boolean flag2 = helper(matrix,rows,cols,i+1,j,str,k+1,flag);
boolean flag3 = helper(matrix,rows,cols,i,j+1,str,k+1,flag);
boolean flag4 = helper(matrix,rows,cols,i,j-1,str,k+1,flag);
if(flag1||flag2||flag3||flag4){ //四个方向,有一条路能走通就返回true
return true;
}
flag[index] = 0;
return false;
}
}
注意点:
1)
flag[index] = 0;
return false;
2)
int index = i*cols + j; //与机器人的题目不同,这里用一位数组表示的二维数组
3)
boolean left = helper(matrix, i-1, j, rows, cols, str, k+1, flag);
boolean right = helper(matrix,i+1,j ,rows,cols, str, k+1,flag );
boolean up = helper(matrix, i ,j+1,rows,cols,str,k+1,flag);
boolean down = helper(matrix ,i ,j-1,rows, cols,str,k+1,flag);
不能改成i--,i++,j++,j--
这里的将标识位设置为0,这是因为从某个字母开始找不到了,这时候要将标识位清理,让其他字母还可以经过这个字母。
关于回溯的理解:其实回溯的套路差不过,在类似于后序遍历的时候,都要做相关的清理工作。
以一个M×N的长方阵表示迷宫,0和1分别表示迷宫中的通路和障碍。设计程序,对任意设定的迷宫,求出从入口到出口的所有通路。
下面我们来详细讲一下迷宫问题的回溯算法。
(入口) 0 0 1 0 0 0 1 0
0 0 1 0 0 0 1 0
0 0 1 0 1 1 0 1
0 1 1 1 0 0 1 0
0 0 0 1 0 0 0 0
0 1 0 0 0 1 0 1
0 1 1 1 1 0 0 1
1 1 0 0 0 1 0 1
1 1 0 0 0 0 0 0(出口)
该图是一个迷宫的图。1代表是墙不能走,0是可以走的路线。只能往上下左右走,直到从左上角到右下角出口。
做法是用一个二维数组来定义迷宫的初始状态,然后从左上角开始,不停的去试探所有可行的路线,碰到1就结束本次路径,然后探索其他的方向,当然我们要标记一下已经走的路线,不能反复的在两个可行的格子之间来回走。直到走到出口为止,算找到了一个正确路径。 (题目描述参考博客https://blog.csdn.net/sunhuaqiang1/article/details/52599850)
代码实现:
public class HuiSu {
static int count = 0;
/**
* 定义迷宫数组
*/
private static int[][] array = {
{0, 0, 1, 0, 0, 0, 1, 0},
{0, 0, 1, 0, 0, 0, 1, 0},
{0, 0, 1, 0, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 0, 1, 0},
{0, 0, 0, 1, 0, 0, 0, 0},
{0, 1, 0, 0, 0, 1, 0, 1},
{0, 1, 1, 1, 1, 0, 0, 1},
{1, 1, 0, 0, 0, 1, 0, 1},
{1, 1, 0, 0, 0, 0, 0, 0}
};
public static void main(String[] args) {
int maxLine = 8; //列
int maxRow = 9; //行
System.out.println("开始时间: "+System.currentTimeMillis());
int[][] flag = new int[maxRow][maxLine];
helper(0,0,maxRow,maxLine,flag);;
System.out.println("结束时间: "+System.currentTimeMillis());
}
public static void helper(int i,int j,int maxRow,int maxLine,int[][] flag){
//如果没有找到就继续
if(i<0||i>=maxRow||j<0||j>=maxLine||array[i][j]==1||array[i][j]==5){
return;
}
if(i==maxRow-1&&j==maxLine-1){
print(maxRow,maxLine); //将能到达目的的路径打印出来
return;
}
array[i][j] = 5; //已经走过的路径设置为5
helper(i-1,j,maxRow,maxLine,flag);
helper(i+1,j,maxRow,maxLine,flag);
helper(i,j+1,maxRow,maxLine,flag);
helper(i,j-1,maxRow,maxLine,flag);
array[i][j] = 0; //从坐标[i,j]开始,都不行,就清除标志
}
//打印
private static void print(int maxRow,int maxLine) {
System.out.println("得到第"+(++count)+"解");
for (int i = 0; i < maxRow; i++) {
for (int j = 0; j < maxLine; j++) {
System.out.print(array[i][j] + " ");
}
System.out.println();
}
}
}
说明:走迷宫问题相比于矩阵中找字符串有几点不同,1)找字符串要从矩阵中的每个字母出现,向其上下左右遍历,而走迷宫问题只从(0,0)坐标点出发,向其上下左右遍历。2)
找字符串问题在某条路径找到字符串后就停止,所以多了这行语句。
if(flag1||flag2||flag3||flag4){ //四个方向,有一条路能走通就返回true
return true; //这里的return true一定要有
}
分析:递归从一点触发,向四周的四个点递归搜索,其实这个根二叉树的深度和二叉树的后序遍历都是一个模子里出来的。
代码实现:
class JianZhiOffer{
public static void main(String[] args) {
Solution s = new Solution();
int threshold = 3;
int rows = 5;
int cols = 5;
System.out.println(s.movingCount(threshold, rows, cols));
}
}
class Solution {
public int movingCount(int threshold, int rows, int cols)
{
int[][] flag = new int[rows][cols];
return helper(threshold,0,0,rows,cols,flag);
}
private int helper(int threshold,int i,int j,int rows,int cols,int[][] flag){
if(i<0||i>=rows||j<0||j>=cols||flag[i][j]==1||(change(i)+change(j)>threshold)){
return 0;
}
flag[i][j] = 1; //已经走过的设置标识位
int left = helper(threshold,i+1,j,rows,cols,flag);
int right = helper(threshold,i-1,j,rows,cols,flag);
int up = helper(threshold,i,j-1,rows,cols,flag);
int down = helper(threshold,i,j+1,rows,cols,flag);
return left + right + up + down + 1; //求各个方向能走过的所有的点
}
public int change(int i){ //求数的各位相加的结果
int sum = 0;
while(i>0){
int temp = i%10;
sum += temp;
i = i/10;
}
return sum;
}
}
说明:1)从某坐标开始,将走过的坐标的标志位都置为1,到达边界(行坐标和列坐标的数位和等于k的格子)此时计数为0,从该坐标返回走,每走一步就加一。
2)这边为了方便用了一个二维数组,其实也可以像上面一样使用一维的数组,使用index = i*cols + j来计算数组的下标值。
有数组{64,50,32,16,8,2,1,96},找和为100的数字的组合。
class HuiSu {
static ArrayList temp = new ArrayList<>();
static ArrayList save = new ArrayList<>();
static ArrayList> result = new ArrayList<>();
static int[] visit;
public static void main(String[] args) {
int[] nums = {64,50,32,16,8,4,2,1,96};
int[] flag = new int[nums.length]; //用来标记已经访问过的坐标
dfs(nums,100,0,flag);
System.out.println(result);
}
private static void dfs(int[] nums,int target,int index,int[] flag){
if(target==0){
result.add(new ArrayList(temp)); //将相加等于100的路径添加到result列表中
}
if(target<0||index>=nums.length||flag[index]==1){ //如果target小于0或者已经访问过了
return;
}
for(int i=index;i
代码输出:
[[64, 32, 4], [50, 32, 16, 2], [4, 96]]
说明:该题的难点在于:前面的几道题,都只考虑相邻的组合,而该题要考虑非相邻的组合。
注意:这两行代码的顺序不能交换
if(target==0){
result.add(new ArrayList(temp)); //将相加等于100的路径添加到result列表中
}
if(target<0||index>=nums.length||flag[index]==1){ //如果target小于0或者已经访问过了
return;
}
例如这题最后一个数是96,如果nums[i]=96,此时target-nums[i] =0,但是此时i+1等于nums.length,所以这两个判断交换机会漏解。
temp.add(nums[i]);
dfs(nums,target-nums[i],i+1,flag);
参考学习牛客网通过的代码:
https://www.nowcoder.com/questionTerminal/6e5207314b5241fb83f2329e89fdecc8
https://www.nowcoder.com/questionTerminal/c61c6999eecb4b8f88a98f66b273a3cc
https://www.nowcoder.com/questionTerminal/fe6b651b66ae47d7acce78ffdd9a96c7
https://blog.csdn.net/sunhuaqiang1/article/details/52599850