以下题目来自力扣,有的答案和讲解也来自于力扣
上面的是力扣的题,下面的是非力扣的题
22. 括号生成[m](20211029)
数字 n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
有效括号组合需满足:左括号必须以正确的顺序闭合。
解法一:
暴力
public List<String> generateParenthesis(int n) {
List<String> resultList = new ArrayList<>();
generateAll(new char[2 * n], 0, resultList);
return resultList;
}
public void generateAll(char[] current, int pos, List<String> result) {
if (pos == current.length) {
if (valid(current)) {
result.add(new String(current));
}
} else {
current[pos] = '(';
generateAll(current, pos + 1, result);
current[pos] = ')';
generateAll(current, pos + 1, result);
}
}
public boolean valid(char[] current) {
int balance = 0;
for (char c : current) {
if (c == '(') {
balance ++;
} else if (c == ')') {
balance --;
}
if (balance < 0) {
return false;
}
}
return balance == 0;
}
解法二:
可以通过回溯算法,回溯算法在思考的时候,可以画决策树,然后帮助理解
只在序列仍然保持有效时才添加 '('
or ')',
可以通过跟踪到目前为止放置的左括号和右括号的数目来做到这一点,
如果左括号数量不大于 n,我们可以放一个左括号。如果右括号数量小于左括号的数量,我们可以放一个右括号。
对于回溯法,涉及到一个问题,为什么走完这一步,进入下一步之后,再下一行代码中要把这步操作删掉,如:
因为:回溯法的本质就是遍历选择树,代码中第二行选择了,第三行进入下一步的回溯,第四部返回,删除当前选择,再选择另外一个选项。
public List<String> generateParenthesis(int n) {
List<String> resultList = new ArrayList<>();
backtrack(resultList, new StringBuilder(), 0, 0, n);
return resultList;
}
public void backtrack(List<String> ans, StringBuilder cur, int left, int right, int max) {
if (cur.length() == max * 2) {
ans.add(cur.toString());
return;
}
if (left < max) {
cur.append('(');
backtrack(ans, cur, left + 1, right, max);
cur.deleteCharAt(cur.length() - 1);
}
if (right < left) {
cur.append(')');
backtrack(ans, cur, left, right + 1, max);
cur.deleteCharAt(cur.length() - 1);
}
}
84. 柱状图中最大的矩形[hard](211023)
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
解法一:
可以枚举柱子的组合,使用两重for 循环的暴力解法。
解法二:
枚举柱子,分别向左右找。这个的思想就是以当前的柱子为中心,同时认为当前柱子的高度是组合中最低的,想找到其他的柱子和它配合,找到面积最大化的组合。
public int largestRectangleArea(int[] heights) {
int length = heights.length;
int ans = 0;
for (int i = 0; i < length; i++) {
int height = heights[i];
int left = i;
int right = i;
while (left -1 >= 0 && heights[left -1] >= height) {
left --;
}
while (right +1 < length && heights[right + 1] >= height) {
right ++;
}
ans = Math.max((right - left + 1) * height, ans);
}
return ans;
}
两种方法时间复杂度都是O(n2),超出时间限制。
解法三:
官方题解叫其为“单调栈”
单调栈的做法,就是为了维护每个柱子的左侧或者右侧的,最近的,小于其高度的柱子。
这个思想类似于“数组的前缀和”。
此题和第三题有区别,可以对比看看。
单调栈的解法就是通过一个栈,维护出上述标红的数组。
public int largestRectangleArea(int[] heights) {
int n = heights.length;
// 对于每个柱子,左边第一个小于这个柱子的高度的柱子的下标
int[] left = new int[n];
// 对于每个柱子,右边第一个小于这个柱子的高度的柱子的下标
int[] right = new int[n];
// 存储柱子的下标
Stack<Integer> stack = new Stack<Integer>();
for (int i = 0; i < n; i++) {
while (!stack.isEmpty()
&& heights[stack.peek()] >= heights[i]){
// 如果不空
// 且
// 栈中第一个元素 大于等于当前位置的柱子的高度
stack.pop();
}
left[i] = (stack.isEmpty() ? -1 : stack.peek());
stack.push(i);
}
stack.clear();
for (int i = n - 1; i>= 0; i--) {
while (!stack.isEmpty()
&& heights[stack.peek()] >= heights[i]) {
stack.pop();
}
right[i] = (stack.isEmpty()) ? n : stack.peek();
stack.push(i);
}
// 数组left 和right 维护完成之后,开始计算结果
int ans = 0;
for (int i = 0; i < n; i++) {
ans = Math.max((right[i] - left[i] - 1) * heights[i], ans);
}
return ans;
}
解法四:
解法三的优化版,复习的时候再看吧。
85. 最大矩形[hard](211023)
给定一个仅包含 0
和 1
、大小为 rows x cols
的二维二进制矩阵,找出只包含 1
的最大矩形,并返回其面积。
解法1:
对于这个矩形,使用二维数组 left 记录,其中 left[i][j] 为矩阵第i 行第j 列元素的左边连续1 的数量。
随后,对于矩阵中任意一个点,我们枚举以该点为右下角的全 11 矩形。
这样矩形的宽度就是从matrix[i][j] 开始的向上,matrix[i - 1][j], matrix[i - 2][j] ... 的最小值,得到一个结果。
时间复杂度是O(m * n * m)。
详情可参考力扣官方解析。
public int maximalRectangle(char[][] matrix) {
int m = matrix.length;
if (m == 0) {
return 0;
}
int n = matrix[0].length;
int[][] left = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '1') {
left[i][j] = (j == 0 ? 0 : left[i][j - 1]) + 1;
}
}
}
int result = 0;
for (int i = 0; i < m; i ++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] != '0') {
int width = left[i][j];
int area = width;
for (int k = i - 1; k >= 0; k --) {
width = Math.min(left[k][j], width);
area = Math.max(area, width * (i - k + 1));
}
result = Math.max(area, result);
}
}
}
return result;
}
解法2:
对于上述解法,每一列生成“柱状图”之后,调用84 题的解法,就可以了。
复习的时候再实现吧。
112. 路径总和[easy](211025)
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。
解法一:
使用广度优先搜索,类似树的层序遍历,但是不需要记录queue 中节点的个数。
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
Queue<TreeNode> nodeQueue = new LinkedList<>();
Queue<Integer> valueQueue = new LinkedList<>();
nodeQueue.offer(root);
valueQueue.offer(root.val);
while (!nodeQueue.isEmpty()) {
// 这个不像层序遍历,这个不需要感知queue 的长度
TreeNode tempNode = nodeQueue.poll();
Integer tempVal = valueQueue.poll();
// 如果当前节点是叶子节点,那么就进行判断
if (tempNode.left == null && tempNode.right == null) {
if (tempVal == targetSum) {
return true;
}
continue;
}
// 子节点进队列
if (tempNode.right != null) {
nodeQueue.offer(tempNode.right);
valueQueue.offer(tempNode.right.val + tempVal);
}
if (tempNode.left != null) {
nodeQueue.offer(tempNode.left);
valueQueue.offer(tempNode.left.val + tempVal);
}
}
return false;
}
解法二:
递归,递归不能了解,得理解。
由简单到深刻。
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return targetSum == root.val;
}
return hasPathSum(root.left, targetSum - root.val)
|| hasPathSum(root.right, targetSum - root.val);
}
131. 分割回文串
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
239. 滑动窗口最大值[hard](2021/11/04)
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
解法一:
优先队列
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
// 队列中维护一个二元组<num, index>
PriorityQueue<int[]> pq = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] != o2[0] ? o2[0] - o1[0] : o2[1] - o1[1];
}
});
for (int i = 0; i < k; i++) {
pq.offer(new int[]{nums[i], i});
}
int[] ans = new int[n - k + 1];
ans[0] = pq.peek()[0];
for(int i = k; i < n; i++) {
pq.offer(new int[]{nums[i], i});
// 判断当前peek 是否在滑动窗口中
while (pq.peek()[1] <= i - k) {
pq.poll();
}
ans[i - k + 1] = pq.peek()[0];
}
return ans;
}
解法二:单调队列
求滑动窗口最大值,如果当前滑动窗口有两个下标i 和j,其中i 在j 的左侧,并且i 对应的元素不大于j 对应的元素,那么:
有精神头的时候再看吧。
判断两棵二叉树是否镜像对称(211031)
递归:
public boolean judge(TreeNode root1, TreeNode root2) {
if (root1 == null && root2 == null) {
return true;
}
if (root1 == null || root2 == null) {
return false;
}
if (root1.val != root2.val) {
return false;
}
return judge(root1.left, root2.right) && judge(root1.right, root2.left);
}
迭代:
public boolean judge(TreeNode root1, TreeNode root2) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root1);
queue.offer(root2);
while (!queue.isEmpty()) {
root1 = queue.poll();
root2 = queue.poll();
if (root1 == null && root2 == null) {
continue;
}
if (root1 == null || root2 == null) {
return false;
}
if (root1.val != root2.val) {
return false;
}
queue.offer(root1.left);
queue.offer(root2.right);
queue.offer(root1.right);
queue.offer(root2.left);
}
return true;
}