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

Posted whc__

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) {
         // 不满足条件

以上是关于算法---- 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很强)