0
点赞
收藏
分享

微信扫一扫

代码随想录之贪心系列区间问题算法

北冥有一鲲 2022-05-04 阅读 55

文章目录

代码随想录之贪心系列区间问题算法

1.跳跃游戏

1.1贪心算法

func canJump(nums []int) bool {
	farthest := 0

	for i, v := range nums {
		//不断的计算能跳到的最大索引
		farthest = max(farthest, i+v)
		//如果faarthest大于等于了终点下标,直接return true就可以了
		if farthest >= len(nums)-1 {
			return true
		}
		//可能碰到0,卡住不动了,及时退出.不能再往后算了,否则不正确
		if farthest == i {
			return false
		}

	}

	return false
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

image-20220302195212676

1.2动态规划


image-20220302192814191

2.跳跃游戏II

2.1贪心算法(详细版)

//题目说了:我们总是可以跳到最后一个位置的
func jump(nums []int) int {
	a := 0               //即返回值,走的最大步数
	maxdistace := 0      // 当前覆盖最远距离下标
	nextmaxdistance := 0 // 下一步覆盖最远距离下标
	
	for i, v := range nums {
		// 更新下一步覆盖最远距离下标
		nextmaxdistance = max(nextmaxdistance, i+v)
		if i == maxdistace {
			if maxdistace != len(nums)-1 {
				a = a + 1 //如果当前覆盖最远距离下标不是终点,则需要再走一步
				maxdistace = nextmaxdistance
				// 下一步的覆盖范围已经可以达到终点,结束循环
                //先移动,再跳
				if nextmaxdistance >= len(nums)-1 {
					return a
				}
			} else if maxdistace == len(nums)-1{
				return a // 当前覆盖最远距离下标是集合终点,不用做ans++操作了,直接结束
			}
		}
	}
	return a
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

2.2贪心算法(优化版)

//相对于详细版来说,其精髓在于控制移动下标i只移动到nums.size() - 2的位置(原因:多加了一步),所以移动下标只要遇到当前覆盖最远距离的下标,直接步数加一,不用考虑别的
func main() {
	jump([]int{2, 3, 1, 1, 4, 3, 2, 5})
}
func jump(nums []int) int {
	a := 0               //即返回值,走的最大步数
	maxdistace := 0      // 当前覆盖最远距离下标
	nextmaxdistance := 0 // 下一步覆盖最远距离下标
	//注意:这里是len[nums]-1,这是关键所在
    //由于一定能到达,所以不用考虑[0,2,1]这种开头第一个是0元素的数据
    //当nums=[0]时,不会进入for循环,直接return 0
    //不能改成i<len(nums),会出错
    //如果这道题的nums时的元素个数>=2时,第一个元素一定不会等于0
    // 这里有个小细节,因为是起跳的时候就 + 1 了,如果最后一次跳跃刚好到达了最后一个位置,那么遍历到最后一个位置的时候就会再次起跳,这是不允许的,因此不能遍历最后一个位置
	for i :=0;i<len(nums)-1;i++  {
		// 更新下一步覆盖最远距离下标
		nextmaxdistance = max(nextmaxdistance, i+nums[i])
        //即遍历完了上次所能覆盖的最大范围,该重新定义范围
		if i == maxdistace {
			//遇到当前覆盖的最远距离下标,则更新当前覆盖的最远距离下标
			maxdistace = nextmaxdistance
			a++//先跳再移动
		}
	}
	return a
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
    
}
//之后会一直循环,一直不进入if循环,maxdistace=8 nextmaxdistance=8  a=3 一直保持不变
//题目说明:假设你总是可以到达数组的最后一个位置。
 即一定存在一条路线能够到达最后一个位置,而不是说中间没有 0 ,只是存在可以越过 0 的路线
//
i=0: nextmaxdistance=2 进入if循环  maxdistace=2 a=1
i=1:nextmaxdistance=2 不进入if循环
i=2 nextmaxdistance=4  进入if循环  maxdistace=4  a=2
i=3 nextmaxdistance=4  不进入if循环  
i=4 nextmaxdistance=8  进入if循环  maxdistace=8 a=3
//https://www.bilibili.com/video/BV1SA41147aU?from=search&seid=6340119370474831070&spm_id_from=333.337.0.0

image-20220303205833179

image-20220303201113517

image-20220303202323656

image-20220303202342210

2.3动态规划

// v1 动态规划
func jump(nums []int) int {
    // 动态规划四步走:
    // 1、状态:f[i] 表示从起点到当前位置跳的最小次数
    // 2、推导:f[i] = min(f[j]+1),a[j]+j >=i 表示从j位置用一步跳到当前位置,这个j位置可能有多个,取最小的一个就行
    // 3、初始化:f[0] = 0
    // 4、结果:f[n-1]
    f := make([]int, len(nums))
    f[0] = 0//跳到第一个数的是最小次数是0
    for i := 1; i < len(nums); i++ {
        // f[i] 先取一个默认最大值i,i是最大值
        f[i] = i
        // 遍历之前结果取一个最小值+1
        //假如说i是3,那么j只遍历到2
        for j := 0; j < i; j++ {
            ///穷举每一个选择
        // 计算每一个子问题的结果
            //”j当前所在索引+它可以走的最大值"大于当前i所在索引
            if nums[j]+j >= i {
                //j所在索引再走一步即可走到i ;  一次次的于F[i]做比较 
                f[i] = min(f[j]+1,f[i])
            }
        }
    }
    return f[len(nums)-1]//跳到最后一个元素的最小次数
}
func min(a, b int) int {
    if a > b {
        return b
    }
    return a
}

//就可以暴力穷举所有可能的跳法,通过备忘录 memo 消除重叠子问题,取其中的最小值最为最终答案
使用数组dp[i]记录跳跃到当前位置所需的最少跳跃次数,dp[i]由dp[j]决定(其中0<=j<i),当j+nums[j]>=i时,说明可以从位置j跳跃到位置i,从所有可以跳跃的j中选出最小的dp[j],则到达当前位置的最小跳跃次数为dp[i]=dp[j]+1。最后返回终点的dp[n-1]即为到达数组最后一个位置所需的最小跳跃次数(其中n为nums长度)。

image-20220303205936411

image-20220302200007351

3.合并区间

3.1贪心算法

//贪心算法
func merge(intervals [][]int) [][]int {
    //先从小到大排序,对左边接进行排序
    sort.Slice(intervals,func(i,j int)bool{
        return intervals[i][0]<intervals[j][0]  s
    })
    //再弄重复的
    for i:=0;i<len(intervals)-1;i++{
    /*有4种情况
      [1,5]  [2,3]  ------[1,5]=[1,5]    这是包含关系,区间包含
      [1,5]  [2,6]  ------[1,5]=[1,6]    区间重叠
      [1,5]  [1,5]  ------[1,5]=[1,5]
      [1,5]  [6,9]
    */  
        if intervals[i][1]>=intervals[i+1][0]{
            intervals[i][1]=max(intervals[i][1],intervals[i+1][1])//赋值最大值
            intervals=append(intervals[:i+1],intervals[i+2:]...)//删除第i+1个元素
            i--
        }
    }
    return intervals
}
func max(a,b int)int{
    if a>b{
        return a
    }
    return b
}
//时间复杂度:O(n*logn)

image-20220303215745734

image-20220305213009070

image-20220303215917561

image-20220303214539068

4.划分字母区间

4.1贪心算法

//贪心算法
func partitionLabels(s string) []int {
	var res []int
	var marks [26]int
	size, left, right := len(s), 0, 0
	//用于记录每个字母最后一次出现的下标位置,第二次遍历时进行字符串的划分。
	for i := 0; i < size; i++ {
		//marks[0]代表的是0;marks[25]代表的是z;且
		marks[s[i]-'a'] = i
		/*不能直接写成marks[s[i]] = i的原因是s[i]的asscii码太大,会超出索引26*/
	}
	/*从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点*/
	for i := 0; i < size; i++ {
		//marks[s[i]-'a']是单当前字母最后一次出现的索引
		right = max(right, marks[s[i]-'a'])
		if i == right {
			res = append(res, right-left+1)
			left = i + 1 //记录每一个片段的第一个值所对应的索引
		}
	}
	return res
}

func max(a, b int) int {
	if a < b {
		a = b
	}
	return a
}

//传入[abab]输出[4]
//时间复杂度:O(N)

image-20220305214858836

image-20220305213526927

image-20220305220016898

5.无重叠区间

5.1贪心算法

//贪心算法
func eraseOverlapIntervals(intervals [][]int) int {
	var flag int
	//先从小到大排序,对左边界进行排序
     升序排序,时间复杂度O(NlogN),快排
	sort.Slice(intervals,func(i,j int)bool{
		return intervals[i][0]<intervals[j][0]
	})
	/*有4种情况
	  [1,5]  [2,3]     这是包含关系,区间包含
	  [1,5]  [2,6]   区间重叠
	  [1,5]  [1,5]  
	  [1,5]  [6,9]
	*/
	for i:=0;i<len(intervals)-1;i++{
        // 因为已经按照左端点进行升序排序,所以只要左边的右端点越过了右边的左端点,便出现了重叠区间
		if intervals[i][1]>intervals[i+1][0]{
            // 此时需要移除的最小重叠区间数累加1
			flag++
            // 要移除的重叠区间数最小,自然是要右边的右端点尽可能小,才能尽可能的不与后继区间(i+1区间)重叠
			intervals[i+1][1]=min(intervals[i][1],intervals[i+1][1])
		}
	}
	return flag
}
func min(a,b int)int{
	if a>b{
		return b
	}
	return a
}

//时间复杂度O(NlogN+N), 空间复杂度O(1)

image-20220305221338763

6.用最少数量的箭引爆气球

6.1贪心算法

func findMinArrowShots(points [][]int) int {
	var res int =1//弓箭数,初始为1
	//先按照第一位排序
	sort.Slice(points,func (i,j int) bool{
		return points[i][0]<points[j][0]
	})

	for i:=0;i<len(points)-1;i++{
		if points[i][1]<points[i+1][0]{//如果前一位的右边界小于后一位的左边界,则一定不重合
			res++//不重合的话需射一箭
		}else{
            //复制完后相等于重新开始计算
			points[i+1][1] = min(points[i][1], points[i+1][1]); // 更新重叠气球最小右边界,覆盖该位置的值,留到下一步使用
		}
	}
	return res
}
func min(a,b int) int{
	if a>b{
		return b
	}
	return a
}
//如果有重合的话,射击的距离一定是再a[i+1]与min(a[i,1],a[i+1,1])之间的,为了更大距离的可以射到下一个气球则赋值a[i+1,1]为min(a[i,1],a[i+1,1])

image-20220307204234389

image-20220307204504623

image-20220307203627172

image-20220307203703014

举报

相关推荐

0 条评论