面试题19 正则表达式匹配
请实现一个函数用来匹配包含’.’和’‘的正则表达式。模式中的字符’.’表示任意一个字符,而’‘表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串“aaa”与模式“a.a”和“ab*ac*a”匹配,但与“aa.a”和“ab*a”均不匹配。
测试样例
- 功能测试(模式字符串里包含普通字符、’.’、’*’;模式字符串和输入字符串匹配/不匹配)
- 特殊输入测试(输入字符串和模式字符串是null、空字符串)
实现代码
/**
* 字符串匹配的主方法,负责处理字符串为空等特殊情况,以及核心递归方法的调用
* @param str 字符串
* @param pattern 模式
* @return true or false
*/
public static boolean match(String str,String pattern){
if (str == null || pattern == null)
return false;
if (str.equals("") && pattern.equals(""))
return true;
return matchCore(str,0,pattern,0);
}
/**
* 匹配的核心递归方法
* @param str 字符串
* @param strIndex 当前遍历到的字符串的位置
* @param pattern 模式
* @param ptnIndex 当前遍历到的模式的位置
* @return true or false
*/
private static boolean matchCore(String str,int strIndex, String pattern,int ptnIndex) {
//出口1,遍历完
if (sIndex == s.length() && ptnIndex == p.length())
return true;
//出口2,s遍历完了,p没完,继续判断p的第二位是不是*
if (sIndex == s.length() && ptnIndex != p.length()){
if (ptnIndex+1<p.length() && p.charAt(ptnIndex+1)=='*')
return matchCore(s,sIndex,p,ptnIndex+2);
else
return false;
}
//出口3,s没完,p完了
if (sIndex != s.length() && ptnIndex == p.length())
return false;
//第二个字符是*
if (ptnIndex+1<p.length() && p.charAt(ptnIndex+1) == '*'){
//第一个字符匹配
if (p.charAt(ptnIndex) == '.' || s.charAt(sIndex)==p.charAt(ptnIndex)){
return matchCore(s,sIndex+1,p,ptnIndex+2) //匹配一次
|| matchCore(s,sIndex+1,p,ptnIndex) //匹配多次
|| matchCore(s,sIndex,p,ptnIndex+2); //匹配0次
}
else
return matchCore(s,sIndex,p,ptnIndex+2);
}
//第二个字符不是*
if (p.charAt(ptnIndex) == '.' || s.charAt(sIndex) == p.charAt(ptnIndex))
return matchCore(s,sIndex+1,p,ptnIndex+1);
return false;
}
public static void main(String[] args) {
String str = "a";
String pattern = ".";
System.out.println(str+" "+match(str,pattern)+" "+pattern);
String str1 = "aaa";
String pattern1 = "ab*ac*a";
System.out.println(str1+" "+match(str1,pattern1)+" "+pattern1);
String str2 = "aaa";
String pattern2 = "aa.a";
System.out.println(str2+" "+match(str2,pattern2)+" "+pattern2);
String str3 = "aaa";
String pattern3 = "ab*a";
System.out.println(str3+" "+match(str3,pattern3)+" "+pattern3);
}
算法思路
这道题的核心其实在于分析’*’,对于’.’来说,它和任意字符都匹配,可把其当做普通字符。对于’*’的分析,我们要进行分情况讨论,当所有的情况都搞清楚了以后,就可以写代码了。
- 在每轮匹配中,Patttern第二个字符是’*’时
- 当第一个字符不匹配时,那么代表模式的一个字符出现0次,模式略过前两个字符,字符串不变。如“ba”和“a*ba”。
- 当第一个字符匹配时,可能匹配0次,1次、多次。比如“aaa”与“a*aaa”,“abc”与“a*bc”,“aaaba”与“a*ba”。匹配0次时,字符串不变,模式向后跳2位,然后匹配剩余字符串和模式;匹配1次时,字符串向后移动1位,模式向后移动两位;匹配多次时,字符串向后移动一位,模式不变;
- 而当Patttern第二个字符不是’*’时,情况就简单多了:
- 如果字符串的第一个字符和模式中的第一个字符匹配,那么在字符串和模式上都向后移动一个字符,然后匹配剩余字符串和模式。
- 如果字符串的第一个字符和模式中的第一个字符不匹配,那么直接返回false。
面试题20 表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串“+100”、“5e2”、“-123”、“3.1416”及“-1E-16”都表示数值,但“12e”、“1a3.14”、“1.2.3”、“+-5”及“12e+5.4”都不是。
测试用例
- 功能测试(正数或者负数;包含或者不包含整数部分的数值;包含或者不包含小数部分的数值;包含或者不包含指数部分的数值;各种不能表达有效数值的字符串)。
- 特殊输入测试(输入字符串和模式字符串是null、空字符串)
实现代码
/**
* 字符串的字符数组形式
* @param str 字符数组
* @return true or false
*/
public boolean isNumeric(char[] str) {
//标记符号、小数点、e是否出现过
boolean sign = false;
boolean decimal = false;
boolean hasE = false;
for (int i=0;i<str.length;i++) {
if (str[i] == 'e' || str[i] == 'E') {
if (i == str.length - 1) return false;
if (hasE) return false;
hasE = true;
} else if (str[i] == '+' || str[i] == '-') {
if (sign && str[i - 1] != 'e' && str[i - 1] != 'E') return false;
if (!sign && i > 0 && str[i - 1] != 'e' && str[i - 1] != 'E') return false;
sign = true;
} else if (str[i] == '.') {
if(hasE||decimal) return false;
decimal = true;
} else if (str[i] < '0' || str[i] > '9') {
return false;
}
}
return true;
}
public static void main(String[] args) {
InterQuestions20 test = new InterQuestions20();
System.out.println(test.isNumeric("-1E-16".toCharArray()));
System.out.println(test.isNumeric("12e+4.3".toCharArray()));
}
算法思路
表示数值的字符串遵循模式A[.[B]][e|EC]或者.B[e|EC]。其中A表示整数部分,可以带符号。B是小数部分,C紧跟着’e’或’E’,是数值的指数部分。在小数里可能没有整数部分,如“.234”。上面A、B和C都是0~9的数位串,但是B前面不能有正负号。按照此模式来完成。
面试题21 调整数组顺序使技术位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
测试用例
- 功能测试(输入一个数组,全为奇数;全为偶数;奇数偶数均有;)
- 特殊值测试(数组为null,数组为空,数组只有一个元素)
实现代码
/**
* 根据isOdd和isEven的标准将数组元素分为前后两个部分
* @param array 数组
*/
public static void reorderOddEven(int[] array){
if (array == null || array.length<1)
return;
int odd = 0,even = array.length-1;
int temp=0;
while (odd<even) {
temp=0;
if (isOdd(array[odd])) {
odd++;
continue;
}
if (isEven(array[even])) {
even--;
continue;
}
temp = array[even];
array[even] = array[odd];
array[odd] = temp;
}
}
/**
* 数组前端的判断标准
* @param odd 要判断的数
* @return true or false
*/
private static boolean isOdd(int odd){
return odd % 2 != 0;
}
/**
* 数组后端的判断标准
* @param even 数
* @return true or false
*/
private static boolean isEven(int even){
return even % 2 == 0;
}
public static void main(String[] args) {
int[] arr = {1,5,6,4,3,2};
reorderOddEven(arr);
for (int i : arr) {
System.out.print(i+",");
}
}
算法思路
设置头尾两个指针,分别从前往后遍历,前后都遇到符合条件的数字就交换。类似于快速排序的思想。同时可以将判断标准独立出来,形成可扩展的方法。
面试题22 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第一个节点。例如,一个链表有6个节点,从头节点开始,它们的值一次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。链表节点定义如下:
/**
* 链表节点类
*/
class ListNode{
int nodeValue;
ListNode nextNode;
}
测试样例
- 功能测试(第k个节点在链表中间;第k个节点是链表的头节点;第k个节点是链表的尾节点)
- 特殊输入测试(链表头节点为null,链表的节点总数少于k;k等于0)
实现代码
/**
* 链表节点类
*/
class ListNode{
int nodeValue;
ListNode nextNode;
public ListNode(int nodeValue) {
this.nodeValue = nodeValue;
}
}
public static ListNode lastButNth(ListNode head,int k){
if (head == null || k==0)
return null;
ListNode one=head,two = head;
for (int i=0;i<k-1;i++)
if (two.nextNode != null)
two = two.nextNode;
else
return null;
while (two.nextNode != null){
two = two.nextNode;
one = one.nextNode;
}
return one;
}
public static void main(String[] args) {
ListNode root = new ListNode(1);
ListNode node1 = new ListNode(2);
ListNode node2 = new ListNode(3);
ListNode node3 = new ListNode(4);
ListNode node4 = new ListNode(5);
ListNode node5 = new ListNode(6);
root.nextNode = node1;
node1.nextNode = node2;
node2.nextNode = node3;
node3.nextNode = node4;
node4.nextNode = node5;
System.out.println(lastButNth(node5,3).nodeValue);
}
算法思路
定义两个指针。第一个指针从链表头部走k-1步,第二个指针保持不动;从第k步开始,第二个指针也开始从链表头部开始遍历。当第一个指针到达尾部时,第二个指针所在位置就是倒数第k个元素。
面试题23 链表中环的入口节点
如果一个链表中包含环,如何找出环的入口节点?
测试样例
- 功能测试(链表包含环;不包含环)
- 特殊值测试(链表为空;链表头指针为null)
实现代码
public static ListNode EntryNodeOfLoop(ListNode head){
ListNode mettingNode = mettingNode(head);
if (mettingNode == null)
return null;
int i = 1;
ListNode next = mettingNode.nextNode;
while (mettingNode != next){
next = next.nextNode;
i++;
}
ListNode one = head,two = head;
for (int j=0;j<i;j++ ){
one = one.nextNode;
}
while (one != two){
one = one.nextNode;
two = two.nextNode;
}
return one;
}
private static ListNode mettingNode(ListNode head) {
ListNode slow = head.nextNode;
if (slow == null)
return null;
ListNode fast = slow.nextNode;
while (fast != null){
if (slow == fast)
return fast;
slow = slow.nextNode;
fast = fast.nextNode;
if (fast != null)
fast = fast.nextNode;
}
return null;
}
public static void main(String[] args) {
ListNode root = new ListNode(1);
ListNode node1 = new ListNode(2);
ListNode node2 = new ListNode(3);
ListNode node3 = new ListNode(4);
ListNode node4 = new ListNode(5);
ListNode node5 = new ListNode(6);
root.nextNode = node1;
node1.nextNode = node2;
node2.nextNode = node3;
node3.nextNode = node4;
node4.nextNode = node5;
node5.nextNode = node3;
System.out.println(mettingNode(root).nodeValue);
System.out.println(EntryNodeOfLoop(root).nodeValue);
}
算法思路
如何判断链表有环?
使用一快一慢两个指针,指针1一次走1步,指针2一次走两步。如果两个指针相遇,那么链表中存在环。
如何找到环的入口?
设环有n个节点。使用两个指针,一个先走n步,当两个节点相遇时,相遇的地点就是环的入口。
如何计算出环有几个节点?
在判断链表是否有环的时候,两个指针相遇的节点一定属于环内。让该指针继续移动,当指针再次回到该点时,经过的节点个数就是换的节点个数。
面试题24 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。链表节点定义同面试题23.
测试用例
- 功能测试(链表有多个节点,链表只有一个节点)
- 特殊值测试(输入的头节点等于null)
实现代码
/**
* 逆序的接口方法
* @param head 链表头节点
* @return 逆序后的头节点
*/
public static ListNode reverseLinkList(ListNode head){
if (head == null)
return null;
ListNode pre = null,cur = head,next = cur.nextNode;
if (next == null)
return head;
return reverseCore(pre,cur,next);
}
/**
* 逆序的核心递归方法
* @param pre 当前已经逆序的最后一个节点
* @param cur 当前没有逆序的第一个节点
* @param next 当前没有逆序的第二个节点
* @return 已经逆序的链表的头节点
*/
public static ListNode reverseCore(ListNode pre,ListNode cur,ListNode next){
cur.nextNode = pre;
if (next == null)
return cur;
return reverseCore(cur,next,next.nextNode);
}
算法思路
定义三个节点pre,cur,next,分别表示当前已经逆序的最后一个节点、当前没有逆序的第一个节点、当前没有逆序的第二个节点。cur的next指针指向pre,就完成了cur节点的逆序。cur变为pre,next变为cur,递归进行。知道next等于null,则将最后一个节点逆序并返回。
面试题25 合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
测试样例
- 功能测试(输入的两个链表有多个节点;节点的值互不相同或者存在值相等的多个节点)
- 特殊输入测试(两个链表为null或者二者之一为null;两个链表中只有一个节点。
实现代码
/**
* 递归地合并两个链表
* @param list1 链表1
* @param list2 链表2
* @return 合并后的链表
*/
public static ListNode mergeList(ListNode list1,ListNode list2) {
if (list1 == null)
return list2;
if (list2 == null)
return list1;
ListNode mergeHead = null;
if (list1.nodeValue < list2.nodeValue) {
mergeHead = list1;
mergeHead.nextNode = mergeList(list1.nextNode, list2);
} else {
mergeHead = list2;
mergeHead.nextNode = mergeList(list1,list2.nextNode);
}
return mergeHead;
}
//测试方法
public static void main(String[] args) {
ListNode node0 = new ListNode(0);
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
ListNode node6 = new ListNode(4);
node0.nextNode = node2;
node2.nextNode = node4;
node4.nextNode = node6;
node1.nextNode = node3;
node3.nextNode = node5;
ListNode merge = mergeList(node0,node1);
while (merge != null){
System.out.print(merge.nodeValue+",");
merge = merge.nextNode;
}
}
算法思路
比较两个头节点,较小节点作为合并后的头节点。剩下的两个链表仍然有,递归地进行求解。
面试题26 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。二叉树节点定义如下:
class BinaryTreeNode{
double nValue;
BinaryTreeNode left;
BinaryTreeNode right;
}
测试用例
- 功能测试(输入两棵树,B是或者不是A的子结构;树中有或者没有相同的值的节点)
- 特殊值测试(两棵树有一棵,或者两棵为null;两棵树相同)
实现代码
/**
* 判断树A是否包含树B
* @param A 树A
* @param B 树B
* @return 是否包含
*/
public static boolean hasSubtree(BinaryTreeNode A,BinaryTreeNode B){
boolean result =false;
if (A != null && B != null){
if (isEqual(A.nValue,B.nValue))
result = doesTree1HasTree2(A,B);
if (!result)
result = hasSubtree(A.left,B);
if (!result)
result = hasSubtree(A.right,B);
}
return result;
}
/**
* 在根节点相同的情况下,判断tree1是否包含tree2
* @param tree1 树1
* @param tree2 树2
* @return 是否包含
*/
private static boolean doesTree1HasTree2(BinaryTreeNode tree1, BinaryTreeNode tree2) {
if (tree2 == null)
return true;
if (tree1 == null)
return false;
if (!isEqual(tree1.nValue,tree2.nValue))
return false;
return doesTree1HasTree2(tree1.left,tree2.left) && doesTree1HasTree2(tree1.right,tree2.right);
}
/**
* 由于只需要判断是否想到能,不需要判断大小,所以将double转为String,
* 再使用equals进行比较
* @param a 数字1
* @param b 数字2
* @return 是否相等
*/
private static boolean isEqual(double a, double b) {
return String.valueOf(a).equals(String.valueOf(b));
}
public static void main(String[] args) {
BinaryTreeNode tree1 = new BinaryTreeNode(0.501);
BinaryTreeNode node1 = new BinaryTreeNode(1.501);
BinaryTreeNode node2 = new BinaryTreeNode(2.501);
tree1.left=node1;
tree1.right = node2;
BinaryTreeNode tree2 = new BinaryTreeNode(0.501);
BinaryTreeNode node3 = new BinaryTreeNode(1.501);
BinaryTreeNode node4 = new BinaryTreeNode(2.501);
tree2.left=node3;
//tree2.right=node4;
System.out.println(hasSubtree(tree1,tree2));
}
算法思路
遍历树A,找到与B的根节点值相同的点,然后判断以这两个节点为根的tree1和tree2,是否满足tree1包含tree2。是,则返回true。若不包含,则继续遍历A余下的节点,重复以上步骤。
面试题27 二叉树的镜像
请完成一个函数,输入一颗二叉树,该函数输出它的镜像。二叉树节点定义如下:
class BinaryTreeNode{
int nValue;
BinaryTreeNode left;
BinaryTreeNode right;
}
测试样例
- 功能测试(有左右子树的二叉树;只有左子树或右子树的二叉树)
- 特殊值测试(二叉树为null;二叉树只有一个树根)
实现代码
public void mirrorRecursively(BinaryTreeNode tree){
if (tree == null)
return ;
if (tree.left == null && tree.right == null)
return ;
//交换左右子节点
BinaryTreeNode temp = tree.left;
tree.left = tree.right;
tree.right = temp;
if (tree.left!=null)
mirrorRecursively(tree.left);
if (tree.right != null)
mirrorRecursively(tree.right);
}
/**
* 在Java里面只有基本类型和按照下面这种定义方式的String是按值传递,
* 其它的都是按引用传递。就是直接使用双引号定义字符串方式:String str = “Java私塾”;
* @param args 参数
*/
public static void main(String[] args) {
BinaryTreeNode tree1 = new BinaryTreeNode(0.501);
BinaryTreeNode node1 = new BinaryTreeNode(1.501);
BinaryTreeNode node2 = new BinaryTreeNode(2.501);
tree1.left=node1;
tree1.right = node2;
System.out.println(tree1.nValue);
System.out.println(tree1.left.nValue);
System.out.println(tree1.right.nValue);
InterQuestions27 mirror = new InterQuestions27();
mirror.mirrorRecursively(tree1);
System.out.println(tree1.nValue);
System.out.println(tree1.left.nValue);
System.out.println(tree1.right.nValue);
}
算法思路
从头节点开始,如果头节点有子树,那么交换左右子树,并递归地对子树进行同样的操作。如果没有子树,那么直接返回。