算法---- Leetcode剑指offer-Java版题解

Posted TheWhc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法---- Leetcode剑指offer-Java版题解相关的知识,希望对你有一定的参考价值。

剑指offer题解

1. 数组中重复的数字

/**
	 * 思路: 原地哈希,将nums[i]不断与nums[nums[i]]交换,直到nums[i] = i为止
	 *      如果在交换过程中出现nums[nums[i]]位置已经有元素了,那么就返回结果nums[i]
	 * 时间复杂度: O(n)
	 * 空间复杂度: O(1)
	 */
	public int findRepeatNumber(int[] nums) 
		for (int i = 0; i < nums.length; i++) 
			while(nums[i] != i) 
				if(nums[nums[i]] == nums[i]) 
					return nums[i];
				
				int temp = nums[i];
				nums[i] = nums[nums[i]];
				nums[temp] = temp;
			
		
		return -1;
	

2. 二维数组中的查找

// 暴力解法,时间复杂度O(n*m) 空间复杂度O(1)
	/*public boolean findNumberIn2DArray(int[][] matrix, int target) 
		for (int i = 0; i < matrix.length; i++) 
			for (int j = 0; j < matrix[i].length; j++) 
				if(matrix[i][j] == target) 
					return true;
				
			
		

		return false;
	*/

	/**
	 * 思路: 二分查找
	 * 枚举每行的最后一个元素x,进行二分查找
	 * 如果 x == target, 则直接返回
	 * 如果 x > target, 则排除当前整一列(纵坐标减1)
	 * 如果 x < target, 则排除当前整一行(横坐标加1)
	 *
	 * 时间复杂度: O(n+m)
	 * 空间复杂度: O(1)
	 */
	public boolean findNumberIn2DArray(int[][] matrix, int target) 
		if(matrix == null || matrix.length == 0) 
			return false;
		

		int i = 0, j = matrix[0].length-1;
		while(i <= matrix.length && j >= 0) 
			if(matrix[i][j] == target) 
				return true;
			 else if(matrix[i][j] > target) 
				j--;
			 else 
				i++;
			
		

		return false;
	

3. 替换空格

/**
 * 思路: 遍历字符串,遇到空格,则添加%20
 * 时间: O(n)
 * 额外空间: O(n)
 */
public String replaceSpace(String s) 
   StringBuilder res = new StringBuilder();
   for(int i = 0; i < s.length(); i++) 
      if(s.charAt(i) == ' ') 
         res.append("%20");
       else 
         res.append(s.charAt(i));
      
   
   return res.toString();

4. 从尾到头打印链表

/*
	 * 思路: 用栈存储遍历得到的数据,然后再依次出栈添加到结果数组中
	 * 时间: O(n)
	 * 空间:O(n)
	 */
	/*public int[] reversePrint(ListNode head) 
		Stack<Integer> stack = new Stack<>();
		while(head != null) 
			stack.push(head.val);
			head = head.next;
		
		int[] res = new int[stack.size()];
		int idx = 0;
		while(!stack.isEmpty()) 
			res[idx++] = stack.pop();
		

		return res;
	*/

	/**
	 * 思路: 两次遍历链表
	 * 时间: O(n)
	 * 空间: O(1)
	 */
	public int[] reversePrint(ListNode head) 
		int count = 0;
		ListNode node = head;

		while(node != null) 
			count++;
			node = node.next;
		

		int[] res = new int[count];
		node = head;

		for (int i = count-1; i >= 0; i--) 
			res[i] = node.val;
			node = node.next;
		

		return res;
	

5. 重建二叉树

/**
	 *  思路: 递归
	 *  1. 取前序遍历的第一个元素作为根节点
	 *  2. 先切割中序数组,根据根节点查找中序数组中的位置,切割成中序左数组[i_start,i_root_index)和中序右数组[i_root_index+1,i_end)   【左闭右开】
	 *  3. 再切割前序数组,根据中序数组中左数组的长度进行切割,切割成前序左数组[p_start+1, p_start+1+(i_root_index-i_start))和前序右数组[p_start+1+(i_root_index-i_start),p_end)
	 */

	// 存储前序遍历中的根节点在中序遍历的下标位置
	HashMap<Integer, Integer> map = new HashMap<>();
	public TreeNode buildTree(int[] preorder, int[] inorder) 

		// 初始化赋值
		for (int i = 0; i < inorder.length; i++) 
			map.put(inorder[i], i);
		

		// 左闭右开区间
		return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length);
	

	public TreeNode buildTreeHelper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) 
		// 递归终止条件,前序数组遍历完,直接返回null
		if(p_start == p_end) 
			return null;
		

		int root_val = preorder[p_start];
		// 中序数组中根节点的下标位置, 根据此下标划分中序数组中的 中序左数组 和 中序右数组
		int i_root_index = map.get(root_val);
		// 构建根节点
		TreeNode root = new TreeNode(root_val);
		// 递归构造左子树
		root.left = buildTreeHelper(preorder, p_start+1, p_start + 1 + (i_root_index-i_start), inorder, i_start, i_root_index);
		// 递归构造右子树
		root.right = buildTreeHelper(preorder, p_start+1+(i_root_index-i_start), p_end, inorder, i_root_index+1, i_end);

		return root;
	

6. 用两个栈实现队列

Stack<Integer> pushStack;
	Stack<Integer> popStack;

	public _剑指_Offer_09_用两个栈实现队列() 
		this.pushStack = new Stack<>();
		this.popStack  = new Stack<>();
	

	public void appendTail(int value) 
		pushStack.add(value);
	

	public int deleteHead() 
		// 弹出元素的栈中仍有元素,则返回出栈元素
		if(popStack.size() > 0) 
			return popStack.pop();
		

		// 弹出元素的栈为空,入栈元素的栈也为空,返回-1
		if(popStack.size() == 0) return -1;

		// 入栈元素的栈为空,则不断从出栈的栈弹出元素
		while(pushStack.size() > 0) 
			popStack.add(pushStack.pop());
		

		return popStack.pop();
	

7. I_斐波那契数列

public int fib(int n) 
		if(n < 2) 
			return n;
		
		int a = 0;
		int b = 1;
		for(int i = 2; i <= n; i++) 
			int sum = (a + b) % 1000000007;
			a = b;
			b = sum;
		
		return b;
	

8. II_青蛙跳台阶问题

/**
	 * 思路: 动态规划
	 * 1. dp[i] = dp[i-1] + dp[i-2]
	 * 2. 由于dp[i]只与前面两项相关,所以用三个变量sum,a,b记录,优化空间,降为O(1)
	 *
	 * 时间:O(n)
	 * 空间:O(1)
	 */
	public int numWays(int n) 
		if(n < 2) 
			return 1;
		

		int a = 1;
		int b = 1;
		int sum = 0;
		for(int i = 2; i <= n; i++) 
			sum = (a + b) % 1000000007;
			a = b;
			b = sum;
		

		return b;
	

9. 旋转数组的最小数字

/**
	 * 思路: 二分查找
	 * 将中间元素mid和右边元素right相比较,缩小搜索的范围
	 * numbers[mid] < numbers[right], 则 right = mid
	 * numbers[mid] > numbers[right], 则 left = mid + 1
	 * numbers[mid] = numbers[right], 则 right--
	 */
	public int minArray(int[] numbers) 
		int left = 0;
		int right = numbers.length-1;
		while(left < right) 
			int mid = (left + right) >>> 1;
			if(numbers[mid] < numbers[right]) 
				right = mid;
			 else if(numbers[mid] > numbers[right]) 
				left = mid + 1;
			 else 
				right--;
			
		

		return numbers[left];
	

10. 矩阵中的路径

/**
	 *
	 * 思路: 深度优先搜索
	 *  1. 枚举矩阵中每个位置
	 *  2. 然后从当前位置开始深度优先搜索,从上下左右四个方向开始搜索
	 *  3. 过程中把遍历过的位置设置为一个特殊的标志符
	 * 时间复杂度: O(MN * k^3)
	 * 		需要枚举MN个起点,时间复杂度为(MN)
	 * 		方案数计算: 设字符串长度为 K ,搜索中每个字符有上、下、左、右四个方向可以选择,舍弃回头(上个字符)的方向,剩下 3 种选择,因此方案数的复杂度为 O(3^K)
	 * 空间复杂度: O(K)
	 * 		最差K=MN
	 */
	int[] dx = -1, 0, 1, 0; // 上右下左
	int[] dy = 0, 1, 0, -1;
	public boolean hasPath (char[][] matrix, String word) 
		if(matrix == null || matrix.length == 0) 
			return false;
		
		// 遍历矩阵中的每个元素,判断路径是否可达
		for(int i = 0; i< matrix.length; i++) 
			for(int j = 0; j < matrix[0].length; j++) 
				if(dfs(matrix, word, i, j, 0)) 
					return true;
				
			
		

		return false;
	

	private boolean dfs(char[][] matrix, String word, int x, int y, int k) 
		// 不满足
		if(matrix[x][y] != word.charAt(k)) 
			return false;
		
		if(k == word.length() - 1) 
			return true;
		
		char temp = matrix[x][y];
		matrix[x][y] = '/';
		// 上下左右四个方向
		for(int i = 0; i < 4; i++) 
			int a = x + dx[i];
			int b = y + dy[i];
			if(a >= 0 && a < matrix.length && b >= 0 && b < matrix[0].length) 
				if(dfs(matrix, word, a, b, k+1)) 
					return true;
				
			
		
		// 若不满足,则进行回溯
		matrix[x][y] = temp;
		return false;
	

11. 机器人的运动范围

/**
	 *  思路: 深度优先搜索
	 *
	 *  1. 设置一个visited布尔数组记录已经被访问过的节点
	 *  2. 从(0,0)位置开始出发,因此只会往右和往下两个方向走
	 *  3. 设置一个全局变量res,记录dfs过程中满足条件的位置
	 *
	 *  时间: O(n*m)
	 *  空间: O(n*m)
	 */
	/*int res = 0;
	int[] dx = 0,1; // 向右 和 向下
	int[] dy = 1,0;
	public int movingCount(int m, int n, int k) 
		if(m <= 0 || n <= 0 || k < 0) 
			return 0;
		
		boolean[][] visited = new boolean[m][n];
		dfs(visited, 0, 0, m, n, k);
		return res;
	

	private void dfs(boolean[][] visited, int x, int y, int m, int n, int k) 
		// 已被遍历过 返回
		if(visited[x][y]) 
			return;
		
		visited[x][y] = true;
		res++;
		for (int i = 0; i < 2; i++) 
			int a = x + dx[i];
			int b = y + dy[i];
			// 满足条件向右和向下搜索
			if(a >= 0 && a < m && b >= 0 && b < n && digitSum(a) + digitSum(b) <= k) 
				dfs(visited, a, b, m, n, k);
			
		
	

	private int digitSum(int x) 
		int sum = 0;
		while(x > 0) 
			sum += x % 10;
			x /= 10;
		
		return sum;
	*/


	/**
	 *  思路: 宽度优先搜索(bfs)
	 *  1. 创建visited[][]数组记录已被访问过的节点
	 *  2. 队列中存放可被访问的位置,每次弹出一个位置坐标(x,y),判断是否被访问过,
	 *  如果被访问过,则continue(易错点,应该在这里),并将其下方和右下方可以被访问的位置入队
	 *  3. 全局变量res记录队列中可以到达的格子数量
	 *  4. 队列为空结束循环,返回res
	 *
	 *  时间: O(n*m)
	 *  空间: O(n*m)
	 */
	public int movingCount(int m, int n, int k) 
		if(m <= 0 || n <= 0 || k < 0) 
			return 0;
		
		int res = 0;
		boolean[][] visited = new boolean[m][n];
		// 存储每个节点的横纵坐标
		Queue<int[]> queue = new LinkedList<>();
		queue.add(new int[]0,0);
		int[] dx = 0,1;
		int[] dy = 1,0;
		while(!queue.isEmpty()) 
			int[] x = queue.poll();
			if(visited[x[0]][x[1]]) 
				continue;
			
			res++;
			visited[x[0]][x[1]] = true;
			for(int i = 0; i < 2; i++) 
				int a = x[0] + dx[i];
				int b = x[1] + dy[i];
				if(a >= 0 && a < m && b >= 0 && b < n && digitSum(a) + digitSum(b) <= k) 
					queue.add(new int[]a,b);
				
			
		

		return res;
	

	private int digitSum(int x) 
		int sum = 0;
		while(x > 0) 
			sum += x % 10;
			x /= 10;
		
		return sum;
	




	// 如果给的数组是一维数组,需要将二维数组转化为一维数组
	// 深度优先搜索
     /*int res = 0;
     int[] dx = 0, 1; // 右下
     int[] dy = 1, 0;
     public int movingCount(int threshold, int rows, int cols) 
         if(rows <= 0 || cols <= 0 || threshold < 0) return 0;
         // 一维代替二维
         boolean[] visited = new boolean[rows * cols];
         dfs(visited, rows, cols, 0, 0, threshold);
         return res;
     

     public void dfs(boolean[] visited, int rows, int cols, int row, int col, int threshold) 
         // 不满足条件
         int index = row * cols + col;
         visited[index] = true;
         res++;
         for(int i = 0; i < 2; i++) 
             int a = row + dx[i]; // 下一个

以上是关于算法---- Leetcode剑指offer-Java版题解的主要内容,如果未能解决你的问题,请参考以下文章

算法leetcode剑指 Offer II 041. 滑动窗口的平均值(rust重拳出击)

算法leetcode剑指 Offer II 041. 滑动窗口的平均值(rust重拳出击)

算法---- Leetcode剑指offer-Java版题解

算法---- Leetcode剑指offer-Java版题解

算法leetcode|剑指 Offer 27. 二叉树的镜像|226. 翻转二叉树(rust很强)

算法leetcode|剑指 Offer 27. 二叉树的镜像|226. 翻转二叉树(rust很强)