题目列表
面试题11:旋转数组的最小数字
分析:题目的意思是,给一个升序数组的旋转后的数组,然后让你找他的最小值。
方法1:观察可以发现,由于数组只是把嘴前面的升序部分放在了后面,所以从前往后找,遇到第一个比前面小的数,就是我们要找的最小值。该方法时间复杂度为O(n)。
方法2:可以用2分法,至少需要分析代码中测试用例中的5中情况,实现比较复杂,但是时间复杂度能够提高到O(log(n))
public class Demo11 {
public static void main(String[] args) throws Exception {
// 测试用例
// int [] arr1 = {1,1,1,1,1,1};// 全等,这种情况有歧义,不考虑这种
int[][] arr = {
{ 1, 1, 1, 0, 1 }, // arr[lo] = arr[hi]
{ 1, 0, 1, 1, 1 }, // arr[lo] = arr[hi]
{ 3, 4, 5, 2, 3 }, // arr[lo] = arr[hi]
{ 1, 2, 3, 4, 5 }, // arr[lo] < arr[hi]
{ 3, 4, 5, 1, 2 }, // arr[lo] > arr[hi]
};
// 测试
for (int i = 0; i < arr.length; i++) {
System.out.println(searchMin(arr[i]));
}
}
public static int searchMin(int[] arr) throws Exception {
if (arr == null || arr.length == 0) {
throw new Exception("输入数组为空!!!");
}
int lo = 0;
int hi = arr.length - 1;
int mid = lo;
while (arr[lo] >= arr[hi]) {
// 如果移动到lo和hi相邻了,则此时的arr[hi]就是要找的值
if (hi - lo == 1) {
mid = hi;
break;
}
mid = lo + (hi - lo)/2;
// 当arr[lo] = arr[mid] = arr[hi]时,在lo~hi之间顺序查找
if (arr[lo] == arr[hi] && arr[lo] == arr[mid]) {
return minInOrder(arr, lo, hi);
}
// 二分法指针移动
if (arr[mid] >= arr[lo]) {
lo = mid;
}else if(arr[mid] <= arr[hi]){
hi = mid;
}
}
return arr[mid];
}
/** 顺序查找 */
private static int minInOrder(int[] arr, int lo, int hi) {
int res = arr[lo];
for (int i = lo + 1; i <= hi; i++) {
if (res > arr[i]) { // 找到
res = arr[i];
break;
}
}
return res;
}
}
面试题12: 矩阵中的路径
分析:
public class Demo12 {
public static void main(String[] args) {
char[] matrix = {
'a', 'b', 't', 'g',
'c', 'f', 'c', 's',
'j', 'd', 'e', 'h' };
char[] path = "bfce".toCharArray();
boolean b = hasPath(matrix, 3, 4, path);
System.out.println(b);
}
public static boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
if (matrix == null || rows < 1 || cols < 1 || str == null) {
return false;
}
boolean[] isVisited = new boolean[rows * cols];
for (boolean v : isVisited) {
v = false;
}
int pathLength = 0;
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (matrix[row * cols + col] == str[pathLength])
if (hasPathCore(matrix, rows, cols, row, col, str, pathLength, isVisited))
return true;
}
}
return false;
}
private static boolean hasPathCore(char[] matrix, int rows, int cols, int row,
int col, char[] str, int pathLength, boolean[] isVisited) {
if (row < 0 || col < 0 || row >= rows || col >= cols
|| isVisited[row * cols + col] == true
|| str[pathLength] != matrix[row * cols + col])
return false;
if (pathLength == str.length - 1)
return true;
boolean hasPath = false;
isVisited[row * cols + col] = true;
hasPath = hasPathCore(matrix, rows, cols, row - 1, col, str,
pathLength + 1, isVisited)
|| hasPathCore(matrix, rows, cols, row + 1, col, str, pathLength + 1, isVisited)
|| hasPathCore(matrix, rows, cols, row, col - 1, str, pathLength + 1, isVisited)
|| hasPathCore(matrix, rows, cols, row, col + 1, str, pathLength + 1, isVisited);
if (!hasPath) {
isVisited[row * cols + col] = false;
}
return hasPath;
}
}
面试题13:机器人的运动范围
public class Demo13 {
public static void main(String[] args) {
int threshold = 3, rows = 3, cols = 3;
int count = movingCount(threshold, rows, cols);
System.out.println(count);
}
public static int movingCount(int threshold, int rows, int cols) {
// 输入参数有问题的直接返回0
if (threshold < 0 || rows <= 0 || cols <= 0) {
return 0;
}
// 初始化标记矩阵
boolean[] visited = new boolean[rows * cols];
for (int i = 0; i < rows * cols; i++) {
visited[i] = false;
}
// 统计满足条件的数量
int count = movingCountCore(threshold, rows, cols, 0, 0, visited);
return count;
}
private static int movingCountCore(int threshold, int rows, int cols,
int row, int col, boolean[] visited) {
int count = 0;
// check检查是否越界,坐标数位之和,是否访问过
if (check(threshold, rows, cols, row, col, visited)) {
visited[row * cols + col] = true; //都满足,标记一下该位置访问过了
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, col + 1, visited);
}
return count;
}
// 判断当前格子是否满足以下条件:check检查是否越界,坐标数位之和 <= threshold,没访问过
private static 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;
}
// 用于计算数位相加的和
private static int getDigitSum(int number) {
int sum = 0;
while (number > 0) {
sum += number % 10;
number /= 10;
}
return sum;
}
}
面试题14:剪绳子
public class Demo14 {
public static void main(String[] args) {
int length = 7;
System.out.println(maxProductAfterCutting1(length));
System.out.println(maxProductAfterCutting2(length));
}
// 动态规划解:状态转移 f(n) = max(f(i)*f(n-i)),该方法是自底向上的解法
public static int maxProductAfterCutting1(int length) {
// 边界条件
if (length < 2) {
return 0;
}
if (length == 2) {
return 1;
}
if (length == 3) {
return 2;
}
// 初始状态
int[] products = new int[length + 1];
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) {
int product = products[j] * products[i - j];
if (max < product) {
max = product;
}
products[i] = max;
}
}
return products[length];
}
// 贪心算法解:当绳子长度 >= 5 时,尽可能多的把绳子剪长度为3的绳子
// 该方法需要理论证明尽可能分成3等分能得到最大值
public static int maxProductAfterCutting2(int length) {
if (length < 2) {
return 0;
}
if (length == 2) {
return 1;
}
if (length == 3) {
return 2;
}
// 分3段数
int timesOf3 = length / 3;
// 如果余数是1,则把最后的一个3与1合成4,后面才能被2等分
if (length - timesOf3 * 3 == 1) {
timesOf3 -= 1;
}
// 剩余部分分2段数
int timesOf2 = (length - timesOf3 * 3) / 2;
// 计算乘积
return (int) (Math.pow(3, timesOf3)) * (int) (Math.pow(2, timesOf2));
}
}
面试题15:二进制中1的个数
public class Demo15 {
public static void main(String[] args) {
/**
* 顺便学习一下二进制:一个整形的共32位=4字节,最高位是符号位,符号位是0表示是正数
* 正数直接用原码表示,负数用补码表示,补码 = 反码 + 1
*/
int n = 0B00010100100; // 3个
System.out.println(countOneInBinary(n));
System.out.println(countOneInBinary2(n));
}
private static int countOneInBinary(int n) {
int count = 0;
int flag = 1;
while (flag != 0) {
if ((n & flag) != 0) {
count++;
}
flag <<= 1;
}
return count;
}
/**
* 把一个整数减去1,再和原整数做与运算,会把该整数最右边的1变成0。
* 所以一个整数二进制中有多少个1,就可以进行多少次这样的操作。
*/
private static int countOneInBinary2(int n) {
int count = 0;
while (n != 0) {
++count;
n = (n - 1) & n;
}
return count;
}
}
面试题16:数值的整数次方
public class Demo16 {
public static void main(String[] args) {
System.out.println(power(-2, -3));
System.out.println(power(-2, 0));
System.out.println(power(-2, 3));
System.out.println(power(0, -3));
System.out.println(power(0, 0));
System.out.println(power(0, 3));
System.out.println(power(2, -3));
System.out.println(power(2, 0));
System.out.println(power(2, 3));
}
private static double power(double base, int exponent) {
// 无意义情况
if (base == 0) {
return 0; // 无意义情况返回0算了
}
if (exponent == 0) {
return 1;
}
// 边界条件
if (exponent == 1) {
return base;
}
// 负数情况
if (exponent < 0) {
return 1 / power(base, -exponent);
}
// 递归
return ((exponent & 1) == 0) ?
power(base, exponent >> 1)* power(base, exponent >> 1)
: base* power(base, exponent - 1 >> 1)* power(base, exponent - 1 >> 1);
}
测试输出:
-0.125
1.0
-8.0
0.0
0.0
0.0
0.125
1.0
8.0
面试题17:打印从1到最大的n位数
public class Demo17 {
public static void main(String[] args) {
printNumbers(1); // 合法输入
printNumbers(-1); // 非法输入
}
private static void printNumbers(int n) {
if (n < 1) {
System.out.println("无法打印,输入数字小于1");
return;
}
// 用一个字符数组模拟大数,最高位如果为1的话,证明打印结束
char[] bigNum = new char[n+1];
for (int i = 0; i < bigNum.length; i++) {
bigNum[i] = '0';
}
while (true) {
bigNum = addOne(bigNum); // 加1
if (bigNum[0] == '1') { // 判断是否打印结束
break;
}
printPresent(bigNum); // 打印
}
}
private static void printPresent(char[] bigNum) {
int i;
for (i = 0; i < bigNum.length; i++) {
if(bigNum[i] != '0'){
break;
}
}
for (int j = i; j < bigNum.length; j++) {
System.out.print(bigNum[j]);
}
System.out.println();
}
private static char[] addOne(char[] bigNum) {
int carry = 0;
// 加1
int sum = ((int)bigNum[bigNum.length - 1] - 48) + 1;
for (int i = bigNum.length - 1; i >= 0; i--) {
carry = 0; // 每次将carry置零
if ( sum <= 9) {
bigNum[i] = (char)(sum + 48);
break;
}else {
bigNum[i] = '0';
carry++;
}
sum = (int)bigNum[i-1] - 48 + carry;
}
return bigNum;
}
}
1
2
3
4
5
6
7
8
9
无法打印,输入数字小于1
面试题18:删除链表的节点
class ListNode {
int val;
ListNode next;
public ListNode(int val) {
this.val = val;
}
}
public class Demo18 {
public static void main(String[] args) {
ListNode head = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
head.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
ListNode p = head;
while (p != null) {
System.out.print(p.val + " ");
p = p.next;
}
System.out.println();
p = head;
removeNode(head, node5);
removeNode(head, node2);
while (p != null) {
System.out.print(p.val + " ");
p = p.next;
}
}
public static ListNode removeNode(ListNode head, ListNode toBeDeleted) {
if (head == null || toBeDeleted == null) {
return head;
}
if (toBeDeleted.next != null) { // 不是尾结点
toBeDeleted.val = toBeDeleted.next.val;
toBeDeleted.next = toBeDeleted.next.next;
} else if (head == toBeDeleted) { // 是尾结点,同时也是头结点
head = null;
} else {// 是尾结点
ListNode p = head;
while (p.next != toBeDeleted) {
p = p.next;
}
p.next = null;
}
return head;
}
}
class ListNode {
int val;
ListNode next;
public ListNode(int x) {
this.val = x;
}
}
public class Demo19 {
public static void main(String[] args) {
// 测试,最前面,中间,最后都有重复的一个链表
ListNode head = new ListNode(1);
ListNode node2 = new ListNode(1);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(4);
ListNode node6 = new ListNode(6);
ListNode node7 = new ListNode(7);
ListNode node8 = new ListNode(7);
head.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node6;
node6.next = node7;
node7.next = node8;
System.out.print("删除重复之前:");
ListNode p = head;
while (p != null) {
System.out.print(p.val + " ");
p = p.next;
}
System.out.print("\n删除重复之后:");
p = removeDupliactions(head);
while (p != null) {
System.out.print(p.val + " ");
p = p.next;
}
}
private static ListNode removeDupliactions(ListNode head) {
if(head == null) return head;
ListNode dummy = new ListNode(-1);
dummy.next = head; // 为了防止最开始是重复元素,给链表加一个哑结点
ListNode p = head;
ListNode preNode = dummy;
while(p != null && p.next != null){
// 如果遇见两个重复的
if(p.val == p.next.val){
int val = p.val; // 将重复值暂存
while(p != null && val == p.val){ // p一直移动到不重复的位置
p = p.next;
}
preNode.next = p; // 将preNode指向p
}else{// 如果没遇到重复的,preNode和p同步向后走
preNode = p;
p = p.next;
}
}
return dummy.next;
}
}
测试输出:
删除重复之前:1 1 3 4 4 6 7 7
删除重复之后:3 6
面试题19:正则表达式匹配
public class Demo20 {
public static void main(String[] args) {
// 测试用例
String[] str = { null, "", "aaa" };
String[] patterns = { null, "", "a.a", "ab*ac*a", "aa.a", "ab*a" };
// 测试
for (int i = 0; i < str.length; i++) {
for (int j = 0; j < patterns.length; j++) {
boolean bool = match(str[i], patterns[j]);
System.out.print(bool + " ");
}
System.out.println();
}
}
/**
* 思路,从后向前遍历
* 1. 如果遇到一个普通字符就将str和pattern作比较,然后指针同时向前移动
* 2. 如果遇到‘.’就不用比较直接一起向前移动指针
* 3. 如果遇到‘*’,就将模式串指针向前移动一位,该位置的字符,模式串指针继续向前移动直到不等于该字符
* 特殊情况:
* 1. 如果模式串或者字符串为null, 直接返回false
* 2. 如果模式串长度小于str也返回false
* 3. 如果
* @param str 字符串
* @param pattern 模式
* @return 匹配成功返回true
*/
private static boolean match(String str, String pattern) {
if (str == null || pattern == null) {
return false;
}
int sp = str.length() - 1; // 字符串指针
int pp = pattern.length() - 1; // 模式串指针
if (pp < sp) {
return false;
}
while (pp >= 0 && sp >= 0) {
if (pattern.charAt(pp) == '.') {
pp--;
sp--;
} else if (pattern.charAt(pp) == '*') {
pp--;
char any = pattern.charAt(pp);
while (pattern.charAt(pp) == any) {
pp--;
}
} else {
if (pattern.charAt(pp) == str.charAt(sp)) {
pp--;
sp--;
} else {
return false;
}
}
}
/**
* while循环遍历完存在3中情况
* 1. sp < 0 pp < 0 这时同步匹配完成 true
* 2. sp < 0 pp >= 0 和 sp >= 0 pp < 0 都有一个没用完,返回false
*/
return (sp < 0 && pp < 0) ? true : false;
}
}
测试输出:
false false false false false false
false true false false false false
false false true true false false
面试题20:表示数值的字符串
说明:非正则表达式实现不能完全通过测试,正则表达式是对的
public class Demo20 {
public static void main(String[] args) {
Demo20 demo = new Demo20();
/**
* 一个小数的形式为:[+|-][整数][.小数][e|E +|- 整指数]
* 1. 所有的正负号都可有可无
* 2. 小数和整数部分至少有其一
* 3. 若有e|E,其后面必须且只能出现[+|- 整指数]
* 4. 其他字符不能出现
*/
// 是数值的字符串测试用例
String[] trueStr = { "+100", "5e2", "-123", "3.1416", ".0e2", "1E-1",
"+31.4e-1", ".12", "12" };
// 不是字符串的测试用例
String[] falseStr = {"", "12e", "1a3.14", "1.2.3", "+-5",
"12e+5.4" };
// 真测试
for (int i = 0; i < trueStr.length; i++) {
boolean bool = demo.isNumeric2(trueStr[i].toCharArray());
System.out.print(bool + " ");
}
// 假测试
System.out.println();
for (int i = 0; i < falseStr.length; i++) {
boolean bool = demo.isNumeric2(falseStr[i].toCharArray());
System.out.print(bool + " ");
}
}
/*private int index = 0;
public boolean isNumeric1(char[] str) {
if (str.length < 1)
return false;
boolean flag = scanInteger(str);
if (index < str.length && str[index] == '.') {
index++;
flag = scanUnsignedInteger(str) || flag;
}
if (index < str.length && (str[index] == 'E' || str[index] == 'e')) {
index++;
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++;
return start < index; // 是否存在整数
}*/
// 正则表达式解法
public boolean isNumeric2(char[] str) {
if (str.length < 1) {
return false;
}
String string = String.valueOf(str);
return string.matches("[\\+\\-]?\\d*(\\.\\d+)?([eE][\\+\\-]?\\d+)?");
}
}
测试输出:
true true true true true true true true true
false false false false false false