1、最长有效括号(栈)
(1)题目描述
给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号 子串 的长度。
左右括号匹配,即每个左括号都有对应的右括号将其闭合的字符串是格式正确的,比如 "(()())"。
(2)解题思路
遇到“(”入栈,遇到“)”出栈。
(3)错误解法
#include <string.h>
#define MAX(a, b) ((a) > (b) ? a : b)
int longestValidParentheses(char* s) {
// ()(()会输出4 但是答案为2
// 遇到(压入栈中,遇到)进行出栈,如果出栈失败c_ans重置为0,否则c_ans++,同时不断更新ans
int ans = 0;
int n = strlen(s);
char z[n];
int bottom = -1; // 定义栈尾指针
int c_ans = 0;
for (int i = 0; i < n; i++) {
if (bottom == -1 && s[i] == ")") continue;
if (s[i] == '(') {
z[++bottom] = '('; // 入栈
} else {
if (z[bottom] == '(') { // 出栈
c_ans += 2;
bottom--;
ans = MAX(ans, c_ans);
} else {
c_ans = 0;
}
}
}
return ans;
}
(4)正确解法,栈中存储下标
#include <string.h>
#define MAX(a, b) ((a) > (b) ? a : b)
int longestValidParentheses(char* s) {
int n = strlen(s);
int ans = 0;
int stack[n+1];
int top = -1;
stack[++top] = -1; // 初始参考
for (int i = 0; i < n; i++) {
if (s[i] == '(') {
stack[++top] = i; // 入栈
} else {
--top; // 先出栈
if (top == -1) { // 说明“)”目前多余
stack[++top] = i; // 新参考
} else {
ans = MAX(ans, i - stack[top]);
}
}
}
return ans;
}
2、柱状图中最大的矩形(栈)
(1)题目描述
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
(2)解题思路
第一种解法是暴力,枚举所有left和right,然后不断更新答案.
第二种解法是维护一个单调递增的栈。穷举:计算了以所有柱子高度为矩形高度的情况。
如果遇到一个高度a比栈顶索引对应的高度b小,即,弹出栈顶,并把a的索引作为右边界j。
把当前栈顶作为左边界i。 j-i-1就是宽度。
栈是更好的发现左边界和右边界。
(3)解法
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int largestRectangleArea(int* heights, int heightsSize) {
int* stack = (int*)malloc(sizeof(int) * (heightsSize + 2));
int top = -1;
int max_area = 0;
for (int i = -1; i <= heightsSize; i++) {
// 左右0
int cur_h = (i == -1 || i == heightsSize) ? 0 : heights[i];
while (top != -1 && ((stack[top] == -1) ? 0 : heights[stack[top]]) > cur_h) {
int h = heights[stack[top--]];
// 此时栈顶就是左边界,当前 i 就是右边界
int left_idx = stack[top];
int width = i - left_idx - 1;
max_area = MAX(max_area, h * width);
}
stack[++top] = i;
}
free(stack);
return max_area;
}
3、寻找两个正序数组的中位数(二分)
(1)题目描述
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
(2)解题思路
先合并数组,再二分。
(3)解法
#include <stdio.h>
#include <stdlib.h>
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
int totalSize = nums1Size + nums2Size;
int* merged = (int*)malloc(sizeof(int) * totalSize); // 开辟内存
// 合并两个数组
int i = 0, j = 0, k = 0;
while (i < nums1Size && j < nums2Size) {
if (nums1[i] < nums2[j]) {
merged[k++] = nums1[i++];
} else {
merged[k++] = nums2[j++];
}
}
// 处理剩余的元素
if (nums1Size > nums2Size) while (i < nums1Size) merged[k++] = nums1[i++];
else while (j < nums2Size) merged[k++] = nums2[j++];
// 计算中位数
if (totalSize % 2 == 1) return merged[totalSize / 2];
else return (merged[totalSize / 2 - 1] + merged[totalSize / 2]) / 2.0;
}
4、N 皇后(DFS)
(1)题目描述
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
(2)解题思路
DFS+回溯
(3)解法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
// 检查在(row, col)放置皇后是否安全
bool isSafe(int row, int col, int n, char** board) {
// 1.检查正上方
for (int i = 0; i < row; i++) {
if (board[i][col] == 'Q') return false;
}
// 2.检查左上方对角线
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q') return false;
}
// 3.检查右上方对角线
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q') return false;
}
return true;
}
// 递归函数
void backtrack(int row, int n, int* returnSize, char*** res, char** board) {
if (row == n) { // 递归出口,存储答案
res[*returnSize] = (char**)malloc(n * sizeof(char*));
for (int i = 0; i < n; i++) {
res[*returnSize][i] = (char*)malloc((n+1)*sizeof(char));
strcpy(res[*returnSize][i], board[i]);
}
(*returnSize)++;
return;
}
for (int col = 0; col < n; col++) {
if (isSafe(row, col, n, board)) {
board[row][col] = 'Q';
backtrack(row + 1, n, returnSize, res, board);
board[row][col] = '.'; // for+回溯枚举所有情况
}
}
}
// 主函数
char*** solveNQueens(int n, int* returnSize, int** returnColumnSizes) {
// 1.准备结果数组
char*** res = (char***)malloc(1000 * sizeof(char**));
*returnSize = 0;
// 2.初始化空的棋盘
char** board = (char**)malloc(n * sizeof(char*));
for (int i = 0; i < n; i++) {
board[i] = (char*)malloc((n + 1) * sizeof(char));
for (int j = 0; j < n; j++) board[i][j] = '.';
board[i][n] = '\0';
}
// 3.开始递归
backtrack(0, n, returnSize, res, board);
// 4.记录每个解的行数
*returnColumnSizes = (int*)malloc((*returnSize) * sizeof(int));
for (int i =0; i < *returnSize; i++) {
(*returnColumnSizes)[i] = n;
}
return res;
}
5、最小覆盖子串(双指针)
(1)题目描述
给定两个字符串 s 和 t,长度分别是 m 和 n,返回 s 中的 最短窗口 子串,使得该子串包含 t 中的每一个字符(包括重复字符)。如果没有这样的 子串,返回空字符串 ""。
测试用例保证答案唯一。
(2)解题思路
滑动窗口
(3)解法
#include <string.h>
char* minWindow(char* s, char* t) {
int m = strlen(s), n = strlen(t);
int reference[128] = {0};
for (int i = 0; i < n; i++) reference[t[i]]++;
int window[128] = {0}; // 记录当前窗口每个字符出现的次数
int total_kinds = 0;
int current_kinds = 0;
for (int i = 0; i < 128; i++) if (reference[i] > 0) total_kinds++;
int left = 0, right = 0;
int ans_len = m + 1; // 设置一个不可能的答案
int start = 0;
while (right < m) {
if (reference[s[right]] > 0) {
window[s[right]]++;
if (window[s[right]] == reference[s[right]]) {
current_kinds++;
}
}
while (current_kinds == total_kinds) {
if ((right - left + 1) <= ans_len) {
start = left; // 更新起点位置
ans_len = right - left + 1; // 更新长度
}
// 收缩窗口(很标准的收缩,只有当满足情况,才进行滑动左边界进一步判断)
if (reference[s[left]] > 0) {
window[s[left]]--;
if (window[s[left]] < reference[s[left]]) current_kinds--;
}
left++;
}
right++;
}
if (ans_len == m + 1) return "";
char* res = (char*)malloc((ans_len+1) * sizeof(char));
strncpy(res, s + start, ans_len + 1); //+1表示连'\0'一同复制过去
return res;
}
6、接雨水(动态规划、双指针)
(1)题目描述
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
(2)解题思路
木桶原理
(3)解题
1)动态规划
#define MIN(a, b) ((a) < (b) ? a : b)
#define MAX(a, b) ((a) > (b) ? a : b)
int trap(int* height, int heightSize) {
// 定义数组left_max_height,left_max_height[i]表示位置i左边柱子的最高高度。
// 定义数组right_max_height,right_max_height[i]表示位置i右边柱子的最高高度。
int left_max_height[heightSize];
int right_max_height[heightSize];
for (int i = 0; i < heightSize; i++) {
left_max_height[i] = -1;
right_max_height[i] = -1;
}
// 动态规划
for (int i = 1; i < heightSize-1; i++) {
left_max_height[i] = MAX(height[i-1], left_max_height[i-1]);
}
for (int i = heightSize-2; i > 0; i--) {
right_max_height[i] = MAX(height[i+1], right_max_height[i+1]);
}
int ans = 0;
for (int i = 1; i < heightSize - 1; i++) {
if (MIN(left_max_height[i], right_max_height[i]) > height[i]) {
ans += MIN(left_max_height[i], right_max_height[i]) - height[i];
}
}
return ans;
}
2)双指针
int trap(int* height, int heightSize) {
int left = 0, right = heightSize - 1;
int l_max = 0, r_max = 0;
int ans = 0;
while (left < right) {
// 更新左右两边的最高纪录
if (height[left] > l_max) l_max = height[left];
if (height[right] > r_max) r_max = height[right];
// 核心逻辑:哪边矮,就计算哪边并移动哪边
if (l_max < r_max) {
// 左边是短板,水量由 l_max 决定
ans += l_max - height[left];
left++;
} else {
// 右边是短板,水量由 r_max 决定
ans += r_max - height[right];
right--;
}
}
return ans;
}