剑指offer题解篇
个人理解JAVA 第一次初筛有些题可能做的不是很完美
不过是自己理解的算法
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
public class Solution {
public boolean Find(int target, int [][] array) {
boolean found = false;
int lie = array[0].length;
int hang = array.length;
int column = lie -1;
int row =0;
while(row
int value = array[row][column];
if(target>value){
row++;
}else if(value>target){
column–;
}else{
found = true;
break;
}
}
return found;
}
}
2. 请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
public class Solution {
public String replaceSpace(StringBuffer str) {
if(strnull){
return null;
}
StringBuilder newStr = new StringBuilder();
for(int i=0;i
newStr.append(’%’);
newStr.append(‘2’);
newStr.append(‘0’);
}else{
newStr.append(str.charAt(i));
}
}
return newStr.toString();
}
}
3. 输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
import java.util.ArrayList;
public class Solution {
ArrayList arrayList=new ArrayList();
public ArrayList printListFromTailToHead(ListNode listNode) {
if(listNode!=null){
printListFromTailToHead(listNode.next);
arrayList.add(listNode.val);
}
return arrayList;
}
}
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if (pre.length == 0 || in.length == 0) {
return null;
}
TreeNode root = new TreeNode(pre[0]);
// 在中序中找到前序的根
for (int i = 0; i < in.length; i++) {
if (in[i] == pre[0]) {
// 左子树,注意 copyOfRange 函数,左闭右开
root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(in, 0, i));
// 右子树,注意 copyOfRange 函数,左闭右开
root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(in, i + 1, in.length));
break;
}
}
return root;
}
}
5. 用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
public class Solution {
Stack stack1 = new Stack();
Stack stack2 = new Stack();
public void push(int node) {
stack1.push(node);
}
public int pop() {
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
int first=stack2.pop();
while(!stack2.isEmpty()){
stack1.push(stack2.pop());
}
return first;
}
}
6. 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
链接:https://www.nowcoder.com/questionTerminal/9f3231a991af4f55b95579b44b7a01ba?answerType=1&f=discussion
来源:牛客网
方法二:二分查找
这种二分查找难就难在,arr[mid]跟谁比.
我们的目的是:当进行一次比较时,一定能够确定答案在mid的某一侧。一次比较为 arr[mid]跟谁比的问题。
一般的比较原则有:
• 如果有目标值target,那么直接让arr[mid] 和 target 比较即可。
• 如果没有目标值,一般可以考虑 端点
这里我们把target 看作是右端点,来进行分析,那就要分析以下三种情况,看是否可以达到上述的目标。
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
int low = 0 ;
int high = array.length - 1;
while(low < high){
int mid = low + (high - low) / 2;
if(array[mid] > array[high]){
low = mid + 1;
}else if(array[mid] == array[high]){
high = high - 1;
}else{
high= mid;
}
}
return array[low];
}
}
斐波那契数列的标准公式为:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
根据公式可以直接写出:
public class Solution {
public int Fibonacci(int n) {
if(n<=1){
return n;
}
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
8. 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
这是一道经典的递推题目,你可以想如果青蛙当前在第n级台阶上,那它上一步是在哪里呢?
显然,由于它可以跳1级台阶或者2级台阶,所以它上一步必定在第n-1,或者第n-2级台阶,也就是说它跳上n级台阶的跳法数是跳上n-1和跳上n-2级台阶的跳法数之和。
设跳上 级台阶有 种跳法,则它跳上n级的台阶有 种跳法。
然后,我们又思考初始( )的情况,跳上1级台阶只有1种跳法,跳上2级台阶有2种跳法,最终我们得到如下的递推式:
这个递推式和菲波那切数列比较相似
public class Solution {
public int JumpFloor(int target) {
if ( target== 1) {
return 1;
} else if (target == 2){
return 2;
}else{
return JumpFloor(target- 1) + JumpFloor(target- 2);
}
}
}
9. 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
还是用递归
首先发现规律
因为n级台阶,第一步有n种跳法:跳1级、跳2级、到跳n级
跳1级,剩下n-1级,则剩下跳法是f(n-1)
跳2级,剩下n-2级,则剩下跳法是f(n-2)
所以f(n)=f(n-1)+f(n-2)+…+f(1)
因为f(n-1)=f(n-2)+f(n-3)+…+f(1)
所以f(n)=2f(n-1)
代码:
public class Solution {
public int JumpFloorII(int target) {
if ( target== 0) {
return0 ;
} else if (target == 1){
return 1;
}else{
return 2JumpFloorII(target- 1) ;
}
}
}
10.我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2n的大矩形,总共有多少种方法?
比如n=3时,23的矩形块有3种覆盖方法:
链接:https://www.nowcoder.com/questionTerminal/72a5a919508a4251859fb2cfb987a0e6?answerType=1&f=discussion
来源:牛客网
方法一:递推
对于这种题没有思路怎么办?
那就对n 从小到大,一步步分析:
n=1时,显然只有一种方法
n=2时,如图有2种方法
n=3,如图有3中方法
n=4,如图有5种方法。
如果到这里,还没有发现规律怎么办呢?
那我们就再分析以下,从n=3到n=4,怎么来的呢?
这里有2种情况:
• 直接在n=3的情况下,再后面中添加一个竖着的。这个很显然成立,有3种情况
• 然后横着的显然能添加到n-2的情况上,也就是在n=2后面,添加2个横着的。有2种情况
通过以上分析,发现刚好和图中的个数一样。
所以总结:f [n]表示2*n大矩阵 的方法数。
可以得出:f[n] = f[n-1] + f[n-2],初始条件f[1] = 1, f[2] =2
所以代码可用递归
public class Solution {
public int RectCover(int target) {
if (target < 1) { //target=1这么写代码通不过去 不明觉厉
return 0;
} else if (target == 1 || target == 2) {
return target;
} else {
return RectCover(target-1) + RectCover(target-2);
}
}
}
11. 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
代码:public class Solution {
public int NumberOf1(int n) {
int count=0;
while(n!=0){
count++;
n=n&(n-1);
}
return count;
}
}
12. 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0
public class Solution {
public double Power(double base, int exponent) {
if (base == 0.0){
return 0.0;
}
//保证base和exponent不同时为0
// 前置结果设为1.0,即当exponent=0 的时候,就是这个结果
double result = 1.0d;
// 获取指数的绝对值
int e = exponent > 0 ? exponent : -exponent;
// 根据指数大小,循环累乘
for(int i = 1 ; i <= e; i ++){
result *= base;
}
// 根据指数正负,返回结果
return exponent > 0 ? result : 1 / result;
}
}
13. 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
public class Solution {
public void reOrderArray(int [] array) {
int[] arrayOdd = new int[array.length];
int[] arrayEven = new int[array.length];
int counter = 0, counterOdd = 0, counterEven = 0;
int oddLength = 0, evenLength = 0;
while(counter
arrayEven[counterEven] = array[counter];
counterEven++;
evenLength++;
}else{//odd
arrayOdd[counterOdd] = array[counter];
counterOdd++;
oddLength++;
}
counter++;
}
for(int i=0;i
}
for(int i=0;i
}
}
}
public class Solution {
public void reOrderArray(int[] array) {
int len = array.length;
if (len <= 1) {
return;
}
int i = 0;
while (i < len) {
//如果i所指的元素是奇数,则继续前进
if (array[i] % 2 == 1) {
i++;
} else {
//当i遇到偶数停下时,j从i的后一位开始走
int j = i + 1;
//当j所指的元素也是偶数时,则j向后移动
while (array[j] % 2 == 0) {
//当j移到队尾,则说明i到队尾全是偶数,已满足题目的奇偶分离要求
if (j == len - 1) {
return;
}
j++;
}
//此时j为奇数,i为偶数,用temp保存array[j]的值
int temp = array[j];
//把i到j-1的元素往后移一位
while (j > i) {
array[j] = array[j - 1];
j–;
}
//把保存在temp中的原第j个元素的值赋给i,此时i就变成奇数了,并进入下个循环
array[i] = temp;
}
}
}
• } 1.用两个下标i,j进行遍历;
• 2.当i走到偶数时停下,并让j从i的后一个元素开始遍历;(若i走到队尾则循环结束)
• 3.若j所指的是偶数则继续前进,j遇到奇数则停下(如果j都没遇到奇数则在队尾停下,结束。)。
• 4.此时j所指的是奇数,i所指的是偶数(i到j-1都是偶数)。
• 5.则可以用临时变量temp保存j对应的值,然后从j-1开始到i,挨个后移一位。
• 6.将temp保存的值插入到i的位置。
冒泡排序
public class Solution {
public void reOrderArray(int [] array) {
boolean changed = true;
for (int i=0;i
for (int j=0;j
if ((array[j]&1)==0 && (array[j+1]&1)==1){
changed = true;
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
}
}
14. 输入一个链表,输出该链表中倒数第k个结点。
使用如图的快慢指针,首先让快指针先行k步,然后让快慢指针每次同行一步,直到快指针指向空节点,慢指针就是倒数第K个节点。
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if(head == null || k 0 ){
return null;
}
ListNode slow=head;
ListNode fast=head;
for(int i=0;i
return null;
}
fast=fast.next;
}
while(fast!=null){
slow=slow.next;
fast=fast.next;
}
return slow;
}
}
15. 输入一个链表,反转链表后,输出新链表的表头。
• 以head结点为例步骤如下:
• 1.反转后head是指向null,所以未反转的时候其前一个结点应该是null,初始化pre指针为null;
• 2.用p指针记录head的下一个结点head.next;
• 3.从链表上摘下head,即让head.next指向pre;
• 4.此时已完成head结点的摘取及与前一个节点的连接,则我们需要操作下一个结点:故需移动pre和head,让pre指向head,head指向下一个节点。
• 重复这四个操作直到head走完原链表,指向null时,循环结束,返回pre。
public class Solution {
public ListNode ReverseList(ListNode head) {
//初始化pre指针,用于记录当前结点的前一个结点地址
ListNode pre = null;
//初始化p指针,用于记录当前结点的下一个结点地址
ListNode p = null;
//head指向null时,循环终止。
while(head != null){
//先用p指针记录当前结点的下一个结点地址。
p = head.next;
//让被当前结点与链表断开并指向前一个结点pre。
head.next = pre;
//pre指针指向当前结点
pre = head;
//head指向p(保存着原链表中head的下一个结点地址)
head = p;
}
return pre;//当循环结束时,pre所指的就是反转链表的头结点
}
}
16. 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
链接:https://www.nowcoder.com/questionTerminal/d8b6b4358f774294a89de2a6ac4d9337?answerType=1&f=discussion
来源:牛客网
• 如果l1指向的结点值小于等于l2指向的结点值,则将l1指向的结点值链接到cur的next指针,然后l1指向下一个结点值
• 否则,让l2指向下一个结点值
• 循环步骤1,2,直到l1或者l2为nullptr
• 将l1或者l2剩下的部分链接到cur的后面
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode h = new ListNode(-1);
ListNode cur = h;
while(list1 != null && list2 !=null){
if(list1.val<=list2.val){
cur.next = list1;
list1 = list1.next;
}else{
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
if(list1!=null) cur.next = list1;
if(list2!=null) cur.next = list2;
return h.next;
}
}
17. 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
链接:https://www.nowcoder.com/questionTerminal/6e196c44c7004d15b1610b9afca8bd88?answerType=1&f=discussion
来源:牛客网
题目抽象:给2棵树A,树B,判断B是否是A的子结构。
子结构定义:树A和树B的根结点相等,并且树A的左子树和树B的左子树相等,树A的右子树和树B的右子树相等
方法:递归求解
第一步:
根据题意可知,需要一个函数判断树A和树B是否有相同的结构。显然是个递归程序。可考察递归程序3部曲。
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
boolean result = false;
//当Tree1和Tree2都不为零的时候,才进行比较。否则直接返回false
if (root2 != null && root1 != null) {
//如果找到了对应Tree2的根节点的点
if(root1.val == root2.val){
//以这个根节点为为起点判断是否包含Tree2
result = doesTree1HaveTree2(root1,root2);
}
//如果找不到,那么就再去root的左儿子当作起点,去判断时候包含Tree2
if (!result) {
result = HasSubtree(root1.left,root2);
}
//如果还找不到,那么就再去root的右儿子当作起点,去判断时候包含Tree2
if (!result) {
result = HasSubtree(root1.right,root2);
}
}
//返回结果
return result;
}
public static boolean doesTree1HaveTree2(TreeNode node1, TreeNode node2) {
//如果Tree2已经遍历完了都能对应的上,返回true
if (node2 == null) {
return true;
}
//如果Tree2还没有遍历完,Tree1却遍历完了。返回false
if (node1 == null) {
return false;
}
//如果其中有一个点没有对应上,返回false
if (node1.val != node2.val) {
return false;
}
//如果根节点对应的上,那么就分别去子节点里面匹配
return doesTree1HaveTree2(node1.left,node2.left) && doesTree1HaveTree2(node1.right,node2.right);
}
}
18. 操作给定的二叉树,将其变换为源二叉树的镜像。
输入描述:
二叉树的镜像定义:源二叉树
8
/
6 10
/ \ /
5 7 9 11
镜像二叉树
8
/
10 6
/ \ /
11 9 7 5
操作给定的二叉树,将其变换为源二叉树的镜像。
思路:
• 交换左右子树
• 递归左右子树的镜像
public class Solution {
public void Mirror(TreeNode root) {
if(root == null){
return;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
Mirror(root.left);
Mirror(root.right);
}
}
19. 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
import java.util.ArrayList;
public class Solution {
public ArrayList printMatrix(int [][] matrix) {
ArrayList list = new ArrayList<>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return new ArrayList(0);
}
int rows = matrix.length, columns = matrix[0].length;
int[] order = new int[rows * columns];
int index = 0;
int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
while (left <= right && top <= bottom) {
for (int column = left; column <= right; column++) {
order[index++] = matrix[top][column];
}
for (int row = top + 1; row <= bottom; row++) {
order[index++] = matrix[row][right];
}
if (left < right && top < bottom) {
for (int column = right - 1; column > left; column–) {
order[index++] = matrix[bottom][column];
}
for (int row = bottom; row > top; row–) {
order[index++] = matrix[row][left];
}
}
left++;
right–;
top++;
bottom–;
}
for(int i=0;i
}
return list;
}
}
20.
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。
import java.util.Stack;
public class Solution {
private Stack mainstack=new Stack();
private Stack minstack=new Stack();
public void push(int node) {
if(mainstack.empty()){
mainstack.push(node);
minstack.push(node);
} else{
mainstack.push(node);
if(minstack.peek()
}
21. 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
if (pushA.length == 0 || popA.length == 0 || popA.length != pushA.length)
return false;
Stack stack = new Stack<>();
int j = 0;
for (int i = 0; i < pushA.length; i++) {
stack.push(pushA[i]);
while (!stack.isEmpty() && stack.peek() == popA[j]){
stack.pop();
j++;
}
}
return stack.isEmpty();
}
22. 从上往下打印出二叉树的每个节点,同层节点从左至右打印。
链接:https://www.nowcoder.com/questionTerminal/7fe2212963db4790b57431d9ed259701?answerType=1&f=discussion
来源:牛客网
在Java中Queue是和List、Map同等级别的接口,LinkedList中也实现了Queue接口,该接口中的主要函数有:
结合图中分析:
• 一棵 BST :左孩子 < 根结点 < 右孩子
• 一棵 BST 的左子树或者右子树都是 BST
后序遍历是,左右根:[3, 4, 9, 5, 12, 11, 10],结合图再从左往右分析后序序列,分析子树,可以发现:
• [3, 4, 9, 5] 10 [12, 11]
o [3, 4] 5 [9]
[3] 4
o [12] 11
发现对于每一棵子树,它的根结点总是对应该子树的后序序列的最后一个数
那么,只需要不断地确定出左子树区间和右子树区间,并且判断:左子树区间的所有结点值 < 根结点值 < 右子树区间所有结点值,这个条件是否满足即可
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence == null || sequence.length == 0){
return false;}else{
return helpVerify(sequence, 0, sequence.length-1);
}
}
public boolean helpVerify(int [] sequence, int start, int root){
if(start >= root)return true;
int key = sequence[root];
int i;
//找到左右子数的分界点
for(i=start; i < root; i++)
if(sequence[i] > key)
break;
//在右子树中判断是否含有小于root的值,如果有返回false
for(int j = i; j < root; j++)
if(sequence[j] < key)
return false;
return helpVerify(sequence, start, i-1) && helpVerify(sequence, i, root-1);
}
}
24. 输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
import java.util.ArrayList;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
private ArrayList
private ArrayList list = new ArrayList<>();
public ArrayList> FindPath(TreeNode root,int target) {
if(root == null){
return result;}
else{
list.add(root.val);
target -= root.val;
}
if(target == 0 && root.left == null && root.right == null)
result.add(new ArrayList(list));
else{
//因为在每一次的递归中,我们使用的是相同的result引用,所以其实左右子树递归得到的结果我们不需要关心,
//可以简写为FindPath(root.left, target);FindPath(root.right, target);
//但是为了大家能够看清楚递归的真相,此处我还是把递归的形式给大家展现了出来。
ArrayList
ArrayList
}
list.remove(list.size()-1);//叶子节点时 寻找下一套路径需要删除这个点
return result;
}
}
25. 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
26. 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
中序遍历二叉树,然后用一个ArrayList类保存遍历的结果,这样在ArratList中节点就按顺序保存了,然后再来修改指针。
import java.util.ArrayList;
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null){
return null;
}
ArrayList list = new ArrayList<>();
Convert(pRootOfTree, list);
return Convert(list);
}
//中序遍历,在list中按遍历顺序保存
public void Convert(TreeNode pRootOfTree, ArrayList list){
if(pRootOfTree.left != null){
Convert(pRootOfTree.left, list);
}
list.add(pRootOfTree);
if(pRootOfTree.right != null){
Convert(pRootOfTree.right, list);
}
}
//遍历list,修改指针
public TreeNode Convert(ArrayList list){
for(int i = 0; i < list.size() - 1; i++){
list.get(i).right = list.get(i + 1);
list.get(i + 1).left = list.get(i);
}
return list.get(0);
}
}
27. 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。(好难)
输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
import java.util.ArrayList;
import java.util.TreeSet;
import java.util.Arrays;
public class Solution {
private ArrayList res = new ArrayList<>();
private TreeSet paths = new TreeSet<>();
private StringBuilder path = new StringBuilder();
private boolean[] visited;
public ArrayList Permutation(String str) {
if (str == null || str.equals("")) {
return res;
}
char[] strs = str.toCharArray();
Arrays.sort(strs);
visited = new boolean[strs.length];
combination(strs, 0);
res.addAll(paths);
return res;
}
private void combination(char[] strs, int len) {
if (len == strs.length) {
paths.add(path.toString());
return;
}
for (int i = 0; i < strs.length; i++) {//boolean数组不赋初值默认全是false;
if (!visited[i]) {
visited[i] = true;
path.append(strs[i]);
combination(strs, len + 1);
//Duang ~ 回溯 - 状态重置
visited[i] = false;
path.deleteCharAt(path.length() - 1);
}
}
}
}
28. 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此
输出2。如果不存在则输出0。
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
if(array == null || array.length == 0)
return 0;
int preValue = array[0];//用来记录上一次的记录
int count = 1;//preValue出现的次数(相减之后)
for(int i = 1; i < array.length; i++){
if(array[i] == preValue)
count++;
else{
count--;
if(count == 0){
preValue = array[i];
count = 1;
}
}
}
int num = 0;//需要判断是否真的是大于1半数,这一步骤是非常有必要的,因为我们的上一次遍历只是保证如果存在超过一半的数就是preValue,但不代表preValue一定会超过一半
for(int i=0; i < array.length; i++)
if(array[i] == preValue)
num++;
return (num > array.length/2)?preValue:0;
}
}
29. 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
import java.util.ArrayList;
public class Solution {
public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
ArrayList result = new ArrayList();
if(k<= 0 || k > input.length)return result;
//初次排序,完成k个元素的排序
for(int i = 1; i< k; i++){
int j = i-1;
int unFindElement = input[i];
while(j >= 0 && input[j] > unFindElement){
input[j+1] = input[j];
j–;
}
input[j+1] = unFindElement;
}
//遍历后面的元素 进行k个元素的更新和替换
for(int i = k; i < input.length; i++){
if(input[i] < input[k-1]){
int newK = input[i];
int j = k-1;
while(j >= 0 && input[j] > newK){
input[j+1] = input[j];
j--;
}
input[j+1] = newK;
}
}
//把前k个元素返回
for(int i=0; i < k; i++)
result.add(input[i]);
return result;
}
}
30. 求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
用flag boolean作为停止递归的判断语句
public class Solution {
public int Sum_Solution(int n) {
int sum = n;
boolean flag = (n>0)&&((sum+=Sum_Solution(n-1))>0);
return sum;
}
}
31 HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
int len = array.length;
int[] dp = new int[len];
int max = array[0];
dp[0] = array[0];
for(int i=1; i < len; i++){
int newMax = dp[i-1] + array[i];
if(newMax > array[i])
dp[i] = newMax;
else
dp[i] = array[i];
if(dp[i] > max)
max = dp[i];
}
return max;
}
}
32. 求出113的整数中1出现的次数,并算出1001300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
解题思路
f(n))函数的意思是1~n这n个整数的十进制表示中1出现的次数,将n拆分为两部分,最高一位的数字high和其他位的数字last,分别判断情况后将结果相加,看例子更加简单。
例子如n=1234,high=1, pow=1000, last=234
可以将数字范围分成两部分1999和10001234
1~999这个范围1的个数是f(pow-1)
1000~1234这个范围1的个数需要分为两部分:
千分位是1的个数:千分位为1的个数刚好就是234+1(last+1),注意,这儿只看千分位,不看其他位
其他位是1的个数:即是234中出现1的个数,为f(last)
所以全部加起来是f(pow-1) + last + 1 + f(last);
例子如3234,high=3, pow=1000, last=234
可以将数字范围分成两部分1999,10001999,20002999和30003234
1~999这个范围1的个数是f(pow-1)
1000~1999这个范围1的个数需要分为两部分:
千分位是1的个数:千分位为1的个数刚好就是pow,注意,这儿只看千分位,不看其他位
其他位是1的个数:即是999中出现1的个数,为f(pow-1)
2000~2999这个范围1的个数是f(pow-1)
3000~3234这个范围1的个数是f(last)
所以全部加起来是pow + highf(pow-1) + f(last);
作者:xujunyi
链接:https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/javadi-gui-by-xujunyi/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
return f(n);
}
private int f(int n ) {
if (n <= 0)
return 0;
String s = String.valueOf(n);
int high = s.charAt(0) - ‘0’;
int pow = (int) Math.pow(10, s.length()-1);
int last = n - highpow;
if (high == 1) {
return f(pow-1) + last + 1 + f(last);
} else {
return pow + high*f(pow-1) + f(last);
}
}
}
33. 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面。
import java.util.ArrayList;
public class Solution {
public String PrintMinNumber(int [] numbers) {
if(numbers == null || numbers.length == 0)
return “”;
for(int i=0; i < numbers.length; i++){
for(int j = i+1; j < numbers.length; j++){
int sum1 = Integer.valueOf(numbers[i]+""+numbers[j]);
int sum2 = Integer.valueOf(numbers[j]+""+numbers[i]);
if(sum1 > sum2){
int temp = numbers[j];
numbers[j] = numbers[i];
numbers[i] = temp;
}
}
}
String str = new String();
for(int i=0; i < numbers.length; i++)
str = str + numbers[i];
return str;
}
}
34. 把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
链接:https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b?answerType=1&f=discussion
来源:牛客网
这道题目自己是有思路的,丑数能够分解成2x3y5^z,
所以只需要把得到的丑数不断地乘以2、3、5之后并放入他们应该放置的位置即可,
而此题的难点就在于如何有序的放在合适的位置。
1乘以 (2、3、5)=2、3、5;2乘以(2、3、5)=4、6、10;3乘以(2、3、5)=6,9,15;5乘以(2、3、5)=10、15、25;
从这里我们可以看到如果不加策略地添加丑数是会有重复并且无序,
而在2x,3y,5z中,如果x=y=z那么最小丑数一定是乘以2的,但关键是有可能存在x》y》z的情况,所以我们要维持三个指针来记录当前乘以2、乘以3、乘以5的最小值,然后当其被选为新的最小值后,要把相应的指针+1;因为这个指针会逐渐遍历整个数组,因此最终数组中的每一个值都会被乘以2、乘以3、乘以5,也就是实现了我们最开始的想法,只不过不是同时成乘以2、3、5,而是在需要的时候乘以2、3、5.
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index <= 0){
return 0;}
int p2=0,p3=0,p5=0;//初始化三个指向三个潜在成为最小丑数的位置
int[] result = new int[index];
result[0] = 1;//
for(int i=1; i < index; i++){
result[i] = Math.min(result[p2]*2, Math.min(result[p3]*3, result[p5]*5));
if(result[i] == result[p2]*2)
p2++;//为了防止重复需要三个if都能够走到
if(result[i] == result[p3]*3)
p3++;//为了防止重复需要三个if都能够走到
if(result[i] == result[p5]*5)
p5++;//为了防止重复需要三个if都能够走到
}
return result[index-1];
}
}
35. 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)
public class Solution {
public int FirstNotRepeatingChar(String str) {
for (int i = 0; i < str.length(); i++) {
char t = str.charAt(i);
if (str.indexOf(t) == i && str.lastIndexOf(t) == i)
return i;
}
return -1;
}
}
Indexof和lastindexof 一个从前往后遍历 一个从后往前遍历,可达到要求。
public class Solution {
public int FirstNotRepeatingChar(String str) {
if(str==null || str.length() == 0)return -1;
int[] count = new int[123];
//用一个类似hash的东西来存储字符出现的次数,很方便
for(int i=0; i < str.length();i++)
count[str.charAt(i)]++;
//其实这个第二步应该也是ka我的地方,没有在第一时间想到只要在遍历一遍数组并访问hash记录就可以了
for(int i=0; i < str.length();i++)
if(count[str.charAt(i)]==1)
return i;
return -1;
}
}
定义int数组 数组默认初始值为0 赋值以后更新。Char类型的最大是字母大Z 对应值是122 所以定义初始数组长度的时候要123 这样数组从0开始到122完全符合。
如果没有小朋友,请返回-1
用环形链表做
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if (n <= 0 || m <= 0) {
return -1;
}
ListNode head = new ListNode(0);
ListNode node = head;
for (int i = 1; i < n; i++) {
node.next = new ListNode(i);
node = node.next;
}
node.next = head;
int k = 0;
while (node.next != node) {
if (k++ == m-1) {
node.next = node.next.next;
k = 0;
} else {
node = node.next;
}
}
return node.val;
}
}
47. 牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
用栈的思想做
import java.util.Stack;
public class Solution {
public String ReverseSentence(String str) {
if(str==null||str.length()0)
return str;
StringBuilder res=new StringBuilder();
String[] tmp = str.split(" ");
if(tmp.length0)
return str;
Stacksx=new Stack<>();
for(int i=0;i
sx.push(" ");
}
sx.push(tmp[tmp.length-1]);
while(!sx.isEmpty()) {
res.append(sx.pop());
}
return res.toString();
}
}
48. 写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
public class Solution {
public int Add(int num1,int num2) {
/**【正如offer书上所说】
在计组中,半加器、全加器中:
两个二进制的相加结果是用一个异或门实现的;
两个二进制的进位结果是用一个与门来实现的。
*/
int result = 0;
int carry = 0;
do{
result = num1 ^ num2; //不带进位的加法
carry = (num1 & num2) << 1; //进位
num1 = result;
num2 = carry;
}while(carry != 0); // 进位不为0则继续执行加法处理进位
return result;
}
}
49. 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
哈希表做
import java.util.HashSet;
public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
HashSet set = new HashSet<>();
for(int i =0 ;i
duplication[0] = numbers[i];
return true;
}else{
set.add(numbers[i]);
}
}
return false;
}
}
50. 给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
B[i]的左边和B[i-1]有关,右边和B[i+1]有关。
B[i]=A[0] * A[1]… * A[i-1]乘上A[i+1]…A[n-1] 以A[i]为分界线,将B分为左右两个部分相乘。
B[i]=左 * 右 利用一轮循环计算所有的左下三角,一轮循环计算所有的右上三角
当把A[0]…A[n-2]利用一轮for循环算出来时,左下角的所有乘积都知道了(得到了n-1个值),同理一轮for循环,右上角的A[1]…A[n-1]所有乘积都知道了(也得到了n-1个值),因此在第两轮for循环之时,进行对应的“组装”就可以得到对应的B[i]的值。
import java.util.ArrayList;
public class Solution {
public int[] multiply(int[] A) {
int length=A.length;
int[] B=new int[length];
//边界
if(Anull||A.length<=1){
return null;
}
//计算下三角
//初始化第一行
B[0]=1;
for(int i=1;i
}
//计算上三角
//初始化最后一行
int temp=1;
for(int i=length-1;i>=0;i–){
B[i]=tempB[i];
temp=A[i]*temp;
}
return B;
}
}
51. 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,“±5"和"12e+4.3"都不是。
public class Solution {
private int index = 0;//指向字符串的指针
public boolean isNumeric(char[] str) {
if (str.length < 1)
return false;
//判断整数部分是否符合要求
boolean flag = scanInteger(str);
// 如果出现’.’,接下来是数字的小数部分
if (index < str.length && str[index] == ‘.’) {
index++;
// 下面一行代码用||的原因:
// 1. 小数可以没有整数部分,例如.123等于0.123;
// 2. 小数点后面可以没有数字,例如233.等于233.0;
// 3. 当然小数点前面和后面可以有数字,例如233.666
flag = scanUnsignedInteger(str) || flag;
}
// 如果出现’e’或者’E’,接下来跟着的是数字的指数部分
if (index < str.length && (str[index] == ‘E’ || str[index] == ‘e’)) {
index++;
// 下面一行代码用&&的原因:(且顺序不能颠倒,短路求值)
// 1. 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
// 2. 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4
flag = flag && scanInteger(str);
}
return flag && index == str.length;
}
private boolean scanInteger(char[] str) {
if (index < str.length && (str[index] == ‘+’ || str[index] == ‘-’) )
index++;
return scanUnsignedInteger(str);
}
private boolean scanUnsignedInteger(char[] str) {
int start = index;
while (index < str.length && str[index] >= ‘0’ && str[index] <= ‘9’)
index++;
// 当str中存在若干0-9的数字时,返回true
return start < index; //是否存在整数
}
}
52. 请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g”。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
public class Solution {
//Insert one char from stringstream
private int index = 0;//index用于记录某个字符出现的位置
private int[] arr = new int[128];
public Solution(){ //构造函数初始化数组为-1,
for(int i=0;i<128;i++)
arr[i]=-1;
}
public void Insert(char ch)
{
if(arr[ch]
arr[ch]=index; //第一次出现时,记录其在字符流中的位置
}else if(arr[ch]>=0) {//大于0,说明某个字符出现过了
arr[ch]=-2; //多次出现时,重置
}
index++;
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce() {
int minIndex = Integer.MAX_VALUE; //方便比较出最靠前的那个出现1次的字符
char ch=’#’;
for(int i=0;i<128;i++) {
if(arr[i]>=0 && arr[i] < minIndex) {//字符赋值给ch,位置赋值给minIndex
ch = (char) i;
minIndex = arr[i];
}
}
return ch;
}
}
53. 给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead == null){
return null;
}
// 1.判断链表中有环
ListNode l=pHead,r=pHead;
boolean flag = false;
while(r != null && r.next!=null){
l=l.next;
r=r.next.next;
if(l==r){
flag=true;
break;
}
}
if(!flag){
return null;
}else{
// 2.得到环中节点的数目
int n=1;
r=r.next;
while(l!=r){
r=r.next;
n++;
}
// 3.找到环中的入口节点
l=r=pHead;
for(int i=0;i
}
while(l!=r){
l=l.next;
r=r.next;
}
return l;
}
}
}
54. 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
既然给了二叉树的某个结点,且二叉树存储着指向父结点的指针(next),那我们可以先找到根节点,再对树进行中序遍历,最后根据中序遍历结果找到给定结点的下一结点
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
/
import java.util.ArrayList;
public class Solution {
private ArrayList list = new ArrayList<>();
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
TreeLinkNode par = pNode;
while(par.next != null){
par = par.next;
}
InOrder(par);
for(int i=0;i
return i == list.size()-1?null:list.get(i+1);
}
}
return null;
}
void InOrder(TreeLinkNode pNode){
if(pNode!=null){
InOrder(pNode.left);
list.add(pNode);
InOrder(pNode.right);
}
}
}
55. 从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
/
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
ArrayList
//queue用来保存当前遍历到了哪个节点,一次性把一个节点的左右子都入队
Queue queue = new LinkedList();
//list用来保存输出的节点
ArrayList
if(pRoot==null){//注意:空树返回一个默认构造的空LinkedList,而不是一个空指针null
return thelist;
}
TreeNode current = pRoot;
queue.offer(current);
while(!queue.isEmpty()){
ArrayList list = new ArrayList();
int size = queue.size();
for(int i=0;i
}
56. 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
//小顶堆,用该堆记录位于中位数后面的部分
private PriorityQueue minHeap = new PriorityQueue();
//大顶堆,用该堆记录位于中位数前面的部分
private PriorityQueue maxHeap = new PriorityQueue(15, new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
int count = 0;
public void Insert(Integer num) {
//个数为偶数的话,则先插入到大顶堆,然后将大顶堆中最大的数插入小顶堆中
if(count % 2 == 0){
maxHeap.offer(num);
int max = maxHeap.poll();
minHeap.offer(max);
}else{
//个数为奇数的话,则先插入到小顶堆,然后将小顶堆中最小的数插入大顶堆中
minHeap.offer(num);
int min = minHeap.poll();
maxHeap.offer(min);
}
count++;
}
public Double GetMedian() {
//当前为偶数个,则取小顶堆和大顶堆的堆顶元素求平均
if(count % 2 == 0){
return new Double(minHeap.peek() + maxHeap.peek())/2;
}else{
//当前为奇数个,则直接从小顶堆中取元素即可,所以我们要保证小顶堆中的元素的个数。
return new Double(minHeap.peek());
}
}
}
57. 给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
public class Solution {
/复杂度基本是O(1)级别。
对于相乘最大问题,数学上可以这么理解:
如果两个数,那么理解为周长相等的矩形,正方形面积比长方形的大;
如果三个数,那么理解为立方体的体积比长方体的大……以此类推。
也就是说,m个数相加等于n,找到一个数curr,curr的m次方大于等于n,
那么curr就是我们要找的绳子的段数,当然最后会有一段绳子小于等于curr。
/
public int cutRope(int target) {
if(target == 2) return 1;
int curr = 1;
while(true){
if(curr * curr >= target) break;
curr++;
}
int res = 1;
while(target > curr){
res = curr;
target -= curr;
}
res = target;
return res;
}
}
58. 请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"aba"均不匹配
public class Solution {
public boolean match(char[] str, char[] pattern)
{
if (str == null || pattern == null)
return false;
return matchCore(str, 0, pattern, 0);
}
private boolean matchCore(char[] str, int s, char[] pattern, int p) {
//下面4行是递归结束标志,两个指针都指到了最后,才是匹配,否则不匹配
if (s == str.length && p == pattern.length)
return true;
if (s < str.length && p == pattern.length)
return false;
//虽然比的是P位置的,但是P后面出现时,规则需要改变。
if (p + 1 < pattern.length && pattern[p + 1] == '’) {
//出现了,并且s和P指向的相同,3种情况并列
if ((s < str.length && pattern[p] == ‘.’)
|| (s < str.length && pattern[p] == str[s])) {
return matchCore(str, s, pattern, p + 2)
|| matchCore(str, s + 1, pattern, p)
|| matchCore(str, s + 1, pattern, p + 2);
} else {//出现了,并且s和p指向的不同,那就把前面的字符理解出现了0次,p+2
return matchCore(str, s, pattern, p + 2);
}
}
//说明P后面不是,那么就进行常规判断。相同就分别给指针+1
if (s < str.length && (pattern[p] == str[s] || pattern[p] == ‘.’))
return matchCore(str, s + 1, pattern, p + 1);
//p后面又不是*,也没有.给你撑腰,你还敢出现不同,那必然false
return false;
}
}
59. 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
/
import java.util.HashSet;
public class Solution {
public ListNode deleteDuplication(ListNode pHead)
{
if(pHead == null){
return null;
}
// 先找出相同结点,存入 set
HashSet set = new HashSet<>();
ListNode pre = pHead;
ListNode cur = pHead.next;
while(cur != null){
if(cur.val == pre.val){
set.add(cur.val);
}
pre = cur;
cur = cur.next;
}
// 再根据相同节点删除
// 先删头部
while(pHead != null && set.contains(pHead.val)){
pHead = pHead.next;
}
if(pHead == null){
return null;
}
// 再删中间结点
pre = pHead;
cur = pHead.next;
while(cur != null){
if(set.contains(cur.val)){
pre.next = cur.next;
cur = cur.next;
}else{
pre = cur;
cur = cur.next;
}
}
return pHead;
}
}
60. 请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
/
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
boolean isSymmetrical(TreeNode pRoot){
if(pRoot == null)return true;
else return isDuiChen(pRoot.left, pRoot.right);
}
boolean isDuiChen(TreeNode leftRoot, TreeNode rightRoot){
if(leftRoot == null && rightRoot == null)
return true;
if(leftRoot == null || rightRoot == null)
return false;
if(leftRoot.val == rightRoot.val)
return isDuiChen(leftRoot.left, rightRoot.right)&&
isDuiChen(leftRoot.right, rightRoot.left);
else
return false;
}
}
61. 给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
思路:二叉树的构造就是左子节点的值<根节点<右子节点的值 中序遍历的顺序一样
直接中序遍历 寻找K个节点就完事了。
import java.util.ArrayList;
public class Solution {
TreeNode KthNode(TreeNode pRoot, int k)
{
ArrayList result = new ArrayList<>();
if(pRoot == null || k<=0){
return null;
}
foreach(result,pRoot);
if(result.size()>= k){
return result.get(k-1);
}else{
return null;
}
}
private void foreach(ArrayList result, TreeNode node) {
if(node.left != null){
foreach(result, node.left);
}
result.add(node);
if(node.right != null){
foreach(result, node.right);
}
}
}
62. 将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
链接:https://www.nowcoder.com/questionTerminal/1277c681251b4372bdef344468e4f26e?answerType=1&f=discussion
来源:牛客网
如果问题背景变成如下:正确的数字范围是-128~127,待分析字符串是-134和129。
import java.util.ArrayList;
import java.util.Stack;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
/
public class Solution {
public ArrayList
ArrayList
if(pRoot == null) return result;
Stack tmp = new Stack<>();
Stack tmp1 = new Stack<>();
tmp.add(pRoot);
while(tmp.size() > 0 || tmp1.size() > 0) {
ArrayList flood = new ArrayList<>();
if(tmp.size() > 0) {
int size = tmp.size();
for(int i=0; i
flood.add(pop.val);
if(pop.left != null) {
tmp1.add(pop.left);
}
if(pop.right != null) {
tmp1.add(pop.right);
}
}
result.add(flood);
continue;
}
if(tmp1.size() > 0) {
int size = tmp1.size();
for(int i=0; i
flood.add(pop.val);
if(pop.right != null) {
tmp.add(pop.right);
}
if(pop.left != null) {
tmp.add(pop.left);
}
}
result.add(flood);
continue;
}
}
return result;
}
}
64. 请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树
/
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
int index =-1;
String Serialize(TreeNode root) {
if (root == null) {
return “#”;
} else {
return root.val + “,” + Serialize(root.left) + “,” + Serialize(root.right);
}
}
TreeNode Deserialize(String str) {
String[] s = str.split(",");//将序列化之后的序列用,分隔符转化为数组
index++;//索引每次加一
int len = s.length;
if (index >= len) {
return null;
}
TreeNode treeNode = null;
if (!s[index].equals("#")) {//不是叶子节点 继续走 是叶子节点出递归
treeNode = new TreeNode(Integer.parseInt(s[index]));
treeNode.left = Deserialize(str);
treeNode.right = Deserialize(str);
}
return treeNode;
}
}
65. 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
可以用大项堆来做。
import java.util.ArrayList;
import java.util.PriorityQueue;
public class Solution {
public PriorityQueue maxQueue = new PriorityQueue((o1,o2)->o2-o1);//大顶堆
public ArrayList result = new ArrayList();//保存结果
public ArrayList maxInWindows(int [] num, int size)
{
if(num==null || num.length<=0 || size<=0 || size>num.length){
return result;
}
int count=0;
for(;count
}
while(count
maxQueue.remove(num[count-size]);
maxQueue.add(num[count]);
count++;
}
result.add(maxQueue.peek());//最后一次入堆后没保存结果,这里额外做一次即可
return result;
}
}
66. 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 \begin{bmatrix} a & b & c &e \ s & f & c & s \ a & d & e& e\ \end{bmatrix}\quad⎣⎡asabfdcceese⎦⎤ 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
public class Solution {
public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
{
char[][] mat = new char[rows][cols];
for(int i = 0, k = 0; i < rows; ++i){//把矩阵二维存储
for(int j = 0; j < cols; ++j){
mat[i][j] = matrix[k++];
}
}
String s = new String(str);//方便调用substring函数
for(int i = 0; i < rows; ++i){
for(int j = 0; j < cols; ++j){
boolean[][] visit = new boolean[rows][cols];//标记是否重复访问
if(f(mat, i, j, s, visit)) return true;//该点为起点能否形成路径
}
}
return false;
}
public static boolean f(char[][] mat, int i, int j, String s, boolean[][] visit){
if(mat[i][j] != s.charAt(0)) return false;
else if(s.length() == 1) return true;//最后一个字符被匹配上了
visit[i][j] = true;//标记该点已访问
boolean r = false;
if(g(i + 1, j, mat) && !visit[i + 1][j]) r = r||f(mat, i + 1, j, s.substring(1), visit);
if(g(i - 1, j, mat) && !visit[i - 1][j]) r = r||f(mat, i - 1, j, s.substring(1), visit);
if(g(i, j + 1, mat) && !visit[i][j + 1]) r = r||f(mat, i, j + 1, s.substring(1), visit);
if(g(i, j - 1, mat) && !visit[i][j - 1]) r = r||f(mat, i, j - 1, s.substring(1), visit);
visit[i][j] = false;//重置该点的访问权
return r;
}
public static boolean g(int i, int j, char[][] mat){//该点下标是否越界
if(i >= 0 && j >= 0 && i < mat.length && j < mat[0].length) return true;
return false;
}
}
67. 地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
public class Solution {
/**链接:https://www.nowcoder.com/questionTerminal/6e5207314b5241fb83f2329e89fdecc8?answerType=1&f=discussion
来源:牛客网
首先在某个节点处,要调用递归来决定某个位置的下一步去哪,此时有4个选择,每个选择都会进入下一个递归调用。当进入某个位置时,
应当标记这个位置已访问过,避免之后又来到这里,从而重复计算,
因此设计一个boolean的数组,这里设计的二维,也可以通过压缩,使用一维数组来表征某个位置是否访问。
二维就是记录横纵坐标即第i行第j列的数据被访问了,直观,推荐新手用二维。接着就是边界条件和递归结束的条件的判断了。
这类型题也是有套路的,主方法在原点作为起点,调用第一次递归后,在递归方法中,
首先判断边界条件以及题目中所提的要求是否满足(本题的要求就是cal方法中的实现),
都没问题,说明该位置可以访问,然后改变对应位置的标记。然后就是以该节点为中心,考虑下一步怎么走,
本题就是4种走法,可以分开写,也可以一起写,由于本题是计数,所以就直接加在一起。然后return这些求和结果再+1,
求和结果是下一步走的结果,而+1是本次访问此时的节点的次数。
*/
public int movingCount(int threshold, int rows, int cols)
{
if (rows <= 0 || cols <= 0 || threshold < 0)
return 0;
boolean[][] isVisited = new boolean[rows][cols];//标记
int count = movingCountCore(threshold, rows, cols, 0, 0, isVisited);
return count;
}
private int movingCountCore(int threshold,int rows,int cols,
int row,int col, boolean[][] isVisited) {
if (row < 0 || col < 0 || row >= rows || col >= cols || isVisited[row][col]
|| cal(row) + cal(col) > threshold)
return 0;
isVisited[row][col] = true;
return 1 + movingCountCore(threshold, rows, cols, row - 1, col, isVisited)
+ movingCountCore(threshold, rows, cols, row + 1, col, isVisited)
+ movingCountCore(threshold, rows, cols, row, col - 1, isVisited)
+ movingCountCore(threshold, rows, cols, row, col + 1, isVisited);
}
private int cal(int num) {
int sum = 0;
while (num > 0) {
sum += num % 10;
num /= 10;
}
return sum;
}
}