題目:
前三題都可以用集合和來求。【sum(set(nums))*N-sum(nums)】
4、數組中別的數都出現2次,只有兩個數只出現1次,找出這兩個數。
5.1、數組中1到1000遍歷(順序打亂),有兩個數缺失,求出這兩個數。
【n個正整數,每個數值不超過n-1,有一個重復的數,找出那個重復的數】
7、n個[0,n)的數,求每個數出現次數(不能開辟額外空間)【排序】
8、數組去重
10、在一個有序數組中查找一個數,怎么最快【二分查找】
12、奇數下標都是奇數或偶數下標都是偶數【even、odd變量】
13、在數組中找到一個局部最小的位置
16、求最短通路值【寬度優先】
解題:
1、(3)第1題相當於第3題N為偶數。解法思路:數組為nums,則數組中所有數異或,最后的值為所求。
def findNum(nums):
if not nums:
return -1
i=1 res=nums[0]
while i<len(nums): res^=nums[i]
i += 1 return res
2、(3)第2題相當於第3題N為奇數。解法思路:所有數的二進制位相加 mod 3 ,所有模3值不為0的位匯總的數即為所求。
def findNum(nums): if not nums: return -1 i=0 res=0 bitSum=[0]*4 #這里是4是因為若設32位,但無論怎么算都只需4位 while i<4: j=0 while j<len(nums): bitSum[i]+=(nums[j]>>i)&1
j+=1 res|=(bitSum[i]%3)<<i i+=1 return res
更簡單的解法:一個數用來表示出現一次的位,也就是說這個數的為1的位就表示這個位上面的數出現過了一次,比如0x10001,就表示bit[0]和bit[4]就是出現過了一次的位。然后再用一個數表示出現過了兩次的位,再用一個數表示出現過了3次的位。只要這個位出現過了3次,我就把這個位拿清除掉,這樣剩余的最后出現過一次的位的這個數就是我們要找的數了。
def findNum(nums): ones=twos=threes=0 for i in range(len(nums)): twos |= ones & A[i] ones ^= A[i] threes = ones & twos ones &= ~threes twos &= ~threes return ones
第3題:所有數的二進制位相加 mod n ,所有模n值不為0的位匯總的數即為所求。
def findNum(nums,n): if not nums: return -1 arr = [0] * 32 for i in range(len(nums)): binnum = nums[i] j = 0 #將十進制轉成二進制 while binnum: binnum = binnum >> j arr[j] += binnum & 1 j += 1 res = 0 #記錄%n有余數的數 for i in range(len(arr)): tmp = (arr[i] % n)&1 res += tmp << i return int(res) nums = [1000,1000,1000,2,3,4,4,4,3,3] n = 3 print(findNum(nums,n))
|
import math def findNum(nums,n): if not nums: return -1 arr = [0] * 32 for i in range(len(nums)): binnum = nums[i] j = 0 while binnum: binnum , remainder = divmod(binnum,2) #divmod將商和余數結合起來 if remainder == 1: arr[j] += 1 j += 1 res = 0 for i in range(len(arr)): if arr[i] % n == 1: res += math.pow(2,i) return int(res) nums = [5,5,5,2,3,4,4,4,3,3] n = 3 print(findNum(nums,n))
|
第4題:先將數組所有數字異或,最后結果就是兩個出現一次的數字相互異或的結果,再將這兩個數分別分在兩個數組中進行異或。如鏈接:
https://blog.csdn.net/morewindows/article/details/8214003
設題目中這兩個只出現1次的數字分別為A和B,如果能將A,B分開到二個數組中,那顯然符合“異或”解法的關鍵點了。因此這個題目的關鍵點就是將A,B分開到二個數組中。由於A,B肯定是不相等的,因此在二進制上必定有一位是不同的。根據這一位是0還是1可以將A,B分開到A組和B組。而這個數組中其它數字要么就屬於A組,要么就屬於B組。再對A組和B組分別執行“異或”解法就可以得到A,B了。而要判斷A,B在哪一位上不相同,只要根據A異或B的結果就可以知道了,這個結果在二進制上為1的位都說明A,B在這一位上是不相同的。
比如int a[] = {1, 1, 3, 5, 2, 2}
整個數組異或的結果為3^5即 0x0011 ^ 0x0101 = 0x0110
對0x0110,第1位(由低向高,從0開始)就是1。因此整個數組根據第1位是0還是1分成兩組。
a[0] =1 0x0001 第一組
a[1] =1 0x0001 第一組
a[2] =3 0x0011 第二組
a[3] =5 0x0101 第一組
a[4] =2 0x0010 第二組
a[5] =2 0x0010 第二組
第一組有{1, 1, 5},第二組有{3, 2, 3},明顯對這二組分別執行“異或”解法就可以得到5和3了。
def findNum(nums): if not nums: return -1 temp=0 ##先所有數異或得到A^B的值,A和B為只出現一次的數 for item in nums: temp^=item #找到A和B不同的第一位,A^B的第一個1位。該位設為j位 j=0 while j<32: if (temp>>j)&1==1: break j+=1 A=[] B=[] #將A、B分到按第j位不同分到不同組A和B for num in nums: if (num>>j)&1==1: A.append(num) else: B.append(num) ##分到的兩個組全部都異或 resA=0 resB=0 for itemA in A: resA^=itemA for itemB in B: resB^=itemB return resA,resB nums=[1,1,3,5,2,2] print(findNum(nums))
第5.1題:數組中1到1000遍歷(順序打亂),有兩個數缺失,求出這兩個數。
- 思路1:list(set(range(1,1001))-set(nums)),即兩個集合相減
- 思路2:排序,然后比較【時間O(nlogn),空間O(1)】
- 思路3:采用字典,將數組的元素存到key中,從1-n查詢時若有哪兩個數缺失則返回。【空間換時間】
第5.2題:數組中未出現的最小正整數【左右變量遍歷】
給定一個無序整型數組arr,找到數組中未出現的最小正整數。
舉例:
arr = 【-1,2,3,4】。返回1
arr = 【1,2,3,4】。返回5.
思路:時間O(N),空間O(1)
分析:
假如arr有序,則:
(1) arr為整數1,2,3…N,N+2,N+3……的一個隨機排列,那個未出現的最小正整數就是N+1。
【設置left變量表示已經有序的N個數 arr [0:N-1] 】
(2) arr中有小於1或者大於N或者重復的數出現(我們稱之為“不合法”的數),則未出現的最小正整數一定在1到N中間(因為數組一共只有N個數,如果出現不合法的數,則出現的1到N之間的數的個數一定小於N,故一定有沒有出現的數)。
【設置right變量,初始N+2, 表示還可以有序的arr [ N:N+2 ]】
【比如:發現 arr[N](即N+2)合法但位置不理想,將N+2換到它理想的位置,然后將N+3換到該位置 arr[ N ],繼續比較N+3是不是合法且理想,發現並不合法,合法區間right-1變成 arr[ N:N+1 ] 】
做法:
(1) 先設置兩個變量L,R。初始值:L=0,R=len(arr)
-
- L表示已經從1到L已經出現(左邊界),L的初值為0。
- 如果一個數字過大(不合法),就會被扔掉,用R表示這個右邊界,即大於R的數會被扔掉。R的初值為N,表示從1到R的元素都不會被扔掉,大於R的就會被扔掉。但是這個R的值是變化的,如果L+1到R中有一個元素不合法,那么R--,因為最多只能放下R-1個合法數。
也就是說,1到L上的數已經出現,[L+1,R]區間上的數未出現但可能會出現。
(2)遍歷數組:
1》當 L 的位置值等於L+1,表示得到想要的,故L ++
2》三種不合法情況:都需R--
-
- 小於左邊的數:當 L 的位置值< L 時,表示 L 位置的值已經存在,所以這個數組不會存在到 R 的值了 so R--
- 大於右邊的數:當 L 的位置值> R 時,表示 L 的位置已經超過 R ,所以數組不會存在到 R 的值了, so R--
- 左邊區域重復的數:當 L 的位置值和 (L 值的位置-1)的的位置值相等時,所有數組不會存在到 R 的值了,so R-- 【即L位置的值已經存在左區域已經出現的數中了,兩個一樣的值,比如 [1,2,3,2,4] ,第二個2已經重復了[1,2,3]中的2了 】
3》合法但位置不理想:else,將 arr【L】 交換到理想位置,理想位置的值換到 L 位置來,繼續判斷 L 位置的值。
代碼:
def missNum(arr): if not arr: return 1 left , right = 0 , len(arr) while left < right: if arr[left] == left + 1: left += 1 elif arr[left] <= left or arr[left] > right or arr[left] == arr[arr[left]-1]: right -= 1 arr[left] = arr[right] #right-=1后合法區域不包括arr【right】了,將arr[right]換到left位置繼續判斷 else: #arr[left]理想位置為arr[left]-1,將arr[arr[left]-1]換到left位置繼續判斷 arr[left] , arr[arr[left] - 1] = arr[arr[left]-1] , arr[left] return left + 1 arr = [1,3,-2,4,5] missNum(arr)
第6題:數組中別的數都出現1次,只有一個數出現2次,找出該數。
- 思路1:【空間換時間】,采用字典,將數組的元素存到key中,value對應數量,若value=2則返回該key值。
- 思路2:【累加求和法,可能溢出】,sum(nums) - sum([1……n])即所求
- 思路3:【異或法】nums數與 [1……n] n個數異或,異或結果為所求
- 思路4:【數據映射法】全部變為負數,最后一個為正數的即所求。【如arr = [2,3,4,3,1],令arr[0] = -2,指向下標為2,即arr[2] = -4, arr[4] = -1, arr[1] = -3,arr[3] = -3,arr[3] = 3變為正數,即為所求。】如果元素不合理時,存在數組越界問題
- 思路5:【環形相遇法】將數組連成鏈表,如果有重復值則會形成環形鏈表,只需要求入環點即可。
- 思路6:排序,找相鄰的數。
9、在數組中找到出現次數大於N/K的數
思路:找到出現最多的那個數且次數超過一半。方法是遍歷過程中成對刪除不同的數,一個數如果出現次數超過一半,剩下的數必是該數,否則可能是也可能不是。如:
【4,3,7,4,4,4,5】,設置一個cand和time變量。開始,
開始,cand = 4,time= 1,
遇到下一個數3,兩者不同,time - 1 = 0
遇到下一個數7,因為time=0,所以cand重置為=7,time=1
遇到下一個數4,cand和4不同,所以time - 1 =0
遇到下一個數4,time = 0,cand重置為4,time= 1
遇到下一個數4,cand和4相同,time + 1 = 2
遇到下一個數5,cand和5不同,time - 1 = 1
故:剩下的數cand為4。
然后從頭開始遍歷,判斷4是否出現超過一半【因為如果沒超過一半也可能cand為4,如 [ 4,3,4,5]】
代碼:
def printHalfMajor(arr): if arr == None or len(arr) == 0: print("No such number!") return False cand = 0 time = 0 for i in range(len(arr)): if time == 0: cand = arr[i] time += 1 elif cand == arr[i]: time += 1 else: time -= 1 time = 0 for i in range(len(arr)): if arr[i] == cand: time += 1 if time > len(arr)//2: return True else: return False arr=[4,3,4,3,4,6,5,4] printHalfMajor(arr)
進階問題思路:一次刪掉K個不同的數,多次刪除,直到剩下的數種類不足K。若一個數出現次數超過N/K,該數最后一定會剩下。
一個map記錄K個不同的數,一旦map大小==k-1,且下一個arr[i]不在map中,就可以開始刪除map中K個不同值。
代碼:
def printHalfMajor(arr,k): if arr == None or len(arr) <= k: print("No such number!") return False #重點:每次刪除K種不同的值【采用字典存儲K種不同的值】 map = {} for i in range(len(arr)): if arr[i] in map: map[arr[i]] += 1 else: if len(map) == k-1: for key in list(map): map[key] -= 1 if map[key] == 0: del map[key] else: map[arr[i]] = 1 #查詢map剩余的數在原數組中的數量,【如[1,1,3,3,4,4,4,4,2,2]中最后map會剩下{4:2,2:2}】 time = 0 flag = False for key in map: for i in range(len(arr)): if arr[i] == key: time += 1 if time > len(arr)//k: flag = True print(key) time = 0 return flag arr=[4,3,4,3,4,6,5,4] K = 3 printHalfMajor(arr,K)
10、在行列都排好序的矩陣中找數
思路:
11、奇數下標都是奇數或偶數下標都是偶數
給定一個長度不小於2的數組arr,實現一個函數調整arr,要么讓所有的偶數下標都是偶數,要么讓所有的奇數下標都是奇數。
思路:時間O(N),空間O(1)
設置三個變量,even下標、odd下標、end下標
只要arr【end】 是偶數,就將arr【end】和arr【even】交換,even+=2.
同樣,arr【end】 是奇數,就將arr【end】和arr【odd】交換,odd+=2.
代碼:
def test(arr): if not arr or len(arr) <= 1: return arr even = 0 odd = 1 end = len(arr) - 1 while even < len(arr) and odd < len(arr): if arr[end] % 2 == 0: arr[end] , arr[even] = arr[even] , arr[end] even += 2 elif arr[end] % 2 == 1: arr[end] , arr[odd] = arr[odd] , arr[end] odd += 2 return arr arr = [1,8,3,2,4,6] test(arr)
12、邊界都是1的最大正方形大小
給定一個N*M的矩陣matrix, 在這個矩陣中, 只有0和1兩種值, 返回邊框全是1的最大正方
形的邊長長度。
例如:
0 1 1 1 1
0 1 0 0 1
0 1 0 0 1
0 1 1 1 1
0 1 0 1 1
其中, 邊框全是1的最大正方形的大小為4*4, 所以返回4
思路1:時間O(N4),枚舉所有正方形,判斷邊框是否都為1
1.矩陣中一共有N*N個位置。O(N2)
2.對每一個位置都可以成為邊長為N~1的正方形左上角。比如,對於(0,0)位置,依次檢查是否是邊長為5的正方形的左上角,然后檢查邊長為4、3等。O(N)
3.如何檢查一個位置是否可以成為邊長為N的正方形的左上角?遍歷這個邊長為N的正方形邊界看是否只由1組成,也就是走過四個邊的長度(4N)。O(N)
總的時間復雜度:O(N2)*O(N)*O(N)=O(N4)
思路2:時間復雜度為O(N3),以空間換時間的做法。
采用預處理矩陣的方法,同樣也是枚舉所有的正方形,但是判斷該正方形是否符合規則是,是O(1)的時間復雜度,所以當M=N時,這是O(N^3)時間復雜度。
用與原矩陣同樣大小的兩個矩陣,一個為right,一個為down,
right[i][j]的值表示從位置(i,j)向右出發有多少個連續的1。
down[i][j]的值表示從位置(i,j)向下出發有多少個連續的1。
right和down的計算過程:從右到左,從下到上依次填好兩個矩陣。
-
- 從矩陣的右下角(n-1,n-1)位置開始計算,如果matrix[n-1][n-1]=1,那么,right[n-1][n-1]=1,down[n-1][n-1]=1,否則都等於0。
- 從右下角向上計算,即在matrix最后一列上計算,位置就表示為(i,n-1)。對right來說,最后一列的右邊沒有內容,所以,如果matrix[i][n-1]=1, right[i][n-1]=1並且down[i][n-1]=down[i+1][n-1]+1,否則right[i][n-1]=0並且down[i][n-1]=0。
- 從右下角向左計算,即在matrix最后一行上計算,位置就表示為(n-1,j)。對down來說,最后一行的下邊沒有內容,所以,如果matrix[n-1][j]=1, down[n-1][j]=1並且right[n-1][j]=down[n-1][j+1]+1,否則right[n-1][j]=0並且down[n-1][j]=0。
- 剩下的位置都是既有右,又有下,假設位置(i,j):
- if matrix[i][j]=1, then right[i][j+1]=right[i][j]+1,down[i][j]=down[i+1][j]+1.
- if matrix[i][j]=0,then right[i][j]=0,down[i][j]=0.
13、不包含本位置值的累乘數組
給定一個整型數組arr,返回不包含本位置值的累乘數組。
例如,arr = [2, 3, 4, 1],返回[12, 8, 24, 6],即除自己以外,其他位置上的累乘。
【要求】
-
- 時間復雜度O(N)
- 除需要返回的結果數組外,額外空間復雜度O(1)。
不用除法思路:
分別使用輔助兩個數組left和right,其中left表示數組從左到右的累乘結果(即left[i] = arr[0…i]的累乘);相反,right表示數組從右到左的累乘結果。那么對於結果數組res,res[i] = left[i-1] * right[i+1]。
實際上,並不需要額外聲明兩個輔助數組。可以復用結果數組res,即先將res當輔助數組用,再把res調整為結果數組即可。具體實現見如下代碼:
def product2(arr): if arr == None or len(arr) < 2: return res = [0 for i in range(len(arr))] res[0] = arr[0] for i in range(1, len(res)): res[i] = res[i-1] * arr[i] tmp = 1 for i in range(len(arr)-1, 0, -1): res[i] = res[i-1] * tmp tmp *= arr[i] res[0] = tmp return res arr = [2,3,1,4] product2(arr)
17、二維數組的轉置
題目:
代碼:
def merge(arr,n): if not arr: return arr res = [] num = [0] * len(arr) count = len(arr) while count: for i in range(len(arr)): m = len(arr[i]) if num[i] + n <= m: res.extend(arr[i][num[i]:num[i]+n]) num[i] += n elif num[i]+n > m and arr[i][num[i]:]: res.extend(arr[i][num[i]:]) num[i] += n count -= 1 return res arr = [[2,5,6,7,9,5,7],[1,7,4,3,4]] n = 3 merge(arr,n)
長度一樣的轉置:【采用解壓*】list(zip(*matrix))
18. 股票最大利潤
思路1:用后一天減去前天得到隔天的利潤,然后將該題目轉化為求最大子序列和的問題。
思路2:當天的價格減去今天以前的股票最小值
代碼:
arr = [7,1,4,3,1] min_P = 2**32 max_P = -min_P if not arr: print('0') else: for i in range(len(arr)): min_P = min(min_P,arr[i]) max_P = max(max_P,arr[i]-min_P) print(max_P)