LeetCode初級演算法程式碼
初級演算法
1. 陣列
1. 雙指標
刪除排序陣列中的重複項:雙指標一快一慢,慢指標為最終輸出陣列
def removeDuplicates(self, nums: List[int]) -> int:
l = len(nums)
i, j = 0, 1
if l < 2:return l;
for a in range(l-1):
if nums[i] != nums[j]:
i += 1
nums[ i] = nums[j]
j +=1
nums = nums[0:i+1]
return len(nums);
2. 貪心演算法
問題求解時,總是做出目前最優的方案
def maxProfit(self, prices: List[int]) -> int:
income = 0
for i in range(1,len(prices)):
income += max(0, prices[i]-prices[i-1])
return income
3. 旋轉陣列
1. 暴力法
每次移動一個元素
def rotate(self, nums: List[int], k: int) -> None:
temp ,previous = 0, 0
for i in range(k):
previous = nums[len(nums) - 1]
for j in range(len(nums)):
temp = nums[j]
nums[j] = previous
previous = temp
return nums
時間複雜度:O(n*k) 空間複雜度O(1)
2. 反轉
當我們旋轉陣列 k 次, *k%n* 個尾部元素會被移動到頭部,剩下的元素會被向後移動
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
k %= n
nums[:] = nums[::-1] #反轉整個陣列
nums[:k] = nums[:k][::-1] #反轉前k個元素
nums[k:] = nums[k:][::-1] #反轉剩下的元素
時間複雜度:O(n) 空間複雜度O(1)
3. 環狀替代
陣列內元素的移動可以形成一個或多個閉環,只需一次遍歷即可
def rotate(self, nums: List[int], k: int) -> None:
count,index,temp = 0,0,nums[0]
done_index = [0]
while count < len(nums):
count,target = count+1, (index + k) % len(nums)
temp,nums[target] = nums[target],temp
if target not in done_index:
index = target
elif target + 1 < len(nums):
index,temp = target + 1,nums[target + 1]
done_index.append(index)
時間複雜度:O(n) 空間複雜度O(1)
4. 存在重複元素
1. 樸素線性查詢
依次逐個檢查列表中的元素,直到找到滿足的元素
def containsDuplicate(self, nums: List[int]) -> bool:
for i in range(len(nums)):
for j in range(i):
if nums[j] == nums[i]:
return True
return False
時間複雜度 : O(n2) 空間複雜度 : O(1)
2. 排序
將陣列排列後,掃描是否粗壯乃連續的重複元素
def containsDuplicate(self, nums: List[int]) -> bool:
nums.sort()
for i in range(len(nums)-1):
if nums[i] == nums[i + 1]:
return True
return False
時間複雜度 : O(n log n) 空間複雜度:O(1)
3. 雜湊表
利用支援快速搜尋和插入操作的動態資料結構。
def containsDuplicate(self, nums: List[int]) -> bool:
return len(nums) != len(set(nums)) #set()可以自動去重,自動排序
時間複雜度:O(n) 空間複雜度:O(n)
5. 只出現一次的數字(重複兩次)
1. 位運算
陣列中的全部元素的異或運算結果即為陣列中只出現一次的數字 eg:6 ^4 = 4^6
def singleNumber(self, nums: List[int]) -> int:
return reduce(lambda x, y: x ^ y, nums)
時間複雜度:O(n) 空間複雜度:O(1)
2.排序
def singleNumber(nums):
if len(nums)==1: #如果陣列長度為1,則直接返回即可
return nums[0]
nums.sort() #對陣列進行排序,使其相同元素靠在一起
for i in range(1,len(nums),2): #迴圈陣列,驗證前後是否相同,由於原始出現兩次,因此可跳躍判斷
if nums[i-1] != nums[i]:
return nums[i-1]
if (i+2) == len(nums): #判斷單一元素在排序後陣列的最後面
return nums[-1]
3.刪除元素
依次刪除列表的元素
def singleNumber(nums):
while True:
d = nums[0]
nums.remove(d)
try:
nums.remove(d)
except:
return d
6. 兩個陣列的交集
1.雜湊表
用雜湊表儲存每個數字出現的次數
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
if len(nums1) > len(nums2):
return self.intersect(nums2, nums1)
m = collections.Counter()
for num in nums1:
m[num] += 1
intersection = list()
for num in nums2:
if (count := m.get(num, 0)) > 0:
intersection.append(num)
m[num] -= 1
if m[num] == 0:
m.pop(num)
return intersection
時間複雜度:O(m+n) 空間複雜度:O(min(m,n))
2. 雙指標
先對兩個陣列進行排序,然後使用兩個指標遍歷兩個陣列。
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1.sort()
nums2.sort()
length1, length2 = len(nums1), len(nums2)
intersection = list()
index1 = index2 = 0
while index1 < length1 and index2 < length2:
if nums1[index1] < nums2[index2]:
index1 += 1
elif nums1[index1] > nums2[index2]:
index2 += 1
else:
intersection.append(nums1[index1])
index1 += 1
index2 += 1
return intersection
時間複雜度:O(m log m+n log n) 空間複雜度:O(min(m,n))
7. 陣列最後一位加一,每個元素為 0-9
1.數組合成數字
def plusOne(digits) :
nums_s = ''.join([str(x) for x in digits]) #先將其陣列中的值轉換為字串,然後進行拼接
nums = str(int(nums_s) + 1)
r = [int(x) for x in nums]
return [0]*(len(digits)-len(r)) + r #陣列前面可能存在多個0,防止這種情況出現
2.從陣列尾部遍歷
def plusOne(self, digits: List[int]) -> List[int]:
for i in range(len(digits),0,-1): #迴圈陣列,從最後一位開始計算
if digits[i-1] == 9: #如果為9,則相加為十,需要向前進一位
digits[i-1] = 0
if i == 1 : #如果滿十進位到陣列第一個,則陣列需要增加一位
digits = [1] + digits
else:
digits[i-1] += 1 #不等於9就終止迴圈結束
break
return digits
8. 移動陣列中零
def moveZeroes(self, nums: List[int]) -> None:
i = 0
for a in range(len(nums)):
if nums[i] == 0:
del nums[i]
nums.append(0)
else:
i += 1
return nums
9.兩數之和
雜湊表
建立一個雜湊表,對於每一個x,我們首先查詢雜湊表中是否存在target - x,然後將x插入到雜湊表中
def twoSum(self, nums: List[int], target: int) -> List[int]:
hashtable = dict()
for i, num in enumerate(nums):
if target - num in hashtable:
return [hashtable[target - num], i]
hashtable[nums[i]] = i
return []
10. 有效的數獨
def isValidSudoku(self, board: List[List[str]]) -> bool:
row = [{} for _ in range(9)]
col = [{} for _ in range(9)]
grid = [[{} for _ in range(3)] for _ in range(3)]
for i in range(9):
for j in range(9):
if board[i][j] != '.':
tmp = int(board[i][j])
row[i][tmp] = row[i].get(tmp, 0) + 1 #get("位置", “不存在時返回值”)
col[j][tmp] = col[j].get(tmp, 0) + 1
grid[i//3][j//3][tmp] = grid[i//3][j//3].get(tmp, 0) + 1
if row[i].get(tmp) > 1 or col[j].get(tmp) > 1 or grid[i//3][j//3].get(tmp) > 1:
return False
return True
11.旋轉影象
def rotate(self, matrix: List[List[int]]) -> None:
n = len(matrix[0])
for i in range(n // 2 + n % 2):
for j in range(n // 2):
tmp = matrix[n - 1 - j][i] #左上角
matrix[n - 1 - j][i] = matrix[n - 1 - i][n - j - 1] #右上角
matrix[n - 1 - i][n - j - 1] = matrix[j][n - 1 -i] #右下角
matrix[j][n - 1 - i] = matrix[i][j] #左下角
matrix[i][j] = tmp #左上角
2.字串
1. 反轉字串
def reverseString(self, s: List[str]) -> None:
for i in range(len(s),len(s)//2,-1):
s[i-1],s[len(s)-i] = s[len(s)-i],s[i-1]
def reverseString(self, s: List[str]) -> None:
s.reverse()
def reverseString(self, s: List[str]) -> None:
s = s[::-1]
2. 整數反轉
將整數轉換為字串
def reverse(self, x: int) -> int:
s = str(x)
if s[0] == '-':
x = int('-' + s[1:][::-1])
else:
x = int(s[::-1])
if (-2147483648 <= x <= 2147483647):
return x
else:
return 0
3. 字串中的第一個唯一字元
def firstUniqChar(self, s: str) -> int:
count = collections.Counter(s) #統計s中元素出現頻率
for idx, ch in enumerate(s): #idx 為s中內容的序號,ch為s中內容
if count[ch] == 1:
return idx
return -1
4. 有效的字母異位詞
1.排序法
將字串儲存到容器,進行sort排序,然後依次對比
def isAnagram(self, s: str, t: str) -> bool:
if len(s) != len (t):
return False
a = 0
s_l = list(s)
t_l = list(t)
s_l.sort()
t_l.sort()
s_0 = "".join(s_l)
t_0 = "".join(t_l)
for i in range(len(s)):
if s_0[i] == t_0[i]:
a += 1
if a == len(s):
return True
else:
return False
2.雜湊表
利用collections.Counter函式
def isAnagram(self, s: str, t: str) -> bool:
return collections.Counter(s) == collections.Counter(t)
5. 驗證迴文串
1.篩選判斷
先篩選出所有字母與數字,並將字母全部小寫,然後判斷
def isPalindrome(self, s: str) -> bool:
sgood = "".join(ch.lower() for ch in s if ch.isalnum()) #lower()將字母小寫,ch.isalnum()判斷是否為字母與數字
return sgood == sgood[::-1]
3. 在原字串上直接判斷
利用雙指標
def isPalindrome(self, s: str) -> bool:
n = len(s)
left, right = 0, n - 1
while left < right:
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
if left < right:
if s[left].lower() != s[right].lower():
return False
left, right = left + 1, right - 1
return True
4. 字串轉換整數 (atoi)
1.正常遍歷
class Solution:
def myAtoi(self, s: str) -> int:
i=0
n=len(s)
while i<n and s[i]==' ':
i=i+1
if n==0 or i==n:
return 0
flag=1
if s[i]=='-':
flag=-1
if s[i]=='+' or s[i]=='-':
i=i+1
INT_MAX=2**31-1
INT_MIN=-2**31
ans=0
while i<n and '0'<=s[i]<='9':
ans=ans*10+int(s[i])-int('0')
i+=1
if(ans-1>INT_MAX):
break
ans=ans*flag
if ans>INT_MAX:
return INT_MAX
return INT_MIN if ans<INT_MIN else ans
2.有限狀態機
' ' | +/- | number | other | |
---|---|---|---|---|
start | start | signed | in_number | end |
signed | end | end | in_number | end |
in_number | end | end | in_number | end |
end | end | end | end | end |
INT_MAX = 2 ** 31 - 1
INT_MIN = -2 ** 31
class Automaton:
def __init__(self):
self.state = 'start'
self.sign = 1
self.ans = 0
self.table = {
'start': ['start', 'signed', 'in_number', 'end'],
'signed': ['end', 'end', 'in_number', 'end'],
'in_number': ['end', 'end', 'in_number', 'end'],
'end': ['end', 'end', 'end', 'end'],
}
def get_col(self, c):
if c.isspace():
return 0
if c == '+' or c == '-':
return 1
if c.isdigit():
return 2
return 3
def get(self, c):
self.state = self.table[self.state][self.get_col(c)]
if self.state == 'in_number':
self.ans = self.ans * 10 + int(c)
self.ans = min(self.ans, INT_MAX) if self.sign == 1 else min(self.ans, -INT_MIN)
elif self.state == 'signed':
self.sign = 1 if c == '+' else -1
class Solution:
def myAtoi(self, str: str) -> int:
automaton = Automaton()
for c in str:
automaton.get(c)
return automaton.sign * automaton.ans
4.正則表達
def myAtoi(self, str: str) -> int:
INT_MAX = 2147483647
INT_MIN = -2147483648
str = str.lstrip() #清除左邊多餘的空格
num_re = re.compile(r'^[\+\-]?\d+') #設定正則規則
num = num_re.findall(str) #查詢匹配的內容
num = int(*num) #由於返回的是個列表,解包並且轉換成整數
return max(min(num,INT_MAX),INT_MIN) #返回值
元字元 | 匹配內容 |
. | 匹配除換行符以外的任意字元 |
\w | 匹配字母或數字或下劃線 |
\s | 匹配任意的空白符 |
\d | 匹配數字 |
\n | 匹配一個換行符 |
\t | 匹配一個製表符 |
\b | 匹配一個單詞的結尾 |
^ | 匹配字串的開始 |
$ | 匹配字串的結尾 |
\W | 匹配非字母或數字或下劃線 |
\D | 匹配非數字 |
\S | 匹配非空白符 |
a|b | 匹配字元a或字元b |
() | 匹配括號內的表示式,也表示一個組 |
[...] | 匹配字元組中的字元 |
[^...] | 匹配除了字元組中字元的所有字元 |
量詞 | 用法說明 |
* | 重複零次或更多次 |
+ | 重複一次或更多次 |
? | 重複零次或一次 |
{n} | 重複n次 |
{n,} | 重複n次或更多次 |
{n,m} | 重複n到m次 |
5. 實現 strStr()
1.子串逐一比較 - 線性時間複雜度
沿著字元換逐步移動滑動視窗,將視窗內的子串與 needle 字串比較。
def strStr(self, haystack: str, needle: str) -> int:
L, n = len(needle), len(haystack)
for start in range(n - L + 1):
if haystack[start: start + L] == needle:
return start
return -1
2.雙指標 - 線性時間複雜度
def strStr(self, haystack: str, needle: str) -> int:
L, n = len(needle), len(haystack)
if L == 0:
return 0
pn = 0
while pn < n - L + 1:
while pn < n - L + 1 and haystack[pn] != needle[0]: # 與第一個字母不匹配
pn += 1
curr_len = pL = 0
while pL < L and pn < n and haystack[pn] == needle[pL]: # 與第一個字母匹配
pn += 1
pL += 1
curr_len += 1
# if the whole needle string is found,
# return its start position
if curr_len == L:
return pn - L
# otherwise, backtrack
pn = pn - curr_len + 1 # 出現不匹配字母
return -1
3。Rabin Karp - 常數複雜度
先生成視窗內子串的雜湊碼,然後再跟 needle 字串的雜湊碼做比較。
def strStr(self, haystack: str, needle: str) -> int:
L, n = len(needle), len(haystack)
if L > n:
return -1
# base value for the rolling hash function
a = 26
# modulus value for the rolling hash function to avoid overflow
modulus = 2**31
# lambda-function to convert character to integer
h_to_int = lambda i : ord(haystack[i]) - ord('a')
needle_to_int = lambda i : ord(needle[i]) - ord('a')
# compute the hash of strings haystack[:L], needle[:L]
h = ref_h = 0
for i in range(L):
h = (h * a + h_to_int(i)) % modulus
ref_h = (ref_h * a + needle_to_int(i)) % modulus
if h == ref_h:
return 0
# const value to be used often : a**L % modulus
aL = pow(a, L, modulus)
for start in range(1, n - L + 1):
# compute rolling hash in O(1) time
h = (h * a - h_to_int(start - 1) * aL + h_to_int(start + L - 1)) % modulus
if h == ref_h:
return start
h 0 = c 0 a L − 1 + c 1 a L − 2 + . . . + c L − 1 a 2 + c L a 1 h_0=c_0a^{L-1}+c_1a^{L-2}+...+c_{L-1}a^2+c_La^1 h0=c0aL−1+c1aL−2+...+cL−1a2+cLa1
h 1 = h 0 a − c 0 a L + c L + 1 h_1=h_0a-c_0a^L+c_{L+1} h1=h0a−c0aL+cL+1
6. 外觀數列
1. 雙指標實現
def countAndSay(self, n: int) -> str:
pre = ''
cur = '1'
# 從第 2 項開始
for _ in range(1, n):
# 這裡注意要將 cur 賦值給 pre
# 因為當前項,就是下一項的前一項。有點繞,嘗試理解下
pre = cur
# 這裡 cur 初始化為空,重新拼接
cur = ''
# 定義雙指標 start,end
start = 0
end = 0
# 開始遍歷前一項,開始描述
while end < len(pre):
# 統計重複元素的次數,出現不同元素時,停止
# 記錄出現的次數,
while end < len(pre) and pre[start] == pre[end]:
end += 1
# 元素出現次數與元素進行拼接
cur += str(end-start) + pre[start]
# 這裡更新 start,開始記錄下一個元素
start = end
return cur
2.正則表示式(實現):提取元素
def countAndSay(self, n: int) -> str:
if n == 1:
return "1"
s = self.countAndSay(n-1)
# 字串 (\d)\1* 可以用來匹配結果。這裡用來提取連在一塊的元素, 如 '111221',提取出的元素是 res = ['111', '22', '1']。
# (\d)\1*解釋:
# \1 是為了引用前面的 \d,表明 \1 是與 \d 匹配到相同的數字。
# 只有 \d 添加了(),才能被引用,在正則裡面稱之為捕獲組。
# * 表示重複匹配前面字元0次或多次。所以可以匹配的到像 1 、111、11 這樣連在一起並相同的數字。
pattern = re.compile(r'(\d)\1*')
res = [_.group() for _ in pattern.finditer(s)]
return ''.join(str(len(c)) + c[0] for c in res)
3.正則表示式(實現):元素替換
def countAndSay(self, n: int) -> str:
res = "1"
pattern= re.compile(r'(\d)\1*')
for _ in range(n-1):
res = pattern.sub(lambda x: str(len(x.group())) + x.group(1), res) # 核心程式碼,實現元素的替換
return res
7. 最長公共字首
1. 縱向掃描
def longestCommonPrefix(self, strs: List[str]) -> str:
if not strs:
return ""
length, count = len(strs[0]), len(strs)
for i in range(length):
c = strs[0][i]
if any(i == len(strs[j]) or strs[j][i] != c for j in range(1, count)):
return strs[0][:i]
return strs[0]
- 表示重複匹配前面字元0次或多次。所以可以匹配的到像 1 、111、11 這樣連在一起並相同的數字。
pattern = re.compile(r’(\d)\1*’)
res = [_.group() for _ in pattern.finditer(s)]
return ‘’.join(str(len©) + c[0] for c in res)
#### 3.正則表示式(實現):元素替換
```python
def countAndSay(self, n: int) -> str:
res = "1"
pattern= re.compile(r'(\d)\1*')
for _ in range(n-1):
res = pattern.sub(lambda x: str(len(x.group())) + x.group(1), res) # 核心程式碼,實現元素的替換
return res
7. 最長公共字首
1. 縱向掃描
def longestCommonPrefix(self, strs: List[str]) -> str:
if not strs:
return ""
length, count = len(strs[0]), len(strs)
for i in range(length):
c = strs[0][i]
if any(i == len(strs[j]) or strs[j][i] != c for j in range(1, count)):
return strs[0][:i]
return strs[0]