代码随想录day58|单调栈|739. 每日温度|496.下一个更大元素 I|Golang

代码随想录day58

倒计时48小时

目录

代码随想录day58

739. 每日温度

思路

496.下一个更大元素 I

思路


739. 每日温度

思路

        首先想到的当然是暴力解法,两层for循环,把至少需要等待的天数就搜出来了。时间复杂度是O(n^2)

        那么接下来在来看看使用单调栈的解法。

        那有同学就问了,我怎么能想到用单调栈呢? 什么时候用单调栈呢?

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

时间复杂度为O(n)。

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

        那么单调栈的原理是什么呢?为什么时间复杂度是O(n)就可以找到每一个元素的右边第一个比它大的元素位置呢?

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

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

1、单调栈里面放的元素是什么?

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

2、单调栈里元素是递增呢? 还是递减呢?

        注意一下顺序为 从栈头到栈底的顺序,因为单纯的说从左到右或者从前到后,不说栈头朝哪个方向的话,大家一定会越看越懵。

        这里我们要使用递增循序(再强调一下是指从栈头到栈底的顺序),因为只有递增的时候,加入一个元素i,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。

        文字描述理解起来有点费劲,接下来我画了一系列的图,来讲解单调栈的工作过程。

使用单调栈主要有三个判断条件。

  • 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

把这三种情况分析清楚了,也就理解透彻了

        接下来我们用temperatures = [73, 74, 75, 71, 71, 72, 76, 73]为例来逐步分析,输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

首先先将第一个遍历元素加入单调栈

         加入T[1] = 74,因为T[1] > T[0](当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况),而我们要保持一个递增单调栈(从栈头到栈底),所以将T[0]弹出,T[1]加入,此时result数组可以记录了,result[0] = 1,即T[0]右面第一个比T[0]大的元素是T[1]。

 加入T[2],同理,T[1]弹出

 加入T[3],T[3] < T[2] (当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况),加T[3]加入单调栈。

 加入T[4],T[4] == T[3] (当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况),此时依然要加入栈,不用计算距离,因为我们要求的是右面第一个大于本元素的位置,而不是大于等于!

加入T[5],T[5] > T[4] (当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况),将T[4]弹出,同时计算距离,更新result 

 T[4]弹出之后, T[5] > T[3] (当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况),将T[3]继续弹出,同时计算距离,更新result

 直到发现T[5]小于T[st.top()],终止弹出,将T[5]加入单调栈

 加入T[6],同理,需要将栈里的T[5],T[2]弹出

 同理,继续弹出

此时栈里只剩下了T[6]

 加入T[7], T[7] < T[6] 直接入栈,这就是最后的情况,result数组也更新完了。

此时有同学可能就疑惑了,那result[6] , result[7]怎么没更新啊,元素也一直在栈里。

其实定义result数组的时候,就应该直接初始化为0,如果result没有更新,说明这个元素右面没有更大的了,也就是为0。

以上在图解的时候,已经把,这三种情况都做了详细的分析。

  • 情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
  • 情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
  • 情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

Go代码如下:

// 暴力
func dailyTemperatures(t []int) []int {
    var res []int
    for i := 0; i < len(t)-1; i++ {
        j := i + 1
        for ; j < len(t); j++ {
            // 如果之后出现更高,说明找到了
            if t[j] > t[i] {
                res = append(res, j-i)
                break
            }
        }
        // 找完了都没找到
        if j == len(t) {
            res = append(res, 0)
        }
    }
    // 最后一个肯定是 0
    res = append(res, 0)
    return res
}


// 未精简
func dailyTemperatures(temperatures []int) []int {
    res := make([]int, len(temperatures))
    // 初始化栈顶元素为第一个下标索引0
    stack := []int{0}

    for i := 1; i < len(temperatures); i++ {
        top := stack[len(stack)-1]
        if temperatures[i] < temperatures[top] {
            stack = append(stack, i)
        } else if temperatures[i] ==  temperatures[top] {
            stack = append(stack, i)
        } else {
            for len(stack) != 0 && temperatures[i] > temperatures[top] {
                res[top] = i - top
                stack = stack[:len(stack)-1]
                if len(stack) != 0 {
                    top = stack[len(stack)-1]
                }
            }
            stack = append(stack, i)
        }
    }
    return res
}


// 精简
// 单调递减栈
func dailyTemperatures(num []int) []int {
    ans := make([]int, len(num))
    stack := []int{}
    for i, v := range num {
        // 栈不空,且当前遍历元素 v 破坏了栈的单调性
        for len(stack) != 0 && v > num[stack[len(stack)-1]] {
            // pop
            top := stack[len(stack)-1]
            stack = stack[:len(stack)-1]

            ans[top] = i - top
        }
        stack = append(stack, i)
    }
    return ans
}

496.下一个更大元素 I

思路

做本题之前,建议先做一下739. 每日温度

739. 每日温度中是求每个元素下一个比当前元素大的元素的位置。

本题则是说nums1 是 nums2的子集,找nums1中的元素在nums2中下一个比当前元素大的元素。

看上去和739. 每日温度就如出一辙了。几乎是一样的,但是这么绕了一下,其实还上升了一点难度。需要对单调栈使用的更熟练一些,才能顺利的把本题写出来。

从题目示例中我们可以看出最后是要求nums1的每个元素在nums2中下一个比当前元素大的元素,那么就要定义一个和nums1一样大小的数组result来存放结果。

一些同学可能看到两个数组都已经懵了,不知道要定一个一个多大的result数组来存放结果了。

这么定义这个result数组初始化应该为多少呢?

        题目说如果不存在对应位置就输出 -1 ,所以result数组如果某位置没有被赋值,那么就应该是是-1,所以就初始化为-1。

        在遍历nums2的过程中,我们要判断nums2[i]是否在nums1中出现过,因为最后是要根据nums1元素的下标来更新result数组。

注意题目中说是两个没有重复元素 的数组 nums1 和 nums2

        没有重复元素,我们就可以用map来做映射了。根据数值快速找到下标,还可以判断nums2[i]是否在nums1中出现过。

那么预处理代码如下:

unordered_map<int, int> umap; // key:下标元素,value:下标
for (int i = 0; i < nums1.size(); i++) {
    umap[nums1[i]] = i;
}

使用单调栈,首先要想单调栈是从大到小还是从小到大。

本题和739. 每日温度是一样的。

栈头到栈底的顺序,要从小到大,也就是保持栈里的元素为递增顺序。只要保持递增,才能找到右边第一个比自己大的元素。

可能这里有一些同学不理解,那么可以自己尝试一下用递减栈,能不能求出来。其实递减栈就是求右边第一个比自己小的元素了。

接下来就要分析如下三种情况,一定要分析清楚。

  1. 情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况

此时满足递增栈(栈头到栈底的顺序),所以直接入栈。

  1. 情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况

如果相等的话,依然直接入栈,因为我们要求的是右边第一个比自己大的元素,而不是大于等于!

  1. 情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

此时如果入栈就不满足递增栈了,这也是找到右边第一个比自己大的元素的时候。

判断栈顶元素是否在nums1里出现过,(注意栈里的元素是nums2的元素),如果出现过,开始记录结果。

记录结果这块逻辑有一点小绕,要清楚,此时栈顶元素在nums2中右面第一个大的元素是nums2[i]即当前遍历元素。

代码如下:

while (!st.empty() && nums2[i] > nums2[st.top()]) {
    if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素
        int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下标
        result[index] = nums2[i];
    }
    st.pop();
}
st.push(i);

以上分析完毕,Go代码如下:

func nextGreaterElement(nums1 []int, nums2 []int) []int {
	ans := make([]int, len(nums1))
	for i := 0; i < len(ans); i++ {
		ans[i] = -1
	}
	has1 := make(map[int]int, len(nums1))
	for i, v := range nums1 {
		has1[v] = i
	}

	stack := []int{0}
	for i := 1; i < len(nums2); i++ {
		top := stack[len(stack)-1]
		if nums2[top] >= nums2[i] {
			stack = append(stack, i)
		} else {
			for len(stack) != 0 && nums2[top] < nums2[i] {
				value, ok := has1[nums2[top]]
				if ok {
					ans[value] = nums2[i]
				}
				stack = stack[:len(stack)-1]
				if len(stack) != 0 {
					top = stack[len(stack)-1]
				}
			}
			stack = append(stack, i)
		}
	}

	return ans
}


// 简化
func nextGreaterElement(nums1 []int, nums2 []int) []int {
    ans := make([]int, len(nums1))
    for i := range nums1 {
        ans[i] = -1
    }
    has1 := make(map[int]int,len(nums1))
    for i, v := range nums1 {
        has1[v] = i
    }
    stack := []int{}
    
    for i:=0;i<len(nums2);i++{
        for len(stack) != 0 && nums2[i] > nums2[stack[len(stack)-1]] {
            _, ok := has1[nums2[stack[len(stack)-1]]]
            if ok {
                ans[has1[nums2[stack[len(stack)-1]]]] = nums2[i]
            }
            stack = stack[:len(stack)-1]
        }
        stack = append(stack,i)
    }


    return ans
}


// 用for range 
func nextGreaterElement(nums1 []int, nums2 []int) []int {
    ans := make([]int,len(nums1))
    for i := range ans {
        ans[i] = -1
    }
    has1 := make(map[int]int,len(nums1))
    for i, v := range nums1 {
        has1[v] = i
    }

    stack := make([]int,len(nums1))

    for i, v := range nums2 {
        for len(stack) != 0 && v > nums2[stack[len(stack)-1]] {
            _, ok := has1[nums2[stack[len(stack)-1]]]
            if ok {
                ans[has1[nums2[stack[len(stack)-1]]]] = v
            }
            stack = stack[:len(stack)-1]
        }
        stack = append(stack, i)
    }
    return ans
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值