来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-common-prefix
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
代码一:
def twoSum(self, nums: List[int], target: int) -> List[int]:
lens = len(nums)
j=-1
for i in range(1,lens):
temp = nums[:i]
if (target - nums[i]) in temp:
j = temp.index(target - nums[i])
break
if j>=0:
return [j,i]
代码二:字典代替哈希表,存放数字和下标,更快
def twoSum(self, nums: List[int], target: int) -> List[int]:
hashmap = {}
for i,num in enumerate(nums):
hashmap[num] = i
for i ,num in enumerate(nums):
j = hashmap.get(target - num)
if j is not None and j != i:
return [i,j]
补充:如果nums是已经排好序的,可以用两个指针一前一后
视为字符串或者取余数的方法
数学解法:
if (x < 0) or (x != 0 and x % 10 == 0):
return False
cmp_num = 0
while x > cmp_num:
cmp_num = cmp_num * 10 + x % 10
x //= 10
#print(x,cmp_num)
return x == cmp_num or x == cmp_num // 10
字符串简单方法:
def isPalindrome(self, x: int) -> bool:
return str(x) == str(x)[::-1]
def romanToInt(self, s: str) -> int:
d = {'I':1, 'IV':3, 'V':5, 'IX':8, 'X':10, 'XL':30, 'L':50, 'XC':80, 'C':100, 'CD':300, 'D':500, 'CM':800, 'M':1000}
return sum(d.get(s[max(i-1, 0):i+1], d[n]) for i, n in enumerate(s))
代码行数:解析
构建一个字典记录所有罗马数字子串,注意长度为2的子串记录的值是(实际值 - 子串内左边罗马数字代表的数值)
这样一来,遍历整个 ss 的时候判断当前位置和前一个位置的两个字符组成的字符串是否在字典内,如果在就记录值,不在就说明当前位置不存在小数字在前面的情况,直接记录当前位置字符对应值
举个例子,遍历经过 IVIV 的时候先记录 II 的对应值 11 再往前移动一步记录 IVIV 的值 33,加起来正好是 IVIV 的真实值 44。max 函数在这里是为了防止遍历第一个字符的时候出现 [-1:0][−1:0] 的情况
(哈希表存储的是由键(key)和值(value)组 成的数据。例如,我们将每个人的性别作为数 据进行存储,键为人名,值为对应的性别。
Python 中我们使用字典 {key : value} 来初始化哈希表
通过 key 查找 value 的时间复杂度为 O(1)O(1)
这题题解中的 d 就是一个字典,其中 get(key, default) 函数可以通过 key 从 d 中找出对应的值,如果 key 不存在则返回默认值 default)
dic = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000}
dicx = {"IV":-2,'IX':-2,'XL':-20,'XC':-20,'CD':-200,'CM':-200}
result=0
for i in s:
result+=dic[i]
for j in dicx:
if j in s:
result+=dicx[j]
return result
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"
示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
解释::两个字符串的话可以考虑动态规划,多个字符串的话可以用Python的内置函数zip
代码:
def longestCommonPrefix(self, strs: List[str]) -> str:
s = ''
for i in zip(*strs):
if len(set(i)) == 1:
s += i[0]
else:
break
return s
zip函数补充:zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。比如
解题思路:
栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号入栈,遇到右括号时将对应栈顶左括号出栈,则遍历完所有括号后 stack 仍然为空;
建立哈希表 dic 构建左右括号对应关系:keykey 左括号,valuevalue 右括号;这样查询 22 个括号是否对应只需 O(1)O(1) 时间复杂度;建立栈 stack,遍历字符串 s 并按照算法流程一一判断。
算法流程
如果 c 是左括号,则入栈 pushpush;
否则通过哈希表判断括号对应关系,若 stack 栈顶出栈括号 stack.pop() 与当前遍历括号 c 不对应,则提前返回 falsefalse。
提前返回 falsefalse
提前返回优点: 在迭代过程中,提前发现不符合的括号并且返回,提升算法效率。
解决边界问题:
栈 stack 为空: 此时 stack.pop() 操作会报错;因此,我们采用一个取巧方法,给 stack 赋初值 ?,并在哈希表 dic 中建立 key: '?',value:'?'key: ′? ′,value: ′? ′ 的对应关系予以配合。此时当 stack 为空且 c 为右括号时,可以正常提前返回 falsefalse;
字符串 s 以左括号结尾: 此情况下可以正常遍历完整个 s,但 stack 中遗留未出栈的左括号;因此,最后需返回 len(stack) == 1,以判断是否是有效的括号组合。
复杂度分析
时间复杂度 O(N)O(N):正确的括号组合需要遍历 11 遍 s;
空间复杂度 O(N)O(N):哈希表和栈使用线性的空间大小。
代码:
def isValid(self, s: str) -> bool:
dic = {'{': '}', '[': ']', '(': ')', '?': '?'}
stack = ['?']
for c in s:
if c in dic: stack.append(c)
elif dic[stack.pop()] != c: return False
return len(stack) == 1
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
非递归:
def mergeTwoLists(self, l1, l2):
"""
:type l1: ListNode
:type l2: ListNode
:rtype: ListNode
"""
head = dummy = ListNode(-1)
while l1 and l2:
if l1.val < l2.val:
head.next = l1
l1 = l1.next
else:
head.next = l2
l2 = l2.next
head = head.next
if l1:
head.next = l1
if l2:
head.next = l2
return dummy.next
递归:
def mergeTwoLists(self, l1, l2):
"""
:type l1: ListNode
:type l2: ListNode
:rtype: ListNode
"""
#递归的结束点就是L1或者L2为空就返回
#如果L1为空就返回L2,L2为空返回L1
if not (l1 and l2):
return l1 if l1 else l2
#L1的val<=L2的val,那么继续递归
#当前L1的next指向一个递归函数
#意思是L1的下一个和L2哪个大,就作为当前L1的next
if l1.val<=l2.val:
l1.next = self.mergeTwoLists(l1.next,l2)
return l1
else:
l2.next = self.mergeTwoLists(l1,l2.next)
return l2
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
解释:
题目要求两件事:
统计数组中不同数字数量 kk ;
修改数组前 kk 个元素为这些不同数字。
算法流程:
第一个指针 ii : 由于数组已经完成排序,因此遍历数组,每遇到 nums[i] \not= nums[i - 1]nums[i] =nums[i−1] ,就说明遇到了新的不同数字,记录之;
第二个指针 kk : 每遇到新的不同数字时,执行 k += 1k+=1 , kk 指针有两个作用:
记录数组中不同数字的数量;
作为修改数组元素的索引index。
最终,返回 kk 即可。
代码:
class Solution:
def removeDuplicates(self, nums: [int]) -> int:
if not nums: return 0
k = 1
for i in range(1, len(nums)):
if nums[i] != nums[i - 1]:
nums[k] = nums[i]
k += 1
return k
给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
解法:
算法流程:
定义指针i=0i=0
从第一个元素开始遍历,对于nums[j]nums[j]: 判断条件nums[j]!=valnums[j]!=val,如果满足,将这个值填入nums[i]nums[i],并令i=i+1i=i+1,指向下一位置
最后一次ii加了1,所以返回长度时无需加1.
复杂度分析
时间复杂度:O\left(n\right)O(n),只扫描了一遍
空间复杂度:O(1)O(1)
Python:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
n=len(nums)
i=0
for j in range(n):
if(nums[j]!=val):
nums[i]=nums[j]
i=i+1
return i
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = "hello", needle = "ll"
输出: 2
分析:1、暴力切片法:
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
for i in range(0,len(haystack) - len(needle) +1):
if haystack[i:i+len(needle)] == needle:
return i
return -1
2、KMP
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
题解
由于是排序不重复数组,本题实质上为二分查找
二分法
初始化左指针l=0l=0,右指针r=n-1r=n−1,其中nn为数组长度。res=-1res=−1,用来保存targettarget不在数组中的情况。
当l<=rl<=r时,执行循环:(注意!,因为初始化r=n-1r=n−1,所以搜索区间为闭合区间[l,r][l,r],因此结束条件为l>rl>r,所以是l<=rl<=r)
定义mid=(l+r)//2mid=(l+r)//2,当nums[mid]==targetnums[mid]==target时,返回midmid,。
当nums[mid]>targetnums[mid]>target时,说明targettarget在搜索区间[l,mid-1][l,mid−1]中,令r=mid-1r=mid−1。。
当nums[mid]
[2,3,5,6,7],target=4[2,3,5,6,7],target=4,此时l=2l=2,满足。
[2,3,4,5,6],target=0[2,3,4,5,6],target=0,此时l=0l=0,满足。
[2,3,4,5,6],target=7[2,3,4,5,6],target=7,此时l=6l=6,满足。
复杂度分析
时间复杂度:O(\log n)O(logn)
空间复杂度:O(1)O(1)
def searchInsert(self, nums: List[int], target: int) -> int:
if(not nums):
return 0
n=len(nums)
l=0
r=n-1
res=-1
while(l<=r):
mid=(l+r)//2
if(nums[mid]==target):
return mid
elif(nums[mid]>target):
r=mid-1
else:
l=mid+1
res=l
return res
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
看见1就说1个1,看见n个1就说n个1,看见其他数字也一样,例如:1121 -> 2个1 1个2 1个1 -> 211211。这道题目用groupby方法做就很容易实现,因为这个方法已经帮我们把数的步骤给解决了。使用了groupby方法后,效果为:1121 -> [['1', '1'], ['2'], ['1']] -> 我们只需要把groupby后的每个子数组长度放在前,元素放在后,拼接起来即可。
from itertools import groupby
class Solution:
def countAndSay(self, n: int) -> str:
result = '1'
for i in range(1, n):
result = ''.join([str(len(list(g))) + k for k, g in groupby(result)])
return result
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
动态规划
定义当前最大连续子序列和cur\_sum=0cur_sum=0,最大子序和res=nums[0]res=nums[0],数组长度nn
对数组进行遍历,对于nums[i]nums[i],存在两种情况:
若当前最大连续子序列和cur\_sum>0cur_sum>0,说明cur\_sumcur_sum对后续结果有着正向增益,即能使后续结果继续增大,则继续加和cur\_sum=cur\_sum+num[i]cur_sum=cur_sum+num[i]。
若当前最大连续子序列和cur\_sum<=0cur_sum<=0,说明cur\_sumcur_sum对后续结果没有增益或负向增益,即若存在更大的加和,一定是从下一元素开始,加上cur\_sumcur_sum,只会使结果更小。因此,令cur\_sumcur_sum更新为nums[i]nums[i]。
更新最大子序和resres,res=max(res,cur\_sum)res=max(res,cur_sum),始终保留最大结果。
复杂度分析
时间复杂度:O\left(n\right)O(n)
空间复杂度:O(1)O(1)
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
cur_sum=0
res=nums[0]
n=len(nums)
for i in range(n):
if(cur_sum>0):
cur_sum+=nums[i]
else:
cur_sum=nums[i]
res=max(res,cur_sum)
return res
给定一个仅包含大小写字母和空格 ' ' 的字符串,返回其最后一个单词的长度。
如果不存在最后一个单词,请返回 0 。
说明:一个单词是指由字母组成,但不包含任何空格的字符串。
示例:输入: "Hello World‘ ’输出: 5
class Solution:
def lengthOfLastWord(self, s: str) -> int:
return len(s.rstrip().split(" ")[-1])
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
列表求和
加1
结果转化成列表
话不多说,直接上代码:
方法一:
class Solution(object):
def plusOne(self, digits):
sums = 0
for i in range(len(digits)):
sums += 10**(len(digits)-1-i)*digits[i]
sums_str = str(sums + 1)
return [int(j) for j in sums_str]
方法二
class Solution(object):
def plusOne(self, digits):
sums = 0
for i in digits:
sums = sums * 10 + i #10进制乘以10,进行累和;
sums_str = str(sums + 1)
return [int(j) for j in sums_str]
给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字 1 和 0。
示例 1:
输入: a = "11", b = "1"
输出: "100"
内置函数
class Solution:
def addBinary(self, a: str, b: str) -> str:
return bin(int(a, 2) + int(b, 2))[2:]
非内置函数(重要!)
class Solution:
def addBinary(self, a: str, b: str) -> str:
r, p = '', 0
d = len(b) - len(a)
a = '0' * d + a
b = '0' * -d + b
for i, j in zip(a[::-1], b[::-1]):
s = int(i) + int(j) + p
r = str(s % 2) + r
p = s // 2
return '1' + r if p else r
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
二分法
思路分析:使用二分法搜索平方根的思想很简单,就类似于小时候我们看的电视节目中的“猜价格”游戏,高了就往低了猜,低了就往高了猜,范围越来越小。因此,使用二分法猜算术平方根就很自然。
一个数的平方根肯定不会超过它自己,不过直觉还告诉我们,一个数的平方根最多不会超过它的一半,例如 88 的平方根,88 的一半是 44,4^2=16>8,如果这个数越大越是如此,因此我们要计算一下,这个边界是多少。于是边界值就是 44,那么对 00、11、22、33 分别计算结果,很容易知道,这 44 个数的平方根依次是 00、11、11、11。
注意:这 44 个特值如果没有考虑到,有可能导致你设置的搜索边界不正确。在使用二分法寻找平方根的时候,要特别注意边界值的选择,以下给出两个参考代码。
参考代码 1:所有的数都放在一起考虑,为了照顾到 00 把左边界设置为 00,为了照顾到 11 把右边界设置为 x // 2 + 1。
class Solution:
def mySqrt(self, x: int) -> int:
# 为了照顾到 0 把左边界设置为 0
left = 0
# 为了照顾到 1 把右边界设置为 x // 2 + 1
right = x // 2 + 1
while left < right:
# 注意:这里一定取右中位数,如果取左中位数,代码可能会进入死循环
# mid = left + (right - left + 1) // 2
mid = (left + right + 1) >> 1
square = mid * mid
if square > x:
right = mid - 1
else:
left = mid
# 因为一定存在,因此无需后处理
return left
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。 -------动态规划问题
(递归超时):
class Solution:
def climbStairs(self,n):
if n == 1:
return 1
elif n == 2:
return 2
return self.climbStairs(n-1) + self.climbStairs(n-2)
(非递归):
class Solution:
def climbStairs(self, n: int) -> int:
a, b = 1, 2
for i in range(n - 1):
a, b = b, a + b
return a
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2 输出: 1->2
非递归写法
为了方便,设置了虚拟头结点。通过pre和cur两个指针指向元素不断比较。
cur指针可以省略,等效于官方的解法。
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
dummy_head = ListNode(None)
dummy_head.next = head
pre = dummy_head
cur = head
while cur:
if pre and cur.val == pre.val:
pre.next = cur.next
cur.next = None
cur = pre.next
continue
pre = cur
cur = cur.next
return dummy_head.next
递归解法
递归函数返回的不重复子链的头结点,在回溯过程中,比较当前节点和子链头结点的val是否相同,若相同则保留当前节点(删除子链的头结点)。
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
if head is None or head.next is None:
return head
child = self.deleteDuplicates(head.next)
if child and head.val == child.val:
head.next = child.next
child.next = None
return head
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
双指针 / 从前往后
直觉
一般而言,对于有序数组可以通过 双指针法 达到O(n + m)O(n+m)的时间复杂度。
最直接的算法实现是将指针p1 置为 nums1的开头, p2为 nums2的开头,在每一步将最小值放入输出数组中。
由于 nums1 是用于输出的数组,需要将nums1中的前m个元素放在其他地方,也就需要 O(m)O(m) 的空间复杂度。
class Solution(object):
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: void Do not return anything, modify nums1 in-place instead.
"""
# Make a copy of nums1.
nums1_copy = nums1[:m]
nums1[:] = []
# Two get pointers for nums1_copy and nums2.
p1 = 0
p2 = 0
# Compare elements from nums1_copy and nums2
# and add the smallest one into nums1.
while p1 < m and p2 < n:
if nums1_copy[p1] < nums2[p2]:
nums1.append(nums1_copy[p1])
p1 += 1
else:
nums1.append(nums2[p2])
p2 += 1
# if there are still elements to add
if p1 < m:
nums1[p1 + p2:] = nums1_copy[p1:]
if p2 < n:
nums1[p1 + p2:] = nums2[p2:]