算法编程题-到达终点数字 & 将字符串翻转到单调递增 & 四因数 & 找出最具竞争力的子序列
摘要:本文将介绍四道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=1ki−target,如果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=1k−1i<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=1ki−target<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)
171万+

被折叠的 条评论
为什么被折叠?



