题目:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
思路:
enumerate函数可以将列表变为以下标为键,列表元素为值的字典,通过循环可以分别拿出列表中对应的元素和下标,同时构建一个哈希表,判断目标值减去当前值是否存在于表中,如果存在,则返回对应元素下标,若不存在,则将当前元素存入表中
class Solution:
def twoSum(self,nums,target):
dic={}
for i,num in enumerate(nums):
if (target - num) in dic:
return [i,dic[target - num]]
dic[num] = i
题目:
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
思路:直接从两个链表中分别取出对应数字,相加,如果这两个数字相加的结果大于10,则需要进位
重复上述操作,直至链表尾,如果此时进位标记仍不为0(比方说5+5),则结果链表的下一个节点值设置为1
class Solution:
def addTwoNumbers(self,l1,l2):
head = ListNode(0)
node = head
carry = 0
while l1 or l2:
x = l1.val if l1 else 0
y = l2.val if l2 else 0
sum = x + y + carry
carry = sum // 10
node.next = ListNode(sum % 10)
if l1:
l1 = l1.next
if l2:
l2 = l2.next
node=node.next
if carry != 0:
node.next = ListNode(1)
return head.next
题目:
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度
示例:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串
思路:
class Solution:
def lengthOfLongestSubstring(self,s):
if not s:
return 0
window = []
max_length = 0
for c in s:
if c not in window: # 当前字符不在滑动窗口中,直接扩展
window.append(c)
else: # 当前字符在滑动窗口中,移除字符之前的部分,再扩展
window[:] = window[window.index(x) + 1:]
window.append(c)
max_length = max(len(window),max_length)
return max_length if max_length != 0 else len(s)
class Solution:
def lengthOfLongestSubstring(self,s):
if not s:
return 0
max_length = 0
left, right = 0, 0
for i, c in enumerate(s):
if c not in s[left:right]:
right += 1
else:
left += s[left:right].index(c) + 1
right += 1
max_length = max(right - left, max_length)
return max_length if max_length != 0 else len(s)
class Solution:
def lengthOfLongestSubstring(self,s):
# 可抛弃字符串的索引尾值 - 字符串索引值,该索引值以及之前的字符串都属于重复字符串中的一部分,不再计算中涉及
ignore_str_index_end = -1
dic = {}
max_length = 0
for i, c in enumerate(s):
if c in dic and dic[c] > ignore_str_index_end:
ignore_str_index_end = dic[c]
dic[c] = i
else:
dic[c] = i
max_length = max(i - ignore_str_index_end, max_length)
return max_length
题目:
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空
示例:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
思路:
题目要求时间复杂度为 O ( log ( m + n ) ) O(\log(m+n)) O(log(m+n)),想到应该用二分法
利用中位数的性质和左右中位数之间的关系来把所有的数先分成两堆,然后在两堆的边界返回答案
class Solution:
def findMedianSortedArrays(self,nums1,nums2):
m = len(nums1)
n = len(nums2)
# 让nums2是更长的那个数组
if m > n:
nums1, nums2, m, n = nums2, nums1, n, m
if n == 0:
raise ValueError
# nums1中index在imid左边的都被分配到左堆,nums2中jmid左边的都被分到左堆
imin, imax = 0, m
while(imin <= imax):
imid = imin + (imax - imin) // 2
jmid = (m + n - 2 * imid) // 2
if(imid > 0 and nums1[imid - 1] > nums2[jmid]):
imax = imid - 1
elif(imid < m and nums2[jmid - 1] > nums1[imid]):
imin = imid + 1
else:
if(imid == m): minright = nums2[jmid]
elif(jmid == n): minright = nums1[imid]
else:
minright = min(nums1[imid], nums2[jmid])
if(imid == 0): maxleft = nums2[jmid - 1]
elif(jmid == 0): maxleft = nums1[imid - 1]
else:
maxleft = max(nums1[imid - 1], nums2[jmid - 1])
if((m + n) % 2 == 1):
return minright
return (maxleft + minright) / 2
题目:
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000
示例:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案
输入: "cbbd"
输出: "bb"
思路:
1.动态规划
1)特判,当s的长度为1或0时,返回s
2)初始化最长会问子串的开始索引start和最长长度max_len = 1
3)初始化dp数组,为n x n,全部初始化为False,dp[i][j]表示s[i - j]是否为回文串
4)将dp中,所有单个字符处都是回文串,置为True,s中若相邻的字符串相同,则同样将这两个字符对应的位置置为True,即遍历s:
5)此时,从长度3开始遍历,遍历区间[3, n + 1),表示所有最长子串可能的长度,因为场地为1和2的已经在上一步找完了,对于可能的长度L:
6)返回s[start, …, start + max_len - 1]
class Solution:
def longestPalindrome(self,s):
if(not s or len(s) == 1):
return s
n = len(s)
dp = [[False]*n for _ in range(n)]
max_len = 1
start = 0
for i in range(n):
dp[i][i] = True
if(i < n - 1 and s[i] == s[i + 1]):
dp[i][i + 1] = True
start = i
max_len = 2
for l in range(3,n + 1):
for i in range(n + 1 - l):
r = i + l - 1
if(s[i] == s[r] and dp[i + 1][r - 1]):
dp[i][r] = True
start = i
max_len = l
return s[start:start + max_len]
2.中心扩展法
回文串的中心可能是一个字符串或者两个字符串,因此,我们遍历每一个字符和每一对相邻的字符
1)特判
2)初始化最长回文子串的开始索引和结束索引start = 0,end = 0
3)扩展函数expand( l , r l,r l,r), l l l表示传入中心字符的左界,r表示传入字符中心的右界
4)遍历s,遍历区间[0, n)
5)返回s[start,…, end]
class Solution:
def longestPalindrome(self,s):
def expand(l,r):
while(0 <= l and r < n and s[l] == s[r]):
l -= 1
r += 1
return r - l - 1
if(not s or len(s) == 1):
return s
n = len(s)
start = 0
end = 0
for i in range(n):
len1 = expand(i,i)
len2 = expand(i,i + 1)
len_long = max(len1,len2)
if(len_long > end - start):
start = i- (len_long - 1) // 2
end = i + (len_long) // 2
return s[start:end + 1]
3.双指针 + 滑动窗口
初始化两个指针left,right,遍历字符串
最开始的情况是第一个字符的索引和右索引相等(都为0),需要判断[0: 2]这段字符串是否是回文字符串(因为1个字符的情况不用讨论),如果这2个字符是回文字符,则更新左指针为当前字符索引和右指针之差(其实这个值为0,目的是为了下次判断还是从头开始,接着判断[0: 3]的情况),同时更新右指针(向右移动一位),如果这两个字符不是回文字符串([0: 2]的情况不是回文串),则开始判断[0: 3]的情况,如果这3个字符是回文串,则更新左右指针
class Solution:
def longestPalindrome(self, s):
str_length = len(s)
left, right = 0, 0
for i in range(str_length):
if i-right >= 1 and s[i-right-1:i+2]==s[i-right-1:i+2][::-1]:
left=i-right-1
right+=2
continue
if i-right>=0 and s[i-right:i+2]==s[i-right:i+2][::-1]:
left=i-right
right+=1
return s[left:left+right+1]
题目:
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列
比如输入字符串为 "LEETCODEISHIRING"
行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"
示例:
输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"
输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"
解释:
L D R
E O E I I
E C I H N
T S G
思路:
设numRows行字符串分别为s1,s2,…,sn,则容易发现:按顺序遍历字符串s时,每个字符c在Z字形中对应的行索引先从s1增大至sn,再从sn减小至s1…,如此反复
因此,解决方案为:模拟这个行索引的变化,在遍历s中把每个字符填到正确的行res[i]
按顺序遍历字符串s:
class Solution:
def convert(self,s,numRows):
if numRows < 2:return s
# 创建和行数对应的空列表
res = ['' for _ in range(numRows)]
i, flag = 0, -1
# 往每行的空列表里填上元素
for c in s:
res[i] += c
if i == 0 or i == numRows - 1:
flag = -flag
i += flag
return ''.join(res)
题目:
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转
示例:
输入: 123
输出: 321
输入: -123
输出: -321
输入: 120
输出: 21
注意:假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [− 2 31 2^{31} 231, 2 31 2^{31} 231-1]。请根据这个假设,如果反转后整数溢出那么就返回 0
class Solution:
def reverse(self,x):
temp = x
temp = str(abs(temp))
res = int(temp[::-1])
if res < 2**31 - 1 and -res > -2**31:
return res if x > 0 else - res
return 0
题目:
请你来实现一个 atoi 函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:
在任何情况下,若函数不能进行有效的转换时,请返回 0
提示:
本题中的空白字符只包括空格字符 ’ ’ 。
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231)
示例:
输入: "42"
输出: 42
输入: " -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42
输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字
输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
因此无法执行有效的转换
输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231)
class Solution:
def myAtoi(self,str):
INT_MAX = 2**31 - 1
INT_MIN = -2**31
str = str.lstrip()
import re
pattern = r'^[\+\-]?\d+'
match = re.match(pattern,str)
if match:
res = int(match[0])
print(res)
return min(max(res, INT_MIN), INT_MAX)
return 0
题目:
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数
示例:
输入: 121
输出: true
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数
class Solution:
def isPalindrome(self,x):
x = str(x)
return x == x[::-1]
题目:
给你一个字符串 s
和一个字符规律 p
,请你来实现一个支持 '.'
和 '*'
的正则表达式匹配
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
说明:
s
可能为空,且只包含从 a-z
的小写字母。p
可能为空,且只包含从 a-z
的小写字母,以及字符 .
和 *
示例:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串
输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
思路:
动态规划
状态定义:
初始状态:
转移方程:
1.当p[n]为‘*’时:
1.当p[n - 1]为‘.'或s[m] == p[n - 1]时:dp[i][j] = dp[i - 1][j];
Tips:此两种情况代表s[m]和p[n - 1]可以匹配,等价于无s[m]的状态dp[i - 1][j]
2.否则:dp[i][j] = dp[i][j - 2];
Tips:此情况代表s[m]和p[n - 1]无法匹配,p[n - 1]p[n]的组合必须出现0次,等价于没有p[n - 1]p[n]时的状态dp[i][j - 2]
2.否则,当p[n]为’.‘,或s[m] == p[n]时:dp[i][j] = dp[i - 1][j - 1];
Tips:此情况代表s[m]和p[n]直接匹配,当前状态等价于未匹配此两字符前的状态dp[i - 1][j - 1]
返回值:
字符串s中前ls个字符和p中前lp个字符是否匹配,即dp[ls][lp]
class Solution:
def isMatch(self, s, p):
ls, lp = len(s), len(p)
dp = [[False for _ in range(lp + 1)] for _ in range(ls + 1)]
dp[0][0] = True
for j in range(2, lp + 1):
dp[0][j] = dp[0][j - 2] and p[j - 1] == '*'
for i in range(1, ls + 1):
for j in range(1, lp + 1):
m, n = i - 1, j - 1
if p[n] == '*':
if s[m] == p[n - 1] or p[n - 1] == '.':
dp[i][j] = dp[i][j - 2] or dp[i - 1][j]
else:dp[i][j] = dp[i][j - 2]
elif s[m] == p[n] or p[n] == '.':
dp[i][j] = dp[i - 1][j - 1]
return dp[-1][-1]
题目:
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水
说明:你不能倾斜容器,且n的值至少为2
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49
实例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
思路:
双指针 + 动态规划
初始化两个指针,分别指向数组的头和尾,并求取此时容器的容积(木桶原理,以最短的长度为标准),然后分别移动左、右指针,同时动态维护一个最大容积,如果此时的容积大于程序维护的容积,则替换
class Solution:
def maxArea(self, height):
left, right = 0, len(height) - 1
max_area= 0
while left < right:
width = right - left
area = width * min(height[left], height[right])
if area > max_area:
max_area = area
if height[left] < height[right]:
left += 1
else: right -= 1
return max_area
题目:
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900
给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内
实例:
输入: 3
输出: "III"
输入: 4
输出: "IV"
输入: 9
输出: "IX"
输入: 58
输出: "LVIII"
解释: L = 50, V = 5, III = 3
输入: 1994
输出: "MCMXCIV"
解释: M = 1000, CM = 900, XC = 90, IV = 4
思路:
贪心法
我们每次尽量使用最大的数字来表示,所以,将哈希表按照从大到小的顺序排列,然后遍历哈希表,直到完成整个输入
class Solution:
def intToRoman(self, num):
hashmap = {1000:'M', 900:'CM', 500:'D', 400:'CD', 100:'C', 90:'XC', 50:'L', 40:'XL', 10:'X', 9:'IX', 5:'V', 4:'IV', 1:'I'}
res = ''
for key in hashmap:
if num // key != 0 :
count = num // key
res += hashmap[key] * count
num %= key
return res
题目:
同上题,只是输入是罗马数字,需要转为整数
思路:
通过观察,只有在遇到特殊情况时,两个字符中左边的字符小于右边的字符,且等于右边的字符代表的数字减左边代表的数
因此,如果s[i] < s[i+1],就将结果减去s[i]代表的数字,否则,将结果加上s[i]代表的数字
class Solution:
def romanToInt(self, s):
Roman2Int = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
res = 0
length = len(s) - 1 # -1的目的是防止越界,因为需要判定index + 1
for index in range(length):
if Roman2Int[s[index]] < Roman2Int[s[index + 1]]:
res -= Roman2Int[s[index]]
else:
res += Roman2Int[s[index]]
return res + Roman2Int[s[-1]]
题目:
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
实例:
输入: ["flower","flow","flight"]
输出: "fl"
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀
说明:所有输入只包含小写字母 a-z
思路:
先把字符串数组排序(根据a-z的顺序排序),然后取出第一个和最后一个字符串,找出他们的相同前缀就是整个数组的相同前缀
class Solution:
def longestCommonPrefix(self,strs):
if not strs:
return ''
strs = sorted(strs)
first, last = strs[0], strs[-1]
length = min(len(first), len(last))
ans = ''
for i in range(length):
if first[i] == last[i]:
res += first[i]
break
return res
题目:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组
实例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
思路:
排序 + 双指针(其实是固定了一个指针,另外有两个活动的指针)
1.特判
2.对数组进行排序
3.遍历排序后的数组:
class Solution:
def threeSum(self, nums):
n = len(nums)
res = []
if not nums or n < 3:
return []
nums = sorted(nums)
for i in range(n):
if nums[i] > 0:
return res
# 去除重复解的步骤
if i > 0 and nums[i] == nums[i - 1]:
continue
L = i + 1
R = n - 1
while (L < R):
if nums[i] + nums[L] + nums[R] == 0:
res.append([nums[i], nums[L], nums[R]])
# 去除重复解的步骤
while (L < R and nums[L] == nums[L + 1]):
L += 1
while (L < R and nums[R] == nums[R - 1]):
R -= 1
L += 1
R -= 1
elif nums[i] + nums[L] + nums[R] > 0 :
R -= 1
else:
L += 1
return res
题目:
给定一个包括n个整数的数组nums和一个目标值target,找出nums中的三个整数,使得他们的和与target最接近,返回这三个整数的和,假定每组输入只存在唯一答案
实例:
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2)
思路:
对数组排序(这点很重要!),思路和上一题类似
class Solution:
def threeSumClosest(self, nums, target):
n = len(nums)
nums = sorted(nums)
res = 2**10 # 这里是随便给的一个结果,但是不能太小
for i in range(n):
left, right = i+1, n-1
while left < right:
cur = nums[i] + nums[left] + nums[right]
if cur == target:
return target
if abs(cur - target) < abs(res - target):
res = cur
if cur - target < 0:
left += 1
else: right -= 1
return res
题目:
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母
实例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
思路:
1.当出现“所有组合”等类似字眼,可能会用到回溯法
定义函数backtrack(combination, nextdigits),当nextdigit非空时,对于nextdigit[0]中的每一个字母letter,执行回溯backtrack(combination + letter, nextdigit[1:]),直到nextdigit为空,最后将combination加入到结果中
class Solution:
def letterCombinations(self, digits):
if not digits:
return []
phone = {
'2':['a', 'b', 'c'],
'3':['d', 'e', 'f'],
'4':['g', 'h', 'i'],
'5':['j', 'k', 'l'],
'6':['m', 'n', 'o'],
'7':['p', 'q', 'r', 's'],
'8':['t', 'u', 'v'],
'9':['w', 'x', 'y', 'z']
}
def backtrack(combination, nextdigit):
if len(nextdigit) == 0:
res.append(combination)
else:
for letter in phone[nextdigit[0]]:
backtrack(combination + letter, nextdigit[1:])
res = []
backtrack('', digits)
return res
2.队列
先将输入的digits中第一个数字对应的每个字母入队,然后将出队的元素与第二个数字对应的每个字母组合后入队,直到遍历到digits的结尾,最后队列中的元素就是所求结果
class Solution:
def letterCombination(self, digits):
if not digits:
return []
phone = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']
queue = ['']
for digit in digits:
for _ in range(len(queue)):
tmp = queue.pop(0)
# 因为数字是从2开始的,而数组的下标是从0开始,所以需要-2
for letter in phone[int(digit) - 2]:
queue.append(tmp + letter)
return queue
题目:
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组
注意:答案中不可以包含重复的四元组
实例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
思路:
在三数之和的基础上,依然用双指针法
1.先排序,指针依次是p, k, i, j,如果nums[p] + 3*nums[p + 1] > target,因为nums升序排列,所以之后的数肯定都大于target,所以,以后不会有比这个更小的数了,直接break
2.如果nums[p] + 3*nums[-1] < target,那么当前的nums[p]加其余三个数一定小于target,所以直接下一位即可
3.k和p的判断一样,只是将3变成了2,target变成了target - nums[p]
同样,为了避免重复结果,某个指针遇到相同的数需要直接跳过
class Solution:
def fourSum(self, nums):
nums = sorted(nums)
n = len(nums)
res = []
p = 0 # p, k, i, j
while p < n - 3:
if nums[p] + 3 * nums[p + 1] > target:
break
if nums[p] + 3 * nums[-1] < target:
while p < n - 4 and nums[p] == nums[p + 1]:
p += 1
p += 1
continue
k = p + 1
while k < n - 2: # k 和 p 的判断是一样的
if nums[p] + nums[k] + 2 * nums[k + 1] > target:
break
if nums[p] + nums[k] + 2 * nums[-1] < target:
while k < n - 3 and nums[k] == nums[k + 1]:
k += 1
k += 1
continue
i = k + 1
j = n - 1
new_target = target - nums[p] - nums[k]
while i < j:
if nums[i] + nums[j] > new_target:
j -= 1
elif nums[i] + nums[j] < new_target:
i += 1
else:
res.append([nums[p],nums[k],nums[i],nums[j]])
i += 1
j -= 1
while i < j and nums[i] == nums[i - 1]:
i += 1
while i < j and nums[j] == nums[j + 1]:
j -= 1
while k < n - 3 and nums[k] == nums[k + 1]:
k += 1
k += 1
while p < n - 4 and nums[p] == nums[p + 1]:
p += 1
p += 1
return res
题目:
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点
实例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5
说明:给定的n保证是有效的
思路:
1.循环两次,第一次拿到节点个数,第二次删除对应节点
class Solution:
def removeNthFromEnd(self, head, n):
count = 0
temp = head
while temp:
count += 1
temp = temp.next
# 最后还要-1是因为计数是从0开始
n = count - n - 1
temp = head
# 如果要删除的节点是头结点
if n == -1:
head = head.next
else:
while n:
temp = temp.next
n -= 1
temp.next = temp.next.next
return head
2.双指针法
第一个指针先走n步,然后两个指针同时开始走,当第一个指针走到末尾的时候,此时第二个指针对应的点就是要删除的点
class Solution:
def removeNthFromEnd(self, head, n):
# 哨兵节点,防止头结点为空的情况
node = ListNode(0)
node.next = head
first = second = node
# 这里要+1是因为计数从0开始,而删除倒数节点则是直接计数
for _ in range(n + 1):
first = first.next
while first:
first = first.next
second = second.next
second.next = second.next.next
return node.next
题目:
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否有效
有效字符串需满足:
1.左括号必须用相同类型的左括号闭合
2.左括号必须以正确的顺序闭合
注意:空字符串可以被认为是有效字符串
示例:
输入: "()"
输出: true
输入: "()[]{}"
输出: true
输入: "(]"
输出: false
输入: "([)]"
输出: false
输入: "{[]}"
输出: true
思路:
利用栈的FILO特性解决
class Solution:
def isValid(self, s):
hashmap = {'(':')', '[':']', '{':'}', '?':'?'}
# 这里是随意添加了一个值在栈中,防止pop的时候空栈
left_stack = ['?']
for ele in s:
if ele in hashmap:
left_stack.append(ele)
else:
left = left_stack.pop()
if hashmap[left] != ele:
return False
return len(left_stack) == 1
题目:
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
实例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
思路:
1.直接法,两个链表都是升序的,找到头结点最小的链表,是新链表的表头,然后依次将元素升序连接
class Solution:
def mergeTwoLists(self, l1, l2):
if not l1:
return l2
if not l2:
return l1
# 创建一个哑节点,作为链表的开头(当中的数字不会被读取)
temp = ListNode(-1)
# 创建一个游标,用来遍历节点
node = temp
while l1 and l2:
if l1.val <= l2.val:
node.next = l1
l1 = l1.next
else:
node.next = l2
l2 = l2.next
node = node.next
node.next = l1 if l1 else l2
return temp.next
2.递归
class Solution:
def mergeTwoLists(self, l1, l2):
if not l1: return l2
if not l2: return l1
if l1.val <= l2.val:
l1.next = self.mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next)
return l2
题目:
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合
实例:
输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
思路:
回溯法(当拿到一个问题,如果感觉不穷举就没法知道答案,就需要用回溯法)
回溯法是一个剪枝了的二叉树,我们要的结果是可以good leaf,如果不满足good leaf,就继续向下搜索,搜索的时候要满足一定条件
可以看到,最后的五条黑线就是最终结果,其中左分支都是添加左括号,右分支都是添加右括号
在什么时候添加左括号?,很明显,最多能添加n个左括号,在递归调用的时候,在能传递到最底层的公用字符串中先添加“(”,然后left - 1,递归调用即可
什么时候添加右括号?,当左括号的个数大于右括号的个数时添加右括号
总之,向下搜索需要满足两个条件:
1.插入数量不超过n
2.可以插入)
的前提是(
的数量大于)
回溯法的代码套路:使用两个变量res和path,res表示最终的结果,path保存已经走过的路径,如果搜到一个状态满足题目的要求,就把path放到res中
class Solution:
def generateParenthesis(self, n):
res = []
self.dfs(res, n, n, '')
return res
def dfs(self, res, left, right, path):
if left == 0 and right == 0:
res.append(path)
return
if left > 0:
self.dfs(res, left - 1, right, path + '(')
if left < right:
self.dfs(res, left, right - 1, path + ')')
题目:
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度
实例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
思路:
1.暴力法
class Solution:
def mergeKLists(self, lists):
nodes = []
head = point = ListNode(0)
for ele in lists:
while ele:
nodes.append(ele.val)
ele = ele.next
nodes = sorted(nodes)
for x in nodes:
point.next = ListNode(x)
point = point.next
return head.next
2.堆排序/优先队列
利用Python的heapq模块进行堆排序
补充:堆
堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针,堆根据”堆属性“来排序,“堆属性”决定了树中节点的位置,堆的特点就是FIFO(先进先出)
堆的常用方法:
堆属性
堆分为两种:最大堆,和最小堆,两者的差别在于节点的排列方式
最大堆:父节点的值比每一个子节点的值都要大,所以总是将最大值存放在树的根节点
最小堆:父节点的值比每一个子节点的值都要小,所以总是将最小值存放在树的根节点
以上就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立,堆常常被当做优先队列使用,因为可以快速访问到“最重要”的元素
注意:
堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个
堆排序
同时,对堆中的节点按层进行编号,将这种逻辑结构映射到数组中就是下面这样
arr: [50, 45, 40, 20, 25, 35, 30, 10, 15]
这个数组从逻辑上讲就是一个堆结构
大顶堆:arr[i] >= arr[2i + 1] && arr[i] >= arr[2i + 2]
小顶堆:arr[i] <= arr[2i + 1] && arr[i] <= arr[2i + 2]
堆排序的基本思想与步骤:
堆排序的基本思想:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点,将其与末尾元素进行交换,此时末尾元素就是最大值,然后将n - 1个元素重新构造成一个堆,这样会得到n个元素的次小值,如此反复,可以得到一个有序序列
class Solution:
def mergeKLists(self, lists):
import heapq
head = point = ListNode(0)
heap = []
for ele in lists:
while ele:
heapq.heappush(heap, ele.val)
ele = ele.next
while heap:
val = heappop(heap)
point.next = ListNode(val)
point = point.next
point.next = None
return head.next
题目:
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换
实例:
给定 1->2->3->4, 你应该返回 2->1->4->3
class Solution:
def swapPairs(self, head):
thead = ListNode(-1)
thead.next = head
c = thead
while c.next and c.next.next:
a, b = c.next, c.next.next
c.next, a.next = b, b.next
b.next = a
c = c.next.next
return thead.next
题目:
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序
实例:
给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明:
思路:
需要把链表节点按照k个一组分组,所以可以使用一个指针head,依次指向每组的头结点,这个指针每次向前移动k步,直至链表结尾
对于每个分组,我们先判断它的长度是否大于等于k,若是,就反转链表,否则不需要反转
接下来就是如何反转一个子链表,对于一个子链表,除了反转其本身之外,还需要将子链表的头部与上一个子链表连接,以及子链表的尾部与下一个子链表连接
因此,在反转子链表的时候,我们不仅需要子链表头结点head,还需要有head的上一个节点pre,以便反转完后把子链表再接回pre
class Solution:
def reverse(self, head, tail):
prev = tail.next
p = head
while prev != tail:
nex = p.next
p.next = prev
prev = p
p = next
return tail, head
def reverseKGroup(self, head, k):
hair = ListNode(0)
hair.next = head
pre = hair
while head:
tail = pre
for i in range(k):
tail = tail.next
if not tail:
return hair.next
nex = tail.next
head, tail = self.reverse(head, tail)
pre.next = head
tail.next = nex
pre = tail
head = tail.next
return hair.next
题目:
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成
实例:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素
思路:
因为是排序数组,所以用一个指针指向下标为1的元素,如果前一个元素和这个元素相等,删除前一个元素
class Solution:
def removeDuplicates(self, nums):
pre = 1
while pre < len(nums):
if nums[pre - 1] == nums[pre]:
nums.pop(pre)
else:
pre += 1
return len(nums)
题目:
给你一个数组nums和一个值val,你需要原地移除所有数值等于val的元素,并返回移出后数组的新长度
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素
实例:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素
class Solution:
def removeElement(self, nums, val):
while val in nums:
nums.remove(val)
return len(nums)
题目:
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1
实例
输入: haystack = "hello", needle = "ll"
输出: 2
输入: haystack = "aaaaa", needle = "bba"
输出: -1
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符
思路:
采用正则表达式
class Solution:
def strStr(self, haystack, needle):
if not needle:
return 0
import re
pattern = needle
res = re.findall(pattern, haystack)
if res:
return len(haystack.split(needle)[0])
return -1
题目:
给定两个整数,被除数 dividend
和除数 divisor
。将两数相除,要求不使用乘法、除法和 mod 运算符
返回被除数 dividend
除以除数 divisor
得到的商
整数除法的结果应当截去(truncate
)其小数部分,例如:truncate(8.345) = 8
以及 truncate(-2.7335) = -2
实例:
输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = truncate(3.33333..) = truncate(3) = 3
输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = truncate(-2.33333..) = -2
class Solution:
def divide(self, dividend, divisor):
sign = (dividend > 0) ^ (divisor > 0)
dividend = abs(dividend)
divisor = abs(divisor)
count = 0
#把除数不断左移,直到它大于被除数
while dividend >= divisor:
count += 1
divisor <<= 1
result = 0
while count > 0:
count -= 1
divisor >>= 1
if divisor <= dividend:
result += 1 << count #这里的移位运算是把二进制(第count+1位上的1)转换为十进制
dividend -= divisor
if sign: result = -result
return result if -(1<<31) <= result <= (1<<31)-1 else (1<<31)-1
题目:
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序
实例:
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案
输入:
s = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
输出:[]
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
lefts,rights = 0,0
if len(words) == 0:
return []
lens = len(words[0])
worddict = {}
currentdict = {}
results = []
for i in range(len(words)):
if words[i] not in worddict:
worddict[words[i]] = 1
else:
worddict[words[i]] = worddict[words[i]]+1
#统计words中每种字符串的个数,将words中每种字符串放置到worddict中
cnt = 0
currentstring = ''
for i in range(lens):
lefts,rights = i,i
#从不同的位置开始打头,比如每一次增长为3的时候,那么长度
#为0,1,2的时候进行开头都可以
cnt = 0
currentdict = {}
while lefts <= len(s)-lens*len(words):
#lefts没有到达最右边的时候(还有能够放下一次完整words部分的时候)
while cnt < len(words) :
#cnt为目前已经匹配到words时的长度
currentstring = s[rights:rights+lens]
#currentstring为往右边走的字符串
if currentstring not in worddict:
#跳出的情况1:该字符串不在worddict之中
break
elif currentstring in currentdict and currentdict[currentstring]+1 > worddict[currentstring]:
#跳出的情况2:该字符串在worddict之中,但是出现的次数超出了words所能承受的大小
break
#匹配成功后将该字符串放入currentdict之中(currentdict统计的是左右指针之间所包含的范围)
if currentstring not in currentdict:
currentdict[currentstring] = 1
else:
currentdict[currentstring] = currentdict[currentstring]+1
#匹配成功后继续往右边行走
cnt = cnt+1
rights = rights+lens
if currentdict == worddict:
results.append(lefts)
if currentstring not in worddict:
#跳出的情况1:右边的字符串不在words之中,此时循环将右侧所有不在words之中的字符串去除,直到找到存在于words之中的字符串为止
while rights+lens <= len(s) and s[rights:rights+lens] not in worddict:
rights = rights+lens
lefts = rights
cnt = 0
currentdict = {}
#此时初始化左右指针并且currentdict设置为空
else:
if currentdict[currentstring] > worddict[currentstring]:
#跳出的情况2:右侧的字符串超过了words的限制,此时将左指针右移,直到左指针移动到超出的那个字符串之前为止
while lefts+lens <= len(s)-lens*len(words) and s[lefts:lefts+lens] != currentstring:
lefts = lefts+lens
#lefts左指针向前迈出一步,将左侧的那个字符串去除掉(如果之前正好currentdict与worddict完美匹配的时候,将会直接走到这一步)
currentdict[s[lefts:lefts+lens]] = currentdict[s[lefts:lefts+lens]]-1
cnt = cnt-1
lefts = lefts+lens
return results