AcWing 13. 找出數組中重復的數字
給定一個長度為 n 的整數數組 nums,數組中所有的數字都在 0∼n−1 的范圍內。
數組中某些數字是重復的,但不知道有幾個數字重復了,也不知道每個數字重復了幾次。
請找出數組中任意一個重復的數字。
注意:如果某些數字不在 0∼n−1 的范圍內,或數組中不包含重復數字,則返回 -1;
樣例
給定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。
返回 2 或 3。
from collections import defaultdict
class Solution(object):
def duplicateInArray(self, nums):
"""
:type nums: List[int]
:rtype int
"""
mp = defaultdict(int)
res = -1
for it in nums:
if it >= len(nums) or it <0: #遺漏
return -1
if mp[it] != 0:
res = it
mp[it] += 1
return res # 遺漏
(數組遍歷) O(n)
首先遍歷一遍數組,如果存在某個數不在0到n-1的范圍內,則返回-1。
下面的算法的主要思想是把每個數放到對應的位置上,即讓 nums[i] = i。
從前往后遍歷數組中的所有數,假設當前遍歷到的數是 nums[i]=x,那么:
如果x != i && nums[x] == x,則說明 x出現了多次,直接返回 x即可;
如果nums[x] != x,那我們就把 x 交換到正確的位置上,即 swap(nums[x], nums[i]),交換完之后如果nums[i] != i,則重復進行該操作。由於每次交換都會將一個數放在正確的位置上,所以swap操作最多會進行 n 次,不會發生死循環。
循環結束后,如果沒有找到任何重復的數,則返回-1。
時間復雜度分析
每次swap操作都會將一個數放在正確的位置上,最后一次swap會將兩個數同時放到正確位置上,一共只有 n 個數和 n 個位置,所以swap最多會進行 n−1次。所以總時間復雜度是 O(n)。
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int n = nums.size();
for (auto x : nums)
if (x < 0 || x >= n)
return -1;
for (int i = 0; i < n; i ++ ) {
while (nums[nums[i]] != nums[i]) swap(nums[i], nums[nums[i]]);
if (nums[i] != i) return nums[i];
}
return -1;
}
};
AcWing 14. 不修改數組找出重復的數字
給定一個長度為 n+1 的數組nums,數組中所有的數均在 1∼n 的范圍內,其中 n≥1。
請找出數組中任意一個重復的數,但不能修改輸入的數組。
樣例
給定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。
返回 2 或 3。
思考題:如果只能使用 O(1) 的額外空間,該怎么做呢?
yxc大佬
(分治,抽屜原理) O(nlogn)
這道題目主要應用了抽屜原理和分治的思想。
抽屜原理:n+1 個蘋果放在 n 個抽屜里,那么至少有一個抽屜中會放兩個蘋果。
用在這個題目中就是,一共有 n+1 個數,每個數的取值范圍是1到n,所以至少會有一個數出現兩次。
然后我們采用分治的思想,將每個數的取值的區間[1, n]划分成[1, n/2]和[n/2+1, n]兩個子區間,然后分別統計兩個區間中數的個數。
注意這里的區間是指 數的取值范圍,而不是 數組下標。即這題跟題目給的數的順序無關
划分之后,左右兩個區間里一定至少存在一個區間,區間中數的個數大於區間長度。
這個可以用反證法來說明:如果兩個區間中數的個數都小於等於區間長度,那么整個區間中數的個數就小於等於n,和有n+1個數矛盾。
因此我們可以把問題划歸到左右兩個子區間中的一個,而且由於區間中數的個數大於區間長度,根據抽屜原理,在這個子區間中一定存在某個數出現了兩次。
依次類推,每次我們可以把區間長度縮小一半,直到區間長度為1時,我們就找到了答案。
復雜度分析
時間復雜度:每次會將區間長度縮小一半,一共會縮小 \(O(logn)\)次。每次統計兩個子區間中的數時需要遍歷整個數組,時間復雜度是 O(n)。所以總時間復雜度是 \(O(nlogn)\)。
空間復雜度:代碼中沒有用到額外的數組,所以額外的空間復雜度是 O(1)。
class Solution(object):
def duplicateInArray(self, nums):
"""
:type nums: List[int]
:rtype int
"""
n = len(nums)
l = 1
r = n
while l < r:
mid = (l + r) >> 1
s = 0
for it in nums:
if it >= l and it <= mid:
s += 1
# print(mid, s)
if s > mid - l + 1:
r = mid
else:
l = mid + 1
return l
AcWing 15. 二維數組中的查找
在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。
請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
樣例
輸入數組:
[ [1,2,8,9], [2,4,9,12], [4,7,10,13], [6,8,11,15] ]
如果輸入查找數值為7,則返回true,
如果輸入查找數值為5,則返回false。
yxc 大佬做法
算法
(單調性掃描) \(O(n+m)\)
核心在於發現每個子矩陣右上角的數的性質:
如下圖所示,x左邊的數都小於等於x,x下邊的數都大於等於x。
因此我們可以從整個矩陣的右上角開始枚舉,假設當前枚舉的數是 xx:
- 如果 x等於target,則說明我們找到了目標值,返回true;
- 如果 x 小於target,則 x左邊的數一定都小於target,我們可以直接排除當前一整行的數;
- 如果 x 大於target,則 x下邊的數一定都大於target,我們可以直接排序當前一整列的數;
排除一整行就是讓枚舉的點的橫坐標加一,排除一整列就是讓縱坐標減一。
當我們排除完整個矩陣后仍沒有找到目標值時,就說明目標值不存在,返回false。
時間復雜度分析
每一步會排除一行或者一列,矩陣一共有 n 行,mm 列,所以最多會進行 n+m 步。所以時間復雜度是 O(n+m)。
class Solution(object):
def searchArray(self, array, target):
"""
:type array: List[List[int]]
:type target: int
:rtype: bool
"""
if len(array) == 0 or len(array[0]) == 0:
return False
i = 0
j = len(array[0]) - 1
while i < len(array) and j >=0:
if target == array[i][j]:
return True
elif target > array[i][j]:
i += 1
elif target < array[i][j]:
j -= 1
return False
AcWing 16. 替換空格
請實現一個函數,把字符串中的每個空格替換成"%20"。
你可以假定輸入字符串的長度最大是1000。
注意輸出字符串的長度可能大於1000。
樣例
輸入:"We are happy."
輸出:"We%20are%20happy."
class Solution(object):
def replaceSpaces(self, s):
"""
:type s: str
:rtype: str
"""
return s.replace(" ","%20")
AcWing 17. 從尾到頭打印鏈表
輸入一個鏈表的頭結點,按照 從尾到頭 的順序返回節點的值。
返回的結果用數組存儲。
樣例
輸入:[2, 3, 5]
返回:[5, 3, 2]
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def printListReversingly(self, head):
"""
:type head: ListNode
:rtype: List[int]
"""
res = []
while head != None:
res.append(head.val)
head = head.next
res.reverse()
return res
AcWing 18. 重建二叉樹
輸入一棵二叉樹前序遍歷和中序遍歷的結果,請重建該二叉樹。
注意:
二叉樹中每個節點的值都互不相同;
輸入的前序遍歷和中序遍歷一定合法;
樣例
給定: 前序遍歷是:[3, 9, 20, 15, 7] 中序遍歷是:[9, 3, 15, 20, 7]
返回:[3, 9, 20, null, null, 15, 7, null, null, null, null]
返回的二叉樹如下所示:
3
/ \
9 20
/ \
15 7
算法
(遞歸) O(n)
遞歸建立整棵二叉樹:先遞歸創建左右子樹,然后創建根節點,並讓指針指向兩棵子樹。
具體步驟如下:
- 先利用前序遍歷找根節點:前序遍歷的第一個數,就是根節點的值;
- 在中序遍歷中找到根節點的位置 k,則 k左邊是左子樹的中序遍歷,右邊是右子樹的中序遍歷;
- 假設左子樹的中序遍歷的長度是 l,則在前序遍歷中,根節點后面的 l個數,是左子樹的前序遍歷,剩下的數是右子樹的前序遍歷;
- 有了左右子樹的前序遍歷和中序遍歷,我們可以先遞歸創建出左右子樹,然后再創建根節點;
時間復雜度分析
我們在初始化時,用哈希(unordered_map<int,int>)記錄每個值在中序遍歷中的位置,這樣我們在遞歸到每個節點時,在中序遍歷中查找根節點位置的操作,只需要 O(1)的時間。此時,創建每個節點需要的時間是 O(1),所以總時間復雜度是 O(n)。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def buildTree(self, preorder, inorder):
"""
:type preorder: List[int]
:type inorder: List[int]
:rtype: TreeNode
"""
if len(preorder) == 0:
return None
pos = [None for i in range(max(inorder) + 1)]
for i in range(len(inorder)):
pos[inorder[i]] = i
def build(il, ir, pl, pr):
if pl > pr:
return None
root = TreeNode(preorder[pl])
k = pos[root.val]
if il < k: root.left = build(il, k - 1, pl + 1, pl + (k - 1 - il) + 1)
if ir > k: root.right = build(k+1 , ir, pl + k - il + 1, pr)
return root
return build(0, len(preorder) - 1, 0, len(preorder) - 1)
AcWing 19. 二叉樹的下一個節點
給定一棵二叉樹的其中一個節點,請找出中序遍歷序列的下一個節點。
注意:
如果給定的節點是中序遍歷序列的最后一個,則返回空節點;
二叉樹一定不為空,且給定的節點一定不是空節點;
樣例
假定二叉樹是:[2, 1, 3, null, null, null, null], 給出的是值等於2的節點。
則應返回值等於3的節點。
解釋:該二叉樹的結構如下,2的后繼節點是3。
2
/ \
1 3
算法
(模擬) O(h)
這道題目就是讓我們求二叉樹中給定節點的后繼。
分情況討論即可,如下圖所示:
- 如果當前節點有右兒子,則右子樹中最左側的節點就是當前節點的后繼。比如F的后繼是H;
- 如果當前節點沒有右兒子,則需要沿着father域一直向上找,找到第一個是其father左兒子的節點,該節點的father就是當前節點的后繼。比如當前節點是D,則第一個滿足是其father左兒子的節點是F,則C的father就是D的后繼,即F是D的后繼。
時間復雜度分析
不論往上找還是往下找,總共遍歷的節點數都不大於樹的高度。所以時間復雜度是 O(h),其中 h 是樹的高度。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
# self.father = None
class Solution(object):
def inorderSuccessor(self, q):
"""
:type q: TreeNode
:rtype: TreeNode
"""
if q.right != None:
p = q.right
while p.left != None:
p = p.left
return p
else:
p = q
while p.father != None and p.father.right == p:
p = p.father
return p.father
AcWing 20. 用兩個棧實現隊列
請用棧實現一個隊列,支持如下四種操作:
push(x) – 將元素x插到隊尾;
pop() – 將隊首的元素彈出,並返回該元素;
peek() – 返回隊首元素;
empty() – 返回隊列是否為空;
注意:
你只能使用棧的標准操作:push to top,peek/pop from top, size 和 is empty;
如果你選擇的編程語言沒有棧的標准庫,你可以使用list或者deque等模擬棧的操作;
輸入數據保證合法,例如,在隊列為空時,不會進行pop或者peek等操作;
樣例
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // returns 1
queue.pop(); // returns 1
queue.empty(); // returns false
class MyQueue(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.sta = []
def push(self, x):
"""
Push element x to the back of queue.
:type x: int
:rtype: void
"""
self.sta.append(x)
def pop(self):
"""
Removes the element from in front of queue and returns that element.
:rtype: int
"""
return self.sta.pop(0)
def peek(self):
"""
Get the front element.
:rtype: int
"""
return self.sta[0]
def empty(self):
"""
Returns whether the queue is empty.
:rtype: bool
"""
if len(self.sta) == 0:
return True
return False
# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
AcWing 21. 斐波那契數列
輸入一個整數 n ,求斐波那契數列的第 n 項。
假定從0開始,第0項為0。(n<=39)
樣例 輸入整數 n=5
返回 5
class Solution(object):
def Fibonacci(self, n):
"""
:type n: int
:rtype: int
"""
if n == 0: return 0
a = 0
b = 1
for i in range(2, n+1):
a, b = b, a + b
return b
生成器
生成器講解
class Solution(object):
def Fibonacci(self, n):
"""
:type n: int
:rtype: int
"""
if n == 0: return 0
def f(n):
a = 0
b = 1
for i in range(n):
yield b # 生成器
a, b = b, a + b
res = 0
for it in f(n):
res = it
return res
AcWing 22. 旋轉數組的最小數字
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之為數組的旋轉。
輸入一個升序的數組的一個旋轉,輸出旋轉數組的最小元素。
例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。
數組可能包含重復項。
注意:數組內所含元素非負,若數組大小為0,請返回-1。
樣例 輸入:nums=[2,2,2,0,1]
輸出:0
class Solution:
def findMin(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if len(nums) == 0:
return -1
else:
return min(nums)
yxc 大佬
算法
(二分) O(n)
為了便於分析,我們先將數組中的數畫在二維坐標系中,橫坐標表示數組下標,縱坐標表示數值,如下所示:
圖中水平的實線段表示相同元素。
我們發現除了最后水平的一段(黑色水平那段)之外,其余部分滿足二分性質:豎直虛線左邊的數滿足 nums[i]≥nums[0]
;而豎直虛線右邊的數不滿足這個條件。
分界點就是整個數組的最小值。
所以我們先將最后水平的一段刪除即可。
另外,不要忘記處理數組完全單調的特殊情況:
當我們刪除最后水平的一段之后,如果剩下的最后一個數大於等於第一個數,則說明數組完全單調。
時間復雜度分析
二分的時間復雜度是 O(logn),刪除最后水平一段的時間復雜度最壞是 O(n),所以總時間復雜度是 O(n)。
class Solution:
def findMin(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if len(nums) == 0:
return -1
n = len(nums) - 1
while nums[n] == nums[0]:
n -= 1
l = 0
r = n
while l < r:
mid = (l + r ) >> 1
if nums[mid] >= nums[0]:
l = mid + 1
else :
r = mid
return min(nums[0], nums[l])
AcWing 23. 矩陣中的路徑
請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。
路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。
如果一條路徑經過了矩陣中的某一個格子,則之后不能再次進入這個格子。
注意:
輸入的路徑不為空;
所有出現的字符均為大寫英文字母;
樣例 matrix= [
["A","B","C","E"],
["S","F","C","S"],
["A","D","E","E"]
]str="BCCE" , return "true"
str="ASAE" , return "false"
算法
DFS \(O(n^{2} 3^k)\)
在深度優先搜索中,最重要的就是考慮好搜索順序。
我們先枚舉單詞的起點,然后依次枚舉單詞的每個字母。
過程中需要將已經使用過的字母改成一個特殊字母,以避免重復使用字符。
時間復雜度分析:單詞起點一共有 \(n^2\) 個,單詞的每個字母一共有上下左右四個方向可以選擇,但由於不能走回頭路,所以除了單詞首字母外,僅有三種選擇。所以總時間復雜度是 \(O(n^{2} 3^k)\)
class Solution(object):
def hasPath(self, matrix, string):
"""
:type matrix: List[List[str]]
:type string: str
:rtype: bool
"""
for i in range(len(matrix)):
for j in range(len(matrix[i])):
st = [[0 for j in range(len(matrix[i]))] for i in range(len(matrix))]
if self.dfs(i, j, 0, st, matrix, string) == True:
return True
return False
def dfs(self, i, j, u, st, matrix, string):
dx = [0, 0, 1, -1]
dy = [1, -1, 0, 0]
if matrix[i][j] != string[u] : return False
st[i][j] = 1
#print(i, j, matrix[i][j], u)
#print(st)
if u == len(string) - 1: return True
for t in range(4):
x = i + dx[t]
y = j + dy[t]
if x < len(matrix) and x >= 0 and y < len(matrix[x]) and y >= 0 and st[x][y] == 0 :
st[x][y] = 1
if self.dfs(x, y, u + 1, st, matrix, string) == True:
return True
st[x][y] = 0
return False