hot100 p0——栈,贪心算法,动态规划

栈

有效的括号

20. 有效的括号 - 力扣(LeetCode)

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

**输入:**s = "()"

**输出:**true

示例 2:

**输入:**s = "()\[\]{}"

**输出:**true

示例 3:

**输入:**s = "(]"

**输出:**false

示例 4:

**输入:**s = "(\[\])"

**输出:**true

示例 5:

**输入:**s = "()"

**输出:**false

解法及思路

栈

遇到左括号入栈,遇到右括号检查栈顶是否匹配。

复制代码
1. 遍历字符串
2. 遇到左括号:入栈
3. 遇到右括号:
   - 栈为空 → false
   - 栈顶不匹配 → false
   - 匹配 → 弹出栈顶
4. 遍历结束,栈为空 → true

输入:"{[]}"

复制代码
遍历:
  '{' → 入栈 → stack=['{']
  '[' → 入栈 → stack=['{', '[']
  ']' → 栈顶是'[',匹配 → 弹出 → stack=['{']
  '}' → 栈顶是'{',匹配 → 弹出 → stack=[]
  '}' → 字符串遍历完

栈为空 → true ✅

输入:"([)]"

复制代码
遍历:
  '(' → 入栈 → stack=['(']
  '[' → 入栈 → stack=['(', '[']
  ')' → 栈顶是'[',不匹配 → false ❌

结果:false
java 复制代码
class Solution {
    public boolean isValid(String s) {
        Deque<Character> stack=new ArrayDeque<>();
        for(char c:s.toCharArray()){
            if(c=='('){
                stack.push(')');
            }else if(c=='['){
                stack.push(']');
            }else if(c=='{'){
                stack.push('}');
            }else{
                if(stack.isEmpty()||stack.pop()!=c){
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }
}

最小栈

155. 最小栈 - 力扣(LeetCode)

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int value) 将元素 value 推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

示例 1:

复制代码
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

解法及思路

辅助栈

用一个辅助栈记录每个位置的最小值。

复制代码
主栈:存储所有元素
辅助栈:存储对应位置的最小值

push时:
  主栈入栈
  辅助栈入 min(当前元素, 辅助栈栈顶)

pop时:
  两个栈都弹出

getMin时:
  返回辅助栈栈顶

操作序列:push(-2), push(0), push(-3)

复制代码
push(-2):
  主栈:[-2]
  辅助栈:[-2]  ← 最小值-2

push(0):
  主栈:[-2, 0]
  辅助栈:[-2, -2]  ← min(0, -2) = -2

push(-3):
  主栈:[-2, 0, -3]
  辅助栈:[-2, -2, -3]  ← min(-3, -2) = -3

getMin() → 辅助栈栈顶 = -3 ✅

pop():
  主栈:[-2, 0]
  辅助栈:[-2, -2]

top() → 主栈栈顶 = 0 ✅
getMin() → 辅助栈栈顶 = -2 ✅
java 复制代码
class MinStack {
    private Deque<Integer> stack;
    private Deque<Integer> minstack;
    public MinStack() {
        stack=new ArrayDeque<>();
        minstack=new ArrayDeque<>();
    }
    
    public void push(int value) {
        stack.push(value);
        //辅助入栈min
        if(minstack.isEmpty()){
            minstack.push(value);
        }else{
            int minval=Math.min(minstack.peek(),value);
            minstack.push(minval);
        }
    }
    
    public void pop() {
        stack.pop();
        minstack.pop();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return minstack.peek();
    }
}

贪心算法

买股票的最佳时机

121. 买卖股票的最佳时机 - 力扣(LeetCode)

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

复制代码
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

复制代码
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

解法及思路

一次遍历

遍历价格,记录"历史最低价",计算"当前卖出"的利润,更新最大利润。

复制代码
1. 记录历史最低价 minPrice
2. 对于每天的价格:
   - 计算利润 = 当前价格 - minPrice
   - 更新最大利润
   - 更新 minPrice = min(minPrice, 当前价格)

prices = 7, 1, 5, 3, 6, 4

复制代码
第1天:price=7
  minPrice = 7
  maxProfit = 0

第2天:price=1
  profit = 1 - 7 = -6(不买)
  minPrice = min(7, 1) = 1
  maxProfit = 0

第3天:price=5
  profit = 5 - 1 = 4
  maxProfit = max(0, 4) = 4
  minPrice = min(1, 5) = 1

第4天:price=3
  profit = 3 - 1 = 2
  maxProfit = max(4, 2) = 4

第5天:price=6
  profit = 6 - 1 = 5
  maxProfit = max(4, 5) = 5
  minPrice = min(1, 6) = 1

第6天:price=4
  profit = 4 - 1 = 3
  maxProfit = max(5, 3) = 5

结果:5 ✅
java 复制代码
class Solution {
    public int maxProfit(int[] prices) {
        int minprice=Integer.MAX_VALUE;
        int maxprofit=0;
        for(int i=0;i<prices.length;i++){
            if(prices[i]<minprice){
                minprice=prices[i];//更新最小价格
            }else if(prices[i]-minprice>maxprofit){
                maxprofit=prices[i]-minprice;//更新最大利润
            }
        }
        return maxprofit;
    }
}

跳跃游戏

55. 跳跃游戏 - 力扣(LeetCode)

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。

示例 1:

复制代码
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2:

复制代码
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

解法及思路

贪心

维护"能到达的最远位置",遍历数组,更新最远位置。

复制代码
1. 初始化 maxReach = 0(能到达的最远位置)
2. 遍历数组:
   - 如果 i > maxReach,说明当前位置不可达,返回false
   - 更新 maxReach = max(maxReach, i + nums[i])
   - 如果 maxReach >= n-1,返回true
3. 遍历结束,返回true

nums = 2,3,1,1,4

复制代码
初始:maxReach = 0

i=0: nums[0]=2
  i=0 <= maxReach=0 ✅
  maxReach = max(0, 0+2) = 2
  2 < 4,继续

i=1: nums[1]=3
  i=1 <= maxReach=2 ✅
  maxReach = max(2, 1+3) = 4
  4 >= 4 → 返回true ✅

nums = 3,2,1,0,4

复制代码
初始:maxReach = 0

i=0: nums[0]=3
  maxReach = max(0, 0+3) = 3

i=1: nums[1]=2
  maxReach = max(3, 1+2) = 3

i=2: nums[2]=1
  maxReach = max(3, 2+1) = 3

i=3: nums[3]=0
  maxReach = max(3, 3+0) = 3

i=4: nums[4]=4
  i=4 > maxReach=3 ❌ 不可达 → 返回false
java 复制代码
class Solution {
    public boolean canJump(int[] nums) {
        int maxreach=0;
        for(int i=0;i<nums.length;i++){
            if(i>maxreach) return false;
            maxreach=Math.max(maxreach,i+nums[i]);
            if(i+nums[i]>=nums.length-1){
                return true;
            }
        }
        return false;
    }
}

动态规划

爬楼梯

70. 爬楼梯 - 力扣(LeetCode)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

复制代码
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶

示例 2:

复制代码
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

解法及思路

动态规划

到达第 n 阶,可以从第 n-1 阶爬1步,或从第 n-2 阶爬2步。

复制代码
dp[n] = dp[n-1] + dp[n-2]
dp[1] = 1
dp[2] = 2

n = 5

复制代码
dp[1] = 1(1种:1)
dp[2] = 2(2种:1+1, 2)
dp[3] = dp[2] + dp[1] = 2 + 1 = 3
dp[4] = dp[3] + dp[2] = 3 + 2 = 5
dp[5] = dp[4] + dp[3] = 5 + 3 = 8

示意图:

复制代码
n=1: 1
n=2: 2
n=3: 3
n=4: 5
n=5: 8

就是斐波那契数列

java 复制代码
class Solution {
    public int climbStairs(int n) {
        if(n<=2) return n;
        int[] dp=new int[n+1];
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
}

打家劫舍

198. 打家劫舍 - 力扣(LeetCode)

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。

示例 1:

复制代码
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

复制代码
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

解法及思路

动态规划

对于第 i 间房,有两种选择:

复制代码
1. 偷第 i 间:金额 = dp[i-2] + nums[i]
2. 不偷第 i 间:金额 = dp[i-1]

取最大值

状态转移方程:

复制代码
dp[i] = max(dp[i-1], dp[i-2] + nums[i])

nums = 2, 7, 9, 3, 1

复制代码
dp[0] = 2(只有1间房,偷)
dp[1] = max(2, 7) = 7(偷7)
dp[2] = max(7, 2+9) = max(7, 11) = 11(偷2和9)
dp[3] = max(11, 7+3) = max(11, 10) = 11(不偷3)
dp[4] = max(11, 11+1) = max(11, 12) = 12(偷2,9,1)

结果:12 ✅
java 复制代码
class Solution {
    public int rob(int[] nums) {
        if(nums.length==0) return 0;
        if(nums.length==1) return nums[0];
        int[] dp=new int[nums.length];
        dp[0]=nums[0];
        dp[1]=Math.max(nums[0],nums[1]);
        for(int i=2;i<nums.length;i++){
            //选和不选
            dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[nums.length-1];
    }
}

零钱兑换

322. 零钱兑换 - 力扣(LeetCode)

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

复制代码
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

示例 2:

复制代码
输入:coins = [2], amount = 3
输出:-1

示例 3:

复制代码
输入:coins = [1], amount = 0
输出:0

解法及思路

动态规划

dpi 表示凑成金额 i 所需的最少硬币数。

复制代码
dp[i] = min(dp[i - coin]) + 1  (对所有 coin)
dp[0] = 0

解释:

复制代码
凑成金额 i,可以选一枚硬币 coin
然后凑成 i - coin
所以 dp[i] = dp[i - coin] + 1
取所有 coin 中最小的

coins = 1, 2, 5, amount = 11

复制代码
dp[0] = 0
dp[1] = min(dp[0]+1) = 1
dp[2] = min(dp[1]+1, dp[0]+1) = min(2, 1) = 1
dp[3] = min(dp[2]+1, dp[1]+1) = min(2, 2) = 2
dp[4] = min(dp[3]+1, dp[2]+1) = min(3, 2) = 2
dp[5] = min(dp[4]+1, dp[3]+1, dp[0]+1) = min(3, 3, 1) = 1
...
dp[11] = min(dp[10]+1, dp[9]+1, dp[6]+1) = min(3, 3, 3) = 3

结果:3 ✅

java 复制代码
class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp=new int[amount+1];
        Arrays.fill(dp,amount+1);
        dp[0]=0;
        for(int i=1;i<=amount;i++){
            for(int coin:coins){
                if(coin<=i){
                    dp[i]=Math.min(dp[i-coin]+1,dp[i]);
                }
            }
        }
        return dp[amount]>amount?-1:dp[amount];
    }
}

单词拆分

139. 单词拆分 - 力扣(LeetCode)

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。

**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:

复制代码
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。

示例 2:

复制代码
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
     注意,你可以重复使用字典中的单词。

示例 3:

复制代码
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

解法及思路

动态规划

dpi 表示 s 的前 i 个字符能否被拆分。

复制代码
dp[i] = true 表示 s[0..i-1] 可以被拆分
dp[0] = true(空字符串可以被拆分)

对于每个 i,遍历 j < i:
  如果 dp[j] == true 且 s[j..i-1] 在字典中
  则 dp[i] = true

s = "leetcode", wordDict = "leet", "code"

复制代码
dp[0] = true(空串)

i=1: s[0..0]="l",不在字典 → dp[1]=false
i=2: s[0..1]="le",不在字典 → dp[2]=false
i=3: s[0..2]="lee",不在字典 → dp[3]=false
i=4: s[0..3]="leet",在字典 → dp[4]=true

i=5: 检查j=0..4
  j=0: dp[0]=true, s[0..4]="leetc",不在字典
  j=1: dp[1]=false
  j=2: dp[2]=false
  j=3: dp[3]=false
  j=4: dp[4]=true, s[4..4]="c",不在字典
  → dp[5]=false

i=6: 检查j=0..5
  j=4: dp[4]=true, s[4..5]="co",不在字典
  → dp[6]=false

i=7: 检查j=0..6
  j=4: dp[4]=true, s[4..6]="cod",不在字典
  → dp[7]=false

i=8: 检查j=0..7
  j=4: dp[4]=true, s[4..7]="code",在字典 → dp[8]=true

结果:dp[8]=true ✅
java 复制代码
class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> set=new HashSet<>(wordDict);
        boolean[] dp=new boolean[s.length()+1];
        dp[0]=true;
        for(int i=1;i<=s.length();i++){
            for(int j=0;j<i;j++){
                if(dp[j]&&set.contains(s.substring(j,i))){
                    dp[i]=true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}

最长递增子序列

300. 最长递增子序列 - 力扣(LeetCode)

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

复制代码
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

复制代码
输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

复制代码
输入:nums = [7,7,7,7,7,7,7]
输出:1

解法及思路

动态规划

dpi = 以 numsi 结尾的最长递增子序列长度。

复制代码
对于每个 i,遍历 j < i:
  如果 nums[j] < nums[i]
  则 dp[i] = max(dp[i], dp[j] + 1)

nums = 10, 9, 2, 5, 3, 7, 101, 18

复制代码
dp[0] = 1(10)
dp[1] = 1(9,前面没有比9小的)
dp[2] = 1(2)
dp[3] = 2(2,5)
dp[4] = 2(2,3)
dp[5] = 3(2,3,7)
dp[6] = 4(2,3,7,101)
dp[7] = 4(2,3,7,18)

结果:4 ✅
java 复制代码
class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp=new int[nums.length];
        Arrays.fill(dp, 1);
        int maxlen=1;
        for(int i=1;i<nums.length;i++){
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]){
                    dp[i]=Math.max(dp[i],dp[j]+1);
                }
            }
            maxlen=Math.max(maxlen,dp[i]);
        }
        return maxlen;
    }
}
相关推荐
程序员Sunday2 小时前
TypeScript 的 any、unknown、never 有什么区别?
前端·面试·typescript
朱涛的自习室2 小时前
70 美金的 Beyond Compare,我让 Gemini 4 和 DeepSeek 造个开源免费版(Day 2)
android·前端·人工智能
需要8263 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
开发小能手1233 小时前
答案daanC语言
数据结构·算法
小 黄 鸡3 小时前
HashMap 面试必会知识点详解(JDK 8)
java·面试·职场和发展
码艺-Alimjan3 小时前
Tauri 源码备份 · 打包 & 解压 总结,源码收集之路
前端·javascript·vue.js
小马不起床3 小时前
零基础玩转 FastAPI_11:前端后台管理页面
前端·状态模式·fastapi
Navigator_Z4 小时前
LeetCode //C - 1291. Sequential Digits
c语言·算法·leetcode
子非鱼a4 小时前
【文件上传】一个普普通通的edu16
前端