算法编程题-到达终点数字 & 将字符串翻转到单调递增 & 四因数 & 找出最具竞争力的子序列


摘要:本文将介绍四道LeetCode原题,分别是到达终点数字,将字符串翻转到单调递增,四因数和找出最具竞争力的子序列。首先会给出相关题目的描述,然后是思路分析,接着给出golang语言实现的且通过所有测试用例的代码,最后是相关的复杂度分析。
关键词:LeetCode,golang,面试,算法,数学,枚举,单调队列

到达终点数字

原题描述

LeetCode754 到达终点数字:在一个无限长的数轴上,从原地出发,每一步可以往左或者往右走,第i次必须走i步,问至少需要多少次才能到达给定的位置。

思路简述

这个问题可以转换为找一个k值,使得 ∑ i = 1 k ± i = t a r g e t \sum_{i=1}^k\pm i=target i=1k±i=target。为了方便描述和处理,可以将target转换成一个正数。首先找到最小的k使得 ∑ i = 1 k i \sum_{i=1}^k i i=1ki大于等于target,很容易证明,任何小于k的数都不可能满足题目的要求。如果上式恰好等于target,则k就是答案。否则计算 g a p = ∑ i = 1 k i − t a r g e t gap=\sum_{i=1}^k i-target gap=i=1kitarget,如果gap是偶数,那么k也能满足要求。为什么?因为现在往右一直走k步超过了target,也就是说前面的某些步需要往左走,且这些步的步数之和为gap/2,如果gap是奇数,显然不能满足要求。由于以下结论成立:若 s = ∑ i = 1 k i s=\sum_{i=1}^k i s=i=1ki,对于[1, s]内的任何数字,都可以通过[1, k]内若干数字之和组成。而根据前面的条件,有 ∑ i = 1 k − 1 i < t a r g e t \sum_{i=1}^{k-1} i < target i=1k1i<target,所以$ g a p = ∑ i = 1 k i − t a r g e t < k gap=\sum_{i=1}^k i -target < k gap=i=1kitarget<k,所以 g a p / 2 < k / 2 gap/2 < k / 2 gap/2<k/2,根据上面的结论,一定可以通过[1,k]内的若干数字组合成gap/2,即如果gap是偶数一定可以做到。那么如果gap是奇数了,那就去看k+1,k+2,因为奇偶数交替出现,所以k+1和k+2中至少有一个会使得gap为偶数。

代码实现

func reachNumber(target int) int {
    t := math.Abs(float64(target))
    // 基于一元二次方程的求根公式
    k := int(math.Ceil((-1 + math.Sqrt(1 + 8 * t)) / 2))
    gap := (k * k + k) / 2 - int(t)
    for gap % 2 != 0 {
        k++
        gap = (k * k + k) / 2 - int(t)
    }
    return k
}

LeetCode运行截图如下:
在这里插入图片描述

复杂度分析

  • 时间复杂度: O ( 1 ) O(1) O(1),开根号运算可以认为时间复杂度是 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( 1 ) O(1) O(1)

将字符串翻转到单调递增

原题描述

LeetCode926 将字符串翻转到单调递增:给定一个只包含0和1两种字符的子序列,可以将0翻转为1,将1翻转为0,求将字符串翻转为单调递增的最少翻转次数。

思路简述

因为递增字符串的话前面确定是若干0,接着是若干1,所以可以直接枚举0和1之间的间隔线,首先通过一次遍历收集0字符的个数,然后接着来一次遍历,将前一段全部翻转为0,后一段全部翻转为1,然后取最小的翻转次数即可。

代码实现

func minFlipsMonoIncr(s string) int {
    n := len(s)
	zeroCount := 0
	for i := 0; i < n; i++ {
		if s[i] == '0' {
			zeroCount++
		}
	}

	ans := n - zeroCount   // 全部翻转为0
    oneCount := 0
	for i := 0; i < n; i++ {
		ans = min(ans, zeroCount + oneCount)
        if s[i] == '1' {
            oneCount++
        } else {
            zeroCount--
        }
	}
	return ans
}

LeetCode运行截图如下:
在这里插入图片描述
看起来性能有点差,可以对一些分支语句进行优化,如下代码和运行截图:

func minFlipsMonoIncr(s string) int {
    n := len(s)
	oneCount := 0
	for i := 0; i < n; i++ {
		oneCount += int(s[i] & 1)
	}
	ans := n - oneCount   // 全部翻转为1
	for i := 0; i < n; i++ {
        oneCount += (-2 * int(s[i] & 1) + 1)
		ans = min(ans, n - oneCount)
	}
	return ans
}

在这里插入图片描述

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

四因数

原题描述

LeetCode1490 四因数:给定一个数组,求数组中所有只有四个因数的数的这四个因数之和

思路简述

可以考虑用一种简单粗暴的方法,遍历每一个数字,然后判断这个数字有那些因数,这样做的时间复杂度为 O ( n m ) O(n \sqrt m) O(nm ),其中m是数组中的最大数字。

代码实现

func sumFourDivisors(nums []int) int {
    ans := 0
	for _, num := range nums {
		ans += sumFourDivisor(num)
	}
	return ans
}


// sumFourDivisor 返回num的四因数之和,如果因数数量不是4,返回0
func sumFourDivisor(num int) int {
	res := 0
	for i := 2; i <= int(math.Sqrt(float64(num))); i++ {
		if num % i == 0 {
			if i * i == num || res != 0 {
				return 0    // 说明存在单个(i, i)因数对
			}
			res += i
			res += num / i
		}
	}
	if res == 0 {
		return 0
	}
	return res + num + 1
}

LeetCode运行截图如下:
在这里插入图片描述

复杂度分析

  • 时间复杂度: O ( n m ) O(n \sqrt m) O(nm )
  • 空间复杂度: O ( 1 ) O(1) O(1)

找出最具竞争力的子序列

原题描述

LeetCode 1673 找出最具竞争力的子序列 :给定一个数组nums和一个整数k,找出数组中长度为k的最具竞争力的子序列,所谓的最具竞争力值得是序列在第一个不相等的位置上要小一点。

思路简述

可以想到,为了得到最具竞争力的子序列,首先在这个序列的第一个位置上,应该取[0, n-k]下标范围内的最小的数字,n-k后面的不能考虑是因为要保证这个子序列的长度为k。接着每次往后考虑一个位置,然后取最小数即可。这一个过程可以使用单调队列来实现。

代码实现

func mostCompetitive(nums []int, k int) []int {
    n := len(nums)
	ans := make([]int, k)
	q := list.New()
	for i := 0; i <= n - k; i++ {
		for q.Len() != 0 && nums[q.Back().Value.(int)] > nums[i] {
			q.Remove(q.Back())
		}
		q.PushBack(i)
	}
	ans[0] = nums[q.Front().Value.(int)]
	q.Remove(q.Front())
	for i := 1; i < k; i++ {
		for q.Len() != 0 && nums[q.Back().Value.(int)] > nums[n - k + i] {
			q.Remove(q.Back())
		}
		q.PushBack(n - k + i)
		ans[i] = nums[q.Front().Value.(int)]
		q.Remove(q.Front())
	}
	return ans
}

LeetCode运行截图如下:
在这里插入图片描述

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值