按照二分查找的思想进行,但是边界指针移动略有不同,保证旋转点在l与r之间
递推式dp[i] = max(dp[j]*dp[i-j])
n>=5时,剪成长度为3的小段是最优的,所以尽可能切分3长度即可,n=4时,切成2即可。
该题想考察的点是用字符串(数组)模拟数字自加,进位的情况,同时也需要我们改写输出函数完成要求的输出格式(过滤数字左边多余的0)
1.首先用n+1位数组存储数字,初始化全为0,当自加到最高位为1时停止循环。
2.循环内部不断自加1且判断进位操作
3.每次自加完后调用输出函数,从左往右第一个不为0的元素开始整合成一个整数输出。
(python没有单字符变量,都是用字符串(21byte),比整数型12byte还要大,所以不如用int数组)
1.如果要删除的节点传入的是一个指针,可以直接让p.val = p.next.val ,p.next = p.next.next覆盖掉要删除的节点(尾节点属于特殊情况)
2.如果是个节点的val值,就老老实实遍历到p.next.val = val,然后p.next = p.next.next,可以加一个哨兵节点在链表开头,就不用处理边界情况。
另外注意扩展题删除链表中重复节点
LeetCode82. 删除排序链表中的重复元素 II.
运用dp思想。
1.a[i] == b[j] 或者b[j]==’.’, dp[i][j]= dp[i-1][j-1]
2.b[j] == ‘*’,先考虑忽略b[j-1],使dp[i][j] = dp[i][j] or dp[i][j-2]
3.如果b[j-1] = a[i]或者b[j-1]=’.’,则延长这个字符,使dp[i][j] = dp[i][j] or dp[i-1][j]
class Solution:
def isMatch(self, s: str, p: str) -> bool:
m = len(s)
n = len(p)
dp = [[False] * (n+1) for _ in range(m+1)]
dp[0][0] = True
for i in range(m+1):
for j in range(1,n+1):
if p[j-1] != '*':
if i > 0 and (s[i-1] == p[j-1] or p[j-1] == '.'):
dp[i][j] = dp[i-1][j-1]
else:
if j >= 2:
#可以选择不匹配,忽略*和之前一个字符
dp[i][j]= dp[i][j] or dp[i][j-2]
if i > 0 and (s[i-1] == p[j-2] or p[j-2] == '.'):
#继续用p的上一个字符匹配
dp[i][j]= dp[i][j] or dp[i-1][j]
return dp[m][n]
1.可以按照剑指offer上面拆分
2.可以设计一个自动机
3.推荐写法,设立4个标志变量,用来记录 e 和小数点以及数字还有e 之后的数字有没有出现过。
然后遍历整个整个字符串
class Solution:
def isNumber(self, s: str) -> bool:
s = s.strip()
n = len(s)
# 用四个标记记录 e 和小数点以及数字和 e 之后的数字有没有出现过
e_show_up, dot_show_up, num_show_up, num_after_e = False, False, False, False
for i in range(n):
c = s[i]
# 如果是数字,则将数字和 e 后出现的数字都标记为 true
# 没有 e 的浮点数也认为 e 之后出现过数字
if c.isdigit():
if e_show_up:
num_after_e = True
else:
num_show_up = True
# 如果是正负号, 只有出现在首位或是 e 后面才合法
elif c in ('+', '-'):
if i > 0 and s[i-1] != 'e':
return False
# 如果是小数点,那么必须保证 e 和小数点都没有出现过
elif c == '.':
if dot_show_up or e_show_up:
return False
dot_show_up = True
# 如果是 e, 要保证已经有数字出现,并且 e 没有出现过
elif c == 'e':
if e_show_up or not num_show_up:
return False
e_show_up = True
# 其他情况都为非法
else:
return False
return num_show_up if not e_show_up else num_show_up and num_after_e
双指针,l=0,r=len-1
当l < r时判断l所在元素满不满足条件,如果不满足,l与r位置的元素互换,然后r-=1
如果满足l+=1,即可完成交换。
常规的双针法,但是要注意鲁棒性,考虑空指针以及链表总长都没有k的情况
剑指offer上把问题分为了3步
该题迭代或者递归都是一个很清晰的解决方法
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
p1 = None
p2 = head
while p2:
p2.next,p1,p2 = p1,p2,p2.next
return p1
或者
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if(head==None or head.next==None):
return head
#head后面的链表都以及反转好了
new_head = self.reverseList(head.next)
#把head加上去
head.next.next = head
#尾节点处理好
head.next = None
return new_head
操作有点像归并排序的merge部分
如果两个链表当前节点都不为空,取值小的为新链表的下一个节点,该链表往前走一步;
如果有一个链表空了,另一个链表直接加到新链表的尾部。
首先遍历A树,找到和B树根节点相同的节点,这个便利顺序或者递归迭代实现都随意。
找到之后调用判断函数,每个节点的遍历判断两个子树是否相等,注意A,B中分别出现空指针的返回不相等。
分析可得,递归把每个子树的左右儿子互换即可达到要求
检查树以一种方式遍历以及对应反序遍历的结果是否相等。(t1左和t2右对比,t1右和t2左对比)
用4个变量记录已经还没打印部分的上下左右四个边界,一个循环打印一圈,并每部分输出时判断是否越界了。
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix: return []
l, r, t, b, res = 0, len(matrix[0]) - 1, 0, len(matrix) - 1, []
while True:
for i in range(l, r + 1): res.append(matrix[t][i]) # left to right
t += 1
if t > b: break
for i in range(t, b + 1): res.append(matrix[i][r]) # top to bottom
r -= 1
if l > r: break
for i in range(r, l - 1, -1): res.append(matrix[b][i]) # right to left
b -= 1
if t > b: break
for i in range(b, t - 1, -1): res.append(matrix[i][l]) # bottom to top
l += 1
if l > r: break
return res
来自LeetCode题解: Krahets.
在正常的栈操作之外,添加一个辅助栈,大小和数据栈一致,但是栈里面的值是当前位置之前的最小值,每次push元素时,辅助栈压入栈顶和当前元素中的较小值。
两个指针记录当前比较的压栈元素和出栈元素,如果辅助栈栈顶元素不是出栈元素,就压入入栈序列的下一个元素,知道入栈序列遍历完之后,看辅助栈的倒序是不是剩下来的出栈序列就可完成判断。
借用FIFO队列完成层次遍历
设立两层循环,每个小循环里面完成一层打印操作,控制循环变量在刚进入循环部分时len(q)的值内技能防止越界,只输入这一层的节点。
同上,但是输出一层节点时判断是偶数层还是奇数层做不同的处理。
以序列最后一个元素为根,小于它的为左子树节点,大于它的为右子树节点,判断序列中大于它的原始是否都在小于它的元素后面,如果时,递归检查左右子树是否满足条件
单调栈思想,对于后序遍历的逆序列而言,遍历时一旦出现了下降的元素,意味着当前根节点的右子树遍历完了,开始处理左子树了。就不能大于当前的根,否则就是不满足的条件的序列。初始根设置为无穷大,相当于把原来的树当作无穷大节点的左子树。
因为路径从根节点开始,很容易利用回溯法,不断修改当前路径和以及路径元素,当路径和满足且为叶节点时把路径加入到输出结果即可,注意利用回溯法在某个点的路径搜索完成时记得pop掉它本身。
1.首先遍历整个链表,把每个链表节点复制一份插入到自身后面,形成A->A‘->B->B’形式。
2.再次遍历链表,把A’->random 设置为A->random->next
3.最后遍历,分割链表
注意二叉搜索树的性质,对其中序遍历的结果就是就是从小到大的排序,记录遍历的上一个节点,然后更改pre.right和cur.left即可
按照遍历规则,把问题拆分为分治的子问题即可。
把字符串的排列分解为固定的第一个字符和后面的待排序字符串,交换头部和尾部的字符就把n长度字符串的排列变成了n-1个的n-1长度字符串排列问题。(还可以用一个集合看某个字符重复,已经当过前面的首字符了,进行剪枝操作)
1.借助快速排序的划分函数,划分到N/2位置
2.利用数组特性,保留当前数字及其次数,遇到一样的数字则数字加1,不一样的数字减一,减到0是更换当前保留的数字
1.借助快排的划分函数,划分到K-1位置,然后输出即可
2.如果海量数据,利用堆或者红黑树,只保留K个数在内存
利用堆数据结构,一个大项堆保存左半数据,一个小项堆保存右半数据,根据数据个数选择大项堆顶或者大小堆顶平均数作为返回值。
1.数组规律,记录遍历过程的数组和,一旦数组和<0那么从当前元素开始重新求和。
2.dp思想,dp[i] = max(nums[i],dp[i-1]+nums[i])
寻找数值规律,每一位出现1的次数有如下规律
1.判断那一位属于几位数的范围
2.判断那一位具体属于哪一个的几位数
3.输入目标几位数的对应位置
定义一种比较规则,mn > nm则n 利用dp思想,利用dp[i]储存从左到i位该数字可以翻译的不同数目,则转移方程可以写成 利用dp思想,每个位置的最大价值只和左边以及上面一格的最大价值有关 可以进步把dp数组优化为与grid行等长的一维数组 采用动态规划或者滑动窗口思想,记录当前字符在以前出现没有以及出现的位置(在不在当前考虑的窗口内),分别做判断。 空间换时间,记录以前生成过的所有丑数,来加快下一步生成。 利用hash表(python里字典即可)记录出现次数,因为python3.6以上字典是顺序的,遍历顺序和插入顺序一致。直接访问到的一个只出现一次的就是题目要求的输出。 1.借助归并排序的思想,所以首先写出归并排序代码。 1.公共节点之后两个链表是一致的,可以设置两个栈保存遍历2个链表的结果,然后找到最后一个相等的栈顶元素。 略微修改二分法,定位数字的左边界和右边界。 二分查找,判断条件变为i == nums[i] 二叉树的中序遍历就是排好序的结果,所以在中序遍历的基础上对k进行计数判断,k减小到0是就是找到条件节点了,可以返回。 depth(root) = max(depth(root.left),depth(root.right)) + 1 该题如果像上面一小问处理的话,会重复处理一个节点好多次。 类比只有一个数字出现一次的那道题,该题有两个数字那么他们的异或结果肯定有一位为1既两个数该位不同,那么根据这一点把数组分为a,b两部分,每部分就只包含一个只出现一次的数,就可以按之前的方法解答了。 用32位数组记录数组中数字的二进制形势下,各位出现1的情况,如果总的出现次数能整除3的肯定不是只出现一次的数该有的位,把不能整除3的位取出来还原成10进制即可。 双指针从左右向中间扫描,如果两数之和大于目标,r-=1,反之l+=1 使用滑动窗口思想,判断滑动窗口内之和是否等于target,滑动时对记录的原窗口之和加减操作即可变成新值,不必每次sum求和。 1.python库函数,split和strip切割以及过滤 1.python简单切片 滑动窗口思想,用一个降序的双端队列保存遍历中的结果,每次遍历到数组中元素大于队列头或者当前队列头是窗口左边界外的元素,则把它挪出队列,压入新的元素;遍历到每个位置上时队列头的元素就是窗口内最大值。 同时维护一个普通队列和上一小问一样的双端队列即可 利用dp思想,记录n-1个骰子的取值概率,然后推演出n个骰子的(n-1的每个概率值均摊到+1~+6的下一个取值中) 数组中不出现对子且最大最小牌之间的差距小于5即可 设f(n,m)为n个数按照m间隔删除数字后留下的那个,那么来寻找f(n-1,m)和f(n,m)的递推关系。 设置变量保存之前的最大利润和出现的最低价格,每次遍历到一个元素都计算出当前可获得的利润,和保存的最大利润最比较。 首先发现递归或者迭代方法的话都要考虑一下终止条件问题。 如果n是1就不会调用下一个n-1了,就终止了迭代。 不能用正常3四则运算时,我们就要想到位运算这个一神器。 从头到尾遍历一次得出元素左边数组之积, 常规考题,但是注意考虑边界情况。 如果树是二叉搜索树的话,根据其特点:根节点大于左子树而小于右子树。 1.使用两个数组保存根节点到两个节点的路径,然后倒着求最后一个公共点即可。面试题46把数字翻译成字符串
dp[i] = dp[i-2] + dp[i-1] if nums[i-1]*10 + nums[i] 属于10到25之间,否则
dp[i] = dp[i-1]面试题47礼物的最大价值
dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + grid[i][j]
面试题48最长不含重复字符的子字符串
面试题49丑数
发现新的丑数一定是由旧的丑数乘上(2,3,5)中一个得到的,所以设立3个指针,指向有可能生成新丑数的3个位数,取其生产的3个丑数中最小的,并对应位置记录右移一个。面试题50第一个只出现一次的字符
涉及到多个字符需要多次判断出现与否或者次数的都可以用hash表。面试题51数组中的逆序对
2.分析在归并排序合并两个子部分的时候,每当选中左边部分元素加入排序后数组时,相对于目前正在合并的数组而言,右部分当前元素到左边界的距离就是选中的左边部分元素构成的逆序对个数。所以在归并排序代码加上计数代码即可。class Solution:
def reversePairs(self, nums: List[int]) -> int:
def mergeSort(l,r):
if l >= r:
return 0
mid = (l + r) // 2
i, j, pos = l, mid + 1, l
#子问题求解
inv_count = mergeSort(l,mid) + mergeSort(mid+1,r)
while i <= mid and j <=r:
if nums[i] <= nums[j]:
tmp[pos] = nums[i]
inv_count += (j-mid-1)
i += 1
else:
tmp[pos] = nums[j]
j += 1
pos += 1
#一者遍历完毕
for k in range(i, mid + 1):
tmp[pos] = nums[k]
inv_count += (j-mid-1)
pos += 1
for k in range(j, r + 1):
tmp[pos] = nums[k]
pos += 1
#赋值
nums[l:r+1] = tmp[l:r+1]
return inv_count
n = len(nums)
tmp = [0] * n
return mergeSort(0, n - 1)
面试题52两个链表的第一个公共节点
2.考虑一般情况两个链表A,B长度不相等,但是A+B和B+A是相等的,所以遍历两个链表,每一步都判断节点是否相等,某一指针到了结尾空节点就转移到另一个链表的头,这样两个指针一定在公共节点相遇(或者都是空节点)。面试题53在排序数组中查找数字
53-1
53-2
面试题54二叉搜索树的第k大节点
面试题55二叉树的深度
55-1
55-2
所以应该从底向上处理,如果某个节点不平衡,直接返回-1给父节点,如果平衡就返回自己的深度以供父节点判断自己平不平衡。父节点收到一个-1也就可以立马返回不平衡-1.面试题56数组中数字出现的次数
56-1
56-2
面试题57和为s的两个数字
57-1
57-2
面试题58反转字符串
58-1
2.双指针定位每个单词左右边界实施反转。58-2
2.通过3次反转实现,a,b反转,再反转a+b面试题59队列的最大值
59-1
59-2
面试题60n个骰子的点数
面试题61扑克牌中的顺子
面试题62圆圈中最后剩下的数字
分析可得f(n,m) = (f(n-1,m)+m)%n按照递推关系式写出递归或者迭代程序即可得出结果。面试题63股票的最大利润
面试题64 1+2+。。。。。。+n
所以可以利用逻辑操作里面的短路操作n > 1 and self.sumNums(n - 1)
面试题65不用加减乘除做加法
二进制的不进位加法可以用a^b来模拟,而加法的进位可用(a&b)<<1模拟,那么不断地把不进位加法的和以及进位结果相加直到进位为0时,就完成了加法操作。#需要注意的是python中整数没有固定储存长度
#需要&0xffffffff固定到32位进行操作
#最后结果的首位如果是1则说明本来应该负数,我们的截断到32位操作需要还原这个负数在python中的表达方式~(a ^ x)是将 32 位以上的位取反,即由0 变为 1 , 1 至 32 位不变。
面试题66构建乘积数组
从尾到头遍历一次得出元素右边数组之积,
两者对应相乘就是结果。面试题67把字符串转换为整数
1.非法输入
2.越界面试题68树中两个节点的最低公共祖先
68-1
从树的根节点开始往下搜索,如果两个节点都大于这个值,往右子树递归,小于就往左子树递归,如果一大一小,那么根节点就恰好是要找的节点。68-2
2.使用后序遍历,把子树中的搜索结果返回,避免对子节点重复检索。