目录
一.栈和排序
1.题目
给你一个 1 到 n 的排列和一个栈,并按照排列顺序入栈。
你要在不打乱入栈顺序的情况下,仅利用入栈和出栈两种操作,输出字典序最大的出栈序列。
排列:指 1 到 n 每个数字出现且仅出现一次。
示例:
输入:[2,1,5,3,4]
返回值:[5,4,3,1,2]
2.图解
3.代码
public int[] solve (int[] a) {
// write code here
int n = a.length;
//保存排序好的结果
int[] res = new int[n];
//保存从当前元素到结尾元素中的最大值
int[] dp = new int[n];
Stack<Integer> stack = new Stack<>();
dp[n-1] = a[n-1];
int index = 0;
//处理dp数组
for(int i=n-2; i>=0; i--) {
dp[i] = Math.max(dp[i+1], a[i]);
}
//遇到元素先入栈,如果当前元素比之后的所有元素都大,就出栈(否则就入栈)
for(int i=0; i<n; i++) {
stack.push(a[i]);
while(!stack.isEmpty() && i<n-1 && stack.peek()>dp[i+1]) {
res[index++] = stack.pop();
}
}
while(!stack.isEmpty()) {
res[index++] = stack.pop();
}
return res;
}
二.单词拆分
1.题目
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。请你判断是否可以利用字典中出现的单词拼接出 s
。
示例:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] 输出: false
2.思路图解
使用动态规划进行求解,首先查看[0,i]之间的字符串是否在字典中,这里查看的方式是使用set集合,保证查找效率为O(1),然后对于[0,i]区间的又可以进行划分为[0, j], [j, i];这里的[0, j]之前的元素就可以使用dp数组来进行判断,如果[0,j]之间的字符组成的单词都在字典中,那么再判断[j, i]之间的单词是否在字典中,如果都存在,那么dp[i]就置为true;说明[0, i]之间的字符组成的字符串都在字典中。直到所有的字符都扫描完后,都存在字典中,也就是dp[s.length]=true,就可以说明能够在字典中找到该单词的拆分单词。
3.代码
Set<String> set = new HashSet<>(wordDict);
//记录所有的字符串是否都存在字典单词中
boolean[] flag = new boolean[s.length()+1];
flag[0] =true;//代表第0个字符存在
for(int i=1; i<=s.length(); i++) {
for(int j=0; j<i; j++) {
if(flag[j] && set.contains(s.substring(j, i))) {
flag[i] = true;//说明从0-i的所有字符都在字典中存在
}
}
}
//返回标记结果,判断是否所欲字符都在字典中
return flag[s.length()];
}
三.接雨水问题
1.题目
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例:
2.思路图解
3.代码
//方法1
public int trap1(int[] height) {
int n = height.length;
int res = 0;
for(int i=0; i<n; i++) {
int left =0;
for(int j=0; j<=i; j++) {
//找当前位置左边的最大高度
left = Math.max(left, height[j]);
}
int right = 0;
for(int j=i; j<n; j++) {
//找当前位置右边的最大高度
right = Math.max(right, height[j]);
}
//求当前位置的接水量
res += Math.min(left, right)-height[i];
}
return res;
}
//方法2
public int trap2(int[] height) {
int n = height.length;
int[] dpL = new int[n];
dpL[0] = height[0];
int[] dpR = new int[n];
dpR[n-1] = height[n-1];
//保存每一位左边的最大高度
for(int i=1; i<n; i++) {
dpL[i] = Math.max(dpL[i-1], height[i]);
}
//保存每一位右边的最大高度
for(int i=n-2; i>=0; i--) {
dpR[i] = Math.max(dpR[i+1], height[i]);
}
int res = 0;
for(int i=0; i<n; i++) {
//依次寻找每一位的储水量
res += Math.min(dpL[i], dpR[i])-height[i];
}
return res;
}
四.不同的二叉搜索树
1.题目
给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例:
2.思路图解
由于是二叉搜索树,所以以当前元素构建的时候,都是左边所有元素都比当前元素值小,右边所有元素都比当前元素值大,然后再划分为子问题,找左边的所有能够排成二叉搜索数的个数,最后统计以当前结点为根结点的所有能够组成二叉搜索树的个数;这里通过循环一次统计每一个结点构成的二叉搜索树的个数,最后求和即可。
3.代码
//递归搜索每一个结点构成的二叉搜索树的个数
public int numTrees1(int n) {
//说明只有一个树根或者树为空,就返回1
if(n==0 || n==1) {
return 1;
}
int count = 0;
//统计以每一个节点为根节点构建树的个数
for(int i=1; i<=n; i++) {
//统计当结点左右子树组成的个数
int leftCount = numTrees(i-1);
int rightCount = numTrees(n-i);
//最后求得以当前节点为根结点的二叉搜索树的个数
count += leftCount*rightCount;
}
return count;
}
//通过使用map来存储中间计算过多次的结果,提高了时间效率
//map的键为以当前节点作为根节点,值为组成二叉搜索树的个数
HashMap<Integer,Integer> map = new HashMap<>();
public int numTrees2(int n) {
if(n==0 || n==1) {
return 1;
}
//说明以该节点为根的二叉搜索树的个数已经计算过了,直接返回结果即可
if(map.containsKey(n)) {
return map.get(n);
}
int count = 0;
for(int i=1; i<=n; i++) {
int leftCount = numTrees(i-1);
int rightCount = numTrees(n-i);
count += leftCount*rightCount;
}
//在map中存储以i为根结点组成的二叉搜索树的个数
map.put(n, count);
return count;
}
五.三角形最小路径和
1.题目
给定一个正三角形数组,自顶到底分别有 1,2,3,4,5...,n 个元素,找出自顶向下的最小路径和。
每一步只能移动到下一行的相邻节点上,相邻节点指下行种下标与之相同或下标加一的两个节点。
例如当输入[[2],[3,4],[6,5,7],[4,1,8,3]]时,对应的输出为11.
2.思路图解
首先寻找关系,根据关系写出动态规划的方程。根据题目,我们可以从最后一行开始构建dp数组,当为最后一行时,就是dp数组指定的值,之后每一个位置的dp[i][j]=min(dp[i+1][j+1])+triangle[i][j];
根据题目可得,当前位置的值就是当前位置下一行对应的位置和右边的最小值,最终结果为dp[0][0]就为最小值。
3.代码
public int minTrace (int[][] triangle) {
if(triangle.length==0) {
return 0;
}
int len = triangle[triangle.length-1].length;
int[][] dp = new int[len][len];
//从下到上依次动态规划进行向上查找
for(int i=len-1; i>=0; i--) {
for(int j=0; j<=i; j++) {
if(i==len-1) {
dp[i][j] = triangle[i][j];
}else {
//dp[i][j]就为当前位置的结果当前位置的下和右dp的最小值
dp[i][j] = Math.min(dp[i+1][j+1], dp[i+1][j])+triangle[i][j];
}
}
}
return dp[0][0];
}
六.求二维矩阵的最大正方形
1.题目
在一个由 '0'
和 '1'
组成的二维矩阵内,找到只包含 '1'
的最大正方形,并返回其面积。
示例:
2.思路图解
解法1的思路:
解法2的思路:
3.代码实现
(1)普通方法求解,验证每一个矩形是否满足正方形
public int maximalSquare(char[][] matrix) {
int rs = matrix.length;
int cs = matrix[0].length;
int maxSide = 0;
for(int i=0; i<rs; i++) {
for(int j=0; j<cs; j++) {
if(matrix[i][j]=='1'){
maxSide = Math.max(maxSide, 1);
//如果左上角的1存在就找开始找最大矩形
//首先从最大矩形开始匹配
int currentSide = Math.min(rs-i, cs-j);
for(int k=1; k<currentSide; k++) {
//首先判断对角线元素是否满足
if(matrix[i+k][j+k]=='0') {
//说明不符合直接返回
break;
}
boolean flag = true;
//然后依次匹配当前行和当前列的元素
for(int x=0; x<k; x++) {
if(matrix[i+k][j+x]=='0' || matrix[i+x][j+k]=='0') {
//说明当前列有不符合,标志位修改,然后退出循环
flag = false;
break;
}
}
//如果flag=true;说明当前边长加1的矩形符合要正方形
//,那么修改最大边长,否则直接退出
if(flag) {
maxSide = Math.max(maxSide, k+1);
}else {
break;
}
}
}
}
}
return maxSide*maxSide;
}
(2)使用动态规划进行求解
public int maximalSquare(char[][] matrix) {
int rows = matrix.length;
int cols = matrix[0].length;
//保存最大边长
int maxSide = 0;
int[][] dp = new int[rows][cols];
for(int i=0; i<rows; i++) {
for(int j=0; j<cols; j++) {
if(matrix[i][j]=='1') {
//边界问题
if(i==0 || j==0) {
dp[i][j] = 1;
} else {
//根据dp确定正方形大小
//dp数组中存储的是每一个位置能够成为正方形的最大值边长
//这里的正方形的右下角由左上,左,上三个位置组成,所以取最小值加1
dp[i][j] = Math.min(dp[i-1][j-1], Math.min(dp[i][j-1],dp[i-1][j]))+1;
}
//确定边界大小
maxSide = Math.max(dp[i][j], maxSide);
}
}
}
return maxSide*maxSide;
}
七.零钱兑换
1.题目
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例:
输入:coins = [1, 2, 5], amount = 6 输出:2 解释:6 = 5 + 1
2.思路图解
3.代码
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
Arrays.fill(dp, amount+1);
dp[0] = 0;
//i 表示当前的钱数,计算从0到amount每个钱需要的硬币数
for(int i=0; i<=amount; i++) {
for(int j=0; j<coins.length; j++) {
//判断当前的硬币金额是否可以构成当前的一部分钱数
if(i>=coins[j]) {
dp[i] = Math.min(dp[i], dp[i-coins[j]]+1);
}
}
}
//根据最后的结果是否为是否可以构成零钱兑换
return dp[amount]==amount+1?-1:dp[amount];
}