链接:https://pan.baidu.com/s/1DbNXI1W_i8gni1Fqz6CWbQ
提取码:p7mu
将 code.py 文件的输出定向到 ouput 文件中
简单的linux例子:cat input | python code.py > output
如果代码执行时间超出指定时间,会返回 time limit exceed(时限超出)。代码执行完成之后,
判题系统将得到的输出文件与正确答案进行比对,如果输出文件与正确答案相同则返回
accepted 。如果输出文件与正确答案比对有差别则返回 wrong answer(错误的答案) 或者
output limie exceed(输出太多信息)。
codeforce:包含输入输出部分,但输出不唯一。
这类题目很有趣,举个例子:
这类题目没有固定的答案,100个人可能有100种答案。这类题目与输出唯一题目不同的是:这
种题目在判题的时候不是对比输出文件和正确答案。网站会预先准备一套判题代码,判题代码会读取输出文件和正确答案,然后通过特有的逻辑判断答案的对错。
LeetCode:给定一个函数定义,在函数中实现逻辑。
对于这种类型的题目 LeetCode 会针对每一道题目写一个测试代码。你输入什么,测试代码对
比输出什么。如果答案不唯一还会根据题目信息写一个校验代码。
判题系统会将我们实现的函数代码进行编译,然后与测试代码放在一起。测试代码调用我们编写
的函数进行测试,并且设置超出时间。当我们编写的代码输出结果后,由校验代码判断是否通
过。
在各种类型的刷题网站中,大部分题目要求答案代码执行时间都是1秒钟。所以我们在做题之前
需要优先考虑我们即将要实现的算法能否在 1 秒钟之内完成,这就要求我们了解 1 秒钟大概能
做什么量级的运算。在特定的数据范围内我们的算法会不会超时,所以对算法的时间复杂度预估
就非常重要。
时间复杂度
说到排序,我们的脑子里总会出现几种耳熟能详的排序算法,如插入排序、冒泡排序,快速排序
和归并排序。其中插入排序、冒泡排序的时间复杂度是o(n^2),快速排序、归并排序的时间复
杂度是o(nlogn)。那么这个时间复杂度是什么意思呢?
大家都知道,计算机执行程序是通过二进制指令来执行的,二进制数据最基本的操作有——与
(&)、或(|)、非()、异或(),基本四则运算(+、-、*、/),逻辑运算(>、<、=)是
转换成有限次二进制指令来执行,次数不跟参与计算的数据大小有关,我们称之为o(1)复杂度。
大家请看如下代码:
int sum = 0;
for (int i = 0;i < N;i++) {
for (int j = 0;j < N;j++) {
sum++;
}
}
这段代码中, int sum = 0 执行了一次, int i = 0 执行了一次, i < N 执行了N次, i++ 执行了 N 次, int j = 0 执行了N次, j < N 、j++ 和sum++ 分别执行了N^2次,我们假设赋值、时间复杂度关注的是影响速度的因素。上述式子,当 N 增大,N^2 比 N 大得多,在 N^2 面前,N 的影响几乎可以忽略,因此我们忽略掉 N 的部分,将上述代码的时间复杂度简写为o(N^2)。
1秒钟
我们在设定变量时,一旦变量赋值成功,该变量就在内存中有一片指向的地址,这片地址是操作
系统分配的。该变量定义的时候需声明需要多少空间,操作系统会给分配多少空间。一旦分配,
这个变量在内存中就固定下来了。
我们在代码中需要声明每个变量的类型,比如int,为4个字节的整型;double,为8个字节的浮
点型;char,为一个字节的字符等等。python由于可以不用声明类型,我们很少关注每个变量
所占用的内存空间。python底层仍然是存在基本类型,只是封装了一层动态识别。
基本类型 | 占用空间 | 表示数的个数 |
---|---|---|
char | 1个字节 | 256 |
byte | 1个字节 | 256 |
bool | 1个字节 | 256(实际上只用了两个值) |
short | 2个字节 | 65536 |
int | 4个字节 | 4294967296 |
double | 8个字节 | 11位整数,52位尾数,1位符号 |
a = [0] * N; # 空间复杂度o(N),但申请的内存空间不止4N个字节,python为数组封装了一层,里面还会记录一些常用的元数据以便快速查询,比如size
写在最后
不管是刷题,还是在平时工作中写代码,我们对自己写出来的代码的时间复杂度和空间复杂度应
当有所理解,对代码消耗的内存和花费的时间有个底。时间复杂度和空间复杂度都需要根据场景
变化而变化。比如响应速度快的,通常用空间换时间的原则;内存放不下,就适当放宽时间复杂
度来换内存的优化。设计代码就是对时间、空间、开发成本各种要素的平衡,尽可能取到最优化
的策略。
刷题内容
难度: Easy
题目链接:https://leetcode-cn.com/problems/two-sum/。
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
题目详解
这道题目给我们一个数组,数组里面全是整数,然后再给我们一个数字target ,需要我们求出
在这个数组中哪两个数字的和正好是target 。注意,你不能重复利用这个数组中同样的元素,
指的是每一个位置上的数字只能被使用一次,比如数组 [3,3,5],你可以使用第一个和第二个
3,但是你不能使用第一个 3 两次。
暴力解法,双重循环遍历:
外层循环从数组中取出下标为 i 的元素 num[i] ,内层循环取出 i 之后的元素 nums[j] 一一与
下标为 i 的元素进行相加操作,判断结果是否为 target 。
题目只要求找到一种,所以一旦找到直接返回。时间复杂度中的 N 代表的是 nums 列表的长
度。
下面我们来看代码:
Python beats 27.6%
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
# 第一轮遍历
for i in range(len(nums)):
# 第二轮遍历不能重复计算了
for j in range(i+1, len(nums)):
if nums[i] + nums[j] == target:
# 注意 leetcode 中要求返回的是索引位置
return [i, j]
思路2:时间复杂度: O(N) 空间复杂度: O(N)
上面的思路1时间复杂度太高了,是典型的加快时间的方法,这样做是不可取的。其实我们可以
牺牲空间来换取时间。
我们希望,在我们顺序遍历取得一个数 num1 的时候,就知道和它配对的数 target-num1 是
否在我们的 nums 里面,并且不单单只存在一个。比如说target 为4 , nums 为[2,3] ,假设
我们此时取得的num1 为2 ,那么和它配对的2 确实在nums 中,但是数字2 在nums 中只出
现了一次,我们无法取得两次,所以也是不行的。
因此我们有了下面的步骤:
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
lookup = {}
for i, num in enumerate(nums):
if target - num in lookup:
return [lookup[target-num], i]
else: # 每一轮都存下当前num和其index到map中
lookup[num] = i
就像之前提到的特殊情况一样,这里注意我们要边遍历边将 num: idx 放入 lookup 中,而不是
在做遍历操作之前就将所有内容放入 lookup 中。例如数组 [3,5],target = 6,如果我们一次
性全部放进了 lookup 里面,那么当我们遍历到3的时候,我们会发现 target - num = 6-3 = 3
在 lookup 中,但其实我们只有一个 3 在数组中,而我们使用了两次,误以为我们有2个3。
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
lookup = {}
for i, num in enumerate(nums):
lookup[num] = i
for i, num in enumerate(nums):
if target - num in lookup:
return [i, lookup[target-num]]
Wrong Answer
Input
[3,2,4]
6
Output
[0,0]
Expected
[1,2]
总结
刷题内容
难度: Easy
原题连接:https://leetcode-cn.com/problems/reverse-integer/。
内容描述
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
注意:假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
题目详解
这道题中我们要注意以下几点:
我们int 型的数值范围是 -2147483648~2147483647 (-2^31 ~ 2^31 - 1) , 那么如果我们要
翻转 1000000009 这个在数值范围内的数,得到 9000000001 ,但翻转后的数就超过了范
围,这个情况就是溢出,这个时候程序返回 0 即可。
如果输入的是负数,就递归调用原函数,参数变成 -x 即可,代码大致执行流程如下:
下面来看详细代码:
Python beats 69.76%
class Solution:
def reverse(self, x: int) -> int:
if x < 0: # 判断是否为负数
return -self.reverse(-x) # 如果是负数则取绝对值调用自身,最后将结果转为负数
res = 0
while x: # 每次得到最后一位数字,并将其作为结果中的当前最高位
res = res * 10 + x % 10
x //= 10
return res if res <= 0x7fffffff else 0 # 如果溢出就返回0
小结
F(0) = 0, F(1) = 1 F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 给定 N,计算
F(N)。示例 1: 输入:2 输出:1 解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2: 输入:3 输出:2 解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3: 输入:4 输出:3 解释:F(4) = F(3) + F(2) = 2 + 1 = 3.
提示:0 ≤ N ≤ 30
递归方案 浪费时间就不写了
法案一
class Solution(object):
def fib(self, N):
"""
:type N: int
:rtype: int
"""
F = [0,1]
for i in range(2, N+1):
F.append(F[-1] + F[-2])
return F[N]
方案2
class Solution(object):
def fib(self, N):
"""
:type N: int
:rtype: int
"""
if N==1:
return 1
if N==0:
return 0
a,b=0,1
for i in range(2, N+1):
a, b = b,a+b
return b
2个方案的时间、空间复杂度几乎一样。
类似的一系列问题可以解决