分析,画图,代码,扩展,小结。
四步分析法
1.模拟:模拟题目的运行。
2.规律:尝试总结出题目的一般规律和特点。
3.匹配:找到符合这些特点的数据结构与算法。
4.边界:考虑特殊情况。
栈
特性:先进后出(LIFO)、后进先出。
//Java代码
Stack<Character> t = new Stack<Character>();
t.push('a');
t.push('b');
t.peek(); // 这里得到栈顶元素'b'
t.pop(); // 这里将栈顶元素'b'弹出
t.peek(); // 此时栈顶元素为'a'
t.pop(); // 这里将栈顶元素'a'弹出
例 1:判断字符串括号是否合法
【题目】字符串中只有字符'('和')'。合法字符串需要括号可以配对。比如:
输入:"()"
输出:true
解释:(),()(),(())是合法的。)(,()(,(()是非法的。
请你实现一个函数,来判断给定的字符串是否合法。
boolean isValid(String s);
当遇到左括号'('时,进行压栈操作
当遇到右括号')'时,进行弹栈操作
boolean isValid(String s) {
// 当字符串本来就是空的时候,我们可以快速返回true
if (s == null || s.length() == 0) {
return true;
}
// 当字符串长度为奇数的时候,不可能是一个有效的合法字符串
if (s.length() % 2 == 1) {
return false;
}
// 消除法的主要核心逻辑:
Stack<Character> t = new Stack<Character>();
for (int i = 0; i < s.length(); i++) {
// 取出字符
char c = s.charAt(i);
if (c == '(') {
// 如果是'(',那么压栈
t.push(c);
} else if (c == ')') {
// 如果是')',那么就尝试弹栈
if (t.empty()) {
// 如果弹栈失败,那么返回false
return false;
}
t.pop();
}
return t.empty();
}
复杂度分析:每个字符只入栈一次,出栈一次,所以时间复杂度为 O(N),而空间复杂度为 O(N),因为最差情况下可能会把整个字符串都入栈。
计数器实现
package stackTest;
import java.util.Stack;
/**
* @Author luminous
* @Description //匹配括号的数量
* @Date 11:31 2022/2/23
**/
public class Demo2 {
public static boolean isValid(String s) {
if (s.length()%2==1)
return false;
if (s==null||s.length()==0)
return true;
int leftBraceNumber=0;
for (int i=0;i<s.length();i++){
char c=s.charAt(i);
if (c=='(')
leftBraceNumber+=1;
else if (c==')'){
if (leftBraceNumber<=0)
return false;
leftBraceNumber--;
}
}
return leftBraceNumber==0;
}
public static void main(String[] args) {
System.out.println(isValid("()())"));
}
}
大鱼吃小鱼
【题目】在水中有许多鱼,可以认为这些鱼停放在 x 轴上。再给定两个数组 Size,Dir,Size[i] 表示第 i条鱼的大小,Dir[i] 表示鱼的方向 (0 表示向左游,1 表示向右游)。这两个数组分别表示鱼的大小和游动的方向,并且两个数组的长度相等。鱼的行为符合以下几个条件:
1.所有的鱼都同时开始游动,每次按照鱼的方向,都游动一个单位距离;
2.当方向相对时,大鱼会吃掉小鱼;
3.鱼的大小都不一样。
输入:Size = [4, 2, 5, 3, 1], Dir = [1, 1, 0, 0, 0]
输出:3
请完成以下接口来计算还剩下几条鱼?
package stackTest;
import java.util.Stack;
public class Demo31 {
public static int solution(int[] size, int[] dir){
//鱼的数量
int fishNumber= size.length;
// 鱼的方向
final int left=0;
final int right=1;
Stack<Integer> t =new Stack<>();
for (int i=0;i<fishNumber;i++){
int curFishDir=dir[i];
int curFishSize=size[i];
//当前鱼是否被吃
boolean hasEat=false;
// 栈中有鱼且栈中的鱼与当前的鱼方向相对,
while(!t.empty()&&dir[t.peek()]==right&&curFishDir==left){
// 判断鱼的大小,如果吃掉就继续下一条鱼
if (size[t.peek()]>curFishSize) {
hasEat=true;
break;
}
// 如果栈中的鱼比当前鱼小,弹出去栈中的鱼,并准备入栈。
t.pop();
}
// 第一次直接入栈
if (!hasEat)
t.push(i);
}
return t.size();
}
public static void main(String[] args) {
int size[] = {4, 2, 5, 3, 1};
int dir[] = {1, 1, 1, 1, 0};
System.out.println(solution(size,dir));
}
}
找出数组中右边比我小的元素
【题目】一个整数数组 A,找到每个元素:右边第一个比我小的下标位置,没有则用 -1 表示。
输入:[5, 2]
输出:[1, -1]
解释:因为元素 5 的右边离我最近且比我小的位置应该是 A[1],最后一个元素 2 右边没有比 2 小的元
素,所以应该输出 -1。
package stackTest;
import java.util.Stack;
/**
* @Author luminous
* @Description //找到右边第一个比我小的下标位置,没有则用 -1 表示
* @Date 11:31 2022/2/23
**/
public class Demo41 {
public static int[] findRightSmall(int[] A){
int[] ans=new int[A.length];
Stack<Integer> t=new Stack<>();
for (int i=0;i<A.length;i++){
while(!t.empty()&&A[t.peek()]>A[i]){
ans[t.peek()]=i;
t.pop();
}
t.push(i);
}
while(!t.empty()){
ans[t.peek()]=-1;
t.pop();
}
return ans;
}
public static void main(String[] args) {
int size[] = {1, 2, 4, 9, 4,0,5};
int dir[] = {1, 1, 0, 0, 0};
for (int i :
findRightSmall(size)) {
System.out.println(i);
}
System.out.println(findRightSmall(size));
}
}
例 4:字典序最小的 k 个数的子序列
【题目】给定一个正整数数组和 k,要求依次取出 k 个数,输出其中数组的一个子序列,需要满足:1.
长度为 k;2.字典序最小。
输入:nums = [3,5,2,6], k = 2
输出:[2,6]
解释:在所有可能的解:{[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]} 中,[2,6] 字典序最小。
所谓字典序就是,给定两个数组:x = [x1,x2,x3,x4],y = [y1,y2,y3,y4],如果 0 ≤ p < i,xp == yp 且 xi <
yi,那么我们认为 x 的字典序小于 y。
package stackTest;
import java.util.Stack;
/**
* @Author luminous
* @Description //
* 给定一个正整数数组和 k,要求依次取出 k 个数,输出其中数组的一个子序列,
* 需要满足:1.
* 长度为 k;2.字典序最小。
* 输入:nums = [3,5,2,6], k = 2
* 输出:[2,6]
* 解释:在所有可能的解:{[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]} 中
* ,[2,6] 字典序最小。
* @Date 11:31 2022/2/23
**/
public class Demo5 {
public static int[] findSmallSeq(int[] nums, int k){
int[] ans=new int[k];
Stack<Integer> s=new Stack<>();
for (int i=0;i< nums.length;i++){
final int x=nums[i];
final int left= nums.length-i;//剩下的元素
// (s.size()+left>k)栈中的元素和剩余的元素不能大于k
while(!s.empty()&&(s.size()+left>k)&&s.peek()>x){
s.pop();
}
s.push(x);
}
// 特殊情况:如果栈中的元素太多,就扔掉。
while (s.size()>k){
s.pop();
}
for (int i=k-1;i>=0;i--){
ans[i]=s.peek();
s.pop();
}
return ans;
}
public static void main(String[] args) {
int size[] = {1, 2, 4, 9, 4,0,5};
int dir[] = {1, 1, 0, 0, 0};
for (int i :
findSmallSeq(size,3)) {
System.out.println(i);
}
// System.out.println(findRightSmall(size));
}
}
图片来自lagouedu
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10
示例 2:
输入: heights = [2,4] 输出: 4
/*正在研究。。。
* 思路:
*
* 2个重要的性质:
*
* 一个递增栈里面存放的是数组的下标 stack = [i, j]
*
* 性质1: 对于j而言,下标[i+1 ... j-1] 这里面的元素的A[x]值都 >= A[j]
* 对于i而言,(-1, i-1] 这里面元素的值都 >= A[i];
*
* 如果此时A[k]要入栈。并且A[k] < A[j],要将A[j]出栈。
*
* 性质2: A[j+1 ... k-1]这个区间里面的元素都大于A[j]
*
*/
// @lc code=start
class Solution {
public int largestRectangleArea(int[] A) {
final int N = A == null ? 0 : A.length;
// 虽然可以用Stack<Integer>,但是这里我们为了更快地操作,我们用
// 数组模拟栈来运行,因为我们知道最多存放的内容实际上就是N个
int top = 0;
// s[top-1]表示栈顶元素
int[] s = new int[N];
int ans = 0;
// 注意,这里我们取到了i == N
// 按理说,不应该取到i == N的。但是这时候,主要是为了处理这种数组
// A = [1, 2, 3]
// 没有任何元素会出栈。
// 那么最后我们用一个0元素,把所有的元素都削出栈。
// 这样代码就可以统一处理掉。
for (int i = 0; i <= N; i++) {
// 注意:当i == N的时候,x = -1;
// 比数组中的元素都要小。
final int x = i == N ? -1 : A[i];
while (top > 0 && A[s[top - 1]] > x) {
// 计算以A[s[top]]的元素的高度的矩形。
final int height = A[s[--top]];
// i元素要将index = s[top-1]的元素出栈。
// 那么根据性质2:
// 此时A[s[top-1] .... i) 这个区间里面的元素都是
// 大于A[s[top-1]]的
final int rightPos = i;
// 这里需要使用性质1.
// 注意:当栈中一个元素都没有的时候,要取-1
final int leftPos = top > 0 ? s[top - 1] : -1;
final int width = rightPos - leftPos - 1;
final int area = height * width;
ans = Math.max(ans, area);
}
s[top++] = i;
}
return ans;
}
}
// @lc code=end