剑指offer学习3(Java语言版)

面试题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 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值