高频算法面试题(七)- 两数之和 & 跳台阶

244 阅读1分钟

「这是我参与11月更文挑战的第 4 天,活动详情查看:2021最后一次更文挑战

两数之和

题目来源LeetCode-1. 两数之和

题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案

示例

示例 1

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2 <= nums.length <= 104
  • 109 <= nums[i] <= 109
  • 109 <= target <= 109
  • 只会存在一个有效答案

解题

思路

这道题暴力去解,很简单,但是时间复杂度比较高。时间复杂度高的原因是,当我们遍历到nuns中的一个元素之后,需要跟nums中的剩余每一个元素相加,与target进行比较。因此我们就想,如何能高效的根据当前的值,在nums中找到另一个与它相加和为target的值

快速查找,不免会想到散列表。因此,我们可以将nums中的值作为下标,下标作为值,存储到散列表中。这样,当我们遍历到一个值时,拿着target减去该值得到的结果,去散列表中寻找,如果找到了,那这两个值对应的下标就是我们要的;如果没找到则将该数据存入散列表,继续遍历。这样我们就可以在O(n)的时间复杂度下找到这两个数字的下标

代码

func TwoSum(nums []int, target int) []int {
	if len(nums) < 2 {
		return nil
	}

	hashMap := make(map[int]int, len(nums))

	for j := 0; j < len(nums); j++ {
		if v, ok := hashMap[target-nums[j]]; ok {
			return []int{j, v}
		}
		hashMap[nums[j]] = j
	}

	return nil
}

跳台阶

题目来源LeetCode-剑指 Offer 10- II. 青蛙跳台阶问题

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1

示例

示例 1

输入:n = 2
输出:2

示例 2

输入:n = 7
输出:21

示例 3

输入:n = 0
输出:1

解题

思路

这道题经常会看见,一看见大家就会想到使用递归来解。那为什么会想到用递归来解这道题?如果我们知道什么样的题适合用递归来解,不就好了吗?

这里简单说一下适合用递归解的题有哪些特点

  • 一个问题的解可以分解成几个子问题的解
  • 子问题除了数据规模不一样,求解思路必须完全一样
  • 存在递归终止条件

关于什么题适合用递归解?如何解递归题?可以参考我的这篇文章

回到本题,套用上边提到的递归题有哪些特点,看一下本题是否满足这些特点

  1. **一个问题的解可以分解成几个子问题的解。**求青蛙跳上一个n级的台阶有几种跳法,那如果我知道跳n-1级台阶有多少种跳法不就好了,想知道跳n-1级台阶有多少种跳法,知道跳n-2级台阶有多少种跳法不就好了。....一直到2级台阶有几种跳法
  2. 从1中可以看出来,n级、n-1级、n-2级、...、2级,这些子问题,除了数据规模不一样,其它都是一样的
  3. 有递归终止条件,当n=2、n=1的时候,就是终止条件

因此,就可以用递归来解

那如何来解递归题?有没有什么规律?

  • 写出递归公式
  • 找到递归条件

OK,按照这个规律来解题。求n级台阶的走法,因为每一级台阶都有两种走法,要么一次走一级,要么一次走两级。那如果我想知道n级台阶的走法,不就是第一次走一级和第一次走两级的和吗?因此就可以轻松的得出下边的公式(f(n)是,n级台阶有多少种走法)

f(n) = f(n-1) + f(n-2)

知道了递归公式,下边就是终止条件。这个就简单了

f(2) = 2(分两个一级走,和一次走完两级,两种走法)
f(1) = 1

知道递归公式和终止条件,代码就很好写了

我们知道,递归代码的时间复杂度很高,是指数级的。里边有很多的重复计算,我们可以将计算过的值“缓存起来”,在求某一个的时候,先去缓存里边看有没有,如果有就不再计算了,这样可以有效提高效率(在实际的开发中,也要注意递归深度,避免溢出)

代码

//跳台阶
var depth int
var hashMap = map[int]int{}
func Step(n int) int {
	if depth > 1000 {
		return -1
	}
	if n <= 1 {
		return 1
	}
	if n==2 {
		return 2
	}

	if hashMap[n] != 0 {
		return hashMap[n]
	}

	res := Step(n-1) + Step(n-2)
	hashMap[n] = res

	return res
}