「青训营 X 码上掘金」主题创作活动主题3:寻友之旅

95 阅读4分钟

当青训营遇上码上掘金

主题 3:寻友之旅

题目

小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。 步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1 公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)

思路

  • 如果有错误希望大佬指正
  • 这个题目,在我印象里和有一年蓝桥杯的的一个填空题很像
  • 思路一:广度优先搜索(BFS)
  • 思路二:贪心(GA)
  • 两个思路的代码都放在了最后

思路一:广度优先搜索(BFS)

这个题和迷宫很像,只是将东南西北四个方向换成了x+1、x-1、2*x

我们只需要从小青的位置开始一层一层的搜索,把先搜索到的点进栈(如果步数已经有记录则不进栈),记录到达该点花了多少步,如果搜完以某个节点开始的一层,则将该点出栈,然后从栈顶取出一个元素继续搜索,直到小青到达小码的位置为止。

思路二:贪心(GA)

这个题贪心思路的正确性证明我不会,但是应该是对的

先将N的值不断的乘二,找到离K最近的一次,差的绝对值记录为distance,乘二的次数记录为count1

然后将distance分解为最少的(正负)pow(2,n)之和(n>=0),这些数的个数记为count2,最终的结果就是count1+count2。这里我的理解就是如果其中有一个数是4(2的2次幂),那么就需要在倒数第二次乘二之前加1或减1(1在这里表示2的0次幂),这样就使得最终结果相差了4。

例如N=3,K=18,那么distance=6(18-3*2*2),count1=2,然后我们可以将distance分解为2+4,则count2=2,最终的结果为count1+count2=2+2=4。实际上最优的路径为:((3+1)*2+1)*2,将该式变形为:(3*2+2+1)*2=(3*2*2+4+2),这个式子就是我贪心思路中的式子

代码

package main

import (
	"fmt"
)

//「青训营 X 码上掘金」主题创作活动主题3
/*
主题 3:寻友之旅
小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。
步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1
公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)
*/
//思路一:广度优先搜索(BFS),具体的思路可以看我青训营专栏里的文章
func theme3BFS() int {
	var N, K = 2, 19 //我这里N和K的值没有从控制台输入,如果需要的话可以自己使用fmt.Scanf
	if N >= K {      //如果小青没在小码的后面,那么她只有一步步往后步行,当然也可以不做判断,直接BFS
		return N - K
	}
	var count = make([]int, 2*K+1, 2*K+1)
	var stack = make([]int, 0, 2*K+1)
	stack = append(stack, N)
	for len(stack) != 0 {
		position := stack[0]
		stack = stack[1:]
		//这里做了3次差不多的判断,有兴趣的话可以自己抽出一个函数
		if count[position+1] == 0 { //判断是否已经搜索过了这个点
			count[position+1] = count[position] + 1
			if position+1 == K {
				break
			}
			stack = append(stack, position+1)
		}

		if position*2 < 2*K && count[position*2] == 0 { //另外做了判断,并且判断的顺序不能改变(利用短路与的特性)
			count[position*2] = count[position] + 1
			if position*2 == K {
				break
			}
			stack = append(stack, position*2)
		}
		if position-1 > 0 && count[position-1] == 0 { //另外做了判断,同上
			count[position-1] = count[position] + 1
			if position-1 == K {
				break
			}
			stack = append(stack, position-1)
		}
		// fmt.Println(stack)
	}
	return count[K]
}

//思路二:贪心(GA),具体的思路可以看我青训营专栏里的文章
func theme3GA() int {
	var N, K = 2, 19 //我这里N和K的值没有从控制台输入,如果需要的话可以自己使用fmt.Scanf
	if N >= K {      //如果小青没在小码的后面,那么她只有一步步往后步行,当然也可以不做判断,直接贪心
		return N - K
	}
	var count, distance, position int = 0, K - N, N //如果上面不判断,那么distance=abs(K-N)
	for {
		if distance <= abs(position*2-K) {
			break
		}
		count++
		distance = abs(position*2 - K)
		position *= 2
	}

	// fmt.Printf("distance: %v\n", distance)
	t := 0
	for distance != 0 {
		//找到不大于distance的 最大的2的n次幂
		n := 0
		for {
			if pow(2, n+1) > distance {
				break
			}
			n++
		}
		distance = distance - pow(2, n)
		t++
		// fmt.Printf("n: %v\n", n)
	}

	return count + t
}

func abs(key int) int { //求绝对值
	if key < 0 {
		return key * -1
	}
	return key
}

func pow(x int, n int) int { //快速幂
	ans := 1
	x_contribute := x
	for n > 0 {
		if n%2 == 1 {
			ans *= x_contribute
		}
		x_contribute *= x_contribute
		n /= 2
	}
	return ans
}

func main() {
	// fmt.Println(theme3BFS())
	fmt.Println(theme3GA())
}