剑指38.字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
1.dfs,一个位置一个位置的选,当这个位置有重复元素的时候,后面重复的元素直接跳过
class Solution:
def permutation(self, s: str) -> List[str]:
if s == "":
return []
self.result = []
self.length = len(s)
self.recursion(s,[])
return self.result
def recursion(self,string,tmp):
if len(tmp) == self.length:
self.result.append("".join(tmp))
return
for i in range(len(string)):
if string.index(string[i]) < i:
continue
tmp.append(string[i])
if i+1<len(string):
tmp_string = string[:i]+string[i+1:]
else:
tmp_string = string[:i]
self.recursion(tmp_string,tmp)
tmp.pop()
2.同样是dfs,不过判断重复元素的方式与我不同,K神定义了一个set用于存放该位置用过的元素,同时原地算法,在原序列上直接进行字符交换。
class Solution:
def permutation(self, s: str) -> List[str]:
temp, result = list(s), []
def dfs(x):
if x == len(s) - 1:
result.append(''.join(temp))
return
dic = set()
for i in range(x,len(s)):
if temp[i] not in dic:
dic.add(temp[i])
temp[x], temp[i] = temp[i], temp[x]
dfs(x+1)
temp[x], temp[i] = temp[i], temp[x]
dfs(0)
return result
**自我反思:**细节细节细节,大佬的代码永远能在细节处提升效率。
剑指39.数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
1.自己的方法太呆了,效率也低就不放出来献丑了。一般容易想到的方法有哈希表法和数组排序法。这里介绍一种比较神奇的方法摩尔投票法,遍历数组,对于数字进行投票,设当前数字num为众数,遇到相同的数字就vote+1,不然vote-1,若vote==0,则设下一个数字为众数重复上述流程。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
vote = 0
for num in nums:
if vote == 0: x = num
vote += 1 if x == num else -1
return x
**自我反思:**相较于哈希表法和数组排序法,摩尔投票法不仅只需要简单遍历,还把空间复杂度压缩到了O(1)。
剑指40.最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
1.恕我直言,一看是简单题,本大聪明直接两行解决,效率还超了95%。。。
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
arr.sort()
return arr[:k]
2.借这个机会把快排复习一下。
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
def quick_sort(arr,l,r):
if l >= r:
return
i, j = l, r
while i < j:
while i < j and arr[j]>=arr[l]: j-=1
while i < j and arr[i]<=arr[l]: i+=1
arr[i], arr[j] = arr[j], arr[i]
arr[l], arr[i] = arr[i], arr[l]
quick_sort(arr,l,i-1)
quick_sort(arr,i+1,r)
quick_sort(arr,0,len(arr)-1)
return arr[:k]
这里有个小tips,下面两句话的顺序很重要,如果先排j,最后i,j会共同指向一个小于标杆的数。如果互换,先排i,最后i,j会共同指向一个大于标杆的数。这与后面标杆的位置交换方式有直接的联系。
while i < j and arr[j]>=arr[l]: j-=1
while i < j and arr[i]<=arr[l]: i+=1
**自我反思:**其实快排的方式还可以继续优化,因为题目没有要求顺序返回,所以时刻关注标杆i与k的关系,只要某次排序完后,标杆正好是第K+1个数,那就可以直接返回前K个数了
if k < i: return quick_sort(l, i - 1)
if k > i: return quick_sort(i + 1, r)
剑指41.数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1:
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
1.效率爆炸,直接击败5%的用户。。。就是每次新数字进行插入排序,然后每次返回中位数,就按照当前队列的长度来计算。
class MedianFinder:
def __init__(self):
"""
initialize your data structure here.
"""
self.seq = []
def addNum(self, num: int) -> None:
if self.seq == []:
self.seq.append(num)
return
else:
for i in range(len(self.seq)):
if self.seq[i]>num:
self.seq.insert(i,num)
return
self.seq.append(num)
return
def findMedian(self) -> float:
if self.seq == []:
return
length = len(self.seq)
mid = length//2
if length%2==1:
return self.seq[mid]
else:
return (self.seq[mid]+self.seq[length//2-1])/2
2.这道题的执行效率其实与插入的方式密切相关,K神选择了使用堆结构。这个大顶堆和小顶堆,还真是没用过,借这个机会学习一下
python heapq是小顶堆,实现大顶堆只需要将元素全部取相反数就可以了。相比于我用插入排序,效率是指数级别的提升。。。
from heapq import *
class MedianFinder:
def __init__(self):
self.A = [] # 小顶堆,保存较大的一半
self.B = [] # 大顶堆,保存较小的一半
def addNum(self, num: int) -> None:
if len(self.A) != len(self.B):
heappush(self.B, -heappushpop(self.A, num))
else:
heappush(self.A, -heappushpop(self.B, -num))
def findMedian(self) -> float:
return self.A[0] if len(self.A) != len(self.B) else (self.A[0] - self.B[0]) / 2.0
**自我反思:**堆结构是面试中经常会问的问题,后面要好好整理一下堆结构。
剑指42.连续子数组的最大和
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
1.有点类似于股票最大价值那道题,由于时间复杂度为O(n),所以单次遍历的过程中,既要维护子数组的start,同时还要维护Sum。当前和temp_sum小于0的时候,可以换新的start。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
sum_ = -100
start = 0
temp_sum = 0
for i in range(len(nums)):
if temp_sum<0:
start = i
temp_sum = nums[start]
sum_ = max(sum_,temp_sum)
continue
temp_sum = temp_sum + nums[i]
sum_ = max(sum_,temp_sum)
return sum_
2.看了K神的解释,其实这道题规定时间复杂度为O(n)的时候,就已经决定了暴力O(N^2)和分治O(NlogN)不适用了,最合适的就是动态规划。设动态规划列表 dp,dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和。
状态转移方程
若 dp[i−1]≤0 ,说明 dp[i - 1]对 dp[i] 产生负贡献,即 dp[i-1] + nums[i]还不如 nums[i]本身大。
当 dp[i - 1] > 0时:执行 dp[i] = dp[i-1] + nums[i] ;
当 dp[i - 1] <= 0时:执行 dp[i] = nums[i] ;
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
for i in range(1,len(nums)):
nums[i] += max(0,nums[i-1])
return max(nums)
但其实运行效率并不是很高,可能是因为最后那个max的原因。
**自我反思:**动态规划的题真的太多遍了,反正看到对时间复杂度要求比较高的题,尽量往动态规划上想想。
剑指43. 1~n中整数中1出现的次数
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
示例 1:
输入:n = 12
输出:5
1.说实话,这道题当时真的恶心到我了。而现在,我被自己当时的代码恶心到了。看了一下,自己当时的大致思路是这样的,每个数位上能出现1的个数,与它两边是有关系的,比如213,十位数字是1的次数,与2和3都有关系。
class Solution:
def countDigitOne(self, n: int) -> int:
place = []
result = 0
temp = n
while temp!=0:
place.append(temp%10)
temp = temp//10
place.insert(0,0)
place.append(0)
print(place)
for i in range(1,len(place)-1):
if place[i] > 1:
result += (10**(i-1))*((n//(10**i))+1)
elif place[i] == 1:
result += (10**(i-1))*(n//(10**i))+(n%(10**(i-1))+1)
else:
result += (10**(i-1))*(n//(10**i))
return result
2.K神的思路貌似和我差不多,不,是一毛一样,总结来讲
设当前位i为cur,低位为low,高位为high,10的i次方为位因子digit,
1.当cur为0时,当前位为1的个数只与high和dight有关,high×digit
2.当cur为1时,当前位为1的个数与high、low和digit有关,high×digit+low+1
3.当cur大于1时,当前位为1的个数与high和dight有关,(high+1)×digit
代码:
class Solution:
def countDigitOne(self, n: int) -> int:
digit, res = 1, 0
high, cur, low = n // 10, n % 10, 0
while high != 0 or cur != 0:
if cur == 0: res += high * digit
elif cur == 1: res += high * digit + low + 1
else: res += (high + 1) * digit
low += cur * digit
cur = high % 10
high //= 10
digit *= 10
return res
**自我反思:**其实这种类型的题没有什么技巧可言,就多积累多思考吧。
剑指44.数字序列中某一位的数字
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
示例 1:
输入:n = 3
输出:3
1.先根据n求出这是几位数,然后从同位数除法,根据取整取余,判断这是第几个数字的第几位。
class Solution:
def findNthDigit(self, n: int) -> int:
nth = 10
n_ = 1
temp = 0
n = n
while True:
if nth>n:
break
n_ += 1
temp = nth
nth += n_*(10**n_ - 10**(n_-1))
a = (n - temp)//n_
b = (n - temp)%n_
start = 10**(n_-1)
if start == 1:
start = 0
a = start + a
a = str(a)
return int(a[b])
2.K神的思路与我的基本一致,不过他的代码真的学到了,再手打一遍。
class Solution:
def findNthDigit(self, n: int) -> int:
digit, start, count = 1, 1, 9
while n > count:
n -= count
digit += 1
start = 10**(digit-1)
count = 9 * start * digit
num = start + (n-1)//digit
index = (n-1)%digit
return int(str(num)[index])
**自我反思:**为什么我的代码总是那么冗长。
剑指45.把数组排成最小的数
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出: "102"
1.基本思路大家都知道,最高位越小越放前面,但这里有个问题50、51和504,它们的排序方式应该是504、50、51,这并不是一个简单的比大小,如果强行按位算,会非常麻烦。这里用快速排序的同时,引入了字符串比较规则,if str(num) + str(mid) >= str(mid) + str(num),则num是“更小的数”也就是要放在前面的数。
class Solution:
def minNumber(self, nums: List[int]) -> str:
stic = {str(x):[] for x in range(10)}
result = ""
for num in nums:
stic[str(num)[0]].append(num)
for k in stic:
result += self.qksort(stic[k])
return result
def qksort(self,data):
if len(data) >= 2:
mid = data[len(data)//2]
left, right = [], []
data.remove(mid)
for num in data:
if str(num) + str(mid) >= str(mid) + str(num):
right.append(num)
else:
left.append(num)
return self.qksort(left) + str(mid) + self.qksort(right)
elif len(data) == 1:
return str(data[0])
else:
return ""
2.菜鸟的代码如上,大神的代码如下
class Solution:
def minNumber(self, nums: List[int]) -> str:
strs = [str(num) for num in nums]
def quick_sort(l,r):
if l >= r:
return
i, j = l, r
while i < j:
while i < j and strs[j]+strs[l]>=strs[l]+strs[j]: j-=1
while i < j and strs[i]+strs[l]<=strs[l]+strs[i]: i+=1
strs[i], strs[j] = strs[j], strs[i]
strs[l], strs[i] = strs[i], strs[l]
quick_sort(l,i-1)
quick_sort(i+1,r)
quick_sort(0,len(strs)-1)
return ''.join(strs)
**自我反思:**快排的代码一定要非常非常熟练
剑指46.把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
1.动态规划。当时在这道题上卡了好久,甚至想用递归。
class Solution:
def translateNum(self, num: int) -> int:
num = str(num)
a, b = 1, 1
for i in range(len(num)-1):
a, b = (a+b if '10'<=num[i:i+2]<='25' else a), a
return a
**自我反思:**万物皆可动态规划