微风需要竹林溪流需要蜻蜓
剑指 Offer 12. 矩阵中的路径
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
思路:原理是采用深度优先遍历的思想,从二维数组的各个元素中进行分别的遍历,在遍历过程中,首先,如果遇到不符合情况,return false;反之 判断是否 二维字符网格符合的长度等于word的长度,如果符合条件 return ture,都不符合递归结束的条件,将二维数组的该元素设置为空,以防重复调用,继续迭代朝着该元素的四个方向, 在迭代结束后,恢复该数组的元素,return Boolean;
/**
* @author shkstart
* @create 2022-07-15 下午1:20
* 矩阵中的路径问题
* 采用dfs深度优先搜索的方法来解决
* 递归结束的条件,然后在深层次进一步递归
* 原理上,判断是否符合两个条件
*/
public class offer_12 {
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++) {
if (dfs(board,words,i,j,0)) return true;
}
}
return false;
}
public boolean dfs(char[][] board,char[] words,int i,int j,int k){
//递归结束的条件:越界,然后与对应数组不对应
if (i<0||i> board.length-1||j<0||j>board[0].length-1||board[i][j]!=words[k]){
return false;
}
//递归结束成功的条件:不满足上述问题后+k匹配完成
if (k==words.length-1) return true;
//在使用完该矩阵的元素后为避免重复使用,设置为‘/0’
board[i][j]='\0';
//四个条件只要满足一个,就能成功
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+1,j+1,k+1);
board[i][j]=words[k];//递归dfs之后在恢复该元素的位置
return res;
}
面试题13. 机器人的运动范围
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0]的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格
[35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
思路:本题主要思想为判断机器人到达格子的数量,首先面对该类问题,依然采用递归的思路来进行处理,考虑到dfs 和bfs 这两类,在该问题中,考虑到一个位数和的问题,建议先写一个函数计算位数he,然后在主函数进行调用!然后设置一个visit数组来判断元素是否被访问过,dfs 递归过程中,设置一个循环出去的一个条件,比如越界,位数和不符合等!!接下来 将该元素设置为已访问,最后返回1+dfs(),进行递归处理!!
bfs的思想类似,设置一个队列用来装二维数组的下标,通过队列的进出,进行矩阵的遍历,在出队列的元素中进行处理,判断是否符合元素的要求!!设置一个全局变量 进行++;最后返回该变量!!
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
/**
* @author shkstart
* @create 2022-07-15 下午2:22
* 机器人的运动范围:依旧采用DFS或者BFS的策略,将每一步拆分开来,判断增加的位数和,然后进行判断是否满足条件
* 首先位数和小于给定的 K;其次要保证没有访问过,最后还不能越界
*作为一个机器人,如果进行运动的
*/
public class offer_13 {
int m,n,k;
boolean[][] visit;
//计算位数和!!!!
public int bitSum(int m){
int sum=0;
while (m!=0){
sum=sum+m%10;
m=m/10;
}
return sum;}
public int movingCount1(int m, int n, int k) {
this.m = m;
this.n = n;
this.k = k;
this.visit = new boolean[m][n];
//从左上角开始进行搜索
return dfs(0, 0);
}
public int dfs(int i,int j)
{
//不满足条件的话直接返回0,称为预剪枝
if (i>m-1 ||i<0 ||j>n-1||j<0||bitSum(i)+bitSum(j)>k ||visit[i][j]) return 0;
//满足上述条件,则可以访问 设置visit矩阵为TRUE,且进行+1;然后依次递归
visit[i][j]=true;
return 1+dfs(i+1,j)+dfs(i,j+1);
}
//采用BFS的策略;使用队列来进行依次访问
public int movingCount2(int m,int n,int k){
int res=0;//初始数量
boolean[][] visited=new boolean[m][n];
Queue<int[]> queue=new LinkedList<int[]>() ;//队列方法
queue.add(new int[]{0,0});//使用数组来作为队列元素
while (!queue.isEmpty()) {
int[] x = queue.poll();//出栈
int i = x[0], j = x[1]; //获取对应元素
if (i > m - 1 || i < 0 || j > n - 1 || j < 0 || bitSum(i) + bitSum(j) > k || visit[i][j]) continue;
visited[i][j] = true;
res++;//f符合条件
queue.add(new int[]{i + 1, j});//继续入栈
queue.add(new int[]{i, j + 1});
}
return res;
}
}
剑指 Offer 14- I. 剪绳子
*给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
思路:主要分为两个方法,第一个是贪心法,首先可以证明出将绳子分为长度为三的段,所得乘积是最大的,因此尽量可能将绳子分为长度为3的段,当然 4的时候除外;;;第二个方法为动态规划,考虑到绳子的最大程度积是与前者存在关系的!!!因此建立动态规划数组,dp[] 在数组的生成过程中,两个for循环,第一个控制绳子的长度,第二控制绳子在改长度下取得范围,以便求得最大值;
/**
* @author shkstart
* @create 2022-07-15 下午4:15
* 剪绳子问题
* 最大长度问题
* m,n
*/
public class offer_14_1 {
public int cuttingRope1(int n) {
if (n == 2) return 1;
if (n == 3) return 2;
if (n == 4) return 4;
if (n == 5) return 6;
if (n == 6) return 9;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
dp[2] = 1;
dp[3] = 2;
dp[4] = 4;
dp[5] = 6;
dp[6] = 9;
for (int i = 7; i <= n; i++) {
dp[i] = dp[i - 2] * 2 > dp[i - 3] * 3 ? dp[i - 2] * 2 : dp[i - 3] * 3;
}
return dp[n];
}
public int cuttingRope2(int n) {
int[] dp = new int[n + 1];
dp[2] = 1;
for (int i = 3; i <= n; i++) {
for (int j = 1; j < i - 1; j++) {
// dp[i]=dp[i-2]*2>dp[i-3]*3?dp[i-2]*2:dp[i-3]*3;
//第一个为按照之前的最优解进行想×,第二个是将绳子分为两部分(i-j)与j
dp[i] = Math.max(dp[i], Math.max(dp[i - j] * j, (i - j) * j));
}
}
return dp[n];
}
//贪心的思想:首先证明了绳子分为长度为三的段获得的总乘积最大
//然后在 绳子 剩余1时, 拆3 补1 可以使得 2*2>3*1,取余防止溢出
public int cuttingRope3(int n) {
long res=1;
if (n<=3) return n-1;
if (n%3==1) {res=res*4;n=n-4;}
if (n%3==2) {res=res*2;n=n-2;}
while (n!=0){res=res*3%1000000007;n=n-3;}
return (int)res;
}
}
剑指 Offer 14- II. 剪绳子 II
*给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
思路:与上题相同,注意取模!!!
剑指 Offer 15. 二进制中1的个数
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为 汉明重量).)。
思路:采用异或的方式,对数字上的每位进行统计
/**
* @author shkstart
* @create 2022-07-15 下午8:48
* 二进制中1的个数
* 注意异或运算的含金量
*/
public class offer_15 {
public int hammingWeight(int n) {
int res = 0;
while(n != 0) {
res += n & 1;
n >>>= 1;
}
return res;
}
剑指 Offer 16. 数值的整数次方
实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。
思路:采用快速幂的方式,考虑将n 与1进行异或,然后判断结果,并将n进行右移,同时 x=x^2;将x进行累乘;
/**
* @author shkstart
* @create 2022-07-15 下午8:55
* 整数幂问题
* 使用快速幂的方法来进行幂的求解,可提高速度
*/
public class offer_16 {
public double myPow(double x, int n) {
double sum = 1;
//使用long型,防止溢出
long b=n;
if (x == 0) return 0;
if (b == 0) return 1.0;
if (b < 0) {
x = 1 / x;
b = -b;
}
while (b != 0) {
if ((b & 1) == 1) sum *= x;//和1取交
x = x * x;
b >>= 1;
}
return sum;
}
}
剑指 Offer 17. 打印从1到最大的n位数
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
public int[] printNumbers(int n) {
int[] nums=new int[(int) Math.pow(10,n)-1];
for (int i = 0; i < Math.pow(10, n)-1; i++) {
nums[i]=i+1;
}
return nums;
剑指 Offer 18. 删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
思路:一个简单的链表节点删除问题,当设置两节点,依次进行遍历,找到相应节点后,在进行前节点的next指向后节点的next;删除该节点
/**
* @author shkstart
* @create 2022-07-15 下午9:50
* 删除链表节点问题
*/
public class offer_18 {
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
public ListNode deleteNode(ListNode head, int val) {
if (head==null) return null;
ListNode temp1=head;
ListNode temp2=head.next;
if (temp1.val==val){head=head.next;return head;}
while(temp2!=null){
if (temp2.val==val){temp1.next=temp2.next;}
temp1=temp1.next;
temp2=temp2.next;
}
return head;
剑指 Offer 20. 表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
数值(按顺序)可以分成以下几个部分:
若干空格
一个 小数 或者 整数
(可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个 整数若干空格
小数(按顺序)可以分成以下几个部分:
(可选)一个符号字符(‘+’ 或 ‘-’)
下述格式之一:
至少一位数字,后面跟着一个点 ‘.’
至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
一个点 ‘.’ ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:
(可选)一个符号字符(‘+’ 或 ‘-’)至少一位数字
部分数值列举如下:
[“+100”, “5e2”, “-123”, “3.1416”, “-1E-16”, “0123”]
部分非数值列举如下:
[“12e”, “1a3.14”, “1.2.3”, “±5”, “12e+5.4”]
思路: * 判断一个字符串是否可以表示为数值
- 一个一个情况进行排查,首先要定义好布尔变量,然后依次遍历数组,进行相应的判断
- 数组为数字,数组为小数点。数组为e/E,数组为加减号
- 考虑到出现小数点后不能有小数点,e之后不能出现e,±只能出现在第一个或者e之后;
/**
* @author shkstart
* @create 2022-07-16 下午4:21
* 判断一个字符串是否可以表示为数值
*
* 一个一个情况进行排查,首先要定义好布尔变量,然后依次遍历数组,进行相应的判断
* 数组为数字,数组为小数点。数组为e/E,数组为加减号
*/
public class offer_20 {
public boolean isNumber1(String s) {
if (s.isEmpty()||s.length()==0) return false;
char[] nums=s.trim().toCharArray();
// if (nums.length==1&&nums[0]=='E'||nums[0]=='e'){return true;}
boolean isnums=false,isEore=false,isdot=false;//设置变量
for (int i = 0; i < nums.length; i++) {
if (nums[i] >= '0' && nums[i] <= '9') {
//判断为数字
isnums = true;
} else if (nums[i] == '.') { //小数点的情况
if (isdot || isEore) return false;//如果出现小数点或者前面不曾有过E或者e
isdot = true;//标记出现过小数点
} else if (nums[i] == 'e' || nums[i] == 'E') {//e 或者E的情况
if (isEore == true || !isnums) return false;//曾经出现过e,或者前面不曾出现过数字
isEore = true;
isnums = false; //对变量进行标记,表明下一次需要数字出现
} else if (nums[i] == '+' || nums[i] == '-') {
if (i != 0 && nums[i - 1] != 'E' && nums[i - 1] != 'e') {
return false;}
//+-号不在数组第一个且前面的数组中的元素不为E
} else return false;
}
return isnums;
}
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。
*调节数组的顺序,使得奇数在前,偶数在后边
- 快排思想:定义两个指针,从前往后,从后往前,进行遍历
/**
* @author shkstart
* @create 2022-07-17 上午10:06
*调节数组的顺序,使得奇数在前,偶数在后边
* 快排思想:定义两个指针,从前往后,从后往前,进行遍历
*/
public class offer_21 {
public int[] exchange(int[] nums) {
int i=0,j=nums.length-1,temp = 0;
while (i<=j){
if (nums[i]%2==0&&nums[j]%2!=0) {
temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
i++;
j--;
}
if (nums[i]%2!=0) i++;
if (nums[j]%2==0) j--;
}
return nums;
}
}
剑指 Offer 22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
第一次去判断链表节点的个数,然后根据节点个数来判断length-k的节点所在
public ListNode getKthFromEnd(ListNode head, int k) {
int length = 0;
ListNode node1 = head;
while (node1 != null) {
node1 = node1.next;
length++;
}
if (length < k) return null;
while (length - k != 0) {
head = head.next;
k++;
}
return head;
}
剑指 Offer 24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
思路:有两种方法,一个是双指针,一个是递归的方法;
递归:考虑到在反转n-1链表的情况下,将head.next的下一个节点指向head,将head.next指向null; 递归既是在考虑到递归结果处理完成的情况下进行处理的过程
双指针,设计两个指针,分别对每个节点进行反转,并有另外一个变量表示下一节点,依次往下走;
public ListNode reverseList1(ListNode head) {
ListNode cur=head,pre=null;
while (cur !=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
public ListNode reverseList2(ListNode head) {
//递归思路:在完成条件一的基础上进行假设,进行下一步的操作
if (head == null || head.next == null) return head;
ListNode node = reverseList2(head.next);
head.next.next = head;
head.next = null;
return node;
}
}
剑指 Offer 25. 合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
思路:首先创建一个新节点,然后进行两个链表节点大小的遍历,遍历后,将小的节点连接到新节点后,之后next;当遍历结束后,看哪个链表节点不空,进行连接;
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head=new ListNode(0),temp=head;//新创造一个头结点,然后使用指针来指向
//伪节点的形式,依次进行比较合适的连入表中
while (l2!=null &&l1!=null){
if (l1.val>= l2.val){
temp.next=l2;l2=l2.next;temp=temp.next;
}else {
temp.next=l1;l1=l1.next;temp=temp.next;
}
}
//看是否有剩余,有剩余的话直接连上
temp.next= l1!=null?l1:l2; //优化
//while (l1!=null){ temp.next=l1;l1=l1.next;temp=temp.next;}
//while (l2!=null){ temp.next=l2;l2=l2.next;temp=temp.next;}
// head=head.next;
return head.next;
}