剑指offer 第12-22题
AcWing 24. 机器人的运动范围
地上有一个 m 行和 n 列的方格,横纵坐标范围分别是 0∼m−1 和 0∼n−1。
一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格。
但是不能进入行坐标和列坐标的数位之和大于 k 的格子。
请问该机器人能够达到多少个格子?
样例1
输入:k=7, m=4, n=5
输出:20
样例2
输入:k=18, m=40, n=40
输出:1484
解释:当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。
但是,它不能进入方格(35,38),因为3+5+3+8 = 19。
注意:
0<=m<=50
0<=n<=50
0<=k<=100
时间复杂度:\(o(nm)\)
宽搜与深搜(容易爆栈)
class Solution(object):
def bfs(self, x, y, k):
def f(x, y):
res = 0
for i in str(x):
res += int(i)
for j in str(y):
res += int(j)
return res
dx = [0, 0, 1, -1]
dy = [1, -1, 0, 0]
hh = 0
tt = -1
q = [[] for i in range(self.cols * self.rows + 100)]
tt += 1
q[tt] = [0, 0]
res = 0
while hh <= tt:
t = q[hh]
hh += 1
if self.st[t[0]][t[1]] == 1:
continue
self.st[t[0]][t[1]] = 1
res += 1
for i in range(4):
x1 = t[0] + dx[i]
y1 = t[1] + dy[i]
if x1 < self.rows and x1 >= 0 and y1 < self.cols and y1 >=0 and f(x1, y1) <= k and self.st[x1][y1] == 0:
tt += 1
q[tt] = [x1, y1]
return res
def movingCount(self, threshold, rows, cols):
"""
:type threshold: int
:type rows: int
:type cols: int
:rtype: int
"""
self.w = 0
self.st = [[0 for j in range(cols)] for i in range(rows)]
if rows == 0 or cols ==0 :
return 0
self.rows = rows
self.cols = cols
return self.bfs(0, 0, threshold)
深搜
class Solution(object):
def dfs(self, x, y, k):
self.w += 1
dx = [0, 0, 1, -1]
dy = [1, -1, 0, 0]
res = 1
for t in range(4):
x1 = dx[t] + x
y1 = dy[t] + y
#
#if self.w == 100:
# return 0;
def f(x, y):
res = 0
for i in str(x):
res += int(i)
for j in str(y):
res += int(j)
return res
if x1 < self.rows and x1 >= 0 and y1 < self.cols and y1 >=0 and f(x1, y1) <= k and self.st[x1][y1] == 0:
# print(x1, y1)
self.st[x1][y1] = 1
res += self.dfs(x1, y1, k)
# self.st[x1][y1] = 0
return res
def movingCount(self, threshold, rows, cols):
"""
:type threshold: int
:type rows: int
:type cols: int
:rtype: int
"""
self.w = 0
self.st = [[0 for j in range(cols)] for i in range(rows)]
if rows == 0 or cols ==0 :
return 0
self.rows = rows
self.cols = cols
self.st[0][0] = 1
return self.dfs(0, 0, threshold)
AcWing 25. 剪绳子
给你一根长度为 n 绳子,请把绳子剪成 m 段(m、n 都是整数,2≤n≤58 并且 m≥2)。
每段的绳子的长度记为k[0]、k[1]、……、k[m]。k[0]k[1] … k[m] 可能的最大乘积是多少?
例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。
样例
输入:8
输出:18
算法
(数学) O(n)
这道题目是数学中一个很经典的问题。
下面我们给出证明:
首先把一个正整数 N拆分成若干正整数只有有限种拆法,所以存在最大乘积。
假设 N=n1+n2+…+nk
,并且 n1×n2×…×nk
是最大乘积。
- 显然1不会出现在其中;
- 如果对于某个
i
有ni≥5
,那么把 ni 拆分成3 + (ni−3)
,我们有3(ni−3) = 3ni−9 > ni
;推出不能有大于5
的数。 - 如果
ni=4
,拆成2+2
乘积不变,所以不妨假设没有4
; - 如果有三个以上的2,那么
3×3>2×2×2
,所以替换成3乘积更大;
综上,选用尽量多的3,直到剩下2或者4时,用2。
时间复杂度分析:当 n比较大时,n 会被拆分成 ⌈n/3⌉
个数,我们需要计算这么多次减法和乘法,所以时间复杂度是 O(n)
。
class Solution(object):
def maxProductAfterCutting(self,length):
"""
:type length: int
:rtype: int
"""
res = 1
if length % 3 == 0:
while length > 0:
length -= 3
res *= 3
elif length % 3 == 1:
length -= 4
while length > 0:
length -= 3
res *= 3
res *= 4
elif length % 3 == 2:
length -= 2
if length == 0:
return 1
while length > 0:
length -= 3
res *= 3
res *= 2
return res
AcWing 26. 二进制中1的个数
正数补码是本身,
负数补码 :符号位不变,逐位求反(从头开始), 加1。
思路1:
lowbit : n&(n-1)的结果:n最右边的1变成0,比如n为6
110&101-》100
循环直到n为0为止
上述思想C++可以直接过。
为什么python中负数需要和 0xFFFFFFFF 做与操作?
在计算机中,所有的数字都是使用补码存储起来的。由于Python没有位数这个概念,所以得到二进制表示需要多一点操作,即将位数限制在32位,通过和一个32位的全1数字按位与运算即可。但对于负数来说,直接bin(-1)是不能得到其补码的,而是得到了1的原码前面加上了负号,即-0b1。则通过和一个32位的全1数字按位与运算可得到其补码二进制表示对应的十进制数(按位与运算把符号位的1视为了数字)。Python要使用 n & 0xffffffff 得到一个数的补码
class Solution(object):
def NumberOf1(self,n):
"""
:type n: int
:rtype: int
"""
res = 0
if n < 0:
n = n & 0xFFFFFFFF
while n != 0 :
n -= n & (-n)
res += 1
return res
思路2
(位运算) O(logn)
迭代进行如下两步,直到 n 变成0为止:
如果 n在二进制表示下末尾是1,则在答案中加1;
将 n右移一位,也就是将 nn 在二进制表示下的最后一位删掉;
这里有个难点是如何处理负数。
在C++中如果我们右移一个负整数,系统会自动在最高位补1,这样会导致 n 永远不为0,就死循环了。
解决办法是把 n 强制转化成无符号整型,这样 n 的二进制表示不会发生改变,但在右移时系统会自动在最高位补0。
时间复杂度
每次会将 n除以2,最多会除 logn 次,所以时间复杂度是 O(logn)。
class Solution {
public:
int NumberOf1(int n) {
int res = 0;
unsigned int un = n;
while (un) res += un & 1, un >>= 1;
return res;
}
};
AcWing 27. 数值的整数次方
实现函数double Power(double base, int exponent),求base的 exponent次方。
不得使用库函数,同时不需要考虑大数问题。
注意:
不会出现底数和指数同为0的情况
当底数为0时,指数一定为正
样例1 输入:10 ,2
输出:100样例2
输入:10 ,-2 >
输出:0.01
注意一下负数即可
https://pxlsdz.blog.csdn.net/article/details/109124180
class Solution(object):
def Power(self, base, exponent):
"""
:type base: float
:type exponent: int
:rtype: float
"""
b = exponent
exponent = abs(exponent)
res = 1
while exponent > 0:
if exponent & 1 == 1:
res *= base
base = base * base
exponent >>= 1
if b < 0:
res = 1 / res
return res
AcWing 28. 在O(1)时间删除链表结点
算法
(链表) O(1)
给定单向链表的一个节点指针,定义一个函数在O(1)时间删除该结点。
假设链表一定存在,并且该节点一定不是尾节点。
样例 输入:链表 1->4->6->8
删掉节点:第2个节点即6(头节点为第0个节点)
输出:新链表 1->4->8
由于是单链表,我们不能找到前驱节点,所以我们不能按常规方法将该节点删除。
我们可以换一种思路,将下一个节点的值复制到当前节点,然后将下一个节点删除即可。
时间复杂度
只有常数次操作,所以时间复杂度是 O(1)。
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def deleteNode(self, node):
"""
:type node: ListNode
:rtype: void
"""
node.val = node.next.val
node.next = node.next.next
AcWing 29. 删除链表中重复的节点
算法
(线性扫描) O(n)
为了方便处理边界情况,我们定义一个虚拟元素 dummy 指向链表头节点。
然后从前往后扫描整个链表,每次扫描元素相同的一段,如果这段中的元素个数多于1个,则将整段元素直接删除。
时间复杂度
整个链表只扫描一遍,所以时间复杂度是 O(n)。
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def deleteDuplication(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
dumpy = ListNode(None)
dumpy.next = head
p = dumpy
while p != None:
q = p.next
while p.next != None and q.next != None and p.next.val == q.next.val:
q = q.next
if p.next != q:
p.next = q.next
else: p = p.next
return dumpy.next
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* head) {
auto dummy = new ListNode(-1);
dummy->next = head;
auto p = dummy;
while (p->next) {
auto q = p->next;
while (q && p->next->val == q->val) q = q->next;
if (p->next->next == q) p = p->next;
else p->next = q;
}
return dummy->next;
}
};
AcWing 30. 正则表达式匹配
题目描述
请实现一个函数用来匹配包括’.’和’*’的正则表达式。
模式中的字符’.’表示任意一个字符,而’*’表示它前面的字符可以出现任意次(含0次)。
在本题中,匹配是指字符串的所有字符匹配整个模式。
例如,字符串”aaa”与模式”a.a”和”abaca”匹配,但是与”aa.a”和”ab*a”均不匹配。
样例 输入:
s="aa" p="a*"
输出:true
算法
(动态规划) O(nm)
状态表示:f[i][j]为所有s[1...i]与p[1....j]匹配的方案的集合。
属性:集合是否为空
状态转移:
- 如果p[j]不是通配符'*'\[f[i][j] = (s[i] == p[j]\ or\ p[j] == '.')\ \&\&\ f[i-1][j-1]== 1 \]
- 如果p[j+1]是通配符'
*'
,以*
表示字母个数来划分集合。
- 当
*
表示的字母个数为0个时:
- 当
*
表示的字母个数为1个时:\[f[i][j] = f[i-1][j-2] \&\& s[i] ==p[j-1] \] - 当
*
表示的字母个数为2个时:
$$f[i][j] = f[i-2][j-2] \&\& s[i] ==p[j-1\&\&s[i-1]==p[j-1]$$
......
类似完全背包的优化方式
后半部都多了\(s[i-1] == p[j-1]\),结合律(A&b|A&c=A&(b|c))即可推出$$f[i][j] =( f[i][j-2])\ ||\ (f[i-1][j] & s[i]== p[j-1])$$
时间复杂度分析:
n表示s的长度,m 表示p的长度,总共 nm个状态,状态转移复杂度 O(1),所以总时间复杂度是 O(nm).
class Solution(object):
def isMatch(self, s, p):
"""
:type s: str
:type p: str
:rtype: bool
"""
n = len(s)
m = len(p)
s = " " + s
p = " " + p
dp = [[False for j in range(m + 1)] for i in range(n + 1)]
dp[0][0] = True
for i in range(0, n + 1):
for j in range(1, m + 1):
if j + 1 < m and p[j+1] == "*":
continue
if i and p[j] != "*":
dp[i][j] = dp[i - 1][j - 1] and (s[i] == p[j] or p[j] == '.')
elif p[j] == '*':
dp[i][j] = dp[i][j - 2] or (i and dp[i - 1][j] and (s[i] == p[j - 1] or p[j - 1] == '.'))
return dp[n][m]
class Solution(object):
def isMatch(self, s, p):
"""
:type s: str
:type p: str
:rtype: bool
"""
n = len(s)
m = len(p)
f = [[False] * (m+1) for i in range(n+1)]
s = ' ' + s
p = ' ' + p
f[0][0] = True
for i in range(n+1):
for j in range(1, m+1):
if j + 1 < m and p[j+1] == '*':
continue
if i and p[j] != '*':
if s[i] == p[j] or p[j] == '.':
f[i][j] = f[i-1][j-1]
elif p[j] == '*':
f[i][j] = f[i][j-2] or (f[i-1][j] and (s[i] == p[j-1] or p[j-1] == '.'))
return f[n][m]
AcWing 31. 表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。
但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
注意:
- 小数可以没有整数部分,例如.123等于0.123;
- 小数点后面可以没有数字,例如233.等于233.0;
- 小数点前面和后面可以有数字,例如233.666;
- 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
- 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4;
样例: 输入: "0"
输出: true
class Solution(object):
def isNumber(self, s):
"""
:type s: str
:rtype: bool
"""
try :
float(s)
return True
except:
return False
AcWing 32. 调整数组顺序使奇数位于偶数前面
算法
(双指针扫描) O(n)
用两个指针分别从首尾开始,往中间扫描。扫描时保证第一个指针前面的数都是奇数,第二个指针后面的数都是偶数。
每次迭代时需要进行的操作:
- 第一个指针一直往后走,直到遇到第一个偶数为止;
- 第二个指针一直往前走,直到遇到第一个奇数为止;
- 交换两个指针指向的位置上的数,再进入下一层迭代,直到两个指针相遇为止;
时间复杂度
当两个指针相遇时,走过的总路程长度是 n,所以时间复杂度是 O(n)。
class Solution(object):
def reOrderArray(self, array):
"""
:type array: List[int]
:rtype: void
"""
i = 0
j = len(array) - 1
while i < j:
while i< j and array[i] & 1 == 1:
i += 1
while j > i and array[j] & 1 != 1:
j -= 1
if i <j : array[i], array[j] = array[j], array[i]
AcWing 33. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个结点。
注意:
- k >= 0; 如果k大于链表长度,则返回 NULL;
样例
输入:链表:1->2->3->4->5 ,k=2
输出:4
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def findKthToTail(self, pListHead, k):
"""
:type pListHead: ListNode
:type k: int
:rtype: ListNode
"""
p = pListHead
n = 1
while p != None:
n += 1
p = p.next
if k > n:
return None
p = pListHead
i = 1
while p != None:
if i == n-k:
return p
p = p.next
i += 1
AcWing 34. 链表中环的入口结点
给定一个链表,若其中包含环,则输出环的入口节点。
若其中不包含环,则输出null。
样例
给定如上所示的链表:
[1, 2, 3, 4, 5, 6]
2
注意,这里的2表示编号是2的节点,节点编号从0开始。所以编号是2的节点就是val等于3的节点。则输出环的入口节点3.
算法
(链表,快慢指针扫描) O(n)
用两个指针 first,second 分别从起点开始走,first 每次走一步,second 每次走两步。
如果过程中 second走到null,则说明不存在环。否则当 first 和 second相遇后,让 first返回起点,second 待在原地不动,然后两个指针每次分别走一步,当相遇时,相遇点就是环的入口。
证明:如上图所示,a 是起点,b 是环的入口,c 是两个指针的第一次相遇点,ab 之间的距离是 x,bc 之间的距离是 y, z表示从 cc点顺时针走到 b 的距离。
则第一次相遇时 second所走的距离是 \(x+(y+z)∗n+y\), n表示圈数,同时 second 走过的距离是 first 的两倍,也就是 \(2(x+y)\),所以我们有\(x+(y+z)∗n+y=2(x+y)\),所以 $$x=(n−1)*(y+z)+z$$
y+z是一圈的距离,z = x %(y+z),那么我们让 second从 c 点开始走,走 x步,会恰好走到 b 点;让 first 从 a 点开始走,走 x 步,也会走到 b 点。
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def entryNodeOfLoop(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
first = head
second = head
while first and second:
if first == None or second == None:
return None
first = first.next
second = second.next
if second :
second = second.next
if first == second:
first = head
while first and second:
if first == second:
return first
first = first.next
second = second.next
return None
map o(nlogn) 空间o(n)
class Solution {
public:
ListNode *entryNodeOfLoop(ListNode *head) {
map<ListNode*,int> mp;
while(head)
{
if(mp[head])
return head;
mp[head]=1;
head=head->next;
}
return NULL;
}
};