【好书推荐】《剑指Offer》之硬技能(编程题12~16)
本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sword
《【好书推荐】《剑指Offer》之硬技能(编程题7~11)》
持续更新,敬请关注公众号:coderbuff,回复关键字“sword”获取相关电子书。
12.矩阵中的路径
*回溯法:适合由多个步骤组成的问题,并且每个步骤有多个选项。
/**
* 矩阵中是否存在给定路径
* @author OKevin
* @date 2019/6/4
**/
public class Solution { /**
*
* @param matrix 一位数组表示矩阵
* @param rows 行数
* @param cols 列数
* @param path 路径
* @return true-存在;false-不存在
*/
public boolean findPath(char[] matrix, Integer rows, Integer cols, char[] path) {
if (matrix == null || rows <= 0 || cols <= 0 || path == null) {
return false;
}
boolean[] visited = new boolean[rows * cols];
int pathLength = 0;
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (findPathCore(matrix, rows, cols, row, col, path, pathLength, visited)) {
return true;
}
}
}
return false;
} private boolean findPathCore(char[] matrix, Integer rows, Integer cols, int row, int col, char[] path, int pathLength, boolean[] visited) {
if (pathLength == path.length) {
return true;
}
if (row >= 0 && row < rows && col >= 0 && col < cols && matrix[row * cols + col] == path[pathLength] && !visited[row * cols + col]) {
visited[row * cols + col] = true;
pathLength++;
if (findPathCore(matrix, rows, cols, row, col - 1, path, pathLength, visited)
|| findPathCore(matrix, rows, cols, row - 1, col, path, pathLength, visited)
|| findPathCore(matrix, rows, cols, row, col + 1, path, pathLength, visited)
|| findPathCore(matrix, rows, cols, row + 1, col, path, pathLength, visited)) {
return true;
}
visited[row * cols + col] = false; }
return false;
}
}
13.机器人的运动范围
此题有一个小的点需要靠平时的积累,数位和的计算。
/**
* 计算数位和
* 例如:85的数位和为8+5=13
* 计算过程:
* 85 % 10 = 5(个位)
* 85 / 10 = 8(移除个位)
* 8 % 10 = 8(十位)
* 5 + 8 = 13
* @param number 数字
* @return 数位和
*/
private int getDigitSum(int number) {
int sum = 0;
while (number > 0) {
sum += number % 10;
number /= 10;
}
return sum;
}
另外还需要注意几个临界条件:
访问的行和列一定是大于等于0;
访问的行和列一定是小于总行数和总列数(并不是小于等于,因为是从第0行开始)
行和列的数位和小于阈值
没有被访问过
row >= 0 && row < rows && col >= 0 && col < cols && (getDigitSum(row) + getDigitSum(col) < threshold) && !visited[row * cols + col]
题目中看似提到了m行n列,立马想到了用二维数字来表示。实际上如果用二维数组是增加了复杂性,用一维数组同样能表示出二维数组。例如:m行n列就一共又m*n个元素,visited[m*n]。访问第1行第1列,在一维数组中则为visited[1*m+1],访问第1行第2列则为visited[1*m+2],也就是在一位数组中,数据是按照一列一列存放的。如果要访问第2行是2*cols+第几列。
另外既然需要求出达到多少个格子,则是需要访问格子周围即:(i - 1, j)、(i, j - 1)、(i + 1, j)、(i, j + 1)。
/**
* Description:
* 机器人的运动范围
* 2019-06-18
* Created with OKevin.
*/
public class Solution {
public int movingCount(int threshold, int rows, int cols) {
if (threshold < 0 || rows <= 0 || cols <= 0) {
return 0;
}
boolean[] visited = new boolean[rows * cols];
int count = movingCountCore(threshold, rows, cols, 0, 0, visited);
return count;
} private int movingCountCore(int threshold, int rows, int cols, int row, int col, boolean[] visited) {
int count = 0;
if (check(threshold, rows, cols, row, col, visited)) {
visited[row * cols + col] = true;
/**
* 当前访问到了(i, j)坐标,此时则继续访问(i - 1, j)、(i, j - 1)、(i + 1, j)、(i, j + 1)
*/
count = 1 + movingCountCore(threshold, rows, cols, row - 1, col, visited) + movingCountCore(threshold, rows, cols, row, col-1, visited) + movingCountCore(threshold, rows, cols, row + 1, col, visited) + movingCountCore(threshold, rows, cols, row + 1, col, visited);
}
return count;
} private boolean check(int threshold, int rows, int cols, int row, int col, boolean[] visited) {
//横坐标与纵坐标的数位和相加小于阈值,且没有访问过
if (row >= 0 && row < rows && col >= 0 && col < cols && (getDigitSum(row) + getDigitSum(col) <= threshold) && !visited[row * cols + col]) {
return true;
}
return false;
} /**
* 计算数位和
* 例如:85的数位和为8+5=13
* 计算过程:
* 85 % 10 = 5(个位)
* 85 / 10 = 8(移除个位)
* 8 % 10 = 8(十位)
* 5 + 8 = 13
* @param number 数字
* @return 数位和
*/
private int getDigitSum(int number) {
int sum = 0;
while (number > 0) {
sum += number % 10;
number /= 10;
} return sum;
}
}
14.剪绳子
这道题是求解最优化问题。理论上讲,在题目中出现最大、最小、一共有多少种解法都可以用动态规划求解。
解法一:动态规划
拿到这道题,习惯性的可能会先从由上往下的解题思路去想,比如:长度为9,可以分为几段:1,1,7;1,2,6等等。会去思考这个长度会分成几个段,再将每个段的乘积求出来,取最大的那个段。
但实际上,对于求解最优化问题,可以转换为一系列子问题。对于本题一段绳子来讲,它无论如何都至少被切为2段。例如长度为8时,可能被切为:1,7;2,6;3,5;4,4。当然还有5,3,这实际上又和前面重复了,所以一段绳子如果被切为2段,就只有n/2种可能性。
切为2段并不是最终的最大乘积长度,例如8切为了以上4种可能性的两段,并不意味着8的切成m段的最大乘积长度为15(3*5)。它当然还能切为2*3*3=18。那为什么说只需要切为2段呢?
这是因为我们需要把这个问题不断地划分为小的问题。
例如8被切为了1和7,这两段不能再继续切分,它就是最小的问题;同理,8被切为了2和6,但是6仍然可以继续被切为1和5,2和4,3和3,所以2和6并不是最小的问题,以此类推,最终推出长度为6的绳子切成m段的最大乘积是9(3*3),那么8被切为2和6时,2*9就等于18。同理继续推3和5,4和4。
上面的分析得出了什么样的结论呢?结论就是,只需要想象成2段,再各自继续切2段。也就是说假设长度为n的绳子,f(n)是它的各段最大乘积长度,它在被切第一刀时,第一段长度为(1,2,...n-1),第二段的长度为(n-1,n-2,...,1)。推出f(n)=max(f(i)*f(n-1))的关联关系。这里一定需要好好理解,切成2段后,并不是直接将两段相乘,而是再继续将各段切分直至不能再切且取最大乘积长度。
在《算法笔记》(刁瑞 谢妍著)一书中对动态规划做了求解步骤的总结:
定义子问题
定义状态转换规则,即递推关系
定义初始状态
套用到这套题上,我认为就是需要明确以下3点:
该问题的核心在于求出每段的最大乘积长度,这是子问题,也就是上文所述,再被切为两段时,需要明确是否能继续切直至不能再切且取最大乘积长度。
递推关系,也已明确(n)=max(f(i)*f(n-1))
初始状态,长度为1不能切,长度为2最长为1,长度为3最长为2。
/**
* Description:
* 剪绳子——动态规划
* 2019-06-19
* Created with OKevin.
*/
public class Solution1 { public int maxProductAfterCutting(int length) {
if (length < 2) {
return 0;
}
if (length == 2) {
return 1;
}
if (length == 3) {
return 2;
}
int[] products = new int[length + 1]; //数组中存储的是每段的最优解
//大于长度3的绳子,当然可以划分出1,2,3长度的绳子
products[0] = 0;
products[1] = 1;
products[2] = 2;
products[3] = 3;
int max = 0;
for (int i = 4; i <= length; i++) {
max = 0;
for (int j = 1; j <= i / 2; j++) { //除以2的原因在上文中也以提到,将一段绳子划分为2段时,实际上中间后的切分和前面是重复的
int product = products[j] * products[i - j]; //递推关系f(i)*f(n-1)
if (max < product) {
max = product;
}
products[i] = max;
}
}
max = products[length];
return max;
}
}
优点:动态规划类似于分治算法,将大的问题逐步划分为小的问题求解。
缺点:此题采用动态规划的时间复杂度为O(n^2),且空间复杂度为O(n)
解法二:贪婪算法
贪婪算法的核心是,先挑最大的,再挑比较大的,再挑小的(贪婪嘛)。
本题对于长度为n(n>=5)的绳子应尽量多划分为长度3的段。对于长度为4的段,应划分为长度为2的段。
也即是,如果长度为10,那么10/3=3个长度为3的段,划分结果为3*3*3*1,最后一个段为1,划分为3*3*4。
/**
* Description:
* 剪绳子——贪婪算法
* 2019-06-20
* Created with OKevin.
*/
public class Solution2 {
public int maxProductAfterCutting(int length) {
if (length < 2) {
return 0;
}
if (length == 2) {
return 1;
}
if (length == 3) {
return 2;
}
int timesOf3 = length / 3;
if (length - timesOf3 * 3 == 1) {
timesOf3 -= 1;
}
int timesOf2 = (length - timesOf3*3) / 2;
return (int) (Math.pow(3, timesOf3) * Math.pow(2, timesOf2));
}
}
15.二进制中1的个数
此题可采用移位运算+与运算求解
/**
* Description:
* 移位运算+与运算
* 2019-06-20
* Created with OKevin.
*/
public class Solution {
public int NumberOf1(int num) {
int count = 0;
while (num != 0) {
if ((num & 1) == 1) {
count++;
}
num = num >>> 1; //因为运算>>>表示无符号右移,意味着如果是负数,仍然会向右移,同时用0补齐。如果使用>>有符号右移,那么符号位1永远会存在,也就是会产生死循环
}
return count;
}
}
16.数值的整数次方
循环暴力法
/**
* Description:
* 循环暴力法
* 2019-06-20
* Created with OKevin.
*/
public class Solution1 {
public int pow(int m, int n) {
int result = 1;
for (int i = 0; i < n; i++) {
result *= m;
}
return result;
}
}
很遗憾,这种解法连校招级都算不上,顶多算是刚学习编程时的水平。
其实这道题,并没有考查过多的算法,更多的是考查对细节的把握。一个数的整数次方,不光是整数,还有可能是负数,也有可能是0。如果数值为0,则0的幂是没有意义的。
/**
* Description:
* 考虑指数为0,负数,整数;数值为0的情况;0^0在数学上没有意义
* 2019-06-21
* Created with OKevin.
*/
public class Solution2 { public double pow(int m, int n) {
double result = 0;
if (m == 0 && n < 0) {
return -1;
}
int absN = Math.abs(n); //取绝对值
result = calc(m, absN);
if (n < 0) {
result = 1 / result;
}
return result;
} private int calc(int m, int n) {
int result = 1;
for (int i = 0; i < n; i++) {
result *= m;
}
return result;
}
}
改进后的代码考虑到了指数是负数的情况。但实际上这仍然有优化的空间。如果指数是32,意味着calc方法需要循环31次。然而实际上循环到一半的时候就可以求它本身。也就是说a^n/2 * a^n/2,n为偶数;a^(n-1)/2 * a^(n-1)/2 * a,n为奇数。
改进后的calc方法:
private int calc(int m, int n) {
if (n == 0) {
return 1;
}
if (n == 1) {
return m;
}
int result = calc(m, n >> 1); //右移1位表示除以2
result *= result;
if ((m & 1) == 1) { //位运算判断是会否为奇数,奇数的二进制第一位一定是1与1做与运算即可判断是否为奇数,代替m%2是否等于0
result *= m;
}
return result;
}
本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/sword
《【好书推荐】《剑指Offer》之硬技能(编程题7~11)》
持续更新,敬请关注公众号:coderbuff,回复关键字“sword”获取相关电子书。
这是一个能给程序员加buff的公众号 (CoderBuff)
【好书推荐】《剑指Offer》之硬技能(编程题12~16)的更多相关文章
- 剑指offer(41-45)编程题
41.入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的. class Solution { public: vector&l ...
- 剑指offer(36-40)编程题
两个链表的第一个公共结点 数字在排序数组中出现的次数 二叉树的深度 平衡二叉树 数组中只出现一次的数字 36.输入两个链表,找出它们的第一个公共结点. class Solution1 { public ...
- (4)剑指Offer之链表相关编程题
一 链表中倒数第k个节点 题目描述: 输入一个链表,输出该链表中倒数第k个结点 问题分析: 一句话概括: 两个指针一个指针p1先开始跑,指针p1跑到k-1个节点后,另一个节点p2开始跑,当p1跑到最后 ...
- 剑指offer——python【第54题】字符流中第一个不重复的字符
题目描述 请实现一个函数用来找出字符流中第一个只出现一次的字符.例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g".当从该字符流中读出 ...
- 剑指offer——python【第21题】栈的压入、弹出序列
题目描述 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压 ...
- 《剑指offer》第十九题(正则表达式匹配)
// 面试题19:正则表达式匹配 // 题目:请实现一个函数用来匹配包含'.'和'*'的正则表达式.模式中的字符'.' // 表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次).在本题 ...
- 剑指offer——python【第59题】按之子形顺序打印二叉树
题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. 解题思路 这道题其实是分层打印二叉树的进阶版 ...
- 剑指offer——python【第60题】把二叉树打印成多行
题目描述 从上到下按层打印二叉树,同一层结点从左至右输出.每一层输出一行.#类似于二维列表[[1,2],[4,5]] 解题思路 其实这倒题和其他类似的题有所区别,这里是分层打印,把每层的节点值放在同一 ...
- 剑指offer——python【第39题】平衡二叉树
题目描述 输入一棵二叉树,判断该二叉树是否是平衡二叉树. 解题思路 平衡二叉树首先是二叉搜索树,且它每个节点的左子树和右子树高度差至多等于1:只要从根节点,依次递归判断每个节点是否满足如上条件即可 ...
随机推荐
- 在Tinymce编辑器里,集成数学公式
在以前,需要在Web页面显示数学公式,常用的都是先制作成图片,然后插入到页面里.这使得后期对数学公式的修改变的麻烦,同时也不利于搜索引擎搜索. 本文将介绍如何在TinyMce编辑器里集成数学公式.先看 ...
- python中for循环删除不全的问题
以前遇到过一次,删除列表中符合条件的元素,for循环挨个判断是否符合条件,符合就删除,删完结果发现有一个符合条件的没有删掉. 那么如果想删除某些列表中的元素,比如有一个a列表,a=[11,22,33, ...
- 使用Python进行防病毒免杀
很多渗透工具都提供了权限维持的能力,如Metasploit.Empire和Cobalt Strike,但是都会被防病毒软件检测到这种恶意行为.在探讨一个权限维持技巧的时候,似乎越来越多的人关注的是,这 ...
- 自然语言处理(NLP) - 数学基础(1) - 排列组合
正如我在<自然语言处理(NLP) - 数学基础(1) - 总述>一文中所提到的NLP所关联的概率论(Probability Theory)知识点是如此的多, 饭只能一口一口地吃了, 我们先 ...
- numpy的基本API(二)——维数操作
numpy的基本维数操作API iwehdio的博客园:https://www.cnblogs.com/iwehdio/ 1.np.copyto(dst, src) copyto方法将数组src复制到 ...
- WSL(Windows Subsystem for Linux) Ubuntu 下byobu状态栏错误的问题
关于WSL的,Win10 的Linux子系统如何安装,就不赘述了,Win10商店里就有,至于win7和win8.1想装这个估计也不行,所以跳过. 最近处于好奇,也懒得弄VMware的虚拟机(那玩意儿占 ...
- 网络安全之Windows基础
1.黑客常用DOS命令 基础: telnet服务:telnet 192.168.1.141 (默认没有打开telnet服务) 常用: color a ping -t -l 65550 ip 死亡之pi ...
- 冒泡排序 C&&C++
冒泡排序(因为过程像冒泡,所以叫做冒泡排序) 流程: (1)对数组中各个数字,一次比较相邻两个 (2)如果前面大于后面,就交换这两个数据 (3)再用同样的方法继续排,直到外层循环排完 或者 (1) ...
- 一线大厂Java面试必问的2大类Tomcat调优
一.前言 最近整理了 Tomcat 调优这块,基本上面试必问,于是就花了点时间去搜集一下 Tomcat 调优都调了些什么,先记录一下调优手段,更多详细的原理和实现以后用到时候再来补充记录,下面就来介绍 ...
- NodeJS4-4静态资源服务器实战_优化引入模板引擎
引入模板引擎(handlebars) cnpm i handlebars 结构大概是这样子的,新建模板dir.tpl文件和route.js dir.tpl <!DOCTYPE html> ...