回溯&&单调栈总结

目录

一、回溯

1、组合

2、组合总和III

3、电话号码的字母组合

4、组合

5、组合II

6、分割字符串

7、复原IP地址

8、子集问题

9、子集II

 10、递增子序列

11、全排列

12、全排列II

13、N皇后

二、单调栈

1、每日温度

2、下一个更大元素I

3、下一个更大元素II

4、接雨水

5、柱状图中最大的矩形


一、回溯

回溯代码模板:

1、组合

class Solution {
    List<List<Integer>> result= new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
        backtracking(n,k,1);
        return result;
    }

    public void backtracking(int n,int k,int startIndex){
        if (path.size() == k){
            result.add(new ArrayList<>(path));
            return;
        }
   for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){
        //for (int i = startIndex;i <= n;i++){
            path.add(i);
            backtracking(n,k,i+1);
            path.removeLast();
        }
    }
}

2、组合总和III

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次 

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 :

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。
class Solution {
	List<List<Integer>> result = new ArrayList<>();
	LinkedList<Integer> path = new LinkedList<>();

	public List<List<Integer>> combinationSum3(int k, int n) {
		backTracking(n, k, 1, 0);
		return result;
	}

	private void backTracking(int targetSum, int k, int startIndex, int sum) {
		// 减枝
		if (sum > targetSum) {
			return;
		}

		if (path.size() == k) {
			if (sum == targetSum) result.add(new ArrayList<>(path));
			return;
		}

		// 减枝 9 - (k - path.size()) + 1
		for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
			path.add(i);
			sum += i;
			backTracking(targetSum, k, i + 1, sum);
			//回溯
			path.removeLast();
			//回溯
			sum -= i;
		}
	}
}

// 上面剪枝 i <= 9 - (k - path.size()) + 1; 如果还是不清楚
// 也可以改为 if (path.size() > k) return; 执行效率上是一样的
class Solution {
    LinkedList<Integer> path = new LinkedList<>();
    List<List<Integer>> ans = new ArrayList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        build(k, n, 1, 0);
        return ans;
    }

    private void build(int k, int n, int startIndex, int sum) {

        if (sum > n) return;

        if (path.size() > k) return;

        if (sum == n && path.size() == k) {
            ans.add(new ArrayList<>(path));
            return;
        }

        for(int i = startIndex; i <= 9; i++) {
            path.add(i);
            sum += i;
            build(k, n, i + 1, sum);
            sum -= i;
            path.removeLast();
        }
    }
}

3、电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。 

public class Case17 {
    //全局变量
    List<String> list = new ArrayList<>();

    public  List<String> letterCombinations(String digits) {
  
        String[] numString = {"","","abc","def","ghi","jkl",",mno","pqrs","tuv","wxyz"};
        if (digits == null || digits.length() == 0){
            return list;
        }
       /* if (map.get(digits)!= null){
           String str = map.get(digits);
            char[] chars = str.toCharArray();
            for (char c : chars) {
                list.add(c + "");
            }
            return list;
        }*/
        //1.确定递归参数
        backtracking(digits,numString,0);
        return list;
    }
    StringBuilder temp = new StringBuilder();
    private  void backtracking(String digits,String[] numString, int num) {
        //2.终止条件
        if (num == digits.length()){
            list.add(temp.toString());
            return;
        }
        //3.单层逻辑
        //拿到字符串的第一个数字对应的字符串,例如2:"abc"
        String str = numString[digits.charAt(num)-'0'];
        for (int i = 0; i <str.length() ; i++) {
            temp.append(str.charAt(i));
            //递归,处理下一层
            backtracking(digits, numString, num + 1);
            //剔除末尾的,继续尝试
            temp.deleteCharAt(temp.length() - 1);
        }
    }
}

4、组合

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

  • 输入:candidates = [2,3,6,7], target = 7,
  • 所求解集为: [ [7], [2,2,3] ]
 
public class Case39 {
    private List<List<Integer>> res = new ArrayList<>();
    private LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backtracking(candidates,target,0,0);
        return res;
    }
    private void backtracking(int[] candidates, int target, int sum,int startIndex) {
        //终止条件
        if (sum > target)return;
        if (sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        //单层循环逻辑
        for (int i = startIndex; i < candidates.length; i++) {
            path.add(candidates[i]);
            sum += candidates[i];
            //这里为了控制不出现(2,2,3),(2,3,2),(3,2,2)的情况,必然要加入一个起始索引
            //不能传startIndex+1,这样保证不了元素可重复,要传i
            backtracking(candidates,target,sum,i);
            //回溯
            sum -=candidates[i];
            path.removeLast();
        }
    }
}

5、组合II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用 一次 。注意:解集不能包含重复的组合。 

  • 示例 1:
  • 输入: candidates = [10,1,2,7,6,1,5], target = 8,
  • 所求解集为:

本题的关键在于去重!!

  1. 本题candidates 中的每个数字在每个组合中只能使用一次。
  2. 本题数组candidates的元素是有重复的,而39.组合总和 (opens new window)是无重复元素的数组candidates

最后本题和39.组合总和 (opens new window)要求一样,解集不能包含重复的组合。

本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合

public class Case40 {
    private List<List<Integer>> res = new ArrayList<>();
    private LinkedList<Integer> path = new LinkedList<>();
    boolean[] used;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        used = new boolean[candidates.length];
        //加标志数组,用来辅助判断同层节点是否已经遍历
        Arrays.fill(used,false);
        //为了将重复的数字都放到一起,所以先排序
        Arrays.sort(candidates);
        backtracking(candidates,target,0,0,used);
        return res;
    }
    private void backtracking(int[] candidates, int target, int startIndex,int sum, boolean[] used) {
        //终止条件
        if (sum > target){
         return;
        }
        if (sum == target){
            res.add(new ArrayList<>(path));
            return;
        }

        //单层循环逻辑  这该怎么弄?(candidates 中的每个数字在每个组合中只能使用 一次 !!!)
        for (int i = startIndex; i < candidates.length ; i++) {
            //树层去重
            if (i > 0 && candidates[i] == candidates[i-1] && used[i-1] == false){
                continue;
            }
            sum += candidates[i];
            path.add(candidates[i]);
            used[i] = true;
            backtracking(candidates,target,i + 1,sum,used);
            //回溯
            used[i] = false;
            sum -=candidates[i];
            path.removeLast();
        }
    }
}

6、分割字符串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串。返回 s 所有可能的分割方案。

示例 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

public class Case131 {
    private static List<List<String>> res = new ArrayList<>();
    private static List<String> path = new ArrayList<>();

    public static void main(String[] args) {
        String s = "aab";
        System.out.println(partition(s));
    }
    public static List<List<String>> partition(String s) {
        //1.确定参数
        backtracking(s,0,new StringBuilder());
        return res;
    }

    private static void backtracking(String s, int startIndex, StringBuilder sb) {
        //终止条件
        if (startIndex == s.length()){
            res.add(new ArrayList<>(path));
            return;
        }
        //3.单层循环
        for (int i = startIndex; i < s.length();i++){
            sb.append(s.charAt(i));
            if (isHuiWen(sb)){
                path.add(sb.toString());
                //递归
                backtracking(s,i+1, new StringBuilder());
                //回溯
                path.remove(path.size()-1);
            }
        }
    }
    private static boolean isHuiWen(StringBuilder str) {
        for (int i = 0; i < str.length()/2; i++) {
            if (str.charAt(i) != str.charAt(str.length()-1-i)){
                return false;
            }
        }
        return true;
    }
}

7、复原IP地址

定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

例如:"0.1.2.201" 和 "192.168.1.1" 是 有效的 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效的 IP 地址。

示例 1:

  • 输入:s = "25525511135"
  • 输出:["255.255.11.135","255.255.111.35"]

public class Case93 {
    private List<String> res = new ArrayList<>();
    public List<String> restoreIpAddresses(String s) {
        if (s.length() > 12) return res;  //字符串长度大于12就不是一个合法的ip
        //确定参数 起始索引 小数点
        backTrack(s,0,0);
        return res;
    }
    private void backTrack(String s, int startIndex, int pointNum) {
        //终止条件
        if (pointNum == 3){  //逗号数量为3时,分割结束
            //判断第四段子字符串是否合法,如果合法就放进res里面
            if (isValid(s,startIndex,s.length() -1)){
                res.add(s);
            }
            return;
        }
        //单层逻辑
        for (int i = startIndex; i < s.length(); i++){
            if (isValid(s,startIndex,i)){
                s = s.substring(0,i + 1) + "." + s.substring(i+1);
                pointNum++;
                //为什么是i+2呢? 因为加了小数点,要调过小数点
                backTrack(s,i+2,pointNum);
                pointNum--; //回溯
                //回溯删掉,也得删掉小数点。
                s = s.substring(0,i+1) + s.substring(i+2);
            }else {
                break;
            }
        }
    }
    private boolean isValid(String s, int start, int end) {  //【】左闭右闭
        if (start > end){
            return false;
        }
        if (s.charAt(start) == '0' && start != end){ //0开头的数字不合法
            return false;
        }
        int num = 0;
        for (int i = start; i<=end; i++){
            if (s.charAt(i) > '9' || s.charAt(i) < '0'){
                return false;
            }
            num = num *10 + (s.charAt(i) - '0');
            if (num > 255){
                return false;
            }
        }
        return true;
    }
}

8、子集问题

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例: 输入: nums = [1,2,3] 输出: [ [3],   [1],   [2],   [1,2,3],   [1,3],   [2,3],   [1,2],   [] ]

其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。

那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!

从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合。

public class Case78 {
    private List<List<Integer>> res = new ArrayList<>();
    private LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> subsets(int[] nums) {
        if (nums == null) return res;
        backTrack(nums,0);
        return res;
    }

    private void backTrack(int[] nums, int startIndex) {
        res.add(new ArrayList<>(path));  //先把空集加进去,同时也表示把节点加入结果集当中
        //2.终止条件是什么?
        if (startIndex >= nums.length){
            return;
        }
        //3.单层循环
        for (int i = startIndex; i < nums.length; i++){
            path.add(nums[i]);
            backTrack(nums,i+1);
            //回溯
            path.removeLast();
        }
    }
}

9、子集II

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

  • 输入: [1,2,2]
  • 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]

这道题目和78.子集 (opens new window)区别就是集合里有重复元素了,而且求取的子集要去重。

public class Case90 {
    private List<List<Integer>> res = new ArrayList<>();
    private LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        Boolean[]used = new Boolean[nums.length];
        Arrays.fill(used,false);
        backTrack(nums,0,used);
        return res;
    }

    private void backTrack(int[] nums, int startIndex, Boolean[] used) {
        res.add(new ArrayList<>(path));  //先把空集加进去
        //终止条件
        if (startIndex >= nums.length){
            return;
        }
        //单层循环
        for (int i = startIndex; i< nums.length; i++){

            if (i > 0 && nums[i] == nums[i-1] && used[i-1] == false){
                continue;
            }
            path.add(nums[i]);
            used[i] = true;
            backTrack(nums,i+1,used);
            //回溯
            used[i] = false;
            path.removeLast();
        }
    }
}

 10、递增子序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

**
 * 1.终止条件是什么?  答:本题类似于求子集问腿,要遍历树形结构的每一个节点
 * 可以不加终止条件,startIndex每次都会加1,并不会无限递归
 * 2.画完树形结构后如何去重? 这与原来的去重逻辑可不一样!!!!
 */
public class Case491 {
    private static List<List<Integer>> res = new ArrayList<>();
    private static LinkedList<Integer> path = new LinkedList<>();

    public static void main(String[] args) {
        int[] nums = {4,7,6,7};
        List<List<Integer>> res = findSubsequences(nums);
        System.out.println(res);
    }
    public static List<List<Integer>> findSubsequences(int[] nums) {
        //1.确定递归参数
        backTrack(nums,0);
        return res;
    }
    private static void backTrack(int[] nums, int startIndex) {
        //收集结果
        if(path.size() >= 2) {
            res.add(new ArrayList<>(path));
        }
        //1.终止条件(可加可不加)
        if (startIndex >= nums.length){
            return;
        }
        HashSet<Integer> set = new HashSet<>(); // 这个set控制的是每一层不允许有重复的元素!!!
        //3.单层逻辑
        for (int i = startIndex; i < nums.length; i++){
            //path.get(path.size()-1) > nums[i]代表不增
            if (!path.isEmpty() && path.get(path.size() -1) > nums[i] || set.contains(nums[i])){
                continue;
            }
            set.add(nums[i]);
            path.add(nums[i]);
            backTrack(nums,i+1);
            //回溯
            path.removeLast();
        }
    }
}

已经习惯写回溯,看到递归函数上面的set.add(nums[i]);,下面却没有对应的pop之类的操作,应该很不习惯吧,这也是需要注意的点,HashSet<Integer> set = new HashSet<>(); 是记录本层元素是否重复使用,新的一层set都会重新定义(清空),所以要知道uset只负责本层!

11、全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

  • 输入: [1,2,3]
  • 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

class Solution {

    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
    LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
    boolean[] used;
    public List<List<Integer>> permute(int[] nums) {
        if (nums.length == 0){
            return result;
        }
        used = new boolean[nums.length];
        permuteHelper(nums);
        return result;
    }

    private void permuteHelper(int[] nums){
        if (path.size() == nums.length){
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++){
            if (used[i]){
                continue;
            }
            used[i] = true;
            path.add(nums[i]);
            permuteHelper(nums);
            path.removeLast();
            used[i] = false;
        }
    }
}

12、全排列II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

  • 输入:nums = [1,1,2]
  • 输出: [[1,1,2], [1,2,1], [2,1,1]]

public class Case47 {
    LinkedList<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    boolean[] used;

    public  List<List<Integer>> permute(int[] nums) {
        if (nums.length == 0)return res;
        used = new boolean[nums.length];
        Arrays.sort(nums);
        //确定参数
        backTrack(nums);
        return res;
    }
    private  void backTrack(int[] nums) {
        //收集结果,终止条件
        if (path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        //单层循环
        for (int i = 0; i < nums.length; i++) {
            if (i > 0 && used[i - 1] == false && nums[i] == nums[i - 1]) {  //这个是控制++操作的
                continue;
            }
            if (used[i] == false) {
                used[i] = true;
                path.add(nums[i]);
                backTrack(nums);
                path.removeLast();
                used[i] = false;
            }
        }
    }
}

13、N皇后

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

  • 输入:n = 4
  • 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
  • 解释:如上图所示,4 皇后问题存在两个不同的解法。

皇后们的约束条件:

  1. 不能同行
  2. 不能同列
  3. 不能同斜线

确定完约束条件,来看看究竟要怎么去搜索皇后们的位置,其实搜索皇后的位置,可以抽象为一棵树。下面用一个 3 * 3 的棋盘,将搜索过程抽象为一棵树,如图:

从图中,可以看出,二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。那么我们用皇后们的约束条件,来回溯搜索这棵树,只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了

public class Case51 {
    private List<List<String>> res = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
        char[][] chessboard = new char[n][n];  //棋盘
        for (char[] c : chessboard) {
            Arrays.fill(c,'.');            //初始化棋盘,单字符
        }
        //1. 确定递归参数
        backTrack(n,0,chessboard);
        return res;
    }

    private void backTrack(int n, int row, char[][] chessboard) {
        //终止条件
        if (row == n){
            res.add(Array2List(chessboard));
            return;
        }
        //单层循环逻辑
        for (int col = 0; col < n; ++col){
            if (isValid(row,col,n,chessboard)){
                chessboard[row][col] = 'Q';
                backTrack(n,row + 1,chessboard);
                chessboard[row][col] = '.';
            }
        }
    }

    private boolean isValid(int row, int col, int n, char[][] chessboard) {
        //检查列(行号变化,列号不变)
        for (int i = 0; i < row; i++) {
            if (chessboard[i][col] == 'Q'){
                return false;
            }
        }
        //检查对角线(先得到矩阵,再判断)
        for (int i = row -1 , j = col -1; i >= 0 && j>=0; i--,j--){
            if (chessboard[i][j] == 'Q'){
                return false;
            }
        }

        //检查135度对角
        for (int i = row - 1, j=col +1; i >= 0 && j <= n-1; i--,j++){
            if (chessboard[i][j] == 'Q'){
                return false;
            }
        }
        return true;
    }
    //对结果进行处理,使其变成字符串
    private List<String> Array2List(char[][] chessboard) {
        List<String> list  = new ArrayList<>();
        for (char[] c : chessboard) {
            list.add(String.copyValueOf(c));
        }
        return list;
    }
}

二、单调栈

1、每日温度

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

首先想到的当然是暴力解法,两层for循环,把至少需要等待的天数就搜出来了。时间复杂度是O(n^2)那有人就问了,我怎么能想到用单调栈呢? 什么时候用单调栈呢?

通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)。

例如本题其实就是找找到一个元素右边第一个比自己大的元素,此时就应该想到用单调栈了。

单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。

更直白来说,就是用一个栈来记录我们遍历过的元素,因为我们遍历数组的时候,我们不知道之前都遍历了哪些元素,以至于遍历一个元素找不到是不是之前遍历过一个更小的,所以我们需要用一个容器(这里用单调栈)来记录我们遍历过的元素。

在使用单调栈的时候首先要明确如下几点:

  1. 单调栈里存放的元素是什么?

单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接T[i]就可以获取。

  1. 单调栈里元素是递增呢? 还是递减呢?

注意以下讲解中,顺序的描述为 从栈头到栈底的顺序,因为单纯的说从左到右或者从前到后,不说栈头朝哪个方向的话,大家一定比较懵。

public class Case739 {
    public static void main(String[] args) {
        int[]temperatures = {73,74,75,71,69,72,76,73};
        int[] a = dailyTemperatures(temperatures);
        for (int i : a) {
            System.out.print(i + " ");
        }
    }
    //先暴力吧,超出时间限制
    public static int[] dailyTemperatures(int[] temperatures) {
        int len = temperatures.length;
        int[] res = new int[len];
        for (int i = 0; i < len - 1; i++) {
            int distance = 1;
            for (int j = i + 1; j < len; j++) {
                    if (temperatures[j] > temperatures[i]){
                        res[i]  = distance;
                       break;
                    }else {
                        distance++;
                    }
            }
        }
        res[len -1] = 0;
        return res;
    }
    // 单调栈呼之欲出

    /**
     * 什么时候使用单调栈? 通常是一维数组,要寻找任意一个元素的右边或者左边第一个比自己大的或者小的元素的位置
     * 时间复杂度为O(n),单调栈的原理,就是用一个栈来记录遍历过的元素。
     * 核心操作是:栈内存放的是数组元素的下标。
     * @param temperatures
     * @return
     */
    public static int[] dailyTemperatures1(int[] temperatures) {
        int lens = temperatures.length;
        int[] res = new int[lens];
        // 栈里面存放的是数组的下标。
        Deque<Integer> stack = new LinkedList<>();
        stack.push(0);
        for (int i = 0; i < lens; i++) {
            if (temperatures[i] <= temperatures[stack.peek()]) {
                stack.push(i);
            } else {
                while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
                    res[stack.peek()] = i - stack.peek();
                    stack.poll();
                }
                stack.push(i);
            }
        }
        return res;
    }
}

2、下一个更大元素I

给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。

示例 1:输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

public class Case496 {

    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        int len1 = nums1.length;
        int len2 = nums2.length;
        int ans[] = new int[len1];
        Arrays.fill(ans,-1);
        Stack<Integer> stack = new Stack<>();
        //stack.push(nums1[0]);
        //开始从nums2中找对应的位置,不用循环了,用循环太麻烦了,用map
        HashMap<Integer,Integer> map = new HashMap<>();
        for (int i = 0; i < len1; i++) {
            map.put(nums1[i],i);
        }
        stack.push(0); //存放nums2元素的下标

        for (int i = 1; i < len2; i++) {
            if (nums2[i] <= nums2[stack.peek()]){
                stack.push(i);
            }else {
                while (!stack.isEmpty() && nums2[stack.peek()] < nums2[i]){
                    if (map.containsKey(nums2[stack.peek()])){
                        Integer index = map.get(nums2[stack.peek()]);
                        ans[index] = nums2[i];
                    }
                    stack.pop();
                }
                stack.add(i);
            }
        }
        return ans;
    }
}

3、下一个更大元素II

给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。

示例 1:

  • 输入: [1,2,1]
  • 输出: [2,-1,2]
  • 解释: 第一个 1 的下一个更大的数是 2;数字 2 找不到下一个更大的数;第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
public class Case503 {
    public static void main(String[] args) {
        int [] nums = {5,4,3,2,1};
        int[] ans = nextGreaterElements(nums);
        for (int an : ans) {
            System.out.print(an + " ");
        }
    }
    public static int[] nextGreaterElements(int[] nums) {
        int len = nums.length;
        int[] res = new int[len];
        Arrays.fill(res,-1);
        Stack<Integer> stack = new Stack<>();
        stack.push(0);
        for (int i = 0; i < 2* len; i++) {
                while (!stack.isEmpty() && nums[stack.peek()]< nums[i % len]){
                    res[stack.peek()] = nums[i % len];
                    stack.pop();
                }
                stack.push(i % len);
            }

        //这种方法不行,一开始也想到了两个数组拼到一起,但是没想到取余操作!! damn!
        /*for (int i = 0; i < len - 1 ; i++) {
            if (nums[i] > nums[len - 1]){
                res[len - 1] = nums[i];
            }
        }*/
        return res;
    }
}

4、接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

public class Case42 {
    //单调栈
    public int trap(int[] height) {
        int size = height.length;
        if (size <= 2) return 0;
        Stack<Integer> stack = new Stack<>();
        stack.push(0); //存放数组下标
        int sum = 0;
        for (int index = 0; index < size; index++) {
            int stackTop = stack.peek();
            if (height[index] < height[stackTop]){
                stack.push(index);
            }/*else if (height[index] == height[stackTop]){
                stack.pop();
                stack.push(index);*/
            else {
                int heightAtIndex = height[index];
                while (!stack.isEmpty() && (heightAtIndex > height[stackTop])){
                    int mid = stack.pop();

                    if (!stack.isEmpty()){
                        int left = stack.peek(); //左边第一个比当前元素大的值
                        int h = Math.min(height[left],height[index]) - height[mid];
                        int w = index - left -1;
                        int s = h * w;
                        if (s > 0)sum += s;
                        stackTop = stack.peek();
                    }
                }
                stack.push(index);
            }
        }
        return sum;
    }

    //接雨水之暴力解法
    public int trap1(int[] height) {
        int sum = 0;
        for (int i = 0; i < height.length; i++) {
            //第一个柱子和最后一个柱子不接雨水
            if (i == 0 || i == height.length - 1) continue;

            int rHeight = height[i]; //记录右边柱子的最高高度
            int lHeight = height[i]; //记录左边柱子的最高高度

            //找到右边第一个比当前柱子高的
            for (int r = i + 1; r < height.length; r++) {
                if (height[r] > rHeight) rHeight = height[r];
            }

            //找到左边第一个比当前柱子高的
            for (int l = i - 1; i >= 0; l--) {
                if (height[l] > lHeight) lHeight = height[l];
            }
            //计算高度,宽度恒为1
            int h = Math.min(lHeight, rHeight) - height[i];
            if (h > 0) sum += h;
        }
        return sum;
    }
}

5、柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。

public class Case84 {

    public static void main(String[] args) {
        int[] heights = {20,30,40,50};
        System.out.println(largestRectangleArea(heights));
    }
    public static int largestRectangleArea(int[] heights) {
        Stack<Integer> st = new Stack<>();
        //组数扩容
        int[] newHeights = new int[heights.length +2];
        newHeights[0] = 0;
        newHeights[newHeights.length -1] = 0;
        for (int index = 0; index < heights.length; index++){
            newHeights[index + 1] = heights[index];
        }
        heights = newHeights; //也就说变名了,
        //单调栈
        st.push(0);
        int res = 0;
        //第一个元素下标已经入栈,从下标1开始
        for(int i = 1; i < heights.length; i++ ){
            if(heights[i] >= heights[st.peek()]){
                st.push(i);
            }else{
                while(heights[i] < heights[st.peek()]){
                    int mid = st.peek();
                    st.pop();
                    int left = st.peek();
                    int right = i;  //找到了右边第一个比当前元素小的元素
                    int w = right - left -1;
                    int h = heights[mid];
                    res = Math.max(res,w*h);
                }
                st.push(i);
            }
        }
        return res;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值