面试题12、13、14、15

面试题12.矩阵中的路径

在这里插入图片描述本题使用回溯算法

  • DFS:通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推

  • 剪枝:在搜索中,遇到这条路不可能和目标字符串匹配成功的情况(例如:此矩阵元素和目标字符不同、此元素已被访问过),则应立即返回。
    在这里插入图片描述

  • 递归参数:当前元素在矩阵 board 中的行列索引 i 和 j,当前目标字符word中的索引 k

  • 终止条件

    • 1.返回false:(1)行或列索引越界;(2)当前矩阵元素与目标字符不同;(3)当前矩阵元素已访问过;
    • 2.返回true: k = len(word) - 1,即字符全部匹配完成。
  • 递推工作

    • 1.标记当前矩阵元素:将 board[i][j] 修改为字符 ‘.’,代表此元素已被访问过防止之后重复访问。
    • 2.搜索下一单元格:朝当前元素的上、下、左、右四个方向开始下层递归,使用或连接
public boolean exist(char[][] board, String word) {
    char[] words = word.toCharArray();
    for (int i = 0; i < board.length; i++) {
        for (int j = 0; j < board[0].length; j++) {
            //从[i,j]这个坐标开始查找
            if (dfs(board, words, i, j, 0))
                return true;
        }
    }
    return false;
}
public boolean dfs(char[][] board, char[] words, int i, int j, int k) {
	//边界的判断,如果越界直接返回false。index表示的是查找到字符串word的第几个字符,
    //如果这个字符不等于board[i][j],说明验证这个坐标路径是走不通的,直接返回false
	if(i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != words[k]) 
		return false;
	//如果word的每个字符都查找完了,直接返回true
	if(k == words.length - 1) return true;
	//把当前坐标的值保存下来,为了在最后复原
	char temp = board[i][j];
	//然后修改当前坐标的值
	board[i][j] = '.';
	//走递归,沿着当前坐标的上下左右4个方向查找
	boolean res = dfs(board, words, i + 1, j, k + 1) || 
				  dfs(board, words, i - 1, j, k + 1) || 
				  dfs(board, words, i, j + 1, k + 1) || 
				  dfs(board, words, i, j - 1, k + 1);
	//递归之后再把当前的坐标复原
	board[i][j] = temp;
	return res;
}

代码二:

class Solution {
    public int m;
    public int n;
    //偏移量数组,代表走的方向
    //        x-1,y
    // x,y-1  x,y    x,y+1
    //        x+1,y
    int[][] direction = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
    //标记该点是否已被选择过,true表示被选择过,false表示还没被选择
    boolean[][] marked;
    public boolean exist(char[][] board, String word) {
		m = board.length;
		if(m == 0) return false;
		n = board[0].length;
		marked = new boolean[m][n];
		//遍历所有的点,并找到其中符合条件的点
		for(int i = 0; i < m; i++) {
			for(int j = 0; j < n; j++) {
				if(dfs(board, i, j, 0, word)) return true;
			}
		}
		return false;
	}
	//判断网格中是否有该字符串
	//start 表示当前是第几个字符,从0开始
	public boolean dfs(char[][] board, int row, int col, int start, String word) {
		//如果start表示最后一个字符,且也和字符串的最后一个字符一致,则返回true;
		if(start == word.length() - 1) return board[row][col] == word.charAt(start);
		if(board[row][col] == word.charAt(start)) {
			marked[row][col] = true; //表示这个字符已被选择
			for(int i = 0; i < 4; i++) {
				int newX = row + direction[i][0]; //新的横坐标
				int newY = col + direction[i][1]; //新的纵坐标
				if(inArea(newX, newY) && !marked[newX][newY]) {
					if(dfs(board, newX, newY, start + 1, word) return true;
				}
			}
			marked[row][col] = false; //撤销选择
		}
		return false;
	}
	//判断这个(x,y)这个点是否越界
	public boolean inArea(int x, int y) {
		return x >= 0 && x < m && y >= 0 && y < n;
	}
}

—————————————————————————————

面试题13.机器人的运动范围

在这里插入图片描述在这里插入图片描述

方法一:DFS

  • 递归参数:当前元素在矩阵中的行列索引 i 和 j。

  • 终止条件:当(1)行或列索引越界;(2)数位和超过目标值 k;(3)当前元素已访问过,返回0

  • 递推工作:

    • 1.标记当前单元格
    • 2.计算当前元素的下、右两个方向元素的数位和,并开启下层递归
  • 回溯返回值:返回 1+右方搜索的可达解总数+下方搜索的可达解总数,代表从本单元格递归搜索的可达解总数。

在这里插入图片描述

class Solution {
    boolean[][] flag;
    int row;
    int col;
    public int movingCount(int m, int n, int k) {
    	flag = new boolean[m][n];
    	row = m;
    	col = n;
    	return dfs(0, 0, k);
    }
    public int dfs(int i, int j, int k) {
    	if(i < 0 || i >= row || j < 0 || j >= col || 
    	   getDigitSum(i) + getDigitSum(j) > k || flag[i][j]) 
    		return 0;
    	flag[i][j] = true;
    	return 1 + dfs(i + 1, j, k) + dfs(i, j + 1, k);
    }
    //计算一个数字的数位之和
    public int getDigitSum(int x) {
        int res = 0;
        while(x > 0) {
            res += x % 10;
            x /= 10;
        }
        return res;
    }
}

方法一:BFS

  • 初始化:将机器人初始点(0,0)加入队列queuel;

  • 迭代终止条件:queue为空,代表已经遍历完所有可达解;

  • 迭代工作:

    • 1.单元格出队:将队首单元格的索引、数位和 弹出,作为当前搜索单元格;
    • 2.判断是否跳过:若(1)索引越界;(2)数位和超过目标值;(3)当前元素已经被访问过,执行 continue;
    • 3.标记当前单元格:将单元格索引(i,j)存入flag,代表已经被访问过了;
    • 4.单元格入队:将当前元素的下方、右方单元格的索引、数位和加入queue。
class Solution {
    public int movingCount(int m, int n, int k) {
		boolean[][] flag = new boolean[m][n];
		int res = 0;
		Queue<int[]> queue = new LinkedList<>();
		//4个0依次表示为:行索引,列索引,行的数位和,列的数位和
		queue.add(new int[]{0, 0, 0, 0});
		while(queue.size() > 0) {
			int[] x = queue.poll();
			int i = x[0], j = x[1], si = x[2], sj = x[3];
			if(i >= m || i < 0 || j >= n || j < 0 ||
			   si + sj > k || flag[i][j])
				continue;
			flag[i][j] = true;
			res++;
			queue.add(new int[]{i + 1, j, getDigitSum(i + 1), sj});
			queue.add(new int[]{i, j + 1, si, getDigitSum(j + 1)});
		}
		return res;
    }
    //计算一个数字的数位之和
    public int getDigitSum(int x) {
        int res = 0;
        while(x > 0) {
            res += x % 10;
            x /= 10;
        }
        return res;
    }
}

—————————————————————————————

面试题14.剪绳子

在这里插入图片描述

动态规划

  • 状态定义:dp[i] 表示将正整数 i 拆分成至少两个正整数的和之后,这些正整数的最大乘积。

  • 状态转移方程:把长度为i的绳子拆成两部分,一部分是j,另一部分是i-j,那么会有下面4种情况

    • 1.j 和 i-j 都不再拆: dp[i] = j * (i-j);
    • 2.j 拆,i-j 不拆:dp[i] = dp[j] * (i-j);
    • 3.j 不拆,i-j 拆:dp[i] = j * dp[i-j];
    • 4.j 不拆,i-j 不拆:dp[i] = dp[j] * dp[i-j];

由此得到的递推公式为 :
dp[i]=max(dp[i],max(j,dp[j]) * max(i-j,dp[i-j]))

  • 初始化:dp[0]=0,dp[1]=1

在这里插入图片描述

class Solution {
    public int cuttingRope(int n) {
        int[] dp = new int[n + 1];
        dp[1] = 1;
        for(int i = 2; i <= n; i++) {
            for(int j = 1; j < i; j++) {
                dp[i] = Math.max(dp[i], Math.max(j, dp[j]) * Math.max(i - j, dp[i - j]));
            }
        }
        return dp[n];
    }
}

另外一种状态转移方程:当 i≥2 时,假设对正整数 i 拆分出的第一个正整数是 j(1≤j<i),则有以下两种方案:

  • 将 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多个正整数,此时的乘积是 j×(i−j);
  • 将 i 拆分成 j 和 i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j×dp[i−j]。

因此状态转移方程为:dp[i]=max(j×(i−j),j×dp[i−j])

class Solution {
    public int cuttingRope(int n) {
        int[] dp = new int[n + 1];
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j < i; j++) {
                dp[i]= Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
            }
        }
        return dp[n];
    }
}

贪心算法

核心思路:尽可能把绳子分成长度为3的小段,这样乘积最大

  • 1.如果 n == 2,返回1,如果 n == 3,返回2,两个可以合并成n小于4的时候返回n - 1
  • 2.如果 n == 4,返回4
  • 3.如果 n > 4,分成尽可能多的长度为3的小段,每次循环长度n减去3,乘积res乘以3;最后返回时乘以小于等于4的最后一小段

证明:原文地址

class Solution {
    public int cuttingRope(int n) {
		if(n < 4) return n - 1;
		int res = 1;
		while(n > 4) {
			n -= 3;
			res *= 3;
		}
		return res * n;
    }
}

—————————————————————————————

面试题15.二进制中1的个数

在这里插入图片描述

方法一:逐位判断

  • 根据与运算定义,设二进制数字 n,则有:

    • 若 n&1 = 0,则 n 二进制最右一位为 0
    • 若 n&1 = 1,则 n 二进制最右一位为 1
  • 作以下循环判断:

    • 1.判断 n 最右一位是否为1,根据结果计数
    • 2.将 n 右移一位
public class Solution {
    public int hammingWeight(int n) {
        int count = 0;
        while(n != 0){
            count += n & 1;
            n = n >>> 1;
        }
        return count;
    }
}

优化:另一种代码

当输入为负数时,这种代码也可以正常运行

public class Solution {
    public int hammingWeight(int n) {
        int count = 0;
        for (int i = 0; i < 32; i++) {
        if ((n & (1 << i)) != 0) {
            count++;
        }
        return count;
    }
}

方法二:巧用 n&(n-1)

  • (n-1)解析:二进制数字 n 最右边的 1 变成 0,这个 1 右边的 0 都变成 1;
  • n&(n-1)解析:二进制数字 n 最右边的 1 变成 0,其余不变。

在这里插入图片描述
从以上可知:一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。

在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述

public class Solution {
    public int hammingWeight(int n) {
        int count = 0;
        while(n != 0) {
        	count++;
        	n = (n & (n - 1));
        }
        return count;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值